@ -1,45 +1,34 @@
import { TimelineInfoInterfa ce } from '@ghostfolio/api/app/portfolio/interfaces/timeline-info.interfa ce' ;
import { ExchangeRateDataServi ce } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.servi ce' ;
import { IDataGatheringItem } from '@ghostfolio/api/services/interfaces/interfaces' ;
import { DATE_FORMAT , parseDate , resetHours } from '@ghostfolio/common/helper' ;
import {
DataProviderInfo ,
HistoricalDataItem ,
InvestmentItem ,
ResponseError ,
SymbolMetrics ,
TimelinePosition
} from '@ghostfolio/common/interfaces' ;
import { GroupBy } from '@ghostfolio/common/types' ;
import { Logger } from '@nestjs/common' ;
import { Type as TypeOfOrder } from '@prisma/client' ;
import Big from 'big.js' ;
import {
addDays ,
addMilliseconds ,
addMonths ,
addYears ,
endOfDay ,
format ,
isAfter ,
isBefore ,
isSameDay ,
isSameMonth ,
isSameYear ,
max ,
min ,
set ,
subDays
} from 'date-fns' ;
import { 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
} from './interfaces/timeline-specification.interface' ;
import { TransactionPointSymbol } from './interfaces/transaction-point-symbol.interface' ;
import { TransactionPoint } from './interfaces/transaction-point.interface' ;
@ -52,23 +41,29 @@ export class PortfolioCalculator {
private currency : string ;
private currentRateService : CurrentRateService ;
private dataProviderInfos : DataProviderInfo [ ] ;
private exchangeRateDataService : ExchangeRateDataService ;
private orders : PortfolioOrder [ ] ;
private transactionPoints : TransactionPoint [ ] ;
public constructor ( {
currency ,
currentRateService ,
exchangeRateDataService ,
orders
} : {
currency : string ;
currentRateService : CurrentRateService ;
exchangeRateDataService : ExchangeRateDataService ;
orders : PortfolioOrder [ ] ;
} ) {
this . currency = currency ;
this . currentRateService = currentRateService ;
this . exchangeRateDataService = exchangeRateDataService ;
this . orders = orders ;
this . orders . sort ( ( a , b ) = > a . date ? . localeCompare ( b . date ) ) ;
this . orders . sort ( ( a , b ) = > {
return a . date ? . localeCompare ( b . date ) ;
} ) ;
}
public computeTransactionPoints() {
@ -212,12 +207,17 @@ export class PortfolioCalculator {
this . transactionPoints = transactionPoints ;
}
public async getChartData (
public async getChartData ( {
start : Date ,
end = new Date ( Date . now ( ) ) ,
step = 1 ,
calculateTimeWeightedPerformance = false
) {
} : {
end? : Date ;
start : Date ;
step? : number ;
calculateTimeWeightedPerformance? : boolean ;
} ) : Promise < HistoricalDataItem [ ] > {
const symbols : { [ symbol : string ] : boolean } = { } ;
const transactionPointsBeforeEndDate =
@ -407,8 +407,10 @@ export class PortfolioCalculator {
valuesBySymbol : {
[ symbol : string ] : {
currentValues : { [ date : string ] : Big } ;
investmentValues : { [ date : string ] : Big } ;
maxInvestmentValues : { [ date : string ] : Big } ;
currentValuesWithCurrencyEffect : { [ date : string ] : Big } ;
investmentValuesAccumulated : { [ date : string ] : Big } ;
investmentValuesAccumulatedWithCurrencyEffect : { [ date : string ] : Big } ;
investmentValuesWithCurrencyEffect : { [ date : string ] : Big } ;
netPerformanceValues : { [ date : string ] : Big } ;
netPerformanceValuesPercentage : { [ date : string ] : Big } ;
} ;
@ -417,8 +419,10 @@ export class PortfolioCalculator {
for ( const symbol of Object . keys ( symbols ) ) {
const {
currentValues ,
investmentValues ,
maxInvestmentValues ,
currentValuesWithCurrencyEffect ,
investmentValuesAccumulated ,
investmentValuesAccumulatedWithCurrencyEffect ,
investmentValuesWithCurrencyEffect ,
netPerformanceValues ,
netPerformanceValuesPercentage
} = this . getSymbolMetrics ( {
@ -427,13 +431,17 @@ export class PortfolioCalculator {
start ,
step ,
symbol ,
exchangeRates :
exchangeRatesByCurrency [ ` ${ currencies [ symbol ] } ${ this . currency } ` ] ,
isChartMode : true
} ) ;
valuesBySymbol [ symbol ] = {
currentValues ,
investmentValues ,
maxInvestmentValues ,
currentValuesWithCurrencyEffect ,
investmentValuesAccumulated ,
investmentValuesAccumulatedWithCurrencyEffect ,
investmentValuesWithCurrencyEffect ,
netPerformanceValues ,
netPerformanceValuesPercentage
} ;
@ -524,9 +532,13 @@ export class PortfolioCalculator {
currentValue : new Big ( 0 ) ,
grossPerformance : new Big ( 0 ) ,
grossPerformancePercentage : new Big ( 0 ) ,
grossPerformancePercentageWithCurrencyEffect : new Big ( 0 ) ,
grossPerformanceWithCurrencyEffect : new Big ( 0 ) ,
hasErrors : false ,
netPerformance : new Big ( 0 ) ,
netPerformancePercentage : new Big ( 0 ) ,
netPerformancePercentageWithCurrencyEffect : new Big ( 0 ) ,
netPerformanceWithCurrencyEffect : new Big ( 0 ) ,
positions : [ ] ,
totalInvestment : new Big ( 0 )
} ;
@ -535,11 +547,11 @@ export class PortfolioCalculator {
const lastTransactionPoint =
transactionPointsBeforeEndDate [ transactionPointsBeforeEndDate . length - 1 ] ;
let firstTransactionPoint : TransactionPoint = null ;
let firstIndex = transactionPointsBeforeEndDate . length ;
let dates = [ ] ;
const dataGatheringItems : IDataGatheringItem [ ] = [ ] ;
const currencies : { [ symbol : string ] : string } = { } ;
const dataGatheringItems : IDataGatheringItem [ ] = [ ] ;
let dates : Date [ ] = [ ] ;
let firstIndex = transactionPointsBeforeEndDate . length ;
let firstTransactionPoint : TransactionPoint = null ;
dates . push ( resetHours ( start ) ) ;
for ( const item of transactionPointsBeforeEndDate [ firstIndex - 1 ] . items ) {
@ -547,8 +559,10 @@ export class PortfolioCalculator {
dataSource : item.dataSource ,
symbol : item . symbol
} ) ;
currencies [ item . symbol ] = item . currency ;
}
for ( let i = 0 ; i < transactionPointsBeforeEndDate . length ; i ++ ) {
if (
! isBefore ( parseDate ( transactionPointsBeforeEndDate [ i ] . date ) , start ) &&
@ -580,22 +594,31 @@ export class PortfolioCalculator {
dates . map ( ( date ) = > {
return date . getTime ( ) ;
} )
) . map ( ( timestamp ) = > {
)
. map ( ( timestamp ) = > {
return new Date ( timestamp ) ;
} )
. sort ( ( a , b ) = > {
return a . getTime ( ) - b . getTime ( ) ;
} ) ;
let exchangeRatesByCurrency =
await this . exchangeRateDataService . getExchangeRatesByCurrency ( {
currencies : uniq ( Object . values ( currencies ) ) ,
endDate : endOfDay ( end ) ,
startDate : parseDate ( this . transactionPoints ? . [ 0 ] ? . date ) ,
targetCurrency : this.currency
} ) ;
dates . sort ( ( a , b ) = > a . getTime ( ) - b . getTime ( ) ) ;
const {
dataProviderInfos ,
errors : currentRateErrors ,
values : marketSymbols
} = await this . currentRateService . getValues ( {
currencies ,
dataGatheringItems ,
dateQuery : {
in : dates
} ,
userCurrency : this.currency
}
} ) ;
this . dataProviderInfos = dataProviderInfos ;
@ -606,12 +629,14 @@ export class PortfolioCalculator {
for ( const marketSymbol of marketSymbols ) {
const date = format ( marketSymbol . date , DATE_FORMAT ) ;
if ( ! marketSymbolMap [ date ] ) {
marketSymbolMap [ date ] = { } ;
}
if ( marketSymbol . marketPriceInBaseCurrency ) {
if ( marketSymbol . marketPrice ) {
marketSymbolMap [ date ] [ marketSymbol . symbol ] = new Big (
marketSymbol . marketPriceInBaseCurrency
marketSymbol . marketPrice
) ;
}
}
@ -629,19 +654,35 @@ export class PortfolioCalculator {
const errors : ResponseError [ 'errors' ] = [ ] ;
for ( const item of lastTransactionPoint . items ) {
const marketValue = marketSymbolMap [ endDateString ] ? . [ item . symbol ] ;
const marketPriceInBaseCurrency = marketSymbolMap [ endDateString ] ? . [
item . symbol
] ? . mul (
exchangeRatesByCurrency [ ` ${ item . currency } ${ this . currency } ` ] ? . [
endDateString
]
) ;
const {
grossPerformance ,
grossPerformancePercentage ,
grossPerformancePercentageWithCurrencyEffect ,
grossPerformanceWithCurrencyEffect ,
hasErrors ,
initialValue ,
netPerformance ,
netPerformancePercentage
netPerformancePercentage ,
netPerformancePercentageWithCurrencyEffect ,
netPerformanceWithCurrencyEffect ,
timeWeightedInvestment ,
timeWeightedInvestmentWithCurrencyEffect ,
totalInvestment ,
totalInvestmentWithCurrencyEffect
} = this . getSymbolMetrics ( {
end ,
marketSymbolMap ,
start ,
exchangeRates :
exchangeRatesByCurrency [ ` ${ item . currency } ${ this . currency } ` ] ,
symbol : item . symbol
} ) ;
@ -649,6 +690,8 @@ export class PortfolioCalculator {
initialValues [ item . symbol ] = initialValue ;
positions . push ( {
timeWeightedInvestment ,
timeWeightedInvestmentWithCurrencyEffect ,
averagePrice : item.quantity.eq ( 0 )
? new Big ( 0 )
: item . investment . div ( item . quantity ) ,
@ -660,12 +703,28 @@ export class PortfolioCalculator {
grossPerformancePercentage : ! hasErrors
? grossPerformancePercentage ? ? null
: null ,
investment : item.investment ,
marketPrice : marketValue?.toNumber ( ) ? ? null ,
grossPerformancePercentageWithCurrencyEffect : ! hasErrors
? grossPerformancePercentageWithCurrencyEffect ? ? null
: null ,
grossPerformanceWithCurrencyEffect : ! hasErrors
? grossPerformanceWithCurrencyEffect ? ? null
: null ,
investment : totalInvestment ,
investmentWithCurrencyEffect : totalInvestmentWithCurrencyEffect ,
marketPrice :
marketSymbolMap [ endDateString ] ? . [ item . symbol ] ? . toNumber ( ) ? ? null ,
marketPriceInBaseCurrency :
marketPriceInBaseCurrency ? . toNumber ( ) ? ? null ,
netPerformance : ! hasErrors ? netPerformance ? ? null : null ,
netPerformancePercentage : ! hasErrors
? netPerformancePercentage ? ? null
: null ,
netPerformancePercentageWithCurrencyEffect : ! hasErrors
? netPerformancePercentageWithCurrencyEffect ? ? null
: null ,
netPerformanceWithCurrencyEffect : ! hasErrors
? netPerformanceWithCurrencyEffect ? ? null
: null ,
quantity : item.quantity ,
symbol : item . symbol ,
tags : item.tags ,
@ -714,107 +773,21 @@ export class PortfolioCalculator {
} ) ;
}
public getInvestmentsByGroup (
groupBy : GroupBy
) : { date : string ; investment : Big } [ ] {
if ( this . orders . length === 0 ) {
return [ ] ;
}
const investments : { date : string ; investment : Big } [ ] = [ ] ;
let currentDate : Date ;
let investmentByGroup = new Big ( 0 ) ;
for ( const [ index , order ] of this . orders . entries ( ) ) {
if (
isSameYear ( parseDate ( order . date ) , currentDate ) &&
( groupBy === 'year' || isSameMonth ( parseDate ( order . date ) , currentDate ) )
) {
// Same group: Add up investments
investmentByGroup = investmentByGroup . plus (
order . quantity . mul ( order . unitPrice ) . mul ( this . getFactor ( order . type ) )
) ;
} else {
// New group: Store previous group and reset
if ( currentDate ) {
investments . push ( {
date : format (
set ( currentDate , {
date : 1 ,
month : groupBy === 'year' ? 0 : currentDate.getMonth ( )
} ) ,
DATE_FORMAT
) ,
investment : investmentByGroup
} ) ;
}
currentDate = parseDate ( order . date ) ;
investmentByGroup = order . quantity
. mul ( order . unitPrice )
. mul ( this . getFactor ( order . type ) ) ;
}
if ( index === this . orders . length - 1 ) {
// Store current group (latest order)
investments . push ( {
date : format (
set ( currentDate , {
date : 1 ,
month : groupBy === 'year' ? 0 : currentDate.getMonth ( )
} ) ,
DATE_FORMAT
) ,
investment : investmentByGroup
} ) ;
}
}
// Fill in the missing dates with investment = 0
const startDate = parseDate ( first ( this . orders ) . date ) ;
const endDate = parseDate ( last ( this . orders ) . date ) ;
const allDates : string [ ] = [ ] ;
currentDate = startDate ;
while ( currentDate <= endDate ) {
allDates . push (
format (
set ( currentDate , {
date : 1 ,
month : groupBy === 'year' ? 0 : currentDate.getMonth ( )
} ) ,
DATE_FORMAT
)
public getInvestmentsByGroup ( {
data ,
groupBy
} : {
data : HistoricalDataItem [ ] ;
groupBy : GroupBy ;
} ) : InvestmentItem [ ] {
const groupedData : { [ dateGroup : string ] : Big } = { } ;
for ( const { date , investmentValueWithCurrencyEffect } of data ) {
const dateGroup =
groupBy === 'month' ? date . substring ( 0 , 7 ) : date . substring ( 0 , 4 ) ;
groupedData [ dateGroup ] = ( groupedData [ dateGroup ] ? ? new Big ( 0 ) ) . plus (
investmentValueWithCurrencyEffect
) ;
currentDate . setMonth ( currentDate . getMonth ( ) + 1 ) ;
}
for ( const date of allDates ) {
const existingInvestment = investments . find ( ( investment ) = > {
return investment . date === date ;
} ) ;
if ( ! existingInvestment ) {
investments . push ( { date , investment : new Big ( 0 ) } ) ;
}
}
return sortBy ( investments , ( investment ) = > {
return investment . date ;
} ) ;
}
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 ;
@ -912,30 +885,56 @@ export class PortfolioCalculator {
) {
let currentValue = new Big ( 0 ) ;
let grossPerformance = new Big ( 0 ) ;
let grossPerformancePercentage = new Big ( 0 ) ;
let grossPerformanceWithCurrencyEffect = new Big ( 0 ) ;
let hasErrors = false ;
let netPerformance = new Big ( 0 ) ;
let netPerformancePercentage = new Big ( 0 ) ;
let sumOfWeights = new Big ( 0 ) ;
let netPerformanceWithCurrencyEffect = new Big ( 0 ) ;
let totalInvestment = new Big ( 0 ) ;
let totalInvestmentWithCurrencyEffect = new Big ( 0 ) ;
let totalTimeWeightedInvestment = new Big ( 0 ) ;
let totalTimeWeightedInvestmentWithCurrencyEffect = new Big ( 0 ) ;
for ( const currentPosition of positions ) {
if ( currentPosition . marketPrice ) {
if (
currentPosition . investment &&
currentPosition . marketPriceInBaseCurrency
) {
currentValue = currentValue . plus (
new Big ( currentPosition . marketPrice ) . mul ( currentPosition . quantity )
new Big ( currentPosition . marketPriceInBaseCurrency ) . mul (
currentPosition . quantity
)
) ;
} else {
hasErrors = true ;
}
if ( currentPosition . investment ) {
totalInvestment = totalInvestment . plus ( currentPosition . investment ) ;
totalInvestmentWithCurrencyEffect =
totalInvestmentWithCurrencyEffect . plus (
currentPosition . investmentWithCurrencyEffect
) ;
} else {
hasErrors = true ;
}
if ( currentPosition . grossPerformance !== null ) {
grossPerformance = grossPerformance . plus (
currentPosition . grossPerformance
) ;
grossPerformanceWithCurrencyEffect =
grossPerformanceWithCurrencyEffect . plus (
currentPosition . grossPerformanceWithCurrencyEffect
) ;
netPerformance = netPerformance . plus ( currentPosition . netPerformance ) ;
netPerformanceWithCurrencyEffect =
netPerformanceWithCurrencyEffect . plus (
currentPosition . netPerformanceWithCurrencyEffect
) ;
} else if ( ! currentPosition . quantity . eq ( 0 ) ) {
hasErrors = true ;
}
@ -961,6 +960,7 @@ export class PortfolioCalculator {
` Missing historical market data for ${ currentPosition . symbol } ( ${ currentPosition . dataSource } ) ` ,
'PortfolioCalculator'
) ;
hasErrors = true ;
}
}
@ -976,130 +976,30 @@ export class PortfolioCalculator {
return {
currentValue ,
grossPerformance ,
grossPerformancePercentage ,
grossPerformanceWithCurrencyEffect ,
hasErrors ,
netPerformance ,
netPerformancePercentage ,
totalInvestment
} ;
}
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 ( {
currencies ,
dataGatheringItems ,
dateQuery : {
gte : startDate ,
lt : endOfDay ( endDate )
} ,
userCurrency : this.currency
} ) ;
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 . marketPriceInBaseCurrency ) {
marketSymbolMap [ date ] [ marketSymbol . symbol ] = new Big (
marketSymbol . marketPriceInBaseCurrency
) ;
}
}
}
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
netPerformanceWithCurrencyEffect ,
totalInvestment ,
totalInvestmentWithCurrencyEffect ,
netPerformancePercentage : totalTimeWeightedInvestment.eq ( 0 )
? new Big ( 0 )
: netPerformance . div ( totalTimeWeightedInvestment ) ,
netPerformancePercentageWithCurrencyEffect :
totalTimeWeightedInvestmentWithCurrencyEffect . eq ( 0 )
? new Big ( 0 )
: netPerformanceWithCurrencyEffect . div (
totalTimeWeightedInvestmentWithCurrencyEffect
) ,
grossPerformancePercentage : totalTimeWeightedInvestment.eq ( 0 )
? new Big ( 0 )
: grossPerformance . div ( totalTimeWeightedInvestment ) ,
grossPerformancePercentageWithCurrencyEffect :
totalTimeWeightedInvestmentWithCurrencyEffect . eq ( 0 )
? new Big ( 0 )
: grossPerformanceWithCurrencyEffect . div (
totalTimeWeightedInvestmentWithCurrencyEffect
)
} ;
}
@ -1122,19 +1022,9 @@ export class PortfolioCalculator {
return factor ;
}
private addToDate ( date : Date , accuracy : Accuracy ) : Date {
switch ( accuracy ) {
case 'day' :
return addDays ( date , 1 ) ;
case 'month' :
return addMonths ( date , 1 ) ;
case 'year' :
return addYears ( date , 1 ) ;
}
}
private getSymbolMetrics ( {
end ,
exchangeRates ,
isChartMode = false ,
marketSymbolMap ,
start ,
@ -1142,6 +1032,7 @@ export class PortfolioCalculator {
symbol
} : {
end : Date ;
exchangeRates : { [ dateString : string ] : number } ;
isChartMode? : boolean ;
marketSymbolMap : {
[ date : string ] : { [ symbol : string ] : Big } ;
@ -1149,24 +1040,85 @@ export class PortfolioCalculator {
start : Date ;
step? : number ;
symbol : string ;
} ) {
let orders : PortfolioOrderItem [ ] = this . orders . filter ( ( order ) = > {
} ) : SymbolMetrics {
const currentExchangeRate = exchangeRates [ format ( new Date ( ) , DATE_FORMAT ) ] ;
const currentValues : { [ date : string ] : Big } = { } ;
const currentValuesWithCurrencyEffect : { [ date : string ] : Big } = { } ;
let fees = new Big ( 0 ) ;
let feesAtStartDate = new Big ( 0 ) ;
let feesAtStartDateWithCurrencyEffect = new Big ( 0 ) ;
let feesWithCurrencyEffect = new Big ( 0 ) ;
let grossPerformance = new Big ( 0 ) ;
let grossPerformanceWithCurrencyEffect = new Big ( 0 ) ;
let grossPerformanceAtStartDate = new Big ( 0 ) ;
let grossPerformanceAtStartDateWithCurrencyEffect = new Big ( 0 ) ;
let grossPerformanceFromSells = new Big ( 0 ) ;
let grossPerformanceFromSellsWithCurrencyEffect = new Big ( 0 ) ;
let initialValue : Big ;
let initialValueWithCurrencyEffect : Big ;
let investmentAtStartDate : Big ;
let investmentAtStartDateWithCurrencyEffect : Big ;
const investmentValuesAccumulated : { [ date : string ] : Big } = { } ;
const investmentValuesAccumulatedWithCurrencyEffect : {
[ date : string ] : Big ;
} = { } ;
const investmentValuesWithCurrencyEffect : { [ date : string ] : Big } = { } ;
let lastAveragePrice = new Big ( 0 ) ;
let lastAveragePriceWithCurrencyEffect = new Big ( 0 ) ;
const netPerformanceValues : { [ date : string ] : Big } = { } ;
const netPerformanceValuesWithCurrencyEffect : { [ date : string ] : Big } = { } ;
const timeWeightedInvestmentValues : { [ date : string ] : Big } = { } ;
const timeWeightedInvestmentValuesWithCurrencyEffect : {
[ date : string ] : Big ;
} = { } ;
let totalInvestment = new Big ( 0 ) ;
let totalInvestmentWithCurrencyEffect = new Big ( 0 ) ;
let totalInvestmentWithGrossPerformanceFromSell = new Big ( 0 ) ;
let totalInvestmentWithGrossPerformanceFromSellWithCurrencyEffect = new Big (
0
) ;
let totalUnits = new Big ( 0 ) ;
let valueAtStartDate : Big ;
let valueAtStartDateWithCurrencyEffect : Big ;
// Clone orders to keep the original values in this.orders
let orders : PortfolioOrderItem [ ] = cloneDeep ( this . orders ) . filter (
( order ) = > {
return order . symbol === symbol ;
} ) ;
}
) ;
if ( orders . length <= 0 ) {
return {
currentValues : { } ,
currentValuesWithCurrencyEffect : { } ,
grossPerformance : new Big ( 0 ) ,
grossPerformancePercentage : new Big ( 0 ) ,
grossPerformancePercentageWithCurrencyEffect : new Big ( 0 ) ,
grossPerformanceWithCurrencyEffect : new Big ( 0 ) ,
hasErrors : false ,
initialValue : new Big ( 0 ) ,
investmentValues : { } ,
maxInvestmentValues : { } ,
initialValueWithCurrencyEffect : new Big ( 0 ) ,
investmentValuesAccumulated : { } ,
investmentValuesAccumulatedWithCurrencyEffect : { } ,
investmentValuesWithCurrencyEffect : { } ,
netPerformance : new Big ( 0 ) ,
netPerformancePercentage : new Big ( 0 ) ,
netPerformancePercentageWithCurrencyEffect : new Big ( 0 ) ,
netPerformanceValues : { } ,
netPerformanceValuesPercentage : { }
netPerformanceValuesPercentage : { } ,
netPerformanceValuesWithCurrencyEffect : { } ,
netPerformanceWithCurrencyEffect : new Big ( 0 ) ,
timeWeightedInvestment : new Big ( 0 ) ,
timeWeightedInvestmentValues : { } ,
timeWeightedInvestmentValuesWithCurrencyEffect : { } ,
timeWeightedInvestmentWithCurrencyEffect : new Big ( 0 ) ,
totalInvestment : new Big ( 0 ) ,
totalInvestmentWithCurrencyEffect : new Big ( 0 )
} ;
}
@ -1183,12 +1135,32 @@ export class PortfolioCalculator {
( ! unitPriceAtStartDate && isBefore ( dateOfFirstTransaction , start ) )
) {
return {
currentValues : { } ,
currentValuesWithCurrencyEffect : { } ,
grossPerformance : new Big ( 0 ) ,
grossPerformancePercentage : new Big ( 0 ) ,
grossPerformancePercentageWithCurrencyEffect : new Big ( 0 ) ,
grossPerformanceWithCurrencyEffect : new Big ( 0 ) ,
hasErrors : true ,
initialValue : new Big ( 0 ) ,
initialValueWithCurrencyEffect : new Big ( 0 ) ,
investmentValuesAccumulated : { } ,
investmentValuesAccumulatedWithCurrencyEffect : { } ,
investmentValuesWithCurrencyEffect : { } ,
netPerformance : new Big ( 0 ) ,
netPerformancePercentage : new Big ( 0 ) ,
grossPerformance : new Big ( 0 ) ,
grossPerformancePercentage : new Big ( 0 )
grossPerformancePercentage : new Big ( 0 ) ,
netPerformancePercentageWithCurrencyEffect : new Big ( 0 ) ,
netPerformanceValues : { } ,
netPerformanceValuesWithCurrencyEffect : { } ,
netPerformanceWithCurrencyEffect : new Big ( 0 ) ,
timeWeightedInvestment : new Big ( 0 ) ,
timeWeightedInvestmentValues : { } ,
timeWeightedInvestmentValuesWithCurrencyEffect : { } ,
timeWeightedInvestmentWithCurrencyEffect : new Big ( 0 ) ,
totalInvestment : new Big ( 0 ) ,
totalInvestmentWithCurrencyEffect : new Big ( 0 )
} ;
}
@ -1529,19 +1501,66 @@ export class PortfolioCalculator {
transactionInvestment
) ) ;
totalInvestmentWithGrossPerformanceFromSellWithCurrencyEffect =
totalInvestmentWithGrossPerformanceFromSellWithCurrencyEffect
. plus ( transactionInvestmentWithCurrencyEffect )
. plus ( grossPerformanceFromSellWithCurrencyEffect ) ;
totalInvestmentWithGrossPerformanceFromSellWithCurrencyEffect =
totalInvestmentWithGrossPerformanceFromSellWithCurrencyEffect
. plus ( transactionInvestmentWithCurrencyEffect )
. plus ( grossPerformanceFromSellWithCurrencyEffect ) ;
lastAveragePrice = totalUnits . eq ( 0 )
? new Big ( 0 )
: totalInvestmentWithGrossPerformanceFromSell . div ( totalUnits ) ;
lastAveragePriceWithCurrencyEffect = totalUnits . eq ( 0 )
? new Big ( 0 )
: totalInvestmentWithGrossPerformanceFromSellWithCurrencyEffect . div (
totalUnits
) ;
if ( PortfolioCalculator . ENABLE_LOGGING ) {
console . log (
'totalInvestmentWithGrossPerformanceFromSell' ,
totalInvestmentWithGrossPerformanceFromSell . toNumber ( )
) ;
console . log (
'totalInvestmentWithGrossPerformanceFromSellWithCurrencyEffect' ,
totalInvestmentWithGrossPerformanceFromSellWithCurrencyEffect . toNumber ( )
) ;
console . log (
'grossPerformanceFromSells' ,
grossPerformanceFromSells . toNumber ( )
) ;
console . log (
'grossPerformanceFromSellWithCurrencyEffect' ,
grossPerformanceFromSellWithCurrencyEffect . toNumber ( )
) ;
}
const newGrossPerformance = valueOfInvestment
. minus ( totalInvestment )
. plus ( grossPerformanceFromSells ) ;
const newGrossPerformanceWithCurrencyEffect =
valueOfInvestmentWithCurrencyEffect
. minus ( totalInvestmentWithCurrencyEffect )
. plus ( grossPerformanceFromSellsWithCurrencyEffect ) ;
grossPerformance = newGrossPerformance ;
grossPerformanceWithCurrencyEffect =
newGrossPerformanceWithCurrencyEffect ;
if ( order . itemType === 'start' ) {
feesAtStartDate = fees ;
feesAtStartDateWithCurrencyEffect = feesWithCurrencyEffect ;
grossPerformanceAtStartDate = grossPerformance ;
grossPerformanceAtStartDateWithCurrencyEffect =
grossPerformanceWithCurrencyEffect ;
}
this . calculatePerformancesForDate (
@ -2025,18 +2044,31 @@ export class PortfolioCalculator {
Unit price : $ { orders [ indexOfStartOrder ] . unitPrice . toFixed (
2
) } - > $ { unitPriceAtEndDate . toFixed ( 2 ) }
Average price : $ { averagePriceAtStartDate . toFixed (
2
) } - > $ { averagePriceAtEndDate . toFixed ( 2 ) }
Total investment : $ { totalInvestment . toFixed ( 2 ) }
Max . total investment : $ { maxTotalInvestment . toFixed ( 2 ) }
Total investment with currency effect : $ { totalInvestmentWithCurrencyEffect . toFixed (
2
) }
Time weighted investment with currency effect : $ { timeWeightedAverageInvestmentBetweenStartAndEndDateWithCurrencyEffect . toFixed (
2
) }
Gross performance : $ { totalGrossPerformance . toFixed (
2
) } / $ { grossPerformancePercentage . mul ( 100 ) . toFixed ( 2 ) } %
Gross performance with currency effect : $ { totalGrossPerformanceWithCurrencyEffect . toFixed (
2
) } / $ { grossPerformancePercentageWithCurrencyEffect
. mul ( 100 )
. toFixed ( 2 ) } %
Fees per unit : $ { feesPerUnit . toFixed ( 2 ) }
Fees per unit with currency effect : $ { feesPerUnitWithCurrencyEffect . toFixed (
2
) }
Net performance : $ { totalNetPerformance . toFixed (
2
) } / $ { netPerformancePercentage . mul ( 100 ) . toFixed ( 2 ) } % `
) } / $ { netPerformancePercentage . mul ( 100 ) . toFixed ( 2 ) } %
Net performance with currency effect : $ { totalNetPerformanceWithCurrencyEffect . toFixed (
2
) } / $ { netPerformancePercentageWithCurrencyEffect . mul ( 100 ) . toFixed ( 2 ) } % `
) ;
}
}