|
@ -69,6 +69,8 @@ export abstract class PortfolioCalculator { |
|
|
dateRange: DateRange; |
|
|
dateRange: DateRange; |
|
|
exchangeRateDataService: ExchangeRateDataService; |
|
|
exchangeRateDataService: ExchangeRateDataService; |
|
|
}) { |
|
|
}) { |
|
|
|
|
|
console.time('--- PortfolioCalculator.constructor - 1'); |
|
|
|
|
|
|
|
|
this.currency = currency; |
|
|
this.currency = currency; |
|
|
this.currentRateService = currentRateService; |
|
|
this.currentRateService = currentRateService; |
|
|
this.exchangeRateDataService = exchangeRateDataService; |
|
|
this.exchangeRateDataService = exchangeRateDataService; |
|
@ -95,9 +97,16 @@ export abstract class PortfolioCalculator { |
|
|
this.endDate = endDate; |
|
|
this.endDate = endDate; |
|
|
this.startDate = startDate; |
|
|
this.startDate = startDate; |
|
|
|
|
|
|
|
|
|
|
|
console.timeEnd('--- PortfolioCalculator.constructor - 1'); |
|
|
|
|
|
console.time('--- PortfolioCalculator.constructor - 2'); |
|
|
|
|
|
|
|
|
this.computeTransactionPoints(); |
|
|
this.computeTransactionPoints(); |
|
|
|
|
|
|
|
|
|
|
|
console.timeEnd('--- PortfolioCalculator.constructor - 2'); |
|
|
|
|
|
|
|
|
|
|
|
console.time('--- PortfolioCalculator.constructor - 3'); |
|
|
this.snapshotPromise = this.initialize(); |
|
|
this.snapshotPromise = this.initialize(); |
|
|
|
|
|
console.timeEnd('--- PortfolioCalculator.constructor - 3'); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
protected abstract calculateOverallPerformance( |
|
|
protected abstract calculateOverallPerformance( |
|
@ -126,6 +135,7 @@ export abstract class PortfolioCalculator { |
|
|
|
|
|
|
|
|
if (!transactionPoints.length) { |
|
|
if (!transactionPoints.length) { |
|
|
return { |
|
|
return { |
|
|
|
|
|
chartData: [], |
|
|
currentValueInBaseCurrency: new Big(0), |
|
|
currentValueInBaseCurrency: new Big(0), |
|
|
grossPerformance: new Big(0), |
|
|
grossPerformance: new Big(0), |
|
|
grossPerformancePercentage: new Big(0), |
|
|
grossPerformancePercentage: new Big(0), |
|
@ -247,6 +257,26 @@ export abstract class PortfolioCalculator { |
|
|
|
|
|
|
|
|
const endDateString = format(endDate, DATE_FORMAT); |
|
|
const endDateString = format(endDate, DATE_FORMAT); |
|
|
|
|
|
|
|
|
|
|
|
const chartStartDate = this.getStartDate(); |
|
|
|
|
|
const daysInMarket = differenceInDays(endDate, chartStartDate) + 1; |
|
|
|
|
|
|
|
|
|
|
|
const step = true /*withDataDecimation*/ |
|
|
|
|
|
? Math.round(daysInMarket / Math.min(daysInMarket, MAX_CHART_ITEMS)) |
|
|
|
|
|
: 1; |
|
|
|
|
|
|
|
|
|
|
|
let chartDates = eachDayOfInterval( |
|
|
|
|
|
{ start: chartStartDate, end }, |
|
|
|
|
|
{ step } |
|
|
|
|
|
).map((date) => { |
|
|
|
|
|
return resetHours(date); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
const includesEndDate = isSameDay(last(chartDates), end); |
|
|
|
|
|
|
|
|
|
|
|
if (!includesEndDate) { |
|
|
|
|
|
chartDates.push(resetHours(end)); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
if (firstIndex > 0) { |
|
|
if (firstIndex > 0) { |
|
|
firstIndex--; |
|
|
firstIndex--; |
|
|
} |
|
|
} |
|
@ -256,6 +286,34 @@ export abstract class PortfolioCalculator { |
|
|
|
|
|
|
|
|
const errors: ResponseError['errors'] = []; |
|
|
const errors: ResponseError['errors'] = []; |
|
|
|
|
|
|
|
|
|
|
|
const accumulatedValuesByDate: { |
|
|
|
|
|
[date: string]: { |
|
|
|
|
|
investmentValueWithCurrencyEffect: Big; |
|
|
|
|
|
totalCurrentValue: Big; |
|
|
|
|
|
totalCurrentValueWithCurrencyEffect: Big; |
|
|
|
|
|
totalInvestmentValue: Big; |
|
|
|
|
|
totalInvestmentValueWithCurrencyEffect: Big; |
|
|
|
|
|
totalNetPerformanceValue: Big; |
|
|
|
|
|
totalNetPerformanceValueWithCurrencyEffect: Big; |
|
|
|
|
|
totalTimeWeightedInvestmentValue: Big; |
|
|
|
|
|
totalTimeWeightedInvestmentValueWithCurrencyEffect: Big; |
|
|
|
|
|
}; |
|
|
|
|
|
} = {}; |
|
|
|
|
|
|
|
|
|
|
|
const valuesBySymbol: { |
|
|
|
|
|
[symbol: string]: { |
|
|
|
|
|
currentValues: { [date: string]: Big }; |
|
|
|
|
|
currentValuesWithCurrencyEffect: { [date: string]: Big }; |
|
|
|
|
|
investmentValuesAccumulated: { [date: string]: Big }; |
|
|
|
|
|
investmentValuesAccumulatedWithCurrencyEffect: { [date: string]: Big }; |
|
|
|
|
|
investmentValuesWithCurrencyEffect: { [date: string]: Big }; |
|
|
|
|
|
netPerformanceValues: { [date: string]: Big }; |
|
|
|
|
|
netPerformanceValuesWithCurrencyEffect: { [date: string]: Big }; |
|
|
|
|
|
timeWeightedInvestmentValues: { [date: string]: Big }; |
|
|
|
|
|
timeWeightedInvestmentValuesWithCurrencyEffect: { [date: string]: Big }; |
|
|
|
|
|
}; |
|
|
|
|
|
} = {}; |
|
|
|
|
|
|
|
|
for (const item of lastTransactionPoint.items) { |
|
|
for (const item of lastTransactionPoint.items) { |
|
|
const marketPriceInBaseCurrency = ( |
|
|
const marketPriceInBaseCurrency = ( |
|
|
marketSymbolMap[endDateString]?.[item.symbol] ?? item.averagePrice |
|
|
marketSymbolMap[endDateString]?.[item.symbol] ?? item.averagePrice |
|
@ -266,16 +324,25 @@ export abstract class PortfolioCalculator { |
|
|
); |
|
|
); |
|
|
|
|
|
|
|
|
const { |
|
|
const { |
|
|
|
|
|
currentValues, |
|
|
|
|
|
currentValuesWithCurrencyEffect, |
|
|
grossPerformance, |
|
|
grossPerformance, |
|
|
grossPerformancePercentage, |
|
|
grossPerformancePercentage, |
|
|
grossPerformancePercentageWithCurrencyEffect, |
|
|
grossPerformancePercentageWithCurrencyEffect, |
|
|
grossPerformanceWithCurrencyEffect, |
|
|
grossPerformanceWithCurrencyEffect, |
|
|
hasErrors, |
|
|
hasErrors, |
|
|
|
|
|
investmentValuesAccumulated, |
|
|
|
|
|
investmentValuesAccumulatedWithCurrencyEffect, |
|
|
|
|
|
investmentValuesWithCurrencyEffect, |
|
|
netPerformance, |
|
|
netPerformance, |
|
|
netPerformancePercentage, |
|
|
netPerformancePercentage, |
|
|
netPerformancePercentageWithCurrencyEffect, |
|
|
netPerformancePercentageWithCurrencyEffect, |
|
|
|
|
|
netPerformanceValues, |
|
|
|
|
|
netPerformanceValuesWithCurrencyEffect, |
|
|
netPerformanceWithCurrencyEffect, |
|
|
netPerformanceWithCurrencyEffect, |
|
|
timeWeightedInvestment, |
|
|
timeWeightedInvestment, |
|
|
|
|
|
timeWeightedInvestmentValues, |
|
|
|
|
|
timeWeightedInvestmentValuesWithCurrencyEffect, |
|
|
timeWeightedInvestmentWithCurrencyEffect, |
|
|
timeWeightedInvestmentWithCurrencyEffect, |
|
|
totalDividend, |
|
|
totalDividend, |
|
|
totalDividendInBaseCurrency, |
|
|
totalDividendInBaseCurrency, |
|
@ -287,15 +354,29 @@ export abstract class PortfolioCalculator { |
|
|
} = this.getSymbolMetrics({ |
|
|
} = this.getSymbolMetrics({ |
|
|
marketSymbolMap, |
|
|
marketSymbolMap, |
|
|
start, |
|
|
start, |
|
|
|
|
|
step, |
|
|
dataSource: item.dataSource, |
|
|
dataSource: item.dataSource, |
|
|
end: endDate, |
|
|
end: endDate, |
|
|
exchangeRates: |
|
|
exchangeRates: |
|
|
exchangeRatesByCurrency[`${item.currency}${this.currency}`], |
|
|
exchangeRatesByCurrency[`${item.currency}${this.currency}`], |
|
|
|
|
|
isChartMode: true, |
|
|
symbol: item.symbol |
|
|
symbol: item.symbol |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
hasAnySymbolMetricsErrors = hasAnySymbolMetricsErrors || hasErrors; |
|
|
hasAnySymbolMetricsErrors = hasAnySymbolMetricsErrors || hasErrors; |
|
|
|
|
|
|
|
|
|
|
|
valuesBySymbol[item.symbol] = { |
|
|
|
|
|
currentValues, |
|
|
|
|
|
currentValuesWithCurrencyEffect, |
|
|
|
|
|
investmentValuesAccumulated, |
|
|
|
|
|
investmentValuesAccumulatedWithCurrencyEffect, |
|
|
|
|
|
investmentValuesWithCurrencyEffect, |
|
|
|
|
|
netPerformanceValues, |
|
|
|
|
|
netPerformanceValuesWithCurrencyEffect, |
|
|
|
|
|
timeWeightedInvestmentValues, |
|
|
|
|
|
timeWeightedInvestmentValuesWithCurrencyEffect |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
positions.push({ |
|
|
positions.push({ |
|
|
dividend: totalDividend, |
|
|
dividend: totalDividend, |
|
|
dividendInBaseCurrency: totalDividendInBaseCurrency, |
|
|
dividendInBaseCurrency: totalDividendInBaseCurrency, |
|
@ -363,10 +444,143 @@ export abstract class PortfolioCalculator { |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
for (const currentDate of chartDates) { |
|
|
|
|
|
const dateString = format(currentDate, DATE_FORMAT); |
|
|
|
|
|
|
|
|
|
|
|
for (const symbol of Object.keys(valuesBySymbol)) { |
|
|
|
|
|
const symbolValues = valuesBySymbol[symbol]; |
|
|
|
|
|
|
|
|
|
|
|
const currentValue = |
|
|
|
|
|
symbolValues.currentValues?.[dateString] ?? new Big(0); |
|
|
|
|
|
|
|
|
|
|
|
const currentValueWithCurrencyEffect = |
|
|
|
|
|
symbolValues.currentValuesWithCurrencyEffect?.[dateString] ?? |
|
|
|
|
|
new Big(0); |
|
|
|
|
|
|
|
|
|
|
|
const investmentValueAccumulated = |
|
|
|
|
|
symbolValues.investmentValuesAccumulated?.[dateString] ?? new Big(0); |
|
|
|
|
|
|
|
|
|
|
|
const investmentValueAccumulatedWithCurrencyEffect = |
|
|
|
|
|
symbolValues.investmentValuesAccumulatedWithCurrencyEffect?.[ |
|
|
|
|
|
dateString |
|
|
|
|
|
] ?? new Big(0); |
|
|
|
|
|
|
|
|
|
|
|
const investmentValueWithCurrencyEffect = |
|
|
|
|
|
symbolValues.investmentValuesWithCurrencyEffect?.[dateString] ?? |
|
|
|
|
|
new Big(0); |
|
|
|
|
|
|
|
|
|
|
|
const netPerformanceValue = |
|
|
|
|
|
symbolValues.netPerformanceValues?.[dateString] ?? new Big(0); |
|
|
|
|
|
|
|
|
|
|
|
const netPerformanceValueWithCurrencyEffect = |
|
|
|
|
|
symbolValues.netPerformanceValuesWithCurrencyEffect?.[dateString] ?? |
|
|
|
|
|
new Big(0); |
|
|
|
|
|
|
|
|
|
|
|
const timeWeightedInvestmentValue = |
|
|
|
|
|
symbolValues.timeWeightedInvestmentValues?.[dateString] ?? new Big(0); |
|
|
|
|
|
|
|
|
|
|
|
const timeWeightedInvestmentValueWithCurrencyEffect = |
|
|
|
|
|
symbolValues.timeWeightedInvestmentValuesWithCurrencyEffect?.[ |
|
|
|
|
|
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) |
|
|
|
|
|
}; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const chartData: HistoricalDataItem[] = Object.entries( |
|
|
|
|
|
accumulatedValuesByDate |
|
|
|
|
|
).map(([date, values]) => { |
|
|
|
|
|
const { |
|
|
|
|
|
investmentValueWithCurrencyEffect, |
|
|
|
|
|
totalCurrentValue, |
|
|
|
|
|
totalCurrentValueWithCurrencyEffect, |
|
|
|
|
|
totalInvestmentValue, |
|
|
|
|
|
totalInvestmentValueWithCurrencyEffect, |
|
|
|
|
|
totalNetPerformanceValue, |
|
|
|
|
|
totalNetPerformanceValueWithCurrencyEffect, |
|
|
|
|
|
totalTimeWeightedInvestmentValue, |
|
|
|
|
|
totalTimeWeightedInvestmentValueWithCurrencyEffect |
|
|
|
|
|
} = values; |
|
|
|
|
|
|
|
|
|
|
|
console.log( |
|
|
|
|
|
'Chart: totalTimeWeightedInvestmentValue', |
|
|
|
|
|
totalTimeWeightedInvestmentValue.toFixed() |
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
const netPerformanceInPercentage = totalTimeWeightedInvestmentValue.eq(0) |
|
|
|
|
|
? 0 |
|
|
|
|
|
: totalNetPerformanceValue |
|
|
|
|
|
.div(totalTimeWeightedInvestmentValue) |
|
|
|
|
|
.mul(100) |
|
|
|
|
|
.toNumber(); |
|
|
|
|
|
|
|
|
|
|
|
const netPerformanceInPercentageWithCurrencyEffect = |
|
|
|
|
|
totalTimeWeightedInvestmentValueWithCurrencyEffect.eq(0) |
|
|
|
|
|
? 0 |
|
|
|
|
|
: totalNetPerformanceValueWithCurrencyEffect |
|
|
|
|
|
.div(totalTimeWeightedInvestmentValueWithCurrencyEffect) |
|
|
|
|
|
.mul(100) |
|
|
|
|
|
.toNumber(); |
|
|
|
|
|
|
|
|
|
|
|
return { |
|
|
|
|
|
date, |
|
|
|
|
|
netPerformanceInPercentage, |
|
|
|
|
|
netPerformanceInPercentageWithCurrencyEffect, |
|
|
|
|
|
investmentValueWithCurrencyEffect: |
|
|
|
|
|
investmentValueWithCurrencyEffect.toNumber(), |
|
|
|
|
|
netPerformance: totalNetPerformanceValue.toNumber(), |
|
|
|
|
|
netPerformanceWithCurrencyEffect: |
|
|
|
|
|
totalNetPerformanceValueWithCurrencyEffect.toNumber(), |
|
|
|
|
|
totalInvestment: totalInvestmentValue.toNumber(), |
|
|
|
|
|
totalInvestmentValueWithCurrencyEffect: |
|
|
|
|
|
totalInvestmentValueWithCurrencyEffect.toNumber(), |
|
|
|
|
|
value: totalCurrentValue.toNumber(), |
|
|
|
|
|
valueWithCurrencyEffect: totalCurrentValueWithCurrencyEffect.toNumber() |
|
|
|
|
|
}; |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
const overall = this.calculateOverallPerformance(positions); |
|
|
const overall = this.calculateOverallPerformance(positions); |
|
|
|
|
|
|
|
|
return { |
|
|
return { |
|
|
...overall, |
|
|
...overall, |
|
|
|
|
|
chartData, |
|
|
errors, |
|
|
errors, |
|
|
positions, |
|
|
positions, |
|
|
totalInterestWithCurrencyEffect, |
|
|
totalInterestWithCurrencyEffect, |
|
@ -383,6 +597,8 @@ export abstract class PortfolioCalculator { |
|
|
dateRange?: DateRange; |
|
|
dateRange?: DateRange; |
|
|
withDataDecimation?: boolean; |
|
|
withDataDecimation?: boolean; |
|
|
}): Promise<HistoricalDataItem[]> { |
|
|
}): Promise<HistoricalDataItem[]> { |
|
|
|
|
|
console.time('-------- PortfolioCalculator.getChart'); |
|
|
|
|
|
|
|
|
if (this.getTransactionPoints().length === 0) { |
|
|
if (this.getTransactionPoints().length === 0) { |
|
|
return []; |
|
|
return []; |
|
|
} |
|
|
} |
|
@ -394,11 +610,15 @@ export abstract class PortfolioCalculator { |
|
|
? Math.round(daysInMarket / Math.min(daysInMarket, MAX_CHART_ITEMS)) |
|
|
? Math.round(daysInMarket / Math.min(daysInMarket, MAX_CHART_ITEMS)) |
|
|
: 1; |
|
|
: 1; |
|
|
|
|
|
|
|
|
return this.getChartData({ |
|
|
const chartData = await this.getChartData({ |
|
|
step, |
|
|
step, |
|
|
end: endDate, |
|
|
end: endDate, |
|
|
start: startDate |
|
|
start: startDate |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
console.timeEnd('-------- PortfolioCalculator.getChart'); |
|
|
|
|
|
|
|
|
|
|
|
return chartData; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
public async getChartData({ |
|
|
public async getChartData({ |
|
@ -637,6 +857,11 @@ export abstract class PortfolioCalculator { |
|
|
totalTimeWeightedInvestmentValueWithCurrencyEffect |
|
|
totalTimeWeightedInvestmentValueWithCurrencyEffect |
|
|
} = values; |
|
|
} = values; |
|
|
|
|
|
|
|
|
|
|
|
console.log( |
|
|
|
|
|
'Chart: totalTimeWeightedInvestmentValue', |
|
|
|
|
|
totalTimeWeightedInvestmentValue.toFixed() |
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
const netPerformanceInPercentage = totalTimeWeightedInvestmentValue.eq(0) |
|
|
const netPerformanceInPercentage = totalTimeWeightedInvestmentValue.eq(0) |
|
|
? 0 |
|
|
? 0 |
|
|
: totalNetPerformanceValue |
|
|
: totalNetPerformanceValue |
|
@ -743,8 +968,11 @@ export abstract class PortfolioCalculator { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
public async getSnapshot() { |
|
|
public async getSnapshot() { |
|
|
|
|
|
console.time('getSnapshot'); |
|
|
await this.snapshotPromise; |
|
|
await this.snapshotPromise; |
|
|
|
|
|
|
|
|
|
|
|
console.timeEnd('getSnapshot'); |
|
|
|
|
|
|
|
|
return this.snapshot; |
|
|
return this.snapshot; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|