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 9246ffc73..13436f495 100644 --- a/apps/api/src/app/portfolio/current-rate.service.mock.ts +++ b/apps/api/src/app/portfolio/current-rate.service.mock.ts @@ -48,6 +48,8 @@ export const CurrentRateServiceMock = { for (const dataGatheringItem of dataGatheringItems) { result.push({ date, + marketPrice: mockGetValue(dataGatheringItem.symbol, date) + .marketPrice, marketPriceInBaseCurrency: mockGetValue( dataGatheringItem.symbol, date @@ -61,6 +63,8 @@ export const CurrentRateServiceMock = { for (const dataGatheringItem of dataGatheringItems) { result.push({ date, + marketPrice: mockGetValue(dataGatheringItem.symbol, date) + .marketPrice, marketPriceInBaseCurrency: mockGetValue( dataGatheringItem.symbol, date 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 653cdc7dc..1e0627fb1 100644 --- a/apps/api/src/app/portfolio/current-rate.service.spec.ts +++ b/apps/api/src/app/portfolio/current-rate.service.spec.ts @@ -100,11 +100,13 @@ describe('CurrentRateService', () => { ).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 86020ce2e..43692e96c 100644 --- a/apps/api/src/app/portfolio/current-rate.service.ts +++ b/apps/api/src/app/portfolio/current-rate.service.ts @@ -40,6 +40,9 @@ export class CurrentRateService { for (const dataGatheringItem of dataGatheringItems) { result.push({ date: today, + marketPrice: + dataResultProvider?.[dataGatheringItem.symbol]?.marketPrice ?? + 0, marketPriceInBaseCurrency: this.exchangeRateDataService.toCurrency( dataResultProvider?.[dataGatheringItem.symbol] @@ -69,6 +72,7 @@ export class CurrentRateService { return data.map((marketDataItem) => { return { date: marketDataItem.date, + marketPrice: marketDataItem.marketPrice, marketPriceInBaseCurrency: this.exchangeRateDataService.toCurrency( marketDataItem.marketPrice, 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 e67aca619..4d12436d3 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,6 @@ export interface GetValueObject { date: Date; + marketPrice: number; marketPriceInBaseCurrency: number; symbol: string; } diff --git a/apps/api/src/app/portfolio/interfaces/portfolio-position-detail.interface.ts b/apps/api/src/app/portfolio/interfaces/portfolio-position-detail.interface.ts index f400923e8..c469c3a9d 100644 --- a/apps/api/src/app/portfolio/interfaces/portfolio-position-detail.interface.ts +++ b/apps/api/src/app/portfolio/interfaces/portfolio-position-detail.interface.ts @@ -20,7 +20,7 @@ export interface PortfolioPositionDetail { SymbolProfile: EnhancedSymbolProfile; tags: Tag[]; transactionCount: number; - value: number; + valueInBaseCurrency: number; } export interface HistoricalDataContainer { diff --git a/apps/api/src/app/portfolio/portfolio-calculator-baln-buy-and-sell.spec.ts b/apps/api/src/app/portfolio/portfolio-calculator-baln-buy-and-sell.spec.ts index ea35cdd79..e702a4ecb 100644 --- a/apps/api/src/app/portfolio/portfolio-calculator-baln-buy-and-sell.spec.ts +++ b/apps/api/src/app/portfolio/portfolio-calculator-baln-buy-and-sell.spec.ts @@ -84,6 +84,7 @@ describe('PortfolioCalculator', () => { netPerformance: new Big('-15.8'), netPerformancePercentage: new Big('-0.0552834149755073478'), marketPrice: 148.9, + marketPriceInBaseCurrency: 148.9, quantity: new Big('0'), symbol: 'BALN.SW', transactionCount: 2 diff --git a/apps/api/src/app/portfolio/portfolio-calculator-baln-buy.spec.ts b/apps/api/src/app/portfolio/portfolio-calculator-baln-buy.spec.ts index a6fe1af40..d547f7b0a 100644 --- a/apps/api/src/app/portfolio/portfolio-calculator-baln-buy.spec.ts +++ b/apps/api/src/app/portfolio/portfolio-calculator-baln-buy.spec.ts @@ -73,6 +73,7 @@ describe('PortfolioCalculator', () => { netPerformance: new Big('23.05'), netPerformancePercentage: new Big('0.08437042459736456808'), marketPrice: 148.9, + marketPriceInBaseCurrency: 148.9, quantity: new Big('2'), symbol: 'BALN.SW', transactionCount: 1 diff --git a/apps/api/src/app/portfolio/portfolio-calculator-novn-buy-and-sell-partially.spec.ts b/apps/api/src/app/portfolio/portfolio-calculator-novn-buy-and-sell-partially.spec.ts index d215f9e1e..e31a6f77e 100644 --- a/apps/api/src/app/portfolio/portfolio-calculator-novn-buy-and-sell-partially.spec.ts +++ b/apps/api/src/app/portfolio/portfolio-calculator-novn-buy-and-sell-partially.spec.ts @@ -84,6 +84,7 @@ describe('PortfolioCalculator', () => { netPerformance: new Big('17.68'), netPerformancePercentage: new Big('0.11662269129287598945'), marketPrice: 87.8, + marketPriceInBaseCurrency: 87.8, quantity: new Big('1'), symbol: 'NOVN.SW', transactionCount: 2 diff --git a/apps/api/src/app/portfolio/portfolio-calculator.ts b/apps/api/src/app/portfolio/portfolio-calculator.ts index 555ad1cab..bb9a97de2 100644 --- a/apps/api/src/app/portfolio/portfolio-calculator.ts +++ b/apps/api/src/app/portfolio/portfolio-calculator.ts @@ -36,7 +36,7 @@ export class PortfolioCalculator { private static readonly CALCULATE_PERCENTAGE_PERFORMANCE_WITH_MAX_INVESTMENT = true; - private static readonly ENABLE_LOGGING = false; + private static readonly ENABLE_LOGGING = true; private currency: string; private currentRateService: CurrentRateService; @@ -223,7 +223,9 @@ export class PortfolioCalculator { }); const marketSymbolMap: { - [date: string]: { [symbol: string]: Big }; + [date: string]: { + [symbol: string]: { marketPrice: Big; marketPriceInBaseCurrency: Big }; + }; } = {}; for (const marketSymbol of marketSymbols) { @@ -231,11 +233,12 @@ export class PortfolioCalculator { if (!marketSymbolMap[date]) { marketSymbolMap[date] = {}; } - if (marketSymbol.marketPriceInBaseCurrency) { - marketSymbolMap[date][marketSymbol.symbol] = new Big( + marketSymbolMap[date][marketSymbol.symbol] = { + marketPrice: new Big(marketSymbol.marketPrice), + marketPriceInBaseCurrency: new Big( marketSymbol.marketPriceInBaseCurrency - ); - } + ) + }; } const todayString = format(today, DATE_FORMAT); @@ -251,7 +254,10 @@ export class PortfolioCalculator { const errors: ResponseError['errors'] = []; for (const item of lastTransactionPoint.items) { - const marketValue = marketSymbolMap[todayString]?.[item.symbol]; + const marketPrice = + marketSymbolMap[todayString]?.[item.symbol].marketPrice; + const marketValue = + marketSymbolMap[todayString]?.[item.symbol].marketPriceInBaseCurrency; const { grossPerformance, @@ -281,7 +287,8 @@ export class PortfolioCalculator { ? grossPerformancePercentage ?? null : null, investment: item.investment, - marketPrice: marketValue?.toNumber() ?? null, + marketPrice: marketPrice?.toNumber() ?? null, + marketPriceInBaseCurrency: marketValue?.toNumber() ?? null, netPerformance: !hasErrors ? netPerformance ?? null : null, netPerformancePercentage: !hasErrors ? netPerformancePercentage ?? null @@ -432,9 +439,28 @@ export class PortfolioCalculator { let totalInvestment = new Big(0); for (const currentPosition of positions) { + if (currentPosition.symbol === 'GOOG') { + console.log( + 'grossPerformance', + currentPosition.grossPerformance.toNumber() + ); + console.log( + 'netPerformance', + currentPosition.netPerformance.toNumber() + ); + console.log('marketPrice', currentPosition.marketPrice); + console.log( + 'marketPriceInBaseCurrency', + currentPosition.marketPriceInBaseCurrency + ); + } + + // Market price or market price in base currency? if (currentPosition.marketPrice) { currentValue = currentValue.plus( - new Big(currentPosition.marketPrice).mul(currentPosition.quantity) + currentPosition.quantity.mul( + currentPosition.marketPriceInBaseCurrency + ) ); } else { hasErrors = true; @@ -649,7 +675,9 @@ export class PortfolioCalculator { symbol }: { marketSymbolMap: { - [date: string]: { [symbol: string]: Big }; + [date: string]: { + [symbol: string]: { marketPrice: Big; marketPriceInBaseCurrency: Big }; + }; }; start: Date; symbol: string; @@ -673,10 +701,10 @@ export class PortfolioCalculator { const endDate = new Date(Date.now()); const unitPriceAtStartDate = - marketSymbolMap[format(start, DATE_FORMAT)]?.[symbol]; + marketSymbolMap[format(start, DATE_FORMAT)]?.[symbol]?.marketPrice; const unitPriceAtEndDate = - marketSymbolMap[format(endDate, DATE_FORMAT)]?.[symbol]; + marketSymbolMap[format(endDate, DATE_FORMAT)]?.[symbol]?.marketPrice; if ( !unitPriceAtEndDate || @@ -962,7 +990,9 @@ export class PortfolioCalculator { ) .minus(1); - if (PortfolioCalculator.ENABLE_LOGGING) { + if (PortfolioCalculator.ENABLE_LOGGING && symbol === 'GOOG') { + console.log(orders); + console.log( ` ${symbol} diff --git a/apps/api/src/app/portfolio/portfolio.service.ts b/apps/api/src/app/portfolio/portfolio.service.ts index 840c17f42..2c0394e2a 100644 --- a/apps/api/src/app/portfolio/portfolio.service.ts +++ b/apps/api/src/app/portfolio/portfolio.service.ts @@ -389,7 +389,7 @@ export class PortfolioService { continue; } - const value = item.quantity.mul(item.marketPrice); + const value = item.quantity.mul(item.marketPriceInBaseCurrency); const symbolProfile = symbolProfileMap[item.symbol]; const dataProviderResponse = dataProviderResponses[item.symbol]; @@ -428,7 +428,7 @@ export class PortfolioService { grossPerformancePercent: item.grossPerformancePercentage?.toNumber() ?? 0, investment: item.investment.toNumber(), - marketPrice: item.marketPrice, + marketPrice: item.marketPriceInBaseCurrency, marketState: dataProviderResponse.marketState, name: symbolProfile.name, netPerformance: item.netPerformance?.toNumber() ?? 0, @@ -508,11 +508,10 @@ export class PortfolioService { quantity: undefined, SymbolProfile: undefined, transactionCount: undefined, - value: undefined + valueInBaseCurrency: undefined }; } - const positionCurrency = orders[0].SymbolProfile.currency; const [SymbolProfile] = await this.symbolProfileService.getSymbolProfiles([ aSymbol ]); @@ -538,7 +537,7 @@ export class PortfolioService { tags = uniqBy(tags, 'id'); const portfolioCalculator = new PortfolioCalculator({ - currency: positionCurrency, + currency: userCurrency, currentRateService: this.currentRateService, orders: portfolioOrders }); @@ -562,6 +561,7 @@ export class PortfolioService { dataSource, firstBuyDate, marketPrice, + marketPriceInBaseCurrency, quantity, transactionCount } = position; @@ -653,11 +653,7 @@ export class PortfolioService { historicalData: historicalDataArray, netPerformancePercent: position.netPerformancePercentage?.toNumber(), quantity: quantity.toNumber(), - value: this.exchangeRateDataService.toCurrency( - quantity.mul(marketPrice).toNumber(), - currency, - userCurrency - ) + valueInBaseCurrency: quantity.mul(marketPriceInBaseCurrency).toNumber() }; } else { const currentData = await this.dataProviderService.getQuotes([ @@ -713,7 +709,7 @@ export class PortfolioService { netPerformancePercent: undefined, quantity: 0, transactionCount: undefined, - value: 0 + valueInBaseCurrency: 0 }; } } @@ -1321,7 +1317,8 @@ export class PortfolioService { for (const order of ordersByAccount) { let currentValueOfSymbolInBaseCurrency = order.quantity * - portfolioItemsNow[order.SymbolProfile.symbol].marketPrice; + portfolioItemsNow[order.SymbolProfile.symbol] + .marketPriceInBaseCurrency; let originalValueOfSymbolInBaseCurrency = this.exchangeRateDataService.toCurrency( order.quantity * order.unitPrice, diff --git a/apps/api/src/models/rule.ts b/apps/api/src/models/rule.ts index 6bbb71e07..231df4ca5 100644 --- a/apps/api/src/models/rule.ts +++ b/apps/api/src/models/rule.ts @@ -42,7 +42,9 @@ export abstract class Rule implements RuleInterface { (previousValue, currentValue) => previousValue + this.exchangeRateDataService.toCurrency( - currentValue.quantity.mul(currentValue.marketPrice).toNumber(), + currentValue.quantity + .mul(currentValue.marketPriceInBaseCurrency) + .toNumber(), currentValue.currency, baseCurrency ), diff --git a/apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.component.ts b/apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.component.ts index 341b4abc0..fbd6af044 100644 --- a/apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.component.ts +++ b/apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.component.ts @@ -7,11 +7,12 @@ import { OnInit } from '@angular/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { EnhancedSymbolProfile } from '@ghostfolio/api/services/interfaces/symbol-profile.interface'; import { DataService } from '@ghostfolio/client/services/data.service'; import { DATE_FORMAT, downloadAsFile } from '@ghostfolio/common/helper'; import { OrderWithAccount } from '@ghostfolio/common/types'; import { LineChartItem } from '@ghostfolio/ui/line-chart/interfaces/line-chart.interface'; -import { SymbolProfile, Tag } from '@prisma/client'; +import { Tag } from '@prisma/client'; import { format, isSameMonth, isToday, parseISO } from 'date-fns'; import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; @@ -48,7 +49,7 @@ export class PositionDetailDialog implements OnDestroy, OnInit { public sectors: { [name: string]: { name: string; value: number }; }; - public SymbolProfile: SymbolProfile; + public SymbolProfile: EnhancedSymbolProfile; public tags: Tag[]; public transactionCount: number; public value: number; @@ -87,7 +88,7 @@ export class PositionDetailDialog implements OnDestroy, OnInit { SymbolProfile, tags, transactionCount, - value + valueInBaseCurrency }) => { this.averagePrice = averagePrice; this.benchmarkDataItems = []; @@ -121,7 +122,7 @@ export class PositionDetailDialog implements OnDestroy, OnInit { this.SymbolProfile = SymbolProfile; this.tags = tags; this.transactionCount = transactionCount; - this.value = value; + this.value = valueInBaseCurrency; if (SymbolProfile?.countries?.length > 0) { for (const country of SymbolProfile.countries) { diff --git a/apps/client/src/app/services/data.service.ts b/apps/client/src/app/services/data.service.ts index 58a900a72..5c946d036 100644 --- a/apps/client/src/app/services/data.service.ts +++ b/apps/client/src/app/services/data.service.ts @@ -6,6 +6,7 @@ import { UpdateAccountDto } from '@ghostfolio/api/app/account/update-account.dto import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto'; import { Activities } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { UpdateOrderDto } from '@ghostfolio/api/app/order/update-order.dto'; +import { PortfolioPositionDetail } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-position-detail.interface'; import { PortfolioPositions } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-positions.interface'; import { LookupItem } from '@ghostfolio/api/app/symbol/interfaces/lookup-item.interface'; import { SymbolItem } from '@ghostfolio/api/app/symbol/interfaces/symbol-item.interface'; @@ -273,13 +274,15 @@ export class DataService { symbol: string; }) { return this.http - .get(`/api/v1/portfolio/position/${dataSource}/${symbol}`) + .get( + `/api/v1/portfolio/position/${dataSource}/${symbol}` + ) .pipe( map((data) => { if (data.orders) { for (const order of data.orders) { - order.createdAt = parseISO(order.createdAt); - order.date = parseISO(order.date); + order.createdAt = parseISO((order.createdAt)); + order.date = parseISO((order.date)); } } diff --git a/libs/common/src/lib/interfaces/timeline-position.interface.ts b/libs/common/src/lib/interfaces/timeline-position.interface.ts index 2e5aa6333..a2bd0a815 100644 --- a/libs/common/src/lib/interfaces/timeline-position.interface.ts +++ b/libs/common/src/lib/interfaces/timeline-position.interface.ts @@ -10,6 +10,7 @@ export interface TimelinePosition { grossPerformancePercentage: Big; investment: Big; marketPrice: number; + marketPriceInBaseCurrency: number; netPerformance: Big; netPerformancePercentage: Big; quantity: Big;