Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Build a Smaller Aikido Runtime

The reconstruction preserves the control loop, not trading sophistication.

Rebuild one proposal source, one router, one single-writer account executor, one broker simulator, desired-versus-observed reconciliation, and safe shutdown.

1. Start with concrete synchronous state

Represent one instrument and one account. Feed predetermined bars into one strategy function and produce Action::Enter, Action::Exit, or Action::Hold. Apply actions to an in-memory account with no traits or tasks.

2. Separate proposal from authority

The strategy returns proposals. A router decides whether a proposal is allowed and which account receives it. The account is the only component allowed to change observed exposure.

3. Introduce a command protocol

enum AccountCommand {
    ApplyProposal(Proposal),
    Converge { target: i32 },
    Reconcile,
    Shutdown,
}

Run one account loop that owns the account and receives commands synchronously. Test ordering and idempotency before adding Tokio.

4. Add bounded Tokio channels

Create one producer task, one router task, and one account task. Give each edge a small bounded channel so overload appears in tests quickly. Close senders and verify receivers finish rather than waiting forever.

5. Add a generic broker

Extract only the capabilities the account loop already uses:

trait Broker: Send {
    async fn adjust(&mut self, delta: i32) -> Result<()>;
    async fn observed_position(&mut self) -> Result<i32>;
}

Implement SimBroker first. Then add FlakyBroker that can accept an order but lose the response, delay observation, and report external position changes.

6. Make target state idempotent

Store desired and observed signed quantities. Submit only their difference. Never assume a successful return changed observed state; poll the broker and recompute.

7. Add stale and duplicate defenses

Give proposals timestamps and intent IDs. Reject stale exposure increases while still allowing exits. Remember recently applied intent IDs in a bounded window.

8. Add lifecycle and supervision

Use a shutdown signal, explicit Shutdown commands, sender-drop behavior, and a JoinSet. Persist a tiny journal before acknowledging mutations. Restart the account task from the journal, then reconcile against the broker.

9. Compare with production

Map the reconstruction back to:

  • SignalEngine as proposal generation;
  • SignalRouter as centralized policy and routing;
  • ExecutorCommand as the ownership-transfer protocol;
  • AccountExecutor as the single writer;
  • BrokerClient as the generic external boundary;
  • ConvergeToTarget as idempotent desired state; and
  • reconciliation as recovery of external truth.

The smaller runtime intentionally omits real market data, multiple instruments, bracket orders, account lifecycle rules, policy packs, persistence databases, copy routing, operational UIs, and broker authentication. Its architectural center should still survive process restart, duplicate intent, delayed broker observation, channel pressure, and orderly shutdown.