| 
						
						
							
								
							
						
						
					 | 
				
				 | 
				
					@ -26,11 +26,15 @@ import { | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					  getYear, | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					  isAfter, | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					  isSameDay, | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					  max, | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					  parse, | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					  parseISO, | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					  setDate, | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					  setDayOfYear, | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					  setMonth, | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					  sub | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					  sub, | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					  subDays, | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					  subYears | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					} from 'date-fns'; | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					import { isEmpty } from 'lodash'; | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					import * as roundTo from 'round-to'; | 
				
			
			
		
	
	
		
			
				
					| 
						
						
						
							
								
							
						
					 | 
				
				 | 
				
					@ -39,6 +43,14 @@ import { | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					  HistoricalDataItem, | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					  PortfolioPositionDetail | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					} 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() | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					export class PortfolioService { | 
				
			
			
		
	
	
		
			
				
					| 
						
						
						
							
								
							
						
					 | 
				
				 | 
				
					@ -51,7 +63,8 @@ export class PortfolioService { | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					    private readonly redisCacheService: RedisCacheService, | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					    @Inject(REQUEST) private readonly request: RequestWithUser, | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					    private readonly rulesService: RulesService, | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					    private readonly userService: UserService | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					    private readonly userService: UserService, | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					    private readonly currentRateService: CurrentRateService | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					  ) {} | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					
 | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					  public async createPortfolio(aUserId: string): Promise<Portfolio> { | 
				
			
			
		
	
	
		
			
				
					| 
						
							
								
							
						
						
							
								
							
						
						
					 | 
				
				 | 
				
					@ -148,7 +161,8 @@ export class PortfolioService { | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      impersonationUserId || this.request.user.id | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					    ); | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					
 | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					    if (portfolio.getOrders().length <= 0) { | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					    const orders = portfolio.getOrders(); | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					    if (orders.length <= 0) { | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      return []; | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					    } | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					
 | 
				
			
			
		
	
	
		
			
				
					| 
						
						
						
							
								
							
						
					 | 
				
				 | 
				
					@ -157,10 +171,14 @@ export class PortfolioService { | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      portfolio.getMinDate() | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					    ); | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					
 | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					    return portfolio | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      .get() | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					    const portfolioCalculator = new PortfolioCalculator( | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      this.currentRateService, | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      this.request.user.Settings.currency | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					    ); | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					
 | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					    const portfolioOrders: PortfolioOrder[] = orders | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      .filter((portfolioItem) => { | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					        if (isAfter(parseISO(portfolioItem.date), endOfToday())) { | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					        if (isAfter(parseISO(portfolioItem.getDate()), endOfToday())) { | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					          // Filter out future dates
 | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					          return false; | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					        } | 
				
			
			
		
	
	
		
			
				
					| 
						
						
						
							
								
							
						
					 | 
				
				 | 
				
					@ -170,18 +188,70 @@ export class PortfolioService { | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					        } | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					
 | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					        return ( | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					          isSameDay(parseISO(portfolioItem.date), dateRangeDate) || | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					          isAfter(parseISO(portfolioItem.date), dateRangeDate) | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					          isSameDay(parseISO(portfolioItem.getDate()), dateRangeDate) || | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					          isAfter(parseISO(portfolioItem.getDate()), dateRangeDate) | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					        ); | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      }) | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      .map((portfolioItem) => { | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					        return { | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					          date: format(parseISO(portfolioItem.date), 'yyyy-MM-dd'), | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					          grossPerformancePercent: portfolioItem.grossPerformancePercent, | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					          marketPrice: portfolioItem.value ?? null, | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					          value: portfolioItem.value - portfolioItem.investment ?? null | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					        }; | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      }); | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      .map((order) => ({ | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					        date: order.getDate().substr(0, 10), | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					        quantity: new Big(order.getQuantity()), | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					        symbol: order.getSymbol(), | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					        type: order.getType(), | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					        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( | 
				
			
			
		
	
	
		
			
				
					| 
						
							
								
							
						
						
						
					 | 
				
				 | 
				
					
  |