Propriedade 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.
Modelo antigo: um único dict settings["exchanges"] no banco, segurando
chaves de Binance + OKX, usado por todo mundo. Quem estivesse logado
como admin podia colocar suas chaves ali, e a engine de trading usaria
para toda estratégia - long sleeves, intraday levels, paper, live.
Isso quebra assim que você tem dois operadores. Quebra pior quando você pensa em embarcar um terceiro. Quebra do pior jeito quando você pensa em resposta a incidente de equipe: rotacionar chaves significa parar todas as estratégias.
O modelo novo
Duas colunas e uma tabela nova:
PortfolioMeta.owner_user_id- toda estratégia long-sleeve pertence a exatamente um usuário.TradingAccount(no banco de trading) - toda estratégia da classe trading pertence a exatamente um usuário, tem venue + credential label.UserCredential- blob criptografado com Fernet, um por (usuário, provedor, label).
Quando a engine quer operar paper_levels_bob, pergunta para
_strategy_creds:
def _strategy_creds(strategy_id, provider):
# 1. Trading account?
ta = TradingAccount.find(strategy_id)
if ta and ta.owner_user_id:
return load_credential(ta.owner_user_id, ta.venue_provider, ta.credential_label)
# 2. Portfolio strategy?
pm = PortfolioMeta.find(strategy_id)
if pm and pm.owner_user_id:
return load_credential(pm.owner_user_id, provider, "default")
# 3. No owner → no creds. Engine logs warning and skips.
return {}
Sem fallback. Uma estratégia sem dono não opera nada. A engine loga
WARNING: no credentials for strategy X e pula o tick. Quietamente.
Sem ordens, sem posições, sem surpresa.
O que isso destrava
Onboarding agora é um formulário admin de quatro passos: criar usuário → atribuir estratégia → usuário adiciona chaves ao cofre dele → admin credita o cash. Cada passo é reversível.
Rotação de chaves é por usuário. O Bob revoga a chave Binance dele na exchange; vai em /account, deleta a cred, gera uma nova, salva. O próximo tick da estratégia pega as chaves novas. Sem reinício, sem intervenção do admin, sem afetar nenhuma outra estratégia.
Resposta a incidente quando um operador é comprometido: revoga o token (MCP para de funcionar), rotaciona as chaves de exchange (apaga do cofre), e as estratégias dele entram em modo degradado. O raio de impacto é um usuário.
Isolamento de saldo de trading
O helper compute_user_balance(user_id) faz join nos dois bancos e
devolve só a árvore daquele usuário. Não existe view global de “AUM
do fundo” por padrão - o site de marketing exibe (Total AUM = soma das
estratégias públicas) porque a gente mantém uma flag is_public=True
especificamente para esse caso de uso. Os dashboards internos de
operação mostram só o seu pedaço.
Por que sem fallback
Consideramos um fallback temporário para settings["exchanges"] por
compatibilidade. Tiramos antes de subir. Motivos:
- Um fallback cria atrativo perigoso - operadores colocam chaves “default” “só para teste em paper”, esquecem que existem, e depois uma estratégia real sem dono pega elas.
- O comportamento da engine tem que ser chato. “Estratégia sem dono não faz nada” é chato. “Estratégia sem dono cai num fallback de chaves globais” exige saber do fallback para prever o que vai acontecer.
- A migração foram algumas linhas de SQL. Estratégias antigas
receberam
is_public=Truepara visibilidade pública read-only, sem dono, sem chaves, sem operar. Novos admins atribuem dono explicitamente.
O que o site de marketing vê
O endpoint público /api/public/strategies/{id} tira owner_user_id,
control internals, sinais e o motivo do kill-switch antes de
responder. O que você vê em inite.fund/strategies é exatamente isso:
patrimônio + drawdown + sleeves + últimas 50 operações + últimos 365
pontos de equity. Sem identificadores, sem estado interno, sem segredos.
As páginas Astro fazem fetch SSR desse endpoint com cache de borda de 30s. Se você der reload duas vezes seguidas, vê os mesmos números; 30 segundos depois atualiza. Esse é o nosso “tempo real” - perto o suficiente para uma página de track record, longe o suficiente da origem para absorver tráfego.
O que custou
O refactor mexeu em 12 arquivos no repositório do produto, 2 tabelas novas, uma migração de startup única, ~600 linhas de código e ~70 testes novos. Os testes cobrem a resolução de propriedade, o cofre criptografado, fluxos de capital com idempotência e as chamadas MCP com RBAC.
Antes: 1004 testes passando. Depois: 1180. Zero regressão.
O site de marketing (este site) é construído no mesmo modelo. A faixa
de métricas no topo? É compute_user_balance(NULL) agregado pelas
estratégias públicas, buscado server-side, renderizado em HTML. Nenhum
dado privado cruza a fronteira.
- 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-22O 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.
- 2026-05-09Quando a engine para
O algoritmo roda sozinho. Mas em três situações ele consulta o operador, e em mais uma simplesmente para. Veja como funcionam a fila HIL, os modos da estratégia e o kill switch — e onde fica a linha entre eles.