fix: stabilize broker display models
This commit is contained in:
parent
2462f2122c
commit
6d2df6a12b
@ -95,6 +95,22 @@ describe('buildBrokerAllocation', () => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('ignores a tiny negative residual caused by decimal arithmetic', () => {
|
||||||
|
const result = buildBrokerAllocation(portfolio({ shares: 0.1, bonds: 0.2, portfolio: 0.3 }));
|
||||||
|
|
||||||
|
expect(result.sectors.map(({ key }) => key)).toEqual(['shares', 'bonds']);
|
||||||
|
expect(result.negative).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('ignores a tiny positive residual caused by decimal arithmetic', () => {
|
||||||
|
const result = buildBrokerAllocation(
|
||||||
|
portfolio({ shares: 0.3, portfolio: 0.30000000000000004 }),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.sectors.map(({ key }) => key)).toEqual(['shares']);
|
||||||
|
expect(result.negative).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
it('returns no allocation for missing or nonpositive portfolio totals', () => {
|
it('returns no allocation for missing or nonpositive portfolio totals', () => {
|
||||||
expect(buildBrokerAllocation(portfolio({ shares: 100, portfolio: null }))).toEqual({
|
expect(buildBrokerAllocation(portfolio({ shares: 100, portfolio: null }))).toEqual({
|
||||||
total: 0,
|
total: 0,
|
||||||
|
|||||||
@ -32,12 +32,22 @@ export function buildBrokerAllocation(portfolio: BrokerPortfolio): {
|
|||||||
const bonds = portfolio.totals.bonds?.value ?? 0;
|
const bonds = portfolio.totals.bonds?.value ?? 0;
|
||||||
const etf = portfolio.totals.etf?.value ?? 0;
|
const etf = portfolio.totals.etf?.value ?? 0;
|
||||||
const cash = portfolio.totals.currencies?.value ?? 0;
|
const cash = portfolio.totals.currencies?.value ?? 0;
|
||||||
|
const mappedTotal = shares + bonds + etf + cash;
|
||||||
|
const residual = total - mappedTotal;
|
||||||
|
const residualTolerance =
|
||||||
|
Number.EPSILON *
|
||||||
|
Math.max(
|
||||||
|
1,
|
||||||
|
Math.abs(total),
|
||||||
|
Math.abs(shares) + Math.abs(bonds) + Math.abs(etf) + Math.abs(cash),
|
||||||
|
) *
|
||||||
|
8;
|
||||||
const values: Record<BrokerAllocationKey, number> = {
|
const values: Record<BrokerAllocationKey, number> = {
|
||||||
shares,
|
shares,
|
||||||
bonds,
|
bonds,
|
||||||
etf,
|
etf,
|
||||||
cash,
|
cash,
|
||||||
other: total - shares - bonds - etf - cash,
|
other: Math.abs(residual) <= residualTolerance ? 0 : residual,
|
||||||
};
|
};
|
||||||
|
|
||||||
const sectors: BrokerAllocationItem[] = [];
|
const sectors: BrokerAllocationItem[] = [];
|
||||||
|
|||||||
@ -138,6 +138,11 @@ describe('broker display helpers', () => {
|
|||||||
expect(labels).toEqual([...labels].sort((left, right) => left.localeCompare(right, 'ru')));
|
expect(labels).toEqual([...labels].sort((left, right) => left.localeCompare(right, 'ru')));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('keeps operation type options immutable at runtime', () => {
|
||||||
|
expect(Object.isFrozen(BROKER_OPERATION_TYPE_OPTIONS)).toBe(true);
|
||||||
|
expect(BROKER_OPERATION_TYPE_OPTIONS.every((option) => Object.isFrozen(option))).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
it('validates only exact known operation type values', () => {
|
it('validates only exact known operation type values', () => {
|
||||||
expect(isBrokerOperationType('OPERATION_TYPE_COUPON')).toBe(true);
|
expect(isBrokerOperationType('OPERATION_TYPE_COUPON')).toBe(true);
|
||||||
expect(isBrokerOperationType('operation_type_coupon')).toBe(false);
|
expect(isBrokerOperationType('operation_type_coupon')).toBe(false);
|
||||||
|
|||||||
@ -94,9 +94,13 @@ const OPERATION_TYPE_LABELS: Record<string, string> = {
|
|||||||
OPERATION_TYPE_OUTPUT_SECURITIES: 'Списание бумаг',
|
OPERATION_TYPE_OUTPUT_SECURITIES: 'Списание бумаг',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const BROKER_OPERATION_TYPE_OPTIONS = Object.entries(OPERATION_TYPE_LABELS)
|
export const BROKER_OPERATION_TYPE_OPTIONS: ReadonlyArray<
|
||||||
.map(([value, label]) => ({ value, label }))
|
Readonly<{ value: string; label: string }>
|
||||||
.sort((left, right) => left.label.localeCompare(right.label, 'ru'));
|
> = Object.freeze(
|
||||||
|
Object.entries(OPERATION_TYPE_LABELS)
|
||||||
|
.map(([value, label]) => Object.freeze({ value, label }))
|
||||||
|
.sort((left, right) => left.label.localeCompare(right.label, 'ru')),
|
||||||
|
);
|
||||||
|
|
||||||
const BROKER_OPERATION_TYPES = new Set(BROKER_OPERATION_TYPE_OPTIONS.map(({ value }) => value));
|
const BROKER_OPERATION_TYPES = new Set(BROKER_OPERATION_TYPE_OPTIONS.map(({ value }) => value));
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user