Browse Source

Move liability calculations to portfolio calculator

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

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

@ -141,6 +141,7 @@ export abstract class PortfolioCalculator {
totalInterestWithCurrencyEffect: new Big(0),
totalInvestment: new Big(0),
totalInvestmentWithCurrencyEffect: new Big(0),
totalLiabilitiesWithCurrencyEffect: new Big(0),
totalValuablesWithCurrencyEffect: new Big(0)
};
}
@ -151,6 +152,7 @@ export abstract class PortfolioCalculator {
let firstIndex = transactionPoints.length;
let firstTransactionPoint: TransactionPoint = null;
let totalInterestWithCurrencyEffect = new Big(0);
let totalLiabilitiesWithCurrencyEffect = new Big(0);
let totalValuablesWithCurrencyEffect = new Big(0);
dates.push(resetHours(start));
@ -280,6 +282,7 @@ export abstract class PortfolioCalculator {
totalInterestInBaseCurrency,
totalInvestment,
totalInvestmentWithCurrencyEffect,
totalLiabilitiesInBaseCurrency,
totalValuablesInBaseCurrency
} = this.getSymbolMetrics({
marketSymbolMap,
@ -342,6 +345,9 @@ export abstract class PortfolioCalculator {
totalInterestInBaseCurrency
);
totalLiabilitiesWithCurrencyEffect =
totalLiabilitiesWithCurrencyEffect.plus(totalLiabilitiesInBaseCurrency);
totalValuablesWithCurrencyEffect = totalValuablesWithCurrencyEffect.plus(
totalValuablesInBaseCurrency
);
@ -364,6 +370,7 @@ export abstract class PortfolioCalculator {
errors,
positions,
totalInterestWithCurrencyEffect,
totalLiabilitiesWithCurrencyEffect,
totalValuablesWithCurrencyEffect,
hasErrors: hasAnySymbolMetricsErrors || overall.hasErrors
};
@ -729,6 +736,12 @@ export abstract class PortfolioCalculator {
}));
}
public async getLiabilitiesInBaseCurrency() {
await this.snapshotPromise;
return this.snapshot.totalLiabilitiesWithCurrencyEffect;
}
public async getSnapshot() {
await this.snapshotPromise;
@ -787,13 +800,6 @@ export abstract class PortfolioCalculator {
type,
unitPrice
} of this.orders) {
if (
// TODO
['LIABILITY'].includes(type)
) {
continue;
}
let currentTransactionPointItem: TransactionPointSymbol;
const oldAccumulatedSymbol = symbols[SymbolProfile.symbol];
@ -878,6 +884,12 @@ export abstract class PortfolioCalculator {
interest = quantity.mul(unitPrice);
}
let liabilities = new Big(0);
if (type === 'LIABILITY') {
liabilities = quantity.mul(unitPrice);
}
let valuables = new Big(0);
if (type === 'ITEM') {
@ -889,6 +901,7 @@ export abstract class PortfolioCalculator {
date,
fees,
interest,
liabilities,
valuables,
items: newItems
};
@ -899,6 +912,8 @@ export abstract class PortfolioCalculator {
lastTransactionPoint.interest =
lastTransactionPoint.interest.plus(interest);
lastTransactionPoint.items = newItems;
lastTransactionPoint.liabilities =
lastTransactionPoint.liabilities.plus(liabilities);
lastTransactionPoint.valuables =
lastTransactionPoint.valuables.plus(valuables);
}

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

@ -177,6 +177,7 @@ describe('PortfolioCalculator', () => {
totalInterestWithCurrencyEffect: new Big('0'),
totalInvestment: new Big('0'),
totalInvestmentWithCurrencyEffect: new Big('0'),
totalLiabilitiesWithCurrencyEffect: new Big('0'),
totalValuablesWithCurrencyEffect: new Big('0')
});

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

@ -160,6 +160,7 @@ describe('PortfolioCalculator', () => {
totalInterestWithCurrencyEffect: new Big('0'),
totalInvestment: new Big('0'),
totalInvestmentWithCurrencyEffect: new Big('0'),
totalLiabilitiesWithCurrencyEffect: new Big('0'),
totalValuablesWithCurrencyEffect: new Big('0')
});

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

@ -145,6 +145,7 @@ describe('PortfolioCalculator', () => {
totalInterestWithCurrencyEffect: new Big('0'),
totalInvestment: new Big('273.2'),
totalInvestmentWithCurrencyEffect: new Big('273.2'),
totalLiabilitiesWithCurrencyEffect: new Big('0'),
totalValuablesWithCurrencyEffect: new Big('0')
});

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

@ -179,6 +179,7 @@ describe('PortfolioCalculator', () => {
totalInterestWithCurrencyEffect: new Big('0'),
totalInvestment: new Big('320.43'),
totalInvestmentWithCurrencyEffect: new Big('318.542667299999967957'),
totalLiabilitiesWithCurrencyEffect: new Big('0'),
totalValuablesWithCurrencyEffect: new Big('0')
});

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

@ -126,6 +126,7 @@ describe('PortfolioCalculator', () => {
totalInterestWithCurrencyEffect: new Big('0'),
totalInvestment: new Big('0'),
totalInvestmentWithCurrencyEffect: new Big('0'),
totalLiabilitiesWithCurrencyEffect: new Big('0'),
totalValuablesWithCurrencyEffect: new Big('0')
});
});

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

