Refactor portfolio calculator (#3203)
* Refactor portfolio calculator * Consume Activity[] * Change computeTransactionPoints() to private * Eliminate getTransactionPoints() * Update changelog
This commit is contained in:
parent
5529fdc0ee
commit
5788c6474e
@ -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 account’s currency
|
||||
- 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 the chart tooltip of the benchmark comparator
|
||||
|
@ -1,15 +1,12 @@
|
||||
import { DataSource, Tag, Type as ActivityType } from '@prisma/client';
|
||||
import { Big } from 'big.js';
|
||||
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
|
||||
|
||||
export interface PortfolioOrder {
|
||||
currency: string;
|
||||
export interface PortfolioOrder extends Pick<Activity, 'tags' | 'type'> {
|
||||
date: string;
|
||||
dataSource: DataSource;
|
||||
fee: Big;
|
||||
name: string;
|
||||
quantity: Big;
|
||||
symbol: string;
|
||||
tags?: Tag[];
|
||||
type: ActivityType;
|
||||
SymbolProfile: Pick<
|
||||
Activity['SymbolProfile'],
|
||||
'currency' | 'dataSource' | 'name' | 'symbol'
|
||||
>;
|
||||
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 { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
|
||||
import { parseDate } from '@ghostfolio/common/helper';
|
||||
@ -36,46 +37,50 @@ describe('PortfolioCalculator', () => {
|
||||
const portfolioCalculator = new PortfolioCalculator({
|
||||
currentRateService,
|
||||
exchangeRateDataService,
|
||||
currency: 'CHF',
|
||||
orders: [
|
||||
activities: <Activity[]>[
|
||||
{
|
||||
date: new Date('2021-11-22'),
|
||||
fee: 1.55,
|
||||
quantity: 2,
|
||||
SymbolProfile: {
|
||||
currency: 'CHF',
|
||||
date: '2021-11-22',
|
||||
dataSource: 'YAHOO',
|
||||
fee: new Big(1.55),
|
||||
name: 'Bâloise Holding AG',
|
||||
quantity: new Big(2),
|
||||
symbol: 'BALN.SW',
|
||||
symbol: 'BALN.SW'
|
||||
},
|
||||
type: 'BUY',
|
||||
unitPrice: new Big(142.9)
|
||||
unitPrice: 142.9
|
||||
},
|
||||
{
|
||||
date: new Date('2021-11-30'),
|
||||
fee: 1.65,
|
||||
quantity: 1,
|
||||
SymbolProfile: {
|
||||
currency: 'CHF',
|
||||
date: '2021-11-30',
|
||||
dataSource: 'YAHOO',
|
||||
fee: new Big(1.65),
|
||||
name: 'Bâloise Holding AG',
|
||||
quantity: new Big(1),
|
||||
symbol: 'BALN.SW',
|
||||
symbol: 'BALN.SW'
|
||||
},
|
||||
type: 'SELL',
|
||||
unitPrice: new Big(136.6)
|
||||
unitPrice: 136.6
|
||||
},
|
||||
{
|
||||
date: new Date('2021-11-30'),
|
||||
fee: 0,
|
||||
quantity: 1,
|
||||
SymbolProfile: {
|
||||
currency: 'CHF',
|
||||
date: '2021-11-30',
|
||||
dataSource: 'YAHOO',
|
||||
fee: new Big(0),
|
||||
name: 'Bâloise Holding AG',
|
||||
quantity: new Big(1),
|
||||
symbol: 'BALN.SW',
|
||||
symbol: 'BALN.SW'
|
||||
},
|
||||
type: 'SELL',
|
||||
unitPrice: new Big(136.6)
|
||||
unitPrice: 136.6
|
||||
}
|
||||
]
|
||||
],
|
||||
currency: 'CHF'
|
||||
});
|
||||
|
||||
portfolioCalculator.computeTransactionPoints();
|
||||
|
||||
const spy = jest
|
||||
.spyOn(Date, 'now')
|
||||
.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 { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
|
||||
import { parseDate } from '@ghostfolio/common/helper';
|
||||
@ -36,35 +37,37 @@ describe('PortfolioCalculator', () => {
|
||||
const portfolioCalculator = new PortfolioCalculator({
|
||||
currentRateService,
|
||||
exchangeRateDataService,
|
||||
currency: 'CHF',
|
||||
orders: [
|
||||
activities: <Activity[]>[
|
||||
{
|
||||
date: new Date('2021-11-22'),
|
||||
fee: 1.55,
|
||||
quantity: 2,
|
||||
SymbolProfile: {
|
||||
currency: 'CHF',
|
||||
date: '2021-11-22',
|
||||
dataSource: 'YAHOO',
|
||||
fee: new Big(1.55),
|
||||
name: 'Bâloise Holding AG',
|
||||
quantity: new Big(2),
|
||||
symbol: 'BALN.SW',
|
||||
symbol: 'BALN.SW'
|
||||
},
|
||||
type: 'BUY',
|
||||
unitPrice: new Big(142.9)
|
||||
unitPrice: 142.9
|
||||
},
|
||||
{
|
||||
date: new Date('2021-11-30'),
|
||||
fee: 1.65,
|
||||
quantity: 2,
|
||||
SymbolProfile: {
|
||||
currency: 'CHF',
|
||||
date: '2021-11-30',
|
||||
dataSource: 'YAHOO',
|
||||
fee: new Big(1.65),
|
||||
name: 'Bâloise Holding AG',
|
||||
quantity: new Big(2),
|
||||
symbol: 'BALN.SW',
|
||||
symbol: 'BALN.SW'
|
||||
},
|
||||
type: 'SELL',
|
||||
unitPrice: new Big(136.6)
|
||||
unitPrice: 136.6
|
||||
}
|
||||
]
|
||||
],
|
||||
currency: 'CHF'
|
||||
});
|
||||
|
||||
portfolioCalculator.computeTransactionPoints();
|
||||
|
||||
const spy = jest
|
||||
.spyOn(Date, 'now')
|
||||
.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 { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
|
||||
import { parseDate } from '@ghostfolio/common/helper';
|
||||
@ -36,24 +37,24 @@ describe('PortfolioCalculator', () => {
|
||||
const portfolioCalculator = new PortfolioCalculator({
|
||||
currentRateService,
|
||||
exchangeRateDataService,
|
||||
currency: 'CHF',
|
||||
orders: [
|
||||
activities: <Activity[]>[
|
||||
{
|
||||
date: new Date('2021-11-30'),
|
||||
fee: 1.55,
|
||||
quantity: 2,
|
||||
SymbolProfile: {
|
||||
currency: 'CHF',
|
||||
date: '2021-11-30',
|
||||
dataSource: 'YAHOO',
|
||||
fee: new Big(1.55),
|
||||
name: 'Bâloise Holding AG',
|
||||
quantity: new Big(2),
|
||||
symbol: 'BALN.SW',
|
||||
symbol: 'BALN.SW'
|
||||
},
|
||||
type: 'BUY',
|
||||
unitPrice: new Big(136.6)
|
||||
unitPrice: 136.6
|
||||
}
|
||||
]
|
||||
],
|
||||
currency: 'CHF'
|
||||
});
|
||||
|
||||
portfolioCalculator.computeTransactionPoints();
|
||||
|
||||
const spy = jest
|
||||
.spyOn(Date, 'now')
|
||||
.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 { 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';
|
||||
@ -49,35 +50,37 @@ describe('PortfolioCalculator', () => {
|
||||
const portfolioCalculator = new PortfolioCalculator({
|
||||
currentRateService,
|
||||
exchangeRateDataService,
|
||||
currency: 'CHF',
|
||||
orders: [
|
||||
activities: <Activity[]>[
|
||||
{
|
||||
date: new Date('2015-01-01'),
|
||||
fee: 0,
|
||||
quantity: 2,
|
||||
SymbolProfile: {
|
||||
currency: 'USD',
|
||||
date: '2015-01-01',
|
||||
dataSource: 'YAHOO',
|
||||
fee: new Big(0),
|
||||
name: 'Bitcoin USD',
|
||||
quantity: new Big(2),
|
||||
symbol: 'BTCUSD',
|
||||
symbol: 'BTCUSD'
|
||||
},
|
||||
type: 'BUY',
|
||||
unitPrice: new Big(320.43)
|
||||
unitPrice: 320.43
|
||||
},
|
||||
{
|
||||
date: new Date('2017-12-31'),
|
||||
fee: 0,
|
||||
quantity: 1,
|
||||
SymbolProfile: {
|
||||
currency: 'USD',
|
||||
date: '2017-12-31',
|
||||
dataSource: 'YAHOO',
|
||||
fee: new Big(0),
|
||||
name: 'Bitcoin USD',
|
||||
quantity: new Big(1),
|
||||
symbol: 'BTCUSD',
|
||||
symbol: 'BTCUSD'
|
||||
},
|
||||
type: 'SELL',
|
||||
unitPrice: new Big(14156.4)
|
||||
unitPrice: 14156.4
|
||||
}
|
||||
]
|
||||
],
|
||||
currency: 'CHF'
|
||||
});
|
||||
|
||||
portfolioCalculator.computeTransactionPoints();
|
||||
|
||||
const spy = jest
|
||||
.spyOn(Date, 'now')
|
||||
.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 { 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';
|
||||
@ -49,24 +50,24 @@ describe('PortfolioCalculator', () => {
|
||||
const portfolioCalculator = new PortfolioCalculator({
|
||||
currentRateService,
|
||||
exchangeRateDataService,
|
||||
currency: 'CHF',
|
||||
orders: [
|
||||
activities: <Activity[]>[
|
||||
{
|
||||
date: new Date('2023-01-03'),
|
||||
fee: 1,
|
||||
quantity: 1,
|
||||
SymbolProfile: {
|
||||
currency: 'USD',
|
||||
date: '2023-01-03',
|
||||
dataSource: 'YAHOO',
|
||||
fee: new Big(1),
|
||||
name: 'Alphabet Inc.',
|
||||
quantity: new Big(1),
|
||||
symbol: 'GOOGL',
|
||||
symbol: 'GOOGL'
|
||||
},
|
||||
type: 'BUY',
|
||||
unitPrice: new Big(89.12)
|
||||
unitPrice: 89.12
|
||||
}
|
||||
]
|
||||
],
|
||||
currency: 'CHF'
|
||||
});
|
||||
|
||||
portfolioCalculator.computeTransactionPoints();
|
||||
|
||||
const spy = jest
|
||||
.spyOn(Date, 'now')
|
||||
.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 { 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';
|
||||
@ -49,35 +50,37 @@ describe('PortfolioCalculator', () => {
|
||||
const portfolioCalculator = new PortfolioCalculator({
|
||||
currentRateService,
|
||||
exchangeRateDataService,
|
||||
currency: 'USD',
|
||||
orders: [
|
||||
activities: <Activity[]>[
|
||||
{
|
||||
date: new Date('2021-09-16'),
|
||||
fee: 19,
|
||||
quantity: 1,
|
||||
SymbolProfile: {
|
||||
currency: 'USD',
|
||||
date: '2021-09-16',
|
||||
dataSource: 'YAHOO',
|
||||
fee: new Big(19),
|
||||
name: 'Microsoft Inc.',
|
||||
quantity: new Big(1),
|
||||
symbol: 'MSFT',
|
||||
symbol: 'MSFT'
|
||||
},
|
||||
type: 'BUY',
|
||||
unitPrice: new Big(298.58)
|
||||
unitPrice: 298.58
|
||||
},
|
||||
{
|
||||
date: new Date('2021-11-16'),
|
||||
fee: 0,
|
||||
quantity: 1,
|
||||
SymbolProfile: {
|
||||
currency: 'USD',
|
||||
date: '2021-11-16',
|
||||
dataSource: 'YAHOO',
|
||||
fee: new Big(0),
|
||||
name: 'Microsoft Inc.',
|
||||
quantity: new Big(1),
|
||||
symbol: 'MSFT',
|
||||
symbol: 'MSFT'
|
||||
},
|
||||
type: 'DIVIDEND',
|
||||
unitPrice: new Big(0.62)
|
||||
unitPrice: 0.62
|
||||
}
|
||||
]
|
||||
],
|
||||
currency: 'USD'
|
||||
});
|
||||
|
||||
portfolioCalculator.computeTransactionPoints();
|
||||
|
||||
const spy = jest
|
||||
.spyOn(Date, 'now')
|
||||
.mockImplementation(() => parseDate('2023-07-10').getTime());
|
||||
|
@ -37,12 +37,10 @@ describe('PortfolioCalculator', () => {
|
||||
const portfolioCalculator = new PortfolioCalculator({
|
||||
currentRateService,
|
||||
exchangeRateDataService,
|
||||
currency: 'CHF',
|
||||
orders: []
|
||||
activities: [],
|
||||
currency: 'CHF'
|
||||
});
|
||||
|
||||
portfolioCalculator.computeTransactionPoints();
|
||||
|
||||
const spy = jest
|
||||
.spyOn(Date, 'now')
|
||||
.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 { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
|
||||
import { parseDate } from '@ghostfolio/common/helper';
|
||||
@ -36,35 +37,37 @@ describe('PortfolioCalculator', () => {
|
||||
const portfolioCalculator = new PortfolioCalculator({
|
||||
currentRateService,
|
||||
exchangeRateDataService,
|
||||
currency: 'CHF',
|
||||
orders: [
|
||||
activities: <Activity[]>[
|
||||
{
|
||||
date: new Date('2022-03-07'),
|
||||
fee: 1.3,
|
||||
quantity: 2,
|
||||
SymbolProfile: {
|
||||
currency: 'CHF',
|
||||
date: '2022-03-07',
|
||||
dataSource: 'YAHOO',
|
||||
fee: new Big(1.3),
|
||||
name: 'Novartis AG',
|
||||
quantity: new Big(2),
|
||||
symbol: 'NOVN.SW',
|
||||
symbol: 'NOVN.SW'
|
||||
},
|
||||
type: 'BUY',
|
||||
unitPrice: new Big(75.8)
|
||||
unitPrice: 75.8
|
||||
},
|
||||
{
|
||||
date: new Date('2022-04-08'),
|
||||
fee: 2.95,
|
||||
quantity: 1,
|
||||
SymbolProfile: {
|
||||
currency: 'CHF',
|
||||
date: '2022-04-08',
|
||||
dataSource: 'YAHOO',
|
||||
fee: new Big(2.95),
|
||||
name: 'Novartis AG',
|
||||
quantity: new Big(1),
|
||||
symbol: 'NOVN.SW',
|
||||
symbol: 'NOVN.SW'
|
||||
},
|
||||
type: 'SELL',
|
||||
unitPrice: new Big(85.73)
|
||||
unitPrice: 85.73
|
||||
}
|
||||
]
|
||||
],
|
||||
currency: 'CHF'
|
||||
});
|
||||
|
||||
portfolioCalculator.computeTransactionPoints();
|
||||
|
||||
const spy = jest
|
||||
.spyOn(Date, 'now')
|
||||
.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 { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
|
||||
import { parseDate } from '@ghostfolio/common/helper';
|
||||
@ -36,35 +37,37 @@ describe('PortfolioCalculator', () => {
|
||||
const portfolioCalculator = new PortfolioCalculator({
|
||||
currentRateService,
|
||||
exchangeRateDataService,
|
||||
currency: 'CHF',
|
||||
orders: [
|
||||
activities: <Activity[]>[
|
||||
{
|
||||
date: new Date('2022-03-07'),
|
||||
fee: 0,
|
||||
quantity: 2,
|
||||
SymbolProfile: {
|
||||
currency: 'CHF',
|
||||
date: '2022-03-07',
|
||||
dataSource: 'YAHOO',
|
||||
fee: new Big(0),
|
||||
name: 'Novartis AG',
|
||||
quantity: new Big(2),
|
||||
symbol: 'NOVN.SW',
|
||||
symbol: 'NOVN.SW'
|
||||
},
|
||||
type: 'BUY',
|
||||
unitPrice: new Big(75.8)
|
||||
unitPrice: 75.8
|
||||
},
|
||||
{
|
||||
date: new Date('2022-04-08'),
|
||||
fee: 0,
|
||||
quantity: 2,
|
||||
SymbolProfile: {
|
||||
currency: 'CHF',
|
||||
date: '2022-04-08',
|
||||
dataSource: 'YAHOO',
|
||||
fee: new Big(0),
|
||||
name: 'Novartis AG',
|
||||
quantity: new Big(2),
|
||||
symbol: 'NOVN.SW',
|
||||
symbol: 'NOVN.SW'
|
||||
},
|
||||
type: 'SELL',
|
||||
unitPrice: new Big(85.73)
|
||||
unitPrice: 85.73
|
||||
}
|
||||
]
|
||||
],
|
||||
currency: 'CHF'
|
||||
});
|
||||
|
||||
portfolioCalculator.computeTransactionPoints();
|
||||
|
||||
const spy = jest
|
||||
.spyOn(Date, 'now')
|
||||
.mockImplementation(() => parseDate('2022-04-11').getTime());
|
||||
|
@ -22,10 +22,10 @@ describe('PortfolioCalculator', () => {
|
||||
|
||||
describe('annualized performance percentage', () => {
|
||||
const portfolioCalculator = new PortfolioCalculator({
|
||||
activities: [],
|
||||
currentRateService,
|
||||
exchangeRateDataService,
|
||||
currency: 'USD',
|
||||
orders: []
|
||||
currency: 'USD'
|
||||
});
|
||||
|
||||
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 { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
|
||||
import { IDataGatheringItem } from '@ghostfolio/api/services/interfaces/interfaces';
|
||||
@ -8,7 +9,8 @@ import {
|
||||
InvestmentItem,
|
||||
ResponseError,
|
||||
SymbolMetrics,
|
||||
TimelinePosition
|
||||
TimelinePosition,
|
||||
UniqueAsset
|
||||
} from '@ghostfolio/common/interfaces';
|
||||
import { GroupBy } from '@ghostfolio/common/types';
|
||||
|
||||
@ -46,122 +48,37 @@ export class PortfolioCalculator {
|
||||
private transactionPoints: TransactionPoint[];
|
||||
|
||||
public constructor({
|
||||
activities,
|
||||
currency,
|
||||
currentRateService,
|
||||
exchangeRateDataService,
|
||||
orders,
|
||||
transactionPoints
|
||||
exchangeRateDataService
|
||||
}: {
|
||||
activities: Activity[];
|
||||
currency: string;
|
||||
currentRateService: CurrentRateService;
|
||||
exchangeRateDataService: ExchangeRateDataService;
|
||||
orders: PortfolioOrder[];
|
||||
transactionPoints?: TransactionPoint[];
|
||||
}) {
|
||||
this.currency = currency;
|
||||
this.currentRateService = currentRateService;
|
||||
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) => {
|
||||
return a.date?.localeCompare(b.date);
|
||||
});
|
||||
|
||||
if (transactionPoints) {
|
||||
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;
|
||||
}
|
||||
this.computeTransactionPoints();
|
||||
}
|
||||
|
||||
public getAnnualizedPerformancePercent({
|
||||
@ -181,10 +98,6 @@ export class PortfolioCalculator {
|
||||
return new Big(0);
|
||||
}
|
||||
|
||||
public getTransactionPoints(): TransactionPoint[] {
|
||||
return this.transactionPoints;
|
||||
}
|
||||
|
||||
public async getChartData({
|
||||
end = new Date(Date.now()),
|
||||
start,
|
||||
@ -248,7 +161,7 @@ export class PortfolioCalculator {
|
||||
await this.exchangeRateDataService.getExchangeRatesByCurrency({
|
||||
currencies: uniq(Object.values(currencies)),
|
||||
endDate: endOfDay(end),
|
||||
startDate: parseDate(this.transactionPoints?.[0]?.date),
|
||||
startDate: this.getStartDate(),
|
||||
targetCurrency: this.currency
|
||||
});
|
||||
|
||||
@ -309,6 +222,7 @@ export class PortfolioCalculator {
|
||||
start,
|
||||
step,
|
||||
symbol,
|
||||
dataSource: null,
|
||||
exchangeRates:
|
||||
exchangeRatesByCurrency[`${currencies[symbol]}${this.currency}`],
|
||||
isChartMode: true
|
||||
@ -551,7 +465,7 @@ export class PortfolioCalculator {
|
||||
await this.exchangeRateDataService.getExchangeRatesByCurrency({
|
||||
currencies: uniq(Object.values(currencies)),
|
||||
endDate: endOfDay(endDate),
|
||||
startDate: parseDate(this.transactionPoints?.[0]?.date),
|
||||
startDate: this.getStartDate(),
|
||||
targetCurrency: this.currency
|
||||
});
|
||||
|
||||
@ -625,6 +539,7 @@ export class PortfolioCalculator {
|
||||
} = this.getSymbolMetrics({
|
||||
marketSymbolMap,
|
||||
start,
|
||||
dataSource: item.dataSource,
|
||||
end: endDate,
|
||||
exchangeRates:
|
||||
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({
|
||||
dataSource,
|
||||
end,
|
||||
exchangeRates,
|
||||
isChartMode = false,
|
||||
@ -861,8 +885,7 @@ export class PortfolioCalculator {
|
||||
};
|
||||
start: Date;
|
||||
step?: number;
|
||||
symbol: string;
|
||||
}): SymbolMetrics {
|
||||
} & UniqueAsset): SymbolMetrics {
|
||||
const currentExchangeRate = exchangeRates[format(new Date(), DATE_FORMAT)];
|
||||
const currentValues: { [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
|
||||
let orders: PortfolioOrderItem[] = cloneDeep(this.orders).filter(
|
||||
(order) => {
|
||||
return order.symbol === symbol;
|
||||
({ SymbolProfile }) => {
|
||||
return SymbolProfile.symbol === symbol;
|
||||
}
|
||||
);
|
||||
|
||||
@ -988,28 +1011,28 @@ export class PortfolioCalculator {
|
||||
|
||||
// Add a synthetic order at the start and the end date
|
||||
orders.push({
|
||||
symbol,
|
||||
currency: null,
|
||||
date: format(start, DATE_FORMAT),
|
||||
dataSource: null,
|
||||
fee: new Big(0),
|
||||
feeInBaseCurrency: new Big(0),
|
||||
itemType: 'start',
|
||||
name: '',
|
||||
quantity: new Big(0),
|
||||
SymbolProfile: {
|
||||
dataSource,
|
||||
symbol
|
||||
},
|
||||
type: 'BUY',
|
||||
unitPrice: unitPriceAtStartDate
|
||||
});
|
||||
|
||||
orders.push({
|
||||
symbol,
|
||||
currency: null,
|
||||
date: format(end, DATE_FORMAT),
|
||||
dataSource: null,
|
||||
fee: new Big(0),
|
||||
feeInBaseCurrency: new Big(0),
|
||||
itemType: 'end',
|
||||
name: '',
|
||||
SymbolProfile: {
|
||||
dataSource,
|
||||
symbol
|
||||
},
|
||||
quantity: new Big(0),
|
||||
type: 'BUY',
|
||||
unitPrice: unitPriceAtEndDate
|
||||
@ -1030,14 +1053,14 @@ export class PortfolioCalculator {
|
||||
|
||||
if (!hasDate) {
|
||||
orders.push({
|
||||
symbol,
|
||||
currency: null,
|
||||
date: format(day, DATE_FORMAT),
|
||||
dataSource: null,
|
||||
fee: new Big(0),
|
||||
feeInBaseCurrency: new Big(0),
|
||||
name: '',
|
||||
quantity: new Big(0),
|
||||
SymbolProfile: {
|
||||
dataSource,
|
||||
symbol
|
||||
},
|
||||
type: 'BUY',
|
||||
unitPrice:
|
||||
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 { OrderService } from '@ghostfolio/api/app/order/order.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 { getFactor } from '@ghostfolio/api/helper/portfolio.helper';
|
||||
import { AccountClusterRiskCurrentInvestment } from '@ghostfolio/api/models/rules/account-cluster-risk/current-investment';
|
||||
@ -266,15 +264,15 @@ export class PortfolioService {
|
||||
}): Promise<PortfolioInvestments> {
|
||||
const userId = await this.getUserId(impersonationId, this.request.user.id);
|
||||
|
||||
const { portfolioOrders, transactionPoints } =
|
||||
await this.getTransactionPoints({
|
||||
const { activities } = await this.orderService.getOrders({
|
||||
filters,
|
||||
userId,
|
||||
includeDrafts: true,
|
||||
types: ['BUY', 'SELL']
|
||||
types: ['BUY', 'SELL'],
|
||||
userCurrency: this.getUserCurrency()
|
||||
});
|
||||
|
||||
if (transactionPoints.length === 0) {
|
||||
if (activities.length === 0) {
|
||||
return {
|
||||
investments: [],
|
||||
streaks: { currentStreak: 0, longestStreak: 0 }
|
||||
@ -282,18 +280,16 @@ export class PortfolioService {
|
||||
}
|
||||
|
||||
const portfolioCalculator = new PortfolioCalculator({
|
||||
transactionPoints,
|
||||
activities,
|
||||
currency: this.request.user.Settings.settings.baseCurrency,
|
||||
currentRateService: this.currentRateService,
|
||||
exchangeRateDataService: this.exchangeRateDataService,
|
||||
orders: portfolioOrders
|
||||
exchangeRateDataService: this.exchangeRateDataService
|
||||
});
|
||||
|
||||
const { items } = await this.getChart({
|
||||
dateRange,
|
||||
impersonationId,
|
||||
portfolioCalculator,
|
||||
transactionPoints,
|
||||
userId,
|
||||
withDataDecimation: false
|
||||
});
|
||||
@ -364,26 +360,25 @@ export class PortfolioService {
|
||||
});
|
||||
}
|
||||
|
||||
const { activities, portfolioOrders, transactionPoints } =
|
||||
await this.getTransactionPoints({
|
||||
const { activities } = await this.orderService.getOrders({
|
||||
filters,
|
||||
types,
|
||||
userCurrency,
|
||||
userId,
|
||||
withExcludedAccounts
|
||||
});
|
||||
|
||||
const portfolioCalculator = new PortfolioCalculator({
|
||||
transactionPoints,
|
||||
activities,
|
||||
currency: userCurrency,
|
||||
currentRateService: this.currentRateService,
|
||||
exchangeRateDataService: this.exchangeRateDataService,
|
||||
orders: portfolioOrders
|
||||
exchangeRateDataService: this.exchangeRateDataService
|
||||
});
|
||||
|
||||
const portfolioStart = parseDate(
|
||||
transactionPoints[0]?.date ?? format(new Date(), DATE_FORMAT)
|
||||
const startDate = this.getStartDate(
|
||||
dateRange,
|
||||
portfolioCalculator.getStartDate()
|
||||
);
|
||||
const startDate = this.getStartDate(dateRange, portfolioStart);
|
||||
const currentPositions =
|
||||
await portfolioCalculator.getCurrentPositions(startDate);
|
||||
|
||||
@ -737,39 +732,22 @@ export class PortfolioService {
|
||||
{ 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');
|
||||
|
||||
const portfolioCalculator = new PortfolioCalculator({
|
||||
activities: orders.filter((order) => {
|
||||
tags = tags.concat(order.tags);
|
||||
|
||||
return ['BUY', 'DIVIDEND', 'ITEM', 'SELL'].includes(order.type);
|
||||
}),
|
||||
currency: userCurrency,
|
||||
currentRateService: this.currentRateService,
|
||||
exchangeRateDataService: this.exchangeRateDataService,
|
||||
orders: portfolioOrders
|
||||
exchangeRateDataService: this.exchangeRateDataService
|
||||
});
|
||||
|
||||
portfolioCalculator.computeTransactionPoints();
|
||||
const portfolioStart = portfolioCalculator.getStartDate();
|
||||
const transactionPoints = portfolioCalculator.getTransactionPoints();
|
||||
|
||||
const portfolioStart = parseDate(transactionPoints[0].date);
|
||||
|
||||
const currentPositions =
|
||||
await portfolioCalculator.getCurrentPositions(portfolioStart);
|
||||
|
||||
@ -982,14 +960,14 @@ export class PortfolioService {
|
||||
const userId = await this.getUserId(impersonationId, this.request.user.id);
|
||||
const user = await this.userService.user({ id: userId });
|
||||
|
||||
const { portfolioOrders, transactionPoints } =
|
||||
await this.getTransactionPoints({
|
||||
const { activities } = await this.orderService.getOrders({
|
||||
filters,
|
||||
userId,
|
||||
types: ['BUY', 'SELL']
|
||||
types: ['BUY', 'SELL'],
|
||||
userCurrency: this.getUserCurrency()
|
||||
});
|
||||
|
||||
if (transactionPoints?.length <= 0) {
|
||||
if (activities?.length <= 0) {
|
||||
return {
|
||||
hasErrors: false,
|
||||
positions: []
|
||||
@ -997,15 +975,16 @@ export class PortfolioService {
|
||||
}
|
||||
|
||||
const portfolioCalculator = new PortfolioCalculator({
|
||||
transactionPoints,
|
||||
activities,
|
||||
currency: this.request.user.Settings.settings.baseCurrency,
|
||||
currentRateService: this.currentRateService,
|
||||
exchangeRateDataService: this.exchangeRateDataService,
|
||||
orders: portfolioOrders
|
||||
exchangeRateDataService: this.exchangeRateDataService
|
||||
});
|
||||
|
||||
const portfolioStart = parseDate(transactionPoints[0].date);
|
||||
const startDate = this.getStartDate(dateRange, portfolioStart);
|
||||
const startDate = this.getStartDate(
|
||||
dateRange,
|
||||
portfolioCalculator.getStartDate()
|
||||
);
|
||||
const currentPositions =
|
||||
await portfolioCalculator.getCurrentPositions(startDate);
|
||||
|
||||
@ -1154,15 +1133,15 @@ export class PortfolioService {
|
||||
)
|
||||
);
|
||||
|
||||
const { portfolioOrders, transactionPoints } =
|
||||
await this.getTransactionPoints({
|
||||
const { activities } = await this.orderService.getOrders({
|
||||
filters,
|
||||
userCurrency,
|
||||
userId,
|
||||
withExcludedAccounts,
|
||||
types: withItems ? ['BUY', 'ITEM', 'SELL'] : ['BUY', 'SELL']
|
||||
});
|
||||
|
||||
if (accountBalanceItems?.length <= 0 && transactionPoints?.length <= 0) {
|
||||
if (accountBalanceItems?.length <= 0 && activities?.length <= 0) {
|
||||
return {
|
||||
chart: [],
|
||||
firstOrderDate: undefined,
|
||||
@ -1184,17 +1163,16 @@ export class PortfolioService {
|
||||
}
|
||||
|
||||
const portfolioCalculator = new PortfolioCalculator({
|
||||
transactionPoints,
|
||||
activities,
|
||||
currency: userCurrency,
|
||||
currentRateService: this.currentRateService,
|
||||
exchangeRateDataService: this.exchangeRateDataService,
|
||||
orders: portfolioOrders
|
||||
exchangeRateDataService: this.exchangeRateDataService
|
||||
});
|
||||
|
||||
const portfolioStart = min(
|
||||
[
|
||||
parseDate(accountBalanceItems[0]?.date),
|
||||
parseDate(transactionPoints[0]?.date)
|
||||
portfolioCalculator.getStartDate()
|
||||
].filter((date) => {
|
||||
return isValid(date);
|
||||
})
|
||||
@ -1230,7 +1208,6 @@ export class PortfolioService {
|
||||
dateRange,
|
||||
impersonationId,
|
||||
portfolioCalculator,
|
||||
transactionPoints,
|
||||
userId
|
||||
});
|
||||
|
||||
@ -1307,25 +1284,22 @@ export class PortfolioService {
|
||||
const user = await this.userService.user({ id: userId });
|
||||
const userCurrency = this.getUserCurrency(user);
|
||||
|
||||
const { activities, portfolioOrders, transactionPoints } =
|
||||
await this.getTransactionPoints({
|
||||
const { activities } = await this.orderService.getOrders({
|
||||
userCurrency,
|
||||
userId,
|
||||
types: ['BUY', 'SELL']
|
||||
});
|
||||
|
||||
const portfolioCalculator = new PortfolioCalculator({
|
||||
transactionPoints,
|
||||
activities,
|
||||
currency: userCurrency,
|
||||
currentRateService: this.currentRateService,
|
||||
exchangeRateDataService: this.exchangeRateDataService,
|
||||
orders: portfolioOrders
|
||||
exchangeRateDataService: this.exchangeRateDataService
|
||||
});
|
||||
|
||||
const portfolioStart = parseDate(
|
||||
transactionPoints[0]?.date ?? format(new Date(), DATE_FORMAT)
|
||||
const currentPositions = await portfolioCalculator.getCurrentPositions(
|
||||
portfolioCalculator.getStartDate()
|
||||
);
|
||||
const currentPositions =
|
||||
await portfolioCalculator.getCurrentPositions(portfolioStart);
|
||||
|
||||
const positions = currentPositions.positions.filter(
|
||||
(item) => !item.quantity.eq(0)
|
||||
@ -1455,18 +1429,16 @@ export class PortfolioService {
|
||||
dateRange = 'max',
|
||||
impersonationId,
|
||||
portfolioCalculator,
|
||||
transactionPoints,
|
||||
userId,
|
||||
withDataDecimation = true
|
||||
}: {
|
||||
dateRange?: DateRange;
|
||||
impersonationId: string;
|
||||
portfolioCalculator: PortfolioCalculator;
|
||||
transactionPoints: TransactionPoint[];
|
||||
userId: string;
|
||||
withDataDecimation?: boolean;
|
||||
}): Promise<HistoricalDataContainer> {
|
||||
if (transactionPoints.length === 0) {
|
||||
if (portfolioCalculator.getTransactionPoints().length === 0) {
|
||||
return {
|
||||
isAllTimeHigh: false,
|
||||
isAllTimeLow: false,
|
||||
@ -1476,8 +1448,10 @@ export class PortfolioService {
|
||||
|
||||
userId = await this.getUserId(impersonationId, userId);
|
||||
|
||||
const portfolioStart = parseDate(transactionPoints[0].date);
|
||||
const startDate = this.getStartDate(dateRange, portfolioStart);
|
||||
const startDate = this.getStartDate(
|
||||
dateRange,
|
||||
portfolioCalculator.getStartDate()
|
||||
);
|
||||
const endDate = new Date();
|
||||
const daysInMarket = differenceInDays(endDate, startDate) + 1;
|
||||
const step = withDataDecimation
|
||||
@ -1865,10 +1839,10 @@ export class PortfolioService {
|
||||
const daysInMarket = differenceInDays(new Date(), firstOrderDate);
|
||||
|
||||
const annualizedPerformancePercent = new PortfolioCalculator({
|
||||
activities: [],
|
||||
currency: userCurrency,
|
||||
currentRateService: this.currentRateService,
|
||||
exchangeRateDataService: this.exchangeRateDataService,
|
||||
orders: []
|
||||
exchangeRateDataService: this.exchangeRateDataService
|
||||
})
|
||||
.getAnnualizedPerformancePercent({
|
||||
daysInMarket,
|
||||
@ -1880,10 +1854,10 @@ export class PortfolioService {
|
||||
|
||||
const annualizedPerformancePercentWithCurrencyEffect =
|
||||
new PortfolioCalculator({
|
||||
activities: [],
|
||||
currency: userCurrency,
|
||||
currentRateService: this.currentRateService,
|
||||
exchangeRateDataService: this.exchangeRateDataService,
|
||||
orders: []
|
||||
exchangeRateDataService: this.exchangeRateDataService
|
||||
})
|
||||
.getAnnualizedPerformancePercent({
|
||||
daysInMarket,
|
||||
@ -1955,71 +1929,9 @@ export class PortfolioService {
|
||||
);
|
||||
}
|
||||
|
||||
private async getTransactionPoints({
|
||||
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) {
|
||||
private getUserCurrency(aUser?: UserWithSettings) {
|
||||
return (
|
||||
aUser.Settings?.settings.baseCurrency ??
|
||||
aUser?.Settings?.settings.baseCurrency ??
|
||||
this.request.user?.Settings?.settings.baseCurrency ??
|
||||
DEFAULT_CURRENCY
|
||||
);
|
||||
|
Loading…
x
Reference in New Issue
Block a user