feat(analytics): аллокация портфеля по классу, сектору, стране и валюте

Каждое измерение покрывает ОДИН И ТОТ ЖЕ итог — бумаги плюс кэш. Четыре диаграммы
одного портфеля обязаны быть одного размера, иначе экраны противоречат друг другу,
поэтому кэш это бакет в каждом разрезе, а не то, что выброшено из тех, куда он
неочевидно ложится. Исключение — валютный разрез: деньги в рубле лежат вместе с
рублёвыми бумагами, потому что вопрос к этой диаграмме именно такой.

Бакет хранится ключом, а не подписью: класс актива как есть, сектор и страна как их
пишет источник, плюс два литерала — cash и unknown. Язык живёт в клиенте; зашивать
его в данные значит зашивать один язык навсегда.

Позиция без цены исключается, а не считается нулём: ноль тихо ужал бы все остальные
веса. Короткая позиция сохраняет свою величину, но не уменьшает знаменатель — иначе
длинная сторона вылезла бы за 100 %, что на круговой диаграмме не значит ничего.

Derived-кэш вынесен в valuation.cash_balances(): одно определение «нашего кэша» для
сверки со снапшотом брокера и для аллокации, с одним и тем же исключением покупок с
карты, деньги которых баланс счёта никогда не видел.
This commit is contained in:
Dmitry
2026-09-18 14:20:37 +03:00
parent c203ae65fc
commit b58ffb3aac
7 changed files with 426 additions and 3 deletions
@@ -22,6 +22,8 @@ from fintracker.models.ledger import (
LotDisposal,
)
from fintracker.models.metrics import (
AllocationDimension,
MetricAllocation,
MetricCashFlowMonthly,
MetricDataQuality,
MetricHolding,
@@ -79,6 +81,7 @@ __all__ = [
"AccountKind",
"AccountLink",
"AccountRole",
"AllocationDimension",
"AppUser",
"AssetClass",
"Broker",
@@ -101,6 +104,7 @@ __all__ = [
"Lot",
"LotDisposal",
"Merchant",
"MetricAllocation",
"MetricCashFlowMonthly",
"MetricDataQuality",
"MetricHolding",