Trading Dev AcademyFree quant education

Free lesson · Quant strategy development

08 / Build a bot that preserves the experiment

Open interactive lessonPractice calculationsExplore labs

Start with the idea

A trading bot is a state machine as well as a prediction function. The state includes fills already received and orders that might still execute. A good forecast does not compensate for duplicated orders or incorrect positions.

Symbols, units & horizon
  • Q_target: intended signed position in units
  • Q_filled: reconciled current signed position
  • Q_open: signed remaining quantity in acknowledged live orders
  • Q_new: additional signed order intent
  • Positive quantity: buy or long, negative: sell or short
  • Order intent: desired quantity, not evidence of a completed fill

When and why to use this

Use explicit state and stable identifiers to make live behaviour match the backtest’s intent. Replay failures before testing capital at risk.

Separate the deterministic research logic from data adapters and order routing. Given the same timestamped inputs and portfolio state, the strategy should produce the same intent. Keep live credentials and broker execution outside this training lab; the code below is architectural pseudocode.

Architecture pseudocode

def on_market_event(event, state):
    data = ingest_and_validate(event)  # timestamps, sequence, missing fields
    if not data.is_fresh or not state.is_reconciled:
        return PAUSE_AND_RECONCILE

    features = transform_with_frozen_model(data.history)
    forecast = predict_return_and_uncertainty(features)
    target = construct_portfolio(forecast, state.positions)
    intent = target - state.positions - state.pending_exposure

    if not risk_checks_pass(intent, state, data):
        return NO_NEW_ORDER
    if expected_benefit(intent) <= all_in_cost(intent) + buffer:
        return NO_NEW_ORDER

    order_id = stable_id_for_this_intent(event, state)
    persist_intent_before_submission(order_id, intent)
    return submit_once_then_reconcile(order_id, intent)

def on_execution_report(report, state):
    apply_fill_once(report.execution_id, report)
    reconcile_positions_cash_and_open_orders(state)
    persist_state_and_metrics(state)

The pseudocode omits broker-specific details on purpose. The core requirements are idempotent fill handling, durable intent, reconciliation after ambiguous responses, and a portfolio-wide risk check. Restart recovery is part of trading behaviour: it must not recreate orders that already exist.

  • Use the same signal and portfolio code in replay and paper mode; vary only data and execution adapters.
  • Replay delayed, duplicated and out-of-order events. Verify that duplicate execution reports cannot double a position.
  • Test partial fills, rejects, cancellation races, disconnections, clock drift and process restarts.
  • Record model version, feature snapshot, intended position, risk decision, order ID and realised costs for every trade.
  • Reconcile daily P&L into forecast return, exposure changes, execution, borrow, funding and unexplained residual.
State-accounting identity

Calculate remaining order intent

  1. If target quantity is T, filled position is Q and signed outstanding order quantity is O, the remaining required intent is I=T−Q−O.
  2. A partial fill moves quantity from O into Q, leaving Q+O unchanged until the order is cancelled, replaced or fully resolved. This conservation check catches accidental duplicate orders.
Work it by hand

Target 100 shares, position 40 and 30 shares still working gives new intent 30. A 10-share partial fill changes position to 50 and outstanding to 20; new intent remains 30.

Python implementation

Self-contained teaching example. Python 3.10+; dependencies and input conventions are shown in the code and notation. Run in your own Python environment.

def remaining_order_intent(target, reconciled_position, acknowledged_open_quantity):
    """Use reconciled broker state; a pending cancellation is still open."""
    return target-reconciled_position-acknowledged_open_quantity

print(remaining_order_intent(100, 40, 30))  # only 30 more, not 60

Continue learning

Quant Strategy Development — all lessons
  1. 01 / Start with a source of return
  2. 02 / The variables that actually enter the decision
  3. 03 / Test predictive information before a complex model
  4. 04 / Momentum and trend: information that persists
  5. 05 / Mean reversion and relative value
  6. 06 / Carry, events, and liquidity provision
  7. 07 / Convert a forecast into a trade decision
  8. 08 / Build a bot that preserves the experiment
  9. 09 / Decide whether the edge is real enough to continue

Quantitative finance and development glossary · Python resources and libraries · Research sources and limitations