O saldo de trading não-linear
Por que o seu NAV de trading pode ser maior do que sua alocação - e por que mantemos os dois em bancos diferentes.
O saldo de um usuário no inite.fund é uma árvore:
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
Duas pilhas, um saldo. Alocações long são lineares: o equity que você vê é o mark-to-market da estratégia em cima do cash que você colocou. Trading é não-linear: o NAV diverge da alocação por causa do PnL, da margem (você pode usar o mesmo dólar duas vezes - short de perp hedgeando long de spot), das oscilações não-realizadas. Por design.
Por que dois bancos
Tentador encaixar o estado de trading no mesmo arquivo SQLite do
portfolio. A gente não fez. Trading carrega seu próprio schema
(tm_orders, tm_fills, tm_positions, tm_account_snapshots,
tm_capital_flows) num trading_module.db separado, com seu próprio
SQLAlchemy Base.
Duas razões:
- A camada de trading existia antes como ferramenta de pesquisa
independente. Tinha seu próprio banco, sua própria session factory,
suas próprias migrações. Dobrar isso em web/ teria significado
reescrever a serialização do trade history sem upside real. O custo
de “dois bancos numa app só” é pequeno: um segundo
sessionmakere uma API cuidadosa para capital flows. - Raio de impacto. Um bug na engine de trading que corrompe estado
fica em
trading_module.db. O lado do portfolio - que segura cash parado, alocações long, audit log, contas de usuário - fica intacto. A gente pode dropar o banco de trading e reconstruir a partir do log de ordens; não pode dropar o banco do portfolio sem perder a trilha do dinheiro.
Primitivas de capital flow
O contrato entre os dois bancos é um conjunto pequeno de helpers
atômicos em web/balance.py:
get_user_cash(user_id)- soma das linhas deUserCashLedger.move_to_long(user, strategy, amount)- acrescenta uma linha assinada no ledger (cash negativo) e incrementaPortfolioMeta.allocationnuma única transação.move_to_trading(user, strategy_id, amount)- mesma coisa, mas também escreve umaCapitalFlowRowno lado de trading. Transação em dois bancos, idempotente emaudit_ref.compute_user_balance(user)- faz join nos dois lados e devolve a árvore.
Cada helper aceita um audit_ref opcional. Passa o mesmo ref duas
vezes → na segunda é no-op. Hiccup de rede, retry quatro vezes → o
ledger tem uma entrada só.
O que “não-linear” significa na prática
O Bob tem $100k em cash. Aloca $30k para paper_levels_bob. A engine
de trading abre um short de perp ETH com 3x notional → margin_used =
$9k, notional da posição = $27k. O preço anda contra, não-realizado =
-$2k.
O saldo do Bob:
cash = $70,000
long = $0 (sem alocações long ainda)
trading[paper_levels_bob]:
initial_alloc = $30,000
cash = $21,000 (travado: $9k de margem)
unrealized_pnl = -$2,000
nav = $28,000 (cash + não-realizado, fora a margem)
total = $98,000 (= 70 + 0 + 28)
Repare: nav ≠ initial_allocation. Repare também: total ≠ Σ allocations. A matemática:
total = cash + Σ long.equity + Σ trading.nav
Se você quer uma foto “honesta”, marca o trading pelo NAV (o que você teria se a posição fechasse agora), não pela alocação. É o que fazemos.
Por que isso importa para a tool MCP
get_my_balance retorna a árvore inteira. Quando um usuário pergunta
ao Claude “qual meu saldo”, recebe de volta exatamente esse dict. O
trabalho da skill é dizer direito:
- Comece pelo
total. - Cash mencionado em separado.
- Alocações long: equity + retorno %.
- Contas de trading: NAV + alerta de margem se
margin_used / nav > 0.5.
Um usuário que visse só um número (“$98,000”) perderia o fato de que $30k do capital dele está em risco em trading - e que $9k desse total estão travados em margem. Matemática linear esconde isso. A árvore mostra.
Lições
- Separe responsabilidades pelo raio de impacto, não pela contagem de tabelas. Dois bancos é tranquilo se tiverem um contrato fino e atômico.
- Idempotência-por-padrão deixa retries seguros. Toda API de capital flow aceita audit_ref; um re-run com o mesmo ref é no-op. A camada MCP pode ser ingênua sobre retries; a camada de auditoria não pode.
- Um “saldo” com margem dentro é uma árvore, não um número. Mostrar um número é mentir. Mostrar a árvore é a única UI honesta.
O schema completo está em trading/state/models.py se você quiser ler.
- 2026-05-11Um registro que ninguém reescreve em silêncio
Cada movimento de capital, cada aprovação e cada troca de modo cai num log de auditoria com cadeia de hash. Por que isso importa em software de trading e como verificar a integridade em menos de um minuto.
- 2026-04-30Por que deletamos os sinais grade A
Contraintuitivo: tirar os setups mais fortes do livro intradiário deixou o sleeve mais lucrativo, mais estável e mais fácil de dimensionar. Os dados de comparação e o raciocínio a que chegamos depois de um ano rodando A e B lado a lado.
- 2026-04-19Propriedade por estratégia: sua cozinha, suas chaves
Largamos o modelo de trading-user global. Cada estratégia agora lê chaves de exchange do cofre do dono.