|
|
@ -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 |
|
|
|
}; |
|
|
|
} |
|
|
|
|
|
|
|