@ -1,4 +1,3 @@
import { TimelineInfoInterface } from '@ghostfolio/api/app/portfolio/interfaces/timeline-info.interface' ;
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service' ;
import { IDataGatheringItem } from '@ghostfolio/api/services/interfaces/interfaces' ;
import { DATE_FORMAT , parseDate , resetHours } from '@ghostfolio/common/helper' ;
@ -20,32 +19,19 @@ import {
differenceInDays ,
endOfDay ,
format ,
isAfter ,
isBefore ,
isSameDay ,
isSameMonth ,
isSameYear ,
max ,
min ,
set ,
subDays
} from 'date-fns' ;
import {
cloneDeep ,
first ,
flatten ,
isNumber ,
last ,
sortBy ,
uniq
} from 'lodash' ;
import { cloneDeep , first , isNumber , last , sortBy , uniq } from 'lodash' ;
import { CurrentRateService } from './current-rate.service' ;
import { CurrentPositions } from './interfaces/current-positions.interface' ;
import { GetValueObject } from './interfaces/get-value-object.interface' ;
import { PortfolioOrderItem } from './interfaces/portfolio-calculator.interface' ;
import { PortfolioOrder } from './interfaces/portfolio-order.interface' ;
import { TimelinePeriod } from './interfaces/timeline-period.interface' ;
import {
Accuracy ,
TimelineSpecification
@ -776,107 +762,6 @@ export class PortfolioCalculator {
} ) ;
}
public async calculateTimeline (
timelineSpecification : TimelineSpecification [ ] ,
endDate : string
) : Promise < TimelineInfoInterface > {
if ( timelineSpecification . length === 0 ) {
return {
maxNetPerformance : new Big ( 0 ) ,
minNetPerformance : new Big ( 0 ) ,
timelinePeriods : [ ]
} ;
}
const startDate = timelineSpecification [ 0 ] . start ;
const start = parseDate ( startDate ) ;
const end = parseDate ( endDate ) ;
const timelinePeriodPromises : Promise < TimelineInfoInterface > [ ] = [ ] ;
let i = 0 ;
let j = - 1 ;
for (
let currentDate = start ;
! isAfter ( currentDate , end ) ;
currentDate = this . addToDate (
currentDate ,
timelineSpecification [ i ] . accuracy
)
) {
if ( this . isNextItemActive ( timelineSpecification , currentDate , i ) ) {
i ++ ;
}
while (
j + 1 < this . transactionPoints . length &&
! isAfter ( parseDate ( this . transactionPoints [ j + 1 ] . date ) , currentDate )
) {
j ++ ;
}
let periodEndDate = currentDate ;
if ( timelineSpecification [ i ] . accuracy === 'day' ) {
let nextEndDate = end ;
if ( j + 1 < this . transactionPoints . length ) {
nextEndDate = parseDate ( this . transactionPoints [ j + 1 ] . date ) ;
}
periodEndDate = min ( [
addMonths ( currentDate , 3 ) ,
max ( [ currentDate , nextEndDate ] )
] ) ;
}
const timePeriodForDates = this . getTimePeriodForDate (
j ,
currentDate ,
endOfDay ( periodEndDate )
) ;
currentDate = periodEndDate ;
if ( timePeriodForDates != null ) {
timelinePeriodPromises . push ( timePeriodForDates ) ;
}
}
let minNetPerformance = new Big ( 0 ) ;
let maxNetPerformance = new Big ( 0 ) ;
const timelineInfoInterfaces : TimelineInfoInterface [ ] = await Promise . all (
timelinePeriodPromises
) ;
try {
minNetPerformance = timelineInfoInterfaces
. map ( ( timelineInfo ) = > timelineInfo . minNetPerformance )
. filter ( ( performance ) = > performance !== null )
. reduce ( ( minPerformance , current ) = > {
if ( minPerformance . lt ( current ) ) {
return minPerformance ;
} else {
return current ;
}
} ) ;
maxNetPerformance = timelineInfoInterfaces
. map ( ( timelineInfo ) = > timelineInfo . maxNetPerformance )
. filter ( ( performance ) = > performance !== null )
. reduce ( ( maxPerformance , current ) = > {
if ( maxPerformance . gt ( current ) ) {
return maxPerformance ;
} else {
return current ;
}
} ) ;
} catch { }
const timelinePeriods = timelineInfoInterfaces . map (
( timelineInfo ) = > timelineInfo . timelinePeriods
) ;
return {
maxNetPerformance ,
minNetPerformance ,
timelinePeriods : flatten ( timelinePeriods )
} ;
}
private calculateOverallPerformance ( positions : TimelinePosition [ ] ) {
let currentValue = new Big ( 0 ) ;
let grossPerformance = new Big ( 0 ) ;
@ -983,123 +868,6 @@ export class PortfolioCalculator {
} ;
}
private async getTimePeriodForDate (
j : number ,
startDate : Date ,
endDate : Date
) : Promise < TimelineInfoInterface > {
let investment : Big = new Big ( 0 ) ;
let fees : Big = new Big ( 0 ) ;
const marketSymbolMap : {
[ date : string ] : { [ symbol : string ] : Big } ;
} = { } ;
if ( j >= 0 ) {
const currencies : { [ name : string ] : string } = { } ;
const dataGatheringItems : IDataGatheringItem [ ] = [ ] ;
for ( const item of this . transactionPoints [ j ] . items ) {
currencies [ item . symbol ] = item . currency ;
dataGatheringItems . push ( {
dataSource : item.dataSource ,
symbol : item . symbol
} ) ;
investment = investment . plus ( item . investment ) ;
fees = fees . plus ( item . fee ) ;
}
let marketSymbols : GetValueObject [ ] = [ ] ;
if ( dataGatheringItems . length > 0 ) {
try {
const { values } = await this . currentRateService . getValues ( {
dataGatheringItems ,
dateQuery : {
gte : startDate ,
lt : endOfDay ( endDate )
}
} ) ;
marketSymbols = values ;
} catch ( error ) {
Logger . error (
` Failed to fetch info for date ${ startDate } with exception ` ,
error ,
'PortfolioCalculator'
) ;
return null ;
}
}
for ( const marketSymbol of marketSymbols ) {
const date = format ( marketSymbol . date , DATE_FORMAT ) ;
if ( ! marketSymbolMap [ date ] ) {
marketSymbolMap [ date ] = { } ;
}
if ( marketSymbol . marketPrice ) {
marketSymbolMap [ date ] [ marketSymbol . symbol ] = new Big (
marketSymbol . marketPrice
) ;
}
}
}
const results : TimelinePeriod [ ] = [ ] ;
let maxNetPerformance : Big = null ;
let minNetPerformance : Big = null ;
for (
let currentDate = startDate ;
isBefore ( currentDate , endDate ) ;
currentDate = addDays ( currentDate , 1 )
) {
let value = new Big ( 0 ) ;
const currentDateAsString = format ( currentDate , DATE_FORMAT ) ;
let invalid = false ;
if ( j >= 0 ) {
for ( const item of this . transactionPoints [ j ] . items ) {
if (
! marketSymbolMap [ currentDateAsString ] ? . hasOwnProperty ( item . symbol )
) {
invalid = true ;
break ;
}
value = value . plus (
item . quantity . mul ( marketSymbolMap [ currentDateAsString ] [ item . symbol ] )
) ;
}
}
if ( ! invalid ) {
const grossPerformance = value . minus ( investment ) ;
const netPerformance = grossPerformance . minus ( fees ) ;
if (
minNetPerformance === null ||
minNetPerformance . gt ( netPerformance )
) {
minNetPerformance = netPerformance ;
}
if (
maxNetPerformance === null ||
maxNetPerformance . lt ( netPerformance )
) {
maxNetPerformance = netPerformance ;
}
const result = {
grossPerformance ,
investment ,
netPerformance ,
value ,
date : currentDateAsString
} ;
results . push ( result ) ;
}
}
return {
maxNetPerformance ,
minNetPerformance ,
timelinePeriods : results
} ;
}
private getFactor ( type : TypeOfOrder ) {
let factor : number ;