Browse Source

filter non-investment positions

pull/5182/head
Attila Cseh 4 days ago
parent
commit
ac0735557e
  1. 148
      apps/api/src/app/portfolio/calculator/portfolio-calculator.ts
  2. 32
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-fee.spec.ts
  3. 1
      apps/api/src/app/portfolio/interfaces/transaction-point-symbol.interface.ts
  4. 14
      libs/common/src/lib/config.ts

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

@ -288,7 +288,9 @@ export abstract class PortfolioCalculator {
firstIndex--;
}
const positions: TimelinePosition[] = [];
const positions: (TimelinePosition & {
isInvestmentAssetProfilePosition: boolean;
})[] = [];
let hasAnySymbolMetricsErrors = false;
const errors: ResponseError['errors'] = [];
@ -412,6 +414,7 @@ export abstract class PortfolioCalculator {
: null,
investment: totalInvestment,
investmentWithCurrencyEffect: totalInvestmentWithCurrencyEffect,
isInvestmentAssetProfilePosition: item.isInvestmentAssetProfileItem,
marketPrice:
marketSymbolMap[endDateString]?.[item.symbol]?.toNumber() ?? null,
marketPriceInBaseCurrency:
@ -606,14 +609,23 @@ export abstract class PortfolioCalculator {
const overall = this.calculateOverallPerformance(positions);
const investmentPositions = positions
.filter(({ isInvestmentAssetProfilePosition }) => {
return isInvestmentAssetProfilePosition;
})
// eslint-disable-next-line @typescript-eslint/no-unused-vars
.map(({ isInvestmentAssetProfilePosition, ...rest }) => {
return rest;
});
return {
...overall,
errors,
historicalData,
positions,
totalInterestWithCurrencyEffect,
totalLiabilitiesWithCurrencyEffect,
hasErrors: hasAnySymbolMetricsErrors || overall.hasErrors
hasErrors: hasAnySymbolMetricsErrors || overall.hasErrors,
positions: investmentPositions
};
}
@ -899,82 +911,82 @@ export abstract class PortfolioCalculator {
} of this.activities) {
let currentTransactionPointItem: TransactionPointSymbol;
if (INVESTMENT_ACTIVITY_TYPES.includes(type)) {
const currency = SymbolProfile.currency;
const dataSource = SymbolProfile.dataSource;
const factor = getFactor(type);
const skipErrors = !!SymbolProfile.userId; // Skip errors for custom asset profiles
const symbol = SymbolProfile.symbol;
const oldAccumulatedSymbol = symbols[symbol];
if (oldAccumulatedSymbol) {
let investment = oldAccumulatedSymbol.investment;
const newQuantity = 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)
);
}
const currency = SymbolProfile.currency;
const dataSource = SymbolProfile.dataSource;
const factor = getFactor(type);
const skipErrors = !!SymbolProfile.userId; // Skip errors for custom asset profiles
const symbol = SymbolProfile.symbol;
currentTransactionPointItem = {
currency,
dataSource,
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
};
}
const oldAccumulatedSymbol = symbols[symbol];
currentTransactionPointItem.tags = uniqBy(
currentTransactionPointItem.tags,
'id'
);
if (oldAccumulatedSymbol) {
let investment = oldAccumulatedSymbol.investment;
symbols[SymbolProfile.symbol] = currentTransactionPointItem;
const newQuantity = 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)
);
}
currentTransactionPointItem = {
currency,
dataSource,
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,
isInvestmentAssetProfileItem:
oldAccumulatedSymbol.isInvestmentAssetProfileItem,
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),
isInvestmentAssetProfileItem:
INVESTMENT_ACTIVITY_TYPES.includes(type),
quantity: quantity.mul(factor),
transactionCount: 1
};
}
currentTransactionPointItem.tags = uniqBy(
currentTransactionPointItem.tags,
'id'
);
symbols[SymbolProfile.symbol] = currentTransactionPointItem;
const items = lastTransactionPoint?.items ?? [];
const newItems = items.filter(({ symbol }) => {
return symbol !== SymbolProfile.symbol;
});
if (currentTransactionPointItem) {
newItems.push(currentTransactionPointItem);
}
newItems.push(currentTransactionPointItem);
newItems.sort((a, b) => {
return a.symbol?.localeCompare(b.symbol);

32
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-fee.spec.ts

@ -116,37 +116,7 @@ describe('PortfolioCalculator', () => {
currentValueInBaseCurrency: new Big('0'),
errors: [],
hasErrors: true,
positions: [
{
averagePrice: new Big('0'),
currency: 'USD',
dataSource: 'MANUAL',
dividend: new Big('0'),
dividendInBaseCurrency: new Big('0'),
fee: new Big('49'),
feeInBaseCurrency: new Big('49'),
firstBuyDate: '2021-09-01',
grossPerformance: null,
grossPerformancePercentage: null,
grossPerformancePercentageWithCurrencyEffect: null,
grossPerformanceWithCurrencyEffect: null,
investment: new Big('0'),
investmentWithCurrencyEffect: new Big('0'),
marketPrice: null,
marketPriceInBaseCurrency: 0,
netPerformance: null,
netPerformancePercentage: null,
netPerformancePercentageWithCurrencyEffectMap: null,
netPerformanceWithCurrencyEffectMap: null,
quantity: new Big('0'),
symbol: '2c463fb3-af07-486e-adb0-8301b3d72141',
tags: [],
timeWeightedInvestment: new Big('0'),
timeWeightedInvestmentWithCurrencyEffect: new Big('0'),
transactionCount: 1,
valueInBaseCurrency: new Big('0')
}
],
positions: [],
totalFeesWithCurrencyEffect: new Big('49'),
totalInterestWithCurrencyEffect: new Big('0'),
totalInvestment: new Big('0'),

1
apps/api/src/app/portfolio/interfaces/transaction-point-symbol.interface.ts

@ -9,6 +9,7 @@ export interface TransactionPointSymbol {
fee: Big;
firstBuyDate: string;
investment: Big;
isInvestmentAssetProfileItem: boolean;
quantity: Big;
skipErrors: boolean;
symbol: string;

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

@ -118,6 +118,13 @@ export const GATHER_HISTORICAL_MARKET_DATA_PROCESS_JOB_OPTIONS: JobOptions = {
removeOnComplete: true
};
export const INVESTMENT_ACTIVITY_TYPES = [
Type.BUY,
Type.DIVIDEND,
Type.ITEM,
Type.SELL
] as Type[];
export const PORTFOLIO_SNAPSHOT_PROCESS_JOB_NAME = 'PORTFOLIO';
export const PORTFOLIO_SNAPSHOT_PROCESS_JOB_OPTIONS: JobOptions = {
removeOnComplete: true
@ -128,13 +135,6 @@ export const HEADER_KEY_TIMEZONE = 'Timezone';
export const HEADER_KEY_TOKEN = 'Authorization';
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 NUMERICAL_PRECISION_THRESHOLD = 100000;

Loading…
Cancel
Save