mirror of https://github.com/ghostfolio/ghostfolio
				
				
			
			
			
				Browse Source
			
			
			
			
				
		* Add exchange rate effects to portfolio calculation * Update changelog --------- Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>pull/2863/head
							committed by
							
								 GitHub
								GitHub
							
						
					
				
				 25 changed files with 1270 additions and 422 deletions
			
			
		| @ -1,5 +1,9 @@ | |||
| import Big from 'big.js'; | |||
| 
 | |||
| import { PortfolioOrder } from './portfolio-order.interface'; | |||
| 
 | |||
| export interface PortfolioOrderItem extends PortfolioOrder { | |||
|   itemType?: '' | 'start' | 'end'; | |||
|   unitPriceInBaseCurrency?: Big; | |||
|   unitPriceInBaseCurrencyWithCurrencyEffect?: Big; | |||
| } | |||
|  | |||
| @ -0,0 +1,144 @@ | |||
| import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service'; | |||
| import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; | |||
| import { ExchangeRateDataServiceMock } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service.mock'; | |||
| import { parseDate } from '@ghostfolio/common/helper'; | |||
| import Big from 'big.js'; | |||
| 
 | |||
| import { CurrentRateServiceMock } from './current-rate.service.mock'; | |||
| import { PortfolioCalculator } from './portfolio-calculator'; | |||
| 
 | |||
| jest.mock('@ghostfolio/api/app/portfolio/current-rate.service', () => { | |||
|   return { | |||
|     // eslint-disable-next-line @typescript-eslint/naming-convention
 | |||
|     CurrentRateService: jest.fn().mockImplementation(() => { | |||
|       return CurrentRateServiceMock; | |||
|     }) | |||
|   }; | |||
| }); | |||
| 
 | |||
| jest.mock( | |||
|   '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service', | |||
|   () => { | |||
|     return { | |||
|       // eslint-disable-next-line @typescript-eslint/naming-convention
 | |||
|       ExchangeRateDataService: jest.fn().mockImplementation(() => { | |||
|         return ExchangeRateDataServiceMock; | |||
|       }) | |||
|     }; | |||
|   } | |||
| ); | |||
| 
 | |||
