feat(analytics): доходы, ребалансировка, налоги, бенчмарки и цели — фаза 4

Второй источник выплат: sources/tinvest/sync_events.py (GetDividends,
GetBondCoupons, GetBondEvents) и sources/moex/payouts.py (ISS bondization +
dividends). Приоритет между ними — pricing/payouts.resolve_payouts, решается
на чтении, а не на записи: corporate_action уникален по (instrument_id, kind,
source, source_id), обе версии сосуществуют, и правило можно поменять без
ресинка истории. Амортизация от MOEX идёт в bond_nominal_schedule, а не
в corporate_action — этим типом безраздельно владеет
ledger/corporate_actions.py.

analytics/income.py — metric_income_monthly (факт) и metric_income_calendar
(прошлое и прогноз) с basis paid/announced/history на каждой строке, три
источника числа не смешиваются. analytics/rebalance.py — сделки по
portfolio_target пропорционально внутри бакета, лоты только вниз, покупки не
занимают у ещё не свершившихся продаж. analytics/tax.py — оценка, не замена
справки брокера: дивиденды/купоны gross, реализованный результат из
lot_disposal с переоценкой каждой ноги на свою дату. analytics/benchmarks.py —
TWR индекса на сетке портфеля, kind (price/total_return) не скрывается.
analytics/goals.py — прогресс цели и нужный взнос по trailing XIRR.

Четыре шага зарегистрированы в register_steps: benchmarks после returns
(общая сетка дат), rebalance после allocation (её веса, не пересчитывает),
income и tax после lots (нужен lot_disposal).
This commit is contained in:
Dmitry
2026-09-19 10:42:50 +03:00
parent ff3b76871d
commit 15f5812ea4
42 changed files with 10607 additions and 3 deletions
@@ -103,14 +103,18 @@ def register_steps() -> None:
from fintracker.analytics import (
allocation,
benchmarks,
cashflow,
cashflow_broker,
classify,
income,
networth,
quality,
rebalance,
returns,
runway,
spending,
tax,
valuation,
)
from fintracker.ledger.corporate_actions import rebuild_corporate_actions
@@ -132,8 +136,15 @@ def register_steps() -> None:
# valuation prices the positions the lots describe; returns reads the series it writes
register_step("valuation", valuation.rebuild_valuation)
register_step("returns", returns.rebuild_returns)
# benchmarks reads metric_returns to sit on the same date grid
register_step("benchmarks", benchmarks.rebuild_benchmark_returns)
register_step("allocation", allocation.rebuild_allocation)
# rebalance reads metric_allocation, never re-derives a weight itself
register_step("rebalance", rebalance.rebuild_rebalance)
register_step("cashflow_broker", cashflow_broker.rebuild_cash_flow_broker)
# income and tax both read event/corporate_action/lot_disposal, all written by now
register_step("income", income.rebuild_income)
register_step("tax", tax.rebuild_tax_year)
register_step("networth", networth.rebuild_net_worth_daily)
register_step("cashflow", cashflow.rebuild_cash_flow_monthly)
register_step("spending", spending.rebuild_spending_by_category)