2021-07-07 21:23:36 +02:00
|
|
|
import { AccountService } from '@ghostfolio/api/app/account/account.service';
|
2021-07-09 21:35:06 +02:00
|
|
|
import { CurrentRateService } from '@ghostfolio/api/app/core/current-rate.service';
|
|
|
|
import {
|
|
|
|
PortfolioCalculator,
|
|
|
|
PortfolioOrder,
|
|
|
|
TimelineSpecification
|
|
|
|
} from '@ghostfolio/api/app/core/portfolio-calculator';
|
2021-07-07 21:23:36 +02:00
|
|
|
import { OrderService } from '@ghostfolio/api/app/order/order.service';
|
|
|
|
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
|
|
|
|
import { UserService } from '@ghostfolio/api/app/user/user.service';
|
2021-07-20 22:52:50 +02:00
|
|
|
import { OrderType } from '@ghostfolio/api/models/order-type';
|
2021-04-21 20:27:39 +02:00
|
|
|
import { Portfolio } from '@ghostfolio/api/models/portfolio';
|
|
|
|
import { DataProviderService } from '@ghostfolio/api/services/data-provider.service';
|
|
|
|
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data.service';
|
|
|
|
import { ImpersonationService } from '@ghostfolio/api/services/impersonation.service';
|
|
|
|
import { IOrder } from '@ghostfolio/api/services/interfaces/interfaces';
|
2021-07-24 10:53:15 +02:00
|
|
|
import {
|
|
|
|
MarketState,
|
|
|
|
Type
|
|
|
|
} from '@ghostfolio/api/services/interfaces/interfaces';
|
2021-04-21 20:27:39 +02:00
|
|
|
import { RulesService } from '@ghostfolio/api/services/rules.service';
|
2021-05-16 21:20:59 +02:00
|
|
|
import {
|
|
|
|
PortfolioItem,
|
2021-07-24 10:53:15 +02:00
|
|
|
PortfolioOverview,
|
|
|
|
Position
|
2021-05-16 22:11:14 +02:00
|
|
|
} from '@ghostfolio/common/interfaces';
|
|
|
|
import { DateRange, RequestWithUser } from '@ghostfolio/common/types';
|
2021-04-13 21:53:58 +02:00
|
|
|
import { Inject, Injectable } from '@nestjs/common';
|
|
|
|
import { REQUEST } from '@nestjs/core';
|
2021-05-27 20:50:10 +02:00
|
|
|
import { DataSource } 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 {
|
|
|
|
add,
|
2021-07-10 18:16:46 +02:00
|
|
|
addMonths,
|
2021-04-13 21:53:58 +02:00
|
|
|
format,
|
|
|
|
getDate,
|
|
|
|
getMonth,
|
|
|
|
getYear,
|
|
|
|
isAfter,
|
|
|
|
isSameDay,
|
2021-07-07 23:29:37 +02:00
|
|
|
max,
|
2021-06-05 17:17:53 +02:00
|
|
|
parse,
|
2021-04-13 21:53:58 +02:00
|
|
|
parseISO,
|
|
|
|
setDate,
|
2021-07-07 23:29:37 +02:00
|
|
|
setDayOfYear,
|
2021-04-13 21:53:58 +02:00
|
|
|
setMonth,
|
2021-07-07 23:29:37 +02:00
|
|
|
sub,
|
|
|
|
subDays,
|
|
|
|
subYears
|
2021-04-13 21:53:58 +02:00
|
|
|
} from 'date-fns';
|
|
|
|
import { isEmpty } from 'lodash';
|
|
|
|
import * as roundTo from 'round-to';
|
|
|
|
|
|
|
|
import {
|
|
|
|
HistoricalDataItem,
|
|
|
|
PortfolioPositionDetail
|
|
|
|
} from './interfaces/portfolio-position-detail.interface';
|
|
|
|
|
|
|
|
@Injectable()
|
|
|
|
export class PortfolioService {
|
|
|
|
public constructor(
|
2021-07-07 21:23:36 +02:00
|
|
|
private readonly accountService: AccountService,
|
2021-04-13 21:53:58 +02:00
|
|
|
private readonly dataProviderService: DataProviderService,
|
|
|
|
private readonly exchangeRateDataService: ExchangeRateDataService,
|
|
|
|
private readonly impersonationService: ImpersonationService,
|
|
|
|
private readonly orderService: OrderService,
|
|
|
|
private readonly redisCacheService: RedisCacheService,
|
|
|
|
@Inject(REQUEST) private readonly request: RequestWithUser,
|
|
|
|
private readonly rulesService: RulesService,
|
2021-07-07 23:29:37 +02:00
|
|
|
private readonly userService: UserService,
|
|
|
|
private readonly currentRateService: CurrentRateService
|
2021-04-13 21:53:58 +02:00
|
|
|
) {}
|
|
|
|
|
|
|
|
public async createPortfolio(aUserId: string): Promise<Portfolio> {
|
|
|
|
let portfolio: Portfolio;
|
2021-07-03 11:32:03 +02:00
|
|
|
const stringifiedPortfolio = await this.redisCacheService.get(
|
2021-04-13 21:53:58 +02:00
|
|
|
`${aUserId}.portfolio`
|
|
|
|
);
|
|
|
|
|
|
|
|
const user = await this.userService.user({ id: aUserId });
|
|
|
|
|
|
|
|
if (stringifiedPortfolio) {
|
|
|
|
// Get portfolio from redis
|
|
|
|
const {
|
|
|
|
orders,
|
|
|
|
portfolioItems
|
2021-07-03 11:32:03 +02:00
|
|
|
}: { orders: IOrder[]; portfolioItems: PortfolioItem[] } =
|
|
|
|
JSON.parse(stringifiedPortfolio);
|
2021-04-13 21:53:58 +02:00
|
|
|
|
|
|
|
portfolio = new Portfolio(
|
2021-07-10 14:57:03 +02:00
|
|
|
this.accountService,
|
2021-04-13 21:53:58 +02:00
|
|
|
this.dataProviderService,
|
|
|
|
this.exchangeRateDataService,
|
|
|
|
this.rulesService
|
|
|
|
).createFromData({ orders, portfolioItems, user });
|
|
|
|
} else {
|
|
|
|
// Get portfolio from database
|
2021-07-11 20:59:23 +02:00
|
|
|
const orders = await this.getOrders(aUserId);
|
2021-04-13 21:53:58 +02:00
|
|
|
|
|
|
|
portfolio = new Portfolio(
|
2021-07-10 14:57:03 +02:00
|
|
|
this.accountService,
|
2021-04-13 21:53:58 +02:00
|
|
|
this.dataProviderService,
|
|
|
|
this.exchangeRateDataService,
|
|
|
|
this.rulesService
|
|
|
|
);
|
|
|
|
portfolio.setUser(user);
|
|
|
|
await portfolio.setOrders(orders);
|
|
|
|
|
|
|
|
// Cache data for the next time...
|
|
|
|
const portfolioData = {
|
|
|
|
orders: portfolio.getOrders(),
|
|
|
|
portfolioItems: portfolio.getPortfolioItems()
|
|
|
|
};
|
|
|
|
|
|
|
|
await this.redisCacheService.set(
|
|
|
|
`${aUserId}.portfolio`,
|
|
|
|
JSON.stringify(portfolioData)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Enrich portfolio with current data
|
2021-07-03 11:32:03 +02:00
|
|
|
await portfolio.addCurrentPortfolioItems();
|
|
|
|
|
|
|
|
// Enrich portfolio with future data
|
|
|
|
await portfolio.addFuturePortfolioItems();
|
|
|
|
|
|
|
|
return portfolio;
|
2021-04-13 21:53:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public async findAll(aImpersonationId: string): Promise<PortfolioItem[]> {
|
|
|
|
try {
|
2021-07-03 11:32:03 +02:00
|
|
|
const impersonationUserId =
|
|
|
|
await this.impersonationService.validateImpersonationId(
|
|
|
|
aImpersonationId,
|
|
|
|
this.request.user.id
|
|
|
|
);
|
2021-04-13 21:53:58 +02:00
|
|
|
|
|
|
|
const portfolio = await this.createPortfolio(
|
|
|
|
impersonationUserId || this.request.user.id
|
|
|
|
);
|
|
|
|
return portfolio.get();
|
|
|
|
} catch (error) {
|
|
|
|
console.error(error);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public async getChart(
|
|
|
|
aImpersonationId: string,
|
|
|
|
aDateRange: DateRange = 'max'
|
|
|
|
): Promise<HistoricalDataItem[]> {
|
2021-07-11 20:32:59 +02:00
|
|
|
console.time('impersonation-service');
|
2021-07-03 11:32:03 +02:00
|
|
|
const impersonationUserId =
|
|
|
|
await this.impersonationService.validateImpersonationId(
|
|
|
|
aImpersonationId,
|
|
|
|
this.request.user.id
|
|
|
|
);
|
2021-07-11 20:32:59 +02:00
|
|
|
console.timeEnd('impersonation-service');
|
2021-04-13 21:53:58 +02:00
|
|
|
|
2021-07-11 20:59:23 +02:00
|
|
|
const userId = impersonationUserId || this.request.user.id;
|
2021-04-13 21:53:58 +02:00
|
|
|
|
2021-07-07 23:29:37 +02:00
|
|
|
const portfolioCalculator = new PortfolioCalculator(
|
|
|
|
this.currentRateService,
|
|
|
|
this.request.user.Settings.currency
|
|
|
|
);
|
|
|
|
|
2021-07-20 22:52:50 +02:00
|
|
|
const transactionPoints = await this.getTransactionPoints(userId);
|
|
|
|
portfolioCalculator.setTransactionPoints(transactionPoints);
|
2021-07-07 23:29:37 +02:00
|
|
|
if (transactionPoints.length === 0) {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
const dateFormat = 'yyyy-MM-dd';
|
|
|
|
let portfolioStart = parse(
|
|
|
|
transactionPoints[0].date,
|
|
|
|
dateFormat,
|
|
|
|
new Date()
|
|
|
|
);
|
|
|
|
portfolioStart = this.getStartDate(aDateRange, portfolioStart);
|
|
|
|
|
|
|
|
const timelineSpecification: TimelineSpecification[] = [
|
|
|
|
{
|
|
|
|
start: format(portfolioStart, dateFormat),
|
|
|
|
accuracy: 'day'
|
|
|
|
}
|
|
|
|
];
|
|
|
|
|
|
|
|
const timeline = await portfolioCalculator.calculateTimeline(
|
|
|
|
timelineSpecification,
|
|
|
|
format(new Date(), dateFormat)
|
|
|
|
);
|
|
|
|
|
2021-07-13 22:51:32 +02:00
|
|
|
return timeline
|
|
|
|
.filter((timelineItem) => timelineItem !== null)
|
|
|
|
.map((timelineItem) => ({
|
|
|
|
date: timelineItem.date,
|
2021-07-20 22:23:56 +02:00
|
|
|
value: timelineItem.grossPerformance.toNumber(),
|
2021-07-13 22:51:32 +02:00
|
|
|
marketPrice: timelineItem.value
|
|
|
|
}));
|
2021-07-07 23:29:37 +02:00
|
|
|
}
|
|
|
|
|
2021-07-24 10:53:15 +02:00
|
|
|
public async getPositions(
|
|
|
|
aImpersonationId: string,
|
|
|
|
aDateRange: DateRange = 'max'
|
|
|
|
): Promise<Position[]> {
|
2021-07-20 22:52:50 +02:00
|
|
|
const impersonationUserId =
|
|
|
|
await this.impersonationService.validateImpersonationId(
|
|
|
|
aImpersonationId,
|
|
|
|
this.request.user.id
|
|
|
|
);
|
|
|
|
|
|
|
|
const userId = impersonationUserId || this.request.user.id;
|
|
|
|
|
|
|
|
const portfolioCalculator = new PortfolioCalculator(
|
|
|
|
this.currentRateService,
|
|
|
|
this.request.user.Settings.currency
|
|
|
|
);
|
|
|
|
|
|
|
|
const transactionPoints = await this.getTransactionPoints(userId);
|
|
|
|
|
|
|
|
portfolioCalculator.setTransactionPoints(transactionPoints);
|
|
|
|
|
2021-07-24 10:53:15 +02:00
|
|
|
// TODO: get positions for date range
|
|
|
|
console.log('Date range:', aDateRange);
|
2021-07-20 22:52:50 +02:00
|
|
|
const positions = await portfolioCalculator.getCurrentPositions();
|
|
|
|
|
|
|
|
return Object.values(positions).map((position) => {
|
|
|
|
return {
|
|
|
|
...position,
|
2021-07-24 10:53:15 +02:00
|
|
|
averagePrice: new Big(position.averagePrice).toNumber(),
|
|
|
|
grossPerformance: new Big(position.grossPerformance).toNumber(),
|
|
|
|
grossPerformancePercentage: new Big(
|
|
|
|
position.grossPerformancePercentage
|
|
|
|
).toNumber(),
|
|
|
|
investment: new Big(position.investment).toNumber(),
|
2021-07-25 12:23:18 +02:00
|
|
|
name: position.name,
|
2021-07-24 10:53:15 +02:00
|
|
|
quantity: new Big(position.quantity).toNumber(),
|
|
|
|
type: Type.Unknown, // TODO
|
|
|
|
url: '' // TODO
|
2021-07-20 22:52:50 +02:00
|
|
|
};
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-07-07 23:29:37 +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;
|
2021-04-13 21:53:58 +02:00
|
|
|
}
|
|
|
|
|
2021-07-20 22:52:50 +02:00
|
|
|
private async getTransactionPoints(userId: string) {
|
|
|
|
console.time('create-portfolio');
|
|
|
|
const orders = await this.getOrders(userId);
|
|
|
|
console.timeEnd('create-portfolio');
|
|
|
|
|
|
|
|
if (orders.length <= 0) {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
|
|
|
const portfolioOrders: PortfolioOrder[] = orders.map((order) => ({
|
2021-07-25 12:23:18 +02:00
|
|
|
currency: order.currency,
|
2021-07-20 22:52:50 +02:00
|
|
|
date: format(order.date, 'yyyy-MM-dd'),
|
2021-07-25 12:23:18 +02:00
|
|
|
name: order.SymbolProfile?.name,
|
2021-07-20 22:52:50 +02:00
|
|
|
quantity: new Big(order.quantity),
|
|
|
|
symbol: order.symbol,
|
|
|
|
type: <OrderType>order.type,
|
2021-07-25 12:23:18 +02:00
|
|
|
unitPrice: new Big(order.unitPrice)
|
2021-07-20 22:52:50 +02:00
|
|
|
}));
|
|
|
|
|
|
|
|
const portfolioCalculator = new PortfolioCalculator(
|
|
|
|
this.currentRateService,
|
|
|
|
this.request.user.Settings.currency
|
|
|
|
);
|
|
|
|
portfolioCalculator.computeTransactionPoints(portfolioOrders);
|
|
|
|
return portfolioCalculator.getTransactionPoints();
|
|
|
|
}
|
|
|
|
|
2021-04-13 21:53:58 +02:00
|
|
|
public async getOverview(
|
|
|
|
aImpersonationId: string
|
|
|
|
): Promise<PortfolioOverview> {
|
2021-07-03 11:32:03 +02:00
|
|
|
const impersonationUserId =
|
|
|
|
await this.impersonationService.validateImpersonationId(
|
|
|
|
aImpersonationId,
|
|
|
|
this.request.user.id
|
|
|
|
);
|
2021-04-13 21:53:58 +02:00
|
|
|
|
|
|
|
const portfolio = await this.createPortfolio(
|
|
|
|
impersonationUserId || this.request.user.id
|
|
|
|
);
|
|
|
|
|
2021-07-10 14:57:03 +02:00
|
|
|
const { balance } = await this.accountService.getCashDetails(
|
2021-07-07 21:23:36 +02:00
|
|
|
impersonationUserId || this.request.user.id,
|
|
|
|
this.request.user.Settings.currency
|
|
|
|
);
|
2021-04-13 21:53:58 +02:00
|
|
|
const committedFunds = portfolio.getCommittedFunds();
|
|
|
|
const fees = portfolio.getFees();
|
|
|
|
|
|
|
|
return {
|
|
|
|
committedFunds,
|
|
|
|
fees,
|
2021-07-10 14:57:03 +02:00
|
|
|
cash: balance,
|
2021-04-13 21:53:58 +02:00
|
|
|
ordersCount: portfolio.getOrders().length,
|
|
|
|
totalBuy: portfolio.getTotalBuy(),
|
|
|
|
totalSell: portfolio.getTotalSell()
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
public async getPosition(
|
|
|
|
aImpersonationId: string,
|
|
|
|
aSymbol: string
|
|
|
|
): Promise<PortfolioPositionDetail> {
|
2021-07-03 11:32:03 +02:00
|
|
|
const impersonationUserId =
|
|
|
|
await this.impersonationService.validateImpersonationId(
|
|
|
|
aImpersonationId,
|
|
|
|
this.request.user.id
|
|
|
|
);
|
2021-04-13 21:53:58 +02:00
|
|
|
|
|
|
|
const portfolio = await this.createPortfolio(
|
|
|
|
impersonationUserId || this.request.user.id
|
|
|
|
);
|
|
|
|
|
2021-07-10 18:16:46 +02:00
|
|
|
const position = portfolio.getPositions(new Date())[aSymbol];
|
2021-04-13 21:53:58 +02:00
|
|
|
|
2021-07-10 18:16:46 +02:00
|
|
|
if (position) {
|
|
|
|
const {
|
2021-04-13 21:53:58 +02:00
|
|
|
averagePrice,
|
|
|
|
currency,
|
|
|
|
firstBuyDate,
|
|
|
|
investment,
|
2021-04-30 21:08:43 +02:00
|
|
|
quantity,
|
|
|
|
transactionCount
|
2021-07-10 18:16:46 +02:00
|
|
|
} = position;
|
|
|
|
let marketPrice = position.marketPrice;
|
2021-06-05 17:17:53 +02:00
|
|
|
const orders = portfolio.getOrders(aSymbol);
|
|
|
|
|
2021-04-13 21:53:58 +02:00
|
|
|
const historicalData = await this.dataProviderService.getHistorical(
|
|
|
|
[aSymbol],
|
|
|
|
'day',
|
|
|
|
parseISO(firstBuyDate),
|
|
|
|
new Date()
|
|
|
|
);
|
|
|
|
|
|
|
|
if (marketPrice === 0) {
|
|
|
|
marketPrice = averagePrice;
|
|
|
|
}
|
|
|
|
|
|
|
|
const historicalDataArray: HistoricalDataItem[] = [];
|
2021-06-05 17:17:53 +02:00
|
|
|
let currentAveragePrice: number;
|
2021-04-13 21:53:58 +02:00
|
|
|
let maxPrice = marketPrice;
|
|
|
|
let minPrice = marketPrice;
|
|
|
|
|
|
|
|
if (historicalData[aSymbol]) {
|
|
|
|
for (const [date, { marketPrice }] of Object.entries(
|
|
|
|
historicalData[aSymbol]
|
|
|
|
)) {
|
2021-06-05 17:17:53 +02:00
|
|
|
const currentDate = parse(date, 'yyyy-MM-dd', new Date());
|
|
|
|
if (
|
|
|
|
isSameDay(currentDate, parseISO(orders[0]?.getDate())) ||
|
|
|
|
isAfter(currentDate, parseISO(orders[0]?.getDate()))
|
|
|
|
) {
|
2021-07-10 18:16:46 +02:00
|
|
|
// Get snapshot of first day of next month
|
|
|
|
const snapshot = portfolio.get(
|
|
|
|
addMonths(setDate(currentDate, 1), 1)
|
|
|
|
)?.[0]?.positions[aSymbol];
|
2021-06-05 17:17:53 +02:00
|
|
|
orders.shift();
|
|
|
|
|
|
|
|
if (snapshot?.averagePrice) {
|
2021-07-10 18:16:46 +02:00
|
|
|
currentAveragePrice = snapshot.averagePrice;
|
2021-06-05 17:17:53 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-13 21:53:58 +02:00
|
|
|
historicalDataArray.push({
|
|
|
|
date,
|
2021-06-05 17:17:53 +02:00
|
|
|
averagePrice: currentAveragePrice,
|
2021-04-13 21:53:58 +02:00
|
|
|
value: marketPrice
|
|
|
|
});
|
|
|
|
|
|
|
|
if (
|
|
|
|
marketPrice &&
|
|
|
|
(marketPrice > maxPrice || maxPrice === undefined)
|
|
|
|
) {
|
|
|
|
maxPrice = marketPrice;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (
|
|
|
|
marketPrice &&
|
|
|
|
(marketPrice < minPrice || minPrice === undefined)
|
|
|
|
) {
|
|
|
|
minPrice = marketPrice;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
averagePrice,
|
|
|
|
currency,
|
|
|
|
firstBuyDate,
|
|
|
|
investment,
|
|
|
|
marketPrice,
|
|
|
|
maxPrice,
|
|
|
|
minPrice,
|
|
|
|
quantity,
|
2021-04-30 21:08:43 +02:00
|
|
|
transactionCount,
|
2021-04-13 21:53:58 +02:00
|
|
|
grossPerformance: this.exchangeRateDataService.toCurrency(
|
|
|
|
marketPrice - averagePrice,
|
|
|
|
currency,
|
|
|
|
this.request.user.Settings.currency
|
|
|
|
),
|
|
|
|
grossPerformancePercent: roundTo(
|
|
|
|
(marketPrice - averagePrice) / averagePrice,
|
|
|
|
4
|
|
|
|
),
|
|
|
|
historicalData: historicalDataArray,
|
|
|
|
symbol: aSymbol
|
|
|
|
};
|
|
|
|
} else if (portfolio.getMinDate()) {
|
|
|
|
const currentData = await this.dataProviderService.get([aSymbol]);
|
|
|
|
|
|
|
|
let historicalData = await this.dataProviderService.getHistorical(
|
|
|
|
[aSymbol],
|
|
|
|
'day',
|
|
|
|
portfolio.getMinDate(),
|
|
|
|
new Date()
|
|
|
|
);
|
|
|
|
|
|
|
|
if (isEmpty(historicalData)) {
|
|
|
|
historicalData = await this.dataProviderService.getHistoricalRaw(
|
2021-05-27 20:50:10 +02:00
|
|
|
[{ dataSource: DataSource.YAHOO, symbol: aSymbol }],
|
2021-04-13 21:53:58 +02:00
|
|
|
portfolio.getMinDate(),
|
|
|
|
new Date()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
const historicalDataArray: HistoricalDataItem[] = [];
|
|
|
|
|
2021-07-03 11:32:03 +02:00
|
|
|
for (const [date, { marketPrice }] of Object.entries(
|
2021-04-13 21:53:58 +02:00
|
|
|
historicalData[aSymbol]
|
|
|
|
).reverse()) {
|
|
|
|
historicalDataArray.push({
|
|
|
|
date,
|
|
|
|
value: marketPrice
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
averagePrice: undefined,
|
2021-07-10 14:57:03 +02:00
|
|
|
currency: currentData[aSymbol]?.currency,
|
2021-04-13 21:53:58 +02:00
|
|
|
firstBuyDate: undefined,
|
|
|
|
grossPerformance: undefined,
|
|
|
|
grossPerformancePercent: undefined,
|
|
|
|
historicalData: historicalDataArray,
|
|
|
|
investment: undefined,
|
2021-07-10 14:57:03 +02:00
|
|
|
marketPrice: currentData[aSymbol]?.marketPrice,
|
2021-04-13 21:53:58 +02:00
|
|
|
maxPrice: undefined,
|
|
|
|
minPrice: undefined,
|
|
|
|
quantity: undefined,
|
2021-04-30 21:08:43 +02:00
|
|
|
symbol: aSymbol,
|
|
|
|
transactionCount: undefined
|
2021-04-13 21:53:58 +02:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
averagePrice: undefined,
|
|
|
|
currency: undefined,
|
|
|
|
firstBuyDate: undefined,
|
|
|
|
grossPerformance: undefined,
|
|
|
|
grossPerformancePercent: undefined,
|
|
|
|
historicalData: [],
|
|
|
|
investment: undefined,
|
|
|
|
marketPrice: undefined,
|
|
|
|
maxPrice: undefined,
|
|
|
|
minPrice: undefined,
|
|
|
|
quantity: undefined,
|
2021-04-30 21:08:43 +02:00
|
|
|
symbol: aSymbol,
|
|
|
|
transactionCount: undefined
|
2021-04-13 21:53:58 +02:00
|
|
|
};
|
|
|
|
}
|
2021-04-20 21:52:01 +02:00
|
|
|
|
|
|
|
private convertDateRangeToDate(aDateRange: DateRange, aMinDate: Date) {
|
|
|
|
let currentDate = new Date();
|
|
|
|
|
|
|
|
const normalizedMinDate =
|
|
|
|
getDate(aMinDate) === 1
|
|
|
|
? aMinDate
|
|
|
|
: add(setDate(aMinDate, 1), { months: 1 });
|
|
|
|
|
|
|
|
const year = getYear(currentDate);
|
|
|
|
const month = getMonth(currentDate);
|
|
|
|
const day = getDate(currentDate);
|
|
|
|
|
|
|
|
currentDate = new Date(Date.UTC(year, month, day, 0));
|
|
|
|
|
|
|
|
switch (aDateRange) {
|
|
|
|
case '1d':
|
|
|
|
return sub(currentDate, {
|
|
|
|
days: 1
|
|
|
|
});
|
|
|
|
case 'ytd':
|
|
|
|
currentDate = setDate(currentDate, 1);
|
|
|
|
currentDate = setMonth(currentDate, 0);
|
|
|
|
return isAfter(currentDate, normalizedMinDate)
|
|
|
|
? currentDate
|
|
|
|
: undefined;
|
|
|
|
case '1y':
|
|
|
|
currentDate = setDate(currentDate, 1);
|
|
|
|
currentDate = sub(currentDate, {
|
|
|
|
years: 1
|
|
|
|
});
|
|
|
|
return isAfter(currentDate, normalizedMinDate)
|
|
|
|
? currentDate
|
|
|
|
: undefined;
|
|
|
|
case '5y':
|
|
|
|
currentDate = setDate(currentDate, 1);
|
|
|
|
currentDate = sub(currentDate, {
|
|
|
|
years: 5
|
|
|
|
});
|
|
|
|
return isAfter(currentDate, normalizedMinDate)
|
|
|
|
? currentDate
|
|
|
|
: undefined;
|
|
|
|
default:
|
|
|
|
// Gets handled as all data
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
}
|
2021-07-11 20:59:23 +02:00
|
|
|
|
|
|
|
private getOrders(aUserId: string) {
|
|
|
|
return this.orderService.orders({
|
|
|
|
include: {
|
|
|
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
|
|
Account: true,
|
|
|
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
|
|
SymbolProfile: true
|
|
|
|
},
|
|
|
|
orderBy: { date: 'asc' },
|
|
|
|
where: { userId: aUserId }
|
|
|
|
});
|
|
|
|
}
|
2021-04-13 21:53:58 +02:00
|
|
|
}
|