Refactor portfolio calculator (#3203)

* Refactor portfolio calculator

* Consume Activity[]
* Change computeTransactionPoints() to private
* Eliminate getTransactionPoints()

* Update changelog
pull/3205/head
Thomas Kaul 9 months ago committed by GitHub
parent 5529fdc0ee
commit 5788c6474e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -12,6 +12,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Extended the export functionality by the user accounts currency - Extended the export functionality by the user accounts currency
- Added support to override the name of an asset profile in the asset profile details dialog of the admin control - Added support to override the name of an asset profile in the asset profile details dialog of the admin control
### Changed
- Optimized the portfolio calculations
### Fixed ### Fixed
- Fixed the chart tooltip of the benchmark comparator - Fixed the chart tooltip of the benchmark comparator

@ -1,15 +1,12 @@
import { DataSource, Tag, Type as ActivityType } from '@prisma/client'; import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
import { Big } from 'big.js';
export interface PortfolioOrder { export interface PortfolioOrder extends Pick<Activity, 'tags' | 'type'> {
currency: string;
date: string; date: string;
dataSource: DataSource;
fee: Big; fee: Big;
name: string;
quantity: Big; quantity: Big;
symbol: string; SymbolProfile: Pick<
tags?: Tag[]; Activity['SymbolProfile'],
type: ActivityType; 'currency' | 'dataSource' | 'name' | 'symbol'
>;
unitPrice: Big; unitPrice: Big;
} }

@ -1,3 +1,4 @@
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service'; import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { parseDate } from '@ghostfolio/common/helper'; import { parseDate } from '@ghostfolio/common/helper';
@ -36,46 +37,50 @@ describe('PortfolioCalculator', () => {
const portfolioCalculator = new PortfolioCalculator({ const portfolioCalculator = new PortfolioCalculator({
currentRateService, currentRateService,
exchangeRateDataService, exchangeRateDataService,
currency: 'CHF', activities: <Activity[]>[
orders: [
{ {
currency: 'CHF', date: new Date('2021-11-22'),
date: '2021-11-22', fee: 1.55,
dataSource: 'YAHOO', quantity: 2,
fee: new Big(1.55), SymbolProfile: {
name: 'Bâloise Holding AG', currency: 'CHF',
quantity: new Big(2), dataSource: 'YAHOO',
symbol: 'BALN.SW', name: 'Bâloise Holding AG',
symbol: 'BALN.SW'
},
type: 'BUY', type: 'BUY',
unitPrice: new Big(142.9) unitPrice: 142.9
}, },
{ {
currency: 'CHF', date: new Date('2021-11-30'),
date: '2021-11-30', fee: 1.65,
dataSource: 'YAHOO', quantity: 1,
fee: new Big(1.65), SymbolProfile: {
name: 'Bâloise Holding AG', currency: 'CHF',
quantity: new Big(1), dataSource: 'YAHOO',
symbol: 'BALN.SW', name: 'Bâloise Holding AG',
symbol: 'BALN.SW'
},
type: 'SELL', type: 'SELL',
unitPrice: new Big(136.6) unitPrice: 136.6
}, },
{ {
currency: 'CHF', date: new Date('2021-11-30'),
date: '2021-11-30', fee: 0,
dataSource: 'YAHOO', quantity: 1,
fee: new Big(0), SymbolProfile: {
name: 'Bâloise Holding AG', currency: 'CHF',
quantity: new Big(1), dataSource: 'YAHOO',
symbol: 'BALN.SW', name: 'Bâloise Holding AG',
symbol: 'BALN.SW'
},
type: 'SELL', type: 'SELL',
unitPrice: new Big(136.6) unitPrice: 136.6
} }
] ],
currency: 'CHF'
}); });
portfolioCalculator.computeTransactionPoints();
const spy = jest const spy = jest
.spyOn(Date, 'now') .spyOn(Date, 'now')
.mockImplementation(() => parseDate('2021-12-18').getTime()); .mockImplementation(() => parseDate('2021-12-18').getTime());

@ -1,3 +1,4 @@
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service'; import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { parseDate } from '@ghostfolio/common/helper'; import { parseDate } from '@ghostfolio/common/helper';
@ -36,35 +37,37 @@ describe('PortfolioCalculator', () => {
const portfolioCalculator = new PortfolioCalculator({ const portfolioCalculator = new PortfolioCalculator({
currentRateService, currentRateService,
exchangeRateDataService, exchangeRateDataService,
currency: 'CHF', activities: <Activity[]>[
orders: [
{ {
currency: 'CHF', date: new Date('2021-11-22'),
date: '2021-11-22', fee: 1.55,
dataSource: 'YAHOO', quantity: 2,
fee: new Big(1.55), SymbolProfile: {
name: 'Bâloise Holding AG', currency: 'CHF',
quantity: new Big(2), dataSource: 'YAHOO',
symbol: 'BALN.SW', name: 'Bâloise Holding AG',
symbol: 'BALN.SW'
},
type: 'BUY', type: 'BUY',
unitPrice: new Big(142.9) unitPrice: 142.9
}, },
{ {
currency: 'CHF', date: new Date('2021-11-30'),
date: '2021-11-30', fee: 1.65,
dataSource: 'YAHOO', quantity: 2,
fee: new Big(1.65), SymbolProfile: {
name: 'Bâloise Holding AG', currency: 'CHF',
quantity: new Big(2), dataSource: 'YAHOO',
symbol: 'BALN.SW', name: 'Bâloise Holding AG',
symbol: 'BALN.SW'
},
type: 'SELL', type: 'SELL',
unitPrice: new Big(136.6) unitPrice: 136.6
} }
] ],
currency: 'CHF'
}); });
portfolioCalculator.computeTransactionPoints();
const spy = jest const spy = jest
.spyOn(Date, 'now') .spyOn(Date, 'now')
.mockImplementation(() => parseDate('2021-12-18').getTime()); .mockImplementation(() => parseDate('2021-12-18').getTime());

@ -1,3 +1,4 @@
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service'; import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { parseDate } from '@ghostfolio/common/helper'; import { parseDate } from '@ghostfolio/common/helper';
@ -36,24 +37,24 @@ describe('PortfolioCalculator', () => {
const portfolioCalculator = new PortfolioCalculator({ const portfolioCalculator = new PortfolioCalculator({
currentRateService, currentRateService,
exchangeRateDataService, exchangeRateDataService,
currency: 'CHF', activities: <Activity[]>[
orders: [
{ {
currency: 'CHF', date: new Date('2021-11-30'),
date: '2021-11-30', fee: 1.55,
dataSource: 'YAHOO', quantity: 2,
fee: new Big(1.55), SymbolProfile: {
name: 'Bâloise Holding AG', currency: 'CHF',
quantity: new Big(2), dataSource: 'YAHOO',
symbol: 'BALN.SW', name: 'Bâloise Holding AG',
symbol: 'BALN.SW'
},
type: 'BUY', type: 'BUY',
unitPrice: new Big(136.6) unitPrice: 136.6
} }
] ],
currency: 'CHF'
}); });
portfolioCalculator.computeTransactionPoints();
const spy = jest const spy = jest
.spyOn(Date, 'now') .spyOn(Date, 'now')
.mockImplementation(() => parseDate('2021-12-18').getTime()); .mockImplementation(() => parseDate('2021-12-18').getTime());

@ -1,3 +1,4 @@
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service'; import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { ExchangeRateDataServiceMock } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service.mock'; import { ExchangeRateDataServiceMock } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service.mock';
@ -49,35 +50,37 @@ describe('PortfolioCalculator', () => {
const portfolioCalculator = new PortfolioCalculator({ const portfolioCalculator = new PortfolioCalculator({
currentRateService, currentRateService,
exchangeRateDataService, exchangeRateDataService,
currency: 'CHF', activities: <Activity[]>[
orders: [
{ {
currency: 'USD', date: new Date('2015-01-01'),
date: '2015-01-01', fee: 0,
dataSource: 'YAHOO', quantity: 2,
fee: new Big(0), SymbolProfile: {
name: 'Bitcoin USD', currency: 'USD',
quantity: new Big(2), dataSource: 'YAHOO',
symbol: 'BTCUSD', name: 'Bitcoin USD',
symbol: 'BTCUSD'
},
type: 'BUY', type: 'BUY',
unitPrice: new Big(320.43) unitPrice: 320.43
}, },
{ {
currency: 'USD', date: new Date('2017-12-31'),
date: '2017-12-31', fee: 0,
dataSource: 'YAHOO', quantity: 1,
fee: new Big(0), SymbolProfile: {
name: 'Bitcoin USD', currency: 'USD',
quantity: new Big(1), dataSource: 'YAHOO',
symbol: 'BTCUSD', name: 'Bitcoin USD',
symbol: 'BTCUSD'
},
type: 'SELL', type: 'SELL',
unitPrice: new Big(14156.4) unitPrice: 14156.4
} }
] ],
currency: 'CHF'
}); });
portfolioCalculator.computeTransactionPoints();
const spy = jest const spy = jest
.spyOn(Date, 'now') .spyOn(Date, 'now')
.mockImplementation(() => parseDate('2018-01-01').getTime()); .mockImplementation(() => parseDate('2018-01-01').getTime());

@ -1,3 +1,4 @@
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service'; import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { ExchangeRateDataServiceMock } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service.mock'; import { ExchangeRateDataServiceMock } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service.mock';
@ -49,24 +50,24 @@ describe('PortfolioCalculator', () => {
const portfolioCalculator = new PortfolioCalculator({ const portfolioCalculator = new PortfolioCalculator({
currentRateService, currentRateService,
exchangeRateDataService, exchangeRateDataService,
currency: 'CHF', activities: <Activity[]>[
orders: [
{ {
currency: 'USD', date: new Date('2023-01-03'),
date: '2023-01-03', fee: 1,
dataSource: 'YAHOO', quantity: 1,
fee: new Big(1), SymbolProfile: {
name: 'Alphabet Inc.', currency: 'USD',
quantity: new Big(1), dataSource: 'YAHOO',
symbol: 'GOOGL', name: 'Alphabet Inc.',
symbol: 'GOOGL'
},
type: 'BUY', type: 'BUY',
unitPrice: new Big(89.12) unitPrice: 89.12
} }
] ],
currency: 'CHF'
}); });
portfolioCalculator.computeTransactionPoints();
const spy = jest const spy = jest
.spyOn(Date, 'now') .spyOn(Date, 'now')
.mockImplementation(() => parseDate('2023-07-10').getTime()); .mockImplementation(() => parseDate('2023-07-10').getTime());

@ -1,3 +1,4 @@
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service'; import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { ExchangeRateDataServiceMock } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service.mock'; import { ExchangeRateDataServiceMock } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service.mock';
@ -49,35 +50,37 @@ describe('PortfolioCalculator', () => {
const portfolioCalculator = new PortfolioCalculator({ const portfolioCalculator = new PortfolioCalculator({
currentRateService, currentRateService,
exchangeRateDataService, exchangeRateDataService,
currency: 'USD', activities: <Activity[]>[
orders: [
{ {
currency: 'USD', date: new Date('2021-09-16'),
date: '2021-09-16', fee: 19,
dataSource: 'YAHOO', quantity: 1,
fee: new Big(19), SymbolProfile: {
name: 'Microsoft Inc.', currency: 'USD',
quantity: new Big(1), dataSource: 'YAHOO',
symbol: 'MSFT', name: 'Microsoft Inc.',
symbol: 'MSFT'
},
type: 'BUY', type: 'BUY',
unitPrice: new Big(298.58) unitPrice: 298.58
}, },
{ {
currency: 'USD', date: new Date('2021-11-16'),
date: '2021-11-16', fee: 0,
dataSource: 'YAHOO', quantity: 1,
fee: new Big(0), SymbolProfile: {
name: 'Microsoft Inc.', currency: 'USD',
quantity: new Big(1), dataSource: 'YAHOO',
symbol: 'MSFT', name: 'Microsoft Inc.',
symbol: 'MSFT'
},
type: 'DIVIDEND', type: 'DIVIDEND',
unitPrice: new Big(0.62) unitPrice: 0.62
} }
] ],
currency: 'USD'
}); });
portfolioCalculator.computeTransactionPoints();
const spy = jest const spy = jest
.spyOn(Date, 'now') .spyOn(Date, 'now')
.mockImplementation(() => parseDate('2023-07-10').getTime()); .mockImplementation(() => parseDate('2023-07-10').getTime());

@ -37,12 +37,10 @@ describe('PortfolioCalculator', () => {
const portfolioCalculator = new PortfolioCalculator({ const portfolioCalculator = new PortfolioCalculator({
currentRateService, currentRateService,
exchangeRateDataService, exchangeRateDataService,
currency: 'CHF', activities: [],
orders: [] currency: 'CHF'
}); });
portfolioCalculator.computeTransactionPoints();
const spy = jest const spy = jest
.spyOn(Date, 'now') .spyOn(Date, 'now')
.mockImplementation(() => parseDate('2021-12-18').getTime()); .mockImplementation(() => parseDate('2021-12-18').getTime());

@ -1,3 +1,4 @@
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service'; import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { parseDate } from '@ghostfolio/common/helper'; import { parseDate } from '@ghostfolio/common/helper';
@ -36,35 +37,37 @@ describe('PortfolioCalculator', () => {
const portfolioCalculator = new PortfolioCalculator({ const portfolioCalculator = new PortfolioCalculator({
currentRateService, currentRateService,
exchangeRateDataService, exchangeRateDataService,
currency: 'CHF', activities: <Activity[]>[
orders: [
{ {
currency: 'CHF', date: new Date('2022-03-07'),
date: '2022-03-07', fee: 1.3,
dataSource: 'YAHOO', quantity: 2,
fee: new Big(1.3), SymbolProfile: {
name: 'Novartis AG', currency: 'CHF',
quantity: new Big(2), dataSource: 'YAHOO',
symbol: 'NOVN.SW', name: 'Novartis AG',
symbol: 'NOVN.SW'
},
type: 'BUY', type: 'BUY',
unitPrice: new Big(75.8) unitPrice: 75.8
}, },
{ {
currency: 'CHF', date: new Date('2022-04-08'),
date: '2022-04-08', fee: 2.95,
dataSource: 'YAHOO', quantity: 1,
fee: new Big(2.95), SymbolProfile: {
name: 'Novartis AG', currency: 'CHF',
quantity: new Big(1), dataSource: 'YAHOO',
symbol: 'NOVN.SW', name: 'Novartis AG',
symbol: 'NOVN.SW'
},
type: 'SELL', type: 'SELL',
unitPrice: new Big(85.73) unitPrice: 85.73
} }
] ],
currency: 'CHF'
}); });
portfolioCalculator.computeTransactionPoints();
const spy = jest const spy = jest
.spyOn(Date, 'now') .spyOn(Date, 'now')
.mockImplementation(() => parseDate('2022-04-11').getTime()); .mockImplementation(() => parseDate('2022-04-11').getTime());

@ -1,3 +1,4 @@
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service'; import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { parseDate } from '@ghostfolio/common/helper'; import { parseDate } from '@ghostfolio/common/helper';
@ -36,35 +37,37 @@ describe('PortfolioCalculator', () => {
const portfolioCalculator = new PortfolioCalculator({ const portfolioCalculator = new PortfolioCalculator({
currentRateService, currentRateService,
exchangeRateDataService, exchangeRateDataService,
currency: 'CHF', activities: <Activity[]>[
orders: [
{ {
currency: 'CHF', date: new Date('2022-03-07'),
date: '2022-03-07', fee: 0,
dataSource: 'YAHOO', quantity: 2,
fee: new Big(0), SymbolProfile: {
name: 'Novartis AG', currency: 'CHF',
quantity: new Big(2), dataSource: 'YAHOO',
symbol: 'NOVN.SW', name: 'Novartis AG',
symbol: 'NOVN.SW'
},
type: 'BUY', type: 'BUY',
unitPrice: new Big(75.8) unitPrice: 75.8
}, },
{ {
currency: 'CHF', date: new Date('2022-04-08'),
date: '2022-04-08', fee: 0,
dataSource: 'YAHOO', quantity: 2,
fee: new Big(0), SymbolProfile: {
name: 'Novartis AG', currency: 'CHF',
quantity: new Big(2), dataSource: 'YAHOO',
symbol: 'NOVN.SW', name: 'Novartis AG',
symbol: 'NOVN.SW'
},
type: 'SELL', type: 'SELL',
unitPrice: new Big(85.73) unitPrice: 85.73
} }
] ],
currency: 'CHF'
}); });
portfolioCalculator.computeTransactionPoints();
const spy = jest const spy = jest
.spyOn(Date, 'now') .spyOn(Date, 'now')
.mockImplementation(() => parseDate('2022-04-11').getTime()); .mockImplementation(() => parseDate('2022-04-11').getTime());

@ -22,10 +22,10 @@ describe('PortfolioCalculator', () => {
describe('annualized performance percentage', () => { describe('annualized performance percentage', () => {
const portfolioCalculator = new PortfolioCalculator({ const portfolioCalculator = new PortfolioCalculator({
activities: [],
currentRateService, currentRateService,
exchangeRateDataService, exchangeRateDataService,
currency: 'USD', currency: 'USD'
orders: []
}); });
it('Get annualized performance', async () => { it('Get annualized performance', async () => {

@ -1,3 +1,4 @@
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
import { getFactor } from '@ghostfolio/api/helper/portfolio.helper'; import { getFactor } from '@ghostfolio/api/helper/portfolio.helper';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { IDataGatheringItem } from '@ghostfolio/api/services/interfaces/interfaces'; import { IDataGatheringItem } from '@ghostfolio/api/services/interfaces/interfaces';
@ -8,7 +9,8 @@ import {
InvestmentItem, InvestmentItem,
ResponseError, ResponseError,
SymbolMetrics, SymbolMetrics,
TimelinePosition TimelinePosition,
UniqueAsset
} from '@ghostfolio/common/interfaces'; } from '@ghostfolio/common/interfaces';
import { GroupBy } from '@ghostfolio/common/types'; import { GroupBy } from '@ghostfolio/common/types';
@ -46,122 +48,37 @@ export class PortfolioCalculator {
private transactionPoints: TransactionPoint[]; private transactionPoints: TransactionPoint[];
public constructor({ public constructor({
activities,
currency, currency,
currentRateService, currentRateService,
exchangeRateDataService, exchangeRateDataService
orders,
transactionPoints
}: { }: {
activities: Activity[];
currency: string; currency: string;
currentRateService: CurrentRateService; currentRateService: CurrentRateService;
exchangeRateDataService: ExchangeRateDataService; exchangeRateDataService: ExchangeRateDataService;
orders: PortfolioOrder[];
transactionPoints?: TransactionPoint[];
}) { }) {
this.currency = currency; this.currency = currency;
this.currentRateService = currentRateService; this.currentRateService = currentRateService;
this.exchangeRateDataService = exchangeRateDataService; this.exchangeRateDataService = exchangeRateDataService;
this.orders = orders; this.orders = activities.map(
({ date, fee, quantity, SymbolProfile, type, unitPrice }) => {
return {
SymbolProfile,
type,
date: format(date, DATE_FORMAT),
fee: new Big(fee),
quantity: new Big(quantity),
unitPrice: new Big(unitPrice)
};
}
);
this.orders.sort((a, b) => { this.orders.sort((a, b) => {
return a.date?.localeCompare(b.date); return a.date?.localeCompare(b.date);
}); });
if (transactionPoints) { this.computeTransactionPoints();
this.transactionPoints = transactionPoints;
}
}
public computeTransactionPoints() {
this.transactionPoints = [];
const symbols: { [symbol: string]: TransactionPointSymbol } = {};
let lastDate: string = null;
let lastTransactionPoint: TransactionPoint = null;
for (const order of this.orders) {
const currentDate = order.date;
let currentTransactionPointItem: TransactionPointSymbol;
const oldAccumulatedSymbol = symbols[order.symbol];
const factor = getFactor(order.type);
if (oldAccumulatedSymbol) {
let investment = oldAccumulatedSymbol.investment;
const newQuantity = order.quantity
.mul(factor)
.plus(oldAccumulatedSymbol.quantity);
if (order.type === 'BUY') {
investment = oldAccumulatedSymbol.investment.plus(
order.quantity.mul(order.unitPrice)
);
} else if (order.type === 'SELL') {
investment = oldAccumulatedSymbol.investment.minus(
order.quantity.mul(oldAccumulatedSymbol.averagePrice)
);
}
currentTransactionPointItem = {
investment,
averagePrice: newQuantity.gt(0)
? investment.div(newQuantity)
: new Big(0),
currency: order.currency,
dataSource: order.dataSource,
dividend: new Big(0),
fee: order.fee.plus(oldAccumulatedSymbol.fee),
firstBuyDate: oldAccumulatedSymbol.firstBuyDate,
quantity: newQuantity,
symbol: order.symbol,
tags: order.tags,
transactionCount: oldAccumulatedSymbol.transactionCount + 1
};
} else {
currentTransactionPointItem = {
averagePrice: order.unitPrice,
currency: order.currency,
dataSource: order.dataSource,
dividend: new Big(0),
fee: order.fee,
firstBuyDate: order.date,
investment: order.unitPrice.mul(order.quantity).mul(factor),
quantity: order.quantity.mul(factor),
symbol: order.symbol,
tags: order.tags,
transactionCount: 1
};
}
symbols[order.symbol] = currentTransactionPointItem;
const items = lastTransactionPoint?.items ?? [];
const newItems = items.filter(
(transactionPointItem) => transactionPointItem.symbol !== order.symbol
);
newItems.push(currentTransactionPointItem);
newItems.sort((a, b) => {
return a.symbol?.localeCompare(b.symbol);
});
if (lastDate !== currentDate || lastTransactionPoint === null) {
lastTransactionPoint = {
date: currentDate,
items: newItems
};
this.transactionPoints.push(lastTransactionPoint);
} else {
lastTransactionPoint.items = newItems;
}
lastDate = currentDate;
}
} }
public getAnnualizedPerformancePercent({ public getAnnualizedPerformancePercent({
@ -181,10 +98,6 @@ export class PortfolioCalculator {
return new Big(0); return new Big(0);
} }
public getTransactionPoints(): TransactionPoint[] {
return this.transactionPoints;
}
public async getChartData({ public async getChartData({
end = new Date(Date.now()), end = new Date(Date.now()),
start, start,
@ -248,7 +161,7 @@ export class PortfolioCalculator {
await this.exchangeRateDataService.getExchangeRatesByCurrency({ await this.exchangeRateDataService.getExchangeRatesByCurrency({
currencies: uniq(Object.values(currencies)), currencies: uniq(Object.values(currencies)),
endDate: endOfDay(end), endDate: endOfDay(end),
startDate: parseDate(this.transactionPoints?.[0]?.date), startDate: this.getStartDate(),
targetCurrency: this.currency targetCurrency: this.currency
}); });
@ -309,6 +222,7 @@ export class PortfolioCalculator {
start, start,
step, step,
symbol, symbol,
dataSource: null,
exchangeRates: exchangeRates:
exchangeRatesByCurrency[`${currencies[symbol]}${this.currency}`], exchangeRatesByCurrency[`${currencies[symbol]}${this.currency}`],
isChartMode: true isChartMode: true
@ -551,7 +465,7 @@ export class PortfolioCalculator {
await this.exchangeRateDataService.getExchangeRatesByCurrency({ await this.exchangeRateDataService.getExchangeRatesByCurrency({
currencies: uniq(Object.values(currencies)), currencies: uniq(Object.values(currencies)),
endDate: endOfDay(endDate), endDate: endOfDay(endDate),
startDate: parseDate(this.transactionPoints?.[0]?.date), startDate: this.getStartDate(),
targetCurrency: this.currency targetCurrency: this.currency
}); });
@ -625,6 +539,7 @@ export class PortfolioCalculator {
} = this.getSymbolMetrics({ } = this.getSymbolMetrics({
marketSymbolMap, marketSymbolMap,
start, start,
dataSource: item.dataSource,
end: endDate, end: endDate,
exchangeRates: exchangeRates:
exchangeRatesByCurrency[`${item.currency}${this.currency}`], exchangeRatesByCurrency[`${item.currency}${this.currency}`],
@ -844,7 +759,116 @@ export class PortfolioCalculator {
}; };
} }
public getStartDate() {
return this.transactionPoints.length > 0
? parseDate(this.transactionPoints[0].date)
: new Date();
}
public getTransactionPoints() {
return this.transactionPoints;
}
private computeTransactionPoints() {
this.transactionPoints = [];
const symbols: { [symbol: string]: TransactionPointSymbol } = {};
let lastDate: string = null;
let lastTransactionPoint: TransactionPoint = null;
for (const {
fee,
date,
quantity,
SymbolProfile,
tags,
type,
unitPrice
} of this.orders) {
let currentTransactionPointItem: TransactionPointSymbol;
const oldAccumulatedSymbol = symbols[SymbolProfile.symbol];
const factor = getFactor(type);
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)
);
}
currentTransactionPointItem = {
investment,
tags,
averagePrice: newQuantity.gt(0)
? investment.div(newQuantity)
: new Big(0),
currency: SymbolProfile.currency,
dataSource: SymbolProfile.dataSource,
dividend: new Big(0),
fee: fee.plus(oldAccumulatedSymbol.fee),
firstBuyDate: oldAccumulatedSymbol.firstBuyDate,
quantity: newQuantity,
symbol: SymbolProfile.symbol,
transactionCount: oldAccumulatedSymbol.transactionCount + 1
};
} else {
currentTransactionPointItem = {
fee,
tags,
averagePrice: unitPrice,
currency: SymbolProfile.currency,
dataSource: SymbolProfile.dataSource,
dividend: new Big(0),
firstBuyDate: date,
investment: unitPrice.mul(quantity).mul(factor),
quantity: quantity.mul(factor),
symbol: SymbolProfile.symbol,
transactionCount: 1
};
}
symbols[SymbolProfile.symbol] = currentTransactionPointItem;
const items = lastTransactionPoint?.items ?? [];
const newItems = items.filter(({ symbol }) => {
return symbol !== SymbolProfile.symbol;
});
newItems.push(currentTransactionPointItem);
newItems.sort((a, b) => {
return a.symbol?.localeCompare(b.symbol);
});
if (lastDate !== date || lastTransactionPoint === null) {
lastTransactionPoint = {
date,
items: newItems
};
this.transactionPoints.push(lastTransactionPoint);
} else {
lastTransactionPoint.items = newItems;
}
lastDate = date;
}
}
private getSymbolMetrics({ private getSymbolMetrics({
dataSource,
end, end,
exchangeRates, exchangeRates,
isChartMode = false, isChartMode = false,
@ -861,8 +885,7 @@ export class PortfolioCalculator {
}; };
start: Date; start: Date;
step?: number; step?: number;
symbol: string; } & UniqueAsset): SymbolMetrics {
}): SymbolMetrics {
const currentExchangeRate = exchangeRates[format(new Date(), DATE_FORMAT)]; const currentExchangeRate = exchangeRates[format(new Date(), DATE_FORMAT)];
const currentValues: { [date: string]: Big } = {}; const currentValues: { [date: string]: Big } = {};
const currentValuesWithCurrencyEffect: { [date: string]: Big } = {}; const currentValuesWithCurrencyEffect: { [date: string]: Big } = {};
@ -908,8 +931,8 @@ export class PortfolioCalculator {
// Clone orders to keep the original values in this.orders // Clone orders to keep the original values in this.orders
let orders: PortfolioOrderItem[] = cloneDeep(this.orders).filter( let orders: PortfolioOrderItem[] = cloneDeep(this.orders).filter(
(order) => { ({ SymbolProfile }) => {
return order.symbol === symbol; return SymbolProfile.symbol === symbol;
} }
); );
@ -988,28 +1011,28 @@ export class PortfolioCalculator {
// Add a synthetic order at the start and the end date // Add a synthetic order at the start and the end date
orders.push({ orders.push({
symbol,
currency: null,
date: format(start, DATE_FORMAT), date: format(start, DATE_FORMAT),
dataSource: null,
fee: new Big(0), fee: new Big(0),
feeInBaseCurrency: new Big(0), feeInBaseCurrency: new Big(0),
itemType: 'start', itemType: 'start',
name: '',
quantity: new Big(0), quantity: new Big(0),
SymbolProfile: {
dataSource,
symbol
},
type: 'BUY', type: 'BUY',
unitPrice: unitPriceAtStartDate unitPrice: unitPriceAtStartDate
}); });
orders.push({ orders.push({
symbol,
currency: null,
date: format(end, DATE_FORMAT), date: format(end, DATE_FORMAT),
dataSource: null,
fee: new Big(0), fee: new Big(0),
feeInBaseCurrency: new Big(0), feeInBaseCurrency: new Big(0),
itemType: 'end', itemType: 'end',
name: '', SymbolProfile: {
dataSource,
symbol
},
quantity: new Big(0), quantity: new Big(0),
type: 'BUY', type: 'BUY',
unitPrice: unitPriceAtEndDate unitPrice: unitPriceAtEndDate
@ -1030,14 +1053,14 @@ export class PortfolioCalculator {
if (!hasDate) { if (!hasDate) {
orders.push({ orders.push({
symbol,
currency: null,
date: format(day, DATE_FORMAT), date: format(day, DATE_FORMAT),
dataSource: null,
fee: new Big(0), fee: new Big(0),
feeInBaseCurrency: new Big(0), feeInBaseCurrency: new Big(0),
name: '',
quantity: new Big(0), quantity: new Big(0),
SymbolProfile: {
dataSource,
symbol
},
type: 'BUY', type: 'BUY',
unitPrice: unitPrice:
marketSymbolMap[format(day, DATE_FORMAT)]?.[symbol] ?? marketSymbolMap[format(day, DATE_FORMAT)]?.[symbol] ??

@ -4,8 +4,6 @@ import { CashDetails } from '@ghostfolio/api/app/account/interfaces/cash-details
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
import { OrderService } from '@ghostfolio/api/app/order/order.service'; import { OrderService } from '@ghostfolio/api/app/order/order.service';
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service'; import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
import { PortfolioOrder } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-order.interface';
import { TransactionPoint } from '@ghostfolio/api/app/portfolio/interfaces/transaction-point.interface';
import { UserService } from '@ghostfolio/api/app/user/user.service'; import { UserService } from '@ghostfolio/api/app/user/user.service';
import { getFactor } from '@ghostfolio/api/helper/portfolio.helper'; import { getFactor } from '@ghostfolio/api/helper/portfolio.helper';
import { AccountClusterRiskCurrentInvestment } from '@ghostfolio/api/models/rules/account-cluster-risk/current-investment'; import { AccountClusterRiskCurrentInvestment } from '@ghostfolio/api/models/rules/account-cluster-risk/current-investment';
@ -266,15 +264,15 @@ export class PortfolioService {
}): Promise<PortfolioInvestments> { }): Promise<PortfolioInvestments> {
const userId = await this.getUserId(impersonationId, this.request.user.id); const userId = await this.getUserId(impersonationId, this.request.user.id);
const { portfolioOrders, transactionPoints } = const { activities } = await this.orderService.getOrders({
await this.getTransactionPoints({ filters,
filters, userId,
userId, includeDrafts: true,
includeDrafts: true, types: ['BUY', 'SELL'],
types: ['BUY', 'SELL'] userCurrency: this.getUserCurrency()
}); });
if (transactionPoints.length === 0) { if (activities.length === 0) {
return { return {
investments: [], investments: [],
streaks: { currentStreak: 0, longestStreak: 0 } streaks: { currentStreak: 0, longestStreak: 0 }
@ -282,18 +280,16 @@ export class PortfolioService {
} }
const portfolioCalculator = new PortfolioCalculator({ const portfolioCalculator = new PortfolioCalculator({
transactionPoints, activities,
currency: this.request.user.Settings.settings.baseCurrency, currency: this.request.user.Settings.settings.baseCurrency,
currentRateService: this.currentRateService, currentRateService: this.currentRateService,
exchangeRateDataService: this.exchangeRateDataService, exchangeRateDataService: this.exchangeRateDataService
orders: portfolioOrders
}); });
const { items } = await this.getChart({ const { items } = await this.getChart({
dateRange, dateRange,
impersonationId, impersonationId,
portfolioCalculator, portfolioCalculator,
transactionPoints,
userId, userId,
withDataDecimation: false withDataDecimation: false
}); });
@ -364,26 +360,25 @@ export class PortfolioService {
}); });
} }
const { activities, portfolioOrders, transactionPoints } = const { activities } = await this.orderService.getOrders({
await this.getTransactionPoints({ filters,
filters, types,
types, userCurrency,
userId, userId,
withExcludedAccounts withExcludedAccounts
}); });
const portfolioCalculator = new PortfolioCalculator({ const portfolioCalculator = new PortfolioCalculator({
transactionPoints, activities,
currency: userCurrency, currency: userCurrency,
currentRateService: this.currentRateService, currentRateService: this.currentRateService,
exchangeRateDataService: this.exchangeRateDataService, exchangeRateDataService: this.exchangeRateDataService
orders: portfolioOrders
}); });
const portfolioStart = parseDate( const startDate = this.getStartDate(
transactionPoints[0]?.date ?? format(new Date(), DATE_FORMAT) dateRange,
portfolioCalculator.getStartDate()
); );
const startDate = this.getStartDate(dateRange, portfolioStart);
const currentPositions = const currentPositions =
await portfolioCalculator.getCurrentPositions(startDate); await portfolioCalculator.getCurrentPositions(startDate);
@ -737,39 +732,22 @@ export class PortfolioService {
{ dataSource: aDataSource, symbol: aSymbol } { dataSource: aDataSource, symbol: aSymbol }
]); ]);
const portfolioOrders: PortfolioOrder[] = orders
.filter((order) => {
tags = tags.concat(order.tags);
return ['BUY', 'DIVIDEND', 'ITEM', 'SELL'].includes(order.type);
})
.map((order) => ({
currency: order.SymbolProfile.currency,
dataSource: order.SymbolProfile.dataSource,
date: format(order.date, DATE_FORMAT),
fee: new Big(order.fee),
name: order.SymbolProfile?.name,
quantity: new Big(order.quantity),
symbol: order.SymbolProfile.symbol,
tags: order.tags,
type: order.type,
unitPrice: new Big(order.unitPrice)
}));
tags = uniqBy(tags, 'id'); tags = uniqBy(tags, 'id');
const portfolioCalculator = new PortfolioCalculator({ const portfolioCalculator = new PortfolioCalculator({
activities: orders.filter((order) => {
tags = tags.concat(order.tags);
return ['BUY', 'DIVIDEND', 'ITEM', 'SELL'].includes(order.type);
}),
currency: userCurrency, currency: userCurrency,
currentRateService: this.currentRateService, currentRateService: this.currentRateService,
exchangeRateDataService: this.exchangeRateDataService, exchangeRateDataService: this.exchangeRateDataService
orders: portfolioOrders
}); });
portfolioCalculator.computeTransactionPoints(); const portfolioStart = portfolioCalculator.getStartDate();
const transactionPoints = portfolioCalculator.getTransactionPoints(); const transactionPoints = portfolioCalculator.getTransactionPoints();
const portfolioStart = parseDate(transactionPoints[0].date);
const currentPositions = const currentPositions =
await portfolioCalculator.getCurrentPositions(portfolioStart); await portfolioCalculator.getCurrentPositions(portfolioStart);
@ -982,14 +960,14 @@ export class PortfolioService {
const userId = await this.getUserId(impersonationId, this.request.user.id); const userId = await this.getUserId(impersonationId, this.request.user.id);
const user = await this.userService.user({ id: userId }); const user = await this.userService.user({ id: userId });
const { portfolioOrders, transactionPoints } = const { activities } = await this.orderService.getOrders({
await this.getTransactionPoints({ filters,
filters, userId,
userId, types: ['BUY', 'SELL'],
types: ['BUY', 'SELL'] userCurrency: this.getUserCurrency()
}); });
if (transactionPoints?.length <= 0) { if (activities?.length <= 0) {
return { return {
hasErrors: false, hasErrors: false,
positions: [] positions: []
@ -997,15 +975,16 @@ export class PortfolioService {
} }
const portfolioCalculator = new PortfolioCalculator({ const portfolioCalculator = new PortfolioCalculator({
transactionPoints, activities,
currency: this.request.user.Settings.settings.baseCurrency, currency: this.request.user.Settings.settings.baseCurrency,
currentRateService: this.currentRateService, currentRateService: this.currentRateService,
exchangeRateDataService: this.exchangeRateDataService, exchangeRateDataService: this.exchangeRateDataService
orders: portfolioOrders
}); });
const portfolioStart = parseDate(transactionPoints[0].date); const startDate = this.getStartDate(
const startDate = this.getStartDate(dateRange, portfolioStart); dateRange,
portfolioCalculator.getStartDate()
);
const currentPositions = const currentPositions =
await portfolioCalculator.getCurrentPositions(startDate); await portfolioCalculator.getCurrentPositions(startDate);
@ -1154,15 +1133,15 @@ export class PortfolioService {
) )
); );
const { portfolioOrders, transactionPoints } = const { activities } = await this.orderService.getOrders({
await this.getTransactionPoints({ filters,
filters, userCurrency,
userId, userId,
withExcludedAccounts, withExcludedAccounts,
types: withItems ? ['BUY', 'ITEM', 'SELL'] : ['BUY', 'SELL'] types: withItems ? ['BUY', 'ITEM', 'SELL'] : ['BUY', 'SELL']
}); });
if (accountBalanceItems?.length <= 0 && transactionPoints?.length <= 0) { if (accountBalanceItems?.length <= 0 && activities?.length <= 0) {
return { return {
chart: [], chart: [],
firstOrderDate: undefined, firstOrderDate: undefined,
@ -1184,17 +1163,16 @@ export class PortfolioService {
} }
const portfolioCalculator = new PortfolioCalculator({ const portfolioCalculator = new PortfolioCalculator({
transactionPoints, activities,
currency: userCurrency, currency: userCurrency,
currentRateService: this.currentRateService, currentRateService: this.currentRateService,
exchangeRateDataService: this.exchangeRateDataService, exchangeRateDataService: this.exchangeRateDataService
orders: portfolioOrders
}); });
const portfolioStart = min( const portfolioStart = min(
[ [
parseDate(accountBalanceItems[0]?.date), parseDate(accountBalanceItems[0]?.date),
parseDate(transactionPoints[0]?.date) portfolioCalculator.getStartDate()
].filter((date) => { ].filter((date) => {
return isValid(date); return isValid(date);
}) })
@ -1230,7 +1208,6 @@ export class PortfolioService {
dateRange, dateRange,
impersonationId, impersonationId,
portfolioCalculator, portfolioCalculator,
transactionPoints,
userId userId
}); });
@ -1307,25 +1284,22 @@ export class PortfolioService {
const user = await this.userService.user({ id: userId }); const user = await this.userService.user({ id: userId });
const userCurrency = this.getUserCurrency(user); const userCurrency = this.getUserCurrency(user);
const { activities, portfolioOrders, transactionPoints } = const { activities } = await this.orderService.getOrders({
await this.getTransactionPoints({ userCurrency,
userId, userId,
types: ['BUY', 'SELL'] types: ['BUY', 'SELL']
}); });
const portfolioCalculator = new PortfolioCalculator({ const portfolioCalculator = new PortfolioCalculator({
transactionPoints, activities,
currency: userCurrency, currency: userCurrency,
currentRateService: this.currentRateService, currentRateService: this.currentRateService,
exchangeRateDataService: this.exchangeRateDataService, exchangeRateDataService: this.exchangeRateDataService
orders: portfolioOrders
}); });
const portfolioStart = parseDate( const currentPositions = await portfolioCalculator.getCurrentPositions(
transactionPoints[0]?.date ?? format(new Date(), DATE_FORMAT) portfolioCalculator.getStartDate()
); );
const currentPositions =
await portfolioCalculator.getCurrentPositions(portfolioStart);
const positions = currentPositions.positions.filter( const positions = currentPositions.positions.filter(
(item) => !item.quantity.eq(0) (item) => !item.quantity.eq(0)
@ -1455,18 +1429,16 @@ export class PortfolioService {
dateRange = 'max', dateRange = 'max',
impersonationId, impersonationId,
portfolioCalculator, portfolioCalculator,
transactionPoints,
userId, userId,
withDataDecimation = true withDataDecimation = true
}: { }: {
dateRange?: DateRange; dateRange?: DateRange;
impersonationId: string; impersonationId: string;
portfolioCalculator: PortfolioCalculator; portfolioCalculator: PortfolioCalculator;
transactionPoints: TransactionPoint[];
userId: string; userId: string;
withDataDecimation?: boolean; withDataDecimation?: boolean;
}): Promise<HistoricalDataContainer> { }): Promise<HistoricalDataContainer> {
if (transactionPoints.length === 0) { if (portfolioCalculator.getTransactionPoints().length === 0) {
return { return {
isAllTimeHigh: false, isAllTimeHigh: false,
isAllTimeLow: false, isAllTimeLow: false,
@ -1476,8 +1448,10 @@ export class PortfolioService {
userId = await this.getUserId(impersonationId, userId); userId = await this.getUserId(impersonationId, userId);
const portfolioStart = parseDate(transactionPoints[0].date); const startDate = this.getStartDate(
const startDate = this.getStartDate(dateRange, portfolioStart); dateRange,
portfolioCalculator.getStartDate()
);
const endDate = new Date(); const endDate = new Date();
const daysInMarket = differenceInDays(endDate, startDate) + 1; const daysInMarket = differenceInDays(endDate, startDate) + 1;
const step = withDataDecimation const step = withDataDecimation
@ -1865,10 +1839,10 @@ export class PortfolioService {
const daysInMarket = differenceInDays(new Date(), firstOrderDate); const daysInMarket = differenceInDays(new Date(), firstOrderDate);
const annualizedPerformancePercent = new PortfolioCalculator({ const annualizedPerformancePercent = new PortfolioCalculator({
activities: [],
currency: userCurrency, currency: userCurrency,
currentRateService: this.currentRateService, currentRateService: this.currentRateService,
exchangeRateDataService: this.exchangeRateDataService, exchangeRateDataService: this.exchangeRateDataService
orders: []
}) })
.getAnnualizedPerformancePercent({ .getAnnualizedPerformancePercent({
daysInMarket, daysInMarket,
@ -1880,10 +1854,10 @@ export class PortfolioService {
const annualizedPerformancePercentWithCurrencyEffect = const annualizedPerformancePercentWithCurrencyEffect =
new PortfolioCalculator({ new PortfolioCalculator({
activities: [],
currency: userCurrency, currency: userCurrency,
currentRateService: this.currentRateService, currentRateService: this.currentRateService,
exchangeRateDataService: this.exchangeRateDataService, exchangeRateDataService: this.exchangeRateDataService
orders: []
}) })
.getAnnualizedPerformancePercent({ .getAnnualizedPerformancePercent({
daysInMarket, daysInMarket,
@ -1955,71 +1929,9 @@ export class PortfolioService {
); );
} }
private async getTransactionPoints({ private getUserCurrency(aUser?: UserWithSettings) {
filters,
includeDrafts = false,
types = getAllActivityTypes(),
userId,
withExcludedAccounts = false
}: {
filters?: Filter[];
includeDrafts?: boolean;
types?: ActivityType[];
userId: string;
withExcludedAccounts?: boolean;
}): Promise<{
activities: Activity[];
transactionPoints: TransactionPoint[];
portfolioOrders: PortfolioOrder[];
}> {
const userCurrency =
this.request.user?.Settings?.settings.baseCurrency ?? DEFAULT_CURRENCY;
const { activities, count } = await this.orderService.getOrders({
filters,
includeDrafts,
types,
userCurrency,
userId,
withExcludedAccounts
});
if (count <= 0) {
return { activities: [], transactionPoints: [], portfolioOrders: [] };
}
const portfolioOrders: PortfolioOrder[] = activities.map((order) => ({
currency: order.SymbolProfile.currency,
dataSource: order.SymbolProfile.dataSource,
date: format(order.date, DATE_FORMAT),
fee: new Big(order.fee),
name: order.SymbolProfile?.name,
quantity: new Big(order.quantity),
symbol: order.SymbolProfile.symbol,
tags: order.tags,
type: order.type,
unitPrice: new Big(order.unitPrice)
}));
const portfolioCalculator = new PortfolioCalculator({
currency: userCurrency,
currentRateService: this.currentRateService,
exchangeRateDataService: this.exchangeRateDataService,
orders: portfolioOrders
});
portfolioCalculator.computeTransactionPoints();
return {
activities,
portfolioOrders,
transactionPoints: portfolioCalculator.getTransactionPoints()
};
}
private getUserCurrency(aUser: UserWithSettings) {
return ( return (
aUser.Settings?.settings.baseCurrency ?? aUser?.Settings?.settings.baseCurrency ??
this.request.user?.Settings?.settings.baseCurrency ?? this.request.user?.Settings?.settings.baseCurrency ??
DEFAULT_CURRENCY DEFAULT_CURRENCY
); );

Loading…
Cancel
Save