@ -22,6 +22,7 @@ import {
} from '@ghostfolio/common/config' ;
} from '@ghostfolio/common/config' ;
import {
import {
DATE_FORMAT ,
DATE_FORMAT ,
getAssetProfileIdentifier ,
getSum ,
getSum ,
parseDate ,
parseDate ,
resetHours
resetHours
@ -72,8 +73,8 @@ export abstract class PortfolioCalculator {
protected accountBalanceItems : HistoricalDataItem [ ] ;
protected accountBalanceItems : HistoricalDataItem [ ] ;
protected activities : PortfolioOrder [ ] ;
protected activities : PortfolioOrder [ ] ;
protected activitiesBySymbol : {
protected activitiesByAssetProfileIdentifier : {
[ symbol : string ] : PortfolioOrder [ ] ;
[ assetProfileIdentifier : string ] : PortfolioOrder [ ] ;
} ;
} ;
private configurationService : ConfigurationService ;
private configurationService : ConfigurationService ;
@ -165,9 +166,12 @@ export abstract class PortfolioCalculator {
return a . date ? . localeCompare ( b . date ) ;
return a . date ? . localeCompare ( b . date ) ;
} ) ;
} ) ;
this . activitiesBySymbol = groupBy ( this . activities , ( { assetProfile } ) = > {
this . activitiesByAssetProfileIdentifier = groupBy (
return assetProfile . symbol ;
this . activities ,
} ) ;
( { assetProfile } ) = > {
return getAssetProfileIdentifier ( assetProfile ) ;
}
) ;
this . portfolioSnapshotService = portfolioSnapshotService ;
this . portfolioSnapshotService = portfolioSnapshotService ;
this . redisCacheService = redisCacheService ;
this . redisCacheService = redisCacheService ;
@ -221,8 +225,8 @@ export abstract class PortfolioCalculator {
} ;
} ;
}
}
const cashSymbol s = new Set < string > ( ) ;
const cashAssetProfileIdentifier s = new Set < string > ( ) ;
const currencies : { [ symbol : string ] : string } = { } ;
const currencies : { [ assetProfileIdentifier : string ] : string } = { } ;
const dataGatheringItems : DataGatheringItem [ ] = [ ] ;
const dataGatheringItems : DataGatheringItem [ ] = [ ] ;
let firstIndex = transactionPoints . length ;
let firstIndex = transactionPoints . length ;
let firstTransactionPoint : TransactionPoint = null ;
let firstTransactionPoint : TransactionPoint = null ;
@ -244,7 +248,7 @@ export abstract class PortfolioCalculator {
} ) ;
} ) ;
}
}
currencies [ symbol ] = currency ;
currencies [ getAssetProfileIdentifier ( { dataSource , symbol } ) ] = currency ;
}
}
for ( let i = 0 ; i < transactionPoints . length ; i ++ ) {
for ( let i = 0 ; i < transactionPoints . length ; i ++ ) {
@ -280,7 +284,7 @@ export abstract class PortfolioCalculator {
this . dataProviderInfos = dataProviderInfos ;
this . dataProviderInfos = dataProviderInfos ;
const marketSymbolMap : {
const marketSymbolMap : {
[ date : string ] : { [ symbol : string ] : Big } ;
[ date : string ] : { [ assetProfileIdentifier : string ] : Big } ;
} = { } ;
} = { } ;
for ( const marketSymbol of marketSymbols ) {
for ( const marketSymbol of marketSymbols ) {
@ -291,9 +295,8 @@ export abstract class PortfolioCalculator {
}
}
if ( marketSymbol . marketPrice ) {
if ( marketSymbol . marketPrice ) {
marketSymbolMap [ date ] [ marketSymbol . symbol ] = new Big (
marketSymbolMap [ date ] [ getAssetProfileIdentifier ( marketSymbol ) ] =
marketSymbol . marketPrice
new Big ( marketSymbol . marketPrice ) ;
) ;
}
}
}
}
@ -346,8 +349,8 @@ export abstract class PortfolioCalculator {
} ;
} ;
} = { } ;
} = { } ;
const valuesBySymbol : {
const valuesByAssetProfileIdentifier : {
[ symbol : string ] : {
[ assetProfileIdentifier : string ] : {
currentValues : { [ date : string ] : Big } ;
currentValues : { [ date : string ] : Big } ;
currentValuesWithCurrencyEffect : { [ date : string ] : Big } ;
currentValuesWithCurrencyEffect : { [ date : string ] : Big } ;
investmentValuesAccumulated : { [ date : string ] : Big } ;
investmentValuesAccumulated : { [ date : string ] : Big } ;
@ -362,8 +365,11 @@ export abstract class PortfolioCalculator {
} = { } ;
} = { } ;
for ( const item of lastTransactionPoint . items ) {
for ( const item of lastTransactionPoint . items ) {
const assetProfileIdentifier = getAssetProfileIdentifier ( item ) ;
const marketPriceInBaseCurrency = (
const marketPriceInBaseCurrency = (
marketSymbolMap [ endDateString ] ? . [ item . symbol ] ? ? item . averagePrice
marketSymbolMap [ endDateString ] ? . [ assetProfileIdentifier ] ? ?
item . averagePrice
) . mul (
) . mul (
exchangeRatesByCurrency [ ` ${ item . currency } ${ this . currency } ` ] ? . [
exchangeRatesByCurrency [ ` ${ item . currency } ${ this . currency } ` ] ? . [
endDateString
endDateString
@ -421,7 +427,8 @@ export abstract class PortfolioCalculator {
// contributes nothing but its balance to the performance calculation. It
// contributes nothing but its balance to the performance calculation. It
// is therefore excluded from the value and the investment, while still
// is therefore excluded from the value and the investment, while still
// contributing to the net worth.
// contributing to the net worth.
valuesBySymbol [ item . symbol ] = isCashInBaseCurrency
valuesByAssetProfileIdentifier [ assetProfileIdentifier ] =
isCashInBaseCurrency
? {
? {
currentValues : { } ,
currentValues : { } ,
currentValuesWithCurrencyEffect : { } ,
currentValuesWithCurrencyEffect : { } ,
@ -474,7 +481,9 @@ export abstract class PortfolioCalculator {
investment : totalInvestment ,
investment : totalInvestment ,
investmentWithCurrencyEffect : totalInvestmentWithCurrencyEffect ,
investmentWithCurrencyEffect : totalInvestmentWithCurrencyEffect ,
marketPrice :
marketPrice :
marketSymbolMap [ endDateString ] ? . [ item . symbol ] ? . toNumber ( ) ? ? 1 ,
marketSymbolMap [ endDateString ] ? . [
assetProfileIdentifier
] ? . toNumber ( ) ? ? 1 ,
marketPriceInBaseCurrency : marketPriceInBaseCurrency?.toNumber ( ) ? ? 1 ,
marketPriceInBaseCurrency : marketPriceInBaseCurrency?.toNumber ( ) ? ? 1 ,
netPerformance : ! hasErrors ? ( netPerformance ? ? null ) : null ,
netPerformance : ! hasErrors ? ( netPerformance ? ? null ) : null ,
netPerformancePercentage : ! hasErrors
netPerformancePercentage : ! hasErrors
@ -493,7 +502,7 @@ export abstract class PortfolioCalculator {
} ) ;
} ) ;
if ( item . assetSubClass === AssetSubClass . CASH ) {
if ( item . assetSubClass === AssetSubClass . CASH ) {
cashSymbol s . add ( item . symbol ) ;
cashAssetProfileIdentifier s . add ( assetProfileIdentifier ) ;
totalCashInBaseCurrency =
totalCashInBaseCurrency =
totalCashInBaseCurrency . plus ( valueInBaseCurrency ) ;
totalCashInBaseCurrency . plus ( valueInBaseCurrency ) ;
@ -519,8 +528,11 @@ export abstract class PortfolioCalculator {
}
}
for ( const dateString of chartDates ) {
for ( const dateString of chartDates ) {
for ( const symbol of Object . keys ( valuesBySymbol ) ) {
for ( const assetProfileIdentifier of Object . keys (
const symbolValues = valuesBySymbol [ symbol ] ;
valuesByAssetProfileIdentifier
) ) {
const symbolValues =
valuesByAssetProfileIdentifier [ assetProfileIdentifier ] ;
const currentValue =
const currentValue =
symbolValues . currentValues ? . [ dateString ] ? ? new Big ( 0 ) ;
symbolValues . currentValues ? . [ dateString ] ? ? new Big ( 0 ) ;
@ -569,7 +581,7 @@ export abstract class PortfolioCalculator {
accumulatedValuesByDate [ dateString ]
accumulatedValuesByDate [ dateString ]
? . totalCashValueWithCurrencyEffect ? ? new Big ( 0 )
? . totalCashValueWithCurrencyEffect ? ? new Big ( 0 )
) . add (
) . add (
cashSymbols . has ( symbol )
cashAssetProfileIdentifiers . has ( assetProfileIdentifier )
? netWorthValueWithCurrencyEffect
? netWorthValueWithCurrencyEffect
: new Big ( 0 )
: new Big ( 0 )
) ,
) ,
@ -879,7 +891,7 @@ export abstract class PortfolioCalculator {
end : Date ;
end : Date ;
exchangeRates : { [ dateString : string ] : number } ;
exchangeRates : { [ dateString : string ] : number } ;
marketSymbolMap : {
marketSymbolMap : {
[ date : string ] : { [ symbol : string ] : Big } ;
[ date : string ] : { [ assetProfileIdentifier : string ] : Big } ;
} ;
} ;
start : Date ;
start : Date ;
} & AssetProfileIdentifier ) : SymbolMetrics ;
} & AssetProfileIdentifier ) : SymbolMetrics ;
@ -977,7 +989,9 @@ export abstract class PortfolioCalculator {
@LogPerformance
@LogPerformance
private computeTransactionPoints() {
private computeTransactionPoints() {
this . transactionPoints = [ ] ;
this . transactionPoints = [ ] ;
const symbols : { [ symbol : string ] : TransactionPointSymbol } = { } ;
const symbols : {
[ assetProfileIdentifier : string ] : TransactionPointSymbol ;
} = { } ;
let lastDate : string = null ;
let lastDate : string = null ;
let lastTransactionPoint : TransactionPoint = null ;
let lastTransactionPoint : TransactionPoint = null ;
@ -1001,7 +1015,9 @@ export abstract class PortfolioCalculator {
const skipErrors = ! ! assetProfile . userId ; // Skip errors for custom asset profiles
const skipErrors = ! ! assetProfile . userId ; // Skip errors for custom asset profiles
const symbol = assetProfile . symbol ;
const symbol = assetProfile . symbol ;
const oldAccumulatedSymbol = symbols [ symbol ] ;
const assetProfileIdentifier = getAssetProfileIdentifier ( assetProfile ) ;
const oldAccumulatedSymbol = symbols [ assetProfileIdentifier ] ;
if ( oldAccumulatedSymbol ) {
if ( oldAccumulatedSymbol ) {
let investment = oldAccumulatedSymbol . investment ;
let investment = oldAccumulatedSymbol . investment ;
@ -1083,12 +1099,12 @@ export abstract class PortfolioCalculator {
'id'
'id'
) ;
) ;
symbols [ symbol ] = currentTransactionPointItem ;
symbols [ assetProfileIdentifier ] = currentTransactionPointItem ;
const items = lastTransactionPoint ? . items ? ? [ ] ;
const items = lastTransactionPoint ? . items ? ? [ ] ;
const newItems = items . filter ( ( { symbol } ) = > {
const newItems = items . filter ( ( item ) = > {
return symbol !== assetProfile . symbol ;
return getAssetProfileIdentifier ( item ) !== assetProfileIdentifier ;
} ) ;
} ) ;
newItems . push ( currentTransactionPointItem ) ;
newItems . push ( currentTransactionPointItem ) ;