Browse Source

non-investment activities not included in holdings

pull/5182/head
Attila Cseh 1 week ago
parent
commit
230e11acc3
  1. 127
      apps/api/src/app/portfolio/calculator/portfolio-calculator.ts
  2. 9
      libs/common/src/lib/config.ts

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

@ -13,6 +13,7 @@ import { IDataGatheringItem } from '@ghostfolio/api/services/interfaces/interfac
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service'; import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import { getIntervalFromDateRange } from '@ghostfolio/common/calculation-helper'; import { getIntervalFromDateRange } from '@ghostfolio/common/calculation-helper';
import { import {
INVESTMENT_ACTIVITY_TYPES,
PORTFOLIO_SNAPSHOT_PROCESS_JOB_NAME, PORTFOLIO_SNAPSHOT_PROCESS_JOB_NAME,
PORTFOLIO_SNAPSHOT_PROCESS_JOB_OPTIONS, PORTFOLIO_SNAPSHOT_PROCESS_JOB_OPTIONS,
PORTFOLIO_SNAPSHOT_COMPUTATION_QUEUE_PRIORITY_HIGH, PORTFOLIO_SNAPSHOT_COMPUTATION_QUEUE_PRIORITY_HIGH,
@ -898,70 +899,72 @@ export abstract class PortfolioCalculator {
} of this.activities) { } of this.activities) {
let currentTransactionPointItem: TransactionPointSymbol; let currentTransactionPointItem: TransactionPointSymbol;
const currency = SymbolProfile.currency; if (INVESTMENT_ACTIVITY_TYPES.includes(type)) {
const dataSource = SymbolProfile.dataSource; const currency = SymbolProfile.currency;
const factor = getFactor(type); const dataSource = SymbolProfile.dataSource;
const skipErrors = !!SymbolProfile.userId; // Skip errors for custom asset profiles const factor = getFactor(type);
const symbol = SymbolProfile.symbol; const skipErrors = !!SymbolProfile.userId; // Skip errors for custom asset profiles
const symbol = SymbolProfile.symbol;
const oldAccumulatedSymbol = symbols[symbol];
const oldAccumulatedSymbol = symbols[symbol];
if (oldAccumulatedSymbol) {
let investment = oldAccumulatedSymbol.investment; if (oldAccumulatedSymbol) {
let investment = oldAccumulatedSymbol.investment;
const newQuantity = quantity
.mul(factor) const newQuantity = quantity
.plus(oldAccumulatedSymbol.quantity); .mul(factor)
.plus(oldAccumulatedSymbol.quantity);
if (type === 'BUY') {
investment = oldAccumulatedSymbol.investment.plus(
quantity.mul(unitPrice)
);
} else if (type === 'SELL') {
investment = oldAccumulatedSymbol.investment.minus(
quantity.mul(oldAccumulatedSymbol.averagePrice)
);
}
if (type === 'BUY') { currentTransactionPointItem = {
investment = oldAccumulatedSymbol.investment.plus( currency,
quantity.mul(unitPrice) dataSource,
); investment,
} else if (type === 'SELL') { skipErrors,
investment = oldAccumulatedSymbol.investment.minus( symbol,
quantity.mul(oldAccumulatedSymbol.averagePrice) averagePrice: newQuantity.gt(0)
); ? investment.div(newQuantity)
: new Big(0),
dividend: new Big(0),
fee: oldAccumulatedSymbol.fee.plus(fee),
firstBuyDate: oldAccumulatedSymbol.firstBuyDate,
quantity: newQuantity,
tags: oldAccumulatedSymbol.tags.concat(tags),
transactionCount: oldAccumulatedSymbol.transactionCount + 1
};
} else {
currentTransactionPointItem = {
currency,
dataSource,
fee,
skipErrors,
symbol,
tags,
averagePrice: unitPrice,
dividend: new Big(0),
firstBuyDate: date,
investment: unitPrice.mul(quantity).mul(factor),
quantity: quantity.mul(factor),
transactionCount: 1
};
} }
currentTransactionPointItem = { currentTransactionPointItem.tags = uniqBy(
currency, currentTransactionPointItem.tags,
dataSource, 'id'
investment, );
skipErrors,
symbol,
averagePrice: newQuantity.gt(0)
? investment.div(newQuantity)
: new Big(0),
dividend: new Big(0),
fee: oldAccumulatedSymbol.fee.plus(fee),
firstBuyDate: oldAccumulatedSymbol.firstBuyDate,
quantity: newQuantity,
tags: oldAccumulatedSymbol.tags.concat(tags),
transactionCount: oldAccumulatedSymbol.transactionCount + 1
};
} else {
currentTransactionPointItem = {
currency,
dataSource,
fee,
skipErrors,
symbol,
tags,
averagePrice: unitPrice,
dividend: new Big(0),
firstBuyDate: date,
investment: unitPrice.mul(quantity).mul(factor),
quantity: quantity.mul(factor),
transactionCount: 1
};
}
currentTransactionPointItem.tags = uniqBy(
currentTransactionPointItem.tags,
'id'
);
symbols[SymbolProfile.symbol] = currentTransactionPointItem; symbols[SymbolProfile.symbol] = currentTransactionPointItem;
}
const items = lastTransactionPoint?.items ?? []; const items = lastTransactionPoint?.items ?? [];
@ -969,7 +972,9 @@ export abstract class PortfolioCalculator {
return symbol !== SymbolProfile.symbol; return symbol !== SymbolProfile.symbol;
}); });
newItems.push(currentTransactionPointItem); if (currentTransactionPointItem) {
newItems.push(currentTransactionPointItem);
}
newItems.sort((a, b) => { newItems.sort((a, b) => {
return a.symbol?.localeCompare(b.symbol); return a.symbol?.localeCompare(b.symbol);

9
libs/common/src/lib/config.ts

@ -1,4 +1,4 @@
import { AssetClass, AssetSubClass, DataSource } from '@prisma/client'; import { AssetClass, AssetSubClass, DataSource, Type } from '@prisma/client';
import { JobOptions, JobStatus } from 'bull'; import { JobOptions, JobStatus } from 'bull';
import ms from 'ms'; import ms from 'ms';
@ -128,6 +128,13 @@ export const HEADER_KEY_TIMEZONE = 'Timezone';
export const HEADER_KEY_TOKEN = 'Authorization'; export const HEADER_KEY_TOKEN = 'Authorization';
export const HEADER_KEY_SKIP_INTERCEPTOR = 'X-Skip-Interceptor'; export const HEADER_KEY_SKIP_INTERCEPTOR = 'X-Skip-Interceptor';
export const INVESTMENT_ACTIVITY_TYPES = [
Type.BUY,
Type.DIVIDEND,
Type.ITEM,
Type.SELL
] as Type[];
export const MAX_TOP_HOLDINGS = 50; export const MAX_TOP_HOLDINGS = 50;
export const NUMERICAL_PRECISION_THRESHOLD = 100000; export const NUMERICAL_PRECISION_THRESHOLD = 100000;

Loading…
Cancel
Save