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

867 lines
27 KiB
TypeScript
Raw Normal View History

import { AccountService } from '@ghostfolio/api/app/account/account.service';
import { CashDetails } from '@ghostfolio/api/app/account/interfaces/cash-details.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 { TimelineSpecification } from '@ghostfolio/api/app/portfolio/interfaces/timeline-specification.interface';
import { TransactionPoint } from '@ghostfolio/api/app/portfolio/interfaces/transaction-point.interface';
import { PortfolioCalculator } from '@ghostfolio/api/app/portfolio/portfolio-calculator';
2021-07-20 22:52:50 +02:00
import { OrderType } from '@ghostfolio/api/models/order-type';
2021-08-01 09:41:44 +02:00
import { AccountClusterRiskCurrentInvestment } from '@ghostfolio/api/models/rules/account-cluster-risk/current-investment';
import { AccountClusterRiskInitialInvestment } from '@ghostfolio/api/models/rules/account-cluster-risk/initial-investment';
import { AccountClusterRiskSingleAccount } from '@ghostfolio/api/models/rules/account-cluster-risk/single-account';
import { CurrencyClusterRiskBaseCurrencyCurrentInvestment } from '@ghostfolio/api/models/rules/currency-cluster-risk/base-currency-current-investment';
import { CurrencyClusterRiskBaseCurrencyInitialInvestment } from '@ghostfolio/api/models/rules/currency-cluster-risk/base-currency-initial-investment';
import { CurrencyClusterRiskCurrentInvestment } from '@ghostfolio/api/models/rules/currency-cluster-risk/current-investment';
import { CurrencyClusterRiskInitialInvestment } from '@ghostfolio/api/models/rules/currency-cluster-risk/initial-investment';
import { FeeRatioInitialInvestment } from '@ghostfolio/api/models/rules/fees/fee-ratio-initial-investment';
import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data.service';
import { ImpersonationService } from '@ghostfolio/api/services/impersonation.service';
import { MarketState } from '@ghostfolio/api/services/interfaces/interfaces';
2021-08-01 09:41:44 +02:00
import { EnhancedSymbolProfile } from '@ghostfolio/api/services/interfaces/symbol-profile.interface';
import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile.service';
import { UNKNOWN_KEY, ghostfolioCashSymbol } from '@ghostfolio/common/config';
2021-07-28 16:11:19 +02:00
import { DATE_FORMAT, parseDate } from '@ghostfolio/common/helper';
import {
PortfolioPerformance,
PortfolioPosition,
PortfolioReport,
PortfolioSummary,
Position,
TimelinePosition
} from '@ghostfolio/common/interfaces';
2021-08-01 09:41:44 +02:00
import { InvestmentItem } from '@ghostfolio/common/interfaces/investment-item.interface';
import {
DateRange,
OrderWithAccount,
RequestWithUser
} from '@ghostfolio/common/types';
2021-04-13 21:53:58 +02:00
import { Inject, Injectable } from '@nestjs/common';
import { REQUEST } from '@nestjs/core';
import {
AssetClass,
Currency,
DataSource,
Type as TypeOfOrder
} from '@prisma/client';
2021-07-09 21:35:06 +02:00
import Big from 'big.js';
2021-04-13 21:53:58 +02:00
import {
endOfToday,
2021-04-13 21:53:58 +02:00
format,
isAfter,
isBefore,
max,
parse,
2021-04-13 21:53:58 +02:00
parseISO,
setDayOfYear,
subDays,
subYears
2021-04-13 21:53:58 +02:00
} from 'date-fns';
import { isEmpty } from 'lodash';
import {
HistoricalDataItem,
PortfolioPositionDetail
} from './interfaces/portfolio-position-detail.interface';
import { RulesService } from './rules.service';
2021-04-13 21:53:58 +02:00
@Injectable()
export class PortfolioService {
public constructor(
private readonly accountService: AccountService,
2021-07-28 16:11:19 +02:00
private readonly currentRateService: CurrentRateService,
2021-04-13 21:53:58 +02:00
private readonly dataProviderService: DataProviderService,
private readonly exchangeRateDataService: ExchangeRateDataService,
private readonly impersonationService: ImpersonationService,
private readonly orderService: OrderService,
@Inject(REQUEST) private readonly request: RequestWithUser,
private readonly rulesService: RulesService,
private readonly symbolProfileService: SymbolProfileService
2021-04-13 21:53:58 +02:00
) {}
public async getInvestments(
aImpersonationId: string
): Promise<InvestmentItem[]> {
const userId = await this.getUserId(aImpersonationId);
2021-04-13 21:53:58 +02:00
const portfolioCalculator = new PortfolioCalculator(
this.currentRateService,
this.request.user.Settings.currency
);
const { transactionPoints } = await this.getTransactionPoints({
userId,
includeDrafts: true
});
portfolioCalculator.setTransactionPoints(transactionPoints);
if (transactionPoints.length === 0) {
return [];
2021-04-13 21:53:58 +02:00
}
return portfolioCalculator.getInvestments().map((item) => {
return {
date: item.date,
investment: item.investment.toNumber()
};
});
2021-04-13 21:53:58 +02:00
}
public async getChart(
aImpersonationId: string,
aDateRange: DateRange = 'max'
): Promise<HistoricalDataItem[]> {
const userId = await this.getUserId(aImpersonationId);
2021-04-13 21:53:58 +02:00
const portfolioCalculator = new PortfolioCalculator(
this.currentRateService,
this.request.user.Settings.currency
);
const { transactionPoints } = await this.getTransactionPoints({ userId });
2021-07-20 22:52:50 +02:00
portfolioCalculator.setTransactionPoints(transactionPoints);
if (transactionPoints.length === 0) {
return [];
}
let portfolioStart = parse(
transactionPoints[0].date,
2021-07-28 16:11:19 +02:00
DATE_FORMAT,
new Date()
);
portfolioStart = this.getStartDate(aDateRange, portfolioStart);
const timelineSpecification: TimelineSpecification[] = [
{
2021-07-28 16:11:19 +02:00
start: format(portfolioStart, DATE_FORMAT),
accuracy: 'day'
}
];
const timeline = await portfolioCalculator.calculateTimeline(
timelineSpecification,
2021-07-28 16:11:19 +02:00
format(new Date(), DATE_FORMAT)
);
2021-07-13 22:51:32 +02:00
return timeline
.filter((timelineItem) => timelineItem !== null)
.map((timelineItem) => ({
date: timelineItem.date,
2021-07-28 16:11:19 +02:00
marketPrice: timelineItem.value,
value: timelineItem.grossPerformance.toNumber()
2021-07-13 22:51:32 +02:00
}));
}
public async getDetails(
aImpersonationId: string,
aDateRange: DateRange = 'max'
): Promise<{ [symbol: string]: PortfolioPosition }> {
const userId = await this.getUserId(aImpersonationId);
const userCurrency = this.request.user.Settings.currency;
const portfolioCalculator = new PortfolioCalculator(
this.currentRateService,
userCurrency
);
const { orders, transactionPoints } = await this.getTransactionPoints({
userId
});
if (transactionPoints?.length <= 0) {
return {};
}
portfolioCalculator.setTransactionPoints(transactionPoints);
const portfolioStart = parseDate(transactionPoints[0].date);
const startDate = this.getStartDate(aDateRange, portfolioStart);
const currentPositions = await portfolioCalculator.getCurrentPositions(
startDate
);
if (currentPositions.hasErrors) {
throw new Error('Missing information');
}
const cashDetails = await this.accountService.getCashDetails(
userId,
userCurrency
);
const result: { [symbol: string]: PortfolioPosition } = {};
const totalInvestment = currentPositions.totalInvestment.plus(
cashDetails.balance
);
const totalValue = currentPositions.currentValue.plus(cashDetails.balance);
const symbols = currentPositions.positions.map(
(position) => position.symbol
);
const [dataProviderResponses, symbolProfiles] = await Promise.all([
this.dataProviderService.get(symbols),
this.symbolProfileService.getSymbolProfiles(symbols)
]);
const symbolProfileMap: { [symbol: string]: EnhancedSymbolProfile } = {};
for (const symbolProfile of symbolProfiles) {
symbolProfileMap[symbolProfile.symbol] = symbolProfile;
}
const portfolioItemsNow: { [symbol: string]: TimelinePosition } = {};
for (const position of currentPositions.positions) {
portfolioItemsNow[position.symbol] = position;
}
const accounts = this.getAccounts(orders, portfolioItemsNow, userCurrency);
for (const item of currentPositions.positions) {
const value = item.quantity.mul(item.marketPrice);
const symbolProfile = symbolProfileMap[item.symbol];
const dataProviderResponse = dataProviderResponses[item.symbol];
result[item.symbol] = {
accounts,
allocationCurrent: value.div(totalValue).toNumber(),
allocationInvestment: item.investment.div(totalInvestment).toNumber(),
assetClass: symbolProfile.assetClass,
countries: symbolProfile.countries,
currency: item.currency,
exchange: dataProviderResponse.exchange,
grossPerformance: item.grossPerformance.toNumber(),
grossPerformancePercent: item.grossPerformancePercentage.toNumber(),
investment: item.investment.toNumber(),
marketPrice: item.marketPrice,
marketState: dataProviderResponse.marketState,
name: symbolProfile.name,
quantity: item.quantity.toNumber(),
sectors: symbolProfile.sectors,
symbol: item.symbol,
transactionCount: item.transactionCount,
value: value.toNumber()
};
}
// TODO: Add a cash position for each currency
result[ghostfolioCashSymbol] = await this.getCashPosition({
cashDetails,
investment: totalInvestment,
value: totalValue
});
return result;
}
2021-04-13 21:53:58 +02:00
public async getPosition(
aImpersonationId: string,
aSymbol: string
): Promise<PortfolioPositionDetail> {
const userId = await this.getUserId(aImpersonationId);
2021-04-13 21:53:58 +02:00
const orders = (await this.orderService.getOrders({ userId })).filter(
(order) => order.symbol === aSymbol
);
if (orders.length <= 0) {
return {
averagePrice: undefined,
currency: undefined,
firstBuyDate: undefined,
grossPerformance: undefined,
grossPerformancePercent: undefined,
historicalData: [],
investment: undefined,
marketPrice: undefined,
maxPrice: undefined,
minPrice: undefined,
quantity: undefined,
symbol: aSymbol,
transactionCount: undefined
};
}
const positionCurrency = orders[0].currency;
const portfolioOrders: PortfolioOrder[] = orders.map((order) => ({
currency: order.currency,
date: format(order.date, DATE_FORMAT),
name: order.SymbolProfile?.name,
quantity: new Big(order.quantity),
symbol: order.symbol,
type: <OrderType>order.type,
unitPrice: new Big(order.unitPrice)
}));
const portfolioCalculator = new PortfolioCalculator(
this.currentRateService,
positionCurrency
);
portfolioCalculator.computeTransactionPoints(portfolioOrders);
const transactionPoints = portfolioCalculator.getTransactionPoints();
const portfolioStart = parseDate(transactionPoints[0].date);
const currentPositions = await portfolioCalculator.getCurrentPositions(
portfolioStart
);
const position = currentPositions.positions.find(
(item) => item.symbol === aSymbol
);
2021-04-13 21:53:58 +02:00
if (position) {
const {
2021-04-13 21:53:58 +02:00
averagePrice,
currency,
firstBuyDate,
marketPrice,
quantity,
transactionCount
} = position;
// Convert investment and gross performance to currency of user
const userCurrency = this.request.user.Settings.currency;
const investment = this.exchangeRateDataService.toCurrency(
position.investment.toNumber(),
currency,
userCurrency
);
const grossPerformance = this.exchangeRateDataService.toCurrency(
position.grossPerformance.toNumber(),
currency,
userCurrency
);
2021-04-13 21:53:58 +02:00
const historicalData = await this.dataProviderService.getHistorical(
[aSymbol],
'day',
parseISO(firstBuyDate),
new Date()
);
const historicalDataArray: HistoricalDataItem[] = [];
let maxPrice = Math.max(orders[0].unitPrice, marketPrice);
let minPrice = Math.min(orders[0].unitPrice, marketPrice);
if (!historicalData?.[aSymbol]?.[firstBuyDate]) {
// Add historical entry for buy date, if no historical data available
historicalDataArray.push({
averagePrice: orders[0].unitPrice,
date: firstBuyDate,
value: orders[0].unitPrice
});
}
2021-04-13 21:53:58 +02:00
if (historicalData[aSymbol]) {
let j = -1;
2021-04-13 21:53:58 +02:00
for (const [date, { marketPrice }] of Object.entries(
historicalData[aSymbol]
)) {
while (
j + 1 < transactionPoints.length &&
!isAfter(parseDate(transactionPoints[j + 1].date), parseDate(date))
) {
j++;
}
let currentAveragePrice = 0;
const currentSymbol = transactionPoints[j].items.find(
(item) => item.symbol === aSymbol
);
if (currentSymbol) {
currentAveragePrice = currentSymbol.quantity.eq(0)
? 0
: currentSymbol.investment.div(currentSymbol.quantity).toNumber();
}
2021-04-13 21:53:58 +02:00
historicalDataArray.push({
date,
averagePrice: currentAveragePrice,
2021-04-13 21:53:58 +02:00
value: marketPrice
});
maxPrice = Math.max(marketPrice ?? 0, maxPrice);
minPrice = Math.min(marketPrice ?? Number.MAX_SAFE_INTEGER, minPrice);
2021-04-13 21:53:58 +02:00
}
}
return {
currency,
firstBuyDate,
grossPerformance,
investment,
2021-04-13 21:53:58 +02:00
marketPrice,
maxPrice,
minPrice,
transactionCount,
averagePrice: averagePrice.toNumber(),
grossPerformancePercent: position.grossPerformancePercentage.toNumber(),
2021-04-13 21:53:58 +02:00
historicalData: historicalDataArray,
quantity: quantity.toNumber(),
2021-04-13 21:53:58 +02:00
symbol: aSymbol
};
} else {
2021-04-13 21:53:58 +02:00
const currentData = await this.dataProviderService.get([aSymbol]);
const marketPrice = currentData[aSymbol]?.marketPrice;
2021-04-13 21:53:58 +02:00
let historicalData = await this.dataProviderService.getHistorical(
[aSymbol],
'day',
portfolioStart,
2021-04-13 21:53:58 +02:00
new Date()
);
if (isEmpty(historicalData)) {
historicalData = await this.dataProviderService.getHistoricalRaw(
[{ dataSource: DataSource.YAHOO, symbol: aSymbol }],
portfolioStart,
2021-04-13 21:53:58 +02:00
new Date()
);
}
const historicalDataArray: HistoricalDataItem[] = [];
let maxPrice = marketPrice;
let minPrice = marketPrice;
2021-04-13 21:53:58 +02:00
for (const [date, { marketPrice }] of Object.entries(
2021-04-13 21:53:58 +02:00
historicalData[aSymbol]
)) {
2021-04-13 21:53:58 +02:00
historicalDataArray.push({
date,
value: marketPrice
});
maxPrice = Math.max(marketPrice ?? 0, maxPrice);
minPrice = Math.min(marketPrice ?? Number.MAX_SAFE_INTEGER, minPrice);
2021-04-13 21:53:58 +02:00
}
return {
marketPrice,
maxPrice,
minPrice,
averagePrice: 0,
currency: currentData[aSymbol]?.currency,
2021-04-13 21:53:58 +02:00
firstBuyDate: undefined,
grossPerformance: undefined,
grossPerformancePercent: undefined,
historicalData: historicalDataArray,
investment: 0,
quantity: 0,
symbol: aSymbol,
transactionCount: undefined
2021-04-13 21:53:58 +02:00
};
}
}
2021-04-20 21:52:01 +02:00
2021-07-25 13:31:44 +02:00
public async getPositions(
aImpersonationId: string,
aDateRange: DateRange = 'max'
): Promise<{ hasErrors: boolean; positions: Position[] }> {
const userId = await this.getUserId(aImpersonationId);
2021-07-25 13:31:44 +02:00
const portfolioCalculator = new PortfolioCalculator(
this.currentRateService,
this.request.user.Settings.currency
);
const { transactionPoints } = await this.getTransactionPoints({ userId });
2021-07-25 13:31:44 +02:00
2021-07-31 09:48:48 +02:00
if (transactionPoints?.length <= 0) {
return {
hasErrors: false,
positions: []
};
}
2021-07-25 13:31:44 +02:00
portfolioCalculator.setTransactionPoints(transactionPoints);
const portfolioStart = parseDate(transactionPoints[0].date);
const startDate = this.getStartDate(aDateRange, portfolioStart);
const currentPositions = await portfolioCalculator.getCurrentPositions(
startDate
);
2021-07-25 13:31:44 +02:00
const positions = currentPositions.positions.filter(
(item) => !item.quantity.eq(0)
);
const symbols = positions.map((position) => position.symbol);
const [dataProviderResponses, symbolProfiles] = await Promise.all([
this.dataProviderService.get(symbols),
this.symbolProfileService.getSymbolProfiles(symbols)
]);
const symbolProfileMap: { [symbol: string]: EnhancedSymbolProfile } = {};
for (const symbolProfile of symbolProfiles) {
symbolProfileMap[symbolProfile.symbol] = symbolProfile;
}
return {
hasErrors: currentPositions.hasErrors,
positions: positions.map((position) => {
return {
...position,
assetClass: symbolProfileMap[position.symbol].assetClass,
averagePrice: new Big(position.averagePrice).toNumber(),
grossPerformance: position.grossPerformance?.toNumber() ?? null,
grossPerformancePercentage:
position.grossPerformancePercentage?.toNumber() ?? null,
investment: new Big(position.investment).toNumber(),
marketState: dataProviderResponses[position.symbol].marketState,
name: symbolProfileMap[position.symbol].name,
quantity: new Big(position.quantity).toNumber()
};
})
};
2021-07-25 13:31:44 +02:00
}
public async getPerformance(
aImpersonationId: string,
aDateRange: DateRange = 'max'
): Promise<{ hasErrors: boolean; performance: PortfolioPerformance }> {
const userId = await this.getUserId(aImpersonationId);
const portfolioCalculator = new PortfolioCalculator(
this.currentRateService,
this.request.user.Settings.currency
);
const { transactionPoints } = await this.getTransactionPoints({ userId });
2021-07-31 09:31:45 +02:00
if (transactionPoints?.length <= 0) {
return {
hasErrors: false,
performance: {
currentGrossPerformance: 0,
currentGrossPerformancePercent: 0,
currentValue: 0
}
};
}
portfolioCalculator.setTransactionPoints(transactionPoints);
const portfolioStart = parseDate(transactionPoints[0].date);
const startDate = this.getStartDate(aDateRange, portfolioStart);
const currentPositions = await portfolioCalculator.getCurrentPositions(
startDate
);
const hasErrors = currentPositions.hasErrors;
const currentValue = currentPositions.currentValue.toNumber();
const currentGrossPerformance =
currentPositions.grossPerformance.toNumber();
const currentGrossPerformancePercent =
currentPositions.grossPerformancePercentage.toNumber();
return {
hasErrors: currentPositions.hasErrors || hasErrors,
performance: {
currentGrossPerformance,
currentGrossPerformancePercent,
currentValue: currentValue
}
};
}
public getFees(orders: OrderWithAccount[], date = new Date(0)) {
return orders
.filter((order) => {
// Filter out all orders before given date
return isBefore(date, new Date(order.date));
})
.map((order) => {
return this.exchangeRateDataService.toCurrency(
order.fee,
order.currency,
this.request.user.Settings.currency
);
})
.reduce((previous, current) => previous + current, 0);
}
public async getReport(impersonationId: string): Promise<PortfolioReport> {
const userId = await this.getUserId(impersonationId);
2021-08-01 00:15:37 +02:00
const baseCurrency = this.request.user.Settings.currency;
const { orders, transactionPoints } = await this.getTransactionPoints({
2021-08-01 00:15:37 +02:00
userId
});
2021-08-01 00:15:37 +02:00
if (isEmpty(orders)) {
return {
rules: {}
};
}
2021-08-01 00:15:37 +02:00
const portfolioCalculator = new PortfolioCalculator(
this.currentRateService,
this.request.user.Settings.currency
);
portfolioCalculator.setTransactionPoints(transactionPoints);
2021-08-01 00:15:37 +02:00
const portfolioStart = parseDate(transactionPoints[0].date);
const currentPositions = await portfolioCalculator.getCurrentPositions(
portfolioStart
);
const portfolioItemsNow: { [symbol: string]: TimelinePosition } = {};
for (const position of currentPositions.positions) {
portfolioItemsNow[position.symbol] = position;
}
const accounts = this.getAccounts(orders, portfolioItemsNow, baseCurrency);
return {
rules: {
accountClusterRisk: await this.rulesService.evaluate(
[
new AccountClusterRiskInitialInvestment(
this.exchangeRateDataService,
2021-08-01 00:15:37 +02:00
accounts
),
new AccountClusterRiskCurrentInvestment(
this.exchangeRateDataService,
2021-08-01 00:15:37 +02:00
accounts
),
new AccountClusterRiskSingleAccount(
this.exchangeRateDataService,
2021-08-01 00:15:37 +02:00
accounts
)
],
{ baseCurrency }
),
currencyClusterRisk: await this.rulesService.evaluate(
[
new CurrencyClusterRiskBaseCurrencyInitialInvestment(
this.exchangeRateDataService,
2021-08-01 00:15:37 +02:00
currentPositions
),
new CurrencyClusterRiskBaseCurrencyCurrentInvestment(
this.exchangeRateDataService,
2021-08-01 00:15:37 +02:00
currentPositions
),
new CurrencyClusterRiskInitialInvestment(
this.exchangeRateDataService,
2021-08-01 00:15:37 +02:00
currentPositions
),
new CurrencyClusterRiskCurrentInvestment(
this.exchangeRateDataService,
2021-08-01 00:15:37 +02:00
currentPositions
)
],
{ baseCurrency }
),
fees: await this.rulesService.evaluate(
[
new FeeRatioInitialInvestment(
this.exchangeRateDataService,
2021-08-01 00:15:37 +02:00
currentPositions.totalInvestment.toNumber(),
this.getFees(orders)
)
],
{ baseCurrency }
)
}
};
}
public async getSummary(aImpersonationId: string): Promise<PortfolioSummary> {
const currency = this.request.user.Settings.currency;
const userId = await this.getUserId(aImpersonationId);
const performanceInformation = await this.getPerformance(aImpersonationId);
const { balance } = await this.accountService.getCashDetails(
userId,
currency
);
const orders = await this.orderService.getOrders({ userId });
const fees = this.getFees(orders);
const firstOrderDate = orders[0]?.date;
const totalBuy = this.getTotalByType(orders, currency, TypeOfOrder.BUY);
const totalSell = this.getTotalByType(orders, currency, TypeOfOrder.SELL);
const committedFunds = new Big(totalBuy).sub(totalSell);
const netWorth = new Big(balance)
.plus(performanceInformation.performance.currentValue)
.toNumber();
return {
...performanceInformation.performance,
fees,
firstOrderDate,
netWorth,
cash: balance,
committedFunds: committedFunds.toNumber(),
ordersCount: orders.length,
totalBuy: totalBuy,
totalSell: totalSell
};
}
private async getCashPosition({
cashDetails,
investment,
value
}: {
cashDetails: CashDetails;
investment: Big;
value: Big;
}) {
const accounts = {};
const cashValue = new Big(cashDetails.balance);
cashDetails.accounts.forEach((account) => {
accounts[account.name] = {
current: account.balance,
original: account.balance
};
});
return {
accounts,
allocationCurrent: cashValue.div(value).toNumber(),
allocationInvestment: cashValue.div(investment).toNumber(),
assetClass: AssetClass.CASH,
countries: [],
currency: Currency.CHF,
grossPerformance: 0,
grossPerformancePercent: 0,
investment: cashValue.toNumber(),
marketPrice: 0,
marketState: MarketState.open,
name: 'Cash',
quantity: 0,
sectors: [],
symbol: ghostfolioCashSymbol,
transactionCount: 0,
value: cashValue.toNumber()
};
}
2021-07-25 13:31:44 +02:00
private getStartDate(aDateRange: DateRange, portfolioStart: Date) {
switch (aDateRange) {
case '1d':
portfolioStart = max([portfolioStart, subDays(new Date(), 1)]);
break;
case 'ytd':
portfolioStart = max([portfolioStart, setDayOfYear(new Date(), 1)]);
break;
case '1y':
portfolioStart = max([portfolioStart, subYears(new Date(), 1)]);
break;
case '5y':
portfolioStart = max([portfolioStart, subYears(new Date(), 5)]);
break;
}
return portfolioStart;
}
private async getTransactionPoints({
includeDrafts = false,
userId
}: {
includeDrafts?: boolean;
userId: string;
}): Promise<{
transactionPoints: TransactionPoint[];
orders: OrderWithAccount[];
}> {
const orders = await this.orderService.getOrders({ includeDrafts, userId });
2021-07-25 13:31:44 +02:00
if (orders.length <= 0) {
return { transactionPoints: [], orders: [] };
2021-07-25 13:31:44 +02:00
}
const userCurrency = this.request.user.Settings.currency;
2021-07-25 13:31:44 +02:00
const portfolioOrders: PortfolioOrder[] = orders.map((order) => ({
currency: order.currency,
2021-07-28 16:11:19 +02:00
date: format(order.date, DATE_FORMAT),
2021-07-25 13:31:44 +02:00
name: order.SymbolProfile?.name,
quantity: new Big(order.quantity),
symbol: order.symbol,
type: <OrderType>order.type,
unitPrice: new Big(
this.exchangeRateDataService.toCurrency(
order.unitPrice,
order.currency,
userCurrency
)
)
2021-07-25 13:31:44 +02:00
}));
const portfolioCalculator = new PortfolioCalculator(
this.currentRateService,
userCurrency
2021-07-25 13:31:44 +02:00
);
portfolioCalculator.computeTransactionPoints(portfolioOrders);
return {
transactionPoints: portfolioCalculator.getTransactionPoints(),
orders
};
2021-07-25 13:31:44 +02:00
}
private getAccounts(
orders: OrderWithAccount[],
portfolioItemsNow: { [p: string]: TimelinePosition },
userCurrency
) {
const accounts: PortfolioPosition['accounts'] = {};
for (const order of orders) {
let currentValueOfSymbol = this.exchangeRateDataService.toCurrency(
order.quantity * portfolioItemsNow[order.symbol].marketPrice,
order.currency,
userCurrency
);
let originalValueOfSymbol = this.exchangeRateDataService.toCurrency(
order.quantity * order.unitPrice,
order.currency,
userCurrency
);
if (order.type === 'SELL') {
currentValueOfSymbol *= -1;
originalValueOfSymbol *= -1;
}
if (accounts[order.Account?.name || UNKNOWN_KEY]?.current) {
accounts[order.Account?.name || UNKNOWN_KEY].current +=
currentValueOfSymbol;
accounts[order.Account?.name || UNKNOWN_KEY].original +=
originalValueOfSymbol;
} else {
accounts[order.Account?.name || UNKNOWN_KEY] = {
current: currentValueOfSymbol,
original: originalValueOfSymbol
};
}
}
return accounts;
}
private async getUserId(aImpersonationId: string) {
const impersonationUserId =
await this.impersonationService.validateImpersonationId(
aImpersonationId,
this.request.user.id
);
return impersonationUserId || this.request.user.id;
}
private getTotalByType(
orders: OrderWithAccount[],
currency: Currency,
type: TypeOfOrder
) {
return orders
.filter(
(order) => !isAfter(order.date, endOfToday()) && order.type === type
)
.map((order) => {
return this.exchangeRateDataService.toCurrency(
order.quantity * order.unitPrice,
order.currency,
currency
);
})
.reduce((previous, current) => previous + current, 0);
}
2021-04-13 21:53:58 +02:00
}