Commit Graph
2 Commits
Author SHA1 Message Date
Dmitry ff4d63e829 feat(ledger): связь перевода ZenMoney с брокерским пополнением
Случай C из плана §1.6: без него одни и те же деньги считаются дважды, а перевод на
брокерский счёт выглядит расходом.

match_flows — чистая функция над двумя последовательностями кандидатов, как apply() в
lots.py; rebuild_flow_links её обвязка с сессией. Скоринг: та же валюта, тот же счёт,
|Δ| ≤ max(1 ₽, 0,5 %), разрыв ≤ 3 РАБОЧИХ дня. Рабочих, а не календарных: деньги на
брокерский счёт в субботу не приходят, и календарное окно систематически теряло бы
пятничные переводы. Праздники не моделируются — лишний праздник делает матчер строже,
а не наглее.

Направление выбрано по данным, а не по тексту плана. План говорит «outcome на
зеркальный счёт = пополнение», но зеркальный ZM-счёт это отражение брокерского кэша:
деньги идут «карта → зеркало» (income на зеркале), а на брокере в тот же день deposit.
На живых данных income-соглашение даёт 525 пар, обратное — 4. Для маршрута через
правило broker_target, где брокера в ZenMoney нет вовсе, направление остаётся как в
плане: outcome ↔ deposit.

Жадность 1:1 по (дельта суммы, разрыв в днях): пара берётся, только если свободны обе
стороны. Ручные линки не пересобираются — обе их стороны исключаются из пулов, иначе
автоматика молча переписывала бы решение человека.

Связанная транзакция получает flow_type = internal_transfer здесь, а не в classify:
классификатор не может знать о линках, которых на момент его работы ещё нет.
2026-09-18 15:05:53 +03:00
Dmitry 012a40981f feat(ledger): единый леджер событий и FIFO-лоты
Одна таблица event, куда маппится каждый брокерский источник: quantity знаковый
по эффекту на позицию, amount — по эффекту на кэш, dedupe_key UNIQUE делает
повторный импорт пустой операцией. Аналитика читает только confirmed.

lots.apply() — чистая функция без сессии, rebuild.py её обвязка с БД. FIFO по
(счёт, инструмент), как требует ст. 214.1 НК; комиссии капитализируются в
покупку и вычитаются из продажи, НКД в себестоимость не входит — это деньги,
авансированные продавцу и возвращаемые купоном.

Короткие продажи — тоже лоты: продажа без остатка открывает короткий лот,
покупка его закрывает, прибыль равна падению цены. В данных такое есть (TATN
продан 22.08 и выкуплен 29.08); считать это ошибкой значило бы оставить
фантомный длинный лот навсегда и потерять реализованную прибыль.

Всё пересобирается с нуля на каждом refresh: объёмы личные, это секунды, зато
исчезает целый класс багов расхождения инкремента с леджером.
2026-09-18 13:44:31 +03:00