98 lines
2.8 KiB
TypeScript
98 lines
2.8 KiB
TypeScript
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();
|
|
});
|
|
});
|