@ -158,6 +158,7 @@ describe('PortfolioCalculator', () => {
totalInterestWithCurrencyEffect: new Big('0'),
totalInvestment: new Big('89.12'),
totalInvestmentWithCurrencyEffect: new Big('82.329056'),
totalLiabilitiesWithCurrencyEffect: new Big('0'),
totalValuablesWithCurrencyEffect: new Big('0')
});

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

@ -126,6 +126,7 @@ describe('PortfolioCalculator', () => {
totalInterestWithCurrencyEffect: new Big('0'),
totalInvestment: new Big('0'),
totalInvestmentWithCurrencyEffect: new Big('0'),
totalLiabilitiesWithCurrencyEffect: new Big('0'),
totalValuablesWithCurrencyEffect: new Big('0')
});
});

134
apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-liability.spec.ts

@ -0,0 +1,134 @@
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 liability 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: 'Loan',
symbol: '55196015-1365-4560-aa60-8751ae6d18f8'
},
type: 'LIABILITY',
unitPrice: 3000
}
];
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('3000'),
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: 3000,
netPerformance: null,
netPerformancePercentage: null,
netPerformancePercentageWithCurrencyEffect: null,
netPerformanceWithCurrencyEffect: null,
quantity: new Big('0'),
symbol: '55196015-1365-4560-aa60-8751ae6d18f8',
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'),
totalLiabilitiesWithCurrencyEffect: new Big('0'),
totalValuablesWithCurrencyEffect: new Big('0')
});
});
});
});

1
apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-msft-buy-with-dividend.spec.ts

@ -133,6 +133,7 @@ describe('PortfolioCalculator', () => {
totalFeesWithCurrencyEffect: new Big('19'),
totalInterestWithCurrencyEffect: new Big('0'),
totalInvestment: new Big('298.58'),
totalLiabilitiesWithCurrencyEffect: new Big('0'),
totalInvestmentWithCurrencyEffect: new Big('298.58')
});
});

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

@ -84,6 +84,7 @@ describe('PortfolioCalculator', () => {
totalInterestWithCurrencyEffect: new Big('0'),
totalInvestment: new Big(0),
totalInvestmentWithCurrencyEffect: new Big(0),
totalLiabilitiesWithCurrencyEffect: new Big('0'),
totalValuablesWithCurrencyEffect: new Big('0')
});

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

@ -162,6 +162,7 @@ describe('PortfolioCalculator', () => {
totalInterestWithCurrencyEffect: new Big('0'),
totalInvestment: new Big('75.80'),
totalInvestmentWithCurrencyEffect: new Big('75.80'),
totalLiabilitiesWithCurrencyEffect: new Big('0'),
totalValuablesWithCurrencyEffect: new Big('0')
});

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

@ -186,6 +186,7 @@ describe('PortfolioCalculator', () => {
totalInterestWithCurrencyEffect: new Big('0'),
totalInvestment: new Big('0'),
totalInvestmentWithCurrencyEffect: new Big('0'),
totalLiabilitiesWithCurrencyEffect: new Big('0'),
totalValuablesWithCurrencyEffect: new Big('0')
});

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

