refactor!: support RUB only (WealthLedger/main#55) (#204)
BREAKING CHANGE: per-currency API fields replaced with single RUB Money.
This commit was merged in pull request #204.
This commit is contained in:
@@ -340,4 +340,59 @@ describe('MoexClient', () => {
|
||||
expect(starts).toEqual(['0', '1']);
|
||||
},
|
||||
);
|
||||
|
||||
it('filters non-RUB coupons and maturity out of bond events', async () => {
|
||||
const pages: Record<string, unknown> = {
|
||||
coupons: Response.json({
|
||||
'coupons': {
|
||||
columns: ['coupondate', 'value', 'faceunit'],
|
||||
data: [
|
||||
['2099-02-01', '2', 'SUR'],
|
||||
['2099-02-02', '3', 'USD'],
|
||||
],
|
||||
},
|
||||
'coupons.cursor': {
|
||||
columns: ['INDEX', 'TOTAL', 'PAGESIZE'],
|
||||
data: [[0, 2, 100]],
|
||||
},
|
||||
}),
|
||||
offers: Response.json({
|
||||
'offers': {
|
||||
columns: ['offerdate'],
|
||||
data: [['2099-02-28']],
|
||||
},
|
||||
'offers.cursor': {
|
||||
columns: ['INDEX', 'TOTAL', 'PAGESIZE'],
|
||||
data: [[0, 1, 100]],
|
||||
},
|
||||
}),
|
||||
description: Response.json({
|
||||
description: {
|
||||
columns: ['name', 'value'],
|
||||
data: [
|
||||
['MATDATE', '2099-02-28'],
|
||||
['FACEVALUE', '500'],
|
||||
['FACEUNIT', 'USD'],
|
||||
],
|
||||
},
|
||||
}),
|
||||
};
|
||||
const fetchMock = jest
|
||||
.spyOn(global, 'fetch')
|
||||
.mockImplementation((input) => {
|
||||
const url = new URL(input instanceof Request ? input.url : String(input));
|
||||
const block = url.searchParams.get('iss.only')?.split(',')[0] ?? 'description';
|
||||
// eslint-disable-next-line security/detect-object-injection -- Keys are fixed MOEX block names of this test's page map.
|
||||
const page = pages[block] ?? pages.description;
|
||||
return Promise.resolve(page as Response);
|
||||
});
|
||||
try {
|
||||
await expect(new MoexClient().bondEvents('BOND')).resolves.toEqual([
|
||||
{ type: 'coupon', date: '2099-02-01', value: '2', currency: 'RUB' },
|
||||
{ type: 'offer', date: '2099-02-28' },
|
||||
]);
|
||||
} finally {
|
||||
fetchMock.mockRestore();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -157,9 +157,9 @@ export class MoexClient {
|
||||
for (const row of coupons) {
|
||||
const date = text(row.coupondate);
|
||||
const value = numberText(row.value);
|
||||
const currency = currencyText(row.faceunit);
|
||||
if (date && value && currency) {
|
||||
events.push({ type: 'coupon', date, value, currency });
|
||||
// RUB-only: coupons in other currencies are filtered out at this ingest boundary.
|
||||
if (date && value && currencyText(row.faceunit) === 'RUB') {
|
||||
events.push({ type: 'coupon', date, value, currency: 'RUB' });
|
||||
}
|
||||
}
|
||||
for (const row of offers) {
|
||||
@@ -171,9 +171,8 @@ export class MoexClient {
|
||||
);
|
||||
const date = values.get('MATDATE');
|
||||
const value = numberText(values.get('FACEVALUE'));
|
||||
const currency = currencyText(values.get('FACEUNIT'));
|
||||
if (date && value && currency) {
|
||||
events.push({ type: 'maturity', date, value, currency });
|
||||
if (date && value && currencyText(values.get('FACEUNIT')) === 'RUB') {
|
||||
events.push({ type: 'maturity', date, value, currency: 'RUB' });
|
||||
}
|
||||
return events;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { Decimal } from '../../common/decimal';
|
||||
import type { Money } from '../../common/money.types';
|
||||
|
||||
import { Decimal, formatDecimal } from '../../common/decimal';
|
||||
|
||||
const MISSING_PROTO_COMPONENT = 0;
|
||||
|
||||
@@ -6,6 +8,12 @@ const NANO = 1_000_000_000n;
|
||||
|
||||
export type ProtoDecimal = { nano?: number | string; units?: number | string };
|
||||
|
||||
export function rubMoney(value?: ProtoDecimal & { currency?: string }): Money | null {
|
||||
return value?.currency?.trim().toUpperCase() === 'RUB'
|
||||
? { currency: 'RUB', value: formatDecimal(decimal(value)) }
|
||||
: null;
|
||||
}
|
||||
|
||||
export function decimal(value: ProtoDecimal): Decimal {
|
||||
return {
|
||||
value: BigInt(value.units ?? MISSING_PROTO_COMPONENT) * NANO + BigInt(value.nano ?? MISSING_PROTO_COMPONENT),
|
||||
|
||||
Reference in New Issue
Block a user