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
AAPL, first five days
ticker day close daily_return
AAPL 1 544.10 -3.31%
AAPL 2 528.03 -2.95%
AAPL 3 524.05 -0.75%
AAPL 4 522.97 -0.21%
AAPL 5 507.80 -2.90%
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