Browse Source

Move (wealth) item calculations to portfolio calculator

pull/3272/head
Thomas Kaul 1 year ago
parent
commit
351ddd2879
  1. 39
      apps/api/src/app/portfolio/calculator/portfolio-calculator.ts
  2. 3
      apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-baln-buy-and-sell-in-two-activities.spec.ts
  3. 3
      apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-baln-buy-and-sell.spec.ts
  4. 3
      apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-baln-buy.spec.ts
  5. 3
      apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts
  6. 3
      apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-fee.spec.ts
  7. 3
      apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-googl-buy.spec.ts
  8. 133
      apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-item.spec.ts
  9. 3
      apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-no-orders.spec.ts
  10. 3
      apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-novn-buy-and-sell-partially.spec.ts
  11. 3
      apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-novn-buy-and-sell.spec.ts
  12. 21
      apps/api/src/app/portfolio/calculator/twr/portfolio-calculator.ts
  13. 1
      apps/api/src/app/portfolio/interfaces/portfolio-snapshot.interface.ts
  14. 1
      apps/api/src/app/portfolio/interfaces/transaction-point.interface.ts
  15. 19
      apps/api/src/app/portfolio/portfolio.service.ts
  16. 1
      apps/api/src/helper/portfolio.helper.ts
  17. 2
      libs/common/src/lib/interfaces/symbol-metrics.interface.ts

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

