Backtest components
from flox.backtest import SimulatedExecutor, BacktestResult, BacktestStats
from flox.backtest import SLIPPAGE_NONE, SLIPPAGE_FIXED_TICKS, SLIPPAGE_FIXED_BPS, SLIPPAGE_VOLUME_IMPACT
from flox.backtest import QUEUE_NONE, QUEUE_TOB, QUEUE_FULL
from flox.engine import Engine, SignalBuilder
BacktestStats
Returned by BacktestRunner.run_csv() / run_ohlcv() and BacktestResult.stats().
| Field |
Type |
Description |
total_trades |
int |
Round-trip trade count |
winning_trades |
int |
Winning trades |
losing_trades |
int |
Losing trades |
max_consecutive_wins |
int |
Max consecutive wins |
max_consecutive_losses |
int |
Max consecutive losses |
initial_capital |
float |
Starting capital |
final_capital |
float |
Ending capital |
total_pnl |
float |
Gross P&L |
total_fees |
float |
Total fees paid |
net_pnl |
float |
Net P&L after fees |
gross_profit |
float |
Sum of winning trades |
gross_loss |
float |
Sum of losing trades |
max_drawdown |
float |
Max drawdown (absolute) |
max_drawdown_pct |
float |
Max drawdown (%) |
win_rate |
float |
Winning trade ratio |
profit_factor |
float |
Gross profit / gross loss |
avg_win |
float |
Average winning trade |
avg_loss |
float |
Average losing trade |
avg_win_loss_ratio |
float |
avg_win / avg_loss |
avg_trade_duration_ns |
float |
Average trade duration (ns) |
median_trade_duration_ns |
float |
Median trade duration (ns) |
max_trade_duration_ns |
float |
Longest trade (ns) |
sharpe |
float |
Annualized Sharpe ratio |
sortino |
float |
Sortino ratio |
calmar |
float |
Calmar ratio |
time_weighted_return |
float |
Time-weighted return |
return_pct |
float |
Net return (%) |
start_time_ns |
int |
Backtest start timestamp (ns) |
end_time_ns |
int |
Backtest end timestamp (ns) |
SimulatedExecutor
Fills orders from simulated market data. Use directly when you need more control than BacktestRunner provides.
from flox.backtest import SimulatedExecutor, SLIPPAGE_FIXED_BPS, QUEUE_TOB
exec = SimulatedExecutor()
exec.set_default_slippage(SLIPPAGE_FIXED_BPS, bps=2.0)
exec.set_queue_model(QUEUE_TOB, depth=1)
exec.submit_order(order_id, "buy", price, qty)
exec.on_bar(symbol, close_price)
exec.on_trade(symbol, price, is_buy)
exec.advance_clock(ts_ns)
Constructor
Methods
| Method |
Description |
submit_order(id, side, price, qty, order_type="market", symbol=1) |
Submit an order (side: "buy"/"sell", order_type: "market"/"limit") |
cancel_order(order_id) |
Cancel an order |
cancel_all(symbol) |
Cancel all orders for a symbol |
on_bar(symbol, close_price) |
Feed a bar close |
on_trade(symbol, price, is_buy) |
Feed a trade |
on_trade_qty(symbol, price, qty, is_buy) |
Feed a trade with quantity (enables queue-fill simulation) |
on_best_levels(symbol, bid_price, bid_qty, ask_price, ask_qty) |
Feed top-of-book snapshot |
advance_clock(timestamp_ns) |
Advance simulated time |
set_default_slippage(model, ticks=0, tick_size=0.0, bps=0.0, impact_coeff=0.0) |
Configure slippage for all symbols |
set_symbol_slippage(symbol, model, ticks=0, tick_size=0.0, bps=0.0, impact_coeff=0.0) |
Per-symbol slippage override |
set_queue_model(model, depth=1) |
Configure limit order queue simulation |
close() |
Free resources |
Properties
| Property |
Type |
Description |
fill_count |
int |
Number of fills generated |
Slippage models
| Constant |
Value |
Description |
SLIPPAGE_NONE |
0 |
No slippage |
SLIPPAGE_FIXED_TICKS |
1 |
Fixed tick count per fill |
SLIPPAGE_FIXED_BPS |
2 |
Fixed basis points per fill |
SLIPPAGE_VOLUME_IMPACT |
3 |
Volume-proportional impact |
Queue models
| Constant |
Value |
Description |
QUEUE_NONE |
0 |
Fill limit orders immediately at price |
QUEUE_TOB |
1 |
Fill only when price trades through level |
QUEUE_FULL |
2 |
Model queue position; fill as volume passes |
BacktestResult
Aggregates fills from a SimulatedExecutor into statistics and an equity curve.
from flox.backtest import BacktestResult
result = BacktestResult(initial_capital=10_000.0, fee_rate=0.0004)
result.ingest_executor(exec)
stats = result.stats()
Constructor
BacktestResult(
initial_capital: float = 100000.0,
fee_rate: float = 0.0001,
use_percentage_fee: bool = True,
fixed_fee_per_trade: float = 0.0,
risk_free_rate: float = 0.0,
annualization_factor: float = 252.0
)
Methods
| Method |
Returns |
Description |
record_fill(order_id, symbol, side, price, qty, timestamp_ns) |
None |
Record a single fill |
ingest_executor(executor) |
None |
Drain all fills from a SimulatedExecutor |
stats() |
BacktestStats |
Compute and return statistics |
equity_curve_size() |
int |
Number of equity curve points |
write_equity_curve_csv(path) |
bool |
Write equity curve to CSV |
close() |
None |
Free resources |
Engine
Bulk backtesting engine. Loads OHLCV data once, then runs a SignalBuilder against it.
from flox.engine import Engine, SignalBuilder
engine = Engine(initial_capital=10_000.0, fee_rate=0.0004)
engine.load_csv("data/btcusdt.csv", symbol="BTCUSDT")
signals = SignalBuilder()
close = engine.close()
for i in range(1, len(close)):
if close[i] > close[i-1]:
signals.buy(engine.ts()[i], 0.01, "BTCUSDT")
else:
signals.sell(engine.ts()[i], 0.01, "BTCUSDT")
stats = engine.run(signals)
Constructor
Engine(initial_capital: float = 100000.0, fee_rate: float = 0.0001)
Methods
| Method |
Returns |
Description |
load_csv(path, symbol="") |
None |
Load OHLCV CSV (columns: timestamp, open, high, low, close, volume) |
load_ohlcv(timestamps, opens, highs, lows, closes, volumes, symbol="") |
None |
Load raw OHLCV arrays |
bar_count(symbol="") |
int |
Number of bars loaded |
run(signals, default_symbol="") |
BacktestStats |
Run a SignalBuilder and return statistics |
Data accessors
| Method |
Returns |
Description |
ts(symbol="") |
List[int] |
Timestamps (ns) |
open(symbol="") |
List[float] |
Open prices |
high(symbol="") |
List[float] |
High prices |
low(symbol="") |
List[float] |
Low prices |
close(symbol="") |
List[float] |
Close prices |
volume(symbol="") |
List[float] |
Volumes |
Properties
| Property |
Type |
Description |
symbols |
List[str] |
Registered symbol names |
SignalBuilder
Builds a signal list to pass to Engine.run().
signals = SignalBuilder()
signals.buy(ts_ms, 0.01, "BTCUSDT")
signals.sell(ts_ms, 0.01)
stats = engine.run(signals)
Methods
| Method |
Description |
buy(ts_ms, qty, symbol="") |
Add a market long entry at ts_ms |
sell(ts_ms, qty, symbol="") |
Add a market short entry at ts_ms |
limit_buy(ts_ms, price, qty, symbol="") |
Add a limit long entry |
limit_sell(ts_ms, price, qty, symbol="") |
Add a limit short entry |
clear() |
Clear all signals |
__len__() |
Number of signals |