import { Money } from "bigint-money/dist" ; import { Broker } from "./Broker" ; import { Percentage } from "./Percentage" ; import { SimulatedPortfolio } from "./SimulatedPortfolio" ; import { Transaction } from "./Transaction" ; export class Simulation { public totalInvestment : Money = new Money ( 0 , 'EUR' ) ; public totalTransactionFees : Money = new Money ( 0 , 'EUR' ) ; public totalServiceFees : Money = new Money ( 0 , 'EUR' ) ; public totalDividendDistributionFees = new Money ( 0 , 'EUR' ) ; private monthsPassed = 0 ; constructor ( private broker , private portfolio , private initialInvestment , private monthlyInvestment , private expectedYearlyReturn , private expectedDividendYield ) { } public getBroker ( ) { return this . broker ; } public run ( years ) { for ( let i = 0 ; i < years ; i ++ ) { this . runYear ( ) ; } } public getPortfolioValue ( ) { return this . portfolio . getValue ( ) ; } public getBrokerCosts ( ) { return this . totalTransactionFees . add ( this . totalServiceFees ) . add ( this . totalDividendDistributionFees ) ; } public getTotalCosts ( ) { return this . portfolio . getTotalCosts ( ) . add ( this . totalTransactionFees ) . add ( this . totalServiceFees ) . add ( this . totalDividendDistributionFees ) ; } public getNetProfit ( ) { return this . portfolio . getValue ( ) . subtract ( this . totalInvestment ) ; } public getNetResult ( ) { return parseFloat ( this . getNetProfit ( ) . toFixed ( 4 ) ) / parseFloat ( this . totalInvestment . toFixed ( 4 ) ) ; } public getExpectedDividendYield ( ) { return this . expectedDividendYield ; } private runYear ( ) { for ( let i = 0 ; i < 4 ; i ++ ) { this . runQuarter ( ) ; } } private runQuarter ( ) { const startValue = this . portfolio . getValue ( ) ; let combinedEndOfMonthValue = new Money ( 0 , 'EUR' ) ; for ( let i = 0 ; i < 3 ; i ++ ) { this . runMonth ( ) ; combinedEndOfMonthValue = combinedEndOfMonthValue . add ( this . portfolio . getValue ( ) ) ; } this . collectAndReinvestDividends ( ) ; const serviceFee = this . broker . getQuarterlyServiceFee ( this . portfolio . getValue ( ) , combinedEndOfMonthValue . divide ( 3 ) , startValue . add ( this . portfolio . getValue ( ) ) . divide ( 2 ) ) ; this . registerServiceFee ( serviceFee ) ; } private runMonth ( ) { const investment = this . isFirstMonth ( ) ? this . initialInvestment : this . monthlyInvestment ; let transactionCosts = new Money ( 0 , 'EUR' ) ; if ( investment . isGreaterThan ( 0 ) ) { this . totalInvestment = this . totalInvestment . add ( investment ) ; transactionCosts = this . getTransactionCosts ( investment ) ; this . portfolio . invest ( investment . subtract ( transactionCosts ) ) ; this . totalTransactionFees = this . totalTransactionFees . add ( transactionCosts ) ; } this . broker . registerTransactionCosts ( transactionCosts ) ; const netReturn = this . expectedYearlyReturn . subtract ( this . portfolio . getYearlyCosts ( ) ) ; const monthlyNetReturn = this . getMonthlyRate ( netReturn ) ; const monthlyCosts = this . getMonthlyRate ( this . portfolio . getYearlyCosts ( ) ) ; this . portfolio . grow ( monthlyNetReturn , monthlyCosts ) ; this . registerServiceFee ( this . broker . getMonthlyServiceFee ( this . portfolio . getValue ( ) ) ) ; this . monthsPassed ++ ; } private getTransactionCosts ( amount ) { const transactions = this . portfolio . allocate ( amount ) ; const transactionCosts = transactions . map ( ( transaction ) => this . broker . getTransactionCosts ( transaction ) ) ; return transactionCosts . reduce ( ( total , current ) : Money => total . add ( current ) , new Money ( 0 , 'EUR' ) ) ; } private collectAndReinvestDividends ( ) { const quarterlyDividendYield = this . expectedDividendYield . multiply ( new Percentage ( 25 ) ) ; const dividend = this . portfolio . collectDividends ( quarterlyDividendYield ) ; const dividendFees = this . broker . calculateDividendFees ( dividend ) ; this . totalDividendDistributionFees = this . totalDividendDistributionFees . add ( dividendFees ) ; const investableDividend = dividend . subtract ( dividendFees ) ; this . portfolio . invest ( investableDividend ) ; } private registerServiceFee ( serviceFee ) { this . portfolio . sell ( serviceFee ) ; this . totalServiceFees = this . totalServiceFees . add ( serviceFee ) ; } private getMonthlyRate ( yearly ) { return Percentage . createFromFraction ( ( 1 + yearly . getFraction ( ) ) ** ( 1 / 12 ) - 1 ) ; } private isFirstMonth ( ) { return this . monthsPassed === 0 ; } }