Commit Graph
3 Commits
Author SHA1 Message Date
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
Dmitry c55fe19e48 feat(sources): контракт источников, worker и синк ZenMoney + ЦБ
Source.sync(ctx) -> SyncResult пишет только raw_* и возвращает курсор; локи,
журнал, ошибки и продвижение курсора берёт на себя worker/runner.

ZenMoney читается единственным доступным способом — POST /v8/diff/ по
serverTimestamp; токен живёт сутки, поэтому worker ротирует refresh_token через
source_credential. Маппер всегда пересобирает core из полных raw_*, так что
удаление в ZenMoney исчезает и у нас.

ЦБ ходит мимо прокси (trust_env=False) и отдаёт cp1251 с делением на Nominal.
Курсы только по рабочим дням — протяжку по календарю делает аналитика.

Планировщик — APScheduler в отдельном процессе, на источник advisory-лок
sync:<name>, чтобы ручной запуск не пересёкся с плановым.
2026-09-18 13:43:49 +03:00
Dmitry 295438914d feat(backend): каркас — конфиг, движок БД, базовые модели и первая миграция
SQLAlchemy 2 async на asyncpg, Alembic, pydantic-settings. Деньги везде
NUMERIC(24,10) с колонкой валюты рядом (db/base.py), postgres-enum'ы хранят
значения, а не имена, чтобы база читалась так же, как API.

Первая миграция: app_user, account, portfolio, instrument, sync_run, sync_job.
metrics/refresh.py задаёт порядок пересборки metric_* и сериализует параллельные
пересчёты advisory-локом на отдельном соединении: каждый шаг заменяет свою
таблицу целиком, два одновременных прогона затёрли бы друг друга.
2026-09-18 13:43:31 +03:00