import { PortfolioDto, PortfolioPositionDto, } from '../../portfolio/presentation/portfolio.dto'; import { instrumentPosition } from './instrument-position'; const money = (value: string, currency = 'RUB') => ({ value, currency }); const row = ( quantity = '2', average = '10', value = '30', uid = 'common', ): PortfolioPositionDto => ({ instrumentUid: uid, figi: uid, displayName: uid, type: 'STOCK', quantity, averagePurchasePrice: money(average), currentValue: money(value), absoluteReturn: money('10'), isin: null, ticker: null, logoName: null, sector: null, issuer: null, }); const portfolio = (positions: PortfolioPositionDto[]): PortfolioDto => ({ positions, cashBalances: [], totalsByCurrency: [], isStale: false, lastSuccessfulAt: '2026-09-10T00:00:00Z', structure: { currencies: [], unvaluedCategories: [] }, }); describe('stock position', () => { it('weights costs exactly and keeps preferred shares separate', () => { const result = instrumentPosition( portfolio([ row(), row('3', '20', '70'), row('100', '1', '100', 'preferred'), ]), 'common', 'STOCK', ); expect(result.quantity).toBe('5'); expect(result.averagePurchasePrice).toEqual(money('16')); expect(result.currentValue).toEqual(money('100')); expect(result.absoluteReturn).toEqual(money('20')); expect(result.returnPercent).toBe('25'); expect(result.accounts[0].returnPercent).toBe('50'); }); it.each(['9007199254740993', '0.25'])( 'preserves exact quantity %s', (quantity) => { const result = instrumentPosition( portfolio([row(quantity, '0.1', '900719925474099.3')]), 'common', 'STOCK', ); expect(result.quantity).toBe(quantity); expect(result.averagePurchasePrice?.value).toBe('0.1'); }, ); it('does not sum currencies or treat unknown values as zero', () => { const second = { ...row(), currentValue: money('2', 'USD'), averagePurchasePrice: null, absoluteReturn: null, }; const result = instrumentPosition( portfolio([row(), second]), 'common', 'STOCK', ); expect(result.quantity).toBe('4'); expect(result.currentValue).toBeNull(); expect(result.averagePurchasePrice).toBeNull(); expect(result.absoluteReturn).toBeNull(); expect(result.returnPercent).toBeNull(); }); it('keeps no-position and offsetting long/short holdings honest', () => { expect(instrumentPosition(portfolio([]), 'common', 'STOCK')).toMatchObject({ quantity: '0', accounts: [], currentValue: null, returnPercent: null, }); expect( instrumentPosition(portfolio([row(), row('-2')]), 'common', 'STOCK') .averagePurchasePrice, ).toBeNull(); }); });