Chris Coleman

Quantitative trading systems developer

Options trading systems, built end to end.

I build the whole stack for systematic index-options trading: tick-level capture of the order book, a backtest engine that prices every fill against the real quotes, strategy tuning on a GPU under nightly walk-forward validation, and Interactive Brokers bots that must produce the same orders as their backtest. Python first, with C and C++ where they're needed. Based in Dallas–Fort Worth.

22.4 B
tick quotes in a lossless store, compressed 4.9×
100 M+
trials in a single parameter sweep
46 min → 16 s
nightly refit, after per-session caching
0
tolerance in the live-vs-backtest order differential
1,600+
tests across the research and live code

The system

Each box is a separate program with its own tests. The backtest and the live bot import the same rule module rather than each restating the rule, and a differential test holds them to identical orders. The bot's measured execution costs feed back into the engine's fill model.

System pipeline Quote capture feeds a tick store, which feeds the backtest and fill engine, then the tuner fleet, then the nightly referee. The referee freezes configs that trade on the live bot as shadow books; the bot's execution logger feeds measured costs back to the engine; a differential test compares the live bot and the engine order for order. DATA RESEARCH LIVE Quote capture IBKR order book,~100 ms per contract Tick store Parquet, 22.4 B quotes,lossless 4.9× Backtest & fills every fill priced onthe true quote tape Tuner fleet GA · PSO · CMA-ES · TPE· GPU deep ensemble Nightly referee walk-forward againsta frozen baseline Execution logger 6 order mechanisms,costs to the cent Live bot IBKR, resting orders,+ shadow books Frozen configs frozen from the page,re-verified nightly freeze measured costs order-for-order differential data and results promotion and feedback differential test
  1. Quote captureIBKR order book, ~100 ms per contract, all session.
  2. Tick storeParquet, 22.4 B quotes, losslessly compressed 4.9×.
  3. Backtest & fillsEvery fill priced against the true quote tape.
  4. Tuner fleetGenetic algorithms, particle swarm, CMA-ES, Bayesian TPE and a GPU deep ensemble.
  5. Nightly refereeEvery tuner walked forward against a frozen baseline.
  6. Frozen configs → live botFrozen from the dashboard, traded live as shadow books beside the bot.
  7. Execution loggerMeasured costs feed back into the engine's fill model.

Projects

All of this is in daily use on a live account. The code and data are private; the demos run on synthetic data generated in your browser.

Strategy tuner fleet & referee dashboard

A fleet of tuners, each refitted every night and scored on the next session against a frozen baseline. The dashboard is for looking at a 1,260-config parameter surface and checking whether its peaks are real.

  • The fleet: genetic algorithms over decision tables and action rules, particle swarm, CMA-ES, Optuna TPE, ridge/MLP models, and a 16-network PyTorch deep ensemble trained on CUDA. Meta-agents choose among the tuners using only their out-of-sample forward records.
  • The referee reports in-sample and walk-forward P&L, the haircut between them, paired gain (mean, median, share of sessions better), Sharpe, max drawdown and a worst-ordering drawdown cascade.
  • Freeze from the page: a local server freezes a tuner's config; it's re-verified every night, scored on every later session, and traded live as a shadow book beside the production bot.
  • Each night's fits run once in parallel worker processes and are cached, so a new day costs one fit per agent. A replay asserts that every cached night re-scores to the same figure.
PythonPyTorch / CUDANumbaOptunaPlotlymultiprocessing
Open the interactive demo →
The referee dashboard: a 3D parameter surface beside its flat heatmap, with summary tiles above
The demo runs the same arithmetic on a synthetic 160-session panel.
Qt desktop and terminal frontends in their built-in demo modes (synthetic session, fast clock).

Live trading bots

Bots on Interactive Brokers that import the backtest's rule module instead of restating it, with a desktop and a terminal frontend.

  • asyncio / ib_async, native resting orders, and fill-driven order chains, which are the only shape the broker accepts when a short leg needs its covering long already on the books.
  • Crash-safe ledgers with midday resume, a paper-account guard, and position reconciliation that holds up against IBKR's netting and its append-only position stream.
  • The differential test: 45 real sessions replayed through the live engine and the backtest, and the orders must be identical, with no tolerance.
  • Every frozen config from the dashboard trades as a shadow book in the same process, on the bot's own quotes.
Pythonasyncioib_asyncPyQtTextual

Backtest & fill engine

An engine that knows only four verbs, submit, close, close_all and settle, so every structure is a composition of them, plus a fill model that walks the real quote tape.

  • Fills are priced against true tick quotes (a median of 2.6 M per contract per session). Resting orders, locally polled watchdogs and latency are separate models, because they fill at different prices.
  • A guard raises an error if a simulated fill falls outside the book or happens before its trigger. It never clamps the price and has no fallback.
  • Commissions match measured broker prints to the cent. Numba kernels score a policy book in under a millisecond.
  • Every sweep writes its full session × config matrix to Parquet, so re-scoring is arithmetic instead of another run.
