From 099571437ef29b25345f60bdab492048e2ee9c33 Mon Sep 17 00:00:00 2001 From: Thomas <4159106+dtslvr@users.noreply.github.com> Date: Fri, 9 Jul 2021 21:35:54 +0200 Subject: [PATCH] Extend current rate service with getRange() --- .../src/app/core/current-rate.service.spec.ts | 47 ++++++++++++- apps/api/src/app/core/current-rate.service.ts | 69 ++++++++++++++++--- apps/api/src/app/core/market-data.service.ts | 21 ++++++ .../src/app/core/portfolio-calculator.spec.ts | 20 +++--- apps/api/src/app/core/portfolio-calculator.ts | 8 +-- 5 files changed, 142 insertions(+), 23 deletions(-) diff --git a/apps/api/src/app/core/current-rate.service.spec.ts b/apps/api/src/app/core/current-rate.service.spec.ts index 4c9d454f1..6a3caa018 100644 --- a/apps/api/src/app/core/current-rate.service.spec.ts +++ b/apps/api/src/app/core/current-rate.service.spec.ts @@ -17,6 +17,28 @@ jest.mock('./market-data.service', () => { id: 'aefcbe3a-ee10-4c4f-9f2d-8ffad7b05584', marketPrice: 1847.839966 }); + }, + getRange: ( + dateRangeEnd: Date, + dateRangeStart: Date, + symbol: string + ) => { + return Promise.resolve([ + { + date: dateRangeStart, + symbol, + createdAt: dateRangeStart, + id: '8fa48fde-f397-4b0d-adbc-fb940e830e6d', + marketPrice: 1841.823902 + }, + { + date: dateRangeEnd, + symbol, + createdAt: dateRangeEnd, + id: '082d6893-df27-4c91-8a5d-092e84315b56', + marketPrice: 1847.839966 + } + ]); } }; }) @@ -71,6 +93,29 @@ describe('CurrentRateService', () => { symbol: 'AMZN', userCurrency: Currency.CHF }) - ).toEqual(1847.839966); + ).toMatchObject({ + marketPrice: 1847.839966 + }); + }); + + it('getValues', async () => { + expect( + await currentRateService.getValues({ + currency: Currency.USD, + dateRangeEnd: new Date(Date.UTC(2020, 0, 2, 0, 0, 0)), + dateRangeStart: new Date(Date.UTC(2020, 0, 1, 0, 0, 0)), + symbol: 'AMZN', + userCurrency: Currency.CHF + }) + ).toMatchObject([ + { + // date + marketPrice: 1841.823902 + }, + { + // date + marketPrice: 1847.839966 + } + ]); }); }); diff --git a/apps/api/src/app/core/current-rate.service.ts b/apps/api/src/app/core/current-rate.service.ts index 0a4b618ea..f80bf14e3 100644 --- a/apps/api/src/app/core/current-rate.service.ts +++ b/apps/api/src/app/core/current-rate.service.ts @@ -1,5 +1,6 @@ import { DataProviderService } from '@ghostfolio/api/services/data-provider.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data.service'; +import { resetHours } from '@ghostfolio/common/helper'; import { Injectable } from '@nestjs/common'; import { Currency } from '@prisma/client'; import { isToday } from 'date-fns'; @@ -19,10 +20,13 @@ export class CurrentRateService { date, symbol, userCurrency - }: GetValueParams): Promise { + }: GetValueParams): Promise { if (isToday(date)) { const dataProviderResult = await this.dataProviderService.get([symbol]); - return dataProviderResult?.[symbol]?.marketPrice ?? 0; + return { + date: resetHours(date), + marketPrice: dataProviderResult?.[symbol]?.marketPrice ?? 0 + }; } const marketData = await this.marketDataService.get({ @@ -31,14 +35,50 @@ export class CurrentRateService { }); if (marketData) { - return this.exchangeRateDataService.toCurrency( - marketData.marketPrice, - currency, - userCurrency - ); + return { + date: marketData.date, + marketPrice: this.exchangeRateDataService.toCurrency( + marketData.marketPrice, + currency, + userCurrency + ) + }; } - throw new Error(`Value not found for ${symbol} at ${date}`); + throw new Error(`Value not found for ${symbol} at ${resetHours(date)}`); + } + + public async getValues({ + currency, + dateRangeEnd, + dateRangeStart, + symbol, + userCurrency + }: GetValuesParams): Promise { + const marketData = await this.marketDataService.getRange({ + dateRangeEnd, + dateRangeStart, + symbol + }); + + if (marketData) { + return marketData.map((marketDataItem) => { + return { + date: marketDataItem.date, + marketPrice: this.exchangeRateDataService.toCurrency( + marketDataItem.marketPrice, + currency, + userCurrency + ) + }; + }); + } + + throw new Error( + `Values not found for ${symbol} from ${resetHours( + dateRangeStart + )} to ${resetHours(dateRangeEnd)}` + ); } } @@ -48,3 +88,16 @@ export interface GetValueParams { currency: Currency; userCurrency: Currency; } + +export interface GetValuesParams { + dateRangeEnd: Date; + dateRangeStart: Date; + symbol: string; + currency: Currency; + userCurrency: Currency; +} + +export interface GetValueObject { + date: Date; + marketPrice: number; +} diff --git a/apps/api/src/app/core/market-data.service.ts b/apps/api/src/app/core/market-data.service.ts index aa93c45a7..4075ade56 100644 --- a/apps/api/src/app/core/market-data.service.ts +++ b/apps/api/src/app/core/market-data.service.ts @@ -2,6 +2,7 @@ import { PrismaService } from '@ghostfolio/api/services/prisma.service'; import { resetHours } from '@ghostfolio/common/helper'; import { Injectable } from '@nestjs/common'; import { MarketData } from '@prisma/client'; +import { endOfDay } from 'date-fns'; @Injectable() export class MarketDataService { @@ -21,4 +22,24 @@ export class MarketDataService { } }); } + + public async getRange({ + dateRangeEnd, + dateRangeStart, + symbol + }: { + dateRangeEnd: Date; + dateRangeStart: Date; + symbol: string; + }): Promise { + return await this.prisma.marketData.findMany({ + where: { + date: { + gte: dateRangeStart, + lt: endOfDay(dateRangeEnd) + }, + symbol + } + }); + } } diff --git a/apps/api/src/app/core/portfolio-calculator.spec.ts b/apps/api/src/app/core/portfolio-calculator.spec.ts index 43ce93c5f..5c87c29f0 100644 --- a/apps/api/src/app/core/portfolio-calculator.spec.ts +++ b/apps/api/src/app/core/portfolio-calculator.spec.ts @@ -1,15 +1,15 @@ +import { + CurrentRateService, + GetValueParams +} from '@ghostfolio/api/app/core/current-rate.service'; import { PortfolioCalculator, PortfolioOrder, TimelinePeriod, TimelineSpecification } from '@ghostfolio/api/app/core/portfolio-calculator'; -import { - CurrentRateService, - GetValueParams -} from '@ghostfolio/api/app/core/current-rate.service'; -import { Currency } from '@prisma/client'; import { OrderType } from '@ghostfolio/api/models/order-type'; +import { Currency } from '@prisma/client'; import Big from 'big.js'; import { differenceInCalendarDays, parse } from 'date-fns'; @@ -46,21 +46,21 @@ jest.mock('./current-rate.service.ts', () => { const today = new Date(); if (symbol === 'VTI') { if (dateEqual(today, date)) { - return Promise.resolve(new Big('213.32')); + return Promise.resolve({ marketPrice: new Big('213.32') }); } else { const startDate = parse('2019-02-01', 'yyyy-MM-dd', new Date()); const daysInBetween = differenceInCalendarDays(date, startDate); - const result = new Big('144.38').plus( + const marketPrice = new Big('144.38').plus( new Big('0.08').mul(daysInBetween) ); - return Promise.resolve(result); + return Promise.resolve({ marketPrice }); } } else if (symbol === 'AMZN') { - return Promise.resolve(2021.99); + return Promise.resolve({ marketPrice: new Big('2021.99') }); } - return Promise.resolve(new Big('0')); + return Promise.resolve({ marketPrice: new Big('0') }); } }; }) diff --git a/apps/api/src/app/core/portfolio-calculator.ts b/apps/api/src/app/core/portfolio-calculator.ts index c37a91f33..84d598cac 100644 --- a/apps/api/src/app/core/portfolio-calculator.ts +++ b/apps/api/src/app/core/portfolio-calculator.ts @@ -1,6 +1,6 @@ -import { Currency } from '@prisma/client'; import { CurrentRateService } from '@ghostfolio/api/app/core/current-rate.service'; import { OrderType } from '@ghostfolio/api/models/order-type'; +import { Currency } from '@prisma/client'; import Big from 'big.js'; import { addDays, @@ -110,7 +110,7 @@ export class PortfolioCalculator { const result: { [symbol: string]: TimelinePosition } = {}; for (const item of lastTransactionPoint.items) { - const marketPrice = await this.currentRateService.getValue({ + const marketValue = await this.currentRateService.getValue({ date: new Date(), symbol: item.symbol, currency: item.currency, @@ -122,7 +122,7 @@ export class PortfolioCalculator { quantity: item.quantity, symbol: item.symbol, investment: item.investment, - marketPrice: marketPrice, + marketPrice: marketValue.marketPrice, transactionCount: item.transactionCount }; } @@ -186,7 +186,7 @@ export class PortfolioCalculator { currency: item.currency, userCurrency: this.currency }) - .then((v) => new Big(v).mul(item.quantity)) + .then(({ marketPrice }) => new Big(marketPrice).mul(item.quantity)) ); } }