Browse Source

fix(portfolio): tolerate a missing exchange rate in the calculator

When an activity's currency cannot be converted (no exchange rate for the
currency/date), the converted fee and unit price arrive as null/NaN. Passing
these to `new Big()` in the PortfolioCalculator constructor throws
"[big.js] Invalid number" and aborts the whole calculation, so a single
unconvertible activity fails the entire performance/details response with a 500.

Add a `parseToBig` guard that defaults a missing/non-finite value to 0 and logs
a warning, so the rest of the portfolio still renders. `quantity` is left as a
direct `new Big()` because it is never currency-converted.

Signed-off-by: martin <martin@0x01.local>
pull/7002/head
martin 2 days ago
parent
commit
a88a305b2a
  1. 43
      apps/api/src/app/portfolio/calculator/portfolio-calculator.ts

43
apps/api/src/app/portfolio/calculator/portfolio-calculator.ts

@ -145,10 +145,18 @@ export abstract class PortfolioCalculator {
tags,
type,
date: format(date, DATE_FORMAT),
fee: new Big(feeInAssetProfileCurrency),
feeInBaseCurrency: new Big(feeInBaseCurrency),
fee: this.parseToBig(feeInAssetProfileCurrency, 'fee', SymbolProfile),
feeInBaseCurrency: this.parseToBig(
feeInBaseCurrency,
'feeInBaseCurrency',
SymbolProfile
),
quantity: new Big(quantity),
unitPrice: new Big(unitPriceInAssetProfileCurrency)
unitPrice: this.parseToBig(
unitPriceInAssetProfileCurrency,
'unitPrice',
SymbolProfile
)
};
}
)
@ -173,6 +181,35 @@ export abstract class PortfolioCalculator {
this.snapshotPromise = this.initialize();
}
// Coerce an activity value into a Big, tolerating a missing/non-finite input.
// Values that depend on a currency conversion (fee and unit price expressed in
// the asset profile or base currency) arrive as null/NaN when no exchange rate
// is available for the activity's currency and date. Passing such a value to
// `new Big()` throws "[big.js] Invalid number" inside the constructor, which
// aborts the whole portfolio calculation — a single unconvertible activity then
// fails the entire performance/details response. Default to 0 and warn instead,
// so the rest of the portfolio still renders.
private parseToBig(
value: number,
field: string,
symbolProfile?: { currency?: string; dataSource?: string; symbol?: string }
): Big {
if (value == null || !Number.isFinite(value)) {
Logger.warn(
`Missing or invalid "${field}" (${value})` +
(symbolProfile?.symbol
? ` for ${symbolProfile.dataSource}/${symbolProfile.symbol} (${symbolProfile.currency})`
: '') +
`; defaulting to 0 (likely an unavailable exchange rate)`,
'PortfolioCalculator'
);
return new Big(0);
}
return new Big(value);
}
protected abstract calculateOverallPerformance(
positions: TimelinePosition[]
): PortfolioSnapshot;

Loading…
Cancel
Save