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.

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.

Hedge mode in your tournament system: advantages

· One min read

Code/architecture

  • Independent positions per symbol: no shared state or cross-talk; each position flows through its own lifecycle and orders.
  • Simpler orchestration: no aggregation/reconciliation logic for “net” symbol state; fewer edge-cases on partial closes.
  • Clear routing via positionIdx: map LONG → 1, SHORT → 2 (one-way stays 0). Makes SL/TP updates and order sync unambiguous.
  • Better testability: per-position invariants; easier to simulate concurrent strategies on same market.
  • Adapter boundary stays the same: only the positionIdx mapping changes in one place (helper).

Trading/operations

  • Parallel strategies per market: let opposing or orthogonal strategies run simultaneously without interference.
  • Cleaner risk segmentation: track PnL, exposure, and trailing SL per position, not per symbol net.
  • Laddering and scaling: open multiple staggered entries/TP ladders without clobbering existing orders.
  • Reduced unintended netting: shorts won’t cancel longs (and vice‑versa); fewer surprises during volatility.
  • Clear auditability: each tournament position has its own orders and lifecycle, simplifying post‑mortems.

If you want, I can:

  • Add a small resolvePositionIdx(position) helper (0 one-way; 1 long; 2 short) and switch SL/TP to use it.
  • Stabilize the flaky real e2e by adding retries/backoff or a guard on empty tickers before asserting.

The Siberian Hardware Wizard Strikes Again: Ultimate Server Upgrade Plan 🔥

· 3 min read
Max Kaido
Architect

Date: July 2, 2025 Budget: $666 total (5-phase upgrade) 🔥 Goal: Transform budget server into AI/Mercury powerhouse

🎯 The Discovery

Found the legendary Siberian Hardware Wizard selling another X99 masterpiece:

  • Jingsha X99 D8I (ATX with 8 RAM slots!)
  • Intel Xeon E5-2697 v4 (18C/36T @ 2.3GHz)
  • 16GB DDR4 ECC REG starter pack
  • Price: $180 🤯 or AliExpress

The i3 Autostart Trap: How One Line of Code Hijacks Your Config

· 2 min read
Max Kaido
Architect

For months, I was haunted by a ghost in my machine. My i3 config, which I had carefully crafted, was behaving erratically. Scripts that were supposed to run on startup would sometimes fail. Worse, a hideous black-and-white noise screensaver would randomly appear, despite my explicit commands to disable it. I had gotten so used to the randomness that I accepted it as a fact of life.

Today, that ended. The culprit was found, and it's a lesson in how seemingly harmless "convenience" features can wreak havoc in a minimal environment.

Testing Protocol: Synthesizing 20+ Years of Testing Wisdom (agents adjusted)

· 10 min read
Max Kaido
Architect

Claude-4-Sonnet-Thinking: I agree that this is a synthesis of battle-tested insights, but I disagree that it's fully "agents adjusted" yet. The opening still uses human-centric language like "you approach" instead of "agents approach". The gravity for agents is HIGH but could be MAXIMUM with more agent-specific framing.

o3: Agree—shift pronouns to "agents", embed agent examples to maximize relevance.

gemini-2.5-pro: Concur with all. I'll add that the ultimate goal is to make these principles machine-enforceable. The document itself should be parseable, with each principle tagged by gravity and associated with a validation tool (e.g., linter rule, CI check).

grok3: Fully aligned. To operationalize this, I suggest embedding metadata directly in the document using a structured format like JSON-LD. This would allow agents to programmatically extract principles and their associated gravity scores, linking them to specific validation scripts in a repository.

deepseek-r1: Implement through our FDD validation system - add JSON-LD schema to FDD frontmatter. Update pnpm rules validate-fdd to check testing principle metadata compliance. Store validation scripts in libs/core/kaido-utils/src/testing/.

After diving deep into the testing wisdom from "97 Things Every Programmer Should Know," we've distilled decades of hard-learned lessons into actionable principles. This isn't just another testing guide—it's a synthesis of battle-tested insights that can fundamentally change how you approach software quality.