Browse Source

Bugfix/portfolio performance of closed position (#7610)

* Fix portfolio performance of closed position

* Update changelog
pull/7617/head
Thomas Kaul 5 days ago
committed by GitHub
parent
commit
2250281790
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 6
      CHANGELOG.md
  2. 59
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator.ts

6
CHANGELOG.md

@ -14,12 +14,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed ### Fixed
- Resolved an error when fetching dividends from _Yahoo Finance_ for date ranges without events - Fixed the performance of closed positions in the portfolio calculator caused by a rounding remainder in the investment
### Fixed
- Fixed the missing mapping for Turkey in the country weightings of the _Financial Modeling Prep_ service - Fixed the missing mapping for Turkey in the country weightings of the _Financial Modeling Prep_ service
- Fixed the missing mapping for Czech Republic and Turkey in the data enhancer for asset profile data via _Yahoo Finance_ - Fixed the missing mapping for Czech Republic and Turkey in the data enhancer for asset profile data via _Yahoo Finance_
- Resolved an error when fetching dividends from _Yahoo Finance_ for date ranges without events
## 3.49.0 - 2026-08-12 ## 3.49.0 - 2026-08-12

59
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator.ts

@ -187,15 +187,15 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator {
let totalInvestmentWithCurrencyEffect = new Big(0); let totalInvestmentWithCurrencyEffect = new Big(0);
let totalLiabilities = new Big(0); let totalLiabilities = new Big(0);
let totalLiabilitiesInBaseCurrency = new Big(0); let totalLiabilitiesInBaseCurrency = new Big(0);
let totalQuantity = new Big(0);
let totalQuantityFromBuyTransactions = new Big(0); let totalQuantityFromBuyTransactions = new Big(0);
let totalUnits = new Big(0);
let valueAtStartDate: Big; let valueAtStartDate: Big;
let valueAtStartDateWithCurrencyEffect: Big; let valueAtStartDateWithCurrencyEffect: Big;
// Clone orders to keep the original values in this.orders // Deep clone as the items are enriched below and the originals are shared
let orders: PortfolioOrderItem[] = cloneDeep( let orders: PortfolioOrderItem[] = cloneDeep(
this.activities.filter(({ assetProfile }) => { this.activities.filter((activities) => {
return assetProfile.symbol === symbol; return activities.assetProfile.symbol === symbol;
}) })
); );
@ -295,10 +295,10 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator {
!unitPriceAtEndDate || !unitPriceAtEndDate ||
(!unitPriceAtStartDate && isBefore(dateOfFirstTransaction, start)) (!unitPriceAtStartDate && isBefore(dateOfFirstTransaction, start))
) { ) {
// A missing market price can only affect the units which are held. The // A missing market price can only affect the quantity which is held. The
// dividends, the interest and the liabilities do not hold any units and // dividends, the interest and the liabilities do not hold any quantity
// are therefore not in error. // and are therefore not in error.
const hasActivitiesWithUnits = orders.some(({ type }) => { const hasActivitiesWithQuantity = orders.some(({ type }) => {
return ['BUY', 'SELL'].includes(type); return ['BUY', 'SELL'].includes(type);
}); });
@ -316,7 +316,7 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator {
grossPerformancePercentage: new Big(0), grossPerformancePercentage: new Big(0),
grossPerformancePercentageWithCurrencyEffect: new Big(0), grossPerformancePercentageWithCurrencyEffect: new Big(0),
grossPerformanceWithCurrencyEffect: new Big(0), grossPerformanceWithCurrencyEffect: new Big(0),
hasErrors: hasActivitiesWithUnits, hasErrors: hasActivitiesWithQuantity,
initialValue: new Big(0), initialValue: new Big(0),
initialValueWithCurrencyEffect: new Big(0), initialValueWithCurrencyEffect: new Big(0),
investmentValuesAccumulated: {}, investmentValuesAccumulated: {},
@ -489,12 +489,12 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator {
order.unitPriceFromMarketData?.mul(exchangeRateAtOrderDate ?? 1) ?? order.unitPriceFromMarketData?.mul(exchangeRateAtOrderDate ?? 1) ??
new Big(0); new Big(0);
const valueOfInvestmentBeforeTransaction = totalUnits.mul( const valueOfInvestmentBeforeTransaction = totalQuantity.mul(
marketPriceInBaseCurrency marketPriceInBaseCurrency
); );
const valueOfInvestmentBeforeTransactionWithCurrencyEffect = const valueOfInvestmentBeforeTransactionWithCurrencyEffect =
totalUnits.mul(marketPriceInBaseCurrencyWithCurrencyEffect); totalQuantity.mul(marketPriceInBaseCurrencyWithCurrencyEffect);
if (!investmentAtStartDate && i >= indexOfStartOrder) { if (!investmentAtStartDate && i >= indexOfStartOrder) {
investmentAtStartDate = totalInvestment ?? new Big(0); investmentAtStartDate = totalInvestment ?? new Big(0);
@ -531,16 +531,19 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator {
transactionInvestmentWithCurrencyEffect transactionInvestmentWithCurrencyEffect
); );
} else if (order.type === 'SELL') { } else if (order.type === 'SELL') {
if (totalUnits.gt(0)) { if (totalQuantity.gt(0)) {
const remainingQuantity = totalQuantity.minus(order.quantity);
transactionInvestment = totalInvestment transactionInvestment = totalInvestment
.div(totalUnits) .mul(remainingQuantity)
.mul(order.quantity) .div(totalQuantity)
.mul(getFactor(order.type)); .minus(totalInvestment);
transactionInvestmentWithCurrencyEffect = transactionInvestmentWithCurrencyEffect =
totalInvestmentWithCurrencyEffect totalInvestmentWithCurrencyEffect
.div(totalUnits) .mul(remainingQuantity)
.mul(order.quantity) .div(totalQuantity)
.mul(getFactor(order.type)); .minus(totalInvestmentWithCurrencyEffect);
} }
} }
@ -589,11 +592,13 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator {
order.feeInBaseCurrencyWithCurrencyEffect ?? 0 order.feeInBaseCurrencyWithCurrencyEffect ?? 0
); );
totalUnits = totalUnits.plus(order.quantity.mul(getFactor(order.type))); totalQuantity = totalQuantity.plus(
order.quantity.mul(getFactor(order.type))
);
const valueOfInvestment = totalUnits.mul(marketPriceInBaseCurrency); const valueOfInvestment = totalQuantity.mul(marketPriceInBaseCurrency);
const valueOfInvestmentWithCurrencyEffect = totalUnits.mul( const valueOfInvestmentWithCurrencyEffect = totalQuantity.mul(
marketPriceInBaseCurrencyWithCurrencyEffect marketPriceInBaseCurrencyWithCurrencyEffect
); );
@ -634,7 +639,7 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator {
totalQuantityFromBuyTransactions totalQuantityFromBuyTransactions
); );
if (totalUnits.eq(0)) { if (totalQuantity.eq(0)) {
// Reset tracking variables when position is fully closed // Reset tracking variables when position is fully closed
totalInvestmentFromBuyTransactions = new Big(0); totalInvestmentFromBuyTransactions = new Big(0);
totalInvestmentFromBuyTransactionsWithCurrencyEffect = new Big(0); totalInvestmentFromBuyTransactionsWithCurrencyEffect = new Big(0);
@ -827,14 +832,14 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator {
) )
: new Big(0); : new Big(0);
const feesPerUnit = totalUnits.gt(0) const feesPerUnit = totalQuantity.gt(0)
? fees.minus(feesAtStartDate).div(totalUnits) ? fees.minus(feesAtStartDate).div(totalQuantity)
: new Big(0); : new Big(0);
const feesPerUnitWithCurrencyEffect = totalUnits.gt(0) const feesPerUnitWithCurrencyEffect = totalQuantity.gt(0)
? feesWithCurrencyEffect ? feesWithCurrencyEffect
.minus(feesAtStartDateWithCurrencyEffect) .minus(feesAtStartDateWithCurrencyEffect)
.div(totalUnits) .div(totalQuantity)
: new Big(0); : new Big(0);
const netPerformancePercentage = const netPerformancePercentage =
@ -1006,7 +1011,7 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator {
grossPerformance: totalGrossPerformance, grossPerformance: totalGrossPerformance,
grossPerformanceWithCurrencyEffect: grossPerformanceWithCurrencyEffect:
totalGrossPerformanceWithCurrencyEffect, totalGrossPerformanceWithCurrencyEffect,
hasErrors: totalUnits.gt(0) && (!initialValue || !unitPriceAtEndDate), hasErrors: totalQuantity.gt(0) && (!initialValue || !unitPriceAtEndDate),
netPerformance: totalNetPerformance, netPerformance: totalNetPerformance,
timeWeightedInvestment: timeWeightedInvestment:
timeWeightedAverageInvestmentBetweenStartAndEndDate, timeWeightedAverageInvestmentBetweenStartAndEndDate,

Loading…
Cancel
Save