@ -62,6 +62,7 @@ import {
Tag
} from '@prisma/client' ;
import Big from 'big.js' ;
import { isUUID } from 'class-validator' ;
import {
differenceInDays ,
format ,
@ -119,7 +120,7 @@ export class PortfolioService {
userId : string ;
withExcludedAccounts? : boolean ;
} ) : Promise < AccountWithValue [ ] > {
const where : Prisma.AccountWhereInput = { userId : userId } ;
const where : Prisma.AccountWhereInput = { userId } ;
const accountFilter = filters ? . find ( ( { type } ) = > {
return type === 'ACCOUNT' ;
@ -227,18 +228,20 @@ export class PortfolioService {
impersonationId : string ;
} ) : Promise < InvestmentItem [ ] > {
const userId = await this . getUserId ( impersonationId , this . request . user . id ) ;
const user = await this . userService . user ( { id : userId } ) ;
const userCurrency = this . getUserCurrency ( user ) ;
const { activities } = await this . orderService . getOrders ( {
filters ,
userCurrency ,
userId ,
types : [ 'DIVIDEND' ] ,
userCurrency : this.request.user.Settings.settings.baseCurrency
types : [ 'DIVIDEND' ]
} ) ;
let dividends = activities . map ( ( dividend ) = > {
let dividends = activities . map ( ( { date , valueInBaseCurrency } ) = > {
return {
date : format ( dividend . d ate , DATE_FORMAT ) ,
investment : dividend. valueInBaseCurrency
date : format ( date , DATE_FORMAT ) ,
investment : valueInBaseCurrency
} ;
} ) ;
@ -275,7 +278,8 @@ export class PortfolioService {
await this . getTransactionPoints ( {
filters ,
userId ,
includeDrafts : true
includeDrafts : true ,
types : [ 'BUY' , 'SELL' ]
} ) ;
if ( transactionPoints . length === 0 ) {
@ -420,7 +424,7 @@ export class PortfolioService {
) ;
const [ dataProviderResponses , symbolProfiles ] = await Promise . all ( [
this . dataProviderService . getQuotes ( { items : dataGatheringItems } ) ,
this . dataProviderService . getQuotes ( { user , items : dataGatheringItems } ) ,
this . symbolProfileService . getSymbolProfiles ( dataGatheringItems )
] ) ;
@ -529,12 +533,20 @@ export class PortfolioService {
grossPerformance : item.grossPerformance?.toNumber ( ) ? ? 0 ,
grossPerformancePercent :
item . grossPerformancePercentage ? . toNumber ( ) ? ? 0 ,
grossPerformancePercentWithCurrencyEffect :
item . grossPerformancePercentageWithCurrencyEffect ? . toNumber ( ) ? ? 0 ,
grossPerformanceWithCurrencyEffect :
item . grossPerformanceWithCurrencyEffect ? . toNumber ( ) ? ? 0 ,
investment : item.investment.toNumber ( ) ,
marketPrice : item.marketPrice ,
marketState : dataProviderResponse?.marketState ? ? 'delayed' ,
name : symbolProfile.name ,
netPerformance : item.netPerformance?.toNumber ( ) ? ? 0 ,
netPerformancePercent : item.netPerformancePercentage?.toNumber ( ) ? ? 0 ,
netPerformancePercentWithCurrencyEffect :
item . netPerformancePercentageWithCurrencyEffect ? . toNumber ( ) ? ? 0 ,
netPerformanceWithCurrencyEffect :
item . netPerformanceWithCurrencyEffect ? . toNumber ( ) ? ? 0 ,
quantity : item.quantity.toNumber ( ) ,
sectors : symbolProfile.sectors ,
symbol : item . symbol ,
@ -606,6 +618,7 @@ export class PortfolioService {
}
const summary = await this . getSummary ( {
holdings ,
impersonationId ,
userCurrency ,
userId ,
@ -692,7 +705,7 @@ export class PortfolioService {
. filter ( ( order ) = > {
tags = tags . concat ( order . tags ) ;
return order . type === 'BUY' || order . type === 'SELL' ;
return [ 'BUY' , 'ITEM' , 'SELL' ] . includes ( order . type ) ;
} )
. map ( ( order ) = > ( {
currency : order.SymbolProfile.currency ,
@ -741,7 +754,9 @@ export class PortfolioService {
} = position ;
const accounts : PortfolioPositionDetail [ 'accounts' ] = uniqBy (
orders ,
orders . filter ( ( { Account } ) = > {
return Account ;
} ) ,
'Account.id'
) . map ( ( { Account } ) = > {
return Account ;
@ -861,6 +876,7 @@ export class PortfolioService {
} ;
} else {
const currentData = await this . dataProviderService . getQuotes ( {
user ,
items : [ { dataSource : DataSource.YAHOO , symbol : aSymbol } ]
} ) ;
const marketPrice = currentData [ aSymbol ] ? . marketPrice ;
@ -939,11 +955,13 @@ export class PortfolioService {
return type === 'SEARCH_QUERY' ;
} ) ? . id ;
const userId = await this . getUserId ( impersonationId , this . request . user . id ) ;
const user = await this . userService . user ( { id : userId } ) ;
const { portfolioOrders , transactionPoints } =
await this . getTransactionPoints ( {
filters ,
userId
userId ,
types : [ 'BUY' , 'SELL' ]
} ) ;
if ( transactionPoints ? . length <= 0 ) {
@ -979,7 +997,7 @@ export class PortfolioService {
} ) ;
const [ dataProviderResponses , symbolProfiles ] = await Promise . all ( [
this . dataProviderService . getQuotes ( { items : dataGatheringItems } ) ,
this . dataProviderService . getQuotes ( { user , items : dataGatheringItems } ) ,
this . symbolProfileService . getSymbolProfiles (
positions . map ( ( { dataSource , symbol } ) = > {
return { dataSource , symbol } ;
@ -1073,13 +1091,15 @@ export class PortfolioService {
filters ,
impersonationId ,
userId ,
withExcludedAccounts = false
withExcludedAccounts = false ,
withItems = false
} : {
dateRange? : DateRange ;
filters? : Filter [ ] ;
impersonationId : string ;
userId : string ;
withExcludedAccounts? : boolean ;
withItems? : boolean ;
} ) : Promise < PortfolioPerformanceResponse > {
userId = await this . getUserId ( impersonationId , userId ) ;
const user = await this . userService . user ( { id : userId } ) ;
@ -1114,7 +1134,8 @@ export class PortfolioService {
await this . getTransactionPoints ( {
filters ,
userId ,
withExcludedAccounts
withExcludedAccounts ,
types : withItems ? [ 'BUY' , 'ITEM' , 'SELL' ] : [ 'BUY' , 'SELL' ]
} ) ;
const portfolioCalculator = new PortfolioCalculator ( {
@ -1266,7 +1287,8 @@ export class PortfolioService {
const { orders , portfolioOrders , transactionPoints } =
await this . getTransactionPoints ( {
userId
userId ,
types : [ 'BUY' , 'SELL' ]
} ) ;
const portfolioCalculator = new PortfolioCalculator ( {
@ -1598,12 +1620,16 @@ export class PortfolioService {
dateOfFirstActivity : undefined ,
grossPerformance : 0 ,
grossPerformancePercent : 0 ,
grossPerformancePercentWithCurrencyEffect : 0 ,
grossPerformanceWithCurrencyEffect : 0 ,
investment : balance ,
marketPrice : 0 ,
marketState : 'open' ,
name : currency ,
netPerformance : 0 ,
netPerformancePercent : 0 ,
netPerformancePercentWithCurrencyEffect : 0 ,
netPerformanceWithCurrencyEffect : 0 ,
quantity : 0 ,
sectors : [ ] ,
symbol : currency ,
@ -1684,12 +1710,14 @@ export class PortfolioService {
private async getSummary ( {
balanceInBaseCurrency ,
emergencyFundPositionsValueInBaseCurrency ,
holdings ,
impersonationId ,
userCurrency ,
userId
} : {
balanceInBaseCurrency : number ;
emergencyFundPositionsValueInBaseCurrency : number ;
holdings : PortfolioDetails [ 'holdings' ] ;
impersonationId : string ;
userCurrency : string ;
userId : string ;
@ -1703,43 +1731,57 @@ export class PortfolioService {
} ) ;
const { activities } = await this . orderService . getOrders ( {
userCurrency ,
userId
} ) ;
let { activities : excludedActivities } = await this . orderService . getOrders ( {
userCurrency ,
userId ,
withExcludedAccounts : true
} ) ;
excludedActivities = excludedActivities . filter ( ( { Account : account } ) = > {
return account ? . isExcluded ? ? false ;
} ) ;
const excludedActivities : Activity [ ] = [ ] ;
const nonExcludedActivities : Activity [ ] = [ ] ;
for ( const activity of activities ) {
if ( activity . Account ? . isExcluded ) {
excludedActivities . push ( activity ) ;
} else {
nonExcludedActivities . push ( activity ) ;
}
}
const dividend = this . getSumOfActivityType ( {
activities ,
userCurrency ,
activityType : 'DIVIDEND'
} ) . toNumber ( ) ;
const emergencyFund = new Big (
Math . max (
emergencyFundPositionsValueInBaseCurrency ,
( user . Settings ? . settings as UserSettings ) ? . emergencyFund ? ? 0
)
) ;
const fees = this . getFees ( { activities , userCurrency } ) . toNumber ( ) ;
const firstOrderDate = activities [ 0 ] ? . date ;
const interest = this . getSumOfActivityType ( {
activities ,
userCurrency ,
activityType : 'INTEREST'
} ) . toNumber ( ) ;
const items = this . getSumOfActivityType ( {
activities ,
userCurrency ,
activityType : 'ITEM'
} ) . toNumber ( ) ;
const items = Object . keys ( holdings )
. filter ( ( symbol ) = > {
return isUUID ( symbol ) && holdings [ symbol ] . dataSource === 'MANUAL' ;
} )
. map ( ( symbol ) = > {
return holdings [ symbol ] . valueInBaseCurrency ;
} )
. reduce (
( previous , current ) = > new Big ( previous ) . plus ( current ) ,
new Big ( 0 )
)
. toNumber ( ) ;
const liabilities = this . getSumOfActivityType ( {
activities ,
userCurrency ,
@ -1747,13 +1789,14 @@ export class PortfolioService {
} ) . toNumber ( ) ;
const totalBuy = this . getSumOfActivityType ( {
activities ,
userCurrency ,
activities : nonExcludedActivities ,
activityType : 'BUY'
} ) . toNumber ( ) ;
const totalSell = this . getSumOfActivityType ( {
activities ,
userCurrency ,
activities : nonExcludedActivities ,
activityType : 'SELL'
} ) . toNumber ( ) ;
@ -1761,7 +1804,9 @@ export class PortfolioService {
. minus ( emergencyFund )
. plus ( emergencyFundPositionsValueInBaseCurrency )
. toNumber ( ) ;
const committedFunds = new Big ( totalBuy ) . minus ( totalSell ) ;
const totalOfExcludedActivities = this . getSumOfActivityType ( {
userCurrency ,
activities : excludedActivities ,
@ -1812,9 +1857,25 @@ export class PortfolioService {
} )
? . toNumber ( ) ;
const annualizedPerformancePercentWithCurrencyEffect =
new PortfolioCalculator ( {
currency : userCurrency ,
currentRateService : this.currentRateService ,
exchangeRateDataService : this.exchangeRateDataService ,
orders : [ ]
} )
. getAnnualizedPerformancePercent ( {
daysInMarket ,
netPerformancePercent : new Big (
performanceInformation . performance . currentNetPerformancePercentWithCurrencyEffect
)
} )
? . toNumber ( ) ;
return {
. . . performanceInformation . performance ,
annualizedPerformancePercent ,
annualizedPerformancePercentWithCurrencyEffect ,
cash ,
dividend ,
excludedAccountsAndActivities ,
@ -1879,11 +1940,13 @@ export class PortfolioService {
private async getTransactionPoints ( {
filters ,
includeDrafts = false ,
types = [ 'BUY' , 'ITEM' , 'SELL' ] ,
userId ,
withExcludedAccounts = false
} : {
filters? : Filter [ ] ;
includeDrafts? : boolean ;
types? : ActivityType [ ] ;
userId : string ;
withExcludedAccounts? : boolean ;
} ) : Promise < {
@ -1897,10 +1960,10 @@ export class PortfolioService {
const { activities , count } = await this . orderService . getOrders ( {
filters ,
includeDrafts ,
types ,
userCurrency ,
userId ,
withExcludedAccounts ,
types : [ 'BUY' , 'SELL' ]
withExcludedAccounts
} ) ;
if ( count <= 0 ) {
@ -1960,7 +2023,7 @@ export class PortfolioService {
withExcludedAccounts = false
} : {
filters? : Filter [ ] ;
orders : OrderWithAccount [ ] ;
orders : Activity [ ] ;
portfolioItemsNow : { [ p : string ] : TimelinePosition } ;
userCurrency : string ;
userId : string ;
@ -1972,7 +2035,7 @@ export class PortfolioService {
userCurrency ,
userId ,
withExcludedAccounts ,
types : [ 'ITEM' , ' LIABILITY' ]
types : [ 'LIABILITY' ]
} ) ;
const accounts : PortfolioDetails [ 'accounts' ] = { } ;
@ -2056,41 +2119,42 @@ export class PortfolioService {
} ;
}
for ( const order of ordersByAccount ) {
for ( const {
Account ,
quantity ,
SymbolProfile ,
type
} of ordersByAccount ) {
let currentValueOfSymbolInBaseCurrency =
order . quantity *
( portfolioItemsNow [ order . SymbolProfile . symbol ]
? . marketPriceInBaseCurrency ? ?
order . unitPrice ? ?
0 ) ;
quantity *
portfolioItemsNow [ SymbolProfile . symbol ]
? . marketPriceInBaseCurrency ? ? 0 ;
if ( order . type === 'LIABILITY' || order . type === 'SELL' ) {
if ( [ 'LIABILITY' , 'SELL' ] . includes ( type ) ) {
currentValueOfSymbolInBaseCurrency *= - 1 ;
}
if ( accounts [ order . Account ? . id || UNKNOWN_KEY ] ? . valueInBaseCurrency ) {
accounts [ order . Account ? . id || UNKNOWN_KEY ] . valueInBaseCurrency +=
if ( accounts [ Account ? . id || UNKNOWN_KEY ] ? . valueInBaseCurrency ) {
accounts [ Account ? . id || UNKNOWN_KEY ] . valueInBaseCurrency +=
currentValueOfSymbolInBaseCurrency ;
} else {
accounts [ order . Account ? . id || UNKNOWN_KEY ] = {
accounts [ Account ? . id || UNKNOWN_KEY ] = {
balance : 0 ,
currency : order. Account?.currency,
currency : Account?.currency ,
name : account.name ,
valueInBaseCurrency : currentValueOfSymbolInBaseCurrency
} ;
}
if (
platforms [ order . Account ? . Platform ? . id || UNKNOWN_KEY ]
? . valueInBaseCurrency
platforms [ Account ? . Platform ? . id || UNKNOWN_KEY ] ? . valueInBaseCurrency
) {
platforms [
order . Account ? . Platform ? . id || UNKNOWN_KEY
] . valueInBaseCurrency += currentValueOfSymbolInBaseCurrency ;
platforms [ Account ? . Platform ? . id || UNKNOWN_KEY ] . valueInBaseCurrency +=
currentValueOfSymbolInBaseCurrency ;
} else {
platforms [ order . Account ? . Platform ? . id || UNKNOWN_KEY ] = {
platforms [ Account ? . Platform ? . id || UNKNOWN_KEY ] = {
balance : 0 ,
currency : order. Account?.currency,
currency : Account?.currency ,
name : account.Platform?.name ,
valueInBaseCurrency : currentValueOfSymbolInBaseCurrency
} ;