Feature/move wealth item and liability calculations to portfolio calculator (#3272)
* Move (wealth) item calculations to portfolio calculator * Move liability calculations to portfolio calculator * Update changelog
This commit is contained in:
parent
6c57609db8
commit
5d4e2fba8c
@ -12,6 +12,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Moved the dividend calculations into the portfolio calculator
|
||||
- Moved the fee calculations into the portfolio calculator
|
||||
- Moved the interest calculations into the portfolio calculator
|
||||
- Moved the liability calculations into the portfolio calculator
|
||||
- Moved the (wealth) item calculations into the portfolio calculator
|
||||
|
||||
## 2.72.0 - 2024-04-13
|
||||
|
||||
|
@ -140,7 +140,9 @@ export abstract class PortfolioCalculator {
|
||||
totalFeesWithCurrencyEffect: new Big(0),
|
||||
totalInterestWithCurrencyEffect: new Big(0),
|
||||
totalInvestment: new Big(0),
|
||||
totalInvestmentWithCurrencyEffect: new Big(0)
|
||||
totalInvestmentWithCurrencyEffect: new Big(0),
|
||||
totalLiabilitiesWithCurrencyEffect: new Big(0),
|
||||
totalValuablesWithCurrencyEffect: new Big(0)
|
||||
};
|
||||
}
|
||||
|
||||
@ -149,6 +151,9 @@ export abstract class PortfolioCalculator {
|
||||
let dates: Date[] = [];
|
||||
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));
|
||||
|
||||
@ -274,8 +279,11 @@ export abstract class PortfolioCalculator {
|
||||
timeWeightedInvestmentWithCurrencyEffect,
|
||||
totalDividend,
|
||||
totalDividendInBaseCurrency,
|
||||
totalInterestInBaseCurrency,
|
||||
totalInvestment,
|
||||
totalInvestmentWithCurrencyEffect
|
||||
totalInvestmentWithCurrencyEffect,
|
||||
totalLiabilitiesInBaseCurrency,
|
||||
totalValuablesInBaseCurrency
|
||||
} = this.getSymbolMetrics({
|
||||
marketSymbolMap,
|
||||
start,
|
||||
@ -333,6 +341,17 @@ export abstract class PortfolioCalculator {
|
||||
)
|
||||
});
|
||||
|
||||
totalInterestWithCurrencyEffect = totalInterestWithCurrencyEffect.plus(
|
||||
totalInterestInBaseCurrency
|
||||
);
|
||||
|
||||
totalLiabilitiesWithCurrencyEffect =
|
||||
totalLiabilitiesWithCurrencyEffect.plus(totalLiabilitiesInBaseCurrency);
|
||||
|
||||
totalValuablesWithCurrencyEffect = totalValuablesWithCurrencyEffect.plus(
|
||||
totalValuablesInBaseCurrency
|
||||
);
|
||||
|
||||
if (
|
||||
(hasErrors ||
|
||||
currentRateErrors.find(({ dataSource, symbol }) => {
|
||||
@ -350,8 +369,10 @@ export abstract class PortfolioCalculator {
|
||||
...overall,
|
||||
errors,
|
||||
positions,
|
||||
hasErrors: hasAnySymbolMetricsErrors || overall.hasErrors,
|
||||
totalInterestWithCurrencyEffect: lastTransactionPoint.interest
|
||||
totalInterestWithCurrencyEffect,
|
||||
totalLiabilitiesWithCurrencyEffect,
|
||||
totalValuablesWithCurrencyEffect,
|
||||
hasErrors: hasAnySymbolMetricsErrors || overall.hasErrors
|
||||
};
|
||||
}
|
||||
|
||||
@ -715,6 +736,12 @@ export abstract class PortfolioCalculator {
|
||||
}));
|
||||
}
|
||||
|
||||
public async getLiabilitiesInBaseCurrency() {
|
||||
await this.snapshotPromise;
|
||||
|
||||
return this.snapshot.totalLiabilitiesWithCurrencyEffect;
|
||||
}
|
||||
|
||||
public async getSnapshot() {
|
||||
await this.snapshotPromise;
|
||||
|
||||
@ -751,6 +778,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 } = {};
|
||||
@ -767,13 +800,6 @@ export abstract class PortfolioCalculator {
|
||||
type,
|
||||
unitPrice
|
||||
} of this.orders) {
|
||||
if (
|
||||
// TODO
|
||||
['ITEM', 'LIABILITY'].includes(type)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let currentTransactionPointItem: TransactionPointSymbol;
|
||||
const oldAccumulatedSymbol = symbols[SymbolProfile.symbol];
|
||||
|
||||
@ -858,11 +884,25 @@ 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') {
|
||||
valuables = quantity.mul(unitPrice);
|
||||
}
|
||||
|
||||
if (lastDate !== date || lastTransactionPoint === null) {
|
||||
lastTransactionPoint = {
|
||||
date,
|
||||
fees,
|
||||
interest,
|
||||
liabilities,
|
||||
valuables,
|
||||
items: newItems
|
||||
};
|
||||
|
||||
@ -872,6 +912,10 @@ 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);
|
||||
}
|
||||
|
||||
lastDate = date;
|
||||
|
@ -176,7 +176,9 @@ describe('PortfolioCalculator', () => {
|
||||
totalFeesWithCurrencyEffect: new Big('3.2'),
|
||||
totalInterestWithCurrencyEffect: new Big('0'),
|
||||
totalInvestment: new Big('0'),
|
||||
totalInvestmentWithCurrencyEffect: new Big('0')
|
||||
totalInvestmentWithCurrencyEffect: new Big('0'),
|
||||
totalLiabilitiesWithCurrencyEffect: new Big('0'),
|
||||
totalValuablesWithCurrencyEffect: new Big('0')
|
||||
});
|
||||
|
||||
expect(investments).toEqual([
|
||||
|
@ -159,7 +159,9 @@ describe('PortfolioCalculator', () => {
|
||||
totalFeesWithCurrencyEffect: new Big('3.2'),
|
||||
totalInterestWithCurrencyEffect: new Big('0'),
|
||||
totalInvestment: new Big('0'),
|
||||
totalInvestmentWithCurrencyEffect: new Big('0')
|
||||
totalInvestmentWithCurrencyEffect: new Big('0'),
|
||||
totalLiabilitiesWithCurrencyEffect: new Big('0'),
|
||||
totalValuablesWithCurrencyEffect: new Big('0')
|
||||
});
|
||||
|
||||
expect(investments).toEqual([
|
||||
|
@ -144,7 +144,9 @@ 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'),
|
||||
totalLiabilitiesWithCurrencyEffect: new Big('0'),
|
||||
totalValuablesWithCurrencyEffect: new Big('0')
|
||||
});
|
||||
|
||||
expect(investments).toEqual([
|
||||
|
@ -178,7 +178,9 @@ 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'),
|
||||
totalLiabilitiesWithCurrencyEffect: new Big('0'),
|
||||
totalValuablesWithCurrencyEffect: new Big('0')
|
||||
});
|
||||
|
||||
expect(investments).toEqual([
|
||||
|
@ -125,7 +125,9 @@ describe('PortfolioCalculator', () => {
|
||||
totalFeesWithCurrencyEffect: new Big('49'),
|
||||
totalInterestWithCurrencyEffect: new Big('0'),
|
||||
totalInvestment: new Big('0'),
|
||||
totalInvestmentWithCurrencyEffect: new Big('0')
|
||||
totalInvestmentWithCurrencyEffect: new Big('0'),
|
||||
totalLiabilitiesWithCurrencyEffect: new Big('0'),
|
||||
totalValuablesWithCurrencyEffect: new Big('0')
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -157,7 +157,9 @@ 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'),
|
||||
totalLiabilitiesWithCurrencyEffect: new Big('0'),
|
||||
totalValuablesWithCurrencyEffect: new Big('0')
|
||||
});
|
||||
|
||||
expect(investments).toEqual([
|
||||
|
@ -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 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'),
|
||||
totalLiabilitiesWithCurrencyEffect: new Big('0'),
|
||||
totalValuablesWithCurrencyEffect: new Big('0')
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -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')
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -133,7 +133,9 @@ describe('PortfolioCalculator', () => {
|
||||
totalFeesWithCurrencyEffect: new Big('19'),
|
||||
totalInterestWithCurrencyEffect: new Big('0'),
|
||||
totalInvestment: new Big('298.58'),
|
||||
totalInvestmentWithCurrencyEffect: new Big('298.58')
|
||||
totalInvestmentWithCurrencyEffect: new Big('298.58'),
|
||||
totalLiabilitiesWithCurrencyEffect: new Big('0'),
|
||||
totalValuablesWithCurrencyEffect: new Big('0')
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -83,7 +83,9 @@ describe('PortfolioCalculator', () => {
|
||||
totalFeesWithCurrencyEffect: new Big('0'),
|
||||
totalInterestWithCurrencyEffect: new Big('0'),
|
||||
totalInvestment: new Big(0),
|
||||
totalInvestmentWithCurrencyEffect: new Big(0)
|
||||
totalInvestmentWithCurrencyEffect: new Big(0),
|
||||
totalLiabilitiesWithCurrencyEffect: new Big('0'),
|
||||
totalValuablesWithCurrencyEffect: new Big('0')
|
||||
});
|
||||
|
||||
expect(investments).toEqual([]);
|
||||
|
@ -161,7 +161,9 @@ 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'),
|
||||
totalLiabilitiesWithCurrencyEffect: new Big('0'),
|
||||
totalValuablesWithCurrencyEffect: new Big('0')
|
||||
});
|
||||
|
||||
expect(investments).toEqual([
|
||||
|
@ -185,7 +185,9 @@ describe('PortfolioCalculator', () => {
|
||||
totalFeesWithCurrencyEffect: new Big('0'),
|
||||
totalInterestWithCurrencyEffect: new Big('0'),
|
||||
totalInvestment: new Big('0'),
|
||||
totalInvestmentWithCurrencyEffect: new Big('0')
|
||||
totalInvestmentWithCurrencyEffect: new Big('0'),
|
||||
totalLiabilitiesWithCurrencyEffect: new Big('0'),
|
||||
totalValuablesWithCurrencyEffect: new Big('0')
|
||||
});
|
||||
|
||||
expect(investments).toEqual([
|
||||
|
@ -109,6 +109,7 @@ export class TWRPortfolioCalculator extends PortfolioCalculator {
|
||||
hasErrors,
|
||||
netPerformance,
|
||||
netPerformanceWithCurrencyEffect,
|
||||
positions,
|
||||
totalFeesWithCurrencyEffect,
|
||||
totalInterestWithCurrencyEffect,
|
||||
totalInvestment,
|
||||
@ -131,7 +132,8 @@ export class TWRPortfolioCalculator extends PortfolioCalculator {
|
||||
: grossPerformanceWithCurrencyEffect.div(
|
||||
totalTimeWeightedInvestmentWithCurrencyEffect
|
||||
),
|
||||
positions
|
||||
totalLiabilitiesWithCurrencyEffect: new Big(0),
|
||||
totalValuablesWithCurrencyEffect: new Big(0)
|
||||
};
|
||||
}
|
||||
|
||||
@ -194,8 +196,12 @@ 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);
|
||||
let totalValuablesInBaseCurrency = new Big(0);
|
||||
let valueAtStartDate: Big;
|
||||
let valueAtStartDateWithCurrencyEffect: Big;
|
||||
|
||||
@ -236,7 +242,11 @@ 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),
|
||||
totalLiabilities: new Big(0),
|
||||
totalLiabilitiesInBaseCurrency: new Big(0),
|
||||
totalValuables: new Big(0),
|
||||
totalValuablesInBaseCurrency: new Big(0)
|
||||
};
|
||||
}
|
||||
|
||||
@ -281,7 +291,11 @@ 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),
|
||||
totalLiabilities: new Big(0),
|
||||
totalLiabilitiesInBaseCurrency: new Big(0),
|
||||
totalValuables: new Big(0),
|
||||
totalValuablesInBaseCurrency: new Big(0)
|
||||
};
|
||||
}
|
||||
|
||||
@ -536,6 +550,20 @@ 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)
|
||||
);
|
||||
} 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);
|
||||
@ -853,6 +881,10 @@ export class TWRPortfolioCalculator extends PortfolioCalculator {
|
||||
totalInterestInBaseCurrency,
|
||||
totalInvestment,
|
||||
totalInvestmentWithCurrencyEffect,
|
||||
totalLiabilities,
|
||||
totalLiabilitiesInBaseCurrency,
|
||||
totalValuables,
|
||||
totalValuablesInBaseCurrency,
|
||||
grossPerformance: totalGrossPerformance,
|
||||
grossPerformanceWithCurrencyEffect:
|
||||
totalGrossPerformanceWithCurrencyEffect,
|
||||
|
@ -19,4 +19,6 @@ export interface PortfolioSnapshot extends ResponseError {
|
||||
totalInterestWithCurrencyEffect: Big;
|
||||
totalInvestment: Big;
|
||||
totalInvestmentWithCurrencyEffect: Big;
|
||||
totalLiabilitiesWithCurrencyEffect: Big;
|
||||
totalValuablesWithCurrencyEffect: Big;
|
||||
}
|
||||
|
@ -7,4 +7,6 @@ export interface TransactionPoint {
|
||||
fees: Big;
|
||||
interest: Big;
|
||||
items: TransactionPointSymbol[];
|
||||
liabilities: Big;
|
||||
valuables: Big;
|
||||
}
|
||||
|
@ -78,10 +78,8 @@ export class PortfolioController {
|
||||
@Query('assetClasses') filterByAssetClasses?: string,
|
||||
@Query('range') dateRange: DateRange = 'max',
|
||||
@Query('tags') filterByTags?: string,
|
||||
@Query('withLiabilities') withLiabilitiesParam = 'false',
|
||||
@Query('withMarkets') withMarketsParam = 'false'
|
||||
): Promise<PortfolioDetails & { hasError: boolean }> {
|
||||
const withLiabilities = withLiabilitiesParam === 'true';
|
||||
const withMarkets = withMarketsParam === 'true';
|
||||
|
||||
let hasDetails = true;
|
||||
@ -107,8 +105,6 @@ export class PortfolioController {
|
||||
dateRange,
|
||||
filters,
|
||||
impersonationId,
|
||||
// TODO
|
||||
// withLiabilities,
|
||||
withMarkets,
|
||||
userId: this.request.user.id,
|
||||
withSummary: true
|
||||
|
@ -60,7 +60,6 @@ import {
|
||||
Prisma
|
||||
} from '@prisma/client';
|
||||
import { Big } from 'big.js';
|
||||
import { isUUID } from 'class-validator';
|
||||
import {
|
||||
differenceInDays,
|
||||
format,
|
||||
@ -324,7 +323,6 @@ export class PortfolioService {
|
||||
impersonationId,
|
||||
userId,
|
||||
withExcludedAccounts = false,
|
||||
withLiabilities = false,
|
||||
withMarkets = false,
|
||||
withSummary = false
|
||||
}: {
|
||||
@ -333,7 +331,6 @@ export class PortfolioService {
|
||||
impersonationId: string;
|
||||
userId: string;
|
||||
withExcludedAccounts?: boolean;
|
||||
withLiabilities?: boolean;
|
||||
withMarkets?: boolean;
|
||||
withSummary?: boolean;
|
||||
}): Promise<PortfolioDetails & { hasErrors: boolean }> {
|
||||
@ -1623,35 +1620,10 @@ 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 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,
|
||||
@ -1701,7 +1673,7 @@ export class PortfolioService {
|
||||
|
||||
const netWorth = new Big(balanceInBaseCurrency)
|
||||
.plus(performanceInformation.performance.currentValue)
|
||||
.plus(items)
|
||||
.plus(valuables)
|
||||
.plus(excludedAccountsAndActivities)
|
||||
.minus(liabilities)
|
||||
.toNumber();
|
||||
@ -1730,8 +1702,6 @@ export class PortfolioService {
|
||||
cash,
|
||||
excludedAccountsAndActivities,
|
||||
firstOrderDate,
|
||||
items,
|
||||
liabilities,
|
||||
totalBuy,
|
||||
totalSell,
|
||||
committedFunds: committedFunds.toNumber(),
|
||||
@ -1752,6 +1722,8 @@ export class PortfolioService {
|
||||
.minus(emergencyFundPositionsValueInBaseCurrency)
|
||||
.toNumber(),
|
||||
interest: interest.toNumber(),
|
||||
items: valuables.toNumber(),
|
||||
liabilities: liabilities.toNumber(),
|
||||
ordersCount: activities.filter(({ type }) => {
|
||||
return type === 'BUY' || type === 'SELL';
|
||||
}).length,
|
||||
|
@ -18,10 +18,8 @@ export function getFactor(activityType: ActivityType) {
|
||||
|
||||
switch (activityType) {
|
||||
case 'BUY':
|
||||
case 'ITEM':
|
||||
factor = 1;
|
||||
break;
|
||||
case 'LIABILITY':
|
||||
case 'SELL':
|
||||
factor = -1;
|
||||
break;
|
||||
|
@ -102,7 +102,7 @@ export class HomeSummaryComponent implements OnDestroy, OnInit {
|
||||
this.isLoading = true;
|
||||
|
||||
this.dataService
|
||||
.fetchPortfolioDetails({ withLiabilities: true })
|
||||
.fetchPortfolioDetails()
|
||||
.pipe(takeUntil(this.unsubscribeSubject))
|
||||
.subscribe(({ summary }) => {
|
||||
this.summary = summary;
|
||||
|
@ -411,19 +411,13 @@ export class DataService {
|
||||
|
||||
public fetchPortfolioDetails({
|
||||
filters,
|
||||
withLiabilities = false,
|
||||
withMarkets = false
|
||||
}: {
|
||||
filters?: Filter[];
|
||||
withLiabilities?: boolean;
|
||||
withMarkets?: boolean;
|
||||
} = {}): Observable<PortfolioDetails> {
|
||||
let params = this.buildFiltersAsQueryParams({ filters });
|
||||
|
||||
if (withLiabilities) {
|
||||
params = params.append('withLiabilities', withLiabilities);
|
||||
}
|
||||
|
||||
if (withMarkets) {
|
||||
params = params.append('withMarkets', withMarkets);
|
||||
}
|
||||
|
@ -46,4 +46,8 @@ export interface SymbolMetrics {
|
||||
totalInterestInBaseCurrency: Big;
|
||||
totalInvestment: Big;
|
||||
totalInvestmentWithCurrencyEffect: Big;
|
||||
totalLiabilities: Big;
|
||||
totalLiabilitiesInBaseCurrency: Big;
|
||||
totalValuables: Big;
|
||||
totalValuablesInBaseCurrency: Big;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user