Accounts

An account in Kraite is the binding between a user and a single exchange — one row per (user, exchange). It carries the API credentials, the position-mode flag, the can_trade toggle, and the operational metadata Kraite needs to dispatch any exchange round-trip on that user's behalf. Accounts are the unit of credential isolation: a Bitget outage doesn't affect Binance accounts; a permission flip on one account doesn't bleed into another.

This is the business-domain lens view. For where account credentials are read on each round-trip, see the Components catalog.


Identity

ColumnRole
user_idOwner
api_system_idWhich exchange (Binance / Bybit / KuCoin / Bitget)
api_key / secret / (optional passphrase)Encrypted credentials — Laravel encrypted casts
can_tradeMaster enable / disable. False = read-only — no order will ever be placed
position_modeOne-way (single net position per symbol) OR Hedge / Dual (LONG and SHORT positions per symbol coexist)
quote_currencyThe settlement asset — typically USDT

The (user_id, api_system_id) pair is unique. A user has at most one account per exchange.


The can_trade invariant

can_trade=false is enforced at multiple layers:

   ┌──────────────────────────────────────────────────┐
   │  cron-create-positions   → skips can_trade=false │
   │  DispatchPositionJob     → guards on entry       │
   │  Place*OrderJob          → guards before HTTP    │
   └──────────────────────────────────────────────────┘

At the time of writing, only one account is can_trade=true (Bruno's Binance). Every other seeded account is read-only — the system fetches balances and indicators for them but will never place an order. This makes onboarding a new user a deliberate, two-stage process: seed first (read-only), promote later (after operator review).


Position mode

Architectural decision

The position-mode flag (one-way vs hedge) is stored at the account level, not the position level, because the exchange enforces it at account level. Switching position mode while positions are open is rejected by every exchange we support. Kraite mirrors that constraint in the domain: changing position_mode requires zero open positions for that account, and the orchestration framework refuses to dispatch a hedge-mode-specific flow against a one-way-mode account (and vice versa). See 02-features/dual-position-mode.md for the operator playbook.


Indicator privilege

Only Binance accounts are allowed to make TAAPI indicator queries. Bybit / KuCoin / Bitget accounts trade off the direction conclusion propagated from Binance via CopyDirectionToOtherExchangesJob. The reasoning: TAAPI's rate-limit budget is finite, every exchange's directionality decision relies on the same BTC-anchored math, and duplicating the indicator query per exchange would burn the budget without changing the answer.


Drift detection

The accounts drift view in admin.kraite.com reconciles every active account against the exchange:

  • Balance drift — local snapshot vs live exchange balance
  • Position drift — local positions vs exchange positions endpoint
  • Order drift — local orders vs exchange open-orders endpoint

Drift is alert-only — it never auto-corrects. An operator decides whether the discrepancy is benign (a transient race during a fast-trade close) or pathological (a missed event from the WS daemon, a duplicate placement).