Browse Source

add draft integration of new portfolio calculator to chart

pull/239/head
Valentin Zickner 4 years ago
committed by Thomas
parent
commit
cfee6c1ddd
  1. 4
      apps/api/src/app/portfolio/portfolio.module.ts
  2. 102
      apps/api/src/app/portfolio/portfolio.service.ts

4
apps/api/src/app/portfolio/portfolio.module.ts

@ -18,6 +18,8 @@ import { Module } from '@nestjs/common';
import { PortfolioController } from './portfolio.controller'; import { PortfolioController } from './portfolio.controller';
import { PortfolioService } from './portfolio.service'; import { PortfolioService } from './portfolio.service';
import { CurrentRateService } from '@ghostfolio/api/app/core/current-rate.service';
import { MarketDataService } from '@ghostfolio/api/app/core/market-data.service';
@Module({ @Module({
imports: [RedisCacheModule], imports: [RedisCacheModule],
@ -26,6 +28,7 @@ import { PortfolioService } from './portfolio.service';
AccountService, AccountService,
AlphaVantageService, AlphaVantageService,
CacheService, CacheService,
CurrentRateService,
ConfigurationService, ConfigurationService,
DataGatheringService, DataGatheringService,
DataProviderService, DataProviderService,
@ -37,6 +40,7 @@ import { PortfolioService } from './portfolio.service';
PrismaService, PrismaService,
RakutenRapidApiService, RakutenRapidApiService,
RulesService, RulesService,
MarketDataService,
UserService, UserService,
YahooFinanceService YahooFinanceService
] ]

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

@ -26,11 +26,15 @@ import {
getYear, getYear,
isAfter, isAfter,
isSameDay, isSameDay,
max,
parse, parse,
parseISO, parseISO,
setDate, setDate,
setDayOfYear,
setMonth, setMonth,
sub sub,
subDays,
subYears
} from 'date-fns'; } from 'date-fns';
import { isEmpty } from 'lodash'; import { isEmpty } from 'lodash';
import * as roundTo from 'round-to'; import * as roundTo from 'round-to';
@ -39,6 +43,14 @@ import {
HistoricalDataItem, HistoricalDataItem,
PortfolioPositionDetail PortfolioPositionDetail
} from './interfaces/portfolio-position-detail.interface'; } from './interfaces/portfolio-position-detail.interface';
import {
PortfolioCalculator,
PortfolioOrder,
TimelineSpecification
} from '@ghostfolio/api/app/core/portfolio-calculator';
import { CurrentRateService } from '@ghostfolio/api/app/core/current-rate.service';
import Big from 'big.js';
import { port } from 'envalid';
@Injectable() @Injectable()
export class PortfolioService { export class PortfolioService {
@ -51,7 +63,8 @@ export class PortfolioService {
private readonly redisCacheService: RedisCacheService, private readonly redisCacheService: RedisCacheService,
@Inject(REQUEST) private readonly request: RequestWithUser, @Inject(REQUEST) private readonly request: RequestWithUser,
private readonly rulesService: RulesService, private readonly rulesService: RulesService,
private readonly userService: UserService private readonly userService: UserService,
private readonly currentRateService: CurrentRateService
) {} ) {}
public async createPortfolio(aUserId: string): Promise<Portfolio> { public async createPortfolio(aUserId: string): Promise<Portfolio> {
@ -148,7 +161,8 @@ export class PortfolioService {
impersonationUserId || this.request.user.id impersonationUserId || this.request.user.id
); );
if (portfolio.getOrders().length <= 0) { const orders = portfolio.getOrders();
if (orders.length <= 0) {
return []; return [];
} }
@ -157,10 +171,14 @@ export class PortfolioService {
portfolio.getMinDate() portfolio.getMinDate()
); );
return portfolio const portfolioCalculator = new PortfolioCalculator(
.get() this.currentRateService,
this.request.user.Settings.currency
);
const portfolioOrders: PortfolioOrder[] = orders
.filter((portfolioItem) => { .filter((portfolioItem) => {
if (isAfter(parseISO(portfolioItem.date), endOfToday())) { if (isAfter(parseISO(portfolioItem.getDate()), endOfToday())) {
// Filter out future dates // Filter out future dates
return false; return false;
} }
@ -170,18 +188,70 @@ export class PortfolioService {
} }
return ( return (
isSameDay(parseISO(portfolioItem.date), dateRangeDate) || isSameDay(parseISO(portfolioItem.getDate()), dateRangeDate) ||
isAfter(parseISO(portfolioItem.date), dateRangeDate) isAfter(parseISO(portfolioItem.getDate()), dateRangeDate)
); );
}) })
.map((portfolioItem) => { .map((order) => ({
return { date: order.getDate().substr(0, 10),
date: format(parseISO(portfolioItem.date), 'yyyy-MM-dd'), quantity: new Big(order.getQuantity()),
grossPerformancePercent: portfolioItem.grossPerformancePercent, symbol: order.getSymbol(),
marketPrice: portfolioItem.value ?? null, type: order.getType(),
value: portfolioItem.value - portfolioItem.investment ?? null unitPrice: new Big(order.getUnitPrice()),
}; currency: order.getCurrency()
}); }));
portfolioCalculator.computeTransactionPoints(portfolioOrders);
const transactionPoints = portfolioCalculator.getTransactionPoints();
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: 'month'
},
{
start: format(subYears(new Date(), 1), dateFormat),
accuracy: 'day'
}
];
const timeline = await portfolioCalculator.calculateTimeline(
timelineSpecification,
format(new Date(), dateFormat)
);
return timeline.map((timelineItem) => ({
date: timelineItem.date,
value: timelineItem.grossPerformance,
marketPrice: timelineItem.value
}));
}
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;
} }
public async getOverview( public async getOverview(

Loading…
Cancel
Save