# tradefloor > A reproducible evaluation environment for financial AI agents: a > deterministic market simulator with order-book execution, macro dynamics > and causal ground truth. > Version 0.5.0. Every page of the learning path, in reading order. ====================================================================== # Learn tradefloor https://tradefloor.dev/ Run reproducible experiments on trading agents in a deterministic market with order-book execution, market impact and causal ground truth. ====================================================================== A reproducible evaluation environment for financial AI agents Run controlled, reproducible experiments on trading agents in a market with realistic execution - not a backtest over historical prices. A deterministic market simulator with order-book execution, macro dynamics, causal ground truth and agent-native interfaces. pip install tradefloor cargo add tradefloor import tradefloor as tf # you name the companies yourself u = tf.Universe([ tf.Instrument("AMZN", "technology", initial_price=397.17, eps=12.55, book_value_per_share=20.19, shares_outstanding=5.1e9), # GOOGL, MSFT, AAPL, NVDA alike ]) # or Universe.random(5, seed=7) to # generate one: AAA, AAB, AAC... e = tf.Engine(seed=776644, universe=u) e.run_days(30) bars = e.bars(grain="day") # prices truth = e.truth() # and why print(u.fingerprint, e.model_fingerprint) index, 100 = day 0 close 80 to 125 AMZN 99.1 GOOGL 99.6 MSFT 100.4 AAPL 96.7 NVDA 101.5 VIX 14.89 > DAY 1 / 30 VIX 14.89 CYCLE expansion BIGGEST MOVE AAPL -3.31% MEAN MISPRICING 0.0134 RETURN SPREAD 4.83 pts Reproducible A universe of companies, a macro state for day zero, and a seed. The same three inputs produce the same market on every supported platform, so a run is reproducible by anyone who has them. Execution-aware Agents trade through an order book and pay the spread, the queue and their own impact. Run the same seed without the orders and the difference is the cost of trading. Inspectable The simulator computed each price, so it can say why. The nine factor contributions sum to the move - an identity, not a fitted explanation. The residual is near 1e-16. Agent-native A Python object with an act method, a Gymnasium environment, or a model calling the simulator as a tool over MCP. The harness never asks what is inside. REPRODUCIBILITY Git for market experiments A market here is an explicit configuration and a seed, so an experiment can be recorded, re-run, compared and branched without depending on a market nobody controls. It is the working practice of version control applied to an experiment rather than to code: share the configuration, share the seed, re-run the experiment and get the same market back. What a RunManifest pins seed The integer the whole market unrolls from. universe Every instrument, in roster order. The order is contractual. macro Day-zero rates, inflation and the cycle. scenario The macro path driven through it, if there was one. strategy The StrategySpec, carried in full and cited by fingerprint. model The preset that priced it, by name, or custom-XXXXXXXX if modified. fingerprints.inputs One digest over the seed and all of the above. This is the experiment's identity. result.digest What the market did. Re-running the inputs has to reproduce it. run → record → reproduce → compare → fork reproduce() refuses on a mismatch rather than returning a number that looks fine. A checkpoint branches a run at a decision point, so two hypotheses share one past instead of being statistically similar. Same world. Same conditions. Different agent. Two agents run against the identical market path, so a difference between them is the agent and not a different roll of the market. Same experiment. Different hypothesis. Change one component, re-run, and attribute the difference to the change. Determinism is what makes the attribution mean anything. Fork an experiment without changing its past checkpoint · day 100 IDENTICAL MARKET HISTORY UP TO HERE CHANGE ONE INPUT, HERE fork A policy A future A fork B policy B future B compare 1 Run once, then mark the state A checkpoint captures the engine at a chosen day: every column, the order book, and the position of the generator that draws the next random number. 2 Branch it mark.branch(2) returns two engines identical to that state, in under a millisecond. Checkpoint.resume() is the slower form that replays the order log and outlives the process. 3 Change exactly one input The agent, the policy, an order size, a pinned macro field. Everything else on both branches is already identical, so there is nothing else it could be. 4 Let both run on Each branch carries its own copy of the generator, so the futures are independent from the fork onward and neither disturbs the other. 5 Compare The two runs shared one past exactly, not approximately. What differs between them is the input you changed. This is a fork of an experiment, not of a repository. Both branches carry the same past bit for bit, so they do not start from similar conditions - they start from the same one, and the difference between the futures is attributable to the input you changed rather than to a different roll of the market. GROUND TRUTH The simulator knows why the price moved Every price came out of a computation, so the move decomposes into the nine contributions that produced it and the accounting closes on the realised move. A backtest cannot do this: nobody recorded why the market did what it did, so an attribution over historical prices is a model fitted after the fact. Questions it makes answerable Did the agent trade on momentum, the macro state, liquidity, or noise? Did its stated reason match the state the market was actually in? What happens to the result when one causal channel is removed? Did two agents reach the same P&L for different reasons? What it is not Ground truth about this market, not about any real one. The decomposition is exact because the simulator did the arithmetic, which is also the reason it says nothing about why a real price moved. STEP 01 - SEE IT The seed fixes every random number The same universe, macro state and seed give the same market on Linux, macOS and Windows, digit for digit. Every release does a check of this. It builds five wheel targets, runs one fixed simulation in each, and compares the digests. A disagreement stops the release. Determinism also makes a fork possible: checkpoint at day 100, then branch, and the two branches share one past. The two runs below are the repository's reference runs, and they have a different seed and a different roster. seed 776644 · calm seed 20260821 · eventful GROUND TRUTH · DAY 1 expansion The nine factors that sum to every move One row per instrument per tick. The nine contributions sum to the total log move. The residual is near 1e-16. An explanation here is therefore an identity, not a model that fits after the fact. reversion momentum crowd_lean company_news order_flow_impact short_squeeze_effect random_noise circuit_breaker jump Three of them are the model's own dynamics, not shocks. The answer can therefore be "nothing happened, the price drifted back toward fair value". That is the correct answer most of the time. STEP 02 - BUILD A UNIVERSE Choose who exists A universe is the roster and its fundamentals. The generator fills twelve sectors round-robin, so a generated roster is more balanced than any real index. Set one up here and take the code. Companies 40 Universe seed 7 Roster shape balanced S&P-like tech-heavy defensive Sector mix technology 4 financial services 4 healthcare 4 energy 4 consumer discretionary 3 consumer staples 3 industrials 3 import tradefloor as tf u = tf.Universe.random(40, seed=7) len(u) # 40 u.fingerprint # sha256, order included u[0].ticker, u[0].sector # heaviest sectors: technology 4, financial_services 4 macro = tf.Macro(federal_funds_rate=0.025, corporate_bond_yield=0.052, vix=16.0) e = tf.Engine(seed=42, universe=u, macro_state=macro) e.run_days(252) # one trading year Roster order is contractual Reorder the roster and you get a different market from the same seed. Sort your tickers alphabetically upstream and you change the world by accident. Do a check with universe.fingerprint . STEP 03 - RUN YOUR STRATEGY Write the strategy down A StrategySpec is JSON with a sha256 , so someone else can check your result with the package and nothing else of yours. Compose one here and take it. Signal hold random momentum mean_reversion oracle Concentration, top_k 5 Participation cap 0.02 Cadence step daily weekly { "spec_version": 1, "signal": { "kind": "momentum", "lookback_days": 1 }, "portfolio": { "gross": 1, "top_k": 5 }, "execution": { "cadence": "step", "max_participation": 0.02 }, "seed": null } spec = tf.StrategySpec.momentum( lookback_days=1.0, top_k=5) scores = tf.evaluate( {"mine": spec}, seed=7, universe=u, days=10) scores["mine"].return_pct scores["mine"].impact_bps scores["mine"].strategy_fingerprint # sha256 -- cite this How an order fills Price-time priority, one book per name, no slippage formula anywhere. Step through the engine's own reference program. submit buy 175 market step 15 of 21 mm 105.00 140 mm 104.00 130 mm 103.00 105 spread 4.00 · mid 101.00 · last 103.00 mm 99.00 100 alice 99.00 50 carol 99.00 25 mm 98.00 110 mm 97.00 120 mm 96.00 130 mm 95.00 140 What this step tests market buy sweeps several levels BEST BID 99.00 held BEST ASK 103.00 moved SPREAD 4.00 moved RESTING 7 / 3 moved Fills 50 @ 101.00 110 @ 102.00 15 @ 103.00 175 filled at a VWAP of 101.80 against the 101.00 best offer resting when the order arrived: +79 bps > STEP 04 - DRIVE IT Three ways to use it Three modes of agent interaction: observe the market, trade inside it, or train against it. A model over MCP, a reinforcement-learning policy, or trading logic you wrote. Agents over MCP No simulator integration code. Claude or another model calls tradefloor through MCP, observes the market and submits trades as tool calls. Eleven tools, everything composed as data. pip install "tradefloor[mcp]" claude mcp add tradefloor \ -- tradefloor-mcp # then just ask it a question Reinforcement learning Passes gymnasium's env_checker. Actions are target weights in [-1, 1]. Reward is measured after the market moves, so it already carries the cost of the agent's own footprint. import tradefloor.gym as ptg env = ptg.TradingEnv(universe=u, seed=42, days=20) obs, info = env.reset(seed=42) obs, r, done, trunc, i = env.step(a) Your own trade logic A TWAP, a VWAP, an iceberg, or your own logic. Any object with an act method will do. The observation is narrow on purpose. It exposes only information available to a trader inside the simulated market. class Mine: def act(self, obs): return {"AAA": 0.2} tf.tca.analyse(Mine(), seed=42, universe=u, days=5) Ten agents, one market each Hand evaluate a dict of ten agents and each one runs in its own copy of an identical world: same seed, same roster, same macro path. Your algorithm, a trained policy, an LLM agent and the five baselines land in one table. Then rank repeats that across seeds and separates them with a paired sign test. The Oracle reads the true mispricing and says so. Read it as a reference point, not a ceiling: it spends the same gross on a naive rule, and mean-reversion beats it on 5 of the 12 markets. SHIPPED · EACH IN ITS OWN COPY Ten agents, ten identical markets. Each agent runs in its own copy of the same deterministic market, so nobody eats another agent's depth and the comparison stays clean and reproducible. A single shared market with many agents is the extension described below. agents = tf.reference_agents(seed=3) agents["mine"] = Mine() agents["policy"] = TrainedPolicy(net) scores = tf.evaluate( agents, seed=7, universe=u, days=10) ranking = tf.rank( lambda: dict(agents), seeds=range(12), universe=u, days=10, workers=4) COMING UP Coming next: competitive multi-agent markets single-agent evaluation → competitive agents → emergent market behaviour The next step is to move from evaluating agents independently to evaluating them together. A shared-book arena will let several agents interact in one market, compete for the same liquidity, respond to each other, and be scored under identical conditions - which turns a reproducible environment for single-agent evaluation into somewhere to study how a market of agents behaves. It is not shipped. The primitives are public and composable in the meantime, and this is what building one takes. What an arena takes COMPONENT PRETIUM GIVES YOU YOU WRITE agent act(obs). The harness never asks what is inside an agent. The model call behind it. merge engine.run_session takes an order_flow dict, so many agents can push orders into the same tick. Combining every agent's pending flow into one dict per tick. observation The book, the position and the truth table, every tick. What each agent sees, and how you serialise it into a prompt a model can answer. model budget Nothing: this one is entirely yours. One call per agent per decision. Ten agents over 20 days at three decisions a day is 600 calls. execution Portfolio turns target weights into orders and reports pending_flow(). One book per name, price-time priority, so the agent that arrives first fills first. The participation cap and the order sizing. scoreboard tf.rank compares agents across seeds, not against each other in one book. Ranking the cohort within the shared book. counterfactual The same seed with no flow at all. The comparison that prices the whole cohort's footprint. # 1. an agent is a model behind act() class LLMTrader: def __init__(self, model, style): self.model = model self.style = style # "aggressive" def act(self, obs): reply = self.model.ask( system=self.style, book=obs.book, position=obs.position) return reply["weights"] agents = { "claude-momentum": LLMTrader(m1, "momentum"), "gpt-value": LLMTrader(m2, "value"), "twap": TWAP(minutes=30), } # 2. one engine, one book, all of them engine = tf.Engine(seed=7, universe=u) books = {n: tf.Portfolio(cash=1e6) for n in agents} for day in range(days): engine.open_market() for step in range(steps_per_day): flow = {} for name, agent in agents.items(): obs = observe(engine, books[name]) orders = tf.baselines.rebalance( obs, agent.act(obs), max_participation=0.02) books[name].execute(orders) merge(flow, books[name].pending_flow()) engine.run_session(*clock(step), ticks, order_flow=flow) engine.close_market() What the arena would give you Agents that pay for each other's size. In a shared book agents do not only compete on returns, they compete for liquidity: one agent's sweep consumes the depth another expected to trade against, and every fill is priced by whoever else was in the book that tick. The counterfactual still works - same seed, no flow - so you can price the whole cohort's footprint. What it would not give you A market maker that learns the cohort. The maker and the aggregate flow do not adapt, so the only strategic behaviour in the arena is the behaviour you brought. The realism envelope is also silent here: it was measured on one agent, so a cohort result sits outside what has been certified. STEP 05 - TRUST THE RESULT How closely it reproduces measured behaviour Real markets have habits. Prices jump by a typical amount. A calm week often comes after a calm week. Names move together in a panic. This page measures fourteen of those habits, each against the range that real equities hold. For the shipped preset, the thirty-seed median of each of the fourteen falls inside its range at 252 days, on the forty-name sector-balanced roster the certification was measured on and again on a held-out sixty-name universe. A median is not every seed: check the intervals before relying on one. There is no single score, because one number travels further than its caveats. annualised_vol_pct 28.3103 excess_kurtosis 10.0043 return_acf1 0.0114 abs_return_acf1 0.0769 abs_return_acf5 0.0305 abs_return_acf20 0.0096 cross_sectional_corr 0.2616 volume_abs_return_corr 0.5108 leverage_effect -0.0258 volume_change_acf1 -0.2794 corr_asymmetry -0.0018 corr_asymmetry_lagged -0.0327 sector_excess_corr 0.2081 corr_persistence_acf1 0.1771 band floor band ceiling Will my result hold up? How long is your run? 252 days, about a year What is in it? Spread Over Sectors Shaped as the S&P Mostly Technology Yes. This is the exact roster the certification was measured on: twelve sectors, filled round-robin, forty names. The thirty-seed median of all fourteen landed in range on it. Run a crisis through it A scenario drives the macro path. Without one, the economy finds its own path. Pick a scenario and see what the market did. import tradefloor as tf u = tf.Universe.random(40, seed=7) shock = tf.Scenario.rate_shock( start=0.045, end=0.016, over=13) pt.evaluate(agents, seed=7, universe=u, days=83, scenario=shock) # leave corporate_bond_yield free: # pinning it severs the channel # the shock is travelling down. Rate scare, 2021 2021-05-28 4270 4001 3731 -4.2% in 13 sessions 2021-02-01 2021-05-28 S&P 500 4204 VIX 16.76 Good results here do not predict real returns The price process comes from a known model. A strategy that fits the structure of that model looks excellent and teaches you nothing. A strategy that fails here tells you more, because it broke against a live order book under honest impact costs. One market is one sample On the twelve-market grid the docs measure, mean-reversion pools at +0.783 capture and momentum at +0.259, so mean-reversion leads by three times. Paired across the same twelve markets it wins 9 to 3, at p = 0.15. One seed picks the pooled leader eight times in twelve, and what a seed says momentum is worth runs from -0.503 to +0.909. WHO IT IS FOR Built for financial AI research LLM agents Evaluate agents that reason, call tools, read state and make a sequence of trading decisions. The MCP server exposes the simulator as eleven tools. Reinforcement learning A Gymnasium environment with explicit rewards, execution costs and market state, and as many independent episodes as you want. Quant and execution Queue position, partial fills, honest impact, and macro scenarios to run a strategy through. Test under controlled dynamics rather than one historical path. Benchmark builders A manifest that names the package version, preset, seed, universe, macro, scenario and strategy, and a reproduce() that refuses on mismatch. Start here Start > Install > Core concepts > The two loops > Running a market Use it > Agents > Scenarios > Execution cost > Checkpoints and forking > EDGAR companies > RL environment > The MCP server Trust it > Realism envelope > The metrics > Principles > Citing a run Reference > Glossary > The nine factors > Conventions > Schemas > Presets > Release notes API > Core types > Evaluate > Parameters NEXT Install element, and that is not a style choice. An HTML parser applies the table content model to anything it parses, and `` is not in it - so a loop written inside a table is foster-parented out of the table and its rows are left behind ungrouped. The template survives a intact because a script's contents are never parsed as markup. --> ====================================================================== # Install https://tradefloor.dev/install.html Install tradefloor from PyPI or crates.io, and what a version number promises about a published result. ====================================================================== LEARN/ INSTALL Install One command, and no dependencies. Prebuilt wheels cover Linux, macOS and Windows on CPython 3.11 and up. Python Rust MCP pip install tradefloor python -c "import tradefloor as tf; \ print(tf.__version__)" From PyPI No dependencies The core package pulls nothing in. Read results into polars, pandas, pyarrow or duckdb if you want them. CPython 3.11 and up Prebuilt wheels for Linux x86_64 and aarch64, macOS arm64 and x86_64, and Windows x86_64. Rust core The engine is compiled. Nothing here is a Python loop over ticks. What a version promises The API can still move before 1.0. A published result can not. A change to a simulated trajectory arrives as a new preset with a new name. It is never an edit to a preset that ships. A run that you cited last month therefore reproduces this month. A trajectory change is a breaking change Whatever its size. A market that runs differently from the same seed invalidates every published result that cited it. Old presets never move Every preset from pt-v1 onward stays selectable and reproduces bit for bit, so an old result replays under its own name. A citation names software, not a run A RunManifest names the run: package version, preset, seed, universe fingerprint, macro conditions, scenario and strategy. The determinism check Every release builds five targets, runs one fixed simulation inside each, and compares the digests. A disagreement stops the release. This is why the crate ships its own exp , log , sin and cos The crate does not call the platform libm, because the platform libm does not agree with itself across operating systems. linux-x86_64 digests match linux-aarch64 digests match macos-arm64 digests match macos-x86_64 digests match windows-x86_64 digests match A person does a check of the WebAssembly build by hand, because it needs a toolchain that CI does not carry. On 2026-08-24 the native and wasm32 builds hashed the same fixed simulation, twelve instruments over five days on pt-v3, to 2b2f3141... Presets and the era boundary Fifteen presets ship, pt-v1 through pt-v14 . You can select all of them, and all of them reproduce bit for bit. 7/14 pt-v3 0.1.x 13/14 pt-v10 0.2.0 14/14 pt-v12 0.3.0 14/14 pt-v14 0.4.0 Statistics in band over two years, thirty seeds, for whichever preset was the default at that release. Each step between bars is an era boundary. pt-v14 became the default on 2026-08-28. That date is an era boundary, because every trajectory from the default changed. You can not compare a run from before that date to a run from after it, unless each run names its preset. What fully specifies a run Package version, model preset, universe fingerprint, and seed. The preset pins the coefficients. The package version pins the implementation. This is measured, not asserted: pt-v1, pt-v2 and pt-v3 give identical market digests under 0.1.4 and 0.2.0. All pages Start > Install > Core concepts > The two loops > Running a market Use it > Agents > Scenarios > Execution cost > Checkpoints and forking > EDGAR companies > RL environment > The MCP server Trust it > Realism envelope > The metrics > Principles > Citing a run Reference > Glossary > The nine factors > Conventions > Schemas > Presets > Release notes API > Core types > Evaluate > Parameters BACK Learn tradefloor NEXT Core concepts element, and that is not a style choice. An HTML parser applies the table content model to anything it parses, and `` is not in it - so a loop written inside a table is foster-parented out of the table and its rows are left behind ungrouped. The template survives a intact because a script's contents are never parsed as markup. --> ====================================================================== # Core concepts https://tradefloor.dev/core-concepts.html Universe, macro and seed: the three things that define a run, and what each one does once the market starts moving. ====================================================================== LEARN/ CORE CONCEPTS Core concepts Three things define a run. Two of them do something you can not expect when the market starts to move. import tradefloor as tf universe = tf.Universe.random(108, seed=7) macro = tf.Macro(federal_funds_rate=0.025, corporate_bond_yield=0.052, vix=16.0) engine = tf.Engine(seed=42, universe=universe, macro_state=macro) Roster order changes the market Universe subclasses list . The engine walks the instruments in index order and draws random numbers as it goes. A re-sorted roster is therefore a different market from the same seed. What a reversed roster costs On Universe.random(20, seed=11) at sim seed 42, AAA closes day 5 at: 143.03 roster order 134.88 reversed Same names, same fundamentals, same seed. A sort of your tickers upstream does this without a warning. There are two seeds The universe seed Picks the fundamentals. The generator assigns tickers and sectors by position. random(40, seed=1) and random(40, seed=99) therefore share every name and every sector. They agree on the fundamentals of none of the forty. The simulation seed This seed picks the market. Hold the universe fixed and change this one. That is the standard setup to find how much of a result was luck. The economy moves on its own The Macro that you give the engine is the state on day zero. Every close advances the chain. The economy updates, the cycle can turn, and the central bank acts. If you want a frozen economy, you must ask for one. How often each field takes a new value 120 days, Universe.random(20, seed=11), sim seed 42 vix 118 of 120 days gdp_growth 6 values inflation_rate 4 values corporate_bond_yield 3 values fundamental_value 3 per instrument federal_funds_rate 2 values VIX moves nearly every day. The policy fields step on the central bank's meeting calendar. Fair value reprices when the discount rate moves at a meeting. In that run it moved on day 45 and day 96. A run that crosses a meeting is therefore not a stationary experiment. Pinning a field does not freeze it A pin replaces the endogenous step for that field one time. The chain then continues from the pinned value. Pin vix=30.0 after day 3. The next four days read: 30.00 pinned 22.75 23.62 23.89 19.30 The series pulled back toward its mean. It did not hold. For a day-by-day exogenous series, pin the field every day, or drive a scenario. Four ways to build a universe tf.Universe.random(108, seed=7) # generated, plausible per sector tf.Universe([tf.Instrument(...), ...]) # your own roster tf.Universe.from_edgar(snapshot) # real SEC fundamentals tf.Universe.from_json(saved) # one you saved with to_json universe.fingerprint # sha256 over the roster, order included The generator fills twelve sectors round-robin, so a generated roster is more balanced than any real index. That costs nothing over one year. It shows in the second year, as the envelope states. All pages Start > Install > Core concepts > The two loops > Running a market Use it > Agents > Scenarios > Execution cost > Checkpoints and forking > EDGAR companies > RL environment > The MCP server Trust it > Realism envelope > The metrics > Principles > Citing a run Reference > Glossary > The nine factors > Conventions > Schemas > Presets > Release notes API > Core types > Evaluate > Parameters BACK Install NEXT The two loops element, and that is not a style choice. An HTML parser applies the table content model to anything it parses, and `` is not in it - so a loop written inside a table is foster-parented out of the table and its rows are left behind ungrouped. The template survives a intact because a script's contents are never parsed as markup. --> ====================================================================== # The two loops https://tradefloor.dev/two-loops.html Training means two different things here. One trains the market model, the other trains a strategy against a market that already exists. ====================================================================== LEARN/ THE TWO LOOPS The two loops The word training means two different things here. One kind trains the market model. The other kind trains a strategy against a market that exists. Most people need only the second kind. LOOP A Calibrate the market model map the space Atlas survey then confirm disjoint seeds then gates thirty seeds then overfitting control emits preset pt-v14, frozen A complete set of coefficients with one name. Change any of them and the set gets a new name. the realism envelope Attached to the preset. Bounds what a result may claim. cited by, and bounds LOOP B Evaluate a strategy or agent write a strategy or train a policy then evaluate many seeds then paired sign test across seeds Nothing travels back. If you tune the market on the results of a strategy, you put the edge of that strategy into the world that then measures it. Which loop you are in decides what you read Test a trading strategy B Running a market Train a reinforcement-learning policy B The RL environment Know what your result depends on A Atlas Fit coefficients to your own data A Atlas Know what the market reproduces neither The realism envelope What the envelope adds The preset carries a measurement with it. pt-v14 holds all fourteen realism statistics inside their real-market bands at the certified horizon of 252 trading days. It also names five gaps where it does not hold. A Loop B result gets both. The certification lets you say the market reproduced those fourteen habits of real equities. The gaps stop you before you say more than that. Check a question against the envelope Start Loop B All pages Start > Install > Core concepts > The two loops > Running a market Use it > Agents > Scenarios > Execution cost > Checkpoints and forking > EDGAR companies > RL environment > The MCP server Trust it > Realism envelope > The metrics > Principles > Citing a run Reference > Glossary > The nine factors > Conventions > Schemas > Presets > Release notes API > Core types > Evaluate > Parameters BACK Core concepts NEXT Running a market element, and that is not a style choice. An HTML parser applies the table content model to anything it parses, and `` is not in it - so a loop written inside a table is foster-parented out of the table and its rows are left behind ungrouped. The template survives a intact because a script's contents are never parsed as markup. --> ====================================================================== # Running a market https://tradefloor.dev/running-a-market.html Build a universe, run a market, test a strategy. For most users this page is the whole library. ====================================================================== LEARN/ RUNNING A MARKET Running a market Build a universe, run a market, and do a test of a strategy. For most people this page is the whole library. import tradefloor as tf universe = tf.Universe.random(108, seed=7) macro = tf.Macro(federal_funds_rate=0.025, corporate_bond_yield=0.052, vix=16.0) engine = tf.Engine(seed=42, universe=universe, macro_state=macro) engine.run_days(252) Three things define a run A universe Which companies exist and what they are worth. Order matters, so keep it fixed. A macro state Rates, inflation and the cycle on day zero only. Every close advances the economy from there. A seed Every random number, in the order drawn. Same three inputs, same market, digit for digit. Pick who exists There are four constructors. A generated roster uses synthetic tickers, AAA to AAZ and on. The generator assigns them by position. tf.Universe.random(108, seed=7) # generated, plausible per sector tf.Universe([tf.Instrument(...), ...]) # your own roster tf.Universe.from_edgar(snapshot) # real SEC fundamentals tf.Universe.from_json(saved) # one you saved earlier One thing that will bite you: roster order is contractual Reorder the roster and you get a different market from the same seed. Sorting your tickers alphabetically upstream changes the world without meaning to. On Universe.random(20, seed=11) at sim seed 42, AAA closes day 5 at 143.03 in roster order and 134.88 reversed. Check with universe.fingerprint . Read what happened A run gives you five Arrow tables. polars, pandas, pyarrow and duckdb read them zero-copy, and the package needs none of them. Bars OHLCV per name, at the grain you ask for Truth The true value, and what drove every move Macro The economy, day by day Fills What you asked for against what you got Book The order book, level by level Bars Truth Macro Fills Book AAPL, first five days One row per name per bar. This is the table a backtest would give you, and the only one it could. Orders move the price Orders match against a simulated limit order book with price-time priority. A big order gets worse prices, because it took the levels that rested there. There is no slippage formula in the code. The one momentum signal below changes only how often it rebalances. 3 a day +37.55% 6 a day +9.79% 12 a day -27.46% pt-v14, seed 2026, Universe.random(40, seed=7), 30 days, no fees charged. The whole gap is spread and depth. One market is one sample Before you call a winner, run the comparison across twelve seeded markets. Then do a check of how often the lead holds. ranking = tf.rank( lambda: tf.reference_agents(seed=3), seeds=range(12), universe=universe, days=10, workers=4) ranking.separation("mean_reversion", "momentum") # {'wins': 9, 'losses': 3, # 'ties': 0, 'p_value': 0.1460}/ste100-writer How to read that Pooled, mean-reversion captures +0.783 against momentum's +0.259. Paired across the same twelve markets it wins 9 to 3, at p = 0.15, which is a lead the sign test does not call settled. Its lead is in the size of its wins, not in how often they come. A single seed picks the pooled leader 5 times in 12. A year from start to finish Build a universe, run a year, and read the results. Score a strategy, then run the same comparison across twelve markets. Each step prints something. import tradefloor as tf # 1. a universe and a market universe = tf.Universe.random(30, seed=11) engine = tf.Engine(seed=42, universe=universe) engine.run_days(252) # one trading year # 2. what happened, and what was actually true bars = engine.bars(grain="day") # OHLCV for every name truth = engine.truth() # true value + what drove each move # 3. score a strategy against a fresh market spec = tf.StrategySpec.momentum(lookback_days=1.0, top_k=5) scores = tf.evaluate({"momentum": spec}, seed=7, universe=universe, days=10) print(scores["momentum"].return_pct, scores["momentum"].strategy_fingerprint) # 4. the same comparison across twelve markets ranking = tf.rank(lambda: tf.reference_agents(seed=3), seeds=range(12), universe=universe, days=10, workers=4) print(ranking.separation("momentum", "mean_reversion")) OUTPUT, ILLUSTRATIVE # 3. 4.87 9f2c8d41e7b6a5309c14be7728df0a63d5e19f84c2b70a6d38ef415c9b2d7a08 # 4. {'wins': 9, 'losses': 3, 'ties': 0, 'p_value': 0.1460} Field names and shapes are real, the values are not: run it and you get your own. The measured 9-3 split above came from the published grid, and a fresh set of seeds will land somewhere else. The fingerprint is the part that matters, because it is what someone else checks your result against. Use this to rule strategies out. A strategy can die under a rate shock. Its edge can disappear when you charge it correctly for each trade. Both results are worth the five seconds of CPU time. Read this before you conclude Good results here do not predict real returns The price process comes from a known model, so a strategy that fits that model's structure will look excellent and teach you nothing. A strategy that fails here is the informative case, because it broke against a live order book under honest impact costs. Momentum can work here for a reason real markets do not supply Returns trend, because the mispricing process has a herding term with a dial on it. The shipped pt-v14 turns that dial well down: momentum_theta sits at 0.0186 and return autocorrelation at lag one reads +0.0114 against a real band of -0.08 to 0.06, so it is in band. pt-v1 ships the same knob at 0.25 and measures +0.249. Either way the dial exists and real markets have no equivalent. pt-v14: the certified panel, 30 seeds, 40 instruments, 252 days. One venue, no latency, no strategic counterparties Orders arrive instantly, there is one book per name, and you trade against a market maker and aggregate flow, never against agents that adapt to you. All pages Start > Install > Core concepts > The two loops > Running a market Use it > Agents > Scenarios > Execution cost > Checkpoints and forking > EDGAR companies > RL environment > The MCP server Trust it > Realism envelope > The metrics > Principles > Citing a run Reference > Glossary > The nine factors > Conventions > Schemas > Presets > Release notes API > Core types > Evaluate > Parameters BACK The two loops NEXT Agents element, and that is not a style choice. An HTML parser applies the table content model to anything it parses, and `` is not in it - so a loop written inside a table is foster-parented out of the table and its rows are left behind ungrouped. The template survives a intact because a script's contents are never parsed as markup. --> ====================================================================== # Agents https://tradefloor.dev/agents.html Any object with an act method can trade this market. What the agent sees, what the harness gives it, and how to compare two of them. ====================================================================== LEARN/ AGENTS Agents An agent is any object with an act method. The harness gives it an observation and takes back target weights. Three kinds of agent use this page An execution algorithm A TWAP, a VWAP, an iceberg. You write the logic in Python. A trained policy A reinforcement-learning policy through the Gymnasium environment. An LLM agent A model that reads the observation and answers with weights at each step. An LLM agent trades inside the market from here. A model that studies the market from outside uses the MCP server instead, and writes no code at all. class Mine: def act(self, obs): # target weights, one per name return {"AAA": 0.2, "AAB": -0.1} scores = tf.evaluate( {"mine": Mine()}, seed=7, universe=u, days=10) scores["mine"].return_pct scores["mine"].impact_bps Three rules of the harness You return target weights Weights in [-1, 1], one per name. The harness turns them into orders, so a policy does not learn each price range first. The market answers back Orders match against the engine's own depth. Reward is measured after the market moves, so it already carries the cost of your own footprint. The seed owns the randomness Give the same agent the same seed and universe, and you get the same run. The observation is narrow on purpose Everything in it is information available to a trader inside the simulated market. Nothing in it is something only the simulator knows. The agent sees prices the order book your position your cash the step number The agent never sees the true mispricing fair value tomorrow's prices other agents' orders The Oracle is the exception. It reads the true mispricing and declares itself, which is why it is a reference and not a competitor. step counts the whole run, not the day. A ten-day run at three decisions a day ends at step 30. Compare agents ONE MARKET EACH, NOT ONE SHARED BOOK Ten agents in one evaluate call get ten private engines, built from the same seed and roster. Each agent pays its own impact and sees nobody else's orders. That is what makes the comparison clean. It is also why there is no arena. To make agents take each other's depth, drive one engine yourself and combine their flow into each tick. Run the pair across twelve seeded markets. Then read the pooled number and the paired number against each other. Here is the measurement the docs publish. ranking = tf.rank( lambda: tf.reference_agents(seed=3), seeds=range(12), universe=u, days=10, workers=4) ranking.separation("mean_reversion", "momentum") {'wins': 9, 'losses': 3, 'ties': 0, 'p_value': 0.1460} Twelve markets, one square each mean-reversion won 9 momentum won 3 The squares show the tally, not the seed order. Which seed fell which way is in the agents docs. 3x pooled capture Mean-reversion at +0.783 against momentum at +0.259, across the grid. 0.15 sign-test p-value A 9 to 3 split on twelve paired markets. The sign test does not call that settled. 8 / 12 single-seed hit rate How often one market picks the pooled leader. The other four crown momentum three times and buy-and-hold once. Pooled, mean-reversion leads by three times. Paired, 9 to 3 at p = 0.15 is not a separation. Both numbers are true and they answer different questions: how much it won by, and how often. Report the pair. The shipped baselines Five reference agents ship. Each one is also a StrategySpec, so you can cite it as data. hold Buy the roster on day one and do nothing. The number every other agent must beat. random Weights from its own seed, which lives in the spec. The noise floor. momentum Buys what rose. Pools at +0.259 on the published grid. Read the herding dial caveat below before you trust it. mean_reversion Buys what fell. Pools at +0.783 on the published grid, and it beats the Oracle on 5 of the 12 markets. oracle Reads the true mispricing, and gets no extra capital for it. A reference point, not a ceiling: agents out-earn it on 5 of 12 markets. Read this before you conclude Momentum can work here for a reason real markets do not supply Returns trend, because the mispricing process has a herding term with a dial on it. pt-v14 turns that dial well down. momentum_theta sits at 0.0186 and return autocorrelation at lag one reads +0.0114, against a real band of -0.08 to 0.06. pt-v1 ships the same knob at 0.25 and measures +0.249. The dial exists either way, and real markets have no equivalent. No counterparty adapts to you You trade against a market maker and aggregate flow. Orders arrive instantly and there is one book per name. Nothing in the market learns your pattern and front-runs it. All pages Start > Install > Core concepts > The two loops > Running a market Use it > Agents > Scenarios > Execution cost > Checkpoints and forking > EDGAR companies > RL environment > The MCP server Trust it > Realism envelope > The metrics > Principles > Citing a run Reference > Glossary > The nine factors > Conventions > Schemas > Presets > Release notes API > Core types > Evaluate > Parameters BACK Running a market NEXT Scenarios element, and that is not a style choice. An HTML parser applies the table content model to anything it parses, and `` is not in it - so a loop written inside a table is foster-parented out of the table and its rows are left behind ungrouped. The template survives a intact because a script's contents are never parsed as markup. --> ====================================================================== # Scenarios https://tradefloor.dev/scenarios.html Drive a macro path through the market instead of letting the economy find its own, and what a scenario result may claim. ====================================================================== LEARN/ SCENARIOS Scenarios A scenario drives the macro path. Without one, the economy finds its own path from day zero. shock = tf.Scenario.rate_shock( start=0.025, end=0.05, over=15) tf.evaluate(agents, seed=7, universe=u, days=20, scenario=shock) # or move one field yourself engine.pin_macro( corporate_bond_yield=0.09) Two ways to move the economy A scenario Drives a path over many days. Use it for a crisis, a hiking cycle, or a real historical window. A pin Sets one field one time. The chain then continues from the pinned value, so a pin does not freeze the field. Which field you drive decides the answer Measured on real 2022 data over six seeds, against a real S&P 500 that fell 20.0%. The lever is inflation, not the policy rate. Real S&P 500, 2022 what actually happened -20.0% Drive inflation_rate the published CPI path -23.3% Drive federal_funds_rate the real seven-hike path -13.1% No scenario at all drift and nothing more -12.6% Inflation works because it steers the central bank's own reaction into the corporate bond yield. Do not pin corporate_bond_yield when you do this. If you pin it, you cut the channel that the inflation path is using. Detect a scenario, do not size one The expected response is calibrated. The spread around it is not, and that is the whole gap. The size is right VIX -0.00461 vs -0.00500 Credit yield -8.106 vs -7.445 Valuation +1.226 vs +1.272 OLS slope of daily return on each driver, model against real Apple, over the same 504 sessions. The spread is too wide 1.565x Residual dispersion over the driven window, against real. One run therefore understates how much of its own move came from the scenario. So the envelope lets you detect a scenario's effect. It forbids you to size one from a single run. An event study agrees on sign two times out of six Over the five sessions after each of six dated 2020-21 events, the model agrees with real Apple on direction twice. The two that agree are the two the macro path carries. The Fed's intermeeting cut of 3 March 2020 goes the wrong way, +9.9% against a real -1.4%, because an announcement channel is absent. The vaccine result and Omicron are single-name news, which a macro path can not know. The endogenous economy can not reach its own crisis regimes Left alone, inflation peaks at 4.0% on every seed over five years. Real US CPI peaked at 9.0% in June 2022. The central bank has a crisis cadence that pulls its next meeting in, and a default run can not reach it. So an inflation regime or a policy crisis must come from a scenario. A volatility crisis does not: on pt-v10 the preset's own VIX crossed its crisis threshold on 10.2% of days against a real 12.5%. All pages Start > Install > Core concepts > The two loops > Running a market Use it > Agents > Scenarios > Execution cost > Checkpoints and forking > EDGAR companies > RL environment > The MCP server Trust it > Realism envelope > The metrics > Principles > Citing a run Reference > Glossary > The nine factors > Conventions > Schemas > Presets > Release notes API > Core types > Evaluate > Parameters BACK Agents NEXT Execution cost element, and that is not a style choice. An HTML parser applies the table content model to anything it parses, and `` is not in it - so a loop written inside a table is foster-parented out of the table and its rows are left behind ungrouped. The template survives a intact because a script's contents are never parsed as markup. --> ====================================================================== # Execution cost https://tradefloor.dev/execution-cost.html Run the same seed with and without your orders, so every fill is priced against the market where you never traded. ====================================================================== LEARN/ EXECUTION COST Execution cost Every TCA vendor approximates one missing number: what the price would have done if you had never traded. Here you can run that world. ex = tf.tca.analyse( my_algo, seed=42, universe=u, days=5) ex.shortfall_bps() # what it cost ex.by_step() # where ex.partial_fills() # asked vs got Two runs, one seed The run with your orders Your fills, at the prices the book gave you. The run without them The same seed, the same world, and no trades from you. Subtract one from the other and the difference is your footprint. Where the cost comes from There is no slippage formula in the code. A big order pays more because it takes the levels that rest above it. Below is one market buy for 175 shares, replayed from the engine's own reference program. The book before the order ask 101.00 50 ask 102.00 110 ask 103.00 120 bid 99.00 100 bid 99.00 50 bid 99.00 25 What 175 shares cost 50 @ 101.00 from bob 110 @ 102.00 from mm 15 @ 103.00 from mm 101.8000 Average fill, against 101.00 on screen when the order arrived. That is 79 basis points, paid to depth. The number nobody else can measure Arrival price, VWAP and fitted impact models all stand in for a counterfactual that real data can not supply. Round-trip shortfall measured over eight simulation seeds: -17.72 bps 0 +2.03 bps The range crosses zero, so a round trip can end up ahead. This happens when the market moves your way during the order. It is the reason one run tells you nothing about your algorithm. One known boundary: the fear gauge leaks VIX drives the market factor's variance, so the market prices fear of your flow. Names that you never touched can move a few basis points through that channel. That is impact, not an artefact of the measurement. If you need the subtraction to be exact, pin VIX in both worlds and the two runs match byte for byte. All pages Start > Install > Core concepts > The two loops > Running a market Use it > Agents > Scenarios > Execution cost > Checkpoints and forking > EDGAR companies > RL environment > The MCP server Trust it > Realism envelope > The metrics > Principles > Citing a run Reference > Glossary > The nine factors > Conventions > Schemas > Presets > Release notes API > Core types > Evaluate > Parameters BACK Scenarios NEXT Checkpoints and forking element, and that is not a style choice. An HTML parser applies the table content model to anything it parses, and `` is not in it - so a loop written inside a table is foster-parented out of the table and its rows are left behind ungrouped. The template survives a intact because a script's contents are never parsed as markup. --> ====================================================================== # Checkpoints and forking https://tradefloor.dev/checkpoints.html Run to day sixty, then ask two questions of the same market, with everything before the fork identical rather than statistically similar. ====================================================================== USE IT/ CHECKPOINTS AND FORKING Checkpoints and forking Run to day sixty, then ask two questions of the same market, with everything before the fork identical rather than statistically similar. mark = tf.Checkpoint.of(engine, universe=universe, seed=42) calm, hiked = mark.branch(2) # two engines, identical up to day 60 hiked.pin_macro(corporate_bond_yield=0.09) # calm and hiked now diverge only from here Two mechanisms for different jobs Cost Survives the process tf.branch(engine, 2, ...) < 1 ms no Checkpoint.resume() 2.7 s yes branch copies engine state, every column plus the generator position, in constant time. Checkpoint replays the order log, three orders of magnitude slower, and is what you want when the fork has to outlive the process. Cite the log in a published result, since that is what someone else can re-run. Both figures are for a sixty-day, forty-instrument run; read them as an order of magnitude, since replay cost scales with the order log. Why it refuses instead of loading wrong A Checkpoint records the universe fingerprint and refuses to load against a roster that changed, because restoring across two same-named universes gives right prices and wrong fair values: plausible everywhere visible, wrong in the one place that drives everything. What restore_state cannot catch The low-level restore_state holds no fundamentals, so it can only verify roster order, size, and the model fingerprint the snapshot was taken under. Restoring a pt-v14 snapshot into a pt-v1 engine raises a ValidationError naming both. What it cannot see is a roster whose tickers still match but whose fundamentals do not - prefer branch and Checkpoint, which check that. All pages Start > Install > Core concepts > The two loops > Running a market Use it > Agents > Scenarios > Execution cost > Checkpoints and forking > EDGAR companies > RL environment > The MCP server Trust it > Realism envelope > The metrics > Principles > Citing a run Reference > Glossary > The nine factors > Conventions > Schemas > Presets > Release notes API > Core types > Evaluate > Parameters BACK Execution cost NEXT Real companies from EDGAR element, and that is not a style choice. An HTML parser applies the table content model to anything it parses, and `` is not in it - so a loop written inside a table is foster-parented out of the table and its rows are left behind ungrouped. The template survives a intact because a script's contents are never parsed as markup. --> ====================================================================== # Real companies from EDGAR https://tradefloor.dev/edgar.html Seed a universe from real SEC filings: real valuation dispersion, real sector weights, synthetic price paths. ====================================================================== USE IT/ REAL COMPANIES FROM EDGAR Real companies from EDGAR Seed a universe from real SEC filings. Real valuation dispersion, real sector weights, loss-makers in realistic proportion. The price path stays synthetic. snap = tf.edgar.fetch(as_of="2024-06-30", limit=100, user_agent="Jane Roe jane@example.org") snap.save("edgar-2024h1.json") # the artifact, hashed and citable universe = tf.Universe.from_edgar(snap, federal_funds_rate=0.03) user_agent must carry a contact address - the SEC's fair-access policy asks for one, and this library will not send a fabricated one for you. Save the snapshot, cite that EDGAR is not append-only: the same request returns different numbers next year. A snapshot is hashed and serialisable, so cite the file, not the query. Two ways to rank, two different biases rank_by="equity" (default) EDGAR carries no market cap, so ranking by shareholders' equity skews balance-sheet-heavy. Measured on the live SEC for CY2025, the top 150 by equity came back 27% financial services and 17% technology, against roughly 13% and 30% for the S&P 500, with five banks in the top ten. rank_by="public_float" Ranks by the one market-derived number EDGAR has, producing a roster that resembles a real index. Costs: stale by six to eighteen months, and understates founder-controlled companies since float excludes what insiders hold. Neither is a market-cap ranking, because EDGAR has no prices. For that, set initial_price yourself from a market data source. And this loads fundamentals, not behaviour: a loaded ticker gives you a stock with that company's fundamentals under this model's assumptions, not that company's volatility or microstructure. All pages Start > Install > Core concepts > The two loops > Running a market Use it > Agents > Scenarios > Execution cost > Checkpoints and forking > EDGAR companies > RL environment > The MCP server Trust it > Realism envelope > The metrics > Principles > Citing a run Reference > Glossary > The nine factors > Conventions > Schemas > Presets > Release notes API > Core types > Evaluate > Parameters BACK Checkpoints and forking NEXT RL environment element, and that is not a style choice. An HTML parser applies the table content model to anything it parses, and `` is not in it - so a loop written inside a table is foster-parented out of the table and its rows are left behind ungrouped. The template survives a intact because a script's contents are never parsed as markup. --> ====================================================================== # RL environment https://tradefloor.dev/rl-environment.html A Gymnasium environment where the market answers back, so what a policy learns about size is not fiction. ====================================================================== LEARN/ RL ENVIRONMENT RL environment A Gymnasium environment where the market answers back. Every seed is a fresh independent episode. from tradefloor.gym import TradingEnv env = TradingEnv( universe=u, seed=42, days=20) obs, info = env.reset(seed=42) obs, reward, terminated, truncated, info = \ env.step(action) Three things to know It passes env_checker Gymnasium's own conformance check, so your existing training code works without a wrapper. Actions are target weights One weight per name, in [-1, 1]. The harness turns them into orders against the book. Reward includes your footprint It is measured after the market moves, so the cost of your own size is already in it. Why a tape can not teach size On historical data A policy can buy a million shares of a name that trades ten thousand a day. The tape carries on exactly as it did in 2019. What the policy learns about size is then fiction. Here The order takes the levels above it and the price moves. Reward is measured after that move, so the cost of the footprint is already in the number the policy optimises. Actions are target weights in [-1, 1]. A policy does not spend its first million steps to learn that one name trades at 40 and another at 400. One seed, one episode Training needs one independent episode per seed. A seed gives you one, and there is no limit on how many you draw. Two things follow. Variance is measurable Hold the universe fixed and change the simulation seed. The spread across seeds tells you how much of a score was the market rather than the policy. A result stays checkable Each episode is defined by package version, preset, universe fingerprint and seed. Someone else can replay the exact episode your policy trained on. for seed in range(2048): obs, info = env.reset(seed=seed) # a new market, same rules done = False while not done: action = policy(obs) obs, reward, terminated, truncated, info = env.step(action) done = terminated or truncated Read this before you trust a policy A policy can learn the model instead of the market The price process comes from a known model. A policy is very good at finding the structure of that model. A high score can mean it found the herding term rather than a real edge. Test the trained policy against the checks on the agents page, and read the envelope before you make a claim about a real market. Volatility memory does not transfer past a month The model forgets a volatile period faster than a real market. The log-log slope over lags 1 to 20 reads -0.953 against a real -0.436. A policy that sizes on a one-month volatility estimate is learning a decay shape that real markets do not have. All pages Start > Install > Core concepts > The two loops > Running a market Use it > Agents > Scenarios > Execution cost > Checkpoints and forking > EDGAR companies > RL environment > The MCP server Trust it > Realism envelope > The metrics > Principles > Citing a run Reference > Glossary > The nine factors > Conventions > Schemas > Presets > Release notes API > Core types > Evaluate > Parameters BACK Real companies from EDGAR NEXT The MCP server element, and that is not a style choice. An HTML parser applies the table content model to anything it parses, and `` is not in it - so a loop written inside a table is foster-parented out of the table and its rows are left behind ungrouped. The template survives a intact because a script's contents are never parsed as markup. --> ====================================================================== # The MCP server https://tradefloor.dev/mcp.html Eleven tools over the simulator, so a coding agent can ask whether a strategy beats buy-and-hold here, and whether the difference is real. ====================================================================== LEARN/ THE MCP SERVER The MCP server Eleven tools over the simulator. A coding agent can ask whether momentum beats buy-and-hold here, and whether the difference is real. pip install "tradefloor[mcp]" claude mcp add tradefloor -- tradefloor-mcp # then ask it a question in plain words Inside the market, or outside it An LLM agent trades The model is inside the market. It sends orders and the book answers. An MCP client studies The model is outside the market. It composes a question, reads the answer, and writes no code. Two places a model can sit Both are supported and they answer different questions. Pick by what you want out. OUTSIDE The model studies the market It writes no code Strategies, universes and scenarios arrive as data. There is no path from a tool argument to code execution. It can not move a price No orders reach the book, so nothing the model asks changes the market it is asking about. It gets the caveats Each result carries computed caveats and provenance, so a summary can not lose them. It is cheap to be wrong check_envelope answers before you spend anything, and refuses a question the model can not support. this page INSIDE The model trades the market It sends orders The model reads an observation and answers with target weights at each step. It pays for size Orders match against the engine's own depth, so the reward already carries the cost of the footprint. It needs a harness Any object with an act method works. The model call sits inside that method. It is slower and it costs One model call per decision. A 20-day run at three decisions a day is 60 calls. the agents page # outside: the model asks check_envelope(days=252, roster="sp_like") rank_strategies( specs=[momentum, hold], seeds=12) # inside: the model decides class LLMTrader: def act(self, obs): reply = ask_model(obs) return reply["weights"] A model that trades pays for its own orders, so its score carries its footprint. A model that studies pays nothing and can not move a price. Neither one adapts to another agent: you trade against a market maker and aggregate flow. The eleven tools describe_simulator What is this, what is it certified to reproduce, what can it not do check_envelope Is my question inside the certified envelope, before I spend anything validate_strategy Is this spec well-formed, and what is its fingerprint build_universe What roster shall I run against: generated, concentrated, or hand-authored build_scenario What macro path shall I run through, and what does it look like day by day evaluate_strategies How do these strategies do on one identical market rank_strategies Which is really better, across seeds, with a paired sign test run_stress_scenario What a shock does, always against the same market unshocked explain_price_move Why did this price move, via the nine factors that sum to it start_job Run something too slow to answer inline, including a full certified year check_job Is it done, and what did it find Strategies are data, never code A client composes a strategy as JSON. There is no path from a tool argument to code execution. { "spec_version": 1, "signal": {"kind": "momentum", "lookback_days": 1.0}, "portfolio": {"gross": 1.0, "top_k": 5}, "execution": {"cadence": "step", "max_participation": 0.02}, "seed": null } Universes and scenarios are data in the same way. Each result carries a fingerprint, so a second client can check the first one. The limits, and why they exist 60 max_days, direct An inline call must answer while the client waits. 252 max_days, job The certified horizon, and no further. 120 max_universe Keeps one question inside one machine. 8 max_strategies Enough to compare, few enough to read. 12 max_seeds Enough for a paired sign test to mean something. A direct call answers inline and caps at 60 days. A background job runs to 252 days, which is the certified horizon. Ask for more than that and the server refuses rather than returns a number it can not support. Every result carries its own caveats A model that summarises a result has the tool output and nothing else. Without the caveats in the payload it will report return_pct: 88.7 as "the strategy made 88.7%". So each result ships with computed caveats and full provenance: package version, preset, seed, universe fingerprint, and the strategy fingerprint. One seed is not an answer A single market is one sample. rank_strategies runs many seeds and compares them with a paired sign test. On the twelve-market grid the docs measure, mean-reversion wins 9 to 3 at p = 0.15. One seed picks the pooled leader eight times in twelve. All pages Back to the front door Start > Install > Running a market > Core concepts > The two loops Use it > Agents > Scenarios > Execution cost > RL environment Trust it > Realism envelope > The metrics > Principles > Citing a run Reference > Glossary > Conventions > Schemas > Presets API > Core types > Evaluate > Parameters > MCP server All pages Start > Install > Core concepts > The two loops > Running a market Use it > Agents > Scenarios > Execution cost > Checkpoints and forking > EDGAR companies > RL environment > The MCP server Trust it > Realism envelope > The metrics > Principles > Citing a run Reference > Glossary > The nine factors > Conventions > Schemas > Presets > Release notes API > Core types > Evaluate > Parameters BACK RL environment NEXT Realism envelope element, and that is not a style choice. An HTML parser applies the table content model to anything it parses, and `` is not in it - so a loop written inside a table is foster-parented out of the table and its rows are left behind ungrouped. The template survives a intact because a script's contents are never parsed as markup. --> ====================================================================== # Realism envelope https://tradefloor.dev/realism-envelope.html Fourteen statistics, a certified horizon of 252 days, and five named gaps. What this market reproduces, and what it forbids you to claim. ====================================================================== LEARN/ THE REALISM ENVELOPE The realism envelope There is no realism score. There is a panel of fourteen measurements, a horizon they hold to, and five named gaps. 14 statistics in band at one year 252 trading days certified 30 seeds behind the panel 5 named gaps What the fourteen measure Each row is one habit of a real market. The bar is the range that real equities hold. The dot is what the shipped preset measured at 252 days. A dot inside the bar means the model got that habit correct. annualised_vol_pct how much prices move in a year 28.3103 excess_kurtosis how fat the tails are 10.0043 return_acf1 does yesterday predict today 0.0114 abs_return_acf1 does a wild day follow a wild day 0.0769 abs_return_acf5 the same, one week apart 0.0305 abs_return_acf20 the same, one month apart 0.0096 cross_sectional_corr how much names move together 0.2616 volume_abs_return_corr do big moves come with volume 0.5108 leverage_effect do falls raise volatility -0.0258 volume_change_acf1 does volume mean-revert -0.2794 corr_asymmetry do names couple more when falling -0.0018 corr_asymmetry_lagged the same, one day later -0.0327 sector_excess_corr do industries move together 0.2081 corr_persistence_acf1 does correlation stay high after a panic 0.1771 band floor band ceiling All fourteen dots sit inside their bands. That is a market with the correct volatility, the correct tails and the correct co-movement. Industries move together more than strangers do. Correlation stays high after a panic, volume behaves, and the model produces its own volatility episodes. Why the horizon stops at 252 days The count is not the reason At 504 days pt-v14 also holds all fourteen, against bands that were re-derived for that window. pt-v3 held seven there. pt-v10 held thirteen. Two things keep the certification at one year. Annualised volatility reads 33.89 against a band that ends at 34.0, which is thin headroom. And certified means measured at 252 days on thirty seeds, which is what this module measures. Nothing runs away Annualised volatility, measured year by year over ten years on twenty seeds: 31.5 35.6 30.2 33.5 33.0 33.1 31.3 32.4 32.4 31.6 Flat, with the year-to-year variation that a real market has. At 2520 days the panel holds ten of the fourteen, graded against the 504-day bands, which are the wrong ruler for a ten-year window. The five gaps Each gap names what it forbids. tf.envelope.check() refuses a question that falls outside one, so this is a function rather than a paragraph. horizon the certified horizon is 252 days FORBIDS multi-year backtests, and anything keyed on volatility dynamics beyond one year MEASURED At 504 days pt-v14 holds all fourteen against re-derived bands. At 2520 days it holds ten of fourteen against the 504-day bands. abs_return_acf1 abs_return_acf5 return_acf1 excess_kurtosis decay-shape volatility memory decays exponentially, not hyperbolically FORBIDS strategies whose edge depends on volatility memory beyond about lag 20 -- vol targeting and risk parity on a one-month or longer estimate MEASURED The log-log slope over lags 1 to 20 reads -0.953 against a real -0.436. The curve turns negative by lag 30, where real markets stay weakly positive to lag 60. abs_return_acf20 scenario-magnitude a scenario's size is right on average and unreliable in one run FORBIDS sizing a scenario's impact rather than detecting it MEASURED The steady-state lever reads 6.04x against a real 6.16x. Residual dispersion over the driven window runs 1.565 times real, which is the defect. no single statistic carries this one macro-range the endogenous macro state cannot reach its own crisis regimes FORBIDS studying inflation regimes or policy crises from the endogenous economy alone MEASURED Endogenous inflation peaks at 4.0% on every seed over five years. Real US CPI peaked at 9.0% in June 2022. no single statistic carries this one roster-concentration a concentrated roster holds at one year and comes apart at two FORBIDS inheriting this envelope for a sector-concentrated roster BEYOND one year -- at the certified horizon it now transfers MEASURED At 252 days every roster shape holds all fourteen. At 504 days an S&P-like roster holds thirteen and a technology-heavy roster holds eleven. cross_sectional_corr annualised_vol_pct corr_persistence_acf1 What a result may claim You can say this The strategy behaved this way in a market that reproduces the fourteen habits of real equities over a trading year. Here is the manifest, so you can run it again. You can not say this The strategy will earn this in a real market. The price process comes from a known model, so a strategy that fits the structure of that model looks excellent and teaches you nothing. All pages Start > Install > Core concepts > The two loops > Running a market Use it > Agents > Scenarios > Execution cost > Checkpoints and forking > EDGAR companies > RL environment > The MCP server Trust it > Realism envelope > The metrics > Principles > Citing a run Reference > Glossary > The nine factors > Conventions > Schemas > Presets > Release notes API > Core types > Evaluate > Parameters BACK The MCP server NEXT The metrics element, and that is not a style choice. An HTML parser applies the table content model to anything it parses, and `` is not in it - so a loop written inside a table is foster-parented out of the table and its rows are left behind ungrouped. The template survives a intact because a script's contents are never parsed as markup. --> ====================================================================== # The metrics https://tradefloor.dev/metrics.html What each of the fourteen realism statistics measures, where its band came from, and how wide the spread around it is. ====================================================================== LEARN/ THE METRICS The metrics Fourteen statistics. What each one measures, why it matters to a strategy, and where its band came from. Where every band came from Ten consecutive 252-day windows of 40 US large caps, 2015 to 2025, measured with this module's own estimators. Nine windows set each band. The tenth, the window that straddles the COVID crash, is reported separately as that band's crisis window. So the panel is a claim about a typical year. Crisis behaviour is measured under pinned scenarios instead. The fourteen all fourteen one series clustering many names annualised_vol_pct How violent the market is 15.00 28.3103 36.00 It sets the scale of every gain and every loss. Too high and every Sharpe ratio you measure is depressed and every stop is hit too often. Too low and risk looks free. REAL WINDOWS 18.3 / 25.9 / 30.7 excess_kurtosis How fat the tails are 1.60 10.0043 41.00 How often a day lands far from typical. Zero is a normal distribution, and real markets are strongly positive, because crashes and melt-ups happen far more often than a bell curve allows. This one decides whether tail risk means anything in your results. REAL WINDOWS 5.6 / 11.1 / 36.7 return_acf1 Does yesterday predict today -0.08 0.0114 0.06 Near zero in a real market. A positive value is free money for a momentum rule, so this is the row that decides whether a trend result here is a finding or an artifact. REAL WINDOWS band -0.08 to 0.06 abs_return_acf1 Does a wild day follow a wild day 0.02 0.0769 0.22 Volatility clustering at one day. Present in every real market, and the reason a calm week is a poor forecast of the next one. REAL WINDOWS band 0.02 to 0.22 abs_return_acf5 The same, one week apart 0.01 0.0305 0.12 Clustering at lag five. Whether a volatile spell persists long enough for a weekly risk model to see it. REAL WINDOWS band 0.02 to 0.09 abs_return_acf20 The same, one month apart -0.04 0.0096 0.08 Clustering at lag twenty. This is where the decay-shape gap lives: the model holds the level here but reaches it with the wrong curve. REAL WINDOWS band -0.04 to 0.08 cross_sectional_corr How much names move together 0.08 0.2616 0.56 The average pairwise correlation. It decides whether a diversified book is actually diversified, and it is the row that concentration moves first. REAL WINDOWS band 0.08 to 0.56 volume_abs_return_corr Do big moves come with volume 0.46 0.5108 0.66 Volume and absolute return move together in every real market. An execution algorithm that assumes constant depth is wrong in exactly the moments that matter. REAL WINDOWS band 0.46 to 0.66 leverage_effect Do falls raise volatility -0.16 -0.0258 0.00 Negative returns raise future volatility more than positive ones do. A symmetric model gets the shape of a drawdown wrong. REAL WINDOWS band -0.16 to 0.00 volume_change_acf1 Does volume mean-revert -0.32 -0.2794 -0.20 Negative in real markets: a heavy day is followed by a lighter one. Participation caps read against ADV depend on it. REAL WINDOWS band -0.32 to -0.20 corr_asymmetry Do names couple more when falling -0.25 -0.0018 0.45 Correlation rises in a selloff, which is when diversification is most wanted and least available. REAL WINDOWS band -0.25 to 0.45 corr_asymmetry_lagged The same, one day later -0.20 -0.0327 0.55 Whether that coupling survives into the next session rather than being a one-day artifact. REAL WINDOWS band -0.20 to 0.55 sector_excess_corr Do industries move together 0.11 0.2081 0.23 How much more a name moves with its own industry than with the market. Undefined on a single-sector roster, because those are the same thing. REAL WINDOWS band 0.11 to 0.23 corr_persistence_acf1 Does correlation stay high after a panic -0.19 0.1771 0.54 Correlation does not snap back the day after a crash. The single row pt-v14 misses on held-out seeds. REAL WINDOWS band -0.19 to 0.54 What one run actually shows you Every banded number is a median across seeds. The spread around it is wide enough to change the answer, and tradefloor.envelope.intervals() reports it per statistic. median The point estimate a single panel would report. low, high The actual minimum and maximum across seeds. p10, p90 The tenth and ninetieth percentiles. sd Across-seed standard deviation, measured on these panels. shipped_sd facts.SEED_SD, measured once at the baseline. distance Band distance, and sd_out gives that distance in units of noise. extremes_straddle The minimum or maximum crosses a band edge. typical_straddles The p10 to p90 range crosses a band edge. This is the one to read. Read typical_straddles, not extremes_straddle extremes_straddle fires when one seed of thirty crossed an edge. That is close to expected, so it is information rather than a finding. typical_straddles says the middle eighty percent crosses. Then a reader running one seed is likely, not merely able, to measure out of band on a statistic whose median sits well inside. Measured on pt-v10 over thirty seeds, nine of the fourteen straddle by that test. That is the previous era's dispersion. pt-v14 moved the medians and the spread around them has not been re-measured. Three bands are not raw measurements A band is what this measurement returns on real data, with three documented exceptions. Each names itself on its own provenance row. annualised_vol_pct EXTENDED OUTWARD The ceiling moves from the windows' 34 to 36, so the band admits a slightly more violent year than the sample held. abs_return_acf1 CLAMPED INWARD The floor is held at +0.02. Zero volatility clustering appears in no retrieved source and no observed window. leverage_effect CLAMPED INWARD The ceiling is held at 0.00. A top above zero would certify a reversed leverage effect as real-market behaviour. All pages Start > Install > Core concepts > The two loops > Running a market Use it > Agents > Scenarios > Execution cost > Checkpoints and forking > EDGAR companies > RL environment > The MCP server Trust it > Realism envelope > The metrics > Principles > Citing a run Reference > Glossary > The nine factors > Conventions > Schemas > Presets > Release notes API > Core types > Evaluate > Parameters BACK Realism envelope NEXT Principles element, and that is not a style choice. An HTML parser applies the table content model to anything it parses, and `` is not in it - so a loop written inside a table is foster-parented out of the table and its rows are left behind ungrouped. The template survives a intact because a script's contents are never parsed as markup. --> ====================================================================== # Principles https://tradefloor.dev/principles.html Twelve rules this project works under, six of them bought with a specific loss. ====================================================================== LEARN/ PRINCIPLES Principles Twelve rules this project works under. Six were bought with a specific loss, and those say which one. 01 Determinism is the product Anything that would make two runs differ is a correctness bug rather than a performance trade. IN PRACTICE The crate ships its own exp, log, pow, sin and cos. Every release builds five targets, runs one fixed simulation in each and compares digests. 02 Measure rather than assert Every claim in the documentation is a number that was produced by running something. IN PRACTICE tools/remeasure re-runs the stated method behind each published figure and reports every number the current build no longer produces. 03 State the limitation next to the capability Never in a footnote. IN PRACTICE The realism page publishes fourteen statistics inside their bands and five named gaps on the same page, and each gap ends in a rule about what it forbids. 04 A changed model has a different name Changing a coefficient is allowed. Reporting the result under the shipped preset name is not. IN PRACTICE Change any settable coefficient and the fingerprint reads custom-7f290e34 rather than pt-v1. 05 Whatever sits outside the objective is free A scalar objective collapses everything you care about into one number, and the optimiser sells whatever is not in that number. IN PRACTICE Atlas exists for this reason: it surveys the parameter space rather than optimising one score. THE FAILURE Six consecutive calibration searches were rejected, each for the same underlying reason. Fixing one blind spot moved the selling to the next one. 06 Discovery seeds and validation seeds must be disjoint A result found on a set of seeds has to be re-measured on seeds it never saw. IN PRACTICE atlas.Survey.confirm re-measures on seed blocks disjoint from the survey and refuses to run on overlapping seeds. THE FAILURE A candidate was declared shippable on a 13% improvement. On fresh seeds it read +0.1297 where it was found, and -0.0315, +0.0209 and +0.0233 elsewhere, reversing sign once. Discovery and validation had used the same thirty seeds, so re-measuring reproduced the same fluctuation exactly. 07 The ruler must match the horizon A statistic measured over 504 days is scored against bands re-derived at 504 days, not against the 252-day bands. IN PRACTICE loss.dual_horizon_loss scores L_real at both horizons and refuses to run on one. 08 A number invites scepticism and a sentence does not A scalar travels and a caveat does not. IN PRACTICE The library publishes fourteen statistics with bands rather than one realism score, and a summary says outright that an additivity check failed rather than quietly presenting an unchecked number. 09 One seed is not an answer One market is one sample, and the cost of believing it has been measured. IN PRACTICE On the twelve-market grid a single seed picks the pooled leader eight times in twelve, and what a seed says momentum is worth runs from -0.503 to +0.909 depending only on which market it drew. The paired sign test is reported beside the pooled number. 10 Strategies are data, never code A StrategySpec is declarative, versioned and hashable. IN PRACTICE That is what lets one travel through the MCP server without executing anything a caller sent. 11 Absence differs from zero, and invalid input raises Nothing is silently clamped, because a simulator that repairs your inputs gives you a market you did not specify. IN PRACTICE corporate_bond_yield=None falls through to the default, and where a column can not carry None, absence is NaN, because zero is a real rate. 12 The reader's job leads A reader who can not tell which loop they are in can not tell which pages are theirs. IN PRACTICE The docs are racked by task rather than by module, and the two loops page exists to place the reader in one of them. What these rules refuse No realism score A scalar travels and a caveat does not. There is a panel of fourteen measurements and a list of gaps instead. No invented evidence No testimonials, customers, competitor benchmarks, pricing or adoption numbers exist. None may be invented. No silent trajectory change A preset is frozen once published. Tuning it would rewrite results that other people already cited. The rule that shapes the rest Any change to the simulated trajectory breaks the contract A market that runs differently from the same seed invalidates every published result that cited it. So a coefficient change arrives as a new preset with a new name, and every preset from pt-v1 onward stays selectable forever. PRINCIPLE 02, TESTED ON ITSELF The one that was wrong twice The realism page called volume_change_acf1 structurally unreachable twice. Both claims carried correct numbers, and both named the wrong mechanism. The first claim Reaching its band costs volume_abs_return_corr, because the common log-volume state adds volume variance unrelated to any name\u0027s own moves. The trade was real, and it was priced on the pt-v3 era base. On the pt-v10 base both one-year bands became reachable together, in a window about 0.03 wide in the innovation sigma. The second claim The two-year half needed more volume memory. What closed it was volume_move_cap, a hard-coded 4.0 in tick.rs that saturated a name\u0027s volume response at a 4% daily move. Lifting it to 12.0 reads -0.2656 at 252 days and -0.2572 at 504, inside both bands. Every number in both claims was right. The mechanism each asserted was wrong, and the second was wrong in the same direction as the first: it described the limit as a property of the model rather than of the value one constant happened to hold. All pages Start > Install > Core concepts > The two loops > Running a market Use it > Agents > Scenarios > Execution cost > Checkpoints and forking > EDGAR companies > RL environment > The MCP server Trust it > Realism envelope > The metrics > Principles > Citing a run Reference > Glossary > The nine factors > Conventions > Schemas > Presets > Release notes API > Core types > Evaluate > Parameters BACK The metrics NEXT Citing a run element, and that is not a style choice. An HTML parser applies the table content model to anything it parses, and `` is not in it - so a loop written inside a table is foster-parented out of the table and its rows are left behind ungrouped. The template survives a intact because a script's contents are never parsed as markup. --> ====================================================================== # Citing a run https://tradefloor.dev/citing.html A citation identifies software. A manifest identifies a run. What to record so someone else can reproduce your result. ====================================================================== LEARN/ CITING A RUN Citing a run A citation identifies software. It does not identify a run. A manifest does. Formerly pretium. Versions through 0.4.3 were published under that name and remain installable forever; cite them as pretium at their exact version and they replay bit for bit. From 0.5.0 the library is tradefloor, with the same version line and the same known-answer digest. Not enough on its own "We used tradefloor." That names the software and says nothing of the market you ran. Two people can obey it, get different numbers, and both be correct. CITATION.cff covers this half Enough to reproduce A RunManifest carries the seven things that fix a trajectory, plus the digest it should produce. reproduce() refuses on a mismatch The seven things that fix a trajectory package version Pins the implementation. 0.3.0 and 0.2.0 can differ even on the same preset. model preset Pins the coefficients. Name it, because the default moves between releases. seed Pins every random number the run draws. universe fingerprint A sha256 over the roster in order, so a re-sorted roster fails the check instead of passing quietly. macro conditions The state on day zero, before the economy starts advancing itself. scenario The driven macro path, if you drove one. strategy fingerprint A sha256 over the spec. This is the one to quote in a sentence. Leave one out and the run is not reproducible. Record all seven and someone with the package and nothing else of yours can replay it. Write it down, then check it manifest = tf.RunManifest.of( engine, spec, seed=7) manifest.to_json("study.json") # somewhere else, later saved = tf.RunManifest.from_json( open("study.json").read()) tf.reproduce(saved) # or it raises Why it raises instead of a warning A manifest that quietly reproduced a different market would be worse than no manifest. So reproduce() compares the digest it got against the digest the manifest expected, and names the field that differs. In most cases it is the preset. pt-v14 became the default on 2026-08-28, so a manifest written before that date and replayed after it fails unless it named its preset. What to put in a paper Momentum, top_k 5, one-day lookback, scored on tradefloor 0.4.0 under preset pt-v14, universe fingerprint 4c1e9f77a3b8d502, seeds 0 to 11, 252 days. Strategy fingerprint 9f2c8d41e7b6a530. Manifest and order log attached. Illustrative. The fingerprints in that sentence are placeholders, and yours will differ. Say this The strategy behaved this way on this preset, at this horizon, in a market that holds the fourteen realism statistics. Here is the manifest. Not this The strategy earns this. A result here is a statement about a model market, and the envelope names five gaps that bound it further. All pages Start > Install > Core concepts > The two loops > Running a market Use it > Agents > Scenarios > Execution cost > Checkpoints and forking > EDGAR companies > RL environment > The MCP server Trust it > Realism envelope > The metrics > Principles > Citing a run Reference > Glossary > The nine factors > Conventions > Schemas > Presets > Release notes API > Core types > Evaluate > Parameters BACK Principles NEXT Glossary element, and that is not a style choice. An HTML parser applies the table content model to anything it parses, and `` is not in it - so a loop written inside a table is foster-parented out of the table and its rows are left behind ungrouped. The template survives a intact because a script's contents are never parsed as markup. --> ====================================================================== # Glossary https://tradefloor.dev/glossary.html Every term this library uses in a specific way, with the measurement that makes it specific. ====================================================================== LEARN/ GLOSSARY Glossary Terms this library uses in one specific way. Where a number pins the meaning, the number is here. all the run price formation provenance 43 of 43 terms ADV the run Average daily volume, the denominator for participation. 13.7 million shares is 0.05x a day in one name and 407x in another. anchor_price price formation fundamental_value times exp(mispricing_s): what the model wanted before the book touched it. So close minus anchor_price isolates the microstructure. Checkpoint and branch the run Two ways to fork a run at one state. On a 60-day 40-instrument run, branch copies engine state in under 1 ms and does not survive the process. Checkpoint replays the order log in 2.7 s and does. circuit_breaker price formation Books the rewrite when the model price leaves the session band and the tick re-derives s from the clamped price. Omit the column and the factor sum misses that correction on any day the breaker binds. close_market the run The day's close bookkeeping: momentum roll, GARCH, macro step. It advances the macro chain into the next day, so a row recorded after it carries the values the next day will trade under. common random numbers provenance Running one seed under two presets so draws_consumed comes out identical. Every difference in the outcome is then a parameter effect rather than reshuffled noise. crowd lean price formation The crowd net-buys what trades below fair value and net-sells what trades above it, capped at 0.02 in log price so the crowd can never dominate a news day. draw schedule provenance Market hours, the 390-tick day, the calendar and the sector key order. Nothing settable may change it: a preset changes what the draws are multiplied into, never the schedule. draw_delta provenance The market stream's draw-count difference between the two runs compare() puts side by side. Zero across all twenty-eight comparisons on pt-v14. Non-zero means a halt, a delisting or a roster change moved the schedule. draws_consumed provenance Total draws from three of the seven streams: market, economy and external. Equal market counts between two runs of the same tick schedule mean both saw an identical noise sequence. Engine the run A whole market stepped through time. It owns the seeded generator, the per-company price state, the economy and the central bank, and nothing beyond those four. era boundary provenance A change that moves every seeded trajectory, caught by a probe digest rather than a version string. One calendar day once brought three trajectory-changing fixes while the version and the preset both stayed put. era_fingerprint() provenance The digest of a small fixed probe simulation, recomputed before every replay. Two builds that agree on the probe agree on the arithmetic it exercises. fair value price formation The level mean reversion pulls toward. It reprices at the central bank's meeting calendar, so a run long enough to cross a meeting is not a stationary experiment. fingerprint, model provenance The first 8 hex characters of a sha256 over the preset's canonical serialisation. Any settable change reads custom-7f290e34 rather than pt-v1, so a changed preset can not present as the shipped one. fingerprint, strategy provenance A sha256 over the spec, unchanged by whitespace, key order, or scaling every blend weight by two. Empty for a hand-built agent, because that result is reproducible only by citing code at a commit. fingerprint, universe provenance A sha256 over the roster's canonical form, order included. AAA closes day 5 at 143.03 in roster order and 134.88 reversed. fundamental_value price formation What the company is worth on its fundamentals, with no mispricing in it. GJR-GARCH price formation The per-name variance recursion. garch_alpha on yesterday's squared return, garch_beta on yesterday's variance, plus a GJR garch_gamma that adds weight when that return was negative, because a symmetric GARCH squares the return and destroys its sign. guards price formation The mispricing cap, the crowd lean cap, the session breaker and the price cap. Settable, and excluded from every calibration search because they are worst-case guarantees. daily_shock_cap is not settable at all. idiosyncratic price formation The part of a name's return no other name shares: one normal per company per tick, sized by that name's own GARCH variance. Instrument the run One tradable company: ticker, sector and eight fundamentals. short_interest is a share count, so a value between 0 and 1 raises rather than being read as a fraction. jump price formation Applied to mispricing_s after the tick loop rather than inside it. Dropping it from the sum takes the worst residual from 1.9e-16 to 0.090 on a run carrying one jump row in 39,000. known-answer test provenance One fixed simulation run inside each of five wheel targets and hashed, compared target against target and against the sha256 committed in tests/known_answer.json. A disagreement stops the upload. Macro the run Day-zero rates, inflation and the cycle. The chain runs on from there by itself, so vix takes 118 distinct values over a 120-day run. market factor price formation One normal drawn per tick that reaches every name through its beta. Without shared factors a 108-name index would have almost no aggregate volatility, because independent noise cancels. market maker the run The counterparty on every book. It quotes both sides and shifts both quotes toward reducing its inventory, so the book absorbs one-sided flow without running dry. mispricing_s price formation Log deviation of price from fair value. The nine factor columns sum to its change, with a worst residual of 1.9e-16 over 39,000 rows. order book the run engine.book(ticker) returns a detached snapshot. Filling against it prices your execution at the levels you consume and leaves prices() byte-identical to the run that never traded. order flow impact price formation The permanent, information share of order flow into mispricing_s: 0.35 of the total impact coefficient of 50.0. The book already charges the temporary component when orders walk the book's depth. order log the run engine.order_log holds every input the engine consumed. An unknown entry raises on replay rather than being skipped, because a replay that ignored one would look like a success. order_flow the run The channel that tells the market you traded, passed to tick() or run_session(). Pushing 500,000 AAA shares through it closes the name at 137.45 against 135.67 untouched. random_noise price formation The market component times the crash amplifier, plus the sector component, plus the idiosyncratic draw, summed into one column. truth can not separate the three. residual provenance The gap between differenced mispricing_s and the sum of the nine columns: 1.9e-16 at worst over 39,000 rows. Anything larger means the join is wrong. Sorting tick without day takes that same run to 0.149. RNG substream provenance One of seven generators derived from the root seed: market, economy, external, jumps, volume, news and per-name volume. A new mechanism on its own stream perturbs nothing, however it draws. RunManifest provenance The five things that identify a run, as one object: roster, macro initial conditions, realised scenario path, order log and strategy. reproduce() raises with the disagreeing component named. sector factor price formation One normal per sector per tick, reaching each member through its sector loading. It blends toward the market factor above VIX 25.5, so diversification stops working exactly when it is most wanted. seed the run One integer, separate from the universe seed. It does not identify a run on its own, because the market an agent trades depends on that agent's own orders. sweep the run Streams one seed at a time. A hundred recorded 252-day 100-instrument engines alive at once is roughly 110 GB, against a little over 1 GB for one. the nine factors price formation reversion, momentum, crowd_lean, company_news, order_flow_impact, short_squeeze_effect, random_noise, circuit_breaker and jump. Difference mispricing_s, add all nine, and you can check the label instead of trusting it. tick the run One minute of game time. A day is 390 regular-session ticks, so a hand loop that passes the same hour and minute 390 times simulates a different market. truth table provenance One row per instrument per tick carrying fundamental_value, anchor_price, mispricing_s and nine factor columns. The nine sum to the change in mispricing_s. Universe the run The roster of instruments. Its order is contractual: the engine walks it in index order drawing random numbers as it goes, so a re-sorted universe gives a different market from the same seed. All pages Start > Install > Core concepts > The two loops > Running a market Use it > Agents > Scenarios > Execution cost > Checkpoints and forking > EDGAR companies > RL environment > The MCP server Trust it > Realism envelope > The metrics > Principles > Citing a run Reference > Glossary > The nine factors > Conventions > Schemas > Presets > Release notes API > Core types > Evaluate > Parameters BACK Citing a run NEXT The nine factors element, and that is not a style choice. An HTML parser applies the table content model to anything it parses, and `` is not in it - so a loop written inside a table is foster-parented out of the table and its rows are left behind ungrouped. The template survives a intact because a script's contents are never parsed as markup. --> ====================================================================== # The nine factors https://tradefloor.dev/factors.html Every tick's change in mispricing decomposes into nine named components that sum to the move. ====================================================================== REFERENCE/ THE NINE FACTORS The nine factors Every tick's change in mispricing breaks into nine components that sum to it. These are the names the truth table uses, and the closed set an agent's explain must answer from. The set, in order The first three are the model's own dynamics, the next four are shocks, the last two are discrete events booked after the tick chain. reversion The pull back toward fair value. Negative above fair value, positive below, decaying at a rate set by its half-life. momentum Herding: the fraction of yesterday's re-rating that continues today. Controlled by momentum_theta, and the reason returns trend here. crowd_lean The crowd's net flow: net-buys what trades below fair value, net-sells what trades above, and chases yesterday's move. Bounded. company_news News priced in: a name's own events at full weight, its sector's at a fraction, market-wide at a smaller fraction. order_flow_impact The permanent, information-bearing part of order imbalance. Your own orders land here. short_squeeze_effect Forced flow: squeezes on high short interest into a rising price, and stop cascades in either direction. random_noise The idiosyncratic draw, scaled by the name's beta, its sector's volatility, its size, and the GARCH variance state. circuit_breaker The session breaker's own correction, when the model price leaves the allowed band. jump The daily jump, applied after the tick loop, recorded on the tick where it is first observed. They sum, and that is the point Difference mispricing_s across two ticks, add the nine columns, and the residual sits near 1e-16. That is what makes this a dataset rather than a commentary: a label can be checked against the outcome instead of trusted. FACTORS = ["reversion", "momentum", "crowd_lean", "company_news", "order_flow_impact", "short_squeeze_effect", "random_noise", "circuit_breaker", "jump"] # Engine.FACTORS one = truth.filter(pl.col("instrument_id") == 0).sort("day", "tick") delta = one["mispricing_s"].diff() summed = one.select(sum(pl.col(c) for c in FACTORS)).to_series() print((delta[1:] - summed[1:]).abs().max()) # ~1e-16 Before 2026-08-26 this did not always hold On any day a jump landed, from pt-v4 onward, or any day the session circuit breaker bound, the seven-column identity fell short: 0.128 off on one measured seed over 120 days. Both mechanisms are columns now, and the identity holds through a crisis. The columns were never wrong, only incomplete. Scope It decomposes the mispricing, the gap between price and fair value, not the price itself. Everything that moves fair value, such as a rate change repricing the discount, lives in fundamental_value instead. And it is per tick, deliberately: a day-level attribution can say order flow moved a price today, never when, and a label that cannot align to a bar is not a label. All pages Start > Install > Core concepts > The two loops > Running a market Use it > Agents > Scenarios > Execution cost > Checkpoints and forking > EDGAR companies > RL environment > The MCP server Trust it > Realism envelope > The metrics > Principles > Citing a run Reference > Glossary > The nine factors > Conventions > Schemas > Presets > Release notes API > Core types > Evaluate > Parameters BACK Glossary NEXT Conventions element, and that is not a style choice. An HTML parser applies the table content model to anything it parses, and `` is not in it - so a loop written inside a table is foster-parented out of the table and its rows are left behind ungrouped. The template survives a intact because a script's contents are never parsed as markup. --> ====================================================================== # Conventions https://tradefloor.dev/conventions.html Eight rules about units and inputs, worth reading before you hit them. ====================================================================== LEARN/ CONVENTIONS Conventions Units and input rules. Read these before you hit them, because each one raises rather than guesses. Nothing is silently clamped. A simulator that repairs your inputs gives you a market you did not specify, so a malformed scenario raises ValidationError and a rejected order raises OrderError . The rules Rates are fractional 0.052 not 5.2 0.052 means 5.2 percent. Passing 5.2 raises, with an error that says so. Absence differs from zero None not 0.0 corporate_bond_yield=None falls through to the policy rate. 0.0 is a real observation and is used as given. In a columnar read, where a column can not carry None, absence is NaN and never zero, because zero is a real maker inventory, a real mispricing and a real return. Negative EPS is legal eps=-1.20 not filter them out Loss-makers are valued off book value, and a universe without them is not realistic. It also never exercises that valuation path, so a study on such a roster covers half the model. Short interest is a share count 3_000_000 not 0.03 The squeeze rule divides it by the float. A value strictly between 0 and 1 raises for a company with a real share count. Roster order is contractual universe.fingerprint not sorted(tickers) A re-sorted universe is a different market. The fingerprint covers order as well as content. Coefficients ship as a preset model="pt-v14" not Engine(garch_alpha=...) They are named and versioned rather than passed as constructor keywords, so two published results can be compared. pt-v14 is the default as of the 2026-08-28 era boundary, pt-v12 was the default before it, and every earlier name from pt-v1 on still reproduces bit for bit. Every numeric column is f64 float64 not mixed dtypes No integer columns, no downcasting. A join that produces a different dtype is a sign the read went wrong. Short interest, in detail This is the convention that bites hardest, because the mistake would otherwise be silent. The squeeze rule divides short interest by the float, so 3% of a hundred million shares is 3,000,000. Why 0.03 raises Three hundredths of one share against a real float is a squeeze ratio of 3e-10, and a squeeze that can never fire. So a value strictly between 0 and 1 raises for a company with a real share count. Zero is legal. It means no short interest, which is a real observation. What the generator draws Log-uniform between 0.4% and 30% of shares outstanding, so the median is the geometric mean of those bounds. Median, whole ticker space 3.45% Above the 20% threshold 9.54% Median on random(108, seed=7) 2.53% A hundred names is a small sample of a draw whose top and bottom differ by a factor of seventy-five, so a roster the size these docs use runs lower and noisier. What a preset does and does not carry In the dictionary The mispricing and crowd model: the half-life, mispricing_phi, momentum_theta, the mispricing and daily-shock caps, and the three crowd terms. Eight numbers, and every one of them live. Live, and absent from it The GARCH parameters, the market and sector factor sigmas, and the order-flow coefficient. None of them appear, and nothing forces a preset name to change when one of them moves. So the preset name is necessary and not sufficient. A fully specified run also names the package version, which pins the implementation. All pages Start > Install > Core concepts > The two loops > Running a market Use it > Agents > Scenarios > Execution cost > Checkpoints and forking > EDGAR companies > RL environment > The MCP server Trust it > Realism envelope > The metrics > Principles > Citing a run Reference > Glossary > The nine factors > Conventions > Schemas > Presets > Release notes API > Core types > Evaluate > Parameters BACK The nine factors NEXT Schemas element, and that is not a style choice. An HTML parser applies the table content model to anything it parses, and `` is not in it - so a loop written inside a table is foster-parented out of the table and its rows are left behind ungrouped. The template survives a intact because a script's contents are never parsed as markup. --> ====================================================================== # Schemas https://tradefloor.dev/schemas.html Every column of the five Arrow tables, the constructor fields, and where each call refuses rather than guesses. ====================================================================== LEARN/ SCHEMAS Schemas Every column of the five tables, and the fields you construct a run from. Every numeric column is float64. bars truth macro fills book Instrument StrategySpec bars engine.bars(grain="day" or "tick") OHLCV per instrument. This is the table a historical backtest would also give you. column type what it is day uint32 Day index. tick uint32 Tick index. A tick is a minute, and there are 390 in a day. Tick grain only. bar uint32 Bar index within the day. Day grain only. instrument_id uint32 Roster index. open · high · low float64 Day grain only. close float64 What the book actually settled. volume float64 Shares. Day grain carries open, high, low and bar. Tick grain carries tick instead, and a tick is one minute of the 390 in a session. Where a call refuses Each of these raises rather than returning a number it can not support. The list is the shape of the library's judgment about its own limits. envelope.intervals Fewer than two panels. A spread over one observation is not a spread. atlas.plan Fewer than 8 samples. Survey.sensitivity Fewer than 8 usable rows, or an output no row measured. Survey.profile Fewer than three rows per bin. Survey.attribution A vector value outside the surveyed range, a parameter set on one side only, or a parameter that was not surveyed. Survey.confirm Any seed shared with the survey or between blocks, and a survey with no seed record at all. An empty list is the absence of a record wearing the key. ModelParams.from_preset An override of a compile-time constant, by name. Accepting one the engine would ignore would make the fingerprint a lie. Scenario Chaining a shape constructor onto a built scenario, or a compare() baseline that realises the same path as the scenario. RunManifest.reproduce A build whose arithmetic does not match the manifest's. It names the culprit rather than replaying a different market. Checkpoint restore A pre-split snapshot with three RNG numbers instead of nine, naming the era. It can not be continued bit-exactly. Instrument A short_interest between 0 and 1 on a real share count, and an unknown sector key. loss.band_distance_loss A missing or zero noise scale. An unweighted sum is a choice, and it will not happen by accident. All pages Start > Install > Core concepts > The two loops > Running a market Use it > Agents > Scenarios > Execution cost > Checkpoints and forking > EDGAR companies > RL environment > The MCP server Trust it > Realism envelope > The metrics > Principles > Citing a run Reference > Glossary > The nine factors > Conventions > Schemas > Presets > Release notes API > Core types > Evaluate > Parameters BACK Conventions NEXT Presets element, and that is not a style choice. An HTML parser applies the table content model to anything it parses, and `` is not in it - so a loop written inside a table is foster-parented out of the table and its rows are left behind ungrouped. The template survives a intact because a script's contents are never parsed as markup. --> ====================================================================== # Presets https://tradefloor.dev/presets.html Fifteen named coefficient sets. Which one to use, what each one measured, and what a preset does not pin. ====================================================================== LEARN/ PRESETS Presets Fifteen named coefficient sets. Use pt-v14 unless a published run names another one. pt-v14 The default since 2026-08-28. All fourteen statistics in band at 252 days and again at 504. 12 Presets ship. All selectable, all reproducing bit for bit. 8 Numbers in the dictionary a preset carries. Others are live and absent from it. Which preset to use all twelve for new work reproduction only Preset Use it for In band, 252d In band, 504d pt-v14 RECOMMENDED Anything. The default, and the one the realism envelope certifies: all fourteen statistics in band at 252 days, and all fourteen again at 504. 14/14 14/14 pt-v12 REPRODUCTION ONLY Work published before the 2026-08-28 era boundary, when this was the default. It holds all fourteen at both horizons; pt-v14 holds them on more seed blocks. 14/14 14/14 pt-v8 RECOMMENDED, BY NAME Anything that measures how correlation moves through time: the factor variance has a memory, and thirteen of fourteen hold at 504 days. - 13/14 pt-v10 REPRODUCTION ONLY Work published before the 2026-08-26 era boundary, when this was the default. All fourteen in band at 252 days, on thirty training seeds. 14/14 13/14 pt-v11 REPRODUCTION ONLY A run that names it: pt-v10 plus the crisis work, crisis_blend_gain, sector_vix_coupling, endogenous news and peer transfer, which pt-v12 inherits. - - pt-v9 REPRODUCTION ONLY A run that names it: thirteen of fourteen at both horizons, and the first preset whose VIX responds to the day move rather than the closing minute. 13/14 13/14 pt-v7 REPRODUCTION ONLY A sector or crisis study that names it: the first preset with industries that survive a crisis, thirteen of fourteen at both horizons. 13/14 13/14 pt-v3 REPRODUCTION ONLY Work published when this was the default, the era before pt-v10. Selectable, and it reproduces bit for bit under 0.1.4 and 0.2.0 alike. - 7/14 pt-v1, v2, v4, v5, v6 REPRODUCTION ONLY A run that names them. pt-v1 ships momentum_theta at 0.25 and measures return autocorrelation at +0.249, outside the real band. - - A band is the range a real equity market produces for one statistic. Fourteen statistics are measured on the simulated market and compared against their real-market ranges: volatility, the fatness of the tails, how much names move together, whether volatility arrives in episodes, whether volume behaves. A statistic is in band when the simulated value falls inside the real range. The columns count how many of the fourteen do, at one and at two trading years. Name it, or inherit a moving default e = tf.Engine(seed=42, universe=u, model="pt-v14") e.model_fingerprint # "pt-v14" tf.model_preset() # the set in force # a settable change renames it # "custom-7f290e34" The era boundary pt-v14 became the default on 2026-08-28, and every trajectory that came from the default changed that day. A run recorded before it is not comparable to one after it unless both name their preset. Checked rather than asserted: pt-v1, pt-v2 and pt-v3 give identical market digests under 0.1.4 and 0.2.0. What a preset pins In the dictionary The mispricing and crowd model: the half-life, mispricing_phi, momentum_theta, the mispricing and daily-shock caps, and the three crowd terms. Two are derived rather than set: mispricing_phi and s_phi_tick come from the half-life. Live, and not in it The GARCH parameters, the market and sector factor sigmas, and the order-flow coefficient. Nothing forces a preset name to change when one of them moves. So a fully specified run is package version, preset, universe fingerprint and seed. The preset alone is necessary and not sufficient. Never settable The draw schedule: market hours, the 390-tick day, the calendar and the sector key order. A preset changes what the draws are multiplied into, never the schedule itself. The guards are settable but excluded from every calibration search, because they are worst-case guarantees. daily_shock_cap is not settable at all. All pages Start > Install > Core concepts > The two loops > Running a market Use it > Agents > Scenarios > Execution cost > Checkpoints and forking > EDGAR companies > RL environment > The MCP server Trust it > Realism envelope > The metrics > Principles > Citing a run Reference > Glossary > The nine factors > Conventions > Schemas > Presets > Release notes API > Core types > Evaluate > Parameters BACK Schemas NEXT Release notes element, and that is not a style choice. An HTML parser applies the table content model to anything it parses, and `` is not in it - so a loop written inside a table is foster-parented out of the table and its rows are left behind ungrouped. The template survives a intact because a script's contents are never parsed as markup. --> ====================================================================== # Release notes https://tradefloor.dev/release-notes.html What changed, what it means for a run you already have, and how to pin an old preset if you need the old behaviour. ====================================================================== REFERENCE/ RELEASE NOTES Release notes What changed, what it means for a run you already have, and how to pin the old behaviour if you need it. Full history in CHANGELOG.md. 0.5.0 The library is now tradefloor. Formerly pretium, which published through 0.4.3 and stays on PyPI and crates.io forever: published results cite those versions, and reproducibility is the point. Install tradefloor, import tradefloor, crate tradefloor, MCP server tradefloor-mcp. The rename changes no behaviour — this release reproduces 0.4.3's known-answer digest on every platform, and the release gate proves it before publishing. Preset names stay pt-v1 through pt-v15: they are citation identifiers, frozen under the old prefix. pt-v15: the first preset to hold both crisis instruments everywhere. pt-v14 plus six numbers: the two-timescale variance mixture (slow weight 0.35, persistence 0.98, gain 0.05, VIX damp 0.374), the daily credit floor at 1.0, and sector-loading dispersion at 0.5. Over thirteen thirty-seed qualification blocks it ties pt-v14's panel on every block, compresses the crisis co-movement range from 0.0774 to 0.0464 — inside the 0.0630 band width — holds crisis co-movement in range on 13 of 13 blocks and the crisis lever on 13 of 13 at median 6.152 against the real 6.16. Selectable by name; not the default, which remains pt-v14. 0.4.3 2026-08-28 0.4.2 changed pt-v13 and pt-v14, and it should not have. If you pinned either preset, this puts them back exactly as they were in 0.4.0 and 0.4.1: all fourteen certified statistics are bit-identical to their pre-0.4.2 values. The 0.4.2 fix pointed the dollar's safe-haven gate at crisis_vix_threshold, and both presets override that parameter, so their gate moved and their trajectories moved with it. No result computed under 0.4.2 is wrong, since every statistic stayed in band, but a run recorded under pt-v14 before 0.4.2 does not replay under it, and the preset name is what a citation carries. The dollar gate is its own dial now, usd_crisis_vix_threshold. A preset that wants both gates to move together sets both. 0.4.2 2026-08-28 Three reported defects, no trajectory change. Every preset runs exactly as it did in 0.4.1. Moving crisis_vix_threshold gated the gold crisis premium at your level and left the dollar's safe-haven drift at the default, silently. The two describe one regime, and both read the parameter now. A held meeting now reports what it decided. advance_day computed the central bank's decision and announcement variant and discarded both; DayAdvanceOutcome carries them. Between meetings the corporate bond yield goes stale while the treasury keeps moving, so the credit spread can drift under its floor and quote an investment-grade yield below the risk-free curve. daily_credit_floor_gain corrects it and ships at 0.0, because that code path is shared by every preset and a trajectory change belongs at a preset boundary. 0.4.1 2026-08-28 pt-v13 and pt-v14 now report the mispricing half-life they run. Both said 68.26 days and both decayed at 60. Nothing you ran was wrong and no trajectory moves in this release: the number the engine reads is mispricing_phi, which was always the 60-day value. What was wrong is a published fact. tf.model_preset() reported the 68.26, a manifest records it, and anyone setting a half-life from that number got a different market than the preset runs. If you pinned either preset in 0.4.0, your results are unaffected and need no rerun. 0.4.0 2026-08-28 pt-v14 is now the default. It beats pt-v12 by a wider margin than any preset before it, and it is never worse on any of the thirteen seed blocks it was measured on. Over 13 seed blocks Before (pt-v12) Now (pt-v14) two-year panel, blocks fully in band 3 of 13 11 of 13 crisis correlation outside its real range 4 of 13 2 of 13 roster shapes in band 131 of 138 137 of 138 Industry-level volatility now carries more of the market's shared movement, and the market's own volatility memory was retuned to pay for it. Stocks in different industries stop moving together quite so uniformly in a crisis, which is what real markets do. One thing got slightly worse. Volume and volatility still arrive together, but less tightly: the measure falls from 0.56 to 0.52 in a band that runs 0.46 to 0.66. It never leaves the band at the resolution this project certifies. The documentation is rebuilt as a learning path. Old URLs still work: twelve redirect to the page that replaced them, and four pages were retired. 0.3.0 2026-08-26 pt-v12 is now the default. It is the first preset that looks like a real market over two years, not just one. In band Before (pt-v10) Now (pt-v12) over one year 14 of 14 14 of 14 over two years 13 of 14 14 of 14 on a roster it was never tuned on 14 of 14 14 of 14 Thirty-seed medians. Only the two-year row moved, and it was not paid for at one year or on a fresh roster. If you have a result to reproduce Results recorded without naming a preset will differ from here on. Every earlier preset still exists and still reproduces bit for bit: eng = tf.Engine(seed=42, universe=u, model="pt-v10") # exactly as before What was actually wrong, and what it cost Volume stopped responding to a move at four percent: a stock down twelve traded exactly like one down four. That single cap had been in the engine since the first version and nobody had chosen it. Raising it to twelve percent fixed the one statistic out of band over two years and cost nothing measurable elsewhere. One thing got worse: driven by a real macro path, daily swings now run 1.57x as wide as the real stock they are compared against, against 1.555x before. That is written up as the scenario-magnitude gap on the realism envelope page. All pages Start > Install > Core concepts > The two loops > Running a market Use it > Agents > Scenarios > Execution cost > Checkpoints and forking > EDGAR companies > RL environment > The MCP server Trust it > Realism envelope > The metrics > Principles > Citing a run Reference > Glossary > The nine factors > Conventions > Schemas > Presets > Release notes API > Core types > Evaluate > Parameters BACK Presets NEXT Core types element, and that is not a style choice. An HTML parser applies the table content model to anything it parses, and `` is not in it - so a loop written inside a table is foster-parented out of the table and its rows are left behind ungrouped. The template survives a intact because a script's contents are never parsed as markup. --> ====================================================================== # Core types https://tradefloor.dev/core-types.html Engine, Universe, Instrument, Macro and OrderBook: the signatures you need to run a market. ====================================================================== LEARN/ CORE TYPES Core types The five types you need to run a market. Every keyword below is keyword-only unless the signature shows otherwise. Engine A whole market, stepped through time. Engine(*, seed: int, universe: Sequence[Instrument], macro_state: Macro | None = None, model: str | ModelParams | None = None) run_days(n) Runs n whole trading days: open, the session, then close. It records a day before closing it. open_market() / close_market() The day boundary by hand. The close does the momentum roll, GARCH and the macro step, so it advances the chain into the next day. run_session(hour, minute, day_of_week, ticks, *, order_flow=None) One block of ticks. order_flow is how you tell the market you traded. tick(hour, minute, day_of_week, *, volatility, news, news_impacts, order_flow) A single minute. Everything run_session does, one step at a time. bars(*, grain="day") An Arrow stream of OHLCV. Day or tick grain. truth(*, day=None) An Arrow stream of the ground truth: fair value, mispricing and the nine factors. macro_table() / book_table() The economy day by day, and the resting book level by level. book(ticker) A detached OrderBook snapshot. Reading it leaves prices() byte-identical. pin_macro(**fields) Overrides the endogenous step for those fields once. The chain then continues from the pinned value. model_fingerprint The preset name, or custom-7f290e34 once anything settable moves. draws_consumed Total draws from the market, economy and external streams. Equal counts between two runs mean an identical noise sequence. order_log Every input the engine consumed, as JSON-serialisable dicts. An unknown entry raises on replay. FACTORS The nine factor names, at runtime, in the order the truth table carries them. The engine owns the seeded generator, the per-company price state, the economy and the central bank, and nothing beyond those four. Universe A list of Instrument, whose order is contractual. Universe(instruments: Sequence[Instrument]) Universe.random(n: int, *, seed: int) Universe.from_edgar(snapshot) Universe.from_json(text) fingerprint A sha256 over the roster in canonical form, order included. to_json() Round-trips through from_json. index_of(ticker) The roster index, or None. tickers The roster order as a list. It subclasses list, so len, iteration and indexing all work. random() fills twelve sectors round-robin and assigns synthetic tickers by position: AAA, AAB, AAC and on. Instrument One tradable company. Instrument(ticker: str, sector: str, *, initial_price: float, shares_outstanding: float, eps: float | None = None, book_value_per_share: float | None = None, revenue_growth: float | None = None, avg_volume: float = ..., beta: float = ..., short_interest: float = ...) short_interest A share count, not a fraction. A value strictly between 0 and 1 is refused for a company with a real share count. eps=None Marks a loss-maker. It is valued off book value and never reprices when the discount rate moves. avg_volume Held fixed through a run, and what a participation cap is measured against. beta The loading on the shared market factor. market_cap Derived and read-only: price times shares outstanding. Ticker and sector are positional. Everything else is keyword-only, so a call can not silently swap two floats. Macro The economy on day zero. Macro(*, vix: float = ..., federal_funds_rate: float = ..., corporate_bond_yield: float | None = None, inflation_rate: float = ..., qe_pe_boost: float = ..., fear_greed_index: float = ..., cycle: CycleName = ...) Rates are fractional 0.052 means 5.2 percent. Passing 5.2 raises. corporate_bond_yield=None Falls through to the policy rate plus the spread. 0.0 is a real observation and is used as given. cycle expansion, peak, contraction, trough or recovery. It lives on Macro only, never in the macro table. This is the state on day zero, not the whole run. Every close advances the chain, so vix takes 118 distinct values over a 120-day run. OrderBook One book, with price-time priority. book = engine.book("AAA") book.best_bid / book.best_ask book.mid_price / book.spread book.depth_buy / book.depth_sell book.bids / book.asks bids / asks The resting orders, each carrying price, remaining quantity, owner and sequence. sequence Arrival order, which is what breaks a tie at the same price. Post behind someone and you fill behind them. best_bid / best_ask The touch, or None when that side is empty. depth_buy / depth_sell Total resting size per side. A snapshot from engine.book() is detached. Filling against it prices your execution at the levels you consume, and leaves the untraded run byte-identical. Two errors, and what each one means ValidationError A malformed input: a rate passed as a percentage, a short interest between 0 and 1 on a real share count, an unknown sector key, a scenario that chains a shape onto a built scenario. OrderError A rejected order: a zero, negative or NaN quantity, or a NaN price on a limit. Nothing is clamped, because a simulator that repairs your order gives you a fill you did not ask for. All pages Start > Install > Core concepts > The two loops > Running a market Use it > Agents > Scenarios > Execution cost > Checkpoints and forking > EDGAR companies > RL environment > The MCP server Trust it > Realism envelope > The metrics > Principles > Citing a run Reference > Glossary > The nine factors > Conventions > Schemas > Presets > Release notes API > Core types > Evaluate > Parameters BACK Release notes NEXT Evaluate element, and that is not a style choice. An HTML parser applies the table content model to anything it parses, and `` is not in it - so a loop written inside a table is foster-parented out of the table and its rows are left behind ungrouped. The template survives a intact because a script's contents are never parsed as markup. --> ====================================================================== # Evaluate https://tradefloor.dev/evaluate.html evaluate, rank, tca and reproduce: the calls that turn a market into a result someone else can check. ====================================================================== LEARN/ EVALUATE Evaluate Four calls turn a market into a result: score one, rank twelve, price the execution, replay it later. tf.evaluate Score agents on one identical market. scores = tf.evaluate( agents: dict[str, Agent | StrategySpec], *, seed: int, universe: Universe, days: int, scenario: Scenario | None = None, model: str | None = None) agents A dict of name to agent, or name to StrategySpec. A spec carries its own fingerprint into the result. one engine each Every agent gets its own Engine built from the same seed and roster, so nobody eats another agent's depth. returns A dict of name to Scorecard. tf.reference_agents(seed=3) The five shipped baselines as a ready dict: hold, random, momentum, mean_reversion and oracle. tf.capture_ratio(scores) Each agent's P&L as a fraction of the Oracle's. One market is one sample. Use rank before you call a winner. tf.rank Repeat that across twelve seeds, and separate with a sign test. ranking = tf.rank( factory: Callable[[], dict], *, seeds: Iterable[int], universe: Universe, days: int, workers: int = 1) ranking.report() ranking.separation("mean_reversion", "momentum") factory A callable returning a fresh agent dict per seed, because an agent that carries state across markets is not being measured on either. report() Pooled captures, their per-seed ranges, and how many seeds each agent topped. separation(a, b) A paired sign test: wins, losses, ties and a p-value. workers Processes. Seeds are independent, so this scales linearly. On the published twelve-market grid, mean-reversion pools at +0.783 and momentum at +0.259, and mean-reversion wins 9 to 3 at p = 0.15. One seed picks the pooled leader eight times in twelve. tf.tca.analyse Price the execution against the world where you never traded. ex = tf.tca.analyse( algo, *, seed: int, universe: Universe, days: int) ex.shortfall_bps() ex.by_step() ex.partial_fills() algo Any object with an act method. shortfall_bps() What the footprint cost, against the same seed run with no orders. by_step() Where it was paid, per decision. partial_fills() What you asked for against what you got. Round-trip shortfall measured over eight simulation seeds runs -17.72 to +2.03 bps. The range crosses zero, so one run tells you nothing. tf.RunManifest Write the run down so someone else can replay it. manifest = tf.RunManifest.of( engine, spec, seed=7) manifest.to_json(path) pt.RunManifest.from_json(text) pt.reproduce(manifest) carries Package version, preset, seed, universe fingerprint, macro conditions, scenario and strategy fingerprint, with the expected digest. reproduce() Raises on a mismatch and names the component that disagreed. era_fingerprint() The digest of a small fixed probe, recomputed before every replay, so two builds that agree on the probe agree on the arithmetic. A manifest that quietly reproduced a different market would be worse than no manifest, so it refuses rather than warning. What a Scorecard carries return_pct What it made, as a percentage. pnl What it made, in currency. impact_bps What its own footprint cost, in basis points. trades How many fills it took. strategy_fingerprint A sha256 over the spec. This is the one to cite. universe_fingerprint A sha256 over the roster, order included. model The preset the run used, so a result names its own coefficients. strategy_fingerprint is empty for a hand-built agent, because that result is reproducible only by citing code at a commit. A StrategySpec gives you one, which is why the spec grammar exists. All pages Start > Install > Core concepts > The two loops > Running a market Use it > Agents > Scenarios > Execution cost > Checkpoints and forking > EDGAR companies > RL environment > The MCP server Trust it > Realism envelope > The metrics > Principles > Citing a run Reference > Glossary > The nine factors > Conventions > Schemas > Presets > Release notes API > Core types > Evaluate > Parameters BACK Core types NEXT Parameters element, and that is not a style choice. An HTML parser applies the table content model to anything it parses, and `` is not in it - so a loop written inside a table is foster-parented out of the table and its rows are left behind ungrouped. The template survives a intact because a script's contents are never parsed as markup. --> ====================================================================== # Parameters https://tradefloor.dev/parameters.html What ModelParams exposes: 87 settable names, 118 visible entries, and why the gap between them exists. ====================================================================== LEARN/ PARAMETERS Parameters Most people never touch these. Read this page if you are calibrating the model rather than running one. 87 SETTABLE() Names you can override at runtime. 118 TO_DICT() Entries visible in the preset and covered by the fingerprint. 31 THE GAP Visible, fingerprinted, and refused as an override. 8 IN THE PRESET DICT The mispricing and crowd model, which is what a preset name carries in prose. Four kinds of coefficient The difference matters, because a change you make in one category renames the preset and a change in another is refused outright. Settable RENAMES THE PRESET 87 names. Override one and the fingerprint becomes custom-, followed by 8 hex characters, so the changed model can not present as the shipped one. momentum_theta, garch_alpha, market_factor_sigma Derived RECOMPUTED, NOT SET Two coefficients are carried as recorded bit patterns and can not be set directly. Overriding the half-life recomputes both, deterministically on a given build but not bit-identically to any recorded constant. mispricing_phi, s_phi_tick Guards SETTABLE, EXCLUDED FROM SEARCH Four guards live in the tick chain and are settable, but a calibration search excludes them, because they are worst-case guarantees rather than tuning knobs. One more is not settable at all. the mispricing cap, the crowd lean cap, the session breaker, the price cap. daily_shock_cap is fixed Compile-time REFUSED BY NAME 28 constants are visible in to_dict() and covered by the fingerprint, and an override is refused. The engine would ignore it, and accepting it would make the fingerprint a lie. the sector table among them What the settable surface covers Read the list rather than a summary of it, with tf.ModelParams.settable() . The shape of it: The two variance processes Per-name GJR-GARCH and the market factor's, cascade components and the slow term included. The factor sigmas Market and sector, plus the sector loadings and the idiosyncratic scale. Size and spread effects What a large order pays, and how wide the maker quotes. Mispricing and crowd dynamics The half-life, the herding term and the three crowd coefficients. News and flow coefficients Endogenous news and peer news among them. Jump sizes and intensities How often a jump fires and how large it is. The VIX channel How the fear gauge reaches the market factor's variance. Crisis blend and stress The crisis lever, the sector coupling and the remembered stress terms. The volume expression volume_move_cap among them, the one pt-v12 moved off its compiled literal. The guards Settable, and excluded from every calibration search. Change one, and the name changes params = tf.ModelParams.from_preset( "pt-v14", momentum_theta=0.05) e = tf.Engine(seed=42, universe=u, model=params) e.model_fingerprint # "custom-7f290e34" Why it renames rather than warns A changed preset that still called itself pt-v14 would let two different markets share one citation. The fingerprint is the first 8 hex characters of a sha256 over the canonical serialisation, so any settable change produces a new name. An override of a compile-time constant is refused by name. Accepting one the engine would ignore would make the fingerprint a lie. All pages Start > Install > Core concepts > The two loops > Running a market Use it > Agents > Scenarios > Execution cost > Checkpoints and forking > EDGAR companies > RL environment > The MCP server Trust it > Realism envelope > The metrics > Principles > Citing a run Reference > Glossary > The nine factors > Conventions > Schemas > Presets > Release notes API > Core types > Evaluate > Parameters BACK Evaluate element, and that is not a style choice. An HTML parser applies the table content model to anything it parses, and `` is not in it - so a loop written inside a table is foster-parented out of the table and its rows are left behind ungrouped. The template survives a intact because a script's contents are never parsed as markup. -->