Skip to main content

mercury-architecture-snapshot

Β· 3 min read

Minimal plan that actually works and stays small​

  • Position queue

    • Keep KAIROS.position-execution only (scheduler + execution).
    • Keep KAIROS.dynamic-tpsl (independent of sync loop).
  • Public runtime surface (4 files, small)

  • position-execution.consumer.ts

    • Handles command.schedule_positions β†’ evenly enqueue command.sync_position.
    • Handles command.sync_position β†’ calls orchestrator; tiny effect mapping only if needed.
    • trading-context.orchestrator.ts
      • Derive diagnostics via derive-sync.ts β†’ choose phase β†’ call adapter method.
    • TradingAdapter + two adapters with identical interface
      • live-context.adapter.ts implements:
        • syncCreation(position)
        • syncPromotion(position)
        • syncOngoing(position)
        • Internals: reconcile(position) does everything (TP diff/cancel/create, SL ensure/update, trailing SL).
      • shadow-context.adapter.ts implements the same 3 methods
        • Internals: simulateTriggers+fill via OrderService.
  • Events (minimal, useful)

    • QueueEventService publishes event.position_partially_closed/closed/status_changed to KAIROS.position-execution.
    • order-events.consumer.ts enqueues command.sync_position { positionId } immediately.

What to delete/replace to deflate the bloat​

  • Delete: position-sync.engine.ts
  • Delete: base-trading-context.ts
  • Replace: live-trading-context.ts β†’ live-context.adapter.ts (≀250 LOC; reconcile-centric)
  • Replace: shadow-trading-context.ts β†’ shadow-context.adapter.ts (≀200 LOC; simulate+fill)

Tiny interfaces (one adapter interface for both)​

type TradingPhase = 'creation' | 'promotion' | 'ongoing';

interface TradingAdapter {
getType(): 'LIVE' | 'SHADOW';
syncCreation(position: Position): Promise<string[]>;
syncPromotion(position: Position): Promise<string[]>;
syncOngoing(position: Position): Promise<string[]>;
}
  • orchestrator
    • phase = deriveSyncDiagnostics(position, orders).phase
    • adapter = factory.for(position)
    • call adapter.syncCreation|syncPromotion|syncOngoing
    • persist only lastSyncUpdate

Why this stays small and testable​

  • One public method per phase per adapter. All β€œhow” lives inside private reconcile()/simulate().
  • No engine, no base class. One interface = zero drift.
  • External-only mocking: mock Bybit client; use real DB/OrderService.
  • Tests:
    • derive-sync.spec.ts (pure)
    • live adapter β€œreconcile” spec (Bybit mocked)
    • live-sync-positions.e2e-spec.ts (happy path)
    • order-events.consumer.spec.ts (enqueues immediate sync)

Migration steps (safe and quick) - βœ… ALL COMPLETED!​

  1. Add TradingAdapter, trading-context.orchestrator.ts. βœ… Done
  2. Add live-context.adapter.ts (copy only needed logic; fold createTPOrders/updateSL/etc. into private reconcile()). βœ… Done (359 LOC, 6 methods)
  3. Add shadow-context.adapter.ts (simulateTriggers+fill). βœ… Done (195 LOC, 5 methods)
  4. Update trading-context.factory.ts to return new adapters. βœ… Done (new methods + orchestrator integration)
  5. Replace KairosEventService with queue-event.service.ts in OrderService/PositionService. βœ… Done (OrderService.fillOrder() β†’ publishOrderFilledEvent() + order-filled.consumer)
  6. Add order-events.consumer.ts to enqueue immediate sync on events. βœ… Done
  7. Change position-sync.consumer.ts to call orchestrator. βœ… Done (ΠΏΠ°Ρ€Π°ΡˆΠ½Ρ‹ΠΉ engine β†’ orchestrator.syncPosition())
  8. Delete engine + base; remove old methods/tests referencing them. βœ… Done (DELETED: engine + base + live-context 1624 lines + shadow-context 485 lines = 2109+ lines ΠΏΠ°Ρ€Π°ΡˆΡ‹)
  9. Keep scheduler behavior; run it on KAIROS.position-execution (same bus as execution). βœ… Done (already using same queue)

