|
@ -1,27 +1,34 @@ |
|
|
import { LogPerformance } from '@ghostfolio/api/aop/logging.interceptor'; |
|
|
import { LogPerformance } from '@ghostfolio/api/aop/logging.interceptor'; |
|
|
|
|
|
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; |
|
|
|
|
|
import { OrderService } from '@ghostfolio/api/app/order/order.service'; |
|
|
|
|
|
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service'; |
|
|
import { |
|
|
import { |
|
|
getFactor, |
|
|
getFactor, |
|
|
getInterval |
|
|
getInterval |
|
|
} from '@ghostfolio/api/helper/portfolio.helper'; |
|
|
} from '@ghostfolio/api/helper/portfolio.helper'; |
|
|
|
|
|
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; |
|
|
|
|
|
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; |
|
|
import { IDataGatheringItem } from '@ghostfolio/api/services/interfaces/interfaces'; |
|
|
import { IDataGatheringItem } from '@ghostfolio/api/services/interfaces/interfaces'; |
|
|
import { MAX_CHART_ITEMS } from '@ghostfolio/common/config'; |
|
|
import { MAX_CHART_ITEMS } from '@ghostfolio/common/config'; |
|
|
import { DATE_FORMAT, parseDate, resetHours } from '@ghostfolio/common/helper'; |
|
|
import { DATE_FORMAT, parseDate, resetHours } from '@ghostfolio/common/helper'; |
|
|
import { HistoricalDataItem } from '@ghostfolio/common/interfaces'; |
|
|
import { HistoricalDataItem } from '@ghostfolio/common/interfaces'; |
|
|
import { DateRange } from '@ghostfolio/common/types'; |
|
|
import { DateRange } from '@ghostfolio/common/types'; |
|
|
|
|
|
|
|
|
import { Logger } from '@nestjs/common'; |
|
|
import { Inject, Logger } from '@nestjs/common'; |
|
|
import { Big } from 'big.js'; |
|
|
import { Big } from 'big.js'; |
|
|
import { |
|
|
import { |
|
|
addDays, |
|
|
addDays, |
|
|
differenceInDays, |
|
|
differenceInDays, |
|
|
eachDayOfInterval, |
|
|
eachDayOfInterval, |
|
|
|
|
|
endOfDay, |
|
|
format, |
|
|
format, |
|
|
isAfter, |
|
|
isAfter, |
|
|
isBefore, |
|
|
isBefore, |
|
|
isEqual, |
|
|
|
|
|
subDays |
|
|
subDays |
|
|
} from 'date-fns'; |
|
|
} from 'date-fns'; |
|
|
|
|
|
|
|
|
|
|
|
import { CurrentRateService } from '../../current-rate.service'; |
|
|
|
|
|
import { DateQuery } from '../../interfaces/date-query.interface'; |
|
|
import { PortfolioOrder } from '../../interfaces/portfolio-order.interface'; |
|
|
import { PortfolioOrder } from '../../interfaces/portfolio-order.interface'; |
|
|
import { TWRPortfolioCalculator } from '../twr/portfolio-calculator'; |
|
|
import { TWRPortfolioCalculator } from '../twr/portfolio-calculator'; |
|
|
|
|
|
|
|
@ -29,6 +36,47 @@ export class CPRPortfolioCalculator extends TWRPortfolioCalculator { |
|
|
private holdings: { [date: string]: { [symbol: string]: Big } } = {}; |
|
|
private holdings: { [date: string]: { [symbol: string]: Big } } = {}; |
|
|
private holdingCurrencies: { [symbol: string]: string } = {}; |
|
|
private holdingCurrencies: { [symbol: string]: string } = {}; |
|
|
|
|
|
|
|
|
|
|
|
constructor( |
|
|
|
|
|
{ |
|
|
|
|
|
accountBalanceItems, |
|
|
|
|
|
activities, |
|
|
|
|
|
configurationService, |
|
|
|
|
|
currency, |
|
|
|
|
|
currentRateService, |
|
|
|
|
|
dateRange, |
|
|
|
|
|
exchangeRateDataService, |
|
|
|
|
|
redisCacheService, |
|
|
|
|
|
useCache, |
|
|
|
|
|
userId |
|
|
|
|
|
}: { |
|
|
|
|
|
accountBalanceItems: HistoricalDataItem[]; |
|
|
|
|
|
activities: Activity[]; |
|
|
|
|
|
configurationService: ConfigurationService; |
|
|
|
|
|
currency: string; |
|
|
|
|
|
currentRateService: CurrentRateService; |
|
|
|
|
|
dateRange: DateRange; |
|
|
|
|
|
exchangeRateDataService: ExchangeRateDataService; |
|
|
|
|
|
redisCacheService: RedisCacheService; |
|
|
|
|
|
useCache: boolean; |
|
|
|
|
|
userId: string; |
|
|
|
|
|
}, |
|
|
|
|
|
@Inject() |
|
|
|
|
|
private orderService: OrderService |
|
|
|
|
|
) { |
|
|
|
|
|
super({ |
|
|
|
|
|
accountBalanceItems, |
|
|
|
|
|
activities, |
|
|
|
|
|
configurationService, |
|
|
|
|
|
currency, |
|
|
|
|
|
currentRateService, |
|
|
|
|
|
dateRange, |
|
|
|
|
|
exchangeRateDataService, |
|
|
|
|
|
redisCacheService, |
|
|
|
|
|
useCache, |
|
|
|
|
|
userId |
|
|
|
|
|
}); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
@LogPerformance |
|
|
@LogPerformance |
|
|
public async getChart({ |
|
|
public async getChart({ |
|
|
dateRange = 'max', |
|
|
dateRange = 'max', |
|
@ -55,52 +103,118 @@ export class CPRPortfolioCalculator extends TWRPortfolioCalculator { |
|
|
if (!withTimeWeightedReturn) { |
|
|
if (!withTimeWeightedReturn) { |
|
|
return item; |
|
|
return item; |
|
|
} else { |
|
|
} else { |
|
|
|
|
|
let itemResult = await item; |
|
|
|
|
|
let dates = itemResult.map((item) => parseDate(item.date)); |
|
|
let timeWeighted = await this.getTimeWeightedChartData({ |
|
|
let timeWeighted = await this.getTimeWeightedChartData({ |
|
|
step, |
|
|
dates |
|
|
end: endDate, |
|
|
|
|
|
start: startDate |
|
|
|
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
return item.then((data) => { |
|
|
return itemResult.map((item) => { |
|
|
return data.map((item) => { |
|
|
let timeWeightedItem = timeWeighted.find( |
|
|
let timeWeightedItem = timeWeighted.find( |
|
|
(timeWeightedItem) => timeWeightedItem.date === item.date |
|
|
(timeWeightedItem) => timeWeightedItem.date === item.date |
|
|
); |
|
|
); |
|
|
if (timeWeightedItem) { |
|
|
if (timeWeightedItem) { |
|
|
item.timeWeightedPerformance = |
|
|
item.timeWeightedPerformance = |
|
|
timeWeightedItem.netPerformanceInPercentage; |
|
|
timeWeightedItem.netPerformanceInPercentage; |
|
|
item.timeWeightedPerformanceWithCurrencyEffect = |
|
|
item.timeWeightedPerformanceWithCurrencyEffect = |
|
|
timeWeightedItem.netPerformanceInPercentageWithCurrencyEffect; |
|
|
timeWeightedItem.netPerformanceInPercentageWithCurrencyEffect; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return item; |
|
|
return item; |
|
|
}); |
|
|
|
|
|
}); |
|
|
}); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@LogPerformance |
|
|
|
|
|
public async getUnfilteredNetWorth(currency: string): Promise<Big> { |
|
|
|
|
|
const activities = await this.orderService.getOrders({ |
|
|
|
|
|
userId: this.userId, |
|
|
|
|
|
userCurrency: currency, |
|
|
|
|
|
types: ['BUY', 'SELL', 'STAKE'], |
|
|
|
|
|
withExcludedAccounts: true |
|
|
|
|
|
}); |
|
|
|
|
|
const orders = this.activitiesToPortfolioOrder(activities.activities); |
|
|
|
|
|
const start = orders.reduce( |
|
|
|
|
|
(date, order) => |
|
|
|
|
|
parseDate(date.date).getTime() < parseDate(order.date).getTime() |
|
|
|
|
|
? date |
|
|
|
|
|
: order, |
|
|
|
|
|
{ date: orders[0].date } |
|
|
|
|
|
).date; |
|
|
|
|
|
|
|
|
|
|
|
const end = new Date(Date.now()); |
|
|
|
|
|
|
|
|
|
|
|
const holdings = await this.getHoldings(orders, parseDate(start), end); |
|
|
|
|
|
const marketMap = await this.currentRateService.getToday( |
|
|
|
|
|
this.mapToDataGatheringItems(orders) |
|
|
|
|
|
); |
|
|
|
|
|
const endString = format(end, DATE_FORMAT); |
|
|
|
|
|
let exchangeRates = await Promise.all( |
|
|
|
|
|
Object.keys(holdings[endString]).map(async (holding) => { |
|
|
|
|
|
let symbol = marketMap.find((m) => m.symbol === holding); |
|
|
|
|
|
let symbolCurrency = this.getCurrencyFromActivities(orders, holding); |
|
|
|
|
|
let exchangeRate = await this.exchangeRateDataService.toCurrencyAtDate( |
|
|
|
|
|
1, |
|
|
|
|
|
symbolCurrency, |
|
|
|
|
|
this.currency, |
|
|
|
|
|
end |
|
|
|
|
|
); |
|
|
|
|
|
return { symbolCurrency, exchangeRate }; |
|
|
|
|
|
}) |
|
|
|
|
|
); |
|
|
|
|
|
let currencyRates = exchangeRates.reduce<{ [currency: string]: number }>( |
|
|
|
|
|
(all, currency): { [currency: string]: number } => { |
|
|
|
|
|
all[currency.symbolCurrency] ??= currency.exchangeRate; |
|
|
|
|
|
return all; |
|
|
|
|
|
}, |
|
|
|
|
|
{} |
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
let totalInvestment = await Object.keys(holdings[endString]).reduce( |
|
|
|
|
|
(sum, holding) => { |
|
|
|
|
|
if (!holdings[endString][holding].toNumber()) { |
|
|
|
|
|
return sum; |
|
|
|
|
|
} |
|
|
|
|
|
let symbol = marketMap.find((m) => m.symbol === holding); |
|
|
|
|
|
|
|
|
|
|
|
if (symbol?.marketPrice === undefined) { |
|
|
|
|
|
Logger.warn( |
|
|
|
|
|
`Missing historical market data for ${holding} (${end})`, |
|
|
|
|
|
'PortfolioCalculator' |
|
|
|
|
|
); |
|
|
|
|
|
return sum; |
|
|
|
|
|
} else { |
|
|
|
|
|
let symbolCurrency = this.getCurrency(holding); |
|
|
|
|
|
let price = new Big(currencyRates[symbolCurrency]).mul( |
|
|
|
|
|
symbol.marketPrice |
|
|
|
|
|
); |
|
|
|
|
|
return sum.plus(new Big(price).mul(holdings[endString][holding])); |
|
|
|
|
|
} |
|
|
|
|
|
}, |
|
|
|
|
|
new Big(0) |
|
|
|
|
|
); |
|
|
|
|
|
return totalInvestment; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
@LogPerformance |
|
|
@LogPerformance |
|
|
private async getTimeWeightedChartData({ |
|
|
private async getTimeWeightedChartData({ |
|
|
end = new Date(Date.now()), |
|
|
dates |
|
|
start, |
|
|
|
|
|
step = 1 |
|
|
|
|
|
}: { |
|
|
}: { |
|
|
end?: Date; |
|
|
dates?: Date[]; |
|
|
start: Date; |
|
|
|
|
|
step?: number; |
|
|
|
|
|
}): Promise<HistoricalDataItem[]> { |
|
|
}): Promise<HistoricalDataItem[]> { |
|
|
let marketMapTask = this.computeMarketMap({ gte: start, lte: end }); |
|
|
dates = dates.sort((a, b) => a.getTime() - b.getTime()); |
|
|
const timelineHoldings = await this.getHoldings(start, end); |
|
|
const start = dates[0]; |
|
|
|
|
|
const end = dates[dates.length - 1]; |
|
|
const calculationDates = Object.keys(timelineHoldings) |
|
|
let marketMapTask = this.computeMarketMap({ |
|
|
.filter((date) => { |
|
|
gte: start, |
|
|
let parsed = parseDate(date); |
|
|
lt: addDays(end, 1) |
|
|
return ( |
|
|
}); |
|
|
isAfter(parsed, subDays(start, 1)) && |
|
|
const timelineHoldings = await this.getHoldings( |
|
|
isBefore(parsed, addDays(end, 1)) |
|
|
this.activities, |
|
|
); |
|
|
start, |
|
|
}) |
|
|
end |
|
|
.sort((a, b) => parseDate(a).getTime() - parseDate(b).getTime()); |
|
|
); |
|
|
|
|
|
|
|
|
let data: HistoricalDataItem[] = []; |
|
|
let data: HistoricalDataItem[] = []; |
|
|
const startString = format(start, DATE_FORMAT); |
|
|
const startString = format(start, DATE_FORMAT); |
|
|
|
|
|
|
|
@ -119,7 +233,7 @@ export class CPRPortfolioCalculator extends TWRPortfolioCalculator { |
|
|
valueWithCurrencyEffect: 0 |
|
|
valueWithCurrencyEffect: 0 |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
await marketMapTask; |
|
|
this.marketMap = await marketMapTask; |
|
|
|
|
|
|
|
|
let totalInvestment = Object.keys(timelineHoldings[startString]).reduce( |
|
|
let totalInvestment = Object.keys(timelineHoldings[startString]).reduce( |
|
|
(sum, holding) => { |
|
|
(sum, holding) => { |
|
@ -135,9 +249,9 @@ export class CPRPortfolioCalculator extends TWRPortfolioCalculator { |
|
|
let previousNetPerformanceInPercentage = new Big(0); |
|
|
let previousNetPerformanceInPercentage = new Big(0); |
|
|
let previousNetPerformanceInPercentageWithCurrencyEffect = new Big(0); |
|
|
let previousNetPerformanceInPercentageWithCurrencyEffect = new Big(0); |
|
|
|
|
|
|
|
|
for (let i = 1; i < calculationDates.length; i++) { |
|
|
for (let i = 1; i < dates.length; i++) { |
|
|
const date = calculationDates[i]; |
|
|
const date = format(dates[i], DATE_FORMAT); |
|
|
const previousDate = calculationDates[i - 1]; |
|
|
const previousDate = format(dates[i - 1], DATE_FORMAT); |
|
|
const holdings = timelineHoldings[previousDate]; |
|
|
const holdings = timelineHoldings[previousDate]; |
|
|
let newTotalInvestment = new Big(0); |
|
|
let newTotalInvestment = new Big(0); |
|
|
let netPerformanceInPercentage = new Big(0); |
|
|
let netPerformanceInPercentage = new Big(0); |
|
@ -158,24 +272,28 @@ export class CPRPortfolioCalculator extends TWRPortfolioCalculator { |
|
|
netPerformanceInPercentageWithCurrencyEffect, |
|
|
netPerformanceInPercentageWithCurrencyEffect, |
|
|
newTotalInvestment |
|
|
newTotalInvestment |
|
|
)); |
|
|
)); |
|
|
totalInvestment = newTotalInvestment; |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
totalInvestment = newTotalInvestment; |
|
|
|
|
|
|
|
|
previousNetPerformanceInPercentage = |
|
|
previousNetPerformanceInPercentage = previousNetPerformanceInPercentage |
|
|
previousNetPerformanceInPercentage.mul( |
|
|
.plus(1) |
|
|
netPerformanceInPercentage.plus(1) |
|
|
.mul(netPerformanceInPercentage.plus(1)) |
|
|
); |
|
|
.minus(1); |
|
|
previousNetPerformanceInPercentageWithCurrencyEffect = |
|
|
previousNetPerformanceInPercentageWithCurrencyEffect = |
|
|
previousNetPerformanceInPercentageWithCurrencyEffect.mul( |
|
|
previousNetPerformanceInPercentageWithCurrencyEffect |
|
|
netPerformanceInPercentageWithCurrencyEffect.plus(1) |
|
|
.plus(1) |
|
|
); |
|
|
.mul(netPerformanceInPercentageWithCurrencyEffect.plus(1)) |
|
|
|
|
|
.minus(1); |
|
|
|
|
|
|
|
|
data.push({ |
|
|
data.push({ |
|
|
date, |
|
|
date, |
|
|
netPerformanceInPercentage: |
|
|
netPerformanceInPercentage: previousNetPerformanceInPercentage |
|
|
previousNetPerformanceInPercentage.toNumber(), |
|
|
.mul(100) |
|
|
|
|
|
.toNumber(), |
|
|
netPerformanceInPercentageWithCurrencyEffect: |
|
|
netPerformanceInPercentageWithCurrencyEffect: |
|
|
previousNetPerformanceInPercentageWithCurrencyEffect.toNumber() |
|
|
previousNetPerformanceInPercentageWithCurrencyEffect |
|
|
|
|
|
.mul(100) |
|
|
|
|
|
.toNumber() |
|
|
}); |
|
|
}); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
@ -210,8 +328,9 @@ export class CPRPortfolioCalculator extends TWRPortfolioCalculator { |
|
|
|
|
|
|
|
|
if (previousHolding.eq(0)) { |
|
|
if (previousHolding.eq(0)) { |
|
|
return { |
|
|
return { |
|
|
netPerformanceInPercentage: new Big(0), |
|
|
netPerformanceInPercentage: netPerformanceInPercentage, |
|
|
netPerformanceInPercentageWithCurrencyEffect: new Big(0), |
|
|
netPerformanceInPercentageWithCurrencyEffect: |
|
|
|
|
|
netPerformanceInPercentageWithCurrencyEffect, |
|
|
newTotalInvestment: newTotalInvestment.plus( |
|
|
newTotalInvestment: newTotalInvestment.plus( |
|
|
timelineHoldings[date][holding].mul(priceInBaseCurrency) |
|
|
timelineHoldings[date][holding].mul(priceInBaseCurrency) |
|
|
) |
|
|
) |
|
@ -223,8 +342,9 @@ export class CPRPortfolioCalculator extends TWRPortfolioCalculator { |
|
|
'PortfolioCalculator' |
|
|
'PortfolioCalculator' |
|
|
); |
|
|
); |
|
|
return { |
|
|
return { |
|
|
netPerformanceInPercentage: new Big(0), |
|
|
netPerformanceInPercentage: netPerformanceInPercentage, |
|
|
netPerformanceInPercentageWithCurrencyEffect: new Big(0), |
|
|
netPerformanceInPercentageWithCurrencyEffect: |
|
|
|
|
|
netPerformanceInPercentageWithCurrencyEffect, |
|
|
newTotalInvestment: newTotalInvestment.plus( |
|
|
newTotalInvestment: newTotalInvestment.plus( |
|
|
timelineHoldings[date][holding].mul(priceInBaseCurrency) |
|
|
timelineHoldings[date][holding].mul(priceInBaseCurrency) |
|
|
) |
|
|
) |
|
@ -242,7 +362,7 @@ export class CPRPortfolioCalculator extends TWRPortfolioCalculator { |
|
|
: new Big(0); |
|
|
: new Big(0); |
|
|
const portfolioWeight = totalInvestment.toNumber() |
|
|
const portfolioWeight = totalInvestment.toNumber() |
|
|
? previousHolding.mul(previousPriceInBaseCurrency).div(totalInvestment) |
|
|
? previousHolding.mul(previousPriceInBaseCurrency).div(totalInvestment) |
|
|
: 0; |
|
|
: new Big(0); |
|
|
|
|
|
|
|
|
netPerformanceInPercentage = netPerformanceInPercentage.plus( |
|
|
netPerformanceInPercentage = netPerformanceInPercentage.plus( |
|
|
currentPrice.div(previousPrice).minus(1).mul(portfolioWeight) |
|
|
currentPrice.div(previousPrice).minus(1).mul(portfolioWeight) |
|
@ -268,8 +388,16 @@ export class CPRPortfolioCalculator extends TWRPortfolioCalculator { |
|
|
|
|
|
|
|
|
@LogPerformance |
|
|
@LogPerformance |
|
|
private getCurrency(symbol: string) { |
|
|
private getCurrency(symbol: string) { |
|
|
|
|
|
return this.getCurrencyFromActivities(this.activities, symbol); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@LogPerformance |
|
|
|
|
|
private getCurrencyFromActivities( |
|
|
|
|
|
activities: PortfolioOrder[], |
|
|
|
|
|
symbol: string |
|
|
|
|
|
) { |
|
|
if (!this.holdingCurrencies[symbol]) { |
|
|
if (!this.holdingCurrencies[symbol]) { |
|
|
this.holdingCurrencies[symbol] = this.activities.find( |
|
|
this.holdingCurrencies[symbol] = activities.find( |
|
|
(a) => a.SymbolProfile.symbol === symbol |
|
|
(a) => a.SymbolProfile.symbol === symbol |
|
|
).SymbolProfile.currency; |
|
|
).SymbolProfile.currency; |
|
|
} |
|
|
} |
|
@ -278,7 +406,11 @@ export class CPRPortfolioCalculator extends TWRPortfolioCalculator { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@LogPerformance |
|
|
@LogPerformance |
|
|
private async getHoldings(start: Date, end: Date) { |
|
|
private async getHoldings( |
|
|
|
|
|
activities: PortfolioOrder[], |
|
|
|
|
|
start: Date, |
|
|
|
|
|
end: Date |
|
|
|
|
|
) { |
|
|
if ( |
|
|
if ( |
|
|
this.holdings && |
|
|
this.holdings && |
|
|
Object.keys(this.holdings).some((h) => |
|
|
Object.keys(this.holdings).some((h) => |
|
@ -291,13 +423,25 @@ export class CPRPortfolioCalculator extends TWRPortfolioCalculator { |
|
|
return this.holdings; |
|
|
return this.holdings; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
this.computeHoldings(start, end); |
|
|
this.computeHoldings(activities, start, end); |
|
|
return this.holdings; |
|
|
return this.holdings; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@LogPerformance |
|
|
@LogPerformance |
|
|
private async computeHoldings(start: Date, end: Date) { |
|
|
private async computeHoldings( |
|
|
const investmentByDate = this.getInvestmentByDate(); |
|
|
activities: PortfolioOrder[], |
|
|
|
|
|
start: Date, |
|
|
|
|
|
end: Date |
|
|
|
|
|
) { |
|
|
|
|
|
const investmentByDate = this.getInvestmentByDate(activities); |
|
|
|
|
|
this.calculateHoldings(investmentByDate, start, end); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private calculateHoldings( |
|
|
|
|
|
investmentByDate: { [date: string]: PortfolioOrder[] }, |
|
|
|
|
|
start: Date, |
|
|
|
|
|
end: Date |
|
|
|
|
|
) { |
|
|
const transactionDates = Object.keys(investmentByDate).sort(); |
|
|
const transactionDates = Object.keys(investmentByDate).sort(); |
|
|
let dates = eachDayOfInterval({ start, end }, { step: 1 }) |
|
|
let dates = eachDayOfInterval({ start, end }, { step: 1 }) |
|
|
.map((date) => { |
|
|
.map((date) => { |
|
@ -360,8 +504,10 @@ export class CPRPortfolioCalculator extends TWRPortfolioCalculator { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@LogPerformance |
|
|
@LogPerformance |
|
|
private getInvestmentByDate(): { [date: string]: PortfolioOrder[] } { |
|
|
private getInvestmentByDate(activities: PortfolioOrder[]): { |
|
|
return this.activities.reduce((groupedByDate, order) => { |
|
|
[date: string]: PortfolioOrder[]; |
|
|
|
|
|
} { |
|
|
|
|
|
return activities.reduce((groupedByDate, order) => { |
|
|
if (!groupedByDate[order.date]) { |
|
|
if (!groupedByDate[order.date]) { |
|
|
groupedByDate[order.date] = []; |
|
|
groupedByDate[order.date] = []; |
|
|
} |
|
|
} |
|
@ -373,8 +519,10 @@ export class CPRPortfolioCalculator extends TWRPortfolioCalculator { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@LogPerformance |
|
|
@LogPerformance |
|
|
private async computeMarketMap(dateQuery: { gte: Date; lte: Date }) { |
|
|
private mapToDataGatheringItems( |
|
|
const dataGatheringItems: IDataGatheringItem[] = this.activities |
|
|
orders: PortfolioOrder[] |
|
|
|
|
|
): IDataGatheringItem[] { |
|
|
|
|
|
return orders |
|
|
.map((activity) => { |
|
|
.map((activity) => { |
|
|
return { |
|
|
return { |
|
|
symbol: activity.SymbolProfile.symbol, |
|
|
symbol: activity.SymbolProfile.symbol, |
|
@ -385,6 +533,14 @@ export class CPRPortfolioCalculator extends TWRPortfolioCalculator { |
|
|
(gathering, i, arr) => |
|
|
(gathering, i, arr) => |
|
|
arr.findIndex((t) => t.symbol === gathering.symbol) === i |
|
|
arr.findIndex((t) => t.symbol === gathering.symbol) === i |
|
|
); |
|
|
); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@LogPerformance |
|
|
|
|
|
private async computeMarketMap(dateQuery: DateQuery): Promise<{ |
|
|
|
|
|
[date: string]: { [symbol: string]: Big }; |
|
|
|
|
|
}> { |
|
|
|
|
|
const dataGatheringItems: IDataGatheringItem[] = |
|
|
|
|
|
this.mapToDataGatheringItems(this.activities); |
|
|
const { values: marketSymbols } = await this.currentRateService.getValues({ |
|
|
const { values: marketSymbols } = await this.currentRateService.getValues({ |
|
|
dataGatheringItems, |
|
|
dataGatheringItems, |
|
|
dateQuery |
|
|
dateQuery |
|
@ -408,6 +564,41 @@ export class CPRPortfolioCalculator extends TWRPortfolioCalculator { |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
this.marketMap = marketSymbolMap; |
|
|
return marketSymbolMap; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@LogPerformance |
|
|
|
|
|
private activitiesToPortfolioOrder(activities: Activity[]): PortfolioOrder[] { |
|
|
|
|
|
return activities |
|
|
|
|
|
.map( |
|
|
|
|
|
({ |
|
|
|
|
|
date, |
|
|
|
|
|
fee, |
|
|
|
|
|
quantity, |
|
|
|
|
|
SymbolProfile, |
|
|
|
|
|
tags = [], |
|
|
|
|
|
type, |
|
|
|
|
|
unitPrice |
|
|
|
|
|
}) => { |
|
|
|
|
|
if (isAfter(date, new Date(Date.now()))) { |
|
|
|
|
|
// Adapt date to today if activity is in future (e.g. liability)
|
|
|
|
|
|
// to include it in the interval
|
|
|
|
|
|
date = endOfDay(new Date(Date.now())); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return { |
|
|
|
|
|
SymbolProfile, |
|
|
|
|
|
tags, |
|
|
|
|
|
type, |
|
|
|
|
|
date: format(date, DATE_FORMAT), |
|
|
|
|
|
fee: new Big(fee), |
|
|
|
|
|
quantity: new Big(quantity), |
|
|
|
|
|
unitPrice: new Big(unitPrice) |
|
|
|
|
|
}; |
|
|
|
|
|
} |
|
|
|
|
|
) |
|
|
|
|
|
.sort((a, b) => { |
|
|
|
|
|
return a.date?.localeCompare(b.date); |
|
|
|
|
|
}); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|