DRRK/docs/project.md

116 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Портфели ДРРК (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 хранится рядом; кто/когда/причина. Править можно цену, доходность, дюрацию |
| 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` при загрузке котировок; ручное значение на дату в приоритете, как в D6 (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 (количество, сумму, результат) и как участвует в расчёте. Пока хранится и в расчёт не входит.