Browse Source

Merge 7fe1c869fb into e1d343fb70

pull/6298/merge
Neeraj Bachani 10 hours ago
committed by GitHub
parent
commit
d04554c34b
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 190
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-jnug-buy-and-sell-and-buy-and-sell.spec.ts
  2. 7
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator.ts
  3. 11
      apps/api/src/app/portfolio/current-rate.service.mock.ts
  4. 58
      test/import/ok/jnug-buy-and-sell-and-buy-and-sell.json

190
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-jnug-buy-and-sell-and-buy-and-sell.spec.ts

@ -0,0 +1,190 @@
import {
activityDummyData,
loadExportFile,
symbolProfileDummyData,
userDummyData
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils';
import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock';
import { parseDate } from '@ghostfolio/common/helper';
import { Activity, ExportResponse } from '@ghostfolio/common/interfaces';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
import { Big } from 'big.js';
import { join } from 'node:path';
jest.mock('@ghostfolio/api/app/portfolio/current-rate.service', () => {
return {
CurrentRateService: jest.fn().mockImplementation(() => {
return CurrentRateServiceMock;
})
};
});
jest.mock(
'@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service',
() => {
return {
PortfolioSnapshotService: jest.fn().mockImplementation(() => {
return PortfolioSnapshotServiceMock;
})
};
}
);
jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
return {
RedisCacheService: jest.fn().mockImplementation(() => {
return RedisCacheServiceMock;
})
};
});
describe('PortfolioCalculator', () => {
let exportResponse: ExportResponse;
let configurationService: ConfigurationService;
let currentRateService: CurrentRateService;
let exchangeRateDataService: ExchangeRateDataService;
let portfolioCalculatorFactory: PortfolioCalculatorFactory;
let portfolioSnapshotService: PortfolioSnapshotService;
let redisCacheService: RedisCacheService;
beforeAll(() => {
exportResponse = loadExportFile(
join(
__dirname,
'../../../../../../../test/import/ok/jnug-buy-and-sell-and-buy-and-sell.json'
)
);
});
beforeEach(() => {
configurationService = new ConfigurationService();
currentRateService = new CurrentRateService(null, null, null, null);
exchangeRateDataService = new ExchangeRateDataService(
null,
null,
null,
null
);
portfolioSnapshotService = new PortfolioSnapshotService(null);
redisCacheService = new RedisCacheService(null, null);
portfolioCalculatorFactory = new PortfolioCalculatorFactory(
configurationService,
currentRateService,
exchangeRateDataService,
portfolioSnapshotService,
redisCacheService
);
});
describe('get current positions', () => {
it.only('with JNUG buy and sell', async () => {
jest.useFakeTimers().setSystemTime(parseDate('2025-12-28').getTime());
const activities: Activity[] = exportResponse.activities.map(
(activity) => ({
...activityDummyData,
...activity,
date: parseDate(activity.date),
feeInAssetProfileCurrency: activity.fee,
feeInBaseCurrency: activity.fee,
SymbolProfile: {
...symbolProfileDummyData,
currency: activity.currency,
dataSource: activity.dataSource,
name: 'Direxion Daily Junior Gold Miners Index Bull 2X Shares',
symbol: activity.symbol
},
unitPriceInAssetProfileCurrency: activity.unitPrice
})
);
const portfolioCalculator = portfolioCalculatorFactory.createCalculator({
activities,
calculationType: PerformanceCalculationType.ROAI,
currency: exportResponse.user.settings.currency,
userId: userDummyData.id
});
const portfolioSnapshot = await portfolioCalculator.computeSnapshot();
const investments = portfolioCalculator.getInvestments();
const investmentsByMonth = portfolioCalculator.getInvestmentsByGroup({
data: portfolioSnapshot.historicalData,
groupBy: 'month'
});
const investmentsByYear = portfolioCalculator.getInvestmentsByGroup({
data: portfolioSnapshot.historicalData,
groupBy: 'year'
});
expect(portfolioSnapshot).toMatchObject({
currentValueInBaseCurrency: new Big('0'),
errors: [],
hasErrors: false,
positions: [
{
activitiesCount: 4,
averagePrice: new Big('0'),
currency: 'USD',
dataSource: 'YAHOO',
dateOfFirstActivity: '2025-12-11',
dividend: new Big('0'),
dividendInBaseCurrency: new Big('0'),
fee: new Big('4'),
feeInBaseCurrency: new Big('4'),
grossPerformance: new Big('43.95'), // (1890.00 - 1885.05) + (2080.10 - 2041.10)
grossPerformanceWithCurrencyEffect: new Big('43.95'), // (1890.00 - 1885.05) + (2080.10 - 2041.10)
investment: new Big('0'),
investmentWithCurrencyEffect: new Big('0'),
netPerformance: new Big('39.95'), // (1890.00 - 1885.05) + (2080.10 - 2041.10) - 4
netPerformanceWithCurrencyEffectMap: {
max: new Big('39.95') // (1890.00 - 1885.05) + (2080.10 - 2041.10) - 4
},
marketPrice: 237.8000030517578,
marketPriceInBaseCurrency: 237.8000030517578,
quantity: new Big('0'),
symbol: 'JNUG',
tags: [],
valueInBaseCurrency: new Big('0')
}
],
totalFeesWithCurrencyEffect: new Big('4'),
totalInterestWithCurrencyEffect: new Big('0'),
totalInvestment: new Big('0'),
totalInvestmentWithCurrencyEffect: new Big('0'),
totalLiabilitiesWithCurrencyEffect: new Big('0')
});
expect(investments).toEqual([
{ date: '2025-12-11', investment: new Big('1885.05') },
{ date: '2025-12-18', investment: new Big('2041.1') },
{ date: '2025-12-28', investment: new Big('0') }
]);
expect(investmentsByMonth).toEqual([
{ date: '2025-12-01', investment: 0 }
]);
expect(investmentsByYear).toEqual([
{ date: '2025-01-01', investment: 0 }
]);
});
});
});

