@ -1,7 +1,7 @@
import { AccountBalanceService } from '@ghostfolio/api/app/account-balance/account-balance.service' ;
import { AccountService } from '@ghostfolio/api/app/account/account.service' ;
import { CashDetails } from '@ghostfolio/api/app/account/interfaces/cash-details.interface' ;
import { Order Service } from '@ghostfolio/api/app/order/order .service' ;
import { Activities Service } from '@ghostfolio/api/app/activities/activities .service' ;
import { UserService } from '@ghostfolio/api/app/user/user.service' ;
import { getFactor } from '@ghostfolio/api/helper/portfolio.helper' ;
import { AccountClusterRiskCurrentInvestment } from '@ghostfolio/api/models/rules/account-cluster-risk/current-investment' ;
@ -13,7 +13,7 @@ import { CurrencyClusterRiskCurrentInvestment } from '@ghostfolio/api/models/rul
import { EconomicMarketClusterRiskDevelopedMarkets } from '@ghostfolio/api/models/rules/economic-market-cluster-risk/developed-markets' ;
import { EconomicMarketClusterRiskEmergingMarkets } from '@ghostfolio/api/models/rules/economic-market-cluster-risk/emerging-markets' ;
import { EmergencyFundSetup } from '@ghostfolio/api/models/rules/emergency-fund/emergency-fund-setup' ;
import { FeeRatioInitialInvestment } from '@ghostfolio/api/models/rules/fees/fee-ratio-initial-investment ' ;
import { FeeRatioTotalInvestmentVolume } from '@ghostfolio/api/models/rules/fees/fee-ratio-total-investment-volume ' ;
import { BuyingPower } from '@ghostfolio/api/models/rules/liquidity/buying-power' ;
import { RegionalMarketClusterRiskAsiaPacific } from '@ghostfolio/api/models/rules/regional-market-cluster-risk/asia-pacific' ;
import { RegionalMarketClusterRiskEmergingMarkets } from '@ghostfolio/api/models/rules/regional-market-cluster-risk/emerging-markets' ;
@ -105,13 +105,13 @@ export class PortfolioService {
public constructor (
private readonly accountBalanceService : AccountBalanceService ,
private readonly accountService : AccountService ,
private readonly activitiesService : ActivitiesService ,
private readonly benchmarkService : BenchmarkService ,
private readonly calculatorFactory : PortfolioCalculatorFactory ,
private readonly dataProviderService : DataProviderService ,
private readonly exchangeRateDataService : ExchangeRateDataService ,
private readonly i18nService : I18nService ,
private readonly impersonationService : ImpersonationService ,
private readonly orderService : OrderService ,
@Inject ( REQUEST ) private readonly request : RequestWithUser ,
private readonly rulesService : RulesService ,
private readonly symbolProfileService : SymbolProfileService ,
@ -179,9 +179,9 @@ export class PortfolioService {
return Promise . all (
accounts . map ( async ( account ) = > {
let activitiesCount = 0 ;
let dividendInBaseCurrency = 0 ;
let interestInBaseCurrency = 0 ;
let transactionCount = 0 ;
for ( const {
currency ,
@ -214,7 +214,7 @@ export class PortfolioService {
}
if ( ! isDraft ) {
transaction Count += 1 ;
activities Count += 1 ;
}
}
@ -223,9 +223,9 @@ export class PortfolioService {
const result = {
. . . account ,
activitiesCount ,
dividendInBaseCurrency ,
interestInBaseCurrency ,
transactionCount ,
valueInBaseCurrency ,
allocationInPercentage : 0 ,
balanceInBaseCurrency : this.exchangeRateDataService.toCurrency (
@ -262,6 +262,8 @@ export class PortfolioService {
withExcludedAccounts
} ) ;
let activitiesCount = 0 ;
const searchQuery = filters . find ( ( { type } ) = > {
return type === 'SEARCH_QUERY' ;
} ) ? . id ;
@ -281,9 +283,10 @@ export class PortfolioService {
let totalDividendInBaseCurrency = new Big ( 0 ) ;
let totalInterestInBaseCurrency = new Big ( 0 ) ;
let totalValueInBaseCurrency = new Big ( 0 ) ;
let transactionCount = 0 ;
for ( const account of accounts ) {
activitiesCount += account . activitiesCount ;
totalBalanceInBaseCurrency = totalBalanceInBaseCurrency . plus (
account . balanceInBaseCurrency
) ;
@ -296,7 +299,6 @@ export class PortfolioService {
totalValueInBaseCurrency = totalValueInBaseCurrency . plus (
account . valueInBaseCurrency
) ;
transactionCount += account . transactionCount ;
}
for ( const account of accounts ) {
@ -310,7 +312,7 @@ export class PortfolioService {
return {
accounts ,
transaction Count,
activities Count,
totalBalanceInBaseCurrency : totalBalanceInBaseCurrency.toNumber ( ) ,
totalDividendInBaseCurrency : totalDividendInBaseCurrency.toNumber ( ) ,
totalInterestInBaseCurrency : totalInterestInBaseCurrency.toNumber ( ) ,
@ -318,13 +320,13 @@ export class PortfolioService {
} ;
}
public async getDividends ( {
public getDividends ( {
activities ,
groupBy
} : {
activities : Activity [ ] ;
groupBy? : GroupBy ;
} ) : Promise < InvestmentItem [ ] > {
} ) : InvestmentItem [ ] {
let dividends = activities . map ( ( { currency , date , value } ) = > {
return {
date : format ( date , DATE_FORMAT ) ,
@ -401,10 +403,10 @@ export class PortfolioService {
const user = await this . userService . user ( { id : userId } ) ;
const userCurrency = this . getUserCurrency ( user ) ;
const { endDate , startDate } = getIntervalFromDateRange ( dateRange ) ;
const { endDate , startDate } = getIntervalFromDateRange ( { dateRange } ) ;
const { activities } =
await this . orderService . getOrder sForPortfolioCalculator( {
await this . activitiesService . getActivitie sForPortfolioCalculator( {
filters ,
userCurrency ,
userId
@ -488,7 +490,7 @@ export class PortfolioService {
) ;
const { activities } =
await this . orderService . getOrder sForPortfolioCalculator( {
await this . activitiesService . getActivitie sForPortfolioCalculator( {
filters ,
userCurrency ,
userId
@ -522,10 +524,6 @@ export class PortfolioService {
return type === 'ACCOUNT' ;
} ) ? ? false ;
const isFilteredByCash = filters ? . some ( ( { id , type } ) = > {
return id === AssetClass . LIQUIDITY && type === 'ASSET_CLASS' ;
} ) ;
const isFilteredByClosedHoldings =
filters ? . some ( ( { id , type } ) = > {
return id === 'CLOSED' && type === 'HOLDING_TYPE' ;
@ -557,6 +555,9 @@ export class PortfolioService {
assetProfileIdentifiers
) ;
const cashSymbolProfiles = this . getCashSymbolProfiles ( cashDetails ) ;
symbolProfiles . push ( . . . cashSymbolProfiles ) ;
const symbolProfileMap : { [ symbol : string ] : EnhancedSymbolProfile } = { } ;
for ( const symbolProfile of symbolProfiles ) {
symbolProfileMap [ symbolProfile . symbol ] = symbolProfile ;
@ -568,9 +569,10 @@ export class PortfolioService {
}
for ( const {
activitiesCount ,
currency ,
dateOfFirstActivity ,
dividend ,
firstBuyDate ,
grossPerformance ,
grossPerformanceWithCurrencyEffect ,
grossPerformancePercentage ,
@ -584,7 +586,6 @@ export class PortfolioService {
quantity ,
symbol ,
tags ,
transactionCount ,
valueInBaseCurrency
} of positions ) {
if ( isFilteredByClosedHoldings === true ) {
@ -611,21 +612,43 @@ export class PortfolioService {
}
holdings [ symbol ] = {
activitiesCount ,
currency ,
markets ,
marketsAdvanced ,
marketPrice ,
symbol ,
tags ,
transactionCount ,
allocationInPercentage : filteredValueInBaseCurrency.eq ( 0 )
? 0
: valueInBaseCurrency . div ( filteredValueInBaseCurrency ) . toNumber ( ) ,
assetClass : assetProfile.assetClass ,
assetProfile : {
assetClass : assetProfile.assetClass ,
assetSubClass : assetProfile.assetSubClass ,
countries : assetProfile.countries ,
currency : assetProfile.currency ,
dataSource : assetProfile.dataSource ,
holdings : assetProfile.holdings.map (
( { allocationInPercentage , name } ) = > {
return {
allocationInPercentage ,
name ,
valueInBaseCurrency : valueInBaseCurrency
. mul ( allocationInPercentage )
. toNumber ( )
} ;
}
) ,
name : assetProfile.name ,
sectors : assetProfile.sectors ,
symbol : assetProfile . symbol ,
url : assetProfile.url
} ,
assetSubClass : assetProfile.assetSubClass ,
countries : assetProfile.countries ,
dataSource : assetProfile.dataSource ,
dateOfFirstActivity : parseDate ( firstBuyDate ) ,
dateOfFirstActivity : parseDate ( dateOfFirstActivity ) ,
dividend : dividend?.toNumber ( ) ? ? 0 ,
grossPerformance : grossPerformance?.toNumber ( ) ? ? 0 ,
grossPerformancePercent : grossPerformancePercentage?.toNumber ( ) ? ? 0 ,
@ -661,18 +684,6 @@ export class PortfolioService {
} ;
}
if ( filters ? . length === 0 || isFilteredByAccount || isFilteredByCash ) {
const cashPositions = this . getCashPositions ( {
cashDetails ,
userCurrency ,
value : filteredValueInBaseCurrency
} ) ;
for ( const symbol of Object . keys ( cashPositions ) ) {
holdings [ symbol ] = cashPositions [ symbol ] ;
}
}
const { accounts , platforms } = await this . getValueOfAccountsAndPlatforms ( {
activities ,
filters ,
@ -769,7 +780,7 @@ export class PortfolioService {
const userCurrency = this . getUserCurrency ( user ) ;
const { activities } =
await this . orderService . getOrder sForPortfolioCalculator( {
await this . activitiesService . getActivitie sForPortfolioCalculator( {
userCurrency ,
userId
} ) ;
@ -802,11 +813,12 @@ export class PortfolioService {
}
const {
activitiesCount ,
averagePrice ,
currency ,
dateOfFirstActivity ,
dividendInBaseCurrency ,
fee ,
firstBuyDate ,
feeInBaseCurrency ,
grossPerformance ,
grossPerformancePercentage ,
grossPerformancePercentageWithCurrencyEffect ,
@ -820,8 +832,7 @@ export class PortfolioService {
quantity ,
tags ,
timeWeightedInvestment ,
timeWeightedInvestmentWithCurrencyEffect ,
transactionCount
timeWeightedInvestmentWithCurrencyEffect
} = holding ;
const activitiesOfHolding = activities . filter ( ( { SymbolProfile } ) = > {
@ -832,7 +843,10 @@ export class PortfolioService {
} ) ;
const dividendYieldPercent = getAnnualizedPerformancePercent ( {
daysInMarket : differenceInDays ( new Date ( ) , parseDate ( firstBuyDate ) ) ,
daysInMarket : differenceInDays (
new Date ( ) ,
parseDate ( dateOfFirstActivity )
) ,
netPerformancePercentage : timeWeightedInvestment.eq ( 0 )
? new Big ( 0 )
: dividendInBaseCurrency . div ( timeWeightedInvestment )
@ -840,7 +854,10 @@ export class PortfolioService {
const dividendYieldPercentWithCurrencyEffect =
getAnnualizedPerformancePercent ( {
daysInMarket : differenceInDays ( new Date ( ) , parseDate ( firstBuyDate ) ) ,
daysInMarket : differenceInDays (
new Date ( ) ,
parseDate ( dateOfFirstActivity )
) ,
netPerformancePercentage : timeWeightedInvestmentWithCurrencyEffect.eq ( 0 )
? new Big ( 0 )
: dividendInBaseCurrency . div ( timeWeightedInvestmentWithCurrencyEffect )
@ -849,7 +866,7 @@ export class PortfolioService {
const historicalData = await this . dataProviderService . getHistorical (
[ { dataSource , symbol } ] ,
'day' ,
parseISO ( firstBuyDate ) ,
parseISO ( dateOfFirstActivity ) ,
new Date ( )
) ;
@ -914,7 +931,7 @@ export class PortfolioService {
// Add historical entry for buy date, if no historical data available
historicalDataArray . push ( {
averagePrice : activitiesOfHolding [ 0 ] . unitPriceInAssetProfileCurrency ,
date : firstBuyDate ,
date : dateOfFirstActivity ,
marketPrice : activitiesOfHolding [ 0 ] . unitPriceInAssetProfileCurrency ,
quantity : activitiesOfHolding [ 0 ] . quantity
} ) ;
@ -927,25 +944,20 @@ export class PortfolioService {
) ;
return {
firstBuyDate ,
activitiesCount ,
dateOfFirstActivity ,
marketPrice ,
marketPriceMax ,
marketPriceMin ,
SymbolProfile ,
tags ,
activities : activitiesOfHolding ,
activitiesCount : transactionCount ,
averagePrice : averagePrice.toNumber ( ) ,
dataProviderInfo : portfolioCalculator.getDataProviderInfos ( ) ? . [ 0 ] ,
dividendInBaseCurrency : dividendInBaseCurrency.toNumber ( ) ,
dividendYieldPercent : dividendYieldPercent.toNumber ( ) ,
dividendYieldPercentWithCurrencyEffect :
dividendYieldPercentWithCurrencyEffect . toNumber ( ) ,
feeInBaseCurrency : this.exchangeRateDataService.toCurrency (
fee . toNumber ( ) ,
SymbolProfile . currency ,
userCurrency
) ,
feeInBaseCurrency : feeInBaseCurrency.toNumber ( ) ,
grossPerformance : grossPerformance?.toNumber ( ) ,
grossPerformancePercent : grossPerformancePercentage?.toNumber ( ) ,
grossPerformancePercentWithCurrencyEffect :
@ -998,7 +1010,7 @@ export class PortfolioService {
userId ,
userCurrency
} ) ,
this . orderService . getOrder sForPortfolioCalculator( {
this . activitiesService . getActivitie sForPortfolioCalculator( {
filters ,
userCurrency ,
userId
@ -1017,7 +1029,8 @@ export class PortfolioService {
netPerformancePercentage : 0 ,
netPerformancePercentageWithCurrencyEffect : 0 ,
netPerformanceWithCurrencyEffect : 0 ,
totalInvestment : 0
totalInvestment : 0 ,
totalInvestmentValueWithCurrencyEffect : 0
}
} ;
}
@ -1034,7 +1047,7 @@ export class PortfolioService {
const { errors , hasErrors , historicalData } =
await portfolioCalculator . getSnapshot ( ) ;
const { endDate , startDate } = getIntervalFromDateRange ( dateRange ) ;
const { endDate , startDate } = getIntervalFromDateRange ( { dateRange } ) ;
const { chart } = await portfolioCalculator . getPerformance ( {
end : endDate ,
@ -1048,6 +1061,7 @@ export class PortfolioService {
netPerformanceWithCurrencyEffect ,
netWorth ,
totalInvestment ,
totalInvestmentValueWithCurrencyEffect ,
valueWithCurrencyEffect
} = chart ? . at ( - 1 ) ? ? {
netPerformance : 0 ,
@ -1068,6 +1082,7 @@ export class PortfolioService {
netPerformance ,
netPerformanceWithCurrencyEffect ,
totalInvestment ,
totalInvestmentValueWithCurrencyEffect ,
currentNetWorth : netWorth ,
currentValueInBaseCurrency : valueWithCurrencyEffect ,
netPerformancePercentage : netPerformanceInPercentage ,
@ -1316,11 +1331,11 @@ export class PortfolioService {
} ) ,
rules : await this . rulesService . evaluate (
[
new FeeRatioInitialInvestment (
new FeeRatioTotalInvestmentVolume (
this . exchangeRateDataService ,
this . i18nService ,
userSettings . language ,
summary . committedFunds ,
summary . totalBuy + summary . totalSell ,
summary . fees
)
] ,
@ -1356,7 +1371,12 @@ export class PortfolioService {
} ) {
userId = await this . getUserId ( impersonationId , userId ) ;
await this . orderService . assignTags ( { dataSource , symbol , tags , userId } ) ;
await this . activitiesService . assignTags ( {
dataSource ,
symbol ,
tags ,
userId
} ) ;
}
private getAggregatedMarkets ( holdings : Record < string , PortfolioPosition > ) : {
@ -1548,6 +1568,37 @@ export class PortfolioService {
return cashPositions ;
}
private getCashSymbolProfiles ( cashDetails : CashDetails ) {
const cashSymbols = [
. . . new Set ( cashDetails . accounts . map ( ( { currency } ) = > currency ) )
] ;
return cashSymbols . map < EnhancedSymbolProfile > ( ( currency ) = > {
const account = cashDetails . accounts . find (
( { currency : accountCurrency } ) = > {
return accountCurrency === currency ;
}
) ;
return {
currency ,
activitiesCount : 0 ,
assetClass : AssetClass.LIQUIDITY ,
assetSubClass : AssetSubClass.CASH ,
countries : [ ] ,
createdAt : account.createdAt ,
dataSource : DataSource.MANUAL ,
holdings : [ ] ,
id : currency ,
isActive : true ,
name : currency ,
sectors : [ ] ,
symbol : currency ,
updatedAt : account.updatedAt
} ;
} ) ;
}
private getDividendsByGroup ( {
dividends ,
groupBy
@ -1644,9 +1695,21 @@ export class PortfolioService {
} ) : PortfolioPosition {
return {
currency ,
activitiesCount : 0 ,
allocationInPercentage : 0 ,
assetClass : AssetClass.LIQUIDITY ,
assetSubClass : AssetSubClass.CASH ,
assetProfile : {
currency ,
assetClass : AssetClass.LIQUIDITY ,
assetSubClass : AssetSubClass.CASH ,
countries : [ ] ,
dataSource : undefined ,
holdings : [ ] ,
name : currency ,
sectors : [ ] ,
symbol : currency
} ,
countries : [ ] ,
dataSource : undefined ,
dateOfFirstActivity : undefined ,
@ -1667,7 +1730,6 @@ export class PortfolioService {
sectors : [ ] ,
symbol : currency ,
tags : [ ] ,
transactionCount : 0 ,
valueInBaseCurrency : balance
} ;
}
@ -1817,7 +1879,7 @@ export class PortfolioService {
userId = await this . getUserId ( impersonationId , userId ) ;
const user = await this . userService . user ( { id : userId } ) ;
const { activities } = await this . orderService . getOrder s( {
const { activities } = await this . activitiesService . getActivitie s( {
userCurrency ,
userId ,
withExcludedAccountsAndActivities : true
@ -1839,8 +1901,11 @@ export class PortfolioService {
}
}
const { currentValueInBaseCurrency , totalInvestment } =
await portfolioCalculator . getSnapshot ( ) ;
const {
currentValueInBaseCurrency ,
totalInvestment ,
totalInvestmentWithCurrencyEffect
} = await portfolioCalculator . getSnapshot ( ) ;
const { performance } = await this . getPerformance ( {
impersonationId ,
@ -1854,18 +1919,17 @@ export class PortfolioService {
netPerformanceWithCurrencyEffect
} = performance ;
const dividendInBaseCurrency =
await portfolioCalculator . getDividendInBaseCurrency ( ) ;
const totalEmergencyFund = this . getTotalEmergencyFund ( {
emergencyFundHoldingsValueInBaseCurrency ,
userSettings : user.settings?.settings as UserSettings
} ) ;
const fees = await portfolioCalculator . getFeesInBaseCurrency ( ) ;
const dateOfFirstActivity = portfolioCalculator . getStartDate ( ) ;
const firstOrderDate = portfolioCalculator . getStartDate ( ) ;
const dividendInBaseCurrency =
await portfolioCalculator . getDividendInBaseCurrency ( ) ;
const fees = await portfolioCalculator . getFeesInBaseCurrency ( ) ;
const interest = await portfolioCalculator . getInterestInBaseCurrency ( ) ;
const liabilities =
@ -1888,8 +1952,6 @@ export class PortfolioService {
. plus ( emergencyFundHoldingsValueInBaseCurrency )
. toNumber ( ) ;
const committedFunds = new Big ( totalBuy ) . minus ( totalSell ) ;
const totalOfExcludedActivities = this . getSumOfActivityType ( {
userCurrency ,
activities : excludedActivities ,
@ -1923,7 +1985,7 @@ export class PortfolioService {
. minus ( liabilities )
. toNumber ( ) ;
const daysInMarket = differenceInDays ( new Date ( ) , firstOrderDate ) ;
const daysInMarket = differenceInDays ( new Date ( ) , dateOfFirstActivity ) ;
const annualizedPerformancePercent = getAnnualizedPerformancePercent ( {
daysInMarket ,
@ -1942,6 +2004,7 @@ export class PortfolioService {
annualizedPerformancePercent ,
annualizedPerformancePercentWithCurrencyEffect ,
cash ,
dateOfFirstActivity ,
excludedAccountsAndActivities ,
netPerformance ,
netPerformancePercentage ,
@ -1952,7 +2015,6 @@ export class PortfolioService {
activityCount : activities.filter ( ( { type } ) = > {
return [ 'BUY' , 'SELL' ] . includes ( type ) ;
} ) . length ,
committedFunds : committedFunds.toNumber ( ) ,
currentValueInBaseCurrency : currentValueInBaseCurrency.toNumber ( ) ,
dividendInBaseCurrency : dividendInBaseCurrency.toNumber ( ) ,
emergencyFund : {
@ -1983,6 +2045,8 @@ export class PortfolioService {
interestInBaseCurrency : interest.toNumber ( ) ,
liabilitiesInBaseCurrency : liabilities.toNumber ( ) ,
totalInvestment : totalInvestment.toNumber ( ) ,
totalInvestmentValueWithCurrencyEffect :
totalInvestmentWithCurrencyEffect . toNumber ( ) ,
totalValueInBaseCurrency : netWorth
} ;
}
@ -2157,7 +2221,7 @@ export class PortfolioService {
accounts [ account ? . id || UNKNOWN_KEY ] = {
balance : 0 ,
currency : account?.currency ,
name : account.name ,
name : account? .name ,
valueInBaseCurrency : currentValueOfSymbolInBaseCurrency
} ;
}
@ -2171,7 +2235,7 @@ export class PortfolioService {
platforms [ account ? . platformId || UNKNOWN_KEY ] = {
balance : 0 ,
currency : account?.currency ,
name : account.platform?.name ,
name : account? .platform?.name ,
valueInBaseCurrency : currentValueOfSymbolInBaseCurrency
} ;
}