@ -140,7 +140,8 @@ export abstract class PortfolioCalculator {
totalFeesWithCurrencyEffect: new Big(0),
totalInterestWithCurrencyEffect: new Big(0),
totalInvestment: new Big(0),
totalInvestmentWithCurrencyEffect: new Big(0)
totalInvestmentWithCurrencyEffect: new Big(0),
totalValuablesWithCurrencyEffect: new Big(0)
};
}
@ -149,6 +150,8 @@ export abstract class PortfolioCalculator {
let dates: Date[] = [];
let firstIndex = transactionPoints.length;
let firstTransactionPoint: TransactionPoint = null;
let totalInterestWithCurrencyEffect = new Big(0);
let totalValuablesWithCurrencyEffect = new Big(0);
dates.push(resetHours(start));
@ -274,8 +277,10 @@ export abstract class PortfolioCalculator {
timeWeightedInvestmentWithCurrencyEffect,
totalDividend,
totalDividendInBaseCurrency,
totalInterestInBaseCurrency,
totalInvestment,
totalInvestmentWithCurrencyEffect
totalInvestmentWithCurrencyEffect,
totalValuablesInBaseCurrency
} = this.getSymbolMetrics({
marketSymbolMap,
start,
@ -333,6 +338,14 @@ export abstract class PortfolioCalculator {
)
});
totalInterestWithCurrencyEffect = totalInterestWithCurrencyEffect.plus(
totalInterestInBaseCurrency
);
totalValuablesWithCurrencyEffect = totalValuablesWithCurrencyEffect.plus(
totalValuablesInBaseCurrency
);
if (
(hasErrors ||
currentRateErrors.find(({ dataSource, symbol }) => {
@ -350,8 +363,9 @@ export abstract class PortfolioCalculator {
...overall,
errors,
positions,
hasErrors: hasAnySymbolMetricsErrors || overall.hasErrors,
totalInterestWithCurrencyEffect: lastTransactionPoint.interest
totalInterestWithCurrencyEffect,
totalValuablesWithCurrencyEffect,
hasErrors: hasAnySymbolMetricsErrors || overall.hasErrors
};
}
@ -751,6 +765,12 @@ export abstract class PortfolioCalculator {
return this.transactionPoints;
}
public async getValuablesInBaseCurrency() {
await this.snapshotPromise;
return this.snapshot.totalValuablesWithCurrencyEffect;
}
private computeTransactionPoints() {
this.transactionPoints = [];
const symbols: { [symbol: string]: TransactionPointSymbol } = {};
@ -769,7 +789,7 @@ export abstract class PortfolioCalculator {
} of this.orders) {
if (
// TODO
['ITEM', 'LIABILITY'].includes(type)
['LIABILITY'].includes(type)
) {
continue;
}
@ -858,11 +878,18 @@ export abstract class PortfolioCalculator {
interest = quantity.mul(unitPrice);
}
let valuables = new Big(0);
if (type === 'ITEM') {
valuables = quantity.mul(unitPrice);
}
if (lastDate !== date || lastTransactionPoint === null) {
lastTransactionPoint = {
date,
fees,
interest,
valuables,
items: newItems
};
@ -872,6 +899,8 @@ export abstract class PortfolioCalculator {
lastTransactionPoint.interest =
lastTransactionPoint.interest.plus(interest);
lastTransactionPoint.items = newItems;
lastTransactionPoint.valuables =
lastTransactionPoint.valuables.plus(valuables);
}
lastDate = date;

3
apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-baln-buy-and-sell-in-two-activities.spec.ts

@ -176,7 +176,8 @@ describe('PortfolioCalculator', () => {
totalFeesWithCurrencyEffect: new Big('3.2'),
totalInterestWithCurrencyEffect: new Big('0'),
totalInvestment: new Big('0'),
totalInvestmentWithCurrencyEffect: new Big('0')
totalInvestmentWithCurrencyEffect: new Big('0'),
totalValuablesWithCurrencyEffect: new Big('0')
});
expect(investments).toEqual([

3
apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-baln-buy-and-sell.spec.ts

@ -159,7 +159,8 @@ describe('PortfolioCalculator', () => {
totalFeesWithCurrencyEffect: new Big('3.2'),
totalInterestWithCurrencyEffect: new Big('0'),
totalInvestment: new Big('0'),
totalInvestmentWithCurrencyEffect: new Big('0')
totalInvestmentWithCurrencyEffect: new Big('0'),
totalValuablesWithCurrencyEffect: new Big('0')
});
expect(investments).toEqual([

3
apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-baln-buy.spec.ts

@ -144,7 +144,8 @@ describe('PortfolioCalculator', () => {
totalFeesWithCurrencyEffect: new Big('1.55'),
totalInterestWithCurrencyEffect: new Big('0'),
totalInvestment: new Big('273.2'),
totalInvestmentWithCurrencyEffect: new Big('273.2')
totalInvestmentWithCurrencyEffect: new Big('273.2'),
totalValuablesWithCurrencyEffect: new Big('0')
});
expect(investments).toEqual([

3
apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts

@ -178,7 +178,8 @@ describe('PortfolioCalculator', () => {
totalFeesWithCurrencyEffect: new Big('0'),
totalInterestWithCurrencyEffect: new Big('0'),
totalInvestment: new Big('320.43'),
totalInvestmentWithCurrencyEffect: new Big('318.542667299999967957')
totalInvestmentWithCurrencyEffect: new Big('318.542667299999967957'),
totalValuablesWithCurrencyEffect: new Big('0')
});
expect(investments).toEqual([

3
apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-fee.spec.ts

@ -125,7 +125,8 @@ describe('PortfolioCalculator', () => {
totalFeesWithCurrencyEffect: new Big('49'),
totalInterestWithCurrencyEffect: new Big('0'),
totalInvestment: new Big('0'),
totalInvestmentWithCurrencyEffect: new Big('0')
totalInvestmentWithCurrencyEffect: new Big('0'),
totalValuablesWithCurrencyEffect: new Big('0')
});
});
});

3
apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-googl-buy.spec.ts

@ -157,7 +157,8 @@ describe('PortfolioCalculator', () => {
totalFeesWithCurrencyEffect: new Big('1'),
totalInterestWithCurrencyEffect: new Big('0'),
totalInvestment: new Big('89.12'),
totalInvestmentWithCurrencyEffect: new Big('82.329056')
totalInvestmentWithCurrencyEffect: new Big('82.329056'),
totalValuablesWithCurrencyEffect: new Big('0')
});
expect(investments).toEqual([

133
apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-item.spec.ts

@ -0,0 +1,133 @@
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
import {
activityDummyData,
symbolProfileDummyData
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils';
import {
PortfolioCalculatorFactory,
PerformanceCalculationType
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { parseDate } from '@ghostfolio/common/helper';
import { Big } from 'big.js';
jest.mock('@ghostfolio/api/app/portfolio/current-rate.service', () => {
return {
// eslint-disable-next-line @typescript-eslint/naming-convention
CurrentRateService: jest.fn().mockImplementation(() => {
return CurrentRateServiceMock;
})
};
});
describe('PortfolioCalculator', () => {
let currentRateService: CurrentRateService;
let exchangeRateDataService: ExchangeRateDataService;
let factory: PortfolioCalculatorFactory;
beforeEach(() => {
currentRateService = new CurrentRateService(null, null, null, null);
exchangeRateDataService = new ExchangeRateDataService(
null,
null,
null,
null
);
factory = new PortfolioCalculatorFactory(
currentRateService,
exchangeRateDataService
);
});
describe('compute portfolio snapshot', () => {
it.only('with item activity', async () => {
const spy = jest
.spyOn(Date, 'now')
.mockImplementation(() => parseDate('2022-01-31').getTime());
const activities: Activity[] = [
{
...activityDummyData,
date: new Date('2022-01-01'),
fee: 0,
quantity: 1,
SymbolProfile: {
...symbolProfileDummyData,
currency: 'USD',
dataSource: 'MANUAL',
name: 'Penthouse Apartment',
symbol: 'dac95060-d4f2-4653-a253-2c45e6fb5cde'
},
type: 'ITEM',
unitPrice: 500000
}
];
const portfolioCalculator = factory.createCalculator({
activities,
calculationType: PerformanceCalculationType.TWR,
currency: 'USD'
});
const portfolioSnapshot = await portfolioCalculator.computeSnapshot(
parseDate('2022-01-01')
);
spy.mockRestore();
expect(portfolioSnapshot).toEqual({
currentValueInBaseCurrency: new Big('0'),
errors: [],
grossPerformance: new Big('0'),
grossPerformancePercentage: new Big('0'),
grossPerformancePercentageWithCurrencyEffect: new Big('0'),
grossPerformanceWithCurrencyEffect: new Big('0'),
hasErrors: true,
netPerformance: new Big('0'),
netPerformancePercentage: new Big('0'),
netPerformancePercentageWithCurrencyEffect: new Big('0'),
netPerformanceWithCurrencyEffect: new Big('0'),
positions: [
{
averagePrice: new Big('500000'),
currency: 'USD',
dataSource: 'MANUAL',
dividend: new Big('0'),
dividendInBaseCurrency: new Big('0'),
fee: new Big('0'),
firstBuyDate: '2022-01-01',
grossPerformance: null,
grossPerformancePercentage: null,
grossPerformancePercentageWithCurrencyEffect: null,
grossPerformanceWithCurrencyEffect: null,
investment: new Big('0'),
investmentWithCurrencyEffect: new Big('0'),
marketPrice: null,
marketPriceInBaseCurrency: 500000,
netPerformance: null,
netPerformancePercentage: null,
netPerformancePercentageWithCurrencyEffect: null,
netPerformanceWithCurrencyEffect: null,
quantity: new Big('0'),
symbol: 'dac95060-d4f2-4653-a253-2c45e6fb5cde',
tags: [],
timeWeightedInvestment: new Big('0'),
timeWeightedInvestmentWithCurrencyEffect: new Big('0'),
transactionCount: 1,
valueInBaseCurrency: new Big('0')
}
],
totalFeesWithCurrencyEffect: new Big('0'),
totalInterestWithCurrencyEffect: new Big('0'),
totalInvestment: new Big('0'),
totalInvestmentWithCurrencyEffect: new Big('0'),
totalValuablesWithCurrencyEffect: new Big('0')
});
});
});
});

3
apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-no-orders.spec.ts

@ -83,7 +83,8 @@ describe('PortfolioCalculator', () => {
totalFeesWithCurrencyEffect: new Big('0'),
totalInterestWithCurrencyEffect: new Big('0'),
totalInvestment: new Big(0),
totalInvestmentWithCurrencyEffect: new Big(0)
totalInvestmentWithCurrencyEffect: new Big(0),
totalValuablesWithCurrencyEffect: new Big('0')
});
expect(investments).toEqual([]);

3
apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-novn-buy-and-sell-partially.spec.ts

@ -161,7 +161,8 @@ describe('PortfolioCalculator', () => {
totalFeesWithCurrencyEffect: new Big('4.25'),
totalInterestWithCurrencyEffect: new Big('0'),
totalInvestment: new Big('75.80'),
totalInvestmentWithCurrencyEffect: new Big('75.80')
totalInvestmentWithCurrencyEffect: new Big('75.80'),
totalValuablesWithCurrencyEffect: new Big('0')
});
expect(investments).toEqual([

3
apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-novn-buy-and-sell.spec.ts

@ -185,7 +185,8 @@ describe('PortfolioCalculator', () => {
totalFeesWithCurrencyEffect: new Big('0'),
totalInterestWithCurrencyEffect: new Big('0'),
totalInvestment: new Big('0'),
totalInvestmentWithCurrencyEffect: new Big('0')
totalInvestmentWithCurrencyEffect: new Big('0'),
totalValuablesWithCurrencyEffect: new Big('0')
});
expect(investments).toEqual([

21
apps/api/src/app/portfolio/calculator/twr/portfolio-calculator.ts

@ -36,6 +36,7 @@ export class TWRPortfolioCalculator extends PortfolioCalculator {
let totalInvestmentWithCurrencyEffect = new Big(0);
let totalTimeWeightedInvestment = new Big(0);
let totalTimeWeightedInvestmentWithCurrencyEffect = new Big(0);
let totalValuablesWithCurrencyEffect = new Big(0);
for (const currentPosition of positions) {
if (currentPosition.fee) {
@ -113,6 +114,7 @@ export class TWRPortfolioCalculator extends PortfolioCalculator {
totalInterestWithCurrencyEffect,
totalInvestment,
totalInvestmentWithCurrencyEffect,
totalValuablesWithCurrencyEffect,
netPerformancePercentage: totalTimeWeightedInvestment.eq(0)
? new Big(0)
: netPerformance.div(totalTimeWeightedInvestment),
@ -196,6 +198,8 @@ export class TWRPortfolioCalculator extends PortfolioCalculator {
let totalInvestmentWithCurrencyEffect = new Big(0);
let totalQuantityFromBuyTransactions = new Big(0);
let totalUnits = new Big(0);
let totalValuables = new Big(0);
let totalValuablesInBaseCurrency = new Big(0);
let valueAtStartDate: Big;
let valueAtStartDateWithCurrencyEffect: Big;
@ -236,7 +240,9 @@ export class TWRPortfolioCalculator extends PortfolioCalculator {
totalInterest: new Big(0),
totalInterestInBaseCurrency: new Big(0),
totalInvestment: new Big(0),
totalInvestmentWithCurrencyEffect: new Big(0)
totalInvestmentWithCurrencyEffect: new Big(0),
totalValuables: new Big(0),
totalValuablesInBaseCurrency: new Big(0)
};
}
@ -281,7 +287,9 @@ export class TWRPortfolioCalculator extends PortfolioCalculator {
totalInterest: new Big(0),
totalInterestInBaseCurrency: new Big(0),
totalInvestment: new Big(0),
totalInvestmentWithCurrencyEffect: new Big(0)
totalInvestmentWithCurrencyEffect: new Big(0),
totalValuables: new Big(0),
totalValuablesInBaseCurrency: new Big(0)
};
}
@ -536,6 +544,13 @@ export class TWRPortfolioCalculator extends PortfolioCalculator {
totalInterestInBaseCurrency = totalInterestInBaseCurrency.plus(
interest.mul(exchangeRateAtOrderDate ?? 1)
);
} else if (order.type === 'ITEM') {
const valuables = order.quantity.mul(order.unitPrice);
totalValuables = totalValuables.plus(valuables);
totalValuablesInBaseCurrency = totalValuablesInBaseCurrency.plus(
valuables.mul(exchangeRateAtOrderDate ?? 1)
);
}
const valueOfInvestment = totalUnits.mul(order.unitPriceInBaseCurrency);
@ -853,6 +868,8 @@ export class TWRPortfolioCalculator extends PortfolioCalculator {
totalInterestInBaseCurrency,
totalInvestment,
totalInvestmentWithCurrencyEffect,
totalValuables,
totalValuablesInBaseCurrency,
grossPerformance: totalGrossPerformance,
grossPerformanceWithCurrencyEffect:
totalGrossPerformanceWithCurrencyEffect,

1
apps/api/src/app/portfolio/interfaces/portfolio-snapshot.interface.ts

@ -19,4 +19,5 @@ export interface PortfolioSnapshot extends ResponseError {
totalInterestWithCurrencyEffect: Big;
totalInvestment: Big;
totalInvestmentWithCurrencyEffect: Big;
totalValuablesWithCurrencyEffect: Big;
}

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

@ -7,4 +7,5 @@ export interface TransactionPoint {
fees: Big;
interest: Big;
items: TransactionPointSymbol[];
valuables: Big;
}

19
apps/api/src/app/portfolio/portfolio.service.ts

@ -1623,20 +1623,7 @@ export class PortfolioService {
const interest = await portfolioCalculator.getInterestInBaseCurrency();
// TODO: Move to portfolio calculator
const items = getSum(
Object.keys(holdings)
.filter((symbol) => {
return (
isUUID(symbol) &&
holdings[symbol].dataSource === 'MANUAL' &&
holdings[symbol].valueInBaseCurrency > 0
);
})
.map((symbol) => {
return new Big(holdings[symbol].valueInBaseCurrency).abs();
})
).toNumber();
const valuables = await portfolioCalculator.getValuablesInBaseCurrency();
// TODO: Move to portfolio calculator
const liabilities = getSum(
@ -1701,7 +1688,7 @@ export class PortfolioService {
const netWorth = new Big(balanceInBaseCurrency)
.plus(performanceInformation.performance.currentValue)
.plus(items)
.plus(valuables)
.plus(excludedAccountsAndActivities)
.minus(liabilities)
.toNumber();
@ -1730,7 +1717,6 @@ export class PortfolioService {
cash,
excludedAccountsAndActivities,
firstOrderDate,
items,
liabilities,
totalBuy,
totalSell,
@ -1752,6 +1738,7 @@ export class PortfolioService {
.minus(emergencyFundPositionsValueInBaseCurrency)
.toNumber(),
interest: interest.toNumber(),
items: valuables.toNumber(),
ordersCount: activities.filter(({ type }) => {
return type === 'BUY' || type === 'SELL';
}).length,

1
apps/api/src/helper/portfolio.helper.ts

@ -18,7 +18,6 @@ export function getFactor(activityType: ActivityType) {
switch (activityType) {
case 'BUY':
case 'ITEM':
factor = 1;
break;
case 'LIABILITY':

2
libs/common/src/lib/interfaces/symbol-metrics.interface.ts

@ -46,4 +46,6 @@ export interface SymbolMetrics {
totalInterestInBaseCurrency: Big;
totalInvestment: Big;
totalInvestmentWithCurrencyEffect: Big;
totalValuables: Big;
totalValuablesInBaseCurrency: Big;
}

Loading…
Cancel
Save