7
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator.ts

@ -626,6 +626,13 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator {
totalQuantityFromBuyTransactions
);
// Reset tracking variables when position is fully closed
if (totalUnits.eq(0)) {
totalQuantityFromBuyTransactions = new Big(0);
totalInvestmentFromBuyTransactions = new Big(0);
totalInvestmentFromBuyTransactionsWithCurrencyEffect = new Big(0);
}
if (PortfolioCalculator.ENABLE_LOGGING) {
console.log(
'grossPerformanceFromSells',

11
apps/api/src/app/portfolio/current-rate.service.mock.ts

@ -64,6 +64,17 @@ function mockGetValue(symbol: string, date: Date) {
return { marketPrice: 0 };
case 'JNUG':
if (isSameDay(parseDate('2025-12-10'), date)) {
return { marketPrice: 204.5599975585938 };
} else if (isSameDay(parseDate('2025-12-17'), date)) {
return { marketPrice: 203.9700012207031 };
} else if (isSameDay(parseDate('2025-12-28'), date)) {
return { marketPrice: 237.8000030517578 };
}
return { marketPrice: 0 };
case 'MSFT':
if (isSameDay(parseDate('2021-09-16'), date)) {
return { marketPrice: 89.12 };

58
test/import/ok/jnug-buy-and-sell-and-buy-and-sell.json

@ -0,0 +1,58 @@
{
"meta": {
"date": "2026-02-07T02:09:15.272Z",
"version": "dev"
},
"activities": [
{
"currency": "USD",
"dataSource": "YAHOO",
"date": "2025-12-11T05:00:00.000Z",
"fee": 1,
"id": "cea33621-9f4b-4cea-9eb7-be38264888aa",
"quantity": 9,
"symbol": "JNUG",
"type": "BUY",
"unitPrice": 209.45
},
{
"currency": "USD",
"dataSource": "YAHOO",
"date": "2025-12-18T05:00:00.000Z",
"fee": 1,
"id": "53be2e35-a0af-476c-9e63-9b1a437114a4",
"quantity": 9,
"symbol": "JNUG",
"type": "SELL",
"unitPrice": 210
},
{
"currency": "USD",
"dataSource": "YAHOO",
"date": "2025-12-18T05:00:00.000Z",
"fee": 1,
"id": "6648eeeb-8ea5-46b6-9a30-f278a9ed477b",
"quantity": 10,
"symbol": "JNUG",
"type": "BUY",
"unitPrice": 204.11
},
{
"currency": "USD",
"dataSource": "YAHOO",
"date": "2025-12-28T05:00:00.000Z",
"fee": 1,
"id": "861e736d-0086-496c-8f85-31328479cf63",
"quantity": 10,
"symbol": "JNUG",
"type": "SELL",
"unitPrice": 208.01
}
],
"user": {
"settings": {
"currency": "USD",
"performanceCalculationType": "ROAI"
}
}
}
Loading…
Cancel
Save