Дивиденды и купоны
Начисление дохода и отражение в CFA‑RUB
Дивиденды и купоны
1) Общий принцип
Доход по инвестиционным ЦФА начисляется в CFA‑RUB, чтобы:
- расчёты оставались в рублёвом контуре;
- выплаты были совместимы с СБП и банковскими расчётами;
- доходность была отражена в выписках и отчётности.
2) Дивиденды (CFA‑EQ)
Дивидендный сценарий:
- Объявление: Эмитент фиксирует дату отсечки (Record Date) и размер выплаты.
- Snapshot:
Dividends Serviceзапрашивает уCFA-Coreреестр владельцев на момент Record Date. - Расчёт: Для каждого держателя вычисляется:
amount = balance * div_per_share. - Выплата:
Ledger-DBсоздаёт начисления и удерживает НДФЛ.- Пользователи получают CFA-RUB на счета.
Модель проводок (Дивиденды)
| Субсчет | Дебет | Кредит | Описание |
|---|---|---|---|
| Issuer.Reserve | -1000 RUB | Списание средств эмитента | |
| User.Main | +870 RUB | Зачисление дивидендов (чистыми) | |
| System.Tax | +130 RUB | Удержание НДФЛ (13%) |
3) Купоны (CFA‑BOND)
Купонный сценарий:
- календарные выплаты по графику;
- расчёт сумм и отражение как начисления.
4) Контроль
- сверка суммарных начислений с фактическими обязательствами эмитента;
- audit trail (кто инициировал начисление, на основании каких данных);
- возможность выписки по начислениям.
5) API (gRPC Connect)
DividendsService
CalculateDividends
Расчет дивидендов для всех держателей.
message CalculateDividendsRequest {
RequestContext context = 1;
string instrument_id = 2;
Decimal dividend_per_share = 3;
google.protobuf.Timestamp record_date = 4;
}
message CalculateDividendsResponse {
repeated DividendCalculation calculations = 1;
}
message DividendCalculation {
string holder_id = 1;
Decimal amount = 2;
Decimal tax = 3;
Decimal net_amount = 4;
}PayDividends
Выплата дивидендов держателям.
message PayDividendsRequest {
RequestContext context = 1;
string instrument_id = 2;
repeated DividendPayment payments = 3;
}
message DividendPayment {
string holder_id = 1;
MonetaryAmount amount = 2;
}CouponsService
CalculateCoupons
Расчет купонов для всех держателей.
message CalculateCouponsRequest {
RequestContext context = 1;
string instrument_id = 2;
Decimal coupon_rate = 3;
google.protobuf.Timestamp payment_date = 4;
}
message CalculateCouponsResponse {
repeated CouponCalculation calculations = 1;
}PayCoupons
Выплата купонов держателям.
message PayCouponsRequest {
RequestContext context = 1;
string instrument_id = 2;
repeated CouponPayment payments = 3;
}6) Use Cases
CalculateDividendsUseCase
Расчет дивидендов для всех держателей.
async execute(request: CalculateDividendsRequest): Promise<CalculateDividendsResponse> {
const { context, instrumentId, dividendPerShare, recordDate } = request;
// 1. Получение реестра держателей
const holders = await this.cfaClient.listHolders(context, instrumentId);
// 2. Расчет дивидендов
const calculations = holders.map(holder => {
const amount = holder.balance * dividendPerShare;
const tax = amount * 0.13; // 13% НДФЛ
const netAmount = amount - tax;
return {
holderId: holder.holderId,
amount,
tax,
netAmount,
};
});
// 3. Проверка баланса эмитента
const totalAmount = calculations.reduce((sum, c) => sum + c.amount, 0);
const issuerBalance = await this.ledgerClient.getBalance(
`${instrumentId}:issuer:reserve`,
'CFA-RUB'
);
if (issuerBalance < totalAmount) {
throw new Error('Insufficient issuer balance for dividends');
}
return { calculations };
}PayDividendsUseCase
Выплата дивидендов держателям.
async execute(request: PayDividendsRequest): Promise<void> {
const { context, instrumentId, payments } = request;
// 1. Создание операции
const operation = await this.ledgerClient.createOperation({
context,
operation: {
type: OperationType.DIVIDEND,
status: OperationStatus.PROCESSING,
userId: 'system',
idempotencyKey: crypto.randomUUID(),
externalRef: `dividends-${instrumentId}`,
},
postings: payments.map(payment => [
{
debitSubaccountId: `${instrumentId}:issuer:reserve`,
creditSubaccountId: `${payment.holderId}:main:CFA-RUB`,
amount: payment.amount,
},
]),
});
// 2. Публикация событий
await this.eventPublisher.publish({
type: 'DividendsPaid',
payload: {
instrumentId,
payments,
},
});
}7) Мониторинг
Метрики
export const dividendMetrics = {
dividendsPaidTotal: new Counter({
name: 'dividends_paid_total',
help: 'Total amount of dividends paid',
labelNames: ['instrument_id'],
}),
couponsPaidTotal: new Counter({
name: 'coupons_paid_total',
help: 'Total amount of coupons paid',
labelNames: ['instrument_id'],
}),
};8) Troubleshooting
Проблема: Дивиденды не начисляются
Диагностика:
# Проверка реестра держателей
curl http://cfa-core-service:3002/holders?instrument_id=$INSTRUMENT_ID
# Проверка баланса эмитента
curl http://ledger-service:3003/balances?subaccount_id=$INSTRUMENT_ID:issuer:reserve:CFA-RUB
# Проверка операции
curl http://ledger-service:3003/operations?external_ref=dividends-$INSTRUMENT_IDРешение:
- Проверьте, что эмитент имеет достаточный баланс
- Проверьте, что держатели существуют
- Проверьте, что операция создана