Browse Source

Support CSV file format of Yahoo Finance in activities import #2288

Rearranged date keys to prioritize trade date over date due to both provided in Yahoo CSV.
If no currency is found in CSV make API call to gather currency info for symbol.
If no fee provided, default to 0 (fee free).
If no type provided, default to 'BUY'.
Next: provide UI warnings for both above.
pull/4056/head
Brandon Wortman 9 months ago
parent
commit
f5e92a663d
  1. 50
      apps/client/src/app/services/import-activities.service.ts

50
apps/client/src/app/services/import-activities.service.ts

@ -1,14 +1,14 @@
import { CreateAccountDto } from '@ghostfolio/api/app/account/create-account.dto';
import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto';
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
import { SymbolItem } from '@ghostfolio/api/app/symbol/interfaces/symbol-item.interface';
import { parseDate as parseDateHelper } from '@ghostfolio/common/helper';
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Account, DataSource, Type as ActivityType } from '@prisma/client';
import { isFinite } from 'lodash';
import { parse as csvToJson } from 'papaparse';
import { EMPTY } from 'rxjs';
import { EMPTY, map, firstValueFrom } from 'rxjs';
import { catchError } from 'rxjs/operators';
@Injectable({
@ -19,12 +19,13 @@ export class ImportActivitiesService {
private static COMMENT_KEYS = ['comment', 'note'];
private static CURRENCY_KEYS = ['ccy', 'currency', 'currencyprimary'];
private static DATA_SOURCE_KEYS = ['datasource'];
private static DATE_KEYS = ['date', 'tradedate'];
private static DATE_KEYS = ['trade date', 'tradedate', 'date'];
private static FEE_KEYS = ['commission', 'fee', 'ibcommission'];
private static QUANTITY_KEYS = ['qty', 'quantity', 'shares', 'units'];
private static SYMBOL_KEYS = ['code', 'symbol', 'ticker'];
private static TYPE_KEYS = ['action', 'buy/sell', 'type'];
private static UNIT_PRICE_KEYS = [
'purchase price',
'price',
'tradeprice',
'unitprice',
@ -55,7 +56,7 @@ export class ImportActivitiesService {
activities.push({
accountId: this.parseAccount({ item, userAccounts }),
comment: this.parseComment({ item }),
currency: this.parseCurrency({ content, index, item }),
currency: await this.parseCurrency({ content, index, item }),
dataSource: this.parseDataSource({ item }),
date: this.parseDate({ content, index, item }),
fee: this.parseFee({ content, index, item }),
@ -191,7 +192,7 @@ export class ImportActivitiesService {
return undefined;
}
private parseCurrency({
private async parseCurrency({
content,
index,
item
@ -202,16 +203,39 @@ export class ImportActivitiesService {
}) {
item = this.lowercaseKeys(item);
//Attempt to find currency in the CSV
for (const key of ImportActivitiesService.CURRENCY_KEYS) {
if (item[key]) {
return item[key];
}
}
// If no currency in CSV, make an API request to get symbol data
const symbol = this.parseSymbol({ content, index, item });
const dataSource = this.parseDataSource({ item }) ?? DataSource.YAHOO;
return firstValueFrom(this.http
.get<SymbolItem>(`/api/v1/symbol/${dataSource}/${symbol}`)
.pipe(
map((response) => {
if (response?.currency) {
console.warn(`activities.${index}.currency was not provided, using ${response.currency} from symbol data`);
return response.currency;
}
throw {
activities: content,
message: `activities.${index}.currency is not valid`
};
}),
catchError((error) => {
console.warn("Failed to fetch currency from symbol service.", error);
throw {
activities: content,
message: `activities.${index}.currency is not valid`
};
})
)
);
}
private parseDataSource({ item }: { item: any }) {
@ -252,7 +276,7 @@ export class ImportActivitiesService {
}
private parseFee({
content,
// content,
index,
item
}: {
@ -268,10 +292,8 @@ export class ImportActivitiesService {
}
}
throw {
activities: content,
message: `activities.${index}.fee is not valid`
};
console.warn(`activities.${index}.fee was not provided, defaulting to 0`);
return 0;
}
private parseQuantity({
@ -321,7 +343,7 @@ export class ImportActivitiesService {
}
private parseType({
content,
//content,
index,
item
}: {
@ -354,10 +376,8 @@ export class ImportActivitiesService {
}
}
throw {
activities: content,
message: `activities.${index}.type is not valid`
};
console.warn(`activities.${index}.type was not provided, defaulting to BUY`);
return 'BUY';
}
private parseUnitPrice({

Loading…
Cancel
Save