Skip to content

Search

Search projects, papers and posts, or ask the AI.

All work

Finance & Trading2026

SIMP

A simulated asset manager, from index decision to fill

A research platform that runs a synthetic S&P 500 index fund end to end, turning CRSP-based decisions into PM, trader and broker orders, executing them against BMLL-schema market data and measuring cost with notional-weighted TCA.

1,002
trading sessions per run on the synthetic NYSE calendar
29
Postgres tables in the order-chain and ledger schema
56
FastAPI endpoints
66
backend tests
simp · recorded demo · 0:21
  1. 01Problem

    The team handed over a research notebook that replicated a four-year S&P 500 index fund, July 2022 to June 2026, under simple assumptions: decide on a Monday close, fill at Tuesday's close, pay no trading cost and see no investor flows. I wanted to know what that fund would have looked like if an asset manager had actually run it, with orders passing from a portfolio manager to a trading desk to brokers, executing against auction and intraday market data, and being measured the way a buy-side TCA team would. That meant replacing the notebook with a platform where every share could be traced and every number reproduced.

  2. 02Data

    I built a Parquet lake with a manifest per pull, a SHA-256 hash for every file and schema contracts checked on write, so each run pinned the snapshot it read. The bundled data is synthetic: a generator writes CRSP CIZ v2 and BMLL Data Lab tables under their real column names, and every manifest, API response, export and screen carries a SYNTHETIC label. I wrote the live connectors, a WRDS loader that checks CIZ columns first and an isolated BMLL SDK puller, but they were not used for these images. I mapped securities point in time from PERMNO to ISIN to BMLL ListingId, sending unmapped names to a review queue.

  3. 03Order chain

    I modelled the order chain as five Postgres tables, pm_order, trader_order, program, child_order and fill, with foreign keys running from each fill back through its child and trader order to the PM order, the decision and the run. On the ledger side I recorded fills, T+1 and T+2 settlement, dividends, flows and fees, which fed daily NAV and position tables. Every run ended with an audit requiring 100% fill lineage, no quantity overruns, timestamps that only moved forward and an explicit EXPIRED or CANCELLED status for any quantity left unfilled. Unsupported corporate events were never given an invented payoff; the affected names were excluded and exited before the ex-date.

  4. 04Engine

    The engine ran a deterministic session loop: corporate actions and settlement before the open, then decisions, the desk, execution and post-close accounting. I checked the NAV identity every session against an independent P&L path and stopped the run with a named error on any break. Index replication decided at the announcement close, five sessions before the effective date, and traded the third-Friday closing auction, while the handoff's Monday-to-Tuesday timing stayed available for comparison. Investor flows followed an AR(1) process with Student-t shocks and a redemption jump, and flow orders were netted against same-day rebalance orders through internal crosses. Desk routing came from a validated YAML rules table.

  5. 05Execution and TCA

    I implemented four execution tiers. T0 filled at the close, T1 applied auction and square-root impact models to daily BMLL data, T2 replayed intraday prints capped at 10% of each interval's volume, and T3 wrote a job spec for BMLL's Data Lab simulator. Analytics were notional-weighted throughout. Implementation shortfall was split into delay, execution, explicit and opportunity cost, every order was repriced at MOC, VWAP and arrival, and confidence intervals came from a block bootstrap by date with 2,000 resamples. A heatmap crossed order size against ADV and auction volume. A known-answer test injected a cost into generated fills and recovered it within 0.5 bps.

  6. 06Fund analytics

    At fund level I computed tracking difference and tracking error, net and gross of fees, against CRSP's value-weighted return or the official total return, along with cash drag, beta, drawdown and worst days. To explain tracking difference, I built a counterfactual chain of six runs that switched one effect on at a time, from the handoff baseline through effective-date timing, investor flows, T1 costs, the management fee and the exclusion policy, and attributed each step's change. Sensitivity studies swept the provisional dividend rule, the exclusion policy, cost parameters at plus and minus 50% and the flow regime. A one-date study broke a single rebalance down order by order.

  7. 07Interface

    I built the frontend in Next.js 15 and TypeScript with ten screens: Overview, Fund, Runs, Blotter, TCA, the one-date study, Market explorer, Studies, Data and Settings. Runs were submitted through a stepped form that checked data availability first, then streamed progress over server-sent events. The blotter expanded PM orders into trader orders, child orders and fills, and a lineage drawer showed the intraday price path, the timeline and the shortfall for a single order. Every filter lived in the URL so that views could be shared, and the TypeScript client was generated from the FastAPI OpenAPI schema. Buys were blue, sells orange, and cost used a diverging scale.

  8. 08Reproducibility and testing

    Each run stored its config hash, git SHA, lake snapshot hash, corporate-action review hash and desk-policy hash, and random draws came from a seeded PCG64 stream per seed, component and date. The replay command re-ran a config on its pinned snapshot and failed unless the output hash matched; for the run in these images it matched. I wrote 66 backend tests covering Hypothesis property tests on the ledger, order-chain invariants, known-answer TCA, timing regressions, mapping, determinism, T2 caps and an OpenAPI contract snapshot. CI ran ruff, strict mypy, pytest, and the frontend lint, type check, Vitest and build. A Playwright journey drove a run from submission to a fill.

Screenshots