Autonomous Trading Platform
An end-to-end autonomous trading system: strategy research and validation, governance and risk controls, and live execution, engineered with the same operational discipline as production fintech infrastructure.
image goes somewhere here?
coe snippet maybe ?
NEED TO ADD FRONTEND SOMEWHERE?
TODO ADD STAT STRIP?
System Overview
Four layers, and an audit that measures its own drift. The stated architecture is contracts (Pydantic) → storage (SQLAlchemy system-of-record plus versioned Parquet) → application → interfaces, with each layer only allowed to depend on the one before it. An internal audit covering the application/services/ files M through Z found that claim didn't fully hold in practice: 17 of 37 files build select()/session.query()/session.commit() directly against ORM models with no repository layer in between. In that slice, that's the dominant pattern, not the exception. Three contracts files import from governance/ and storage/, and one storage model imports back from execution/. The layering is real and does most of the work, it just isn't strict, and the codebase names its own exceptions rather than hiding them.
A generic rule engine, not if-else logic scattered across 17 files. contracts/validators/core.py defines a small, typed framework. Rule[T] and ValidationContext are frozen dataclasses, Violation carries an ERROR/WARNING severity, and run_rules() applies a list of rules and collects every failure instead of stopping at the first. If a rule itself throws, the framework catches it and converts it into its own ERROR violation rather than crashing the whole validation pass, so one broken check just reports itself as failed instead of taking everything else down with it. Seventeen validator modules compose this one framework, one per contract type, so the rule set for any given object is data you can list and count, not logic buried in conditionals.
Contracts are typed for correctness, not blanket immutability. contracts/common/types.py is a 26-line file that quietly eliminates two entire bug classes: UTCDateTime rejects any naive datetime at parse time and normalizes everything to UTC, and Money/Quantity are both Decimal, not float, so financial values can't silently lose precision. Immutability is applied selectively rather than everywhere: only one Pydantic model in the whole package (DividendEvent) is frozen, while a separate set of 63 internal dataclasses splits roughly evenly between frozen and mutable. That's a deliberate per-type choice, not an accidental gap.
The frontend is a working app, one missing file away from a clean build. Every one of its 6 mounted pages calls real backend endpoints (24 distinct routes) through TanStack Query and a real JWT-authenticated Axios client. The mock data file is fully superseded; nothing in the codebase imports it anymore. The build currently fails on one missing scaffold file: src/lib/utils.ts, the standard shadcn cn() helper. It's referenced by 9 files but was never generated. Its dependencies are already installed, so restoring it is a ten-line fix, not a redesign. A fully-built 598-line strategy comparison page exists in the codebase but isn't wired into any route, and several TODO comments name the exact missing backend field rather than faking a number in its place. This is real, working software with two loose threads, not an unfinished stub.
One core, several purpose-built adapters, not five copies of the trading logic. OrderExecutionService, OrderStateMachineService, and PortfolioConstructionService are the same instances whether an order originates from live trading, paper trading, a backtest, shadow mode, or a chaos-replay run. Live and paper both route through AlpacaBrokerClient, differentiated only by account configuration. Backtests substitute a SimulatedBrokerClient through a separate execution path built for bar-by-bar simulation. Shadow mode runs the full pipeline and intercepts only the final broker-submit call. Replay injects synthetic failures directly into system-of-record tables to test how the same core reacts. The environments aren't unified behind one formal interface yet, but the effect already holds: a passing test against one environment is real evidence about the other four, because none of them run a separate reimplementation of the trading logic itself.
Safety
Four independent gates instead of one flag. LiveTradingGateService replaces a single "is live trading on" boolean with four sequential gates: environment/config, account allowlist, armed/disarmed runtime state, and kill switch. Each gate raises its own typed exception, so a blocked trade is diagnosable at a glance. A failure at any gate short-circuits the rest, so an unauthorized account never even reaches the kill-switch check. The same fail-closed pattern extends to the API layer, where a consistent RBAC and error-handling standard covers 14 route files. One known gap remains: `portfolio_routes.py` has no RBAC on 12 endpoints, already flagged.
A kill switch that survives a restart. The kill switch needed to survive more than the current process: a restart, a deploy, a crash. KillSwitchService reads and writes a single authoritative row in Postgres rather than holding an in-memory flag, using one singleton ID so there's exactly one source of truth platform-wide. Every enable/disable call commits immediately, and every check reads straight from the database. `test_kill_switch_persistence.py` (14 tests) confirms this by constructing a fresh service instance and verifying it re-reads the persisted state instead of defaulting to off.
Two-layer enforcement against live trading. EnvironmentSafetyPolicy checks NO_LIVE_TRADING, then ENABLE_LIVE_TRADING, then whether live-trading modules were even compiled into the build: three independent ways to refuse live execution at the config/build layer. A second, runtime layer inside the replay/debug tooling refuses to run if the environment is LIVE at all, and separately re-checks the persisted settings snapshot. A stale in-memory value can't let a debug run silently execute against live state.
Shadow mode validates the real decision path. Shadow mode lets the platform compute and fully evaluate a trade decision, including every pre-trade risk check, without submitting it to the broker. The order still passes through the entire order-generation and risk-check pipeline; only the final broker-submission call is skipped. Shadow mode tests real conditions rather than a separate, simplified simulation that could drift from production behavior.
Pre-trade risk checks project the resulting portfolio. PreTradeRiskService answers one question before any order reaches a broker: would the resulting portfolio still be within every risk limit? It projects gross exposure, per-symbol exposure, daily notional, and concentration caps, then raises a typed exception on the first breach. The exposure math correctly distinguishes a position-reducing sell from one that adds risk. For sector concentration, where metadata isn't always available, the service supports three configurable policies for unknown sectors: reject, warn and allow, or route into a limit-checked "unknown" bucket.
The database enforces its own promotion rules. Postgres CHECK constraints require at least 30 days tested and a nonzero trade count for any strategy transition into live status. Application code already enforces these thresholds. But if that logic has a bug, gets bypassed by a raw write, or a future migration reintroduces old behavior, the database itself refuses to store a row that violates the constraint.
Safety tests separate the math from the enforcement. Safety tests follow a consistent layered pattern: test the underlying math in isolation, test enforcement separately, then test the audit and metric side effects, then add regression tests. Separating math from enforcement is deliberate: if a real order gets incorrectly blocked, you want to know immediately whether the exposure number was wrong or the policy threshold was wrong. `test_sector_concentration.py` (33 tests across 9 classes) is the clearest example of the full pattern in one file.
Order lifecycle transitions live in one table, not scattered conditionals. OrderStateMachineService declares every legal transition as data in one table. Terminal states (filled, canceled, rejected, expired) have no outgoing transitions, so a filled order can never be resurrected; the table itself enforces it. Any transition not in the table raises an error immediately rather than being silently ignored, which matters because broker events can arrive late, out of order, or duplicated.
One shared function prevents double-counted fills. Brokers report cumulative filled quantity, not incremental fills, and the platform learns about fills through two channels, polling and streaming, that can both report the same state. One function shared by both paths computes the delta between current and previous filled quantity and only records a new fill when that delta is positive. A negative delta is treated as stale, logged as critical, and ignored rather than rewinding state.
Research / Strategy Generation / Experiment Systems
Four generators, one shared parameter registry. Generating strategy candidates is parameter-space sampling over a typed registry, not machine learning. Grid search takes the full Cartesian product: complete but combinatorially explosive (10 params times 5 values equals 9.7M candidates), so large ranges get collapsed to a min/default/max set. Random sampling draws with replacement, seeded, default 50 samples. The "evolutionary" generator has no fitness function or selection; it mutates each member randomly each generation, exploring neighborhoods rather than climbing toward better regions. The composite path assembles strategy skeletons from a hand-curated table of 13 indicators, producing a documented pool of 854 skeletons checked against the live component registry before acceptance.
Config hashing dedupes within and across sessions. Every accepted config gets a SHA-256 hash: a canonical, order-independent identity, not a security hash. It's used for in-session dedup and, separately, for cross-session dedup via a persistent cache that survives across processes and fails open (a broken cache file logs a warning and starts empty rather than crashing generation). One real gap: the persistent cache is a standalone facility the engine itself never calls; only the in-session set gets checked automatically.
A strict survivor funnel: cheap, intermediate, walk-forward, Monte Carlo. Each stage only receives the prior stage's survivors. "Cheap" and "intermediate" aren't separate stage types. They're two configurations of the same simulation stage: one with a short window and loose thresholds, one with the full window and real thresholds. The ordering is deliberate, cheapest and most-discriminating test first, so a badly broken candidate dies early instead of consuming expensive compute it was never going to survive.
Eight checks decide pass/fail; a separate score decides rank. Thresholds are inclusive, and all checks are evaluated (not short-circuited), so a rejected candidate returns its full failure profile. The eight core checks cover Sharpe, drawdown, trade count, consistency, profit factor, win rate, return variance, and total return; only three are meaningfully active by default. Filtering and ranking are separate steps. Ranking only orders strategies that already passed every filter, so a high score never compensates for a failed gate.
**Walk-forward tests a fixed config across rolling time windows. A fixed, un-retrained strategy configuration has to keep clearing the bar as both the training window and the following unseen window roll forward through time. The committed config produces 9 folds. A fold passes only if the config clears filters on both windows, and test simulation is skipped entirely after a train failure. The 6-of-9 pass requirement is a deliberate tightening, not a framework default. Like the other stages, this one fails open: if every fold's train run returns nothing, the unfiltered survivor set passes through rather than being eliminated.
Monte Carlo varies the execution seed, not the time period. It holds the strategy, data, and dates fixed and instead varies the random seed driving execution realism (rejection, partial fills, slippage), testing whether a strategy's edge depends on one favorable draw. Filtering happens per-run, not on aggregate means, because a mean Sharpe of 1.2 can hide 40% of runs going sharply negative. One confirmed gap: the pass-rate denominator counts only completed runs rather than the configured total, so missing simulations make survival artificially easier.
A 9-candidate sweep can mean 630 simulations. The committed sweep covers 9 configurations across all four stages and 5 symbols, small enough to inspect while still exercising the full pipeline. A full survivor can require up to 70 simulations (cheap plus intermediate plus up to 18 walk-forward runs plus 50 Monte Carlo trials). The larger default pools stay affordable only because the funnel guarantees most candidates never reach the expensive stages.
A shared seed service keeps every strategy's randomness independent. DeterministicSeedService hashes each unit's full logical identity (strategy, config, stage, fold, trial) into its seed, so two different strategies never reuse the same random stream. The key safety property is the identity-mismatch guard: if a checkpoint's ID matches but its full identity doesn't, the service raises an error instead of resuming with mixed lineage. Execution runs on a thread pool, with results reordered by a stable sort key so thread-completion order never affects reported results.
Research hands off to governance with no human gate, by design. Only complete pipeline survivors get auto-inserted into governance as `approved_research`, which is safe because that state is non-executing and non-capital: eligible for review, not authorized to trade. Every later promotion still runs through governance's own machinery. The insertion is idempotent and never resets an existing row, so rerunning an experiment can't accidentally undo a prior demotion.
Governance & Capital Allocation
Four generators, one shared parameter registry. Generating strategy candidates is parameter-space sampling over a typed registry, not machine learning. Grid search takes the full Cartesian product: complete but combinatorially explosive (10 params times 5 values equals 9.7M candidates), so large ranges get collapsed to a min/default/max set. Random sampling draws with replacement, seeded, default 50 samples. The "evolutionary" generator has no fitness function or selection; it mutates each member randomly each generation, exploring neighborhoods rather than climbing toward better regions. The composite path assembles strategy skeletons from a hand-curated table of 13 indicators, producing a documented pool of 854 skeletons checked against the live component registry before acceptance.
Config hashing dedupes within and across sessions. Every accepted config gets a SHA-256 hash: a canonical, order-independent identity, not a security hash. It's used for in-session dedup and, separately, for cross-session dedup via a persistent cache that survives across processes and fails open (a broken cache file logs a warning and starts empty rather than crashing generation). One real gap: the persistent cache is a standalone facility the engine itself never calls; only the in-session set gets checked automatically.
A strict survivor funnel: cheap, intermediate, walk-forward, Monte Carlo. Each stage only receives the prior stage's survivors. "Cheap" and "intermediate" aren't separate stage types. They're two configurations of the same simulation stage: one with a short window and loose thresholds, one with the full window and real thresholds. The ordering is deliberate, cheapest and most-discriminating test first, so a badly broken candidate dies early instead of consuming expensive compute it was never going to survive.
Eight checks decide pass/fail; a separate score decides rank. Thresholds are inclusive, and all checks are evaluated (not short-circuited), so a rejected candidate returns its full failure profile. The eight core checks cover Sharpe, drawdown, trade count, consistency, profit factor, win rate, return variance, and total return; only three are meaningfully active by default. Filtering and ranking are separate steps. Ranking only orders strategies that already passed every filter, so a high score never compensates for a failed gate.
Walk-forward tests a fixed config across rolling time windows. A fixed, un-retrained strategy configuration has to keep clearing the bar as both the training window and the following unseen window roll forward through time. The committed config produces 9 folds. A fold passes only if the config clears filters on both windows, and test simulation is skipped entirely after a train failure. The 6-of-9 pass requirement is a deliberate tightening, not a framework default. Like the other stages, this one fails open: if every fold's train run returns nothing, the unfiltered survivor set passes through rather than being eliminated.
Monte Carlo varies the execution seed, not the time period. It holds the strategy, data, and dates fixed and instead varies the random seed driving execution realism (rejection, partial fills, slippage), testing whether a strategy's edge depends on one favorable draw. Filtering happens per-run, not on aggregate means, because a mean Sharpe of 1.2 can hide 40% of runs going sharply negative. One confirmed gap: the pass-rate denominator counts only completed runs rather than the configured total, so missing simulations make survival artificially easier.
A 9-candidate sweep can mean 630 simulations. The committed sweep covers 9 configurations across all four stages and 5 symbols, small enough to inspect while still exercising the full pipeline. A full survivor can require up to 70 simulations (cheap plus intermediate plus up to 18 walk-forward runs plus 50 Monte Carlo trials). The larger default pools stay affordable only because the funnel guarantees most candidates never reach the expensive stages.
A shared seed service keeps every strategy's randomness independent. DeterministicSeedService hashes each unit's full logical identity (strategy, config, stage, fold, trial) into its seed, so two different strategies never reuse the same random stream. The key safety property is the identity-mismatch guard: if a checkpoint's ID matches but its full identity doesn't, the service raises an error instead of resuming with mixed lineage. Execution runs on a thread pool, with results reordered by a stable sort key so thread-completion order never affects reported results.
Research hands off to governance with no human gate, by design. Only complete pipeline survivors get auto-inserted into governance as `approved_research`, which is safe because that state is non-executing and non-capital: eligible for review, not authorized to trade. Every later promotion still runs through governance's own machinery. The insertion is idempotent and never resets an existing row, so rerunning an experiment can't accidentally undo a prior demotion.
Portfolio Intelligence
Section A: The Math Layer
Black-Litterman starts from equilibrium, not a guess. Given a covariance matrix, benchmark weights, risk aversion, and an operator's views on expected returns, the service answers a narrow question: what portfolio would a rational investor hold? Rather than asking a researcher to specify raw expected returns directly (historically the weakest input in mean-variance optimization), it starts from an implied equilibrium prior and lets the operator nudge it with views they actually have confidence in.
The research-only boundary is enforced in code, not just docs. Most safety controls in this platform draw their boundary through docstrings or downstream checks. This one is enforced directly in the code path: a call raises an error unless the caller's context is on an explicit allowlist and not simultaneously on a blocklist that includes live trading and production rebalancing. A caller can't invoke the service from a live context even by accident.
A hand-rolled optimizer, not an off-the-shelf QP library. The 1,220-line optimizer solves the classic problem, maximizing return for a given risk tolerance under a full-investment constraint, with a from-scratch solver: projected gradient descent, projected back onto the feasible simplex via KKT bisection. Turnover is discouraged via an L1 penalty, and sector caps are enforced by scaling down any sector that exceeds its limit. A convex-backend flag routes the same problem through CVXPY instead; the hand-rolled solver is the default path, not the only one.
dry_run defaults to True, and one constraint only works on the other path. A run is persisted for inspection but explicitly does not write live allocation changes, so no output can go live without an operator deliberately flipping that flag. The optimizer also supports factor-neutralization, constraining exposure to a risk factor toward zero, but the hand-rolled solver never enforces it. It's implemented only on the CVXPY path.
Section B: Candidate Intelligence
Ranking prioritizes; it does not promote. CandidateRankingService converts research evidence into one ranked list via a weighted score across seven components. Alongside the score, it produces a deployability gate at a 0.55 threshold and a human-readable explanation. Its own docstring states the boundary plainly: "This service does NOT promote strategies or enable trading. It is a prioritisation tool for research review."
Overfitting and robustness are different questions, not the same one twice. OverfittingEstimationService looks backward at signs a strategy was shaped by its own training data. RobustnessPredictionService asks a forward-looking question instead, but layers a hard-floor fragility check on top: five catastrophic-signal floors that can override the weighted average entirely, so one genuine failure mode can't get averaged away by five reassuring numbers.
A cosine-similarity fingerprint flags hidden regime overlap. Each strategy gets a 20-element fingerprint across five regime dimensions: trend, volatility, liquidity, mean-reversion, risk. Comparing fingerprints by cosine similarity produces a portfolio-level diversification score. A low score flags strategies that tend to succeed and fail together across the same regimes, a risk a plain return-correlation check can miss entirely.
From-scratch clustering surfaces near-duplicate strategies. StrategyClusteringService groups similar strategies with hand-rolled single-linkage clustering; no external library import appears in the file. A ranked list can contain a dozen near-duplicate variants of the same idea, and clustering surfaces that redundancy before a researcher mistakes many similar strategies scoring well for many independent sources of edge.
This layer has no REST endpoint, by design. Unlike governance and safety, nothing in Portfolio Intelligence is exposed via API. It's driven entirely through CLI commands and scripts: an internal research toolkit a researcher runs deliberately, not something the platform calls automatically.
Data & Storage Layer
Immutable, versioned Parquet, never overwritten in place. Before writing, the system checks whether a version already exists and raises an error if so. Every downstream cached simulation and audit record points at a specific dataset version, so if that version could be silently rewritten, the platform's "same inputs, same result" guarantee collapses. Two independent SHA-256 checksums protect against different failure modes: one checks whether the data matches, the other whether the file was corrupted on disk. The roughly 33.6K committed Parquet files in the repo are the expected consequence; old versions are never deleted, only superseded.
Atomic transactions for state changes; independent sessions for audit trails. One shared database session gives multi-table operations, like a rebalance touching positions, cash, and allocations, atomic commit-or-rollback semantics. Governance, audit, and diagnostic tables deliberately sit outside that boundary and manage their own session scope. That's a reasonable choice: an audit record should commit on its own terms, independent of whatever business transaction triggered it.
The tradeable universe is versioned like market data. A universe that silently drifts makes every backtest against it systematically optimistic. The lifecycle moves through four states, candidate, proposed, active, retired, tested against a real database, not mocked. Before activation, a churn check catches an unusually large day-over-day membership change, which more often signals a broken data feed than a real market event. Rollback builds a brand-new proposed version rather than pointing the active flag backward, so every version, once created, means exactly one thing forever.
Four ingestion schedules, each matched to its data's cadence. A daily backfill catches historical gaps, a 5-minute DAG keeps market data fresh during trading hours, a daily corporate-actions DAG reflects that splits and dividends don't happen intraday, and a weekday-only trading DAG encodes the market calendar directly in its schedule.
Two feature-pipeline guards ask two different questions. One asks a performance question: has this already been computed, so recomputation can be skipped. The other asks a correctness question: does an available dataset actually satisfy a strategy's required features, including price basis (raw versus adjusted, where using the wrong one silently corrupts every return calculation). A dataset can pass one check while failing the other, which is exactly why they stay separate.
The golden-path orchestrator is a manual validation tool, not a fifth schedule. It chains backfill, corporate actions, features, and experiments as one durably tracked run. Its only caller anywhere in the codebase is a manually-invoked soak-testing tool; the real DAGs run independently on their own schedules.
Corporate actions, survivorship, and ticker lifecycle solve related but separate problems. Corporate-action adjustment happens at processing time, not ingestion; raw bars are never mutated. Of eight modeled action types, only splits and cash dividends currently have implemented logic. Survivorship runs in two layers: a hard pre-flight gate, and a softer validation service for fold-level walk-forward checks. Ticker lifecycle is a flat event log of renames, delistings, and mergers, walked as-of a given date with explicit cycle protection.
Testing Suite
A schema-drift tripwire enforces a rule the docs alone can't. A written rule isn't enforced by anything on its own. One test diffs 28 contract/ORM pairs field-by-field on every run; a second, integration-tier test runs a real migration against live Postgres and diffs the model against what the database actually has. Together they catch both contract-versus-code disagreement and code-versus-database disagreement, including the kind of failure that would surface a hand-edited or silently failed migration.
Ten dedicated safety test files, not one catch-all. That granularity reflects the code itself: this is the platform's highest-consequence path, so a failing test name pointing at exactly one gate matters more here than elsewhere. The repeated pattern across files: verify the math, verify the gate enforces it, verify enforcement is logged, then add a dedicated regression test for the specific bug the gate was built to prevent.
Fixtures write through real code, not mocks. Eight fixture modules layer from single-stage state up through full multi-cycle chains. The trading-cycle fixture writes a real dataset and seeds real database rows, patching only the one genuinely unpredictable seam (strategy output) before handing control to the actual production code. A repository bug would surface here, since the fixture writes through the real repository rather than asserting against a mock.
A real Postgres tier in CI, a fast SQLite tier locally. CI boots a real Postgres container for integration-marked tests, including the schema-drift suite's database half, so that check actually executes rather than being skipped. Locally, the suite defaults to in-memory SQLite for speed. The schema-drift suite is what makes that split safe: without it, a green SQLite run wouldn't prove the schema was correct against the database it runs on in production.
Near-zero skip and xfail debt across 4,080 tests. A direct grep turns up exactly one xfail marker and no unconditional skips. Skip and xfail exist as escape hatches to keep a known-broken test around without blocking CI, and left unchecked they pile up into tests nobody trusts. Having almost none of that debt means passing tests are passing because the code works.
A/B replays prove settings actually change behavior. One test file proves risk settings change real trading-cycle output, not just that they get saved to the database, by running the same deterministic replay twice and changing one setting between runs. The most pointed case sets the portfolio-level drawdown limit so loose it can never fire and the strategy-level limit so tight it fires immediately, then confirms orders actually get blocked, proving the two gates are independent.
Observability
261 instruments across a 1,932-line metrics module. A platform trading autonomously has no operator watching every tick, so the only way to catch a problem early is if the code emits numbers at every meaningful state transition. Histograms slightly outnumber counters. A codebase leaning this heavily on histograms is built to answer "what does this normally look like," the harder, more useful question when the failure mode is gradual drift rather than a clean outage.
One shared function stamps every trace automatically. In a system running concurrent cycles, a raw trace is close to useless unless it can be filtered to exactly one run, and that can't depend on every call site remembering to attach the right IDs by hand. A shared runtime context lives in a context variable and merges onto whatever's already bound, so a nested call inherits its caller's identity automatically. Correctness of one seam guarantees consistency everywhere.
Clickable trace links are wired into the real API, not sitting unused. A small utility builds ready-to-click Grafana links from a correlation ID. It's called per row by the operations service, and the resulting links are serialized out through the real API. A job-run response contains a clickable link straight into the trace and logs for that run.
Allocation config is snapshotted once so a live edit can't leak into a backtest. A multi-year backtest re-reading policy from the database on every bar has no way of knowing if an operator edited that policy mid-run. The fix snapshots the config once before the loop starts. The second database read simply doesn't exist, so the guarantee is structural, not incidental.
The replay tool calls the real production functions, unmodified. Backtesting infrastructure quietly stops meaning anything when the test path and production path drift apart. This orchestrator imports and calls the real ingestion, feature, and trading-cycle functions straight from their production modules, so there's no parallel code path to drift.
A chaos-testing matrix injects known-bad conditions on purpose. Verifying the platform handles a broker mismatch or a governance trigger correctly is hard to test safely against real infrastructure. `platform_replay` covers nine failure kinds across six injection targets, with every incident written into real database tables rather than mocked.
Backtesting & Simulation Engine
Three cash buckets, because settled and unsettled aren't the same money. A backtest that treats cash as one number will happily let a strategy spend proceeds from a sale it hasn't actually received yet. The engine tracks settled, unsettled, and reserved cash separately, with buying power as a hard gate: an order exceeding it is rejected and logged, not silently under-filled. Settlement is bar-indexed rather than wall-clock, so runs at any bar cadence settle after the same number of bars.
Latency is modeled by deferring execution, not adjusting price. Dividends are applied the moment the ex-date bar is reached, credited directly into settled cash. Order latency pushes execution to a future bar index rather than approximating a delay in the fill price. With nonzero latency, an order fills at the execution bar's open, not the signal bar's close, because it genuinely didn't exist yet when the signal fired.
Byte-identical replay is a real CI gate, not a loose check. Every probabilistic decision, including rejection, partial fills, and fill probability, runs through a single seeded RNG reset at the start of every run. The deterministic-replay suite runs the same seeded simulation twice and asserts exact equality across equity, cash, positions, and fill counts, paired with a companion test proving different seeds do eventually diverge.
Sim and live share the slicing math but not the routing. The simulated and live execution engines both construct instances of the identical TWAP/VWAP slicer classes. The honest caveat: on the live path, the slice schedule gets folded into a single order's metadata rather than routed as real child orders. The simulator genuinely schedules and fills each slice independently. The math is shared; the routing isn't.
Slippage assumptions are calibrated from real paper-trading fills. The calibration service pulls realized fill-quality metrics from paper trading and derives an impact coefficient as an explicit, reproducible formula. A minimum of 30 fill samples is required before a calibration counts as reliable; below that, it falls back to safe defaults rather than letting a handful of fills swing the model.
A live two-year backtest is already mid-flight. A 2023-to-2024 backtest over a 100-symbol universe is running now, driven through the real production trading-cycle function tick by tick, with checkpoint/resume support. The checkpoint shows 206 of roughly 504 trading days completed, zero failed ticks, and 11,078 real orders with matching fills so far, directly contradicting an earlier finding that every previously-committed backtest artifact showed zero total orders.
Visualization / Storytelling
TODO
TODO ADD IMAGES HERE
What's Next
TODO ADD
TODO