From af0863d19324d7f5cff1b9b03090bf61a200d578 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 21 May 2022 19:58:47 +0200 Subject: [PATCH] Bugfix/fix currency conversion in accounts (#937) * Fix currency conversion in accounts * Update changelog --- CHANGELOG.md | 1 + .../portfolio/current-rate.service.mock.ts | 20 ++++++--- .../portfolio/current-rate.service.spec.ts | 7 ++-- .../src/app/portfolio/current-rate.service.ts | 34 +++++++-------- .../interfaces/get-value-object.interface.ts | 2 +- .../src/app/portfolio/portfolio-calculator.ts | 8 ++-- .../src/app/portfolio/portfolio.service.ts | 41 +++++++++++++------ 7 files changed, 68 insertions(+), 45 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c03e17d6..9de36b543 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- Fixed an issue with the currency conversion in the account calculations - Fixed an issue with countries in the symbol profile overrides ## 1.149.0 - 16.05.2022 diff --git a/apps/api/src/app/portfolio/current-rate.service.mock.ts b/apps/api/src/app/portfolio/current-rate.service.mock.ts index c91ed9d9a..9246ffc73 100644 --- a/apps/api/src/app/portfolio/current-rate.service.mock.ts +++ b/apps/api/src/app/portfolio/current-rate.service.mock.ts @@ -1,6 +1,7 @@ import { parseDate, resetHours } from '@ghostfolio/common/helper'; import { addDays, endOfDay, isBefore, isSameDay } from 'date-fns'; +import { GetValueObject } from './interfaces/get-value-object.interface'; import { GetValuesParams } from './interfaces/get-values-params.interface'; function mockGetValue(symbol: string, date: Date) { @@ -33,8 +34,11 @@ function mockGetValue(symbol: string, date: Date) { } export const CurrentRateServiceMock = { - getValues: ({ dataGatheringItems, dateQuery }: GetValuesParams) => { - const result = []; + getValues: ({ + dataGatheringItems, + dateQuery + }: GetValuesParams): Promise => { + const result: GetValueObject[] = []; if (dateQuery.lt) { for ( let date = resetHours(dateQuery.gte); @@ -44,8 +48,10 @@ export const CurrentRateServiceMock = { for (const dataGatheringItem of dataGatheringItems) { result.push({ date, - marketPrice: mockGetValue(dataGatheringItem.symbol, date) - .marketPrice, + marketPriceInBaseCurrency: mockGetValue( + dataGatheringItem.symbol, + date + ).marketPrice, symbol: dataGatheringItem.symbol }); } @@ -55,8 +61,10 @@ export const CurrentRateServiceMock = { for (const dataGatheringItem of dataGatheringItems) { result.push({ date, - marketPrice: mockGetValue(dataGatheringItem.symbol, date) - .marketPrice, + marketPriceInBaseCurrency: mockGetValue( + dataGatheringItem.symbol, + date + ).marketPrice, symbol: dataGatheringItem.symbol }); } diff --git a/apps/api/src/app/portfolio/current-rate.service.spec.ts b/apps/api/src/app/portfolio/current-rate.service.spec.ts index ca45483e1..653cdc7dc 100644 --- a/apps/api/src/app/portfolio/current-rate.service.spec.ts +++ b/apps/api/src/app/portfolio/current-rate.service.spec.ts @@ -4,6 +4,7 @@ import { MarketDataService } from '@ghostfolio/api/services/market-data.service' import { DataSource, MarketData } from '@prisma/client'; import { CurrentRateService } from './current-rate.service'; +import { GetValueObject } from './interfaces/get-value-object.interface'; jest.mock('@ghostfolio/api/services/market-data.service', () => { return { @@ -96,15 +97,15 @@ describe('CurrentRateService', () => { }, userCurrency: 'CHF' }) - ).toMatchObject([ + ).toMatchObject([ { date: undefined, - marketPrice: 1841.823902, + marketPriceInBaseCurrency: 1841.823902, symbol: 'AMZN' }, { date: undefined, - marketPrice: 1847.839966, + marketPriceInBaseCurrency: 1847.839966, symbol: 'AMZN' } ]); diff --git a/apps/api/src/app/portfolio/current-rate.service.ts b/apps/api/src/app/portfolio/current-rate.service.ts index 0549596ce..86020ce2e 100644 --- a/apps/api/src/app/portfolio/current-rate.service.ts +++ b/apps/api/src/app/portfolio/current-rate.service.ts @@ -28,13 +28,7 @@ export class CurrentRateService { (!dateQuery.gte || isBefore(dateQuery.gte, new Date())) && (!dateQuery.in || this.containsToday(dateQuery.in)); - const promises: Promise< - { - date: Date; - marketPrice: number; - symbol: string; - }[] - >[] = []; + const promises: Promise[] = []; if (includeToday) { const today = resetHours(new Date()); @@ -42,16 +36,17 @@ export class CurrentRateService { this.dataProviderService .getQuotes(dataGatheringItems) .then((dataResultProvider) => { - const result = []; + const result: GetValueObject[] = []; for (const dataGatheringItem of dataGatheringItems) { result.push({ date: today, - marketPrice: this.exchangeRateDataService.toCurrency( - dataResultProvider?.[dataGatheringItem.symbol]?.marketPrice ?? - 0, - dataResultProvider?.[dataGatheringItem.symbol]?.currency, - userCurrency - ), + marketPriceInBaseCurrency: + this.exchangeRateDataService.toCurrency( + dataResultProvider?.[dataGatheringItem.symbol] + ?.marketPrice ?? 0, + dataResultProvider?.[dataGatheringItem.symbol]?.currency, + userCurrency + ), symbol: dataGatheringItem.symbol }); } @@ -74,11 +69,12 @@ export class CurrentRateService { return data.map((marketDataItem) => { return { date: marketDataItem.date, - marketPrice: this.exchangeRateDataService.toCurrency( - marketDataItem.marketPrice, - currencies[marketDataItem.symbol], - userCurrency - ), + marketPriceInBaseCurrency: + this.exchangeRateDataService.toCurrency( + marketDataItem.marketPrice, + currencies[marketDataItem.symbol], + userCurrency + ), symbol: marketDataItem.symbol }; }); diff --git a/apps/api/src/app/portfolio/interfaces/get-value-object.interface.ts b/apps/api/src/app/portfolio/interfaces/get-value-object.interface.ts index 8072a5ea6..e67aca619 100644 --- a/apps/api/src/app/portfolio/interfaces/get-value-object.interface.ts +++ b/apps/api/src/app/portfolio/interfaces/get-value-object.interface.ts @@ -1,5 +1,5 @@ export interface GetValueObject { date: Date; - marketPrice: number; + marketPriceInBaseCurrency: number; symbol: string; } diff --git a/apps/api/src/app/portfolio/portfolio-calculator.ts b/apps/api/src/app/portfolio/portfolio-calculator.ts index 4f8631a3f..555ad1cab 100644 --- a/apps/api/src/app/portfolio/portfolio-calculator.ts +++ b/apps/api/src/app/portfolio/portfolio-calculator.ts @@ -231,9 +231,9 @@ export class PortfolioCalculator { if (!marketSymbolMap[date]) { marketSymbolMap[date] = {}; } - if (marketSymbol.marketPrice) { + if (marketSymbol.marketPriceInBaseCurrency) { marketSymbolMap[date][marketSymbol.symbol] = new Big( - marketSymbol.marketPrice + marketSymbol.marketPriceInBaseCurrency ); } } @@ -548,9 +548,9 @@ export class PortfolioCalculator { if (!marketSymbolMap[date]) { marketSymbolMap[date] = {}; } - if (marketSymbol.marketPrice) { + if (marketSymbol.marketPriceInBaseCurrency) { marketSymbolMap[date][marketSymbol.symbol] = new Big( - marketSymbol.marketPrice + marketSymbol.marketPriceInBaseCurrency ); } } diff --git a/apps/api/src/app/portfolio/portfolio.service.ts b/apps/api/src/app/portfolio/portfolio.service.ts index 409a40017..840c17f42 100644 --- a/apps/api/src/app/portfolio/portfolio.service.ts +++ b/apps/api/src/app/portfolio/portfolio.service.ts @@ -462,8 +462,9 @@ export class PortfolioService { const accounts = await this.getValueOfAccounts({ orders, - userId, portfolioItemsNow, + userCurrency, + userId, filters: aFilters }); @@ -899,7 +900,8 @@ export class PortfolioService { const accounts = await this.getValueOfAccounts({ orders, portfolioItemsNow, - userId + userId, + userCurrency: currency }); return { rules: { @@ -1268,11 +1270,13 @@ export class PortfolioService { filters = [], orders, portfolioItemsNow, + userCurrency, userId }: { filters?: Filter[]; orders: OrderWithAccount[]; portfolioItemsNow: { [p: string]: TimelinePosition }; + userCurrency: string; userId: string; }) { const accounts: PortfolioDetails['accounts'] = {}; @@ -1301,34 +1305,47 @@ export class PortfolioService { accounts[account.id] = { balance: account.balance, currency: account.currency, - current: account.balance, + current: this.exchangeRateDataService.toCurrency( + account.balance, + account.currency, + userCurrency + ), name: account.name, - original: account.balance + original: this.exchangeRateDataService.toCurrency( + account.balance, + account.currency, + userCurrency + ) }; for (const order of ordersByAccount) { - let currentValueOfSymbol = + let currentValueOfSymbolInBaseCurrency = order.quantity * portfolioItemsNow[order.SymbolProfile.symbol].marketPrice; - let originalValueOfSymbol = order.quantity * order.unitPrice; + let originalValueOfSymbolInBaseCurrency = + this.exchangeRateDataService.toCurrency( + order.quantity * order.unitPrice, + order.SymbolProfile.currency, + userCurrency + ); if (order.type === 'SELL') { - currentValueOfSymbol *= -1; - originalValueOfSymbol *= -1; + currentValueOfSymbolInBaseCurrency *= -1; + originalValueOfSymbolInBaseCurrency *= -1; } if (accounts[order.Account?.id || UNKNOWN_KEY]?.current) { accounts[order.Account?.id || UNKNOWN_KEY].current += - currentValueOfSymbol; + currentValueOfSymbolInBaseCurrency; accounts[order.Account?.id || UNKNOWN_KEY].original += - originalValueOfSymbol; + originalValueOfSymbolInBaseCurrency; } else { accounts[order.Account?.id || UNKNOWN_KEY] = { balance: 0, currency: order.Account?.currency, - current: currentValueOfSymbol, + current: currentValueOfSymbolInBaseCurrency, name: account.name, - original: originalValueOfSymbol + original: originalValueOfSymbolInBaseCurrency }; } }