Нелинейный торговый баланс
Почему торговому NAV разрешено быть больше аллокации — и почему мы держим их в разных БД.
Баланс пользователя на inite.fund — это дерево:
cash (idle)
├─ long allocations[] → PortfolioMeta(allocation, equity) [linear]
└─ trading allocations[] → TradingAccount + AccountSnapshot [non-linear]
cash + nav + margin_used
+ realized_pnl + unrealized_pnl
total = cash + Σ long.equity + Σ trading.nav
Два стека, один баланс. Длинные аллокации линейны: equity, который вы видите, — это mark-to-market стратегии на тот кэш, что вы туда положили. Торговая часть нелинейна: NAV расходится с аллокацией из-за PnL, из-за маржи (один доллар можно использовать дважды — perp-шорт хеджит spot-лонг), из-за нереализованных колебаний. И это — по задумке, а не баг.
Почему две БД
Соблазнительно сложить состояние торговли в тот же SQLite-файл, что и
портфель. Мы не стали. У торговой части собственная схема (tm_orders,
tm_fills, tm_positions, tm_account_snapshots, tm_capital_flows)
в отдельной trading_module.db со своим SQLAlchemy Base.
Две причины:
- Торговый слой раньше жил как отдельный исследовательский
инструмент. У него была своя БД, своя фабрика сессий, свои
миграции. Перенести его в
web/значило бы переписывать сериализацию истории сделок без явной выгоды. Цена «двух БД в одном приложении» невелика: второйsessionmakerи аккуратное API для движений капитала. - Радиус поражения. Баг в торговом движке, портящий состояние,
остаётся в
trading_module.db. Портфельная сторона — где лежит свободный кэш, длинные аллокации, аудит-лог, аккаунты пользователей — не задета. Торговую БД можно уронить и пересобрать из лога ордеров; портфельную нельзя уронить, не потеряв след денег.
Примитивы движений капитала
Контракт между двумя БД — это маленький набор атомарных хелперов в
web/balance.py:
get_user_cash(user_id)— сумма строк вUserCashLedger.move_to_long(user, strategy, amount)— добавляет знаковую запись в ledger (минус по кэшу) и в той же транзакции увеличиваетPortfolioMeta.allocation.move_to_trading(user, strategy_id, amount)— то же самое, плюс пишетCapitalFlowRowна стороне торговой БД. Транзакция через две БД, идемпотентна поaudit_ref.compute_user_balance(user)— джоинит обе стороны и возвращает дерево.
Каждый хелпер принимает опциональный audit_ref. Передали тот же ref
дважды — второй вызов ничего не делает. Сетевой сбой, четыре ретрая —
в ledger всё равно одна запись.
Что «нелинейный» значит на практике
У Боба $100k кэша. Аллоцирует $30k на paper_levels_bob. Торговый
движок открывает ETH perp short с 3× notional → margin_used = $9k,
notional позиции = $27k. Цена идёт против, нереализованный PnL = −$2k.
Баланс Боба:
cash = $70,000
long = $0 (длинных аллокаций пока нет)
trading[paper_levels_bob]:
initial_alloc = $30,000
cash = $21,000 (заперто: $9k маржа)
unrealized_pnl = -$2,000
nav = $28,000 (cash + нереализованный, без маржи)
total = $98,000 (= 70 + 0 + 28)
Заметьте: nav ≠ initial_allocation. Заметьте также: total ≠ Σ allocations. Формула:
total = cash + Σ long.equity + Σ trading.nav
Чтобы картинка была честной, торговая часть размечается по NAV (то, что получилось бы, закройся позиция сейчас), а не по аллокации. Так мы и делаем.
Почему это важно для MCP-инструмента
get_my_balance возвращает дерево целиком. Когда пользователь
спрашивает у Claude «какой у меня баланс», он получает ровно этот
объект. Задача скилла — правильно его подать:
- Сначала
total. - Кэш — отдельной строкой.
- Длинные аллокации: equity + доходность в %.
- Торговые аккаунты: NAV + предупреждение по марже, если
margin_used / nav > 0.5.
Пользователь, видящий только одно число ($98,000), упустил бы тот факт, что $30k его капитала в риске в торговле, и что $9k из них заперты в марже. Линейная арифметика это прячет. Дерево — показывает.
Уроки
- Разделяйте слои по радиусу поражения, а не по числу таблиц. Две БД — нормально, если контракт между ними тонкий и атомарный.
- Идемпотентность по умолчанию делает ретраи безопасными. Каждый
API движений капитала принимает
audit_ref; повторный вызов с тем же ref ничего не делает. MCP-слой может быть наивным насчёт ретраев; аудит-слой — не может. - «Баланс» с маржой внутри — это дерево, а не число. Показ одного числа — это враньё. Показ дерева — единственно честный UI.
Полная схема — в trading/state/models.py, если хотите почитать.
- 2026-05-11Журнал, который нельзя подделать
Каждое движение капитала, каждый апрув и каждая смена режима пишутся в журнал с хеш-цепочкой. Зачем это нужно в трейдинг-софте и как проверить целостность за полминуты.
- 2026-04-30Почему мы убрали сигналы grade A
Контринтуитивно: убрав самые сильные сетапы из внутридневной книги, стратегия стала прибыльнее, стабильнее и проще в сайзинге. Данные сравнения и аргументация, к которой мы пришли за год параллельной работы A и B.
- 2026-04-19Своя стратегия — свой ключ
Мы выкинули модель глобального торгового юзера. Каждая стратегия теперь читает ключи бирж из vault'а своего владельца.