Browse Source

Improve algorithm

pull/701/head
Reto Kaul 3 years ago
parent
commit
3a4254b98e
  1. 141
      apps/api/src/app/portfolio/portfolio-calculator-new.ts

141
apps/api/src/app/portfolio/portfolio-calculator-new.ts

@ -33,6 +33,8 @@ import { TransactionPointSymbol } from './interfaces/transaction-point-symbol.in
import { TransactionPoint } from './interfaces/transaction-point.interface';
export class PortfolioCalculatorNew {
private static readonly ENABLE_LOGGING = false;
private currency: string;
private currentRateService: CurrentRateService;
private orders: PortfolioOrder[];
@ -665,6 +667,8 @@ export class PortfolioCalculatorNew {
};
}
let averagePriceAtEndDate = new Big(0);
let averagePriceAtStartDate = new Big(0);
let feesAtStartDate = new Big(0);
let fees = new Big(0);
let grossPerformance = new Big(0);
@ -674,17 +678,13 @@ export class PortfolioCalculatorNew {
let lastAveragePrice = new Big(0);
let lastTransactionInvestment = new Big(0);
let lastValueOfInvestmentBeforeTransaction = new Big(0);
let maxTotalInvestment = new Big(0);
let timeWeightedGrossPerformancePercentage = new Big(1);
let timeWeightedNetPerformancePercentage = new Big(1);
let totalInvestment = new Big(0);
let totalInvestmentWithGrossPerformanceFromSell = new Big(0);
let totalUnits = new Big(0);
const holdingPeriodPerformances: {
grossReturn: Big;
netReturn: Big;
valueOfInvestment: Big;
}[] = [];
// Add a synthetic order at the start and the end date
orders.push({
symbol,
@ -696,7 +696,7 @@ export class PortfolioCalculatorNew {
name: '',
quantity: new Big(0),
type: TypeOfOrder.BUY,
unitPrice: unitPriceAtStartDate ?? new Big(0)
unitPrice: unitPriceAtStartDate
});
orders.push({
@ -709,7 +709,7 @@ export class PortfolioCalculatorNew {
name: '',
quantity: new Big(0),
type: TypeOfOrder.BUY,
unitPrice: unitPriceAtEndDate ?? new Big(0)
unitPrice: unitPriceAtEndDate
});
// Sort orders so that the start and end placeholder order are at the right
@ -732,9 +732,31 @@ export class PortfolioCalculatorNew {
return order.itemType === 'start';
});
const indexOfEndOrder = orders.findIndex((order) => {
return order.itemType === 'end';
});
for (let i = 0; i < orders.length; i += 1) {
const order = orders[i];
if (order.itemType === 'start') {
// Take the unit price of the order as the market price if there are no
// orders of this symbol before the start date
order.unitPrice =
indexOfStartOrder === 0
? orders[i + 1]?.unitPrice
: unitPriceAtStartDate;
}
// Calculate the average start price as soon as any units are held
if (
averagePriceAtStartDate.eq(0) &&
i >= indexOfStartOrder &&
totalUnits.gt(0)
) {
averagePriceAtStartDate = totalInvestment.div(totalUnits);
}
const valueOfInvestmentBeforeTransaction = totalUnits.mul(
order.unitPrice
);
@ -743,6 +765,16 @@ export class PortfolioCalculatorNew {
.mul(order.unitPrice)
.mul(this.getFactor(order.type));
totalInvestment = totalInvestment.plus(transactionInvestment);
if (totalInvestment.gt(maxTotalInvestment)) {
maxTotalInvestment = totalInvestment;
}
if (i === indexOfEndOrder && totalUnits.gt(0)) {
averagePriceAtEndDate = totalInvestment.div(totalUnits);
}
if (i >= indexOfStartOrder && !initialValue) {
if (
i === indexOfStartOrder &&
@ -771,16 +803,17 @@ export class PortfolioCalculatorNew {
grossPerformanceFromSell
);
totalInvestment = totalInvestment
.plus(transactionInvestment)
.plus(grossPerformanceFromSell);
totalInvestmentWithGrossPerformanceFromSell =
totalInvestmentWithGrossPerformanceFromSell
.plus(transactionInvestment)
.plus(grossPerformanceFromSell);
lastAveragePrice = totalUnits.eq(0)
? new Big(0)
: totalInvestment.div(totalUnits);
: totalInvestmentWithGrossPerformanceFromSell.div(totalUnits);
const newGrossPerformance = valueOfInvestment
.minus(totalInvestment)
.minus(totalInvestmentWithGrossPerformanceFromSell)
.plus(grossPerformanceFromSells);
if (
@ -823,14 +856,6 @@ export class PortfolioCalculatorNew {
timeWeightedNetPerformancePercentage.mul(
new Big(1).plus(netHoldingPeriodReturn)
);
holdingPeriodPerformances.push({
grossReturn: grossHoldingPeriodReturn,
netReturn: netHoldingPeriodReturn,
valueOfInvestment: lastValueOfInvestmentBeforeTransaction.plus(
lastTransactionInvestment
)
});
}
grossPerformance = newGrossPerformance;
@ -860,39 +885,61 @@ export class PortfolioCalculatorNew {
.minus(grossPerformanceAtStartDate)
.minus(fees.minus(feesAtStartDate));
let valueOfInvestmentSum = new Big(0);
for (const holdingPeriodPerformance of holdingPeriodPerformances) {
valueOfInvestmentSum = valueOfInvestmentSum.plus(
holdingPeriodPerformance.valueOfInvestment
);
}
let totalWeightedGrossPerformance = new Big(0);
let totalWeightedNetPerformance = new Big(0);
// Weight the holding period returns according to their value of investment
for (const holdingPeriodPerformance of holdingPeriodPerformances) {
totalWeightedGrossPerformance = totalWeightedGrossPerformance.plus(
holdingPeriodPerformance.grossReturn
.mul(holdingPeriodPerformance.valueOfInvestment)
.div(valueOfInvestmentSum)
);
totalWeightedNetPerformance = totalWeightedNetPerformance.plus(
holdingPeriodPerformance.netReturn
.mul(holdingPeriodPerformance.valueOfInvestment)
.div(valueOfInvestmentSum)
const grossPerformancePercentage =
averagePriceAtStartDate.eq(0) ||
averagePriceAtEndDate.eq(0) ||
orders[indexOfStartOrder].unitPrice.eq(0)
? totalGrossPerformance.div(maxTotalInvestment)
: unitPriceAtEndDate
.div(averagePriceAtEndDate)
.div(
orders[indexOfStartOrder].unitPrice.div(averagePriceAtStartDate)
)
.minus(1);
const feesPerUnit = totalUnits.gt(0)
? fees.minus(feesAtStartDate).div(totalUnits)
: new Big(0);
const netPerformancePercentage =
averagePriceAtStartDate.eq(0) ||
averagePriceAtEndDate.plus(feesPerUnit).eq(0) ||
orders[indexOfStartOrder].unitPrice.eq(0)
? totalNetPerformance.div(maxTotalInvestment)
: unitPriceAtEndDate
.div(averagePriceAtEndDate.plus(feesPerUnit))
.div(
orders[indexOfStartOrder].unitPrice.div(averagePriceAtStartDate)
)
.minus(1);
if (PortfolioCalculatorNew.ENABLE_LOGGING) {
console.log(
`
${symbol}
Unit price: ${orders[indexOfStartOrder].unitPrice.toFixed(
2
)} -> ${unitPriceAtEndDate.toFixed(2)}
Average price: ${averagePriceAtStartDate.toFixed(
2
)} -> ${averagePriceAtEndDate.toFixed(2)}
Max. total investment: ${maxTotalInvestment.toFixed(2)}
Gross performance: ${totalGrossPerformance.toFixed(
2
)} / ${grossPerformancePercentage.mul(100).toFixed(2)}%
Net performance: ${totalNetPerformance.toFixed(
2
)} / ${netPerformancePercentage.mul(100).toFixed(2)}%`
);
}
return {
initialValue,
grossPerformancePercentage,
netPerformancePercentage,
hasErrors: !initialValue || !unitPriceAtEndDate,
netPerformance: totalNetPerformance,
netPerformancePercentage: totalWeightedNetPerformance,
grossPerformance: totalGrossPerformance,
grossPerformancePercentage: totalWeightedGrossPerformance
grossPerformance: totalGrossPerformance
};
}

Loading…
Cancel
Save