Paper trading with PaperBroker¶
PaperBroker runs your strategy against a live market data feed but routes orders to a SimulatedExecutor instead of a real exchange. The fill model is the same one backtests use: configurable slippage, queue position tracking, top-of-book or full-depth modes. A strategy that survives backtest gets to rehearse against current market conditions before any real capital is at stake.
Install¶
If you also want to drive the broker off a live ccxt feed, add [ccxt]:
The broker itself does not depend on ccxt; it is feed-agnostic.
Quick start¶
import flox_py as flox
from flox_py.paper import PaperBroker
registry = flox.SymbolRegistry()
sym = registry.add_symbol("paper", "BTCUSDT", tick_size=0.01)
broker = PaperBroker(
registry=registry,
default_slippage_model="fixed_bps",
default_slippage_params={"bps": 5.0}, # 5 bps against the taker
)
class MyStrategy(flox.Strategy):
def on_trade(self, ctx, trade):
if ctx.is_flat():
self.market_buy(0.01)
broker.runner.add_strategy(MyStrategy([int(sym)]))
broker.start()
# In your feed loop, forward each trade:
for ts_ns, price, qty, is_buy in your_live_feed():
broker.observe_trade(int(sym), price, qty, is_buy, ts_ns)
broker.stop()
print(broker.fills_list())
How the wiring works¶
your feed ──► broker.observe_trade ──┬─► runner.on_trade ──► strategy
│
└─► sim.on_trade_qty (queue tracker)
strategy ──► self.market_buy() ──► broker._route_signal ──► sim.submit_order
│
▼
fill
Every observed trade goes both into the runner (so strategies see it) and into the simulator (so its queue tracker stays current). When the strategy emits a signal, the broker's signal callback maps it to SimulatedExecutor.submit_order. No network call, no exchange. The fill comes back through sim.fills_list() once the simulator's matching engine settles it against subsequent observed trades.
Slippage and queue model¶
PaperBroker exposes the same slippage / queue knobs the backtest engine uses. Defaults are:
- Slippage:
none - Queue model: whatever
SimulatedExecutor's default is (typically TOB)
Override per-broker at construction:
broker = PaperBroker(
registry=registry,
default_slippage_model="volume_impact",
default_slippage_params={"impact_coeff": 0.002},
)
Or per-symbol after construction:
What works, what does not¶
Routed to the simulator:
MARKET(market_buy,market_sell)LIMIT(limit_buy,limit_sell)CANCELfor a knownorder_idCANCEL_ALLfor a symbol
Not routed (yet). These signal types fall through to the user's optional on_signal callback so you can log or approximate them externally:
STOP_MARKET/STOP_LIMITTAKE_PROFIT_MARKET/TAKE_PROFIT_LIMITTRAILING_STOP/TRAILING_STOP_PERCENTCLOSE_POSITIONMODIFY
The reason is plain: SimulatedExecutor.submit_order accepts market / limit types only. Stops and trailing variants need a richer order machine. That gap closes as part of W14 follow-up; until then, keep paper-mode strategies on market and limit.
Hooking in ccxt for a live feed¶
import asyncio
import ccxt.pro as ccxt
from flox_py import SymbolRegistry
from flox_py.paper import PaperBroker
async def main():
registry = SymbolRegistry()
btc = registry.add_symbol("ccxt", "BTCUSDT", tick_size=0.01)
broker = PaperBroker(
registry=registry,
default_slippage_model="fixed_bps",
default_slippage_params={"bps": 5.0},
)
# ... add_strategy / start as in the quick start ...
broker.start()
exchange = ccxt.binance()
try:
while True:
trades = await exchange.watch_trades("BTC/USDT")
for t in trades:
broker.observe_trade(
int(btc),
float(t["price"]),
float(t["amount"]),
t["side"] == "buy",
int(t["timestamp"]) * 1_000_000,
)
finally:
broker.stop()
await exchange.close()
print(broker.fills_list())
asyncio.run(main())
This is the minimal pattern. Production setups will add error handling around watch_trades, persistent reconnection, and probably a separate task for watch_order_book plus broker.observe_book_snapshot so the simulator's queue tracker has top-of-book context.
See also¶
- Backtest with realistic fills. The same
SimulatedExecutorpowering paper mode runs the backtest fills. - CCXT adapter. The live order broker for the same flox pipeline; swap in
PaperBrokerto rehearse without sending live orders. - Record and replay tapes. Recorded tapes can drive
PaperBrokerinstead of a live feed for repeatable rehearsals.