@ -36,7 +36,6 @@ 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) {
@ -110,11 +109,11 @@ export class TWRPortfolioCalculator extends PortfolioCalculator {
hasErrors,
netPerformance,
netPerformanceWithCurrencyEffect,
positions,
totalFeesWithCurrencyEffect,
totalInterestWithCurrencyEffect,
totalInvestment,
totalInvestmentWithCurrencyEffect,
totalValuablesWithCurrencyEffect,
netPerformancePercentage: totalTimeWeightedInvestment.eq(0)
? new Big(0)
: netPerformance.div(totalTimeWeightedInvestment),
@ -133,7 +132,8 @@ export class TWRPortfolioCalculator extends PortfolioCalculator {
: grossPerformanceWithCurrencyEffect.div(
totalTimeWeightedInvestmentWithCurrencyEffect
),
positions
totalLiabilitiesWithCurrencyEffect: new Big(0),
totalValuablesWithCurrencyEffect: new Big(0)
};
}
@ -196,6 +196,8 @@ export class TWRPortfolioCalculator extends PortfolioCalculator {
let totalInvestmentFromBuyTransactions = new Big(0);
let totalInvestmentFromBuyTransactionsWithCurrencyEffect = new Big(0);
let totalInvestmentWithCurrencyEffect = new Big(0);
let totalLiabilities = new Big(0);
let totalLiabilitiesInBaseCurrency = new Big(0);
let totalQuantityFromBuyTransactions = new Big(0);
let totalUnits = new Big(0);
let totalValuables = new Big(0);
@ -241,6 +243,8 @@ export class TWRPortfolioCalculator extends PortfolioCalculator {
totalInterestInBaseCurrency: new Big(0),
totalInvestment: new Big(0),
totalInvestmentWithCurrencyEffect: new Big(0),
totalLiabilities: new Big(0),
totalLiabilitiesInBaseCurrency: new Big(0),
totalValuables: new Big(0),
totalValuablesInBaseCurrency: new Big(0)
};
@ -288,6 +292,8 @@ export class TWRPortfolioCalculator extends PortfolioCalculator {
totalInterestInBaseCurrency: new Big(0),
totalInvestment: new Big(0),
totalInvestmentWithCurrencyEffect: new Big(0),
totalLiabilities: new Big(0),
totalLiabilitiesInBaseCurrency: new Big(0),
totalValuables: new Big(0),
totalValuablesInBaseCurrency: new Big(0)
};
@ -551,6 +557,13 @@ export class TWRPortfolioCalculator extends PortfolioCalculator {
totalValuablesInBaseCurrency = totalValuablesInBaseCurrency.plus(
valuables.mul(exchangeRateAtOrderDate ?? 1)
);
} else if (order.type === 'LIABILITY') {
const liabilities = order.quantity.mul(order.unitPrice);
totalLiabilities = totalLiabilities.plus(liabilities);
totalLiabilitiesInBaseCurrency = totalLiabilitiesInBaseCurrency.plus(
liabilities.mul(exchangeRateAtOrderDate ?? 1)
);
}
const valueOfInvestment = totalUnits.mul(order.unitPriceInBaseCurrency);
@ -868,6 +881,8 @@ export class TWRPortfolioCalculator extends PortfolioCalculator {
totalInterestInBaseCurrency,
totalInvestment,
totalInvestmentWithCurrencyEffect,
totalLiabilities,
totalLiabilitiesInBaseCurrency,
totalValuables,
totalValuablesInBaseCurrency,
grossPerformance: totalGrossPerformance,

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

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

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

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

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

@ -60,7 +60,6 @@ import {
Prisma
} from '@prisma/client';
import { Big } from 'big.js';
import { isUUID } from 'class-validator';
import {
differenceInDays,
format,
@ -1623,22 +1622,10 @@ export class PortfolioService {
const interest = await portfolioCalculator.getInterestInBaseCurrency();
const valuables = await portfolioCalculator.getValuablesInBaseCurrency();
const liabilities =
await portfolioCalculator.getLiabilitiesInBaseCurrency();
// TODO: Move to portfolio calculator
const liabilities = 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();
const totalBuy = this.getSumOfActivityType({
userCurrency,
@ -1717,7 +1704,6 @@ export class PortfolioService {
cash,
excludedAccountsAndActivities,
firstOrderDate,
liabilities,
totalBuy,
totalSell,
committedFunds: committedFunds.toNumber(),
@ -1739,6 +1725,7 @@ export class PortfolioService {
.toNumber(),
interest: interest.toNumber(),
items: valuables.toNumber(),
liabilities: liabilities.toNumber(),
ordersCount: activities.filter(({ type }) => {
return type === 'BUY' || type === 'SELL';
}).length,

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

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

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

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

Loading…
Cancel
Save