17 KiB
Портфели ДРРК (BIQ-38115)
Решения (D…) и открытые вопросы (Q…), на которые ссылается код. Где бизнес-решения нет, в коде TODO(BIQ-38115) с номером вопроса.
Что это
Сервис заменяет два Excel-файла с VBA-макросами, в которых ДРРК ведёт портфели облигаций:
- UW — портфель андеррайтинга («Отчет по портфелю ДСМ_2026.xlsm»);
- SALES — портфель продаж («Отчет по портфелю ПП_2026.xlsm»).
Плюс сводный отчёт по обоим портфелям (сейчас собирается руками).
Текущие макросы и формулы: docs/legacy_excel.md, исходники VBA: docs/vba/.
Стек и платформа
- Раиса (k8s), Streamlit, PostgreSQL. Авторизация — Keycloak через
raisa-streamlit-oauth. - async SQLAlchemy + asyncpg, Alembic со схемой.
- БД из Streamlit — через
run_db()(движок сNullPool). - Внешние данные — через шлюз Gravitee.
- Интеграции с QUIK нет: пользователь выгружает отчёт по сделкам в .xlsx и загружает файл в сервис (Q25).
Внешние данные
Проверено на тестовом контуре 09.10.2026.
- Gravitee: тест
https://gateway.rshbdev.ru, продhttps://gateway.rshb.pro. - Заголовок
X-Rshb-Api-Key. У MOEX и ЦБ разные ключи (ключ ЦБ на MOEX — 401). - MOEX:
/external/moex-iss→https://iss.moex.com, в запросах нужен/iss/..../external/moexне существует (404 «No context-path matches the request URI»). - ЦБ:
/external/cbr→https://cbr.ru. - История и параметры выпуска на ISS — по SECID, не по ISIN (у ОФЗ они разные). Добавление бумаги:
/iss/securities.json?q={ISIN}→ SECID →/iss/securities/{SECID}.json(D25). - В истории MOEX есть больше, чем берёт макрос:
FACEVALUE(номинал после амортизации),COUPONPERCENT,BONDTYPE, признак оферты,NUMTRADES,VOLUME. Храним. DURATION— в днях, в годы /365 (D23).- Пример: РЭО-02 (RU000A106169) за 08.10.2026 — TQCB, WAPRICE 101.42, MARKETPRICE3 101.38, YIELDATWAP 17.78, YIELDCLOSE 17.98, DURATION 759, ACCINT 3.68, FACEVALUE 645.19, COUPONPERCENT 17.35, NUMTRADES 3, VOLUME 15.
X-Gravitee-Transaction-Idкаждого запроса сохраняем (D18).
Объёмы
- Сделки: UW — 4150 за 2026 (2848 за 2025), SALES — ~860 в год. До 1660 сделок по одному выпуску, в день размещения в выгрузке 1000+ строк — поэтому сделки только файлом, одной транзакцией. Вручную — только то, чего нет в QUIK (D21).
- РЕПО: ~150 сделок в год, лист РЕПО не переносим.
- Котировки: 1–10 открытых бумаг в день, ~1,5 КБ на бумагу. Курс ЦБ: 1 запрос в день.
Выгрузка QUIK
Пример: tests/fixtures/quik_sample_300326_anon.xlsx (обезличенный, оригинал вне git, см. README). Формат «Лист2», 21 колонка:
Дата сделки, Время, Дата расчетов, Инструмент сокр., Операция (Купля/Продажа), Счет, Кол-во, Цена, Объем, Расчетная сумма, Комментарий, Код расчетов, Заявка, Орг-я партнера, Инструмент сокр., Валюта расчетов, Комиссия ТС, Клиринговая комиссия, ФБ комиссия, ТЦ комиссия, Комиссия брокера.
- «Лист1» — другой формат: без комментария, заявки и комиссий, но с НКД и доходностью. Не поддерживаем. Лист ищем по заголовкам, не по имени.
- Колонка A — номер строки без заголовка. «Инструмент сокр.» дважды (E и P). После данных бывают пустые строки.
- Даты и время — значения Excel с форматом даты (
datetime/timeв openpyxl). Принимаем также серийные числа / долю суток и текстДД.ММ.ГГГГ/ЧЧ:ММ:СС. SUR= RUB. Номер заявки — 11 цифр, храним строкой.- НКД в «Лист2» нет. В примере (размещение) «Расчетная сумма» = «Объем»; какая из них «сумма с НКД» — Q17.
- «Объем» = кол-во × цена × 10 (номинал 1000).
- Счёт у обоих портфелей один. Комментарий:
/uw— UW,/pp— SALES,/trde— ни к какому портфелю. Портфель выбирает пользователь при загрузке, комментарий — подсказка (D19). - Номера сделки нет, только номер заявки (одна заявка — несколько сделок). Дедупликации по строкам нет, защита от повтора — на уровне файла (D19, Q2).
- Купоны, амортизация, комиссия за андеррайтинг и корректировки в QUIK не приходят — вводятся вручную (D21, D22).
Решения
| # | Решение |
|---|---|
| D1 | PK — BIGINT identity везде |
| D2 | Общий справочник бумаг, ISIN уникален (ISIN = выпуск). Принадлежность к UW/SALES — через операции, одна бумага может быть в обоих портфелях. Лотов нет (D20) |
| D3 | Параметры бумаги и операции хранятся раздельно; объём и цена покупки — в операции BUY |
| D4 | «Дата размещения» — дата покупки/размещения у банка, вводит пользователь (по умолчанию сегодня), не дата выпуска на бирже. Дата начала торгов с MOEX — отдельно, справочно. MOEX не запрашиваем, пока дата отчёта ≤ даты размещения |
| D5 | «Продать до» — параметр портфеля: SALES +28 дней, UW +364, как в Excel (Q12). Разный срок для покупок одного выпуска — Q24 |
| D6 | Ручная цена на дату в приоритете и не затирается загрузкой MOEX; значение MOEX хранится рядом; кто/когда/причина. Править можно цену, доходность, дюрацию. Отдельная таблица market_data_manual, пустое поле — действует MOEX; повторная правка на дату перезаписывает |
| D7 | Комиссии — пять отдельных полей. «Комиссия ТС» = клиринговая + ФБ (8217,60 = 3492,48 + 4725,12; в файле продаж 2026 — во всех 285 сделках с комиссией). «Комиссии МБ» в отчёте = сумма «Комиссии ТС», как в Excel. Все пять не суммируются — двойной счёт |
| D8 | Режимы торгов: TQCB, затем TQOB. Если есть только другие борды (например PACT) — ошибка, а не «торгов не было» (Q14) |
| D9 | Курсы CNY грузим с даты самой ранней CNY-операции |
| D10 | Котировки обновляем только по бумагам с открытой позицией на дату отчёта |
| D11 | История номинала (security_nominal_history) с источником moex / manual. Заполняется из FACEVALUE при загрузке котировок, только когда он отличается от последнего значения MOEX; ручное значение на дату в приоритете и действует до следующего изменения на MOEX. До первой записи номинала нет (Q26) |
| D12 | Исходные данные храним всегда; текущий день считаем на лету; при закрытии дня — снапшот итогов для T-1, истории и сводного отчёта (Q20) |
| D13 | Табличный редактор и редактируемые формулы вне скоупа: параметры в справочниках + экспорт в .xlsx (Q23) |
| D14 | Лист control и лист РЕПО не переносим. Производственный календарь нужен (Q15) |
| D15 | Деньги и цены — Decimal/Numeric, не float |
| D16 | Внешние API — только по кнопке и в джобе, экраны читают из БД |
| D17 | Сделка QUIK сопоставляется с бумагой по «Инструмент сокр.» = SHORTNAME MOEX (security.name). Строки Excel вида «МТС 2Р-18_1» — отдельные покупки того же выпуска (D20) |
| D18 | В external_request_log пишем X-Gravitee-Transaction-Id ответа шлюза. Остальные заголовки и ключи не сохраняем и не логируем |
| D19 | Повтор загрузки QUIK отсекаем на уровне файла: уникальность (хэш содержимого, портфель). Хэш — по нормализованным строкам, пересохранённый в Excel файл с теми же данными тоже повтор. Один файл — в каждый портфель по разу, каждая загрузка берёт строки своего портфеля (/uw → UW, /pp → SALES). Строки с пустым, неизвестным комментарием и /trde — отдельным блоком: отнести к выбранному портфелю или исключить. Строка, уже загруженная из этого файла в другой портфель, повторно не грузится. В import_batch — сколько строк загружено и исключено; на странице — «загружено в UW / в SALES / никуда». Пересечение периода с другим файлом того же портфеля — предупреждение |
| D20 | Лотов нет: ISIN = выпуск, каждая покупка — отдельная операция BUY. Учёт партий — Q24 |
| D21 | Вручную вводятся только купоны (CPN), амортизация (NOM), комиссия за андеррайтинг (D22) и корректировки (ADJ). Купля/продажа — только файлом |
| D22 | Комиссия за андеррайтинг — операция UWFEE (дата + сумма) по выпуску, только в UW; их может быть несколько, в своде суммируются. «ЧОД с учётом комиссии андеррайтинга» = P/L (Net) + комиссия |
| D23 | Дюрация = DURATION MOEX / 365, как в макросе |
| D24 | Сводный отчёт: портфель MM не делаем; группировка по типу купона (фикс/флоутер); MD и DV01 (сверено на РЭО-02): MD = дюрация_лет / (1 + YTW_текущая / 100) (0,1 / 1,1813 = 0,08465); объём по номиналу = остаток × номинал на дату × FX; DV01 (1 б.п.) = MD × объём × 0,0001 (0,018 млн ₽); «DV01×100» = DV01 × 100 (1,7996); % лимита DV01 = DV01 / 10 млн (0,18 %). Единицы — Q27, AFS и лимиты — Q4 |
| D25 | SECID ищем по ISIN один раз при добавлении бумаги и храним в security; дальше все запросы к ISS по SECID |
| D26 | Отмены загрузки файла, удаления ручных операций и сброса ручных значений (цена, номинал) пока нет |
Открытые вопросы
- Q2. Есть ли номер сделки в свежей выгрузке QUIK. Ключ «заявка + время + кол-во + цена» неуникален: в файле продаж 2026 — 5 пар разных сделок с одинаковым ключом.
- Q3. Уникальность
portfolio_security_statusв ТЗ — наsecurity_idили наportfolio_security_id. - Q4. Сводный отчёт: «AFS Портфель из ДУ»; классификатор типа бумаги (КОРП/Муниц/Иностр/ОФЗ/Хедж/Торг/ПАИ/Репо); лимиты (DV01, КОРП 25 000 млн по номиналу, сигнальные, стоп-лосс, 480-П) и контроль превышений.
- Q5. Доходность покупки (YTW) — свойство операции покупки или бумаги. В макросе — поле бумаги (SECURITIES INFO, кол. O).
- Q6. Участвует ли NOM в реализованном результате (в Excel — только для РЭО-02, в первой строке CALC).
- Q7. Операция VAL: смысл неизвестен, в данных нет, в формуле фондирования наравне с Sell.
- Q8. Индивидуальные ставки фондирования (РЭО-02 = ключевая + 2,42): единичный случай или механизм.
- Q9. Реализованная переоценка в Excel считает номинал 1000 (×10), нереализованная — по фактическому. Ошибка или так задумано.
- Q10. Неразмещённая бумага: в макросе цена 100, НКД 0, доходность покупки.
- Q11. Перенос позиции на новый год: по цене входа (РЭО-02 по 97,97) или по рыночной на 31.12.
- Q12. «Продать до» для UW: +364 или срок комитета.
- Q13. RES_HISTORY: переносить ли и как (колонки в Excel сдвинуты, см. legacy_excel.md).
- Q14. Цена при отсутствии торгов (последняя известная или ручная) и режимы торгов (D8).
- Q15. Источник производственного календаря. Пока только выходные.
- Q16. Права на ручные значения (цена, номинал, ручные операции).
- Q17. Сумма с НКД: «Расчетная сумма» из QUIK? Для CPN/NOM — вводится или считается.
- Q19. Фондирование: дата ставки при продаже (дата сделки или предыдущий день; в Excel — накопленное на дату сделки − 1) и база (номинал, как в Excel, или стоимость покупки).
- Q20. Хранение результатов расчёта (D12).
- Q21. Параметры при добавлении по ISIN: наименование, номинал, купон, дата начала торгов — с MOEX; дата размещения, объём, цена, YTW — вводит пользователь (D3, D4).
- Q23. Редактируемые формулы: в БИ пользователь сам правит формулы, D13 это исключает.
- Q24. Учёт партий внутри выпуска: частичные продажи (FIFO, средняя, конкретная покупка) и разный «Продать до» у покупок одного выпуска («МТС 2Р-18» / «МТС 2Р-18_1», «Магнит5P06» / «Магнит5P06_1» в файле продаж).
- Q25. Загрузка файла QUIK как основной способ ввода сделок.
- Q26. Есть ли у MOEX исторический номинал по уже амортизированным бумагам или прошлые значения вводятся вручную.
- Q27. Единицы MD и DV01 в сводном отчёте (млн ₽, «DV01×100», % лимита).
- Q28. Что корректирует ADJ (количество, сумму, результат) и как участвует в расчёте. Пока хранится и в расчёт не входит.