Browse Source

Combine symbol with data source in get() of data provider service

pull/374/head
Thomas 4 years ago
parent
commit
adf98bfcb3
  1. 4
      apps/api/src/app/order/order.service.ts
  2. 4
      apps/api/src/app/portfolio/current-rate.service.spec.ts
  3. 26
      apps/api/src/app/portfolio/current-rate.service.ts
  4. 3
      apps/api/src/app/portfolio/interfaces/get-values-params.interface.ts
  5. 3
      apps/api/src/app/portfolio/interfaces/portfolio-order.interface.ts
  6. 3
      apps/api/src/app/portfolio/interfaces/transaction-point-symbol.interface.ts
  7. 132
      apps/api/src/app/portfolio/portfolio-calculator.spec.ts
  8. 28
      apps/api/src/app/portfolio/portfolio-calculator.ts
  9. 22
      apps/api/src/app/portfolio/portfolio.service.ts
  10. 7
      apps/api/src/app/symbol/symbol.controller.ts
  11. 19
      apps/api/src/app/symbol/symbol.service.ts
  12. 21
      apps/api/src/services/data-gathering.service.ts
  13. 77
      apps/api/src/services/data-provider/data-provider.service.ts
  14. 4
      apps/api/src/services/exchange-rate-data.service.ts
  15. 2
      apps/client/src/app/pages/home/home-page.component.ts
  16. 3
      libs/common/src/lib/interfaces/timeline-position.interface.ts

4
apps/api/src/app/order/order.service.ts

@ -56,7 +56,9 @@ export class OrderService {
]);
}
this.dataGatheringService.gatherProfileData([data.symbol]);
this.dataGatheringService.gatherProfileData([
{ dataSource: data.dataSource, symbol: data.symbol }
]);
await this.cacheService.flush();

4
apps/api/src/app/portfolio/current-rate.service.spec.ts

