Манитон Docs

Дивиденды и купоны

Начисление дохода и отражение в CFA‑RUB

Дивиденды и купоны

1) Общий принцип

Доход по инвестиционным ЦФА начисляется в CFA‑RUB, чтобы:

  • расчёты оставались в рублёвом контуре;
  • выплаты были совместимы с СБП и банковскими расчётами;
  • доходность была отражена в выписках и отчётности.

2) Дивиденды (CFA‑EQ)

Дивидендный сценарий:

  1. Объявление: Эмитент фиксирует дату отсечки (Record Date) и размер выплаты.
  2. Snapshot: Dividends Service запрашивает у CFA-Core реестр владельцев на момент Record Date.
  3. Расчёт: Для каждого держателя вычисляется: amount = balance * div_per_share.
  4. Выплата:
    • 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

Решение:

  1. Проверьте, что эмитент имеет достаточный баланс
  2. Проверьте, что держатели существуют
  3. Проверьте, что операция создана

Дополнительные ресурсы

On this page