Quickstart
tradefloor is a simulated stock market with a limit order book and an economy, for testing trading strategies and AI agents. This page installs it, runs a small market, scores a simple agent on that market and explains the result, in about five minutes.
The snippets on this page run in order in one Python session, and each one builds on the last. A block labeled Output shows what the code above it prints on tradefloor 0.8.6 with the default preset. To run the whole page at once, download quickstart.py and run python quickstart.py.
Install
pip install tradefloor
tradefloor needs Python 3.11 or later and installs no other packages. Install lists the platforms with prebuilt wheels and the extras that some features need.
import tradefloor as tf print(tf.__version__, tf.preset_record()["preset"])
0.8.6 pt-v20
The first value is the package version. The second is the default preset, the named and frozen set of model coefficients a market runs on when you do not choose one.
Run a small market
import struct
universe = tf.Universe.random(5, seed=11) # five made-up companies
engine = tf.Engine(seed=42, universe=universe) # a market on the default preset
engine.run_days(20) # run it for 20 trading days
bars = engine.bars(grain="day") # one row per company per day
print(bars.num_rows, "daily bars:", ", ".join(bars.columns))
closes = struct.unpack("<5d", engine.prices()) # each company's price now
for company, close in zip(universe, closes):
print(f"{company.ticker} {company.sector:<22} "
f"{company.initial_price:7.2f} -> {close:7.2f}")100 daily bars: day, bar, instrument_id, open, high, low, close, volume AAA technology 135.37 -> 133.98 AAB financial_services 282.62 -> 290.24 AAC healthcare 28.59 -> 29.34 AAD energy 12.73 -> 13.42 AAE consumer_discretionary 31.22 -> 32.41
The universe is the list of companies the market trades, in order. Universe.random(5, seed=11) makes five companies with made-up tickers, sectors and fundamentals, and it makes the same five every time. The engine's seed, 42, fixes every random draw the market takes, so this block prints the same numbers on every run.
The first line of output counts 100 daily bars: one for each of the 5 companies on each of the 20 days, each with an open, high, low, close and volume. Each line after it is one company: its ticker, its sector, its price on day zero and its price when the 20 days ended. AAA, a technology company, started at 135.37 and ended at 133.98.
bars is an Arrow table, which pandas, polars, pyarrow and duckdb read without copying. engine.prices() returns each company's last price as little-endian float64 bytes in roster order, and struct.unpack("<5d", ...) reads the five of them with no extra package. engine.truth() gives each company's fair value and the factors behind every price move. Engine and data lists every table.
Score a simple agent
An agent is any object with an act(obs) method. The harness calls it at every decision step, six a day by default, and act returns the orders to send: a dict of ticker to a number of shares, positive to buy and negative to sell, or None to send nothing.
class EqualWeight:
"""Put about 100,000 into each company at the first step, then hold."""
def act(self, obs):
if obs.step == 0:
return {ticker: int(100_000 / price) # a number of shares
for ticker, price in zip(obs.tickers, obs.prices)}
return None # hold
scores = tf.evaluate({"equal": EqualWeight()},
seed=42, universe=universe, days=20)
print(scores["equal"])Scorecard('equal', pnl=13,036, return=+1.30%, trades=5, impact=+0.38bps, sharpe=+1.62, vol=10.4%, in_market=100%, exposure=0.51x)tf.evaluate builds its own market from the seed and universe you pass, gives the agent 1,000,000 in cash and runs it for 20 days of six steps each. The orders fill against the simulated order book, so every trade pays the spread and moves the price a little. The quantities are shares, and a value such as 0.2 buys a fifth of one share (shares and portfolio weights).
Understand the result
| Field | Here | What it means |
|---|---|---|
pnl | 13,036 | Net worth at the end minus the 1,000,000 the agent started with. |
return | +1.30% | The same profit as a percentage of the starting cash. |
trades | 5 | Fills. Each of the five market orders here, one per company, filled at once and counts once. A limit order counts once for each part that fills. |
impact | +0.38bps | How far the agent's own trades moved the prices of what it traded, against the same market with nobody trading, weighted by the money traded. Positive means the move cost the agent. |
sharpe | +1.62 | The mean daily return over its standard deviation, annualized over 252 days, with no risk-free rate subtracted. Under 20 days it prints as n/a (short run). |
vol | 10.4% | The annualized volatility of the daily returns. |
in_market | 100% | The share of steps that ended with a position open. |
exposure | 0.51x | The average of all positions, long and short, as a multiple of net worth. 1.0 is fully invested. |
The scorecard adds errors=N when a step raised or the market refused an order, and the lines are in scores["equal"].errors. Rejected orders explains each kind of line and what to change.
A profit on its own says little, so score a reference agent on the same market:
scores = tf.evaluate({"equal": EqualWeight(),
"buy_and_hold": tf.baselines.BuyAndHold()},
seed=42, universe=universe, days=20)
print(scores["buy_and_hold"])
print({name: round(gap) for name, gap in tf.versus_buy_and_hold(scores).items()})Scorecard('buy_and_hold', pnl=23,765, return=+2.38%, trades=5, impact=+0.43bps, sharpe=+1.63, vol=19.3%, in_market=100%, exposure=0.94x)
{'equal': -10729}Each agent trades its own copy of the same starting market. Buy-and-hold put nearly all its cash into the five companies (exposure 0.94x) and made 23,765. The equal-weight agent put in about half (0.51x) and made 13,036, so versus_buy_and_hold puts it 10,729 behind. This is one market on one seed, and another seed can reverse the order, so compare agents across seeds before you draw a conclusion.
Reproducing a run
Run this page again on tradefloor 0.8.6 and every number comes out the same. The market depends on the universe, the economy on day zero, the seed, the preset and the package version. It also depends on the agent's orders, because orders move prices. Reproducibility lists each condition, says which ones hold across platforms and releases, and shows how to save a run as a manifest that someone else can replay.
Next steps
Compare agents across seeds
One seed is one sample. tf.rank runs the same agents on many seeds and counts, seed by seed, which one did better, as Compare strategies shows.
Fork a market
Copy a running market, change one input in one copy and run both on. The copies share their past exactly, so a later difference comes from the change. Fork a market walks through it.
Connect an LLM
Run an agent built with the OpenAI Agents SDK, PydanticAI, LangGraph or your own model call, and record its answers so the run replays without the model. LLM adapters shows how.
Train an RL policy
tradefloor.gym wraps a market as a Gymnasium environment, after pip install "tradefloor[rl]", and Gym environment documents it.
If something on this page fails, Troubleshooting lists the usual causes, and the Glossary defines the terms used here.