Browse Source

Bugfix/portfolio performance of closed position (#7610)

* Fix portfolio performance of closed position

* Update changelog
pull/7617/head
Thomas Kaul 4 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
- Resolved an error when fetching dividends from _Yahoo Finance_ for date ranges without events
### Fixed
- Fixed the performance of closed positions in the portfolio calculator caused by a rounding remainder in the investment
- 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_
- Resolved an error when fetching dividends from _Yahoo Finance_ for date ranges without events
## 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 totalLiabilities = new Big(0);
let totalLiabilitiesInBaseCurrency = new Big(0);
let totalQuantity = new Big(0);
let totalQuantityFromBuyTransactions = new Big(0);
let totalUnits = new Big(0);
let valueAtStartDate: 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(
this.activities.filter(({ assetProfile }) => {
return assetProfile.symbol === symbol;
this.activities.filter((activities) => {
return activities.assetProfile.symbol === symbol;
})
);
@ -295,10 +295,10 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator {
!unitPriceAtEndDate ||
(!unitPriceAtStartDate && isBefore(dateOfFirstTransaction, start))
) {
// A missing market price can only affect the units which are held. The
// dividends, the interest and the liabilities do not hold any units and
// are therefore not in error.
const hasActivitiesWithUnits = orders.some(({ type }) => {
// A missing market price can only affect the quantity which is held. The
// dividends, the interest and the liabilities do not hold any quantity
// and are therefore not in error.
const hasActivitiesWithQuantity = orders.some(({ type }) => {
return ['BUY', 'SELL'].includes(type);
});
@ -316,7 +316,7 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator {
grossPerformancePercentage: new Big(0),
grossPerformancePercentageWithCurrencyEffect: new Big(0),
grossPerformanceWithCurrencyEffect: new Big(0),
hasErrors: hasActivitiesWithUnits,
hasErrors: hasActivitiesWithQuantity,
initialValue: new Big(0),
initialValueWithCurrencyEffect: new Big(0),
investmentValuesAccumulated: {},
@ -489,12 +489,12 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator {
order.unitPriceFromMarketData?.mul(exchangeRateAtOrderDate ?? 1) ??
new Big(0);
const valueOfInvestmentBeforeTransaction = totalUnits.mul(
const valueOfInvestmentBeforeTransaction = totalQuantity.mul(
marketPriceInBaseCurrency
);
const valueOfInvestmentBeforeTransactionWithCurrencyEffect =
totalUnits.mul(marketPriceInBaseCurrencyWithCurrencyEffect);
totalQuantity.mul(marketPriceInBaseCurrencyWithCurrencyEffect);
if (!investmentAtStartDate && i >= indexOfStartOrder) {
investmentAtStartDate = totalInvestment ?? new Big(0);
@ -531,16 +531,19 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator {
transactionInvestmentWithCurrencyEffect
);
} else if (order.type === 'SELL') {
if (totalUnits.gt(0)) {
if (totalQuantity.gt(0)) {
const remainingQuantity = totalQuantity.minus(order.quantity);
transactionInvestment = totalInvestment
.div(totalUnits)
.mul(order.quantity)
.mul(getFactor(order.type));
.mul(remainingQuantity)
.div(totalQuantity)
.minus(totalInvestment);
transactionInvestmentWithCurrencyEffect =
totalInvestmentWithCurrencyEffect
.div(totalUnits)
.mul(order.quantity)
.mul(getFactor(order.type));
.mul(remainingQuantity)
.div(totalQuantity)
.minus(totalInvestmentWithCurrencyEffect);
}
}
@ -589,11 +592,13 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator {
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
);
@ -634,7 +639,7 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator {
totalQuantityFromBuyTransactions
);
if (totalUnits.eq(0)) {
if (totalQuantity.eq(0)) {
// Reset tracking variables when position is fully closed
totalInvestmentFromBuyTransactions = new Big(0);
totalInvestmentFromBuyTransactionsWithCurrencyEffect = new Big(0);
@ -827,14 +832,14 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator {
)
: new Big(0);
const feesPerUnit = totalUnits.gt(0)
? fees.minus(feesAtStartDate).div(totalUnits)
const feesPerUnit = totalQuantity.gt(0)
? fees.minus(feesAtStartDate).div(totalQuantity)
: new Big(0);
const feesPerUnitWithCurrencyEffect = totalUnits.gt(0)
const feesPerUnitWithCurrencyEffect = totalQuantity.gt(0)
? feesWithCurrencyEffect
.minus(feesAtStartDateWithCurrencyEffect)
.div(totalUnits)
.div(totalQuantity)
: new Big(0);
const netPerformancePercentage =
@ -1006,7 +1011,7 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator {
grossPerformance: totalGrossPerformance,
grossPerformanceWithCurrencyEffect:
totalGrossPerformanceWithCurrencyEffect,
hasErrors: totalUnits.gt(0) && (!initialValue || !unitPriceAtEndDate),
hasErrors: totalQuantity.gt(0) && (!initialValue || !unitPriceAtEndDate),
netPerformance: totalNetPerformance,
timeWeightedInvestment:
timeWeightedAverageInvestmentBetweenStartAndEndDate,

Loading…
Cancel
Save