PythonPolarsNumPyNumbaParquet
Open the exit-policy explorer →

An earlier browser tool for resolving stops, trails and targets over a synthetic panel of entries.

def condor(s, model, width=10.0, offset=40.0, at="09:30"):
    """Four legs, one atomic package. The engine is not told it is a condor."""
    b = Broker(s, model)
    m = s.entry_minute(at)          # never hardcode 09:31: see Session.first_live
    spot = s.spot(m)
    short_c = float(s.strikes[CALL][s.strikes[CALL] >= spot + offset][0])
    short_p = float(s.strikes[PUT][s.strikes[PUT] <= spot - offset][-1])
    long_c = s.step(CALL, short_c, int(width // 5))
    long_p = s.step(PUT, short_p, int(width // 5))
    o = b.submit([(s.contract(CALL, short_c), -1), (s.contract(CALL, long_c), 1),
                  (s.contract(PUT, short_p), -1), (s.contract(PUT, long_p), 1)],
                 m, "condor")
    if not o:                       # one unpriceable leg and nothing trades
        return b, f"REFUSED: {o.reason}"
    b.settle()
    return b, f"credit {o.price:.2f}"
An iron condor in the engine, lightly trimmed from its demo module. The engine has no concept of a condor: it's four legs in one atomic submit, priced against the book at the start of the decision minute.

Order-book capture

IBKR offers no tick feed for these options and can't rebuild a past day, so I capture the book myself, every session.

  • The full same-day SPX options book at ~100 ms per contract, plus index prints and 1-minute bars. Sharded every minute, resumable, and locked to a single writer.
  • The schema is asserted identical to the commercial vendor's store (13 columns, Arrow types, timezone), so the existing pipelines read it unchanged.
  • The settlement price comes from the official daily bar; the last 1-minute bar misses it by a median of $0.66.
asyncioIBKR APIParquet

Execution-cost logger

What does an order actually cost? Six order mechanisms on the same strikes at the same instants, all session.

  • Market, limit and peg-to-mid entries; resting exits against locally watched ones. The opening bell, the regular session and the final minute are scored as separate populations.
  • Across 600+ filled legs it measured commission tiers to the cent ($1.6251 on CBOE, $1.0253 internalized) and a fill improvement for internalized pegs at t ≈ 9.
IBKR APIpaper tradingstatistics

Lossless tick store

99.3 GB down to 20.2 GB, from 22.4 B to 5.3 B rows, in 22 minutes.

  • Deduplication proven to reconstruct the source exactly on all 148 sessions, checked two independent ways.
  • It replaced a proposed 250 ms time-bucketing, which I showed would have silently erased the latency model and made resting orders impossible to represent.
PolarsParquetverification

How I work

Most of the engineering is in the checks that catch a result before it's believed. Three that paid for themselves:

23.7 s

A floating-point stop sold 23.7 seconds late

The live-vs-backtest differential flagged a trailing stop the bot fired late. The level was computed as 0.85 × 3.00 = 2.55, but the tape stores that bid as 2.5500000000000003, so the touch didn't register and the stop sold 23.7 s later.

Stop levels now snap to the exchange's tick grid, and price tests compare decimals.

6 of 16

The backtest was buying a different strike

On the bot's first paper day, its log said +$106 and the backtest of the same day said −$1,843. The entry times matched to the 100 ms poll, but the backtest picked its strike at the start of the minute and the bot at the trigger instant. They disagreed on 6 of 16 entries.

The backtest now runs the bot's own selector on the tick tape at the trigger instant, 16 of 16.

+$702 → −$165

The best config of a 77M-trial search lost money

In sample, it beat the baseline by $702 a session. Refitted nightly and traded on the next session, it trailed the baseline by $165. Across the 1,260-config surface, the split-half correlation was 0.098.

Nothing is promoted on in-sample numbers. The nightly walk-forward referee is the gate, and every run reports its haircut.

Toolkit

Languages

Python, C, C++, SQL, Bash, JavaScript

Optimization & ML

PyTorch (CUDA), Optuna, genetic algorithms, particle swarm, CMA-ES, ridge & MLP, meta-learning

Validation

Nightly walk-forward, permutation nulls, block bootstrap, deflated Sharpe, lookahead guards, differential tests

Data & performance

Polars, NumPy, Numba, Parquet / Arrow, DuckDB, multiprocessing

Trading systems

IBKR TWS API, ib_async, ThetaData, multi-leg combo orders, execution measurement

Interfaces

Plotly 3D, interactive HTML dashboards, PyQt, Textual

Get in touch

Open to quantitative developer and trading-systems roles.