Time weighted portfolio performance calculation (#2778)

* Implement time weighted portfolio performance calculation

* Update changelog
pull/2787/head
gizmodus 1 year ago committed by GitHub
parent 7d68905f1b
commit b183c45027
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed ### Changed
- Changed the performance calculation to a time-weighted approach
- Used the `HasPermission` annotation in endpoints - Used the `HasPermission` annotation in endpoints
## 2.32.0 - 2023-12-26 ## 2.32.0 - 2023-12-26

@ -92,6 +92,7 @@ describe('PortfolioCalculator', () => {
marketPrice: 148.9, marketPrice: 148.9,
quantity: new Big('0'), quantity: new Big('0'),
symbol: 'BALN.SW', symbol: 'BALN.SW',
timeWeightedInvestment: new Big('285.8'),
transactionCount: 2 transactionCount: 2
} }
], ],

@ -81,6 +81,7 @@ describe('PortfolioCalculator', () => {
marketPrice: 148.9, marketPrice: 148.9,
quantity: new Big('2'), quantity: new Big('2'),
symbol: 'BALN.SW', symbol: 'BALN.SW',
timeWeightedInvestment: new Big('273.2'),
transactionCount: 1 transactionCount: 1
} }
], ],

@ -73,10 +73,10 @@ describe('PortfolioCalculator', () => {
currentValue: new Big('13657.2'), currentValue: new Big('13657.2'),
errors: [], errors: [],
grossPerformance: new Big('27172.74'), grossPerformance: new Big('27172.74'),
grossPerformancePercentage: new Big('42.40043067128546016291'), grossPerformancePercentage: new Big('42.41978276196153750666'),
hasErrors: false, hasErrors: false,
netPerformance: new Big('27172.74'), netPerformance: new Big('27172.74'),
netPerformancePercentage: new Big('42.40043067128546016291'), netPerformancePercentage: new Big('42.41978276196153750666'),
positions: [ positions: [
{ {
averagePrice: new Big('320.43'), averagePrice: new Big('320.43'),
@ -85,13 +85,14 @@ describe('PortfolioCalculator', () => {
fee: new Big('0'), fee: new Big('0'),
firstBuyDate: '2015-01-01', firstBuyDate: '2015-01-01',
grossPerformance: new Big('27172.74'), grossPerformance: new Big('27172.74'),
grossPerformancePercentage: new Big('42.40043067128546016291'), grossPerformancePercentage: new Big('42.41978276196153750666'),
investment: new Big('320.43'), investment: new Big('320.43'),
netPerformance: new Big('27172.74'), netPerformance: new Big('27172.74'),
netPerformancePercentage: new Big('42.40043067128546016291'), netPerformancePercentage: new Big('42.41978276196153750666'),
marketPrice: 13657.2, marketPrice: 13657.2,
quantity: new Big('1'), quantity: new Big('1'),
symbol: 'BTCUSD', symbol: 'BTCUSD',
timeWeightedInvestment: new Big('640.56763686131386861314'),
transactionCount: 2 transactionCount: 2
} }
], ],

@ -73,10 +73,10 @@ describe('PortfolioCalculator', () => {
currentValue: new Big('87.8'), currentValue: new Big('87.8'),
errors: [], errors: [],
grossPerformance: new Big('21.93'), grossPerformance: new Big('21.93'),
grossPerformancePercentage: new Big('0.14465699208443271768'), grossPerformancePercentage: new Big('0.15113417083448194384'),
hasErrors: false, hasErrors: false,
netPerformance: new Big('17.68'), netPerformance: new Big('17.68'),
netPerformancePercentage: new Big('0.11662269129287598945'), netPerformancePercentage: new Big('0.12184460284330327256'),
positions: [ positions: [
{ {
averagePrice: new Big('75.80'), averagePrice: new Big('75.80'),
@ -85,13 +85,14 @@ describe('PortfolioCalculator', () => {
fee: new Big('4.25'), fee: new Big('4.25'),
firstBuyDate: '2022-03-07', firstBuyDate: '2022-03-07',
grossPerformance: new Big('21.93'), grossPerformance: new Big('21.93'),
grossPerformancePercentage: new Big('0.14465699208443271768'), grossPerformancePercentage: new Big('0.15113417083448194384'),
investment: new Big('75.80'), investment: new Big('75.80'),
netPerformance: new Big('17.68'), netPerformance: new Big('17.68'),
netPerformancePercentage: new Big('0.11662269129287598945'), netPerformancePercentage: new Big('0.12184460284330327256'),
marketPrice: 87.8, marketPrice: 87.8,
quantity: new Big('1'), quantity: new Big('1'),
symbol: 'NOVN.SW', symbol: 'NOVN.SW',
timeWeightedInvestment: new Big('145.10285714285714285714'),
transactionCount: 2 transactionCount: 2
} }
], ],

@ -112,6 +112,7 @@ describe('PortfolioCalculator', () => {
marketPrice: 87.8, marketPrice: 87.8,
quantity: new Big('0'), quantity: new Big('0'),
symbol: 'NOVN.SW', symbol: 'NOVN.SW',
timeWeightedInvestment: new Big('151.6'),
transactionCount: 2 transactionCount: 2
} }
], ],

@ -15,6 +15,7 @@ import {
addMilliseconds, addMilliseconds,
addMonths, addMonths,
addYears, addYears,
differenceInDays,
endOfDay, endOfDay,
format, format,
isAfter, isAfter,
@ -43,7 +44,7 @@ import { TransactionPointSymbol } from './interfaces/transaction-point-symbol.in
import { TransactionPoint } from './interfaces/transaction-point.interface'; import { TransactionPoint } from './interfaces/transaction-point.interface';
export class PortfolioCalculator { export class PortfolioCalculator {
private static readonly CALCULATE_PERCENTAGE_PERFORMANCE_WITH_MAX_INVESTMENT = private static readonly CALCULATE_PERCENTAGE_PERFORMANCE_WITH_TIME_WEIGHTED_INVESTMENT =
true; true;
private static readonly ENABLE_LOGGING = false; private static readonly ENABLE_LOGGING = false;
@ -238,12 +239,13 @@ export class PortfolioCalculator {
} }
} }
const valuesByDate: { const accumulatedValuesByDate: {
[date: string]: { [date: string]: {
maxTotalInvestmentValue: Big; maxTotalInvestmentValue: Big;
totalCurrentValue: Big; totalCurrentValue: Big;
totalInvestmentValue: Big; totalInvestmentValue: Big;
totalNetPerformanceValue: Big; totalNetPerformanceValue: Big;
totalTimeWeightedInvestmentValue: Big;
}; };
} = {}; } = {};
@ -253,6 +255,7 @@ export class PortfolioCalculator {
investmentValues: { [date: string]: Big }; investmentValues: { [date: string]: Big };
maxInvestmentValues: { [date: string]: Big }; maxInvestmentValues: { [date: string]: Big };
netPerformanceValues: { [date: string]: Big }; netPerformanceValues: { [date: string]: Big };
timeWeightedInvestmentValues: { [date: string]: Big };
}; };
} = {}; } = {};
@ -261,7 +264,8 @@ export class PortfolioCalculator {
currentValues, currentValues,
investmentValues, investmentValues,
maxInvestmentValues, maxInvestmentValues,
netPerformanceValues netPerformanceValues,
timeWeightedInvestmentValues
} = this.getSymbolMetrics({ } = this.getSymbolMetrics({
end, end,
marketSymbolMap, marketSymbolMap,
@ -275,7 +279,8 @@ export class PortfolioCalculator {
currentValues, currentValues,
investmentValues, investmentValues,
maxInvestmentValues, maxInvestmentValues,
netPerformanceValues netPerformanceValues,
timeWeightedInvestmentValues
}; };
} }
@ -293,38 +298,50 @@ export class PortfolioCalculator {
symbolValues.maxInvestmentValues?.[dateString] ?? new Big(0); symbolValues.maxInvestmentValues?.[dateString] ?? new Big(0);
const netPerformanceValue = const netPerformanceValue =
symbolValues.netPerformanceValues?.[dateString] ?? new Big(0); symbolValues.netPerformanceValues?.[dateString] ?? new Big(0);
const timeWeightedInvestmentValue =
symbolValues.timeWeightedInvestmentValues?.[dateString] ?? new Big(0);
valuesByDate[dateString] = { accumulatedValuesByDate[dateString] = {
totalCurrentValue: ( totalCurrentValue: (
valuesByDate[dateString]?.totalCurrentValue ?? new Big(0) accumulatedValuesByDate[dateString]?.totalCurrentValue ?? new Big(0)
).add(currentValue), ).add(currentValue),
totalInvestmentValue: ( totalInvestmentValue: (
valuesByDate[dateString]?.totalInvestmentValue ?? new Big(0) accumulatedValuesByDate[dateString]?.totalInvestmentValue ??
new Big(0)
).add(investmentValue), ).add(investmentValue),
totalTimeWeightedInvestmentValue: (
accumulatedValuesByDate[dateString]
?.totalTimeWeightedInvestmentValue ?? new Big(0)
).add(timeWeightedInvestmentValue),
maxTotalInvestmentValue: ( maxTotalInvestmentValue: (
valuesByDate[dateString]?.maxTotalInvestmentValue ?? new Big(0) accumulatedValuesByDate[dateString]?.maxTotalInvestmentValue ??
new Big(0)
).add(maxInvestmentValue), ).add(maxInvestmentValue),
totalNetPerformanceValue: ( totalNetPerformanceValue: (
valuesByDate[dateString]?.totalNetPerformanceValue ?? new Big(0) accumulatedValuesByDate[dateString]?.totalNetPerformanceValue ??
new Big(0)
).add(netPerformanceValue) ).add(netPerformanceValue)
}; };
} }
} }
return Object.entries(valuesByDate).map(([date, values]) => { return Object.entries(accumulatedValuesByDate).map(([date, values]) => {
const { const {
maxTotalInvestmentValue, maxTotalInvestmentValue,
totalCurrentValue, totalCurrentValue,
totalInvestmentValue, totalInvestmentValue,
totalNetPerformanceValue totalNetPerformanceValue,
totalTimeWeightedInvestmentValue
} = values; } = values;
const netPerformanceInPercentage = maxTotalInvestmentValue.eq(0) let investmentValue =
PortfolioCalculator.CALCULATE_PERCENTAGE_PERFORMANCE_WITH_TIME_WEIGHTED_INVESTMENT
? totalTimeWeightedInvestmentValue
: maxTotalInvestmentValue;
const netPerformanceInPercentage = investmentValue.eq(0)
? 0 ? 0
: totalNetPerformanceValue : totalNetPerformanceValue.div(investmentValue).mul(100).toNumber();
.div(maxTotalInvestmentValue)
.mul(100)
.toNumber();
return { return {
date, date,
@ -447,7 +464,6 @@ export class PortfolioCalculator {
if (firstIndex > 0) { if (firstIndex > 0) {
firstIndex--; firstIndex--;
} }
const initialValues: { [symbol: string]: Big } = {};
const positions: TimelinePosition[] = []; const positions: TimelinePosition[] = [];
let hasAnySymbolMetricsErrors = false; let hasAnySymbolMetricsErrors = false;
@ -461,9 +477,9 @@ export class PortfolioCalculator {
grossPerformance, grossPerformance,
grossPerformancePercentage, grossPerformancePercentage,
hasErrors, hasErrors,
initialValue,
netPerformance, netPerformance,
netPerformancePercentage netPerformancePercentage,
timeWeightedInvestment
} = this.getSymbolMetrics({ } = this.getSymbolMetrics({
end, end,
marketSymbolMap, marketSymbolMap,
@ -472,9 +488,9 @@ export class PortfolioCalculator {
}); });
hasAnySymbolMetricsErrors = hasAnySymbolMetricsErrors || hasErrors; hasAnySymbolMetricsErrors = hasAnySymbolMetricsErrors || hasErrors;
initialValues[item.symbol] = initialValue;
positions.push({ positions.push({
timeWeightedInvestment,
averagePrice: item.quantity.eq(0) averagePrice: item.quantity.eq(0)
? new Big(0) ? new Big(0)
: item.investment.div(item.quantity), : item.investment.div(item.quantity),
@ -509,7 +525,7 @@ export class PortfolioCalculator {
} }
} }
const overall = this.calculateOverallPerformance(positions, initialValues); const overall = this.calculateOverallPerformance(positions);
return { return {
...overall, ...overall,
@ -732,18 +748,13 @@ export class PortfolioCalculator {
}; };
} }
private calculateOverallPerformance( private calculateOverallPerformance(positions: TimelinePosition[]) {
positions: TimelinePosition[],
initialValues: { [symbol: string]: Big }
) {
let currentValue = new Big(0); let currentValue = new Big(0);
let grossPerformance = new Big(0); let grossPerformance = new Big(0);
let grossPerformancePercentage = new Big(0);
let hasErrors = false; let hasErrors = false;
let netPerformance = new Big(0); let netPerformance = new Big(0);
let netPerformancePercentage = new Big(0);
let sumOfWeights = new Big(0);
let totalInvestment = new Big(0); let totalInvestment = new Big(0);
let totalTimeWeightedInvestment = new Big(0);
for (const currentPosition of positions) { for (const currentPosition of positions) {
if (currentPosition.marketPrice) { if (currentPosition.marketPrice) {
@ -766,21 +777,9 @@ export class PortfolioCalculator {
hasErrors = true; hasErrors = true;
} }
if (currentPosition.grossPerformancePercentage) { if (currentPosition.timeWeightedInvestment) {
// Use the average from the initial value and the current investment as totalTimeWeightedInvestment = totalTimeWeightedInvestment.plus(
// a weight currentPosition.timeWeightedInvestment
const weight = (initialValues[currentPosition.symbol] ?? new Big(0))
.plus(currentPosition.investment)
.div(2);
sumOfWeights = sumOfWeights.plus(weight);
grossPerformancePercentage = grossPerformancePercentage.plus(
currentPosition.grossPerformancePercentage.mul(weight)
);
netPerformancePercentage = netPerformancePercentage.plus(
currentPosition.netPerformancePercentage.mul(weight)
); );
} else if (!currentPosition.quantity.eq(0)) { } else if (!currentPosition.quantity.eq(0)) {
Logger.warn( Logger.warn(
@ -791,22 +790,18 @@ export class PortfolioCalculator {
} }
} }
if (sumOfWeights.gt(0)) {
grossPerformancePercentage = grossPerformancePercentage.div(sumOfWeights);
netPerformancePercentage = netPerformancePercentage.div(sumOfWeights);
} else {
grossPerformancePercentage = new Big(0);
netPerformancePercentage = new Big(0);
}
return { return {
currentValue, currentValue,
grossPerformance, grossPerformance,
grossPerformancePercentage,
hasErrors, hasErrors,
netPerformance, netPerformance,
netPerformancePercentage, totalInvestment,
totalInvestment netPerformancePercentage: totalTimeWeightedInvestment.eq(0)
? new Big(0)
: netPerformance.div(totalTimeWeightedInvestment),
grossPerformancePercentage: totalTimeWeightedInvestment.eq(0)
? new Big(0)
: grossPerformance.div(totalTimeWeightedInvestment)
}; };
} }
@ -1018,6 +1013,7 @@ export class PortfolioCalculator {
let averagePriceAtEndDate = new Big(0); let averagePriceAtEndDate = new Big(0);
let averagePriceAtStartDate = new Big(0); let averagePriceAtStartDate = new Big(0);
const currentValues: { [date: string]: Big } = {};
let feesAtStartDate = new Big(0); let feesAtStartDate = new Big(0);
let fees = new Big(0); let fees = new Big(0);
let grossPerformance = new Big(0); let grossPerformance = new Big(0);
@ -1025,12 +1021,12 @@ export class PortfolioCalculator {
let grossPerformanceFromSells = new Big(0); let grossPerformanceFromSells = new Big(0);
let initialValue: Big; let initialValue: Big;
let investmentAtStartDate: Big; let investmentAtStartDate: Big;
const currentValues: { [date: string]: Big } = {};
const investmentValues: { [date: string]: Big } = {}; const investmentValues: { [date: string]: Big } = {};
const maxInvestmentValues: { [date: string]: Big } = {}; const maxInvestmentValues: { [date: string]: Big } = {};
let lastAveragePrice = new Big(0); let lastAveragePrice = new Big(0);
let maxTotalInvestment = new Big(0); let maxTotalInvestment = new Big(0);
const netPerformanceValues: { [date: string]: Big } = {}; const netPerformanceValues: { [date: string]: Big } = {};
const timeWeightedInvestmentValues: { [date: string]: Big } = {};
let totalInvestment = new Big(0); let totalInvestment = new Big(0);
let totalInvestmentWithGrossPerformanceFromSell = new Big(0); let totalInvestmentWithGrossPerformanceFromSell = new Big(0);
let totalUnits = new Big(0); let totalUnits = new Big(0);
@ -1122,6 +1118,9 @@ export class PortfolioCalculator {
return order.itemType === 'end'; return order.itemType === 'end';
}); });
let totalInvestmentDays = 0;
let sumOfTimeWeightedInvestments = new Big(0);
for (let i = 0; i < orders.length; i += 1) { for (let i = 0; i < orders.length; i += 1) {
const order = orders[i]; const order = orders[i];
@ -1162,11 +1161,11 @@ export class PortfolioCalculator {
order.type === 'BUY' order.type === 'BUY'
? order.quantity.mul(order.unitPrice).mul(this.getFactor(order.type)) ? order.quantity.mul(order.unitPrice).mul(this.getFactor(order.type))
: totalUnits.gt(0) : totalUnits.gt(0)
? totalInvestment ? totalInvestment
.div(totalUnits) .div(totalUnits)
.mul(order.quantity) .mul(order.quantity)
.mul(this.getFactor(order.type)) .mul(this.getFactor(order.type))
: new Big(0); : new Big(0);
if (PortfolioCalculator.ENABLE_LOGGING) { if (PortfolioCalculator.ENABLE_LOGGING) {
console.log('totalInvestment', totalInvestment.toNumber()); console.log('totalInvestment', totalInvestment.toNumber());
@ -1174,6 +1173,7 @@ export class PortfolioCalculator {
console.log('transactionInvestment', transactionInvestment.toNumber()); console.log('transactionInvestment', transactionInvestment.toNumber());
} }
const totalInvestmentBeforeTransaction = totalInvestment;
totalInvestment = totalInvestment.plus(transactionInvestment); totalInvestment = totalInvestment.plus(transactionInvestment);
if (i >= indexOfStartOrder && totalInvestment.gt(maxTotalInvestment)) { if (i >= indexOfStartOrder && totalInvestment.gt(maxTotalInvestment)) {
@ -1243,14 +1243,48 @@ export class PortfolioCalculator {
grossPerformanceAtStartDate = grossPerformance; grossPerformanceAtStartDate = grossPerformance;
} }
if (isChartMode && i > indexOfStartOrder) { if (i > indexOfStartOrder) {
currentValues[order.date] = valueOfInvestment; // Only consider periods with an investment for the calculation of
netPerformanceValues[order.date] = grossPerformance // the time weighted investment
.minus(grossPerformanceAtStartDate) if (totalInvestmentBeforeTransaction.gt(0)) {
.minus(fees.minus(feesAtStartDate)); // Calculate the number of days since the previous order
const orderDate = new Date(order.date);
const previousOrderDate = new Date(orders[i - 1].date);
let daysSinceLastOrder = differenceInDays(
orderDate,
previousOrderDate
);
// Set to at least 1 day, otherwise the transactions on the same day
// would not be considered in the time weighted calculation
if (daysSinceLastOrder <= 0) {
daysSinceLastOrder = 1;
}
// Sum up the total investment days since the start date to calculate
// the time weighted investment
totalInvestmentDays += daysSinceLastOrder;
investmentValues[order.date] = totalInvestment; sumOfTimeWeightedInvestments = sumOfTimeWeightedInvestments.add(
maxInvestmentValues[order.date] = maxTotalInvestment; totalInvestmentBeforeTransaction.mul(daysSinceLastOrder)
);
}
if (isChartMode) {
currentValues[order.date] = valueOfInvestment;
netPerformanceValues[order.date] = grossPerformance
.minus(grossPerformanceAtStartDate)
.minus(fees.minus(feesAtStartDate));
investmentValues[order.date] = totalInvestment;
maxInvestmentValues[order.date] = maxTotalInvestment;
timeWeightedInvestmentValues[order.date] =
totalInvestmentDays > 0
? sumOfTimeWeightedInvestments.div(totalInvestmentDays)
: new Big(0);
}
} }
if (PortfolioCalculator.ENABLE_LOGGING) { if (PortfolioCalculator.ENABLE_LOGGING) {
@ -1274,50 +1308,79 @@ export class PortfolioCalculator {
.minus(grossPerformanceAtStartDate) .minus(grossPerformanceAtStartDate)
.minus(fees.minus(feesAtStartDate)); .minus(fees.minus(feesAtStartDate));
const timeWeightedAverageInvestmentBetweenStartAndEndDate =
totalInvestmentDays > 0
? sumOfTimeWeightedInvestments.div(totalInvestmentDays)
: new Big(0);
const maxInvestmentBetweenStartAndEndDate = valueAtStartDate.plus( const maxInvestmentBetweenStartAndEndDate = valueAtStartDate.plus(
maxTotalInvestment.minus(investmentAtStartDate) maxTotalInvestment.minus(investmentAtStartDate)
); );
const grossPerformancePercentage = let grossPerformancePercentage: Big;
PortfolioCalculator.CALCULATE_PERCENTAGE_PERFORMANCE_WITH_MAX_INVESTMENT ||
averagePriceAtStartDate.eq(0) || if (
averagePriceAtEndDate.eq(0) || PortfolioCalculator.CALCULATE_PERCENTAGE_PERFORMANCE_WITH_TIME_WEIGHTED_INVESTMENT
orders[indexOfStartOrder].unitPrice.eq(0) ) {
? maxInvestmentBetweenStartAndEndDate.gt(0) grossPerformancePercentage =
? totalGrossPerformance.div(maxInvestmentBetweenStartAndEndDate) timeWeightedAverageInvestmentBetweenStartAndEndDate.gt(0)
: new Big(0) ? totalGrossPerformance.div(
: // This formula has the issue that buying more units with a price timeWeightedAverageInvestmentBetweenStartAndEndDate
// lower than the average buying price results in a positive
// performance even if the market price stays constant
unitPriceAtEndDate
.div(averagePriceAtEndDate)
.div(
orders[indexOfStartOrder].unitPrice.div(averagePriceAtStartDate)
) )
.minus(1); : new Big(0);
} else {
grossPerformancePercentage =
averagePriceAtStartDate.eq(0) ||
averagePriceAtEndDate.eq(0) ||
orders[indexOfStartOrder].unitPrice.eq(0)
? maxInvestmentBetweenStartAndEndDate.gt(0)
? totalGrossPerformance.div(maxInvestmentBetweenStartAndEndDate)
: new Big(0)
: // This formula has the issue that buying more units with a price
// lower than the average buying price results in a positive
// performance even if the market price stays constant
unitPriceAtEndDate
.div(averagePriceAtEndDate)
.div(
orders[indexOfStartOrder].unitPrice.div(averagePriceAtStartDate)
)
.minus(1);
}
const feesPerUnit = totalUnits.gt(0) const feesPerUnit = totalUnits.gt(0)
? fees.minus(feesAtStartDate).div(totalUnits) ? fees.minus(feesAtStartDate).div(totalUnits)
: new Big(0); : new Big(0);
const netPerformancePercentage = let netPerformancePercentage: Big;
PortfolioCalculator.CALCULATE_PERCENTAGE_PERFORMANCE_WITH_MAX_INVESTMENT ||
averagePriceAtStartDate.eq(0) || if (
averagePriceAtEndDate.eq(0) || PortfolioCalculator.CALCULATE_PERCENTAGE_PERFORMANCE_WITH_TIME_WEIGHTED_INVESTMENT
orders[indexOfStartOrder].unitPrice.eq(0) ) {
? maxInvestmentBetweenStartAndEndDate.gt(0) netPerformancePercentage =
? totalNetPerformance.div(maxInvestmentBetweenStartAndEndDate) timeWeightedAverageInvestmentBetweenStartAndEndDate.gt(0)
: new Big(0) ? totalNetPerformance.div(
: // This formula has the issue that buying more units with a price timeWeightedAverageInvestmentBetweenStartAndEndDate
// lower than the average buying price results in a positive
// performance even if the market price stays constant
unitPriceAtEndDate
.minus(feesPerUnit)
.div(averagePriceAtEndDate)
.div(
orders[indexOfStartOrder].unitPrice.div(averagePriceAtStartDate)
) )
.minus(1); : new Big(0);
} else {
netPerformancePercentage =
averagePriceAtStartDate.eq(0) ||
averagePriceAtEndDate.eq(0) ||
orders[indexOfStartOrder].unitPrice.eq(0)
? maxInvestmentBetweenStartAndEndDate.gt(0)
? totalNetPerformance.div(maxInvestmentBetweenStartAndEndDate)
: new Big(0)
: // This formula has the issue that buying more units with a price
// lower than the average buying price results in a positive
// performance even if the market price stays constant
unitPriceAtEndDate
.minus(feesPerUnit)
.div(averagePriceAtEndDate)
.div(
orders[indexOfStartOrder].unitPrice.div(averagePriceAtStartDate)
)
.minus(1);
}
if (PortfolioCalculator.ENABLE_LOGGING) { if (PortfolioCalculator.ENABLE_LOGGING) {
console.log( console.log(
@ -1330,6 +1393,9 @@ export class PortfolioCalculator {
2 2
)} -> ${averagePriceAtEndDate.toFixed(2)} )} -> ${averagePriceAtEndDate.toFixed(2)}
Total investment: ${totalInvestment.toFixed(2)} Total investment: ${totalInvestment.toFixed(2)}
Time weighted investment: ${timeWeightedAverageInvestmentBetweenStartAndEndDate.toFixed(
2
)}
Max. total investment: ${maxTotalInvestment.toFixed(2)} Max. total investment: ${maxTotalInvestment.toFixed(2)}
Gross performance: ${totalGrossPerformance.toFixed( Gross performance: ${totalGrossPerformance.toFixed(
2 2
@ -1349,9 +1415,12 @@ export class PortfolioCalculator {
maxInvestmentValues, maxInvestmentValues,
netPerformancePercentage, netPerformancePercentage,
netPerformanceValues, netPerformanceValues,
timeWeightedInvestmentValues,
grossPerformance: totalGrossPerformance, grossPerformance: totalGrossPerformance,
hasErrors: totalUnits.gt(0) && (!initialValue || !unitPriceAtEndDate), hasErrors: totalUnits.gt(0) && (!initialValue || !unitPriceAtEndDate),
netPerformance: totalNetPerformance netPerformance: totalNetPerformance,
timeWeightedInvestment:
timeWeightedAverageInvestmentBetweenStartAndEndDate
}; };
} }

@ -16,5 +16,6 @@ export interface TimelinePosition {
quantity: Big; quantity: Big;
symbol: string; symbol: string;
tags?: Tag[]; tags?: Tag[];
timeWeightedInvestment: Big;
transactionCount: number; transactionCount: number;
} }

Loading…
Cancel
Save