diff --git a/apps/api/src/app/portfolio/interfaces/portfolio-order.interface.ts b/apps/api/src/app/portfolio/interfaces/portfolio-order.interface.ts index 558a4643d..63a936c32 100644 --- a/apps/api/src/app/portfolio/interfaces/portfolio-order.interface.ts +++ b/apps/api/src/app/portfolio/interfaces/portfolio-order.interface.ts @@ -1,15 +1,12 @@ -import { DataSource, Tag, Type as ActivityType } from '@prisma/client'; -import { Big } from 'big.js'; +import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; -export interface PortfolioOrder { - currency: string; +export interface PortfolioOrder extends Pick { date: string; - dataSource: DataSource; fee: Big; - name: string; quantity: Big; - symbol: string; - tags?: Tag[]; - type: ActivityType; + SymbolProfile: Pick< + Activity['SymbolProfile'], + 'currency' | 'dataSource' | 'name' | 'symbol' + >; unitPrice: Big; } diff --git a/apps/api/src/app/portfolio/portfolio-calculator-baln-buy-and-sell-in-two-activities.spec.ts b/apps/api/src/app/portfolio/portfolio-calculator-baln-buy-and-sell-in-two-activities.spec.ts index 4db8dcdb7..4a90409c9 100644 --- a/apps/api/src/app/portfolio/portfolio-calculator-baln-buy-and-sell-in-two-activities.spec.ts +++ b/apps/api/src/app/portfolio/portfolio-calculator-baln-buy-and-sell-in-two-activities.spec.ts @@ -1,3 +1,4 @@ +import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { parseDate } from '@ghostfolio/common/helper'; @@ -36,46 +37,50 @@ describe('PortfolioCalculator', () => { const portfolioCalculator = new PortfolioCalculator({ currentRateService, exchangeRateDataService, - currency: 'CHF', - orders: [ + activities: [ { - currency: 'CHF', - date: '2021-11-22', - dataSource: 'YAHOO', - fee: new Big(1.55), - name: 'Bâloise Holding AG', - quantity: new Big(2), - symbol: 'BALN.SW', + date: new Date('2021-11-22'), + fee: 1.55, + quantity: 2, + SymbolProfile: { + currency: 'CHF', + dataSource: 'YAHOO', + name: 'Bâloise Holding AG', + symbol: 'BALN.SW' + }, type: 'BUY', - unitPrice: new Big(142.9) + unitPrice: 142.9 }, { - currency: 'CHF', - date: '2021-11-30', - dataSource: 'YAHOO', - fee: new Big(1.65), - name: 'Bâloise Holding AG', - quantity: new Big(1), - symbol: 'BALN.SW', + date: new Date('2021-11-30'), + fee: 1.65, + quantity: 1, + SymbolProfile: { + currency: 'CHF', + dataSource: 'YAHOO', + name: 'Bâloise Holding AG', + symbol: 'BALN.SW' + }, type: 'SELL', - unitPrice: new Big(136.6) + unitPrice: 136.6 }, { - currency: 'CHF', - date: '2021-11-30', - dataSource: 'YAHOO', - fee: new Big(0), - name: 'Bâloise Holding AG', - quantity: new Big(1), - symbol: 'BALN.SW', + date: new Date('2021-11-30'), + fee: 0, + quantity: 1, + SymbolProfile: { + currency: 'CHF', + dataSource: 'YAHOO', + name: 'Bâloise Holding AG', + symbol: 'BALN.SW' + }, type: 'SELL', - unitPrice: new Big(136.6) + unitPrice: 136.6 } - ] + ], + currency: 'CHF' }); - portfolioCalculator.computeTransactionPoints(); - const spy = jest .spyOn(Date, 'now') .mockImplementation(() => parseDate('2021-12-18').getTime()); 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 b99f93252..6176acbb5 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 @@ -1,3 +1,4 @@ +import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { parseDate } from '@ghostfolio/common/helper'; @@ -36,35 +37,37 @@ describe('PortfolioCalculator', () => { const portfolioCalculator = new PortfolioCalculator({ currentRateService, exchangeRateDataService, - currency: 'CHF', - orders: [ + activities: [ { - currency: 'CHF', - date: '2021-11-22', - dataSource: 'YAHOO', - fee: new Big(1.55), - name: 'Bâloise Holding AG', - quantity: new Big(2), - symbol: 'BALN.SW', + date: new Date('2021-11-22'), + fee: 1.55, + quantity: 2, + SymbolProfile: { + currency: 'CHF', + dataSource: 'YAHOO', + name: 'Bâloise Holding AG', + symbol: 'BALN.SW' + }, type: 'BUY', - unitPrice: new Big(142.9) + unitPrice: 142.9 }, { - currency: 'CHF', - date: '2021-11-30', - dataSource: 'YAHOO', - fee: new Big(1.65), - name: 'Bâloise Holding AG', - quantity: new Big(2), - symbol: 'BALN.SW', + date: new Date('2021-11-30'), + fee: 1.65, + quantity: 2, + SymbolProfile: { + currency: 'CHF', + dataSource: 'YAHOO', + name: 'Bâloise Holding AG', + symbol: 'BALN.SW' + }, type: 'SELL', - unitPrice: new Big(136.6) + unitPrice: 136.6 } - ] + ], + currency: 'CHF' }); - portfolioCalculator.computeTransactionPoints(); - const spy = jest .spyOn(Date, 'now') .mockImplementation(() => parseDate('2021-12-18').getTime()); 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 15208aca5..61dfb5915 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 @@ -1,3 +1,4 @@ +import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { parseDate } from '@ghostfolio/common/helper'; @@ -36,24 +37,24 @@ describe('PortfolioCalculator', () => { const portfolioCalculator = new PortfolioCalculator({ currentRateService, exchangeRateDataService, - currency: 'CHF', - orders: [ + activities: [ { - currency: 'CHF', - date: '2021-11-30', - dataSource: 'YAHOO', - fee: new Big(1.55), - name: 'Bâloise Holding AG', - quantity: new Big(2), - symbol: 'BALN.SW', + date: new Date('2021-11-30'), + fee: 1.55, + quantity: 2, + SymbolProfile: { + currency: 'CHF', + dataSource: 'YAHOO', + name: 'Bâloise Holding AG', + symbol: 'BALN.SW' + }, type: 'BUY', - unitPrice: new Big(136.6) + unitPrice: 136.6 } - ] + ], + currency: 'CHF' }); - portfolioCalculator.computeTransactionPoints(); - const spy = jest .spyOn(Date, 'now') .mockImplementation(() => parseDate('2021-12-18').getTime()); diff --git a/apps/api/src/app/portfolio/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts b/apps/api/src/app/portfolio/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts index e9d88721d..d17a12ba8 100644 --- a/apps/api/src/app/portfolio/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts +++ b/apps/api/src/app/portfolio/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts @@ -1,3 +1,4 @@ +import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { ExchangeRateDataServiceMock } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service.mock'; @@ -49,35 +50,37 @@ describe('PortfolioCalculator', () => { const portfolioCalculator = new PortfolioCalculator({ currentRateService, exchangeRateDataService, - currency: 'CHF', - orders: [ + activities: [ { - currency: 'USD', - date: '2015-01-01', - dataSource: 'YAHOO', - fee: new Big(0), - name: 'Bitcoin USD', - quantity: new Big(2), - symbol: 'BTCUSD', + date: new Date('2015-01-01'), + fee: 0, + quantity: 2, + SymbolProfile: { + currency: 'USD', + dataSource: 'YAHOO', + name: 'Bitcoin USD', + symbol: 'BTCUSD' + }, type: 'BUY', - unitPrice: new Big(320.43) + unitPrice: 320.43 }, { - currency: 'USD', - date: '2017-12-31', - dataSource: 'YAHOO', - fee: new Big(0), - name: 'Bitcoin USD', - quantity: new Big(1), - symbol: 'BTCUSD', + date: new Date('2017-12-31'), + fee: 0, + quantity: 1, + SymbolProfile: { + currency: 'USD', + dataSource: 'YAHOO', + name: 'Bitcoin USD', + symbol: 'BTCUSD' + }, type: 'SELL', - unitPrice: new Big(14156.4) + unitPrice: 14156.4 } - ] + ], + currency: 'CHF' }); - portfolioCalculator.computeTransactionPoints(); - const spy = jest .spyOn(Date, 'now') .mockImplementation(() => parseDate('2018-01-01').getTime()); diff --git a/apps/api/src/app/portfolio/portfolio-calculator-googl-buy.spec.ts b/apps/api/src/app/portfolio/portfolio-calculator-googl-buy.spec.ts index 1a672766d..5e870b447 100644 --- a/apps/api/src/app/portfolio/portfolio-calculator-googl-buy.spec.ts +++ b/apps/api/src/app/portfolio/portfolio-calculator-googl-buy.spec.ts @@ -1,3 +1,4 @@ +import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { ExchangeRateDataServiceMock } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service.mock'; @@ -49,24 +50,24 @@ describe('PortfolioCalculator', () => { const portfolioCalculator = new PortfolioCalculator({ currentRateService, exchangeRateDataService, - currency: 'CHF', - orders: [ + activities: [ { - currency: 'USD', - date: '2023-01-03', - dataSource: 'YAHOO', - fee: new Big(1), - name: 'Alphabet Inc.', - quantity: new Big(1), - symbol: 'GOOGL', + date: new Date('2023-01-03'), + fee: 1, + quantity: 1, + SymbolProfile: { + currency: 'USD', + dataSource: 'YAHOO', + name: 'Alphabet Inc.', + symbol: 'GOOGL' + }, type: 'BUY', - unitPrice: new Big(89.12) + unitPrice: 89.12 } - ] + ], + currency: 'CHF' }); - portfolioCalculator.computeTransactionPoints(); - const spy = jest .spyOn(Date, 'now') .mockImplementation(() => parseDate('2023-07-10').getTime()); diff --git a/apps/api/src/app/portfolio/portfolio-calculator-msft-buy-with-dividend.spec.ts b/apps/api/src/app/portfolio/portfolio-calculator-msft-buy-with-dividend.spec.ts index 8920a6cba..9ec51094e 100644 --- a/apps/api/src/app/portfolio/portfolio-calculator-msft-buy-with-dividend.spec.ts +++ b/apps/api/src/app/portfolio/portfolio-calculator-msft-buy-with-dividend.spec.ts @@ -1,3 +1,4 @@ +import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { ExchangeRateDataServiceMock } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service.mock'; @@ -49,35 +50,37 @@ describe('PortfolioCalculator', () => { const portfolioCalculator = new PortfolioCalculator({ currentRateService, exchangeRateDataService, - currency: 'USD', - orders: [ + activities: [ { - currency: 'USD', - date: '2021-09-16', - dataSource: 'YAHOO', - fee: new Big(19), - name: 'Microsoft Inc.', - quantity: new Big(1), - symbol: 'MSFT', + date: new Date('2021-09-16'), + fee: 19, + quantity: 1, + SymbolProfile: { + currency: 'USD', + dataSource: 'YAHOO', + name: 'Microsoft Inc.', + symbol: 'MSFT' + }, type: 'BUY', - unitPrice: new Big(298.58) + unitPrice: 298.58 }, { - currency: 'USD', - date: '2021-11-16', - dataSource: 'YAHOO', - fee: new Big(0), - name: 'Microsoft Inc.', - quantity: new Big(1), - symbol: 'MSFT', + date: new Date('2021-11-16'), + fee: 0, + quantity: 1, + SymbolProfile: { + currency: 'USD', + dataSource: 'YAHOO', + name: 'Microsoft Inc.', + symbol: 'MSFT' + }, type: 'DIVIDEND', - unitPrice: new Big(0.62) + unitPrice: 0.62 } - ] + ], + currency: 'USD' }); - portfolioCalculator.computeTransactionPoints(); - const spy = jest .spyOn(Date, 'now') .mockImplementation(() => parseDate('2023-07-10').getTime()); diff --git a/apps/api/src/app/portfolio/portfolio-calculator-no-orders.spec.ts b/apps/api/src/app/portfolio/portfolio-calculator-no-orders.spec.ts index fdbdb78bd..9947e2f45 100644 --- a/apps/api/src/app/portfolio/portfolio-calculator-no-orders.spec.ts +++ b/apps/api/src/app/portfolio/portfolio-calculator-no-orders.spec.ts @@ -37,12 +37,10 @@ describe('PortfolioCalculator', () => { const portfolioCalculator = new PortfolioCalculator({ currentRateService, exchangeRateDataService, - currency: 'CHF', - orders: [] + activities: [], + currency: 'CHF' }); - portfolioCalculator.computeTransactionPoints(); - const spy = jest .spyOn(Date, 'now') .mockImplementation(() => parseDate('2021-12-18').getTime()); 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 d71a97072..8eeb216ba 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 @@ -1,3 +1,4 @@ +import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { parseDate } from '@ghostfolio/common/helper'; @@ -36,35 +37,37 @@ describe('PortfolioCalculator', () => { const portfolioCalculator = new PortfolioCalculator({ currentRateService, exchangeRateDataService, - currency: 'CHF', - orders: [ + activities: [ { - currency: 'CHF', - date: '2022-03-07', - dataSource: 'YAHOO', - fee: new Big(1.3), - name: 'Novartis AG', - quantity: new Big(2), - symbol: 'NOVN.SW', + date: new Date('2022-03-07'), + fee: 1.3, + quantity: 2, + SymbolProfile: { + currency: 'CHF', + dataSource: 'YAHOO', + name: 'Novartis AG', + symbol: 'NOVN.SW' + }, type: 'BUY', - unitPrice: new Big(75.8) + unitPrice: 75.8 }, { - currency: 'CHF', - date: '2022-04-08', - dataSource: 'YAHOO', - fee: new Big(2.95), - name: 'Novartis AG', - quantity: new Big(1), - symbol: 'NOVN.SW', + date: new Date('2022-04-08'), + fee: 2.95, + quantity: 1, + SymbolProfile: { + currency: 'CHF', + dataSource: 'YAHOO', + name: 'Novartis AG', + symbol: 'NOVN.SW' + }, type: 'SELL', - unitPrice: new Big(85.73) + unitPrice: 85.73 } - ] + ], + currency: 'CHF' }); - portfolioCalculator.computeTransactionPoints(); - const spy = jest .spyOn(Date, 'now') .mockImplementation(() => parseDate('2022-04-11').getTime()); diff --git a/apps/api/src/app/portfolio/portfolio-calculator-novn-buy-and-sell.spec.ts b/apps/api/src/app/portfolio/portfolio-calculator-novn-buy-and-sell.spec.ts index a27b6d42a..ec00fffe7 100644 --- a/apps/api/src/app/portfolio/portfolio-calculator-novn-buy-and-sell.spec.ts +++ b/apps/api/src/app/portfolio/portfolio-calculator-novn-buy-and-sell.spec.ts @@ -1,3 +1,4 @@ +import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { parseDate } from '@ghostfolio/common/helper'; @@ -36,35 +37,37 @@ describe('PortfolioCalculator', () => { const portfolioCalculator = new PortfolioCalculator({ currentRateService, exchangeRateDataService, - currency: 'CHF', - orders: [ + activities: [ { - currency: 'CHF', - date: '2022-03-07', - dataSource: 'YAHOO', - fee: new Big(0), - name: 'Novartis AG', - quantity: new Big(2), - symbol: 'NOVN.SW', + date: new Date('2022-03-07'), + fee: 0, + quantity: 2, + SymbolProfile: { + currency: 'CHF', + dataSource: 'YAHOO', + name: 'Novartis AG', + symbol: 'NOVN.SW' + }, type: 'BUY', - unitPrice: new Big(75.8) + unitPrice: 75.8 }, { - currency: 'CHF', - date: '2022-04-08', - dataSource: 'YAHOO', - fee: new Big(0), - name: 'Novartis AG', - quantity: new Big(2), - symbol: 'NOVN.SW', + date: new Date('2022-04-08'), + fee: 0, + quantity: 2, + SymbolProfile: { + currency: 'CHF', + dataSource: 'YAHOO', + name: 'Novartis AG', + symbol: 'NOVN.SW' + }, type: 'SELL', - unitPrice: new Big(85.73) + unitPrice: 85.73 } - ] + ], + currency: 'CHF' }); - portfolioCalculator.computeTransactionPoints(); - const spy = jest .spyOn(Date, 'now') .mockImplementation(() => parseDate('2022-04-11').getTime()); diff --git a/apps/api/src/app/portfolio/portfolio-calculator.spec.ts b/apps/api/src/app/portfolio/portfolio-calculator.spec.ts index b8d784dc3..a385774ff 100644 --- a/apps/api/src/app/portfolio/portfolio-calculator.spec.ts +++ b/apps/api/src/app/portfolio/portfolio-calculator.spec.ts @@ -22,10 +22,10 @@ describe('PortfolioCalculator', () => { describe('annualized performance percentage', () => { const portfolioCalculator = new PortfolioCalculator({ + activities: [], currentRateService, exchangeRateDataService, - currency: 'USD', - orders: [] + currency: 'USD' }); it('Get annualized performance', async () => { diff --git a/apps/api/src/app/portfolio/portfolio-calculator.ts b/apps/api/src/app/portfolio/portfolio-calculator.ts index d37a872c5..d4fc49189 100644 --- a/apps/api/src/app/portfolio/portfolio-calculator.ts +++ b/apps/api/src/app/portfolio/portfolio-calculator.ts @@ -1,3 +1,4 @@ +import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { getFactor } from '@ghostfolio/api/helper/portfolio.helper'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { IDataGatheringItem } from '@ghostfolio/api/services/interfaces/interfaces'; @@ -8,7 +9,8 @@ import { InvestmentItem, ResponseError, SymbolMetrics, - TimelinePosition + TimelinePosition, + UniqueAsset } from '@ghostfolio/common/interfaces'; import { GroupBy } from '@ghostfolio/common/types'; @@ -46,33 +48,40 @@ export class PortfolioCalculator { private transactionPoints: TransactionPoint[]; public constructor({ + activities, currency, currentRateService, - exchangeRateDataService, - orders, - transactionPoints + exchangeRateDataService }: { + activities: Activity[]; currency: string; currentRateService: CurrentRateService; exchangeRateDataService: ExchangeRateDataService; - orders: PortfolioOrder[]; - transactionPoints?: TransactionPoint[]; }) { this.currency = currency; this.currentRateService = currentRateService; this.exchangeRateDataService = exchangeRateDataService; - this.orders = orders; + this.orders = activities.map( + ({ date, fee, quantity, SymbolProfile, type, unitPrice }) => { + return { + SymbolProfile, + type, + date: format(date, DATE_FORMAT), + fee: new Big(fee), + quantity: new Big(quantity), + unitPrice: new Big(unitPrice) + }; + } + ); this.orders.sort((a, b) => { return a.date?.localeCompare(b.date); }); - if (transactionPoints) { - this.transactionPoints = transactionPoints; - } + this.computeTransactionPoints(); } - public computeTransactionPoints() { + private computeTransactionPoints() { this.transactionPoints = []; const symbols: { [symbol: string]: TransactionPointSymbol } = {}; @@ -83,7 +92,7 @@ export class PortfolioCalculator { const currentDate = order.date; let currentTransactionPointItem: TransactionPointSymbol; - const oldAccumulatedSymbol = symbols[order.symbol]; + const oldAccumulatedSymbol = symbols[order.SymbolProfile.symbol]; const factor = getFactor(order.type); @@ -109,38 +118,39 @@ export class PortfolioCalculator { averagePrice: newQuantity.gt(0) ? investment.div(newQuantity) : new Big(0), - currency: order.currency, - dataSource: order.dataSource, + currency: order.SymbolProfile.currency, + dataSource: order.SymbolProfile.dataSource, dividend: new Big(0), fee: order.fee.plus(oldAccumulatedSymbol.fee), firstBuyDate: oldAccumulatedSymbol.firstBuyDate, quantity: newQuantity, - symbol: order.symbol, + symbol: order.SymbolProfile.symbol, tags: order.tags, transactionCount: oldAccumulatedSymbol.transactionCount + 1 }; } else { currentTransactionPointItem = { averagePrice: order.unitPrice, - currency: order.currency, - dataSource: order.dataSource, + currency: order.SymbolProfile.currency, + dataSource: order.SymbolProfile.dataSource, dividend: new Big(0), fee: order.fee, firstBuyDate: order.date, investment: order.unitPrice.mul(order.quantity).mul(factor), quantity: order.quantity.mul(factor), - symbol: order.symbol, + symbol: order.SymbolProfile.symbol, tags: order.tags, transactionCount: 1 }; } - symbols[order.symbol] = currentTransactionPointItem; + symbols[order.SymbolProfile.symbol] = currentTransactionPointItem; const items = lastTransactionPoint?.items ?? []; const newItems = items.filter( - (transactionPointItem) => transactionPointItem.symbol !== order.symbol + (transactionPointItem) => + transactionPointItem.symbol !== order.SymbolProfile.symbol ); newItems.push(currentTransactionPointItem); @@ -309,6 +319,7 @@ export class PortfolioCalculator { start, step, symbol, + dataSource: null, exchangeRates: exchangeRatesByCurrency[`${currencies[symbol]}${this.currency}`], isChartMode: true @@ -625,6 +636,7 @@ export class PortfolioCalculator { } = this.getSymbolMetrics({ marketSymbolMap, start, + dataSource: item.dataSource, end: endDate, exchangeRates: exchangeRatesByCurrency[`${item.currency}${this.currency}`], @@ -845,6 +857,7 @@ export class PortfolioCalculator { } private getSymbolMetrics({ + dataSource, end, exchangeRates, isChartMode = false, @@ -861,8 +874,7 @@ export class PortfolioCalculator { }; start: Date; step?: number; - symbol: string; - }): SymbolMetrics { + } & UniqueAsset): SymbolMetrics { const currentExchangeRate = exchangeRates[format(new Date(), DATE_FORMAT)]; const currentValues: { [date: string]: Big } = {}; const currentValuesWithCurrencyEffect: { [date: string]: Big } = {}; @@ -908,8 +920,8 @@ export class PortfolioCalculator { // Clone orders to keep the original values in this.orders let orders: PortfolioOrderItem[] = cloneDeep(this.orders).filter( - (order) => { - return order.symbol === symbol; + ({ SymbolProfile }) => { + return SymbolProfile.symbol === symbol; } ); @@ -988,28 +1000,28 @@ export class PortfolioCalculator { // Add a synthetic order at the start and the end date orders.push({ - symbol, - currency: null, date: format(start, DATE_FORMAT), - dataSource: null, fee: new Big(0), feeInBaseCurrency: new Big(0), itemType: 'start', - name: '', quantity: new Big(0), + SymbolProfile: { + dataSource, + symbol + }, type: 'BUY', unitPrice: unitPriceAtStartDate }); orders.push({ - symbol, - currency: null, date: format(end, DATE_FORMAT), - dataSource: null, fee: new Big(0), feeInBaseCurrency: new Big(0), itemType: 'end', - name: '', + SymbolProfile: { + dataSource, + symbol + }, quantity: new Big(0), type: 'BUY', unitPrice: unitPriceAtEndDate @@ -1030,14 +1042,14 @@ export class PortfolioCalculator { if (!hasDate) { orders.push({ - symbol, - currency: null, date: format(day, DATE_FORMAT), - dataSource: null, fee: new Big(0), feeInBaseCurrency: new Big(0), - name: '', quantity: new Big(0), + SymbolProfile: { + dataSource, + symbol + }, type: 'BUY', unitPrice: marketSymbolMap[format(day, DATE_FORMAT)]?.[symbol] ?? diff --git a/apps/api/src/app/portfolio/portfolio.service.ts b/apps/api/src/app/portfolio/portfolio.service.ts index 061c4b8be..028a3a2d4 100644 --- a/apps/api/src/app/portfolio/portfolio.service.ts +++ b/apps/api/src/app/portfolio/portfolio.service.ts @@ -266,13 +266,12 @@ export class PortfolioService { }): Promise { const userId = await this.getUserId(impersonationId, this.request.user.id); - const { portfolioOrders, transactionPoints } = - await this.getTransactionPoints({ - filters, - userId, - includeDrafts: true, - types: ['BUY', 'SELL'] - }); + const { activities, transactionPoints } = await this.getTransactionPoints({ + filters, + userId, + includeDrafts: true, + types: ['BUY', 'SELL'] + }); if (transactionPoints.length === 0) { return { @@ -282,11 +281,10 @@ export class PortfolioService { } const portfolioCalculator = new PortfolioCalculator({ - transactionPoints, + activities, currency: this.request.user.Settings.settings.baseCurrency, currentRateService: this.currentRateService, - exchangeRateDataService: this.exchangeRateDataService, - orders: portfolioOrders + exchangeRateDataService: this.exchangeRateDataService }); const { items } = await this.getChart({ @@ -364,20 +362,18 @@ export class PortfolioService { }); } - const { activities, portfolioOrders, transactionPoints } = - await this.getTransactionPoints({ - filters, - types, - userId, - withExcludedAccounts - }); + const { activities, transactionPoints } = await this.getTransactionPoints({ + filters, + types, + userId, + withExcludedAccounts + }); const portfolioCalculator = new PortfolioCalculator({ - transactionPoints, + activities, currency: userCurrency, currentRateService: this.currentRateService, - exchangeRateDataService: this.exchangeRateDataService, - orders: portfolioOrders + exchangeRateDataService: this.exchangeRateDataService }); const portfolioStart = parseDate( @@ -737,35 +733,19 @@ export class PortfolioService { { dataSource: aDataSource, symbol: aSymbol } ]); - const portfolioOrders: PortfolioOrder[] = orders - .filter((order) => { - tags = tags.concat(order.tags); - - return ['BUY', 'DIVIDEND', 'ITEM', 'SELL'].includes(order.type); - }) - .map((order) => ({ - currency: order.SymbolProfile.currency, - dataSource: order.SymbolProfile.dataSource, - date: format(order.date, DATE_FORMAT), - fee: new Big(order.fee), - name: order.SymbolProfile?.name, - quantity: new Big(order.quantity), - symbol: order.SymbolProfile.symbol, - tags: order.tags, - type: order.type, - unitPrice: new Big(order.unitPrice) - })); - tags = uniqBy(tags, 'id'); const portfolioCalculator = new PortfolioCalculator({ + activities: orders.filter((order) => { + tags = tags.concat(order.tags); + + return ['BUY', 'DIVIDEND', 'ITEM', 'SELL'].includes(order.type); + }), currency: userCurrency, currentRateService: this.currentRateService, - exchangeRateDataService: this.exchangeRateDataService, - orders: portfolioOrders + exchangeRateDataService: this.exchangeRateDataService }); - portfolioCalculator.computeTransactionPoints(); const transactionPoints = portfolioCalculator.getTransactionPoints(); const portfolioStart = parseDate(transactionPoints[0].date); @@ -982,12 +962,11 @@ export class PortfolioService { const userId = await this.getUserId(impersonationId, this.request.user.id); const user = await this.userService.user({ id: userId }); - const { portfolioOrders, transactionPoints } = - await this.getTransactionPoints({ - filters, - userId, - types: ['BUY', 'SELL'] - }); + const { activities, transactionPoints } = await this.getTransactionPoints({ + filters, + userId, + types: ['BUY', 'SELL'] + }); if (transactionPoints?.length <= 0) { return { @@ -997,11 +976,10 @@ export class PortfolioService { } const portfolioCalculator = new PortfolioCalculator({ - transactionPoints, + activities, currency: this.request.user.Settings.settings.baseCurrency, currentRateService: this.currentRateService, - exchangeRateDataService: this.exchangeRateDataService, - orders: portfolioOrders + exchangeRateDataService: this.exchangeRateDataService }); const portfolioStart = parseDate(transactionPoints[0].date); @@ -1154,13 +1132,12 @@ export class PortfolioService { ) ); - const { portfolioOrders, transactionPoints } = - await this.getTransactionPoints({ - filters, - userId, - withExcludedAccounts, - types: withItems ? ['BUY', 'ITEM', 'SELL'] : ['BUY', 'SELL'] - }); + const { activities, transactionPoints } = await this.getTransactionPoints({ + filters, + userId, + withExcludedAccounts, + types: withItems ? ['BUY', 'ITEM', 'SELL'] : ['BUY', 'SELL'] + }); if (accountBalanceItems?.length <= 0 && transactionPoints?.length <= 0) { return { @@ -1184,11 +1161,10 @@ export class PortfolioService { } const portfolioCalculator = new PortfolioCalculator({ - transactionPoints, + activities, currency: userCurrency, currentRateService: this.currentRateService, - exchangeRateDataService: this.exchangeRateDataService, - orders: portfolioOrders + exchangeRateDataService: this.exchangeRateDataService }); const portfolioStart = min( @@ -1307,18 +1283,16 @@ export class PortfolioService { const user = await this.userService.user({ id: userId }); const userCurrency = this.getUserCurrency(user); - const { activities, portfolioOrders, transactionPoints } = - await this.getTransactionPoints({ - userId, - types: ['BUY', 'SELL'] - }); + const { activities, transactionPoints } = await this.getTransactionPoints({ + userId, + types: ['BUY', 'SELL'] + }); const portfolioCalculator = new PortfolioCalculator({ - transactionPoints, + activities, currency: userCurrency, currentRateService: this.currentRateService, - exchangeRateDataService: this.exchangeRateDataService, - orders: portfolioOrders + exchangeRateDataService: this.exchangeRateDataService }); const portfolioStart = parseDate( @@ -1865,10 +1839,10 @@ export class PortfolioService { const daysInMarket = differenceInDays(new Date(), firstOrderDate); const annualizedPerformancePercent = new PortfolioCalculator({ + activities: [], currency: userCurrency, currentRateService: this.currentRateService, - exchangeRateDataService: this.exchangeRateDataService, - orders: [] + exchangeRateDataService: this.exchangeRateDataService }) .getAnnualizedPerformancePercent({ daysInMarket, @@ -1880,10 +1854,10 @@ export class PortfolioService { const annualizedPerformancePercentWithCurrencyEffect = new PortfolioCalculator({ + activities: [], currency: userCurrency, currentRateService: this.currentRateService, - exchangeRateDataService: this.exchangeRateDataService, - orders: [] + exchangeRateDataService: this.exchangeRateDataService }) .getAnnualizedPerformancePercent({ daysInMarket, @@ -1955,6 +1929,7 @@ export class PortfolioService { ); } + // TODO: Eliminate private async getTransactionPoints({ filters, includeDrafts = false, @@ -1989,27 +1964,26 @@ export class PortfolioService { } const portfolioOrders: PortfolioOrder[] = activities.map((order) => ({ - currency: order.SymbolProfile.currency, - dataSource: order.SymbolProfile.dataSource, + // currency: order.SymbolProfile.currency, + // dataSource: order.SymbolProfile.dataSource, date: format(order.date, DATE_FORMAT), fee: new Big(order.fee), - name: order.SymbolProfile?.name, + // name: order.SymbolProfile?.name, quantity: new Big(order.quantity), - symbol: order.SymbolProfile.symbol, - tags: order.tags, + // symbol: order.SymbolProfile.symbol, + SymbolProfile: order.SymbolProfile, + // tags: order.tags, type: order.type, unitPrice: new Big(order.unitPrice) })); const portfolioCalculator = new PortfolioCalculator({ + activities, currency: userCurrency, currentRateService: this.currentRateService, - exchangeRateDataService: this.exchangeRateDataService, - orders: portfolioOrders + exchangeRateDataService: this.exchangeRateDataService }); - portfolioCalculator.computeTransactionPoints(); - return { activities, portfolioOrders,