Filter non-RUB data at ingest, collapse per-currency collections into single RUB Money, drop currency from TbankPortfolioValuePoint, remove NON_RUB_ACTIVITY and screener currency filter. BREAKING CHANGE: per-currency API fields replaced with single RUB Money; stock screener currency param removed.
50 lines
2.2 KiB
TypeScript
50 lines
2.2 KiB
TypeScript
import { formatDecimal } from '#src/common/decimal';
|
|
|
|
import { candidateTransfers, readPerformanceOperations, sumFlows } from './performance-flows';
|
|
|
|
const at = (date: string) => new Date(date);
|
|
const op = (accountId: string, type: string, date: string, units: string, currency = 'rub') => ({
|
|
accountId, occurredAt: at(date), instrumentUid: null,
|
|
payload: { type, payment: { units, nano: 0, currency } },
|
|
});
|
|
|
|
describe('performance cash flows', () => {
|
|
it('includes only external cash and preserves dividends within the portfolio', () => {
|
|
const operations = [
|
|
op('A', 'OPERATION_TYPE_INPUT', '2025-01-01T15:00:00Z', '100000'),
|
|
op('A', 'OPERATION_TYPE_INPUT_ACQUIRING', '2025-02-01T15:00:00Z', '50000'),
|
|
op('A', 'OPERATION_TYPE_OUTPUT', '2025-03-01T15:00:00Z', '-20000'),
|
|
op('A', 'OPERATION_TYPE_DIVIDEND', '2025-04-01T15:00:00Z', '3000'),
|
|
];
|
|
const result = readPerformanceOperations(operations);
|
|
expect(formatDecimal(sumFlows(result.flows))).toBe('130000');
|
|
expect(result.flows).toHaveLength(3);
|
|
});
|
|
|
|
it('treats equal opposite cash movements as unconfirmed transfer candidates, not a proven pair', () => {
|
|
const result = readPerformanceOperations([
|
|
op('A', 'OPERATION_TYPE_OUTPUT', '2025-03-01T15:00:00Z', '-50000'),
|
|
op('B', 'OPERATION_TYPE_INPUT', '2025-03-01T15:01:00Z', '50000'),
|
|
]);
|
|
expect(candidateTransfers(result)).toEqual([{ accountIds: ['A', 'B'], type: 'cash' }]);
|
|
expect(result.flows).toHaveLength(2);
|
|
});
|
|
|
|
it('detects unvalued securities transfers', () => {
|
|
const result = readPerformanceOperations([
|
|
op('B', 'OPERATION_TYPE_INPUT_SECURITIES', '2025-01-01T15:00:00Z', '0'),
|
|
]);
|
|
expect([...result.unvaluedAccounts]).toEqual(['B']);
|
|
});
|
|
|
|
it('skips non-RUB cash movements read back from persisted payloads', () => {
|
|
const result = readPerformanceOperations([
|
|
op('A', 'OPERATION_TYPE_INPUT', '2025-01-01T15:00:00Z', '100'),
|
|
op('A', 'OPERATION_TYPE_INPUT', '2025-02-01T15:00:00Z', '21', 'usd'),
|
|
]);
|
|
expect(result.flows).toHaveLength(1);
|
|
expect(formatDecimal(sumFlows(result.flows))).toBe('100');
|
|
expect([...result.missingAmountAccounts]).toEqual(['A']);
|
|
});
|
|
});
|