|
|
|
@ -8,6 +8,7 @@ import {
|
|
|
|
|
} from '@angular/core';
|
|
|
|
|
import { FormBuilder, FormControl, Validators } from '@angular/forms';
|
|
|
|
|
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
|
|
|
|
import { MatSnackBar } from '@angular/material/snack-bar';
|
|
|
|
|
import { UpdateAssetProfileDto } from '@ghostfolio/api/app/admin/update-asset-profile.dto';
|
|
|
|
|
import { AdminService } from '@ghostfolio/client/services/admin.service';
|
|
|
|
|
import { DataService } from '@ghostfolio/client/services/data.service';
|
|
|
|
@ -25,8 +26,8 @@ import {
|
|
|
|
|
} from '@prisma/client';
|
|
|
|
|
import { format } from 'date-fns';
|
|
|
|
|
import { parse as csvToJson } from 'papaparse';
|
|
|
|
|
import { Subject } from 'rxjs';
|
|
|
|
|
import { takeUntil } from 'rxjs/operators';
|
|
|
|
|
import { EMPTY, Subject } from 'rxjs';
|
|
|
|
|
import { catchError, takeUntil } from 'rxjs/operators';
|
|
|
|
|
|
|
|
|
|
import { AssetProfileDialogParams } from './interfaces/interfaces';
|
|
|
|
|
|
|
|
|
@ -50,6 +51,9 @@ export class AssetProfileDialog implements OnDestroy, OnInit {
|
|
|
|
|
assetClass: new FormControl<AssetClass>(undefined),
|
|
|
|
|
assetSubClass: new FormControl<AssetSubClass>(undefined),
|
|
|
|
|
comment: '',
|
|
|
|
|
historicalData: this.formBuilder.group({
|
|
|
|
|
csvString: ''
|
|
|
|
|
}),
|
|
|
|
|
name: ['', Validators.required],
|
|
|
|
|
scraperConfiguration: '',
|
|
|
|
|
symbolMapping: ''
|
|
|
|
@ -59,7 +63,6 @@ export class AssetProfileDialog implements OnDestroy, OnInit {
|
|
|
|
|
public countries: {
|
|
|
|
|
[code: string]: { name: string; value: number };
|
|
|
|
|
};
|
|
|
|
|
public historicalDataAsCsvString: string;
|
|
|
|
|
public isBenchmark = false;
|
|
|
|
|
public marketDataDetails: MarketData[] = [];
|
|
|
|
|
public sectors: {
|
|
|
|
@ -78,7 +81,8 @@ export class AssetProfileDialog implements OnDestroy, OnInit {
|
|
|
|
|
@Inject(MAT_DIALOG_DATA) public data: AssetProfileDialogParams,
|
|
|
|
|
private dataService: DataService,
|
|
|
|
|
public dialogRef: MatDialogRef<AssetProfileDialog>,
|
|
|
|
|
private formBuilder: FormBuilder
|
|
|
|
|
private formBuilder: FormBuilder,
|
|
|
|
|
private snackBar: MatSnackBar
|
|
|
|
|
) {}
|
|
|
|
|
|
|
|
|
|
public ngOnInit(): void {
|
|
|
|
@ -88,9 +92,6 @@ export class AssetProfileDialog implements OnDestroy, OnInit {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public initialize() {
|
|
|
|
|
this.historicalDataAsCsvString =
|
|
|
|
|
AssetProfileDialog.HISTORICAL_DATA_TEMPLATE;
|
|
|
|
|
|
|
|
|
|
this.adminService
|
|
|
|
|
.fetchAdminMarketDataBySymbol({
|
|
|
|
|
dataSource: this.data.dataSource,
|
|
|
|
@ -131,6 +132,9 @@ export class AssetProfileDialog implements OnDestroy, OnInit {
|
|
|
|
|
assetClass: this.assetProfile.assetClass ?? null,
|
|
|
|
|
assetSubClass: this.assetProfile.assetSubClass ?? null,
|
|
|
|
|
comment: this.assetProfile?.comment ?? '',
|
|
|
|
|
historicalData: {
|
|
|
|
|
csvString: AssetProfileDialog.HISTORICAL_DATA_TEMPLATE
|
|
|
|
|
},
|
|
|
|
|
name: this.assetProfile.name ?? this.assetProfile.symbol,
|
|
|
|
|
scraperConfiguration: JSON.stringify(
|
|
|
|
|
this.assetProfile?.scraperConfiguration ?? {}
|
|
|
|
@ -163,26 +167,46 @@ export class AssetProfileDialog implements OnDestroy, OnInit {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public onImportHistoricalData() {
|
|
|
|
|
const marketData = csvToJson(this.historicalDataAsCsvString, {
|
|
|
|
|
dynamicTyping: true,
|
|
|
|
|
header: true,
|
|
|
|
|
skipEmptyLines: true
|
|
|
|
|
}).data;
|
|
|
|
|
|
|
|
|
|
this.adminService
|
|
|
|
|
.postMarketData({
|
|
|
|
|
dataSource: this.data.dataSource,
|
|
|
|
|
marketData: {
|
|
|
|
|
marketData: marketData.map(({ date, marketPrice }) => {
|
|
|
|
|
return { marketPrice, date: parseDate(date).toISOString() };
|
|
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
symbol: this.data.symbol
|
|
|
|
|
})
|
|
|
|
|
.pipe(takeUntil(this.unsubscribeSubject))
|
|
|
|
|
.subscribe(() => {
|
|
|
|
|
this.initialize();
|
|
|
|
|
});
|
|
|
|
|
try {
|
|
|
|
|
const marketData = csvToJson(
|
|
|
|
|
this.assetProfileForm.controls['historicalData'].controls['csvString']
|
|
|
|
|
.value,
|
|
|
|
|
{
|
|
|
|
|
dynamicTyping: true,
|
|
|
|
|
header: true,
|
|
|
|
|
skipEmptyLines: true
|
|
|
|
|
}
|
|
|
|
|
).data;
|
|
|
|
|
|
|
|
|
|
this.adminService
|
|
|
|
|
.postMarketData({
|
|
|
|
|
dataSource: this.data.dataSource,
|
|
|
|
|
marketData: {
|
|
|
|
|
marketData: marketData.map(({ date, marketPrice }) => {
|
|
|
|
|
return { marketPrice, date: parseDate(date).toISOString() };
|
|
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
symbol: this.data.symbol
|
|
|
|
|
})
|
|
|
|
|
.pipe(
|
|
|
|
|
catchError(({ error, message }) => {
|
|
|
|
|
this.snackBar.open(`${error}: ${message[0]}`, undefined, {
|
|
|
|
|
duration: 3000
|
|
|
|
|
});
|
|
|
|
|
return EMPTY;
|
|
|
|
|
}),
|
|
|
|
|
takeUntil(this.unsubscribeSubject)
|
|
|
|
|
)
|
|
|
|
|
.subscribe(() => {
|
|
|
|
|
this.initialize();
|
|
|
|
|
});
|
|
|
|
|
} catch {
|
|
|
|
|
this.snackBar.open(
|
|
|
|
|
$localize`Oops! Could not parse historical data.`,
|
|
|
|
|
undefined,
|
|
|
|
|
{ duration: 3000 }
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public onMarketDataChanged(withRefresh: boolean = false) {
|
|
|
|
|