Open positions

A Position is the central business object Kraite manages. One row represents one attempt to enter the market for an account and exchange symbol. The row carries the position from selection to close and is the source of truth that reconciles against the exchange.

This is the business-domain lens view. For the step-by-step flow that drives every transition, jump to position lifecycle.


State machine

StatusMeaningReached fromReached how
newSlot assigned, no on-exchange action yet— (created by AssignBestTokensToPositionSlotsJob)Selection picked this symbol for this slot
openingOpen block running on a workernewPreparePositionData step entry
activeLive on the exchange, fully wired (market + limits + TP + SL)opening, syncing, wapingActivatePositionJob
syncingReconciling DB orders against exchange (transient)activePrepareSyncOrdersJob (only if current = active)
wapingTP being recalculated against new weighted average entry (transient)activeDCA LIMIT fill triggers ApplyWapJob
closingClose block runningactiveTP or SL reached FILLED
closedClosed cleanlyclosingUpdatePositionStatus final step
cancellingCancel workflow running (failure path)any open statusCancelPositionJob
cancelledFailure cleanup completed and no exchange residual remainscancellingFinal verified cancel step
failedCleanup could not prove a safe terminal state (or terminal exchange error like Binance -2022)cancelling, closing (on -2022)Side-effects: notification + auto-block of symbol

Statuses new, opening, active, syncing, waping, closing, cancelling are non-terminal (treated as "open" for the duplicate-open guard). closed, cancelled, failed are terminal.


Duplicate-open invariant

The product allows only one bot-owned position per account and trading pair, regardless of direction. Hedge mode can represent LONG and SHORT together at the exchange, but Kraite deliberately does not assign that shape. Enforcement is layered:

  1. Selection-level — locally-open positions, exchange position snapshots, exchange open-order snapshots, and symbols already selected in the current batch are removed before either LONG or SHORT assignment.
  2. Pre-entry exchange checkVerifyTradingPairNotOpenJob blocks the pair when any LONG, SHORT, or one-way BOTH position key exists, independent of the requested slot direction.
  3. DB-level — virtual is_open is 1 for non-terminal statuses and NULL otherwise. The unique index rejects a second non-terminal row with the same account and exchange symbol, regardless of direction.
  4. Orchestrator-level — parent election and child creation commit together under a parent-row lock. A retry sees the populated child block and becomes a no-op instead of appending another opening chain.

All layers are intentional: selection prevents bad intent, the exchange check catches stale local state, the DB rejects duplicate storage, and orchestration prevents retry duplication.


Slot caps

Each account carries configured LONG and SHORT maximums. BSCS derives a lower effective cap during elevated and fragile regimes without rewriting those saved settings. The selection phase will not assign a position above the effective cap, even when the test-only symbol override is configured; an override never creates capacity.

The read-only mobile dashboard shows effective versus configured caps and the selected account's latest clean close. Only closed history qualifies; cancelled and failed rows remain operational history and never become the trader's last successful close.


Exchange-truth safety

Position existence is matched by exact symbol and logical direction. Hedge LONG / SHORT rows remain directional; one-way BOTH rows derive direction from signed quantity. A same-symbol opposite-side row therefore cannot stand in for the bot-owned exposure.

Bitget Classic and Unified responses are normalized into the same position shape. If an external hedge account contains both sides of one pair, recovery rejects the whole pair instead of adopting one side and risking the other.

Before a REST response can drive replacement, WAP, quantity sync, drift follow-up, or recovery, its vendor envelope and normalized rows must validate. HTTP success with an exchange error is unknown state, not an empty account.

One valid missing result only schedules a 20-second high-priority confirmation. The second valid flat snapshot may cancel Kraite-owned opening LIMITs and let the owning workflow continue. Reappearance or invalid data preserves every order. A direct User Data Stream zero-quantity event remains immediate because it is exchange-pushed position truth rather than a REST absence inference.


Selection priority order

When a slot is open, HasTokenDiscovery::assignTokensToPositions walks four priorities top-down and stops at the first hit:

#PriorityNotes
0Symbol override (test-only)Pin a specific symbol on a configured account, bypassing scoring, correlation, BTC-bias, and eligibility flags. Used for rehearsing WAP / close / drift flows on a known token.
1Fast-tracked symbolsRecently-closed-and-profitable repeats; direction match only, no scoring
2BTC-bias scoringWhen BTC has a concluded direction: same timeframe, correlation-sign filter, score = `elasticity ×
3Fallback scoringWhen BTC has no direction: iterate all configured timeframes, no correlation-sign filter, same score formula

Priorities 2 and 3 also apply the S/R proximity gate as a soft penalty multiplier — never a hard filter.


Failure semantics

An opening failure triggers the cancel workflow. When cleanup verifies that no exchange exposure or open orders remain, the position ends cancelled with no failure alert and no symbol block. Only cleanup that cannot prove a safe terminal state ends failed, which triggers two side effects (decision detail):

  • position_opening_failed Pushover notification fires (priority high)
  • a separate automatic system block stops the next selection tick from re-picking the same broken symbol without changing the sysadmin-owned manual switch

A -2022 from Binance during close → status='failed' with no retry, with the position_residual_detected notification routed to the operator since exchange state may diverge from DB.


  • Position lifecycle — the step-by-step flow that drives every transition above
  • Orders — the rows owned by a Position (1 MARKET + N LIMITs + 1 TP + 1 SL)
  • Token selection — the four-priority pipeline that decides which symbol fills a slot