|  |  | @ -37,13 +37,15 @@ import { | 
			
		
	
		
			
				
					|  |  |  |   isBefore, | 
			
		
	
		
			
				
					|  |  |  |   isSameDay, | 
			
		
	
		
			
				
					|  |  |  |   max, | 
			
		
	
		
			
				
					|  |  |  |   min, | 
			
		
	
		
			
				
					|  |  |  |   subDays | 
			
		
	
		
			
				
					|  |  |  | } from 'date-fns'; | 
			
		
	
		
			
				
					|  |  |  | import { last, uniq, uniqBy } from 'lodash'; | 
			
		
	
		
			
				
					|  |  |  | import { first, last, uniq, uniqBy } from 'lodash'; | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  | export abstract class PortfolioCalculator { | 
			
		
	
		
			
				
					|  |  |  |   protected static readonly ENABLE_LOGGING = false; | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |   protected accountBalanceItems: HistoricalDataItem[]; | 
			
		
	
		
			
				
					|  |  |  |   protected orders: PortfolioOrder[]; | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |   private currency: string; | 
			
		
	
	
		
			
				
					|  |  | @ -57,18 +59,21 @@ export abstract class PortfolioCalculator { | 
			
		
	
		
			
				
					|  |  |  |   private transactionPoints: TransactionPoint[]; | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |   public constructor({ | 
			
		
	
		
			
				
					|  |  |  |     accountBalanceItems, | 
			
		
	
		
			
				
					|  |  |  |     activities, | 
			
		
	
		
			
				
					|  |  |  |     currency, | 
			
		
	
		
			
				
					|  |  |  |     currentRateService, | 
			
		
	
		
			
				
					|  |  |  |     dateRange, | 
			
		
	
		
			
				
					|  |  |  |     exchangeRateDataService | 
			
		
	
		
			
				
					|  |  |  |   }: { | 
			
		
	
		
			
				
					|  |  |  |     accountBalanceItems: HistoricalDataItem[]; | 
			
		
	
		
			
				
					|  |  |  |     activities: Activity[]; | 
			
		
	
		
			
				
					|  |  |  |     currency: string; | 
			
		
	
		
			
				
					|  |  |  |     currentRateService: CurrentRateService; | 
			
		
	
		
			
				
					|  |  |  |     dateRange: DateRange; | 
			
		
	
		
			
				
					|  |  |  |     exchangeRateDataService: ExchangeRateDataService; | 
			
		
	
		
			
				
					|  |  |  |   }) { | 
			
		
	
		
			
				
					|  |  |  |     this.accountBalanceItems = accountBalanceItems; | 
			
		
	
		
			
				
					|  |  |  |     this.currency = currency; | 
			
		
	
		
			
				
					|  |  |  |     this.currentRateService = currentRateService; | 
			
		
	
		
			
				
					|  |  |  |     this.exchangeRateDataService = exchangeRateDataService; | 
			
		
	
	
		
			
				
					|  |  | @ -383,10 +388,6 @@ export abstract class PortfolioCalculator { | 
			
		
	
		
			
				
					|  |  |  |     dateRange?: DateRange; | 
			
		
	
		
			
				
					|  |  |  |     withDataDecimation?: boolean; | 
			
		
	
		
			
				
					|  |  |  |   }): Promise<HistoricalDataItem[]> { | 
			
		
	
		
			
				
					|  |  |  |     if (this.getTransactionPoints().length === 0) { | 
			
		
	
		
			
				
					|  |  |  |       return []; | 
			
		
	
		
			
				
					|  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |     const { endDate, startDate } = getInterval(dateRange, this.getStartDate()); | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |     const daysInMarket = differenceInDays(endDate, startDate) + 1; | 
			
		
	
	
		
			
				
					|  |  | @ -485,6 +486,7 @@ export abstract class PortfolioCalculator { | 
			
		
	
		
			
				
					|  |  |  |         investmentValueWithCurrencyEffect: Big; | 
			
		
	
		
			
				
					|  |  |  |         totalCurrentValue: Big; | 
			
		
	
		
			
				
					|  |  |  |         totalCurrentValueWithCurrencyEffect: Big; | 
			
		
	
		
			
				
					|  |  |  |         totalAccountBalanceWithCurrencyEffect: Big; | 
			
		
	
		
			
				
					|  |  |  |         totalInvestmentValue: Big; | 
			
		
	
		
			
				
					|  |  |  |         totalInvestmentValueWithCurrencyEffect: Big; | 
			
		
	
		
			
				
					|  |  |  |         totalNetPerformanceValue: Big; | 
			
		
	
	
		
			
				
					|  |  | @ -544,9 +546,24 @@ export abstract class PortfolioCalculator { | 
			
		
	
		
			
				
					|  |  |  |       }; | 
			
		
	
		
			
				
					|  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |     let lastDate = format(this.startDate, DATE_FORMAT); | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |     for (const currentDate of dates) { | 
			
		
	
		
			
				
					|  |  |  |       const dateString = format(currentDate, DATE_FORMAT); | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |       accumulatedValuesByDate[dateString] = { | 
			
		
	
		
			
				
					|  |  |  |         investmentValueWithCurrencyEffect: new Big(0), | 
			
		
	
		
			
				
					|  |  |  |         totalAccountBalanceWithCurrencyEffect: new Big(0), | 
			
		
	
		
			
				
					|  |  |  |         totalCurrentValue: new Big(0), | 
			
		
	
		
			
				
					|  |  |  |         totalCurrentValueWithCurrencyEffect: new Big(0), | 
			
		
	
		
			
				
					|  |  |  |         totalInvestmentValue: new Big(0), | 
			
		
	
		
			
				
					|  |  |  |         totalInvestmentValueWithCurrencyEffect: new Big(0), | 
			
		
	
		
			
				
					|  |  |  |         totalNetPerformanceValue: new Big(0), | 
			
		
	
		
			
				
					|  |  |  |         totalNetPerformanceValueWithCurrencyEffect: new Big(0), | 
			
		
	
		
			
				
					|  |  |  |         totalTimeWeightedInvestmentValue: new Big(0), | 
			
		
	
		
			
				
					|  |  |  |         totalTimeWeightedInvestmentValueWithCurrencyEffect: new Big(0) | 
			
		
	
		
			
				
					|  |  |  |       }; | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |       for (const symbol of Object.keys(valuesBySymbol)) { | 
			
		
	
		
			
				
					|  |  |  |         const symbolValues = valuesBySymbol[symbol]; | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
	
		
			
				
					|  |  | @ -584,49 +601,94 @@ export abstract class PortfolioCalculator { | 
			
		
	
		
			
				
					|  |  |  |             dateString | 
			
		
	
		
			
				
					|  |  |  |           ] ?? new Big(0); | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |         accumulatedValuesByDate[dateString] = { | 
			
		
	
		
			
				
					|  |  |  |           investmentValueWithCurrencyEffect: ( | 
			
		
	
		
			
				
					|  |  |  |             accumulatedValuesByDate[dateString] | 
			
		
	
		
			
				
					|  |  |  |               ?.investmentValueWithCurrencyEffect ?? new Big(0) | 
			
		
	
		
			
				
					|  |  |  |           ).add(investmentValueWithCurrencyEffect), | 
			
		
	
		
			
				
					|  |  |  |           totalCurrentValue: ( | 
			
		
	
		
			
				
					|  |  |  |             accumulatedValuesByDate[dateString]?.totalCurrentValue ?? new Big(0) | 
			
		
	
		
			
				
					|  |  |  |           ).add(currentValue), | 
			
		
	
		
			
				
					|  |  |  |           totalCurrentValueWithCurrencyEffect: ( | 
			
		
	
		
			
				
					|  |  |  |             accumulatedValuesByDate[dateString] | 
			
		
	
		
			
				
					|  |  |  |               ?.totalCurrentValueWithCurrencyEffect ?? new Big(0) | 
			
		
	
		
			
				
					|  |  |  |           ).add(currentValueWithCurrencyEffect), | 
			
		
	
		
			
				
					|  |  |  |           totalInvestmentValue: ( | 
			
		
	
		
			
				
					|  |  |  |             accumulatedValuesByDate[dateString]?.totalInvestmentValue ?? | 
			
		
	
		
			
				
					|  |  |  |             new Big(0) | 
			
		
	
		
			
				
					|  |  |  |           ).add(investmentValueAccumulated), | 
			
		
	
		
			
				
					|  |  |  |           totalInvestmentValueWithCurrencyEffect: ( | 
			
		
	
		
			
				
					|  |  |  |             accumulatedValuesByDate[dateString] | 
			
		
	
		
			
				
					|  |  |  |               ?.totalInvestmentValueWithCurrencyEffect ?? new Big(0) | 
			
		
	
		
			
				
					|  |  |  |           ).add(investmentValueAccumulatedWithCurrencyEffect), | 
			
		
	
		
			
				
					|  |  |  |           totalNetPerformanceValue: ( | 
			
		
	
		
			
				
					|  |  |  |             accumulatedValuesByDate[dateString]?.totalNetPerformanceValue ?? | 
			
		
	
		
			
				
					|  |  |  |             new Big(0) | 
			
		
	
		
			
				
					|  |  |  |           ).add(netPerformanceValue), | 
			
		
	
		
			
				
					|  |  |  |           totalNetPerformanceValueWithCurrencyEffect: ( | 
			
		
	
		
			
				
					|  |  |  |             accumulatedValuesByDate[dateString] | 
			
		
	
		
			
				
					|  |  |  |               ?.totalNetPerformanceValueWithCurrencyEffect ?? new Big(0) | 
			
		
	
		
			
				
					|  |  |  |           ).add(netPerformanceValueWithCurrencyEffect), | 
			
		
	
		
			
				
					|  |  |  |           totalTimeWeightedInvestmentValue: ( | 
			
		
	
		
			
				
					|  |  |  |             accumulatedValuesByDate[dateString] | 
			
		
	
		
			
				
					|  |  |  |               ?.totalTimeWeightedInvestmentValue ?? new Big(0) | 
			
		
	
		
			
				
					|  |  |  |           ).add(timeWeightedInvestmentValue), | 
			
		
	
		
			
				
					|  |  |  |           totalTimeWeightedInvestmentValueWithCurrencyEffect: ( | 
			
		
	
		
			
				
					|  |  |  |             accumulatedValuesByDate[dateString] | 
			
		
	
		
			
				
					|  |  |  |               ?.totalTimeWeightedInvestmentValueWithCurrencyEffect ?? new Big(0) | 
			
		
	
		
			
				
					|  |  |  |           ).add(timeWeightedInvestmentValueWithCurrencyEffect) | 
			
		
	
		
			
				
					|  |  |  |         }; | 
			
		
	
		
			
				
					|  |  |  |         accumulatedValuesByDate[dateString].investmentValueWithCurrencyEffect = | 
			
		
	
		
			
				
					|  |  |  |           accumulatedValuesByDate[ | 
			
		
	
		
			
				
					|  |  |  |             dateString | 
			
		
	
		
			
				
					|  |  |  |           ].investmentValueWithCurrencyEffect.add( | 
			
		
	
		
			
				
					|  |  |  |             investmentValueWithCurrencyEffect | 
			
		
	
		
			
				
					|  |  |  |           ); | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |         accumulatedValuesByDate[dateString].totalCurrentValue = | 
			
		
	
		
			
				
					|  |  |  |           accumulatedValuesByDate[dateString].totalCurrentValue.add( | 
			
		
	
		
			
				
					|  |  |  |             currentValue | 
			
		
	
		
			
				
					|  |  |  |           ); | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |         accumulatedValuesByDate[ | 
			
		
	
		
			
				
					|  |  |  |           dateString | 
			
		
	
		
			
				
					|  |  |  |         ].totalCurrentValueWithCurrencyEffect = accumulatedValuesByDate[ | 
			
		
	
		
			
				
					|  |  |  |           dateString | 
			
		
	
		
			
				
					|  |  |  |         ].totalCurrentValueWithCurrencyEffect.add( | 
			
		
	
		
			
				
					|  |  |  |           currentValueWithCurrencyEffect | 
			
		
	
		
			
				
					|  |  |  |         ); | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |         accumulatedValuesByDate[dateString].totalInvestmentValue = | 
			
		
	
		
			
				
					|  |  |  |           accumulatedValuesByDate[dateString].totalInvestmentValue.add( | 
			
		
	
		
			
				
					|  |  |  |             investmentValueAccumulated | 
			
		
	
		
			
				
					|  |  |  |           ); | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |         accumulatedValuesByDate[ | 
			
		
	
		
			
				
					|  |  |  |           dateString | 
			
		
	
		
			
				
					|  |  |  |         ].totalInvestmentValueWithCurrencyEffect = accumulatedValuesByDate[ | 
			
		
	
		
			
				
					|  |  |  |           dateString | 
			
		
	
		
			
				
					|  |  |  |         ].totalInvestmentValueWithCurrencyEffect.add( | 
			
		
	
		
			
				
					|  |  |  |           investmentValueAccumulatedWithCurrencyEffect | 
			
		
	
		
			
				
					|  |  |  |         ); | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |         accumulatedValuesByDate[dateString].totalNetPerformanceValue = | 
			
		
	
		
			
				
					|  |  |  |           accumulatedValuesByDate[dateString].totalNetPerformanceValue.add( | 
			
		
	
		
			
				
					|  |  |  |             netPerformanceValue | 
			
		
	
		
			
				
					|  |  |  |           ); | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |         accumulatedValuesByDate[ | 
			
		
	
		
			
				
					|  |  |  |           dateString | 
			
		
	
		
			
				
					|  |  |  |         ].totalNetPerformanceValueWithCurrencyEffect = accumulatedValuesByDate[ | 
			
		
	
		
			
				
					|  |  |  |           dateString | 
			
		
	
		
			
				
					|  |  |  |         ].totalNetPerformanceValueWithCurrencyEffect.add( | 
			
		
	
		
			
				
					|  |  |  |           netPerformanceValueWithCurrencyEffect | 
			
		
	
		
			
				
					|  |  |  |         ); | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |         accumulatedValuesByDate[dateString].totalTimeWeightedInvestmentValue = | 
			
		
	
		
			
				
					|  |  |  |           accumulatedValuesByDate[ | 
			
		
	
		
			
				
					|  |  |  |             dateString | 
			
		
	
		
			
				
					|  |  |  |           ].totalTimeWeightedInvestmentValue.add(timeWeightedInvestmentValue); | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |         accumulatedValuesByDate[ | 
			
		
	
		
			
				
					|  |  |  |           dateString | 
			
		
	
		
			
				
					|  |  |  |         ].totalTimeWeightedInvestmentValueWithCurrencyEffect = | 
			
		
	
		
			
				
					|  |  |  |           accumulatedValuesByDate[ | 
			
		
	
		
			
				
					|  |  |  |             dateString | 
			
		
	
		
			
				
					|  |  |  |           ].totalTimeWeightedInvestmentValueWithCurrencyEffect.add( | 
			
		
	
		
			
				
					|  |  |  |             timeWeightedInvestmentValueWithCurrencyEffect | 
			
		
	
		
			
				
					|  |  |  |           ); | 
			
		
	
		
			
				
					|  |  |  |       } | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |       if ( | 
			
		
	
		
			
				
					|  |  |  |         this.accountBalanceItems.some(({ date }) => { | 
			
		
	
		
			
				
					|  |  |  |           return date === dateString; | 
			
		
	
		
			
				
					|  |  |  |         }) | 
			
		
	
		
			
				
					|  |  |  |       ) { | 
			
		
	
		
			
				
					|  |  |  |         accumulatedValuesByDate[ | 
			
		
	
		
			
				
					|  |  |  |           dateString | 
			
		
	
		
			
				
					|  |  |  |         ].totalAccountBalanceWithCurrencyEffect = new Big( | 
			
		
	
		
			
				
					|  |  |  |           this.accountBalanceItems.find(({ date }) => { | 
			
		
	
		
			
				
					|  |  |  |             return date === dateString; | 
			
		
	
		
			
				
					|  |  |  |           }).value | 
			
		
	
		
			
				
					|  |  |  |         ); | 
			
		
	
		
			
				
					|  |  |  |       } else { | 
			
		
	
		
			
				
					|  |  |  |         accumulatedValuesByDate[ | 
			
		
	
		
			
				
					|  |  |  |           dateString | 
			
		
	
		
			
				
					|  |  |  |         ].totalAccountBalanceWithCurrencyEffect = | 
			
		
	
		
			
				
					|  |  |  |           accumulatedValuesByDate[lastDate] | 
			
		
	
		
			
				
					|  |  |  |             ?.totalAccountBalanceWithCurrencyEffect ?? new Big(0); | 
			
		
	
		
			
				
					|  |  |  |       } | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |       lastDate = dateString; | 
			
		
	
		
			
				
					|  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |     return Object.entries(accumulatedValuesByDate).map(([date, values]) => { | 
			
		
	
		
			
				
					|  |  |  |       const { | 
			
		
	
		
			
				
					|  |  |  |         investmentValueWithCurrencyEffect, | 
			
		
	
		
			
				
					|  |  |  |         totalAccountBalanceWithCurrencyEffect, | 
			
		
	
		
			
				
					|  |  |  |         totalCurrentValue, | 
			
		
	
		
			
				
					|  |  |  |         totalCurrentValueWithCurrencyEffect, | 
			
		
	
		
			
				
					|  |  |  |         totalInvestmentValue, | 
			
		
	
	
		
			
				
					|  |  | @ -661,6 +723,11 @@ export abstract class PortfolioCalculator { | 
			
		
	
		
			
				
					|  |  |  |         netPerformance: totalNetPerformanceValue.toNumber(), | 
			
		
	
		
			
				
					|  |  |  |         netPerformanceWithCurrencyEffect: | 
			
		
	
		
			
				
					|  |  |  |           totalNetPerformanceValueWithCurrencyEffect.toNumber(), | 
			
		
	
		
			
				
					|  |  |  |         // TODO: Add valuables
 | 
			
		
	
		
			
				
					|  |  |  |         netWorth: totalCurrentValueWithCurrencyEffect | 
			
		
	
		
			
				
					|  |  |  |           .plus(totalAccountBalanceWithCurrencyEffect) | 
			
		
	
		
			
				
					|  |  |  |           .toNumber(), | 
			
		
	
		
			
				
					|  |  |  |         totalAccountBalance: totalAccountBalanceWithCurrencyEffect.toNumber(), | 
			
		
	
		
			
				
					|  |  |  |         totalInvestment: totalInvestmentValue.toNumber(), | 
			
		
	
		
			
				
					|  |  |  |         totalInvestmentValueWithCurrencyEffect: | 
			
		
	
		
			
				
					|  |  |  |           totalInvestmentValueWithCurrencyEffect.toNumber(), | 
			
		
	
	
		
			
				
					|  |  | @ -749,9 +816,30 @@ export abstract class PortfolioCalculator { | 
			
		
	
		
			
				
					|  |  |  |   } | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |   public getStartDate() { | 
			
		
	
		
			
				
					|  |  |  |     return this.transactionPoints.length > 0 | 
			
		
	
		
			
				
					|  |  |  |       ? parseDate(this.transactionPoints[0].date) | 
			
		
	
		
			
				
					|  |  |  |       : new Date(); | 
			
		
	
		
			
				
					|  |  |  |     let firstAccountBalanceDate: Date; | 
			
		
	
		
			
				
					|  |  |  |     let firstActivityDate: Date; | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |     try { | 
			
		
	
		
			
				
					|  |  |  |       const firstAccountBalanceDateString = first( | 
			
		
	
		
			
				
					|  |  |  |         this.accountBalanceItems | 
			
		
	
		
			
				
					|  |  |  |       )?.date; | 
			
		
	
		
			
				
					|  |  |  |       firstAccountBalanceDate = firstAccountBalanceDateString | 
			
		
	
		
			
				
					|  |  |  |         ? parseDate(firstAccountBalanceDateString) | 
			
		
	
		
			
				
					|  |  |  |         : new Date(); | 
			
		
	
		
			
				
					|  |  |  |     } catch (error) { | 
			
		
	
		
			
				
					|  |  |  |       firstAccountBalanceDate = new Date(); | 
			
		
	
		
			
				
					|  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |     try { | 
			
		
	
		
			
				
					|  |  |  |       const firstActivityDateString = this.transactionPoints[0].date; | 
			
		
	
		
			
				
					|  |  |  |       firstActivityDate = firstActivityDateString | 
			
		
	
		
			
				
					|  |  |  |         ? parseDate(firstActivityDateString) | 
			
		
	
		
			
				
					|  |  |  |         : new Date(); | 
			
		
	
		
			
				
					|  |  |  |     } catch (error) { | 
			
		
	
		
			
				
					|  |  |  |       firstActivityDate = new Date(); | 
			
		
	
		
			
				
					|  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |     return min([firstAccountBalanceDate, firstActivityDate]); | 
			
		
	
		
			
				
					|  |  |  |   } | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |   protected abstract getSymbolMetrics({ | 
			
		
	
	
		
			
				
					|  |  | 
 |