Browse Source

Refactor portfolio calculator

* Consume Activity[]
* Change computeTransactionPoints() to private
pull/3203/head
Thomas Kaul 1 year ago
parent
commit
82c41da81a
  1. 15
      apps/api/src/app/portfolio/interfaces/portfolio-order.interface.ts
  2. 63
      apps/api/src/app/portfolio/portfolio-calculator-baln-buy-and-sell-in-two-activities.spec.ts
  3. 45
      apps/api/src/app/portfolio/portfolio-calculator-baln-buy-and-sell.spec.ts
  4. 27
      apps/api/src/app/portfolio/portfolio-calculator-baln-buy.spec.ts
  5. 45
      apps/api/src/app/portfolio/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts
  6. 27
      apps/api/src/app/portfolio/portfolio-calculator-googl-buy.spec.ts
  7. 45
      apps/api/src/app/portfolio/portfolio-calculator-msft-buy-with-dividend.spec.ts
  8. 6
      apps/api/src/app/portfolio/portfolio-calculator-no-orders.spec.ts
  9. 45
      apps/api/src/app/portfolio/portfolio-calculator-novn-buy-and-sell-partially.spec.ts
  10. 45
      apps/api/src/app/portfolio/portfolio-calculator-novn-buy-and-sell.spec.ts
  11. 4
      apps/api/src/app/portfolio/portfolio-calculator.spec.ts
  12. 84
      apps/api/src/app/portfolio/portfolio-calculator.ts
  13. 138
      apps/api/src/app/portfolio/portfolio.service.ts

15
apps/api/src/app/portfolio/interfaces/portfolio-order.interface.ts

@ -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;
}

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

