178 lines
6.1 KiB
TypeScript
178 lines
6.1 KiB
TypeScript
import type { BrokerOperation, BrokerPosition } from '../../api/responses';
|
||
|
||
export type BrokerPositionGroup = 'shares' | 'bonds' | 'other';
|
||
export type BrokerOperationImpact = 'adds' | 'reduces' | 'neutral' | 'unknown';
|
||
|
||
type BrokerInstrumentLinkInput = {
|
||
ticker: string | null;
|
||
instrumentType: string | null;
|
||
classCode: string | null;
|
||
};
|
||
|
||
const STOCK_CLASS_CODES = new Set(['TQBR']);
|
||
const BOND_CLASS_CODES = new Set(['TQOB', 'TQCB', 'TQIR']);
|
||
|
||
const TRADE_TYPES = new Set([
|
||
'OPERATION_TYPE_BUY',
|
||
'OPERATION_TYPE_BUY_CARD',
|
||
'OPERATION_TYPE_SELL',
|
||
'OPERATION_TYPE_SELL_CARD',
|
||
'OPERATION_TYPE_BUY_MARGIN',
|
||
'OPERATION_TYPE_SELL_MARGIN',
|
||
'OPERATION_TYPE_DELIVERY_BUY',
|
||
'OPERATION_TYPE_DELIVERY_SELL',
|
||
]);
|
||
|
||
const BOND_REPAYMENT_TYPES = new Set([
|
||
'OPERATION_TYPE_BOND_REPAYMENT',
|
||
'OPERATION_TYPE_BOND_REPAYMENT_FULL',
|
||
]);
|
||
|
||
const INCOME_TYPES = new Set(['OPERATION_TYPE_COUPON', 'OPERATION_TYPE_DIVIDEND']);
|
||
|
||
const TAX_TYPES = new Set([
|
||
'OPERATION_TYPE_TAX',
|
||
'OPERATION_TYPE_BOND_TAX',
|
||
'OPERATION_TYPE_DIVIDEND_TAX',
|
||
'OPERATION_TYPE_TAX_CORRECTION',
|
||
'OPERATION_TYPE_TAX_CORRECTION_COUPON',
|
||
]);
|
||
|
||
const FEE_TYPES = new Set([
|
||
'OPERATION_TYPE_BROKER_FEE',
|
||
'OPERATION_TYPE_SERVICE_FEE',
|
||
'OPERATION_TYPE_MARGIN_FEE',
|
||
'OPERATION_TYPE_SUCCESS_FEE',
|
||
]);
|
||
|
||
const TRANSFER_INPUT_TYPES = new Set([
|
||
'OPERATION_TYPE_INPUT',
|
||
'OPERATION_TYPE_INPUT_SWIFT',
|
||
'OPERATION_TYPE_INPUT_ACQUIRING',
|
||
'OPERATION_TYPE_INP_MULTI',
|
||
]);
|
||
|
||
const TRANSFER_OUTPUT_TYPES = new Set([
|
||
'OPERATION_TYPE_OUTPUT',
|
||
'OPERATION_TYPE_OUTPUT_SWIFT',
|
||
'OPERATION_TYPE_OUTPUT_ACQUIRING',
|
||
'OPERATION_TYPE_OUT_MULTI',
|
||
]);
|
||
|
||
const SECURITY_TRANSFER_TYPES = new Set([
|
||
'OPERATION_TYPE_INPUT_SECURITIES',
|
||
'OPERATION_TYPE_OUTPUT_SECURITIES',
|
||
'OPERATION_TYPE_TRANS_IIS_BS',
|
||
'OPERATION_TYPE_TRANS_BS_BS',
|
||
]);
|
||
|
||
const OPERATION_TYPE_LABELS: Record<string, string> = {
|
||
OPERATION_TYPE_BUY: 'Покупка',
|
||
OPERATION_TYPE_BUY_CARD: 'Покупка',
|
||
OPERATION_TYPE_SELL: 'Продажа',
|
||
OPERATION_TYPE_SELL_CARD: 'Продажа',
|
||
OPERATION_TYPE_BUY_MARGIN: 'Покупка с маржой',
|
||
OPERATION_TYPE_SELL_MARGIN: 'Продажа с маржой',
|
||
OPERATION_TYPE_DELIVERY_BUY: 'Поставка покупки',
|
||
OPERATION_TYPE_DELIVERY_SELL: 'Поставка продажи',
|
||
OPERATION_TYPE_COUPON: 'Выплата купона',
|
||
OPERATION_TYPE_DIVIDEND: 'Дивиденды',
|
||
OPERATION_TYPE_BOND_REPAYMENT: 'Погашение облигации',
|
||
OPERATION_TYPE_BOND_REPAYMENT_FULL: 'Полное погашение облигации',
|
||
OPERATION_TYPE_TAX: 'Налог',
|
||
OPERATION_TYPE_BOND_TAX: 'Налог по облигациям',
|
||
OPERATION_TYPE_DIVIDEND_TAX: 'Налог на дивиденды',
|
||
OPERATION_TYPE_TAX_CORRECTION: 'Корректировка налога',
|
||
OPERATION_TYPE_TAX_CORRECTION_COUPON: 'Корректировка налога по купону',
|
||
OPERATION_TYPE_BROKER_FEE: 'Комиссия брокера',
|
||
OPERATION_TYPE_SERVICE_FEE: 'Комиссия за обслуживание',
|
||
OPERATION_TYPE_MARGIN_FEE: 'Комиссия за маржу',
|
||
OPERATION_TYPE_SUCCESS_FEE: 'Комиссия за результат',
|
||
OPERATION_TYPE_INPUT: 'Пополнение',
|
||
OPERATION_TYPE_OUTPUT: 'Вывод средств',
|
||
OPERATION_TYPE_INPUT_SECURITIES: 'Зачисление бумаг',
|
||
OPERATION_TYPE_OUTPUT_SECURITIES: 'Списание бумаг',
|
||
};
|
||
|
||
export const BROKER_OPERATION_TYPE_OPTIONS: ReadonlyArray<
|
||
Readonly<{ value: string; label: string }>
|
||
> = 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));
|
||
|
||
export function isBrokerOperationType(value: string | null): value is string {
|
||
return value !== null && BROKER_OPERATION_TYPES.has(value);
|
||
}
|
||
|
||
export function getBrokerPositionGroup(
|
||
position: Pick<BrokerPosition, 'instrumentType'>,
|
||
): BrokerPositionGroup {
|
||
const instrumentType = position.instrumentType?.toLowerCase();
|
||
|
||
if (instrumentType === 'share') return 'shares';
|
||
if (instrumentType === 'bond') return 'bonds';
|
||
|
||
return 'other';
|
||
}
|
||
|
||
export function getBrokerInstrumentPath(input: BrokerInstrumentLinkInput): string | null {
|
||
const ticker = input.ticker?.trim().toUpperCase();
|
||
if (!ticker) return null;
|
||
|
||
const instrumentType = input.instrumentType?.toLowerCase();
|
||
const classCode = input.classCode?.toUpperCase() ?? null;
|
||
|
||
if (instrumentType === 'share') {
|
||
return `/stocks/${encodeURIComponent(ticker)}`;
|
||
}
|
||
|
||
if (instrumentType === 'bond') {
|
||
return `/bonds/${encodeURIComponent(ticker)}`;
|
||
}
|
||
|
||
if (instrumentType) return null;
|
||
|
||
if (classCode && STOCK_CLASS_CODES.has(classCode)) return `/stocks/${encodeURIComponent(ticker)}`;
|
||
if (classCode && BOND_CLASS_CODES.has(classCode)) return `/bonds/${encodeURIComponent(ticker)}`;
|
||
|
||
return null;
|
||
}
|
||
|
||
export function getBrokerOperationTypeLabel(
|
||
operation: Pick<BrokerOperation, 'type' | 'description'>,
|
||
): string {
|
||
const knownLabel = OPERATION_TYPE_LABELS[operation.type];
|
||
if (knownLabel) return knownLabel;
|
||
if (operation.description) return operation.description;
|
||
|
||
return operation.type
|
||
.replace(/^OPERATION_TYPE_/, '')
|
||
.replace(/_/g, ' ')
|
||
.toLowerCase();
|
||
}
|
||
|
||
export function getBrokerOperationImpact(
|
||
operation: Pick<BrokerOperation, 'type' | 'category' | 'payment'>,
|
||
): BrokerOperationImpact {
|
||
if (
|
||
TRADE_TYPES.has(operation.type) ||
|
||
BOND_REPAYMENT_TYPES.has(operation.type) ||
|
||
SECURITY_TRANSFER_TYPES.has(operation.type)
|
||
) {
|
||
return 'neutral';
|
||
}
|
||
|
||
if (INCOME_TYPES.has(operation.type)) return 'adds';
|
||
if (TAX_TYPES.has(operation.type) || FEE_TYPES.has(operation.type)) return 'reduces';
|
||
if (TRANSFER_INPUT_TYPES.has(operation.type)) return 'adds';
|
||
if (TRANSFER_OUTPUT_TYPES.has(operation.type)) return 'reduces';
|
||
if (operation.category === 'tax' || operation.category === 'fee') return 'reduces';
|
||
if (operation.category === 'income' && (operation.payment?.value ?? 0) > 0) return 'adds';
|
||
|
||
return 'unknown';
|
||
}
|