| describe('PortfolioCalculator', () => { | |||
|   let currentRateService: CurrentRateService; | |||
|   let exchangeRateDataService: ExchangeRateDataService; | |||
| 
 | |||
|   beforeEach(() => { | |||
|     currentRateService = new CurrentRateService(null, null); | |||
| 
 | |||
|     exchangeRateDataService = new ExchangeRateDataService( | |||
|       null, | |||
|       null, | |||
|       null, | |||
|       null | |||
|     ); | |||
|   }); | |||
| 
 | |||
|   describe('get current positions', () => { | |||
|     it.only('with GOOGL buy', async () => { | |||
|       const portfolioCalculator = new PortfolioCalculator({ | |||
|         currentRateService, | |||
|         exchangeRateDataService, | |||
|         currency: 'CHF', | |||
|         orders: [ | |||
|           { | |||
|             currency: 'USD', | |||
|             date: '2023-01-03', | |||
|             dataSource: 'YAHOO', | |||
|             fee: new Big(1), | |||
|             name: 'Alphabet Inc.', | |||
|             quantity: new Big(1), | |||
|             symbol: 'GOOGL', | |||
|             type: 'BUY', | |||
|             unitPrice: new Big(89.12) | |||
|           } | |||
|         ] | |||
|       }); | |||
| 
 | |||
|       portfolioCalculator.computeTransactionPoints(); | |||
| 
 | |||
|       const spy = jest | |||
|         .spyOn(Date, 'now') | |||
|         .mockImplementation(() => parseDate('2023-07-10').getTime()); | |||
| 
 | |||
|       const currentPositions = await portfolioCalculator.getCurrentPositions( | |||
|         parseDate('2023-01-03') | |||
|       ); | |||
| 
 | |||
|       const investments = portfolioCalculator.getInvestments(); | |||
| 
 | |||
|       const investmentsByMonth = | |||
|         portfolioCalculator.getInvestmentsByGroup('month'); | |||
| 
 | |||
|       spy.mockRestore(); | |||
| 
 | |||
|       expect(currentPositions).toEqual({ | |||
|         currentValue: new Big('103.10483'), | |||
|         errors: [], | |||
|         grossPerformance: new Big('27.33'), | |||
|         grossPerformancePercentage: new Big('0.3066651705565529623'), | |||
|         grossPerformancePercentageWithCurrencyEffect: new Big( | |||
|           '0.25235044599563974109' | |||
|         ), | |||
|         grossPerformanceWithCurrencyEffect: new Big('20.775774'), | |||
|         hasErrors: false, | |||
|         netPerformance: new Big('26.33'), | |||
|         netPerformancePercentage: new Big('0.29544434470377019749'), | |||
|         netPerformancePercentageWithCurrencyEffect: new Big( | |||
|           '0.24112962014285697628' | |||
|         ), | |||
|         netPerformanceWithCurrencyEffect: new Big('19.851974'), | |||
|         positions: [ | |||
|           { | |||
|             averagePrice: new Big('89.12'), | |||
|             currency: 'USD', | |||
|             dataSource: 'YAHOO', | |||
|             fee: new Big('1'), | |||
|             firstBuyDate: '2023-01-03', | |||
|             grossPerformance: new Big('27.33'), | |||
|             grossPerformancePercentage: new Big('0.3066651705565529623'), | |||
|             grossPerformancePercentageWithCurrencyEffect: new Big( | |||
|               '0.25235044599563974109' | |||
|             ), | |||
|             grossPerformanceWithCurrencyEffect: new Big('20.775774'), | |||
|             investment: new Big('89.12'), | |||
|             investmentWithCurrencyEffect: new Big('82.329056'), | |||
|             netPerformance: new Big('26.33'), | |||
|             netPerformancePercentage: new Big('0.29544434470377019749'), | |||
|             netPerformancePercentageWithCurrencyEffect: new Big( | |||
|               '0.24112962014285697628' | |||
|             ), | |||
|             netPerformanceWithCurrencyEffect: new Big('19.851974'), | |||
|             marketPrice: 116.45, | |||
|             marketPriceInBaseCurrency: 103.10483, | |||
|             quantity: new Big('1'), | |||
|             symbol: 'GOOGL', | |||
|             tags: undefined, | |||
|             timeWeightedInvestment: new Big('89.12'), | |||
|             timeWeightedInvestmentWithCurrencyEffect: new Big('82.329056'), | |||
|             transactionCount: 1 | |||
|           } | |||
|         ], | |||
|         totalInvestment: new Big('89.12'), | |||
|         totalInvestmentWithCurrencyEffect: new Big('82.329056') | |||
|       }); | |||
| 
 | |||
|       expect(investments).toEqual([ | |||
|         { date: '2023-01-03', investment: new Big('89.12') } | |||
|       ]); | |||
| 
 | |||
|       expect(investmentsByMonth).toEqual([ | |||
|         { date: '2023-01-01', investment: new Big('89.12') } | |||
|       ]); | |||
|     }); | |||
|   }); | |||
| }); | |||
								
									
										File diff suppressed because it is too large
									
								
							
						
					| @ -0,0 +1,29 @@ | |||
| export const ExchangeRateDataServiceMock = { | |||
|   getExchangeRatesByCurrency: ({ | |||
|     currencies, | |||
|     endDate, | |||
|     startDate, | |||
|     targetCurrency | |||
|   }): Promise<any> => { | |||
|     if (targetCurrency === 'CHF') { | |||
|       return Promise.resolve({ | |||
|         CHF: { | |||
|           '2015-01-01': 1, | |||
|           '2017-12-31': 1, | |||
|           '2018-01-01': 1, | |||
|           '2023-01-03': 1, | |||
|           '2023-07-10': 1 | |||
|         }, | |||
|         USD: { | |||
|           '2015-01-01': 0.9941099999999999, | |||
|           '2017-12-31': 0.9787, | |||
|           '2018-01-01': 0.97373, | |||
|           '2023-01-03': 0.9238, | |||
|           '2023-07-10': 0.8854 | |||
|         } | |||
|       }); | |||
|     } | |||
| 
 | |||
|     return Promise.resolve({}); | |||
|   } | |||
| }; | |||
					Loading…
					
					
				
		Reference in new issue