import { Money } from "bigint-money/dist" ; import { Fund } from "./Fund/Fund" ; import { Percentage } from "./Percentage" ; import { Transaction } from "./Transaction" ; export class SimulatedPortfolio { private value : Money = new Money ( 0 , 'EUR' ) ; private totalEntryCosts : Money = new Money ( 0 , 'EUR' ) ; private totalRunningCosts : Money = new Money ( 0 , 'EUR' ) ; private totalDividendLeakage : Money = new Money ( 0 , 'EUR' ) ; constructor ( public assets ) { const totalAllocation = assets . reduce ( ( sum , current ) => sum . add ( current . allocation ) , new Percentage ( 0 ) ) ; if ( ! totalAllocation . equals ( new Percentage ( 100 ) ) ) { throw new Error ( 'The total allocation of all assets should be 100%' ) ; } } public reset ( ) { this . value = this . totalRunningCosts = this . totalDividendLeakage = new Money ( 0 , 'EUR' ) ; } public invest ( amount ) { this . value = this . value . add ( amount ) ; } public grow ( growthPercentage , fundCostsPercentage ) { this . totalRunningCosts = this . totalRunningCosts . add ( fundCostsPercentage . applyTo ( this . value ) ) ; this . value = this . value . add ( growthPercentage . applyTo ( this . value ) ) ; } public sell ( amount ) { this . value = this . value . subtract ( amount ) ; } public collectDividends ( dividendYield ) { const dividend = dividendYield . applyTo ( this . value ) ; const dividendLeakage = this . getDividendLeakage ( dividend ) ; this . totalDividendLeakage = this . totalDividendLeakage . add ( dividendLeakage ) ; return dividend . subtract ( dividendLeakage ) ; } public getValue ( ) { return this . value ; } public getTotalRunningCosts ( ) { return this . totalRunningCosts ; } public getTotalDividendLeakage ( ) { return this . totalDividendLeakage ; } public getTotalCosts ( ) { return this . totalRunningCosts . add ( this . totalDividendLeakage ) ; } /**
     * Excluding dividend leakage
     */ public getYearlyCosts ( ) { return this . assets . reduce ( ( sum , asset ) => sum . add ( asset . allocation . multiply ( asset . fund . getTotalExpenseRatio ( ) . add ( asset . fund . getInternalTransactionCosts ( ) ) ) ) , new Percentage ( 0 ) ) ; } public getTotalExpenseRatio ( ) { return this . assets . reduce ( ( sum , asset ) => sum . add ( asset . allocation . multiply ( asset . fund . getTotalExpenseRatio ( ) ) ) , new Percentage ( 0 ) ) ; } public getMarketCapPercentage ( ) { return this . getFunds ( ) . reduce ( ( previous , current ) => previous . add ( current . getMarketCapitalization ( ) ) , new Percentage ( 0 ) ) ; } public getFundNames ( ) { return this . getFunds ( ) . map ( ( fund ) => fund . getIdentifier ( ) ) ; } public containsSmallCaps ( ) { for ( let asset of this . assets ) { if ( asset . fund . containsSmallCaps ( ) ) { return true ; } } return false ; } public containsOnlyEmergingMarketsSmallCaps ( ) { const smallCapFunds = this . getFunds ( ) . filter ( ( fund ) => fund . containsSmallCaps ( ) ) ; const developedSmallCapFunds = smallCapFunds . filter ( ( fund ) => fund . getTrackedIndex ( ) . markets === 'all-world' || fund . getTrackedIndex ( ) . markets === 'developed' ) ; return developedSmallCapFunds . length === 0 ; } public allocate ( investment ) { return this . assets . map ( ( asset ) => new Transaction ( asset . fund , asset . allocation . applyTo ( investment ) ) ) ; } public describe ( ) { let developed = [ ] ; let emerging = [ ] ; for ( let asset of this . assets ) { let index = asset . fund . getTrackedIndex ( ) ; if ( index . markets == 'developed' ) { developed = developed . concat ( index . sizes ) ; } else if ( index . markets == 'emerging' ) { emerging = emerging . concat ( index . sizes ) ; } else if ( index . markets == 'all-world' ) { developed = developed . concat ( index . sizes ) ; emerging = emerging . concat ( index . sizes ) ; } } if ( this . arraysEqual ( developed , [ 'large' , 'mid' , 'small' ] ) ) { developed = [ 'all' ] ; } if ( this . arraysEqual ( emerging , [ 'large' , 'mid' , 'small' ] ) ) { emerging = [ 'all' ] ; } if ( this . arraysEqual ( developed , emerging ) ) { return 'all-world ' + developed . join ( ' & ' ) + ' cap' ; } return 'developed markets ' + developed . join ( ' & ' ) + ' cap + emerging markets ' + emerging . join ( ' & ' ) + ' cap' ; } private getDividendLeakage ( dividend ) { const dividendLeakage = this . assets . map ( ( asset ) => asset . allocation . multiply ( asset . fund . getDividendLeak ( ) ) ) ; const totalDividendLeakage = dividendLeakage . reduce ( ( sum , current ) => sum . add ( current ) ) ; return totalDividendLeakage . applyTo ( dividend ) ; } private getFunds ( ) { return this . assets . map ( ( asset ) => asset . fund ) ; } private arraysEqual ( array1 , array2 ) { if ( array1 . length !== array2 . length ) { return false ; } return array1 . every ( ( value , index ) => value === array2 [ index ] ) ; } }