@ -1,6 +1,6 @@
import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data.service';
import { Currency, MarketData } from '@prisma/client';
import { Currency, DataSource, MarketData } from '@prisma/client';
import { CurrentRateService } from './current-rate.service';
import { MarketDataService } from './market-data.service';
@ -106,11 +106,11 @@ describe('CurrentRateService', () => {
expect(
await currentRateService.getValues({
currencies: { AMZN: Currency.USD },
dataGatheringItems: [{ dataSource: DataSource.YAHOO, symbol: 'AMZN' }],
dateQuery: {
lt: new Date(Date.UTC(2020, 0, 2, 0, 0, 0)),
gte: new Date(Date.UTC(2020, 0, 1, 0, 0, 0))
},
symbols: ['AMZN'],
userCurrency: Currency.CHF
})
).toMatchObject([

26
apps/api/src/app/portfolio/current-rate.service.ts

@ -2,6 +2,7 @@ import { DataProviderService } from '@ghostfolio/api/services/data-provider/data
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data.service';
import { resetHours } from '@ghostfolio/common/helper';
import { Injectable } from '@nestjs/common';
import { DataSource } from '@prisma/client';
import { isBefore, isToday } from 'date-fns';
import { flatten } from 'lodash';
@ -25,7 +26,9 @@ export class CurrentRateService {
userCurrency
}: GetValueParams): Promise<GetValueObject> {
if (isToday(date)) {
const dataProviderResult = await this.dataProviderService.get([symbol]);
const dataProviderResult = await this.dataProviderService.get([
{ symbol, dataSource: DataSource.YAHOO }
]);
return {
date: resetHours(date),
marketPrice: dataProviderResult?.[symbol]?.marketPrice ?? 0,
@ -55,8 +58,8 @@ export class CurrentRateService {
public async getValues({
currencies,
dataGatheringItems,
dateQuery,
symbols,
userCurrency
}: GetValuesParams): Promise<GetValueObject[]> {
const includeToday =
@ -75,17 +78,20 @@ export class CurrentRateService {
if (includeToday) {
const today = resetHours(new Date());
promises.push(
this.dataProviderService.get(symbols).then((dataResultProvider) => {
this.dataProviderService
.get(dataGatheringItems)
.then((dataResultProvider) => {
const result = [];
for (const symbol of symbols) {
for (const dataGatheringItem of dataGatheringItems) {
result.push({
symbol,
date: today,
marketPrice: this.exchangeRateDataService.toCurrency(
dataResultProvider?.[symbol]?.marketPrice ?? 0,
dataResultProvider?.[symbol]?.currency,
dataResultProvider?.[dataGatheringItem.symbol]?.marketPrice ??
0,
dataResultProvider?.[dataGatheringItem.symbol]?.currency,
userCurrency
)
),
symbol: dataGatheringItem.symbol
});
}
return result;
@ -93,6 +99,10 @@ export class CurrentRateService {
);
}
const symbols = dataGatheringItems.map((dataGatheringItem) => {
return dataGatheringItem.symbol;
});
promises.push(
this.marketDataService
.getRange({

3
apps/api/src/app/portfolio/interfaces/get-values-params.interface.ts

@ -1,10 +1,11 @@
import { IDataGatheringItem } from '@ghostfolio/api/services/interfaces/interfaces';
import { Currency } from '@prisma/client';
import { DateQuery } from './date-query.interface';
export interface GetValuesParams {
currencies: { [symbol: string]: Currency };
dataGatheringItems: IDataGatheringItem[];
dateQuery: DateQuery;
symbols: string[];
userCurrency: Currency;
}

3
apps/api/src/app/portfolio/interfaces/portfolio-order.interface.ts

@ -1,10 +1,11 @@
import { OrderType } from '@ghostfolio/api/models/order-type';
import { Currency } from '@prisma/client';
import { Currency, DataSource } from '@prisma/client';
import Big from 'big.js';
export interface PortfolioOrder {
currency: Currency;
date: string;
dataSource: DataSource;
fee: Big;
name: string;
quantity: Big;

3
apps/api/src/app/portfolio/interfaces/transaction-point-symbol.interface.ts

@ -1,8 +1,9 @@
import { Currency } from '@prisma/client';
import { Currency, DataSource } from '@prisma/client';
import Big from 'big.js';
export interface TransactionPointSymbol {
currency: Currency;
dataSource: DataSource;
fee: Big;
firstBuyDate: string;
investment: Big;

132
apps/api/src/app/portfolio/portfolio-calculator.spec.ts

@ -1,7 +1,7 @@
import { PortfolioService } from '@ghostfolio/api/app/portfolio/portfolio.service';
import { OrderType } from '@ghostfolio/api/models/order-type';
import { parseDate, resetHours } from '@ghostfolio/common/helper';
import { Currency } from '@prisma/client';
import { Currency, DataSource } from '@prisma/client';
import Big from 'big.js';
import {
addDays,
@ -85,7 +85,7 @@ jest.mock('./current-rate.service', () => {
getValues: ({
currencies,
dateQuery,
symbols,
dataGatheringItems,
userCurrency
}: GetValuesParams) => {
const result = [];
@ -95,21 +95,23 @@ jest.mock('./current-rate.service', () => {
isBefore(date, endOfDay(dateQuery.lt));
date = addDays(date, 1)
) {
for (const symbol of symbols) {
for (const dataGatheringItem of dataGatheringItems) {
result.push({
date,
symbol,
marketPrice: mockGetValue(symbol, date).marketPrice
marketPrice: mockGetValue(dataGatheringItem.symbol, date)
.marketPrice,
symbol: dataGatheringItem.symbol
});
}
}
} else {
for (const date of dateQuery.in) {
for (const symbol of symbols) {
for (const dataGatheringItem of dataGatheringItems) {
result.push({
date,
symbol,
marketPrice: mockGetValue(symbol, date).marketPrice
marketPrice: mockGetValue(dataGatheringItem.symbol, date)
.marketPrice,
symbol: dataGatheringItem.symbol
});
}
}
@ -148,7 +150,7 @@ describe('PortfolioCalculator', () => {
currentRateService,
Currency.USD
);
const orders = [
const orders: PortfolioOrder[] = [
{
date: '2019-02-01',
name: 'Vanguard Total Stock Market Index Fund ETF Shares',
@ -157,6 +159,7 @@ describe('PortfolioCalculator', () => {
type: OrderType.Buy,
unitPrice: new Big('144.38'),
currency: Currency.USD,
dataSource: DataSource.YAHOO,
fee: new Big('5')
},
{
@ -167,6 +170,7 @@ describe('PortfolioCalculator', () => {
type: OrderType.Buy,
unitPrice: new Big('147.99'),
currency: Currency.USD,
dataSource: DataSource.YAHOO,
fee: new Big('10')
},
{
@ -177,6 +181,7 @@ describe('PortfolioCalculator', () => {
type: OrderType.Sell,
unitPrice: new Big('151.41'),
currency: Currency.USD,
dataSource: DataSource.YAHOO,
fee: new Big('5')
}
];
@ -189,6 +194,7 @@ describe('PortfolioCalculator', () => {
date: '2019-02-01',
items: [
{
dataSource: DataSource.YAHOO,
quantity: new Big('10'),
symbol: 'VTI',
investment: new Big('1443.8'),
@ -203,6 +209,7 @@ describe('PortfolioCalculator', () => {
date: '2019-08-03',
items: [
{
dataSource: DataSource.YAHOO,
quantity: new Big('20'),
symbol: 'VTI',
investment: new Big('2923.7'),
@ -217,6 +224,7 @@ describe('PortfolioCalculator', () => {
date: '2020-02-02',
items: [
{
dataSource: DataSource.YAHOO,
quantity: new Big('5'),
symbol: 'VTI',
investment: new Big('652.55'),
@ -235,7 +243,7 @@ describe('PortfolioCalculator', () => {
currentRateService,
Currency.USD
);
const orders = [
const orders: PortfolioOrder[] = [
{
date: '2019-02-01',
name: 'Vanguard Total Stock Market Index Fund ETF Shares',
@ -244,6 +252,7 @@ describe('PortfolioCalculator', () => {
type: OrderType.Buy,
unitPrice: new Big('144.38'),
currency: Currency.USD,
dataSource: DataSource.YAHOO,
fee: new Big('5')
},
{
@ -254,6 +263,7 @@ describe('PortfolioCalculator', () => {
type: OrderType.Buy,
unitPrice: new Big('147.99'),
currency: Currency.USD,
dataSource: DataSource.YAHOO,
fee: new Big('10')
},
{
@ -264,6 +274,7 @@ describe('PortfolioCalculator', () => {
type: OrderType.Sell,
unitPrice: new Big('151.41'),
currency: Currency.USD,
dataSource: DataSource.YAHOO,
fee: new Big('5')
}
];
@ -276,6 +287,7 @@ describe('PortfolioCalculator', () => {
date: '2019-02-01',
items: [
{
dataSource: DataSource.YAHOO,
quantity: new Big('10'),
symbol: 'VTI',
investment: new Big('1443.8'),
@ -290,6 +302,7 @@ describe('PortfolioCalculator', () => {
date: '2019-08-03',
items: [
{
dataSource: DataSource.YAHOO,
quantity: new Big('10'),
symbol: 'VTI',
investment: new Big('1443.8'),
@ -299,6 +312,7 @@ describe('PortfolioCalculator', () => {
fee: new Big('5')
},
{
dataSource: DataSource.YAHOO,
quantity: new Big('10'),
symbol: 'VTX',
investment: new Big('1479.9'),
@ -313,6 +327,7 @@ describe('PortfolioCalculator', () => {
date: '2020-02-02',
items: [
{
dataSource: DataSource.YAHOO,
quantity: new Big('5'),
symbol: 'VTI',
investment: new Big('686.75'),
@ -322,6 +337,7 @@ describe('PortfolioCalculator', () => {
fee: new Big('10')
},
{
dataSource: DataSource.YAHOO,
quantity: new Big('10'),
symbol: 'VTX',
investment: new Big('1479.9'),
@ -336,10 +352,11 @@ describe('PortfolioCalculator', () => {
});
it('with two orders at the same day of the same type', () => {
const orders = [
const orders: PortfolioOrder[] = [
...ordersVTI,
{
currency: Currency.USD,
dataSource: DataSource.YAHOO,
date: '2021-02-01',
name: 'Vanguard Total Stock Market Index Fund ETF Shares',
quantity: new Big('20'),
@ -363,6 +380,7 @@ describe('PortfolioCalculator', () => {
items: [
{
currency: Currency.USD,
dataSource: DataSource.YAHOO,
firstBuyDate: '2019-02-01',
investment: new Big('1443.8'),
quantity: new Big('10'),
@ -377,6 +395,7 @@ describe('PortfolioCalculator', () => {
items: [
{
currency: Currency.USD,
dataSource: DataSource.YAHOO,
firstBuyDate: '2019-02-01',
investment: new Big('2923.7'),
quantity: new Big('20'),
@ -391,6 +410,7 @@ describe('PortfolioCalculator', () => {
items: [
{
currency: Currency.USD,
dataSource: DataSource.YAHOO,
firstBuyDate: '2019-02-01',
investment: new Big('652.55'),
quantity: new Big('5'),
@ -405,6 +425,7 @@ describe('PortfolioCalculator', () => {
items: [
{
currency: Currency.USD,
dataSource: DataSource.YAHOO,
firstBuyDate: '2019-02-01',
investment: new Big('6627.05'),
quantity: new Big('35'),
@ -419,6 +440,7 @@ describe('PortfolioCalculator', () => {
items: [
{
currency: Currency.USD,
dataSource: DataSource.YAHOO,
firstBuyDate: '2019-02-01',
investment: new Big('8403.95'),
quantity: new Big('45'),
@ -432,10 +454,11 @@ describe('PortfolioCalculator', () => {
});
it('with additional order', () => {
const orders = [
const orders: PortfolioOrder[] = [
...ordersVTI,
{
currency: Currency.USD,
dataSource: DataSource.YAHOO,
date: '2019-09-01',
name: 'Amazon.com, Inc.',
quantity: new Big('5'),
@ -458,6 +481,7 @@ describe('PortfolioCalculator', () => {
date: '2019-02-01',
items: [
{
dataSource: DataSource.YAHOO,
quantity: new Big('10'),
symbol: 'VTI',
investment: new Big('1443.8'),
@ -472,6 +496,7 @@ describe('PortfolioCalculator', () => {
date: '2019-08-03',
items: [
{
dataSource: DataSource.YAHOO,
quantity: new Big('20'),
symbol: 'VTI',
investment: new Big('2923.7'),
@ -486,6 +511,7 @@ describe('PortfolioCalculator', () => {
date: '2019-09-01',
items: [
{
dataSource: DataSource.YAHOO,
quantity: new Big('5'),
symbol: 'AMZN',
investment: new Big('10109.95'),
@ -495,6 +521,7 @@ describe('PortfolioCalculator', () => {
transactionCount: 1
},
{
dataSource: DataSource.YAHOO,
quantity: new Big('20'),
symbol: 'VTI',
investment: new Big('2923.7'),
@ -509,6 +536,7 @@ describe('PortfolioCalculator', () => {
date: '2020-02-02',
items: [
{
dataSource: DataSource.YAHOO,
quantity: new Big('5'),
symbol: 'AMZN',
investment: new Big('10109.95'),
@ -518,6 +546,7 @@ describe('PortfolioCalculator', () => {
transactionCount: 1
},
{
dataSource: DataSource.YAHOO,
quantity: new Big('5'),
symbol: 'VTI',
investment: new Big('652.55'),
@ -532,6 +561,7 @@ describe('PortfolioCalculator', () => {
date: '2021-02-01',
items: [
{
dataSource: DataSource.YAHOO,
quantity: new Big('5'),
symbol: 'AMZN',
investment: new Big('10109.95'),
@ -541,6 +571,7 @@ describe('PortfolioCalculator', () => {
transactionCount: 1
},
{
dataSource: DataSource.YAHOO,
quantity: new Big('15'),
symbol: 'VTI',
investment: new Big('2684.05'),
@ -555,6 +586,7 @@ describe('PortfolioCalculator', () => {
date: '2021-08-01',
items: [
{
dataSource: DataSource.YAHOO,
quantity: new Big('5'),
symbol: 'AMZN',
investment: new Big('10109.95'),
@ -564,6 +596,7 @@ describe('PortfolioCalculator', () => {
transactionCount: 1
},
{
dataSource: DataSource.YAHOO,
quantity: new Big('25'),
symbol: 'VTI',
investment: new Big('4460.95'),
@ -578,7 +611,7 @@ describe('PortfolioCalculator', () => {
});
it('with additional buy & sell', () => {
const orders = [
const orders: PortfolioOrder[] = [
...ordersVTI,
{
date: '2019-09-01',
@ -588,6 +621,7 @@ describe('PortfolioCalculator', () => {
type: OrderType.Buy,
unitPrice: new Big('2021.99'),
currency: Currency.USD,
dataSource: DataSource.YAHOO,
fee: new Big(0)
},
{
@ -598,6 +632,7 @@ describe('PortfolioCalculator', () => {
type: OrderType.Sell,
unitPrice: new Big('2412.23'),
currency: Currency.USD,
dataSource: DataSource.YAHOO,
fee: new Big(0)
}
];
@ -628,6 +663,7 @@ describe('PortfolioCalculator', () => {
date: '2017-01-03',
items: [
{
dataSource: DataSource.YAHOO,
quantity: new Big('50'),
symbol: 'TSLA',
investment: new Big('2148.5'),
@ -642,6 +678,7 @@ describe('PortfolioCalculator', () => {
date: '2017-07-01',
items: [
{
dataSource: DataSource.YAHOO,
quantity: new Big('0.5614682'),
symbol: 'BTCUSD',
investment: new Big('1999.9999999999998659756'),
@ -651,6 +688,7 @@ describe('PortfolioCalculator', () => {
transactionCount: 1
},
{
dataSource: DataSource.YAHOO,
quantity: new Big('50'),
symbol: 'TSLA',
investment: new Big('2148.5'),
@ -665,6 +703,7 @@ describe('PortfolioCalculator', () => {
date: '2018-09-01',
items: [
{
dataSource: DataSource.YAHOO,
quantity: new Big('5'),
symbol: 'AMZN',
investment: new Big('10109.95'),
@ -674,6 +713,7 @@ describe('PortfolioCalculator', () => {
transactionCount: 1
},
{
dataSource: DataSource.YAHOO,
quantity: new Big('0.5614682'),
symbol: 'BTCUSD',
investment: new Big('1999.9999999999998659756'),
@ -683,6 +723,7 @@ describe('PortfolioCalculator', () => {
transactionCount: 1
},
{
dataSource: DataSource.YAHOO,
quantity: new Big('50'),
symbol: 'TSLA',
investment: new Big('2148.5'),
@ -929,6 +970,7 @@ describe('PortfolioCalculator', () => {
symbol: 'VTI',
investment: new Big('805.9'),
currency: Currency.USD,
dataSource: DataSource.YAHOO,
firstBuyDate: '2019-09-01',
fee: new Big(0),
transactionCount: 1
@ -943,6 +985,7 @@ describe('PortfolioCalculator', () => {
symbol: 'VTI',
investment: new Big('0'),
currency: Currency.USD,
dataSource: DataSource.YAHOO,
firstBuyDate: '2019-09-01',
fee: new Big(0),
transactionCount: 2
@ -957,6 +1000,7 @@ describe('PortfolioCalculator', () => {
symbol: 'VTI',
investment: new Big('1013.9'),
currency: Currency.USD,
dataSource: DataSource.YAHOO,
firstBuyDate: '2019-09-01',
fee: new Big(0),
transactionCount: 3
@ -1005,16 +1049,16 @@ describe('PortfolioCalculator', () => {
currentRateService,
Currency.USD
);
const transactionPoints = [
const transactionPoints: TransactionPoint[] = [
{
date: '2019-02-01',
items: [
{
quantity: new Big('10'),
name: 'Vanguard Total Stock Market Index Fund ETF Shares',
symbol: 'VTI',
investment: new Big('1443.8'),
currency: Currency.USD,
dataSource: DataSource.YAHOO,
firstBuyDate: '2019-02-01',
fee: new Big(0),
transactionCount: 1
@ -1026,10 +1070,10 @@ describe('PortfolioCalculator', () => {
items: [
{
quantity: new Big('20'),
name: 'Vanguard Total Stock Market Index Fund ETF Shares',
symbol: 'VTI',
investment: new Big('2923.7'),
currency: Currency.USD,
dataSource: DataSource.YAHOO,
firstBuyDate: '2019-02-01',
fee: new Big(0),
transactionCount: 2
@ -1088,16 +1132,16 @@ describe('PortfolioCalculator', () => {
currentRateService,
Currency.USD
);
const transactionPoints = [
const transactionPoints: TransactionPoint[] = [
{
date: '2019-02-01',
items: [
{
quantity: new Big('10'),
name: 'Vanguard Total Stock Market Index Fund ETF Shares',
symbol: 'VTI',
investment: new Big('1443.8'),
currency: Currency.USD,
dataSource: DataSource.YAHOO,
firstBuyDate: '2019-02-01',
fee: new Big(50),
transactionCount: 1
@ -1109,10 +1153,10 @@ describe('PortfolioCalculator', () => {
items: [
{
quantity: new Big('20'),
name: 'Vanguard Total Stock Market Index Fund ETF Shares',
symbol: 'VTI',
investment: new Big('2923.7'),
currency: Currency.USD,
dataSource: DataSource.YAHOO,
firstBuyDate: '2019-02-01',
fee: new Big(100),
transactionCount: 2
@ -1155,6 +1199,7 @@ describe('PortfolioCalculator', () => {
positions: [
{
averagePrice: new Big('146.185'),
dataSource: DataSource.YAHOO,
firstBuyDate: '2019-02-01',
quantity: new Big('20'),
symbol: 'VTI',
@ -1180,16 +1225,16 @@ describe('PortfolioCalculator', () => {
currentRateService,
Currency.USD
);
const transactionPoints = [
const transactionPoints: TransactionPoint[] = [
{
date: '2019-02-01',
items: [
{
quantity: new Big('10'),
name: 'Vanguard Total Stock Market Index Fund ETF Shares',
symbol: 'VTI',
investment: new Big('1443.8'),
currency: Currency.USD,
dataSource: DataSource.YAHOO,
firstBuyDate: '2019-02-01',
fee: new Big(50),
transactionCount: 1
@ -1201,10 +1246,10 @@ describe('PortfolioCalculator', () => {
items: [
{
quantity: new Big('20'),
name: 'Vanguard Total Stock Market Index Fund ETF Shares',
symbol: 'VTI',
investment: new Big('2923.7'),
currency: Currency.USD,
dataSource: DataSource.YAHOO,
firstBuyDate: '2019-02-01',
fee: new Big(100),
transactionCount: 2
@ -1277,6 +1322,7 @@ describe('PortfolioCalculator', () => {
symbol: 'MFA', // Mutual Fund A
investment: new Big('1000000'), // 1 million
currency: Currency.USD,
dataSource: DataSource.YAHOO,
firstBuyDate: '2010-12-31',
fee: new Big(0),
transactionCount: 1
@ -1291,6 +1337,7 @@ describe('PortfolioCalculator', () => {
symbol: 'MFA', // Mutual Fund A
investment: new Big('1100000'), // 1,000,000 + 100,000
currency: Currency.USD,
dataSource: DataSource.YAHOO,
firstBuyDate: '2010-12-31',
fee: new Big(0),
transactionCount: 2
@ -1352,6 +1399,7 @@ describe('PortfolioCalculator', () => {
symbol: 'SPA', // Sub Portfolio A
investment: new Big('200'),
currency: Currency.CHF,
dataSource: DataSource.YAHOO,
firstBuyDate: '2012-12-31',
fee: new Big(0),
transactionCount: 1
@ -1361,6 +1409,7 @@ describe('PortfolioCalculator', () => {
symbol: 'SPB', // Sub Portfolio B
investment: new Big('300'),
currency: Currency.CHF,
dataSource: DataSource.YAHOO,
firstBuyDate: '2012-12-31',
fee: new Big(0),
transactionCount: 1
@ -1375,6 +1424,7 @@ describe('PortfolioCalculator', () => {
symbol: 'SPA', // Sub Portfolio A
investment: new Big('200'),
currency: Currency.CHF,
dataSource: DataSource.YAHOO,
firstBuyDate: '2012-12-31',
fee: new Big(0),
transactionCount: 1
@ -1384,6 +1434,7 @@ describe('PortfolioCalculator', () => {
symbol: 'SPB', // Sub Portfolio B
investment: new Big('300'),
currency: Currency.CHF,
dataSource: DataSource.YAHOO,
firstBuyDate: '2012-12-31',
fee: new Big(0),
transactionCount: 1
@ -1488,7 +1539,7 @@ describe('PortfolioCalculator', () => {
currentRateService,
Currency.USD
);
const transactionPoints = [
const transactionPoints: TransactionPoint[] = [
{
date: '2019-02-01',
items: [
@ -1497,6 +1548,7 @@ describe('PortfolioCalculator', () => {
symbol: 'VTI',
investment: new Big('1443.8'),
currency: Currency.USD,
dataSource: DataSource.YAHOO,
firstBuyDate: '2019-02-01',
fee: new Big(50),
transactionCount: 1
@ -1511,6 +1563,7 @@ describe('PortfolioCalculator', () => {
symbol: 'VTI',
investment: new Big('2923.7'),
currency: Currency.USD,
dataSource: DataSource.YAHOO,
firstBuyDate: '2019-02-01',
fee: new Big(100),
transactionCount: 2
@ -1525,6 +1578,7 @@ describe('PortfolioCalculator', () => {
symbol: 'VTI',
investment: new Big('652.55'),
currency: Currency.USD,
dataSource: DataSource.YAHOO,
firstBuyDate: '2019-02-01',
fee: new Big(150),
transactionCount: 3
@ -1539,6 +1593,7 @@ describe('PortfolioCalculator', () => {
symbol: 'VTI',
investment: new Big('2684.05'),
currency: Currency.USD,
dataSource: DataSource.YAHOO,
firstBuyDate: '2019-02-01',
fee: new Big(200),
transactionCount: 4
@ -1553,6 +1608,7 @@ describe('PortfolioCalculator', () => {
symbol: 'VTI',
investment: new Big('4460.95'),
currency: Currency.USD,
dataSource: DataSource.YAHOO,
firstBuyDate: '2019-02-01',
fee: new Big(250),
transactionCount: 5
@ -2217,6 +2273,7 @@ describe('PortfolioCalculator', () => {
symbol: 'AMZN',
investment: new Big('10109.95'),
currency: Currency.USD,
dataSource: DataSource.YAHOO,
firstBuyDate: '2019-02-01',
fee: new Big(0),
transactionCount: 1
@ -2226,6 +2283,7 @@ describe('PortfolioCalculator', () => {
symbol: 'VTI',
investment: new Big('1443.8'),
currency: Currency.USD,
dataSource: DataSource.YAHOO,
firstBuyDate: '2019-02-01',
fee: new Big(0),
transactionCount: 1
@ -2334,6 +2392,7 @@ const ordersMixedSymbols: PortfolioOrder[] = [
type: OrderType.Buy,
unitPrice: new Big('42.97'),
currency: Currency.USD,
dataSource: DataSource.YAHOO,
fee: new Big(0)
},
{
@ -2344,6 +2403,7 @@ const ordersMixedSymbols: PortfolioOrder[] = [
type: OrderType.Buy,
unitPrice: new Big('3562.089535970158'),
currency: Currency.USD,
dataSource: DataSource.YAHOO,
fee: new Big(0)
},
{
@ -2354,6 +2414,7 @@ const ordersMixedSymbols: PortfolioOrder[] = [
type: OrderType.Buy,
unitPrice: new Big('2021.99'),
currency: Currency.USD,
dataSource: DataSource.YAHOO,
fee: new Big(0)
}
];
@ -2367,6 +2428,7 @@ const ordersVTI: PortfolioOrder[] = [
type: OrderType.Buy,
unitPrice: new Big('144.38'),
currency: Currency.USD,
dataSource: DataSource.YAHOO,
fee: new Big(0)
},
{
@ -2377,6 +2439,7 @@ const ordersVTI: PortfolioOrder[] = [
type: OrderType.Buy,
unitPrice: new Big('147.99'),
currency: Currency.USD,
dataSource: DataSource.YAHOO,
fee: new Big(0)
},
{
@ -2387,6 +2450,7 @@ const ordersVTI: PortfolioOrder[] = [
type: OrderType.Sell,
unitPrice: new Big('151.41'),
currency: Currency.USD,
dataSource: DataSource.YAHOO,
fee: new Big(0)
},
{
@ -2397,6 +2461,7 @@ const ordersVTI: PortfolioOrder[] = [
type: OrderType.Buy,
unitPrice: new Big('177.69'),
currency: Currency.USD,
dataSource: DataSource.YAHOO,
fee: new Big(0)
},
{
@ -2407,6 +2472,7 @@ const ordersVTI: PortfolioOrder[] = [
type: OrderType.Buy,
unitPrice: new Big('203.15'),
currency: Currency.USD,
dataSource: DataSource.YAHOO,
fee: new Big(0)
}
];
@ -2420,6 +2486,7 @@ const orderTslaTransactionPoint: TransactionPoint[] = [
symbol: 'TSLA',
investment: new Big('719.46'),
currency: Currency.USD,
dataSource: DataSource.YAHOO,
firstBuyDate: '2021-01-01',
fee: new Big(0),
transactionCount: 1
@ -2437,6 +2504,7 @@ const ordersVTITransactionPoints: TransactionPoint[] = [
symbol: 'VTI',
investment: new Big('1443.8'),
currency: Currency.USD,
dataSource: DataSource.YAHOO,
firstBuyDate: '2019-02-01',
fee: new Big(0),
transactionCount: 1
@ -2451,6 +2519,7 @@ const ordersVTITransactionPoints: TransactionPoint[] = [
symbol: 'VTI',
investment: new Big('2923.7'),
currency: Currency.USD,
dataSource: DataSource.YAHOO,
firstBuyDate: '2019-02-01',
fee: new Big(0),
transactionCount: 2
@ -2465,6 +2534,7 @@ const ordersVTITransactionPoints: TransactionPoint[] = [
symbol: 'VTI',
investment: new Big('652.55'),
currency: Currency.USD,
dataSource: DataSource.YAHOO,
firstBuyDate: '2019-02-01',
fee: new Big(0),
transactionCount: 3
@ -2479,6 +2549,7 @@ const ordersVTITransactionPoints: TransactionPoint[] = [
symbol: 'VTI',
investment: new Big('2684.05'),
currency: Currency.USD,
dataSource: DataSource.YAHOO,
firstBuyDate: '2019-02-01',
fee: new Big(0),
transactionCount: 4
@ -2493,6 +2564,7 @@ const ordersVTITransactionPoints: TransactionPoint[] = [
symbol: 'VTI',
investment: new Big('4460.95'),
currency: Currency.USD,
dataSource: DataSource.YAHOO,
firstBuyDate: '2019-02-01',
fee: new Big(0),
transactionCount: 5
@ -2501,7 +2573,7 @@ const ordersVTITransactionPoints: TransactionPoint[] = [
}
];
const transactionPointsBuyAndSell = [
const transactionPointsBuyAndSell: TransactionPoint[] = [
{
date: '2019-02-01',
items: [
@ -2510,6 +2582,7 @@ const transactionPointsBuyAndSell = [
symbol: 'VTI',
investment: new Big('1443.8'),
currency: Currency.USD,
dataSource: DataSource.YAHOO,
firstBuyDate: '2019-02-01',
fee: new Big(0),
transactionCount: 1
@ -2524,6 +2597,7 @@ const transactionPointsBuyAndSell = [
symbol: 'VTI',
investment: new Big('2923.7'),
currency: Currency.USD,
dataSource: DataSource.YAHOO,
firstBuyDate: '2019-02-01',
fee: new Big(0),
transactionCount: 2
@ -2538,6 +2612,7 @@ const transactionPointsBuyAndSell = [
symbol: 'AMZN',
investment: new Big('10109.95'),
currency: Currency.USD,
dataSource: DataSource.YAHOO,
firstBuyDate: '2019-09-01',
fee: new Big(0),
transactionCount: 1
@ -2547,6 +2622,7 @@ const transactionPointsBuyAndSell = [
symbol: 'VTI',
investment: new Big('2923.7'),
currency: Currency.USD,
dataSource: DataSource.YAHOO,
firstBuyDate: '2019-02-01',
fee: new Big(0),
transactionCount: 2
@ -2561,6 +2637,7 @@ const transactionPointsBuyAndSell = [
symbol: 'AMZN',
investment: new Big('10109.95'),
currency: Currency.USD,
dataSource: DataSource.YAHOO,
firstBuyDate: '2019-09-01',
fee: new Big(0),
transactionCount: 1
@ -2570,6 +2647,7 @@ const transactionPointsBuyAndSell = [
symbol: 'VTI',
investment: new Big('652.55'),
currency: Currency.USD,
dataSource: DataSource.YAHOO,
firstBuyDate: '2019-02-01',
fee: new Big(0),
transactionCount: 3
@ -2584,6 +2662,7 @@ const transactionPointsBuyAndSell = [
symbol: 'AMZN',
investment: new Big('0'),
currency: Currency.USD,
dataSource: DataSource.YAHOO,
firstBuyDate: '2019-09-01',
fee: new Big(0),
transactionCount: 2
@ -2593,6 +2672,7 @@ const transactionPointsBuyAndSell = [
symbol: 'VTI',
investment: new Big('652.55'),
currency: Currency.USD,
dataSource: DataSource.YAHOO,
firstBuyDate: '2019-02-01',
fee: new Big(0),
transactionCount: 3
@ -2607,6 +2687,7 @@ const transactionPointsBuyAndSell = [
symbol: 'AMZN',
investment: new Big('0'),
currency: Currency.USD,
dataSource: DataSource.YAHOO,
firstBuyDate: '2019-09-01',
fee: new Big(0),
transactionCount: 2
@ -2616,6 +2697,7 @@ const transactionPointsBuyAndSell = [
symbol: 'VTI',
investment: new Big('2684.05'),
currency: Currency.USD,
dataSource: DataSource.YAHOO,
firstBuyDate: '2019-02-01',
fee: new Big(0),
transactionCount: 4
@ -2630,6 +2712,7 @@ const transactionPointsBuyAndSell = [
symbol: 'AMZN',
investment: new Big('0'),
currency: Currency.USD,
dataSource: DataSource.YAHOO,
firstBuyDate: '2019-09-01',
fee: new Big(0),
transactionCount: 2
@ -2639,6 +2722,7 @@ const transactionPointsBuyAndSell = [
symbol: 'VTI',
investment: new Big('4460.95'),
currency: Currency.USD,
dataSource: DataSource.YAHOO,
firstBuyDate: '2019-02-01',
fee: new Big(0),
transactionCount: 5

28
apps/api/src/app/portfolio/portfolio-calculator.ts

@ -1,7 +1,8 @@
import { OrderType } from '@ghostfolio/api/models/order-type';
import { IDataGatheringItem } from '@ghostfolio/api/services/interfaces/interfaces';
import { DATE_FORMAT, parseDate, resetHours } from '@ghostfolio/common/helper';
import { TimelinePosition } from '@ghostfolio/common/interfaces';
import { Currency } from '@prisma/client';
import { Currency, DataSource } from '@prisma/client';
import Big from 'big.js';
import {
addDays,
@ -59,6 +60,7 @@ export class PortfolioCalculator {
.plus(oldAccumulatedSymbol.quantity);
currentTransactionPointItem = {
currency: order.currency,
dataSource: order.dataSource,
fee: order.fee.plus(oldAccumulatedSymbol.fee),
firstBuyDate: oldAccumulatedSymbol.firstBuyDate,
investment: newQuantity.eq(0)
@ -74,6 +76,7 @@ export class PortfolioCalculator {
} else {
currentTransactionPointItem = {
currency: order.currency,
dataSource: order.dataSource,
fee: order.fee,
firstBuyDate: order.date,
investment: unitPrice.mul(order.quantity).mul(factor),
@ -153,12 +156,15 @@ export class PortfolioCalculator {
let firstTransactionPoint: TransactionPoint = null;
let firstIndex = this.transactionPoints.length;
const dates = [];
const symbols = new Set<string>();
const dataGatheringItems: IDataGatheringItem[] = [];
const currencies: { [symbol: string]: Currency } = {};
dates.push(resetHours(start));
for (const item of this.transactionPoints[firstIndex - 1].items) {
symbols.add(item.symbol);
dataGatheringItems.push({
dataSource: item.dataSource,
symbol: item.symbol
});
currencies[item.symbol] = item.currency;
}
for (let i = 0; i < this.transactionPoints.length; i++) {
@ -178,10 +184,10 @@ export class PortfolioCalculator {
const marketSymbols = await this.currentRateService.getValues({
currencies,
dataGatheringItems,
dateQuery: {
in: dates
},
symbols: Array.from(symbols),
userCurrency: this.currency
});
@ -309,6 +315,7 @@ export class PortfolioCalculator {
? new Big(0)
: item.investment.div(item.quantity),
currency: item.currency,
dataSource: item.dataSource,
firstBuyDate: item.firstBuyDate,
grossPerformance: isValid
? grossPerformance[item.symbol] ?? null
@ -515,25 +522,28 @@ export class PortfolioCalculator {
} = {};
if (j >= 0) {
const currencies: { [name: string]: Currency } = {};
const symbols: string[] = [];
const dataGatheringItems: IDataGatheringItem[] = [];
for (const item of this.transactionPoints[j].items) {
currencies[item.symbol] = item.currency;
symbols.push(item.symbol);
dataGatheringItems.push({
dataSource: item.dataSource,
symbol: item.symbol
});
investment = investment.add(item.investment);
fees = fees.add(item.fee);
}
let marketSymbols: GetValueObject[] = [];
if (symbols.length > 0) {
if (dataGatheringItems.length > 0) {
try {
marketSymbols = await this.currentRateService.getValues({
currencies,
dataGatheringItems,
dateQuery: {
gte: startDate,
lt: endOfDay(endDate)
},
symbols,
currencies,
userCurrency: this.currency
});
} catch (error) {

22
apps/api/src/app/portfolio/portfolio.service.ts

@ -191,12 +191,18 @@ export class PortfolioService {
);
const totalValue = currentPositions.currentValue.plus(cashDetails.balance);
const dataGatheringItems = currentPositions.positions.map((position) => {
return {
dataSource: position.dataSource,
symbol: position.symbol
};
});
const symbols = currentPositions.positions.map(
(position) => position.symbol
);
const [dataProviderResponses, symbolProfiles] = await Promise.all([
this.dataProviderService.get(symbols),
this.dataProviderService.get(dataGatheringItems),
this.symbolProfileService.getSymbolProfiles(symbols)
]);
@ -297,6 +303,7 @@ export class PortfolioService {
const portfolioOrders: PortfolioOrder[] = orders.map((order) => ({
currency: order.currency,
dataSource: order.dataSource,
date: format(order.date, DATE_FORMAT),
fee: new Big(order.fee),
name: order.SymbolProfile?.name,
@ -421,7 +428,9 @@ export class PortfolioService {
symbol: aSymbol
};
} else {
const currentData = await this.dataProviderService.get([aSymbol]);
const currentData = await this.dataProviderService.get([
{ dataSource: DataSource.YAHOO, symbol: aSymbol }
]);
const marketPrice = currentData[aSymbol]?.marketPrice;
let historicalData = await this.dataProviderService.getHistorical(
@ -507,10 +516,16 @@ export class PortfolioService {
const positions = currentPositions.positions.filter(
(item) => !item.quantity.eq(0)
);
const dataGatheringItem = positions.map((position) => {
return {
dataSource: position.dataSource,
symbol: position.symbol
};
});
const symbols = positions.map((position) => position.symbol);
const [dataProviderResponses, symbolProfiles] = await Promise.all([
this.dataProviderService.get(symbols),
this.dataProviderService.get(dataGatheringItem),
this.symbolProfileService.getSymbolProfiles(symbols)
]);
@ -813,6 +828,7 @@ export class PortfolioService {
const userCurrency = this.request.user.Settings.currency;
const portfolioOrders: PortfolioOrder[] = orders.map((order) => ({
currency: order.currency,
dataSource: order.dataSource,
date: format(order.date, DATE_FORMAT),
fee: new Big(
this.exchangeRateDataService.toCurrency(

7
apps/api/src/app/symbol/symbol.controller.ts

@ -53,6 +53,13 @@ export class SymbolController {
@Param('dataSource') dataSource: DataSource,
@Param('symbol') symbol: string
): Promise<SymbolItem> {
if (!DataSource[dataSource]) {
throw new HttpException(
getReasonPhrase(StatusCodes.NOT_FOUND),
StatusCodes.NOT_FOUND
);
}
const result = await this.symbolService.get({ dataSource, symbol });
if (!result || isEmpty(result)) {

19
apps/api/src/app/symbol/symbol.service.ts

@ -1,4 +1,5 @@
import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service';
import { IDataGatheringItem } from '@ghostfolio/api/services/interfaces/interfaces';
import { PrismaService } from '@ghostfolio/api/services/prisma.service';
import { Injectable } from '@nestjs/common';
import { Currency, DataSource } from '@prisma/client';
@ -13,21 +14,15 @@ export class SymbolService {
private readonly prismaService: PrismaService
) {}
public async get({
dataSource,
symbol
}: {
dataSource: DataSource;
symbol: string;
}): Promise<SymbolItem> {
const response = await this.dataProviderService.get([symbol]);
const { currency, marketPrice } = response[symbol] ?? {};
public async get(dataGatheringItem: IDataGatheringItem): Promise<SymbolItem> {
const response = await this.dataProviderService.get([dataGatheringItem]);
const { currency, marketPrice } = response[dataGatheringItem.symbol] ?? {};
if (dataSource && marketPrice) {
if (dataGatheringItem.dataSource && marketPrice) {
return {
dataSource,
marketPrice,
currency: <Currency>(<unknown>currency)
currency: <Currency>(<unknown>currency),
dataSource: dataGatheringItem.dataSource
};
}

21
apps/api/src/services/data-gathering.service.ts

@ -3,17 +3,11 @@ import {
currencyPairs,
ghostfolioFearAndGreedIndexSymbol
} from '@ghostfolio/common/config';
import {
DATE_FORMAT,
getUtc,
isGhostfolioScraperApiSymbol,
resetHours
} from '@ghostfolio/common/helper';
import { DATE_FORMAT, getUtc, resetHours } from '@ghostfolio/common/helper';
import { Injectable } from '@nestjs/common';
import { DataSource } from '@prisma/client';
import {
differenceInHours,
endOfToday,
format,
getDate,
getMonth,
@ -123,20 +117,17 @@ export class DataGatheringService {
}
}
public async gatherProfileData(aSymbols?: string[]) {
public async gatherProfileData(aDataGatheringItems?: IDataGatheringItem[]) {
console.log('Profile data gathering has been started.');
console.time('data-gathering-profile');
let symbols = aSymbols;
let dataGatheringItems = aDataGatheringItems;
if (!symbols) {
const dataGatheringItems = await this.getSymbolsProfileData();
symbols = dataGatheringItems.map((dataGatheringItem) => {
return dataGatheringItem.symbol;
});
if (!dataGatheringItems) {
dataGatheringItems = await this.getSymbolsProfileData();
}
const currentData = await this.dataProviderService.get(symbols);
const currentData = await this.dataProviderService.get(dataGatheringItems);
for (const [
symbol,

77
apps/api/src/services/data-provider/data-provider.service.ts

@ -6,11 +6,7 @@ import {
IDataProviderResponse
} from '@ghostfolio/api/services/interfaces/interfaces';
import { PrismaService } from '@ghostfolio/api/services/prisma.service';
import {
DATE_FORMAT,
isGhostfolioScraperApiSymbol,
isRakutenRapidApiSymbol
} from '@ghostfolio/common/helper';
import { DATE_FORMAT } from '@ghostfolio/common/helper';
import { Granularity } from '@ghostfolio/common/types';
import { Injectable } from '@nestjs/common';
import { DataSource, MarketData } from '@prisma/client';
@ -37,53 +33,32 @@ export class DataProviderService {
this.rakutenRapidApiService?.setPrisma(this.prismaService);
}
public async get(
aSymbols: string[]
): Promise<{ [symbol: string]: IDataProviderResponse }> {
if (aSymbols.length === 1) {
const symbol = aSymbols[0];
if (isGhostfolioScraperApiSymbol(symbol)) {
return this.ghostfolioScraperApiService.get(aSymbols);
} else if (isRakutenRapidApiSymbol(symbol)) {
return this.rakutenRapidApiService.get(aSymbols);
}
}
const yahooFinanceSymbols = aSymbols
.filter((symbol) => {
return (
!isGhostfolioScraperApiSymbol(symbol) &&
!isRakutenRapidApiSymbol(symbol)
);
})
.map((symbol) => {
return convertToYahooFinanceSymbol(symbol);
});
const response = await this.yahooFinanceService.get(yahooFinanceSymbols);
const ghostfolioScraperApiSymbols = aSymbols.filter((symbol) => {
return isGhostfolioScraperApiSymbol(symbol);
});
for (const symbol of ghostfolioScraperApiSymbols) {
if (symbol) {
const ghostfolioScraperApiResult =
await this.ghostfolioScraperApiService.get([symbol]);
response[symbol] = ghostfolioScraperApiResult[symbol];
}
}
const rakutenRapidApiSymbols = aSymbols.filter((symbol) => {
return isRakutenRapidApiSymbol(symbol);
});
public async get(items: IDataGatheringItem[]): Promise<{
[symbol: string]: IDataProviderResponse;
}> {
const response: {
[symbol: string]: IDataProviderResponse;
} = {};
for (const symbol of rakutenRapidApiSymbols) {
if (symbol) {
const rakutenRapidApiResult =
await this.ghostfolioScraperApiService.get([symbol]);
response[symbol] = rakutenRapidApiResult[symbol];
for (const item of items) {
if (item.dataSource === DataSource.ALPHA_VANTAGE) {
response[item.symbol] = (
await this.alphaVantageService.get([item.symbol])
)[item.symbol];
} else if (item.dataSource === DataSource.GHOSTFOLIO) {
response[item.symbol] = (
await this.ghostfolioScraperApiService.get([item.symbol])
)[item.symbol];
} else if (item.dataSource === DataSource.RAKUTEN) {
response[item.symbol] = (
await this.rakutenRapidApiService.get([item.symbol])
)[item.symbol];
} else if (item.dataSource === DataSource.YAHOO) {
response[item.symbol] = (
await this.yahooFinanceService.get([
convertToYahooFinanceSymbol(item.symbol)
])
)[item.symbol];
}
}

4
apps/api/src/services/exchange-rate-data.service.ts

@ -1,7 +1,7 @@
import { currencyPairs } from '@ghostfolio/common/config';
import { DATE_FORMAT, getYesterday } from '@ghostfolio/common/helper';
import { Injectable } from '@nestjs/common';
import { Currency } from '@prisma/client';
import { Currency, DataSource } from '@prisma/client';
import { format } from 'date-fns';
import { isEmpty, isNumber } from 'lodash';
@ -40,7 +40,7 @@ export class ExchangeRateDataService {
// if historical data is not yet available
const historicalData = await this.dataProviderService.get(
this.currencyPairs.map((currencyPair) => {
return currencyPair;
return { dataSource: DataSource.YAHOO, symbol: currencyPair };
})
);

2
apps/client/src/app/pages/home/home-page.component.ts

@ -114,7 +114,7 @@ export class HomePageComponent implements OnDestroy, OnInit {
if (this.hasPermissionToAccessFearAndGreedIndex) {
this.dataService
.fetchSymbolItem({
dataSource: DataSource.GHOSTFOLIO,
dataSource: DataSource.RAKUTEN,
symbol: ghostfolioFearAndGreedIndexSymbol
})
.pipe(takeUntil(this.unsubscribeSubject))

3
libs/common/src/lib/interfaces/timeline-position.interface.ts

@ -1,9 +1,10 @@
import { Currency } from '@prisma/client';
import { Currency, DataSource } from '@prisma/client';
import Big from 'big.js';
export interface TimelinePosition {
averagePrice: Big;
currency: Currency;
dataSource: DataSource;
firstBuyDate: string;
grossPerformance: Big;
grossPerformancePercentage: Big;

Loading…
Cancel
Save