@ -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[]>[
{
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',
date: new Date('2021-11-22'),
fee: 1.55,
quantity: 2,
SymbolProfile: {
currency: 'CHF',
dataSource: 'YAHOO',
name: 'Bâloise Holding AG',
symbol: 'BALN.SW'
},
type: 'BUY',
unitPrice: new Big(142.9)
unitPrice: 142.9
},
{
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',
date: new Date('2021-11-30'),
fee: 1.65,
quantity: 1,
SymbolProfile: {
currency: 'CHF',
dataSource: 'YAHOO',
name: 'Bâloise Holding AG',
symbol: 'BALN.SW'
},
type: 'SELL',
unitPrice: new Big(136.6)
unitPrice: 136.6
},
{
currency: 'CHF',
date: '2021-11-30',
dataSource: 'YAHOO',
fee: new Big(0),
name: 'Bâloise Holding AG',
quantity: new Big(1),
symbol: 'BALN.SW',
date: new Date('2021-11-30'),
fee: 0,
quantity: 1,
SymbolProfile: {
currency: 'CHF',
dataSource: 'YAHOO',
name: 'Bâloise Holding AG',
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());

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

@ -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[]>[
{
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',
date: new Date('2021-11-22'),
fee: 1.55,
quantity: 2,
SymbolProfile: {
currency: 'CHF',
dataSource: 'YAHOO',
name: 'Bâloise Holding AG',
symbol: 'BALN.SW'
},
type: 'BUY',
unitPrice: new Big(142.9)
unitPrice: 142.9
},
{
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',
date: new Date('2021-11-30'),
fee: 1.65,
quantity: 2,
SymbolProfile: {
currency: 'CHF',
dataSource: 'YAHOO',
name: 'Bâloise Holding AG',
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());

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

@ -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[]>[
{
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',
date: new Date('2021-11-30'),
fee: 1.55,
quantity: 2,
SymbolProfile: {
currency: 'CHF',
dataSource: 'YAHOO',
name: 'Bâloise Holding AG',
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());

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

@ -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[]>[
{
currency: 'USD',
date: '2015-01-01',
dataSource: 'YAHOO',
fee: new Big(0),
name: 'Bitcoin USD',
quantity: new Big(2),
symbol: 'BTCUSD',
date: new Date('2015-01-01'),
fee: 0,
quantity: 2,
SymbolProfile: {
currency: 'USD',
dataSource: 'YAHOO',
name: 'Bitcoin USD',
symbol: 'BTCUSD'
},
type: 'BUY',
unitPrice: new Big(320.43)
unitPrice: 320.43
},
{
currency: 'USD',
date: '2017-12-31',
dataSource: 'YAHOO',
fee: new Big(0),
name: 'Bitcoin USD',
quantity: new Big(1),
symbol: 'BTCUSD',
date: new Date('2017-12-31'),
fee: 0,
quantity: 1,
SymbolProfile: {
currency: 'USD',
dataSource: 'YAHOO',
name: 'Bitcoin USD',
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());

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

@ -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[]>[
{
currency: 'USD',
date: '2023-01-03',
dataSource: 'YAHOO',
fee: new Big(1),
name: 'Alphabet Inc.',
quantity: new Big(1),
symbol: 'GOOGL',
date: new Date('2023-01-03'),
fee: 1,
quantity: 1,
SymbolProfile: {
currency: 'USD',
dataSource: 'YAHOO',
name: 'Alphabet Inc.',
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());

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

@ -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[]>[
{
currency: 'USD',
date: '2021-09-16',
dataSource: 'YAHOO',
fee: new Big(19),
name: 'Microsoft Inc.',
quantity: new Big(1),
symbol: 'MSFT',
date: new Date('2021-09-16'),
fee: 19,
quantity: 1,
SymbolProfile: {
currency: 'USD',
dataSource: 'YAHOO',
name: 'Microsoft Inc.',
symbol: 'MSFT'
},
type: 'BUY',
unitPrice: new Big(298.58)
unitPrice: 298.58
},
{
currency: 'USD',
date: '2021-11-16',
dataSource: 'YAHOO',
fee: new Big(0),
name: 'Microsoft Inc.',
quantity: new Big(1),
symbol: 'MSFT',
date: new Date('2021-11-16'),
fee: 0,
quantity: 1,
SymbolProfile: {
currency: 'USD',
dataSource: 'YAHOO',
name: 'Microsoft Inc.',
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());

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

@ -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());

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

@ -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[]>[
{
currency: 'CHF',
date: '2022-03-07',
dataSource: 'YAHOO',
fee: new Big(1.3),
name: 'Novartis AG',
quantity: new Big(2),
symbol: 'NOVN.SW',
date: new Date('2022-03-07'),
fee: 1.3,
quantity: 2,
SymbolProfile: {
currency: 'CHF',
dataSource: 'YAHOO',
name: 'Novartis AG',
symbol: 'NOVN.SW'
},
type: 'BUY',
unitPrice: new Big(75.8)
unitPrice: 75.8
},
{
currency: 'CHF',
date: '2022-04-08',
dataSource: 'YAHOO',
fee: new Big(2.95),
name: 'Novartis AG',
quantity: new Big(1),
symbol: 'NOVN.SW',
date: new Date('2022-04-08'),
fee: 2.95,
quantity: 1,
SymbolProfile: {
currency: 'CHF',
dataSource: 'YAHOO',
name: 'Novartis AG',
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());

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

@ -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[]>[
{
currency: 'CHF',
date: '2022-03-07',
dataSource: 'YAHOO',
fee: new Big(0),
name: 'Novartis AG',
quantity: new Big(2),
symbol: 'NOVN.SW',
date: new Date('2022-03-07'),
fee: 0,
quantity: 2,
SymbolProfile: {
currency: 'CHF',
dataSource: 'YAHOO',
name: 'Novartis AG',
symbol: 'NOVN.SW'
},
type: 'BUY',
unitPrice: new Big(75.8)
unitPrice: 75.8
},
{
currency: 'CHF',
date: '2022-04-08',
dataSource: 'YAHOO',
fee: new Big(0),
name: 'Novartis AG',
quantity: new Big(2),
symbol: 'NOVN.SW',
date: new Date('2022-04-08'),
fee: 0,
quantity: 2,
SymbolProfile: {
currency: 'CHF',
dataSource: 'YAHOO',
name: 'Novartis AG',
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());

4
apps/api/src/app/portfolio/portfolio-calculator.spec.ts

@ -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 () => {

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

@ -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,33 +48,40 @@ 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;
}
this.computeTransactionPoints();
}
public computeTransactionPoints() {
private computeTransactionPoints() {
this.transactionPoints = [];
const symbols: { [symbol: string]: TransactionPointSymbol } = {};
@ -83,7 +92,7 @@ export class PortfolioCalculator {
const currentDate = order.date;
let currentTransactionPointItem: TransactionPointSymbol;
const oldAccumulatedSymbol = symbols[order.symbol];
const oldAccumulatedSymbol = symbols[order.SymbolProfile.symbol];
const factor = getFactor(order.type);
@ -109,38 +118,39 @@ export class PortfolioCalculator {
averagePrice: newQuantity.gt(0)
? investment.div(newQuantity)
: new Big(0),
currency: order.currency,
dataSource: order.dataSource,
currency: order.SymbolProfile.currency,
dataSource: order.SymbolProfile.dataSource,
dividend: new Big(0),
fee: order.fee.plus(oldAccumulatedSymbol.fee),
firstBuyDate: oldAccumulatedSymbol.firstBuyDate,
quantity: newQuantity,
symbol: order.symbol,
symbol: order.SymbolProfile.symbol,
tags: order.tags,
transactionCount: oldAccumulatedSymbol.transactionCount + 1
};
} else {
currentTransactionPointItem = {
averagePrice: order.unitPrice,
currency: order.currency,
dataSource: order.dataSource,
currency: order.SymbolProfile.currency,
dataSource: order.SymbolProfile.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,
symbol: order.SymbolProfile.symbol,
tags: order.tags,
transactionCount: 1
};
}
symbols[order.symbol] = currentTransactionPointItem;
symbols[order.SymbolProfile.symbol] = currentTransactionPointItem;
const items = lastTransactionPoint?.items ?? [];
const newItems = items.filter(
(transactionPointItem) => transactionPointItem.symbol !== order.symbol
(transactionPointItem) =>
transactionPointItem.symbol !== order.SymbolProfile.symbol
);
newItems.push(currentTransactionPointItem);
@ -309,6 +319,7 @@ export class PortfolioCalculator {
start,
step,
symbol,
dataSource: null,
exchangeRates:
exchangeRatesByCurrency[`${currencies[symbol]}${this.currency}`],
isChartMode: true
@ -625,6 +636,7 @@ export class PortfolioCalculator {
} = this.getSymbolMetrics({
marketSymbolMap,
start,
dataSource: item.dataSource,
end: endDate,
exchangeRates:
exchangeRatesByCurrency[`${item.currency}${this.currency}`],
@ -845,6 +857,7 @@ export class PortfolioCalculator {
}
private getSymbolMetrics({
dataSource,
end,
exchangeRates,
isChartMode = false,
@ -861,8 +874,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 +920,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 +1000,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 +1042,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] ??

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

@ -266,13 +266,12 @@ export class PortfolioService {
}): Promise<PortfolioInvestments> {
const userId = await this.getUserId(impersonationId, this.request.user.id);
const { portfolioOrders, transactionPoints } =
await this.getTransactionPoints({
filters,
userId,
includeDrafts: true,
types: ['BUY', 'SELL']
});
const { activities, transactionPoints } = await this.getTransactionPoints({
filters,
userId,
includeDrafts: true,
types: ['BUY', 'SELL']
});
if (transactionPoints.length === 0) {
return {
@ -282,11 +281,10 @@ 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({
@ -364,20 +362,18 @@ export class PortfolioService {
});
}
const { activities, portfolioOrders, transactionPoints } =
await this.getTransactionPoints({
filters,
types,
userId,
withExcludedAccounts
});
const { activities, transactionPoints } = await this.getTransactionPoints({
filters,
types,
userId,
withExcludedAccounts
});
const portfolioCalculator = new PortfolioCalculator({
transactionPoints,
activities,
currency: userCurrency,
currentRateService: this.currentRateService,
exchangeRateDataService: this.exchangeRateDataService,
orders: portfolioOrders
exchangeRateDataService: this.exchangeRateDataService
});
const portfolioStart = parseDate(
@ -737,35 +733,19 @@ 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 transactionPoints = portfolioCalculator.getTransactionPoints();
const portfolioStart = parseDate(transactionPoints[0].date);
@ -982,12 +962,11 @@ 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({
filters,
userId,
types: ['BUY', 'SELL']
});
const { activities, transactionPoints } = await this.getTransactionPoints({
filters,
userId,
types: ['BUY', 'SELL']
});
if (transactionPoints?.length <= 0) {
return {
@ -997,11 +976,10 @@ 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);
@ -1154,13 +1132,12 @@ export class PortfolioService {
)
);
const { portfolioOrders, transactionPoints } =
await this.getTransactionPoints({
filters,
userId,
withExcludedAccounts,
types: withItems ? ['BUY', 'ITEM', 'SELL'] : ['BUY', 'SELL']
});
const { activities, transactionPoints } = await this.getTransactionPoints({
filters,
userId,
withExcludedAccounts,
types: withItems ? ['BUY', 'ITEM', 'SELL'] : ['BUY', 'SELL']
});
if (accountBalanceItems?.length <= 0 && transactionPoints?.length <= 0) {
return {
@ -1184,11 +1161,10 @@ 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(
@ -1307,18 +1283,16 @@ export class PortfolioService {
const user = await this.userService.user({ id: userId });
const userCurrency = this.getUserCurrency(user);
const { activities, portfolioOrders, transactionPoints } =
await this.getTransactionPoints({
userId,
types: ['BUY', 'SELL']
});
const { activities, transactionPoints } = await this.getTransactionPoints({
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(
@ -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,6 +1929,7 @@ export class PortfolioService {
);
}
// TODO: Eliminate
private async getTransactionPoints({
filters,
includeDrafts = false,
@ -1989,27 +1964,26 @@ export class PortfolioService {
}
const portfolioOrders: PortfolioOrder[] = activities.map((order) => ({
currency: order.SymbolProfile.currency,
dataSource: order.SymbolProfile.dataSource,
// currency: order.SymbolProfile.currency,
// dataSource: order.SymbolProfile.dataSource,
date: format(order.date, DATE_FORMAT),
fee: new Big(order.fee),
name: order.SymbolProfile?.name,
// name: order.SymbolProfile?.name,
quantity: new Big(order.quantity),
symbol: order.SymbolProfile.symbol,
tags: order.tags,
// symbol: order.SymbolProfile.symbol,
SymbolProfile: order.SymbolProfile,
// tags: order.tags,
type: order.type,
unitPrice: new Big(order.unitPrice)
}));
const portfolioCalculator = new PortfolioCalculator({
activities,
currency: userCurrency,
currentRateService: this.currentRateService,
exchangeRateDataService: this.exchangeRateDataService,
orders: portfolioOrders
exchangeRateDataService: this.exchangeRateDataService
});
portfolioCalculator.computeTransactionPoints();
return {
activities,
portfolioOrders,

Loading…
Cancel
Save