Skip to main content

3-ways-to-simplify-position-lifecycle

· 2 min read

Top 3 ways to simplify the position lifecycle

  1. Collapse patterns into a single declarative state machine
  • What: Replace BaseTradingContext + Template Method + JobFlowOrchestrator with one finite-state machine (e.g., XState or a tiny hand-rolled map) that defines states, transitions, and actions.
  • How:
    • Define transitions for CALCULATED → OPEN → PROMOTED → CLOSED | ERROR in one place.
    • Attach actions for each transition (create position, create orders, sync).
    • Drive next-step scheduling directly from the machine (no separate orchestrator).
  • Impact:
    • One source of truth for lifecycle logic.
    • Fewer files: deprecate base-trading-context.ts, shrink job-flow-orchestrator.service.ts.
    • Easier testing: pure transitions + effect handlers.
  1. Unify contexts via composition instead of inheritance/strategy
  • What: Merge LiveTradingContext and ShadowTradingContext into a single TradingContext that delegates IO to an ExchangeAdapter (e.g., BybitAdapter vs ShadowAdapter).
  • How:
    • Create ExchangeAdapter interface: submitOrder, getPositionInfo, getActiveOrders, setTradingStop, etc.
    • Inject adapter based on PositionType (or account), drop TradingContextFactory and BaseTradingContext.
    • Keep one code path; swap only the adapter.
  • Impact:
    • Removes Strategy + Template Method.
    • Cuts duplicate logic; the business rules live in one place.
    • Mocks become trivial (fake adapter).
  1. Flatten queues and in-process orchestration
  • What: Use a single queue (KairosQueueName.POSITION) and handle next-step decisions inside the consumer. Keep retries/backoff but drop cross-queue orchestration.

  • How:

    • Consolidate DYNAMIC_TPSL, POSITION_EXECUTION, POSITION_SCHEDULER into one queue with namespaced jobs.
    • In position-sync.consumer.ts, return a “next step” descriptor and enqueue it immediately (or call synchronously when cheap).
    • Keep cron-only scheduling separate if needed.
  • Impact:

    • Fewer moving parts; simpler mental model.
    • Less cross-queue glue and fewer BullMQ handles.
    • Easier observability around a single pipeline.
  • Bonus quick wins (low risk):

    • Always create TP/SL as separate orders (already aligned with current code/tests).
    • Merge error statuses into a single ERROR with lastError metadata.
    • Remove canHandlePosition() and factory indirection once adapter-based composition is in.

Summary

  • Consolidate lifecycle into one declarative machine.
  • Replace contexts with an adapter-based single implementation.
  • Collapse queues and inline orchestration in one consumer.