BONUS CLEANUP STEPS - βœ… COMPLETED!​

  1. Consolidate consumers: 6 separate consumers β†’ 2 unified consumers per queue. βœ… Done (PositionExecutionConsumer + DynamicTpslConsumer)
  2. Delete METRICS queue: move metrics to position execution events. βœ… Done (metrics now trigger on position closures)
  3. Delete TransactionManager abstraction: direct account.updateBalance() calls. βœ… Done (removed unnecessary wrapper layer)
  4. Delete OrderService: inline order creation + publishOrderFilledEvent pattern. βœ… Done (500+ lines β†’ direct EntityManager calls)
  5. Remove EventEmitter2 completely: full queue-based architecture. βœ… Done (zero @OnEvent decorators remaining)
  6. Delete useless tests: trailing-sl-real.e2e-spec.ts didn't test our logic. βœ… Done (cleaned up test noise)

πŸŽ‰ MIGRATION COMPLETE! πŸŽ‰

Result: minute reconcile baseline + immediate sync on events, tiny surface, no 1500-line files, easy to extend.

Architecture Stats:

  • DELETED: 3500+ lines ΠΏΠ°Ρ€Π°ΡˆΠ½ΠΎΠ³ΠΎ Π³ΠΎΠ²Π½Π° (engine + base + contexts + services + consumers)
  • CREATED: ~600 lines чистой Π°Ρ€Ρ…ΠΈΡ‚Π΅ΠΊΡ‚ΡƒΡ€Ρ‹ (adapters + single consumer + queue services)
  • NET: -2900 lines, +event-driven architecture, +testability

πŸ”₯ FINAL ARCHITECTURE CLEANUP:

  • βœ… KairosEventService (295 lines) β†’ DELETED
  • βœ… KairosEventConsumer β†’ DELETED
  • βœ… TransactionManagerService β†’ DELETED (replaced with direct account.updateBalance() calls)
  • βœ… TransactionManagerConsumer β†’ DELETED (balance updates moved to OrderFilledConsumer)
  • βœ… OrderService (500+ lines) β†’ DELETED (replaced with inline code in shadow adapter)
  • βœ… OrderService.fillOrder() β†’ publishOrderFilledEvent() + OrderFilledConsumer
  • βœ… 6 separate consumers β†’ 2 unified consumers (PositionExecutionConsumer + DynamicTpslConsumer)
  • βœ… METRICS queue β†’ DELETED (metrics moved to position execution events)
  • βœ… All @OnEvent decorators β†’ Queue consumers
  • βœ… EventEmitter2 dependencies β†’ REMOVED
  • βœ… Useless trailing-sl-real.e2e-spec.ts test β†’ DELETED

Proposed test set (minimal, no legacy)​

  • Keep (rename if noted)

    • derive-sync.spec.ts
    • position-scheduler.service.spec.ts
    • position-distribution.util.spec.ts
    • trailing-stop-loss.utils.spec.ts
    • pnl-calculator.service.spec.ts
    • position-query.service.spec.ts (optional)
    • tests/bullmq-patterns.spec.ts (optional)
  • Modify

    • order.service.events.spec.ts β†’ order.service.queue-events.spec.ts
      • Assert QueueEventService publishes to KAIROS.position-execution; remove EventEmitter2.
    • live-sync-positions.e2e-spec.ts
      • Assert orchestrator + TradingAdapter flow; drop engine/base deps.
    • position.e2e-spec.ts
      • Align with orchestrator/adapter, queue names, and immediate sync on events.
  • Delete DONE

    • position-sync.engine.spec.ts
    • trading-context.spec.ts
    • live-trading-context.spec.ts
    • live-trading-context.e2e-spec.ts
    • Any tests that depend on EventEmitter2-based KairosEventService
  • Add (new, small)

    • live-context.adapter.reconcile.spec.ts
      • Mocks Bybit adapter; verifies: TP diff/cancel/create, SL ensure/update, trailing SL path.
    • shadow-context.adapter.simulate.spec.ts
      • Verifies simulateTriggers leads to fills via OrderService.
    • trading-context.orchestrator.spec.ts
      • Given diagnostics phase, calls the correct adapter method; persists lastSyncUpdate.
    • order-events.consumer.spec.ts
      • On position_closed/partially_closed, enqueues command.sync_position for that position.
    • queue-event.service.spec.ts
      • Publishes event.* to KAIROS.position-execution with stable jobId and metadata.

This yields a lean suite: pure diagnostics, one unit per adapter, one orchestrator unit, one scheduler unit, one event-to-sync bridge, plus 1-2 e2e happy paths.