From 4210e07947ad15f53eb09b04ba0f38213975a5ba Mon Sep 17 00:00:00 2001 From: neverstop2023 Date: Wed, 21 Jan 2026 22:20:34 +0800 Subject: [PATCH] test: add reproduction test case for COIN realized P&L bug --- ...folio-calculator-baln-buy-and-sell.spec.ts | 89 ++++++++++++++++++- 1 file changed, 87 insertions(+), 2 deletions(-) diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell.spec.ts index 35e4309eb..4d5ebf4e6 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell.spec.ts @@ -79,7 +79,7 @@ describe('PortfolioCalculator', () => { }); describe('get current positions', () => { - it.only('with BALN.SW buy and sell', async () => { + it('with BALN.SW buy and sell', async () => { jest.useFakeTimers().setSystemTime(parseDate('2021-12-18').getTime()); const activities: Activity[] = [ @@ -201,5 +201,90 @@ describe('PortfolioCalculator', () => { { date: '2021-12-01', investment: 0 } ]); }); + + it('with COIN multiple round-trip trades', async () => { + jest.useFakeTimers().setSystemTime(parseDate('2024-12-01').getTime()); + + const activities: Activity[] = [ + { + ...activityDummyData, + date: new Date('2024-10-31'), + feeInAssetProfileCurrency: 16, + feeInBaseCurrency: 16, + quantity: 20, + SymbolProfile: { + ...symbolProfileDummyData, + currency: 'USD', + dataSource: 'MANUAL', + name: 'Coinbase Global, Inc.', + symbol: 'COIN' + }, + type: 'BUY', + unitPriceInAssetProfileCurrency: 218 + }, + { + ...activityDummyData, + date: new Date('2024-11-13'), + feeInAssetProfileCurrency: 16.18, + feeInBaseCurrency: 16.18, + quantity: 20, + SymbolProfile: { + ...symbolProfileDummyData, + currency: 'USD', + dataSource: 'MANUAL', + name: 'Coinbase Global, Inc.', + symbol: 'COIN' + }, + type: 'SELL', + unitPriceInAssetProfileCurrency: 320 + }, + { + ...activityDummyData, + date: new Date('2024-11-19'), + feeInAssetProfileCurrency: 16, + feeInBaseCurrency: 16, + quantity: 30, + SymbolProfile: { + ...symbolProfileDummyData, + currency: 'USD', + dataSource: 'MANUAL', + name: 'Coinbase Global, Inc.', + symbol: 'COIN' + }, + type: 'BUY', + unitPriceInAssetProfileCurrency: 290 + }, + { + ...activityDummyData, + date: new Date('2024-11-20'), + feeInAssetProfileCurrency: 16.27, + feeInBaseCurrency: 16.27, + quantity: 30, + SymbolProfile: { + ...symbolProfileDummyData, + currency: 'USD', + dataSource: 'MANUAL', + name: 'Coinbase Global, Inc.', + symbol: 'COIN' + }, + type: 'SELL', + unitPriceInAssetProfileCurrency: 321 + } + ]; + + const portfolioCalculator = portfolioCalculatorFactory.createCalculator({ + activities, + calculationType: PerformanceCalculationType.ROAI, + currency: 'USD', + userId: userDummyData.id + }); + + const portfolioSnapshot = await portfolioCalculator.computeSnapshot(); + const coinPosition = portfolioSnapshot.positions.find((p) => p.symbol === 'COIN'); + + expect(coinPosition).toBeDefined(); + expect(coinPosition.netPerformance).not.toBeNull(); + expect(coinPosition.netPerformance.toNumber()).toBeCloseTo(2905.55, 2); + }); }); -}); +}); \ No newline at end of file