Python Bindings¶
Run Flox backtests from Python. Strategies stay in Python (numpy/pandas), execution runs in C++ with GIL released and multi-threaded batch support.
Build¶
cmake -B build \
-DFLOX_ENABLE_BACKTEST=ON \
-DFLOX_ENABLE_PYTHON=ON \
-DCMAKE_BUILD_TYPE=Release
cmake --build build
Requires: Python 3.9+, pybind11, numpy (pip install pybind11 numpy).
The module builds at build/python/flox_py.cpython-*.so.
Quick Start¶
import numpy as np
import flox_py as flox
engine = flox.Engine(initial_capital=100_000, fee_rate=0.0001)
# Load OHLCV data
engine.load_bars_df(timestamps, opens, highs, lows, closes, volumes)
# Create signals (all market orders)
signals = flox.make_signals(
timestamps=np.array([1704067200000, 1704068400000], dtype=np.int64),
sides=np.array([0, 1], dtype=np.uint8), # 0=buy, 1=sell
quantities=np.array([0.5, 0.5]),
)
stats = engine.run(signals)
print(f"PnL: {stats['net_pnl']:.2f}, Sharpe: {stats['sharpe']:.4f}")
Loading Bar Data¶
Two options:
Load once, then run as many backtests as needed against the same data.
Creating Signals¶
make_signals() converts numpy arrays into a packed struct array:
signals = flox.make_signals(
timestamps, # int64 — unix ms, us, or ns (auto-normalized)
sides, # uint8 — 0=buy, 1=sell
quantities, # float64 — position size
prices, # float64 — limit price (optional, default: market)
types, # uint8 — 0=market, 1=limit (optional, default: market)
)
For market-only strategies, prices and types can be omitted:
Single Run¶
Returns a dict with all backtest metrics:
| Key | Description |
|---|---|
total_trades |
Round-trip trade count |
net_pnl |
Gross PnL minus all fees |
total_fees |
Total execution fees |
sharpe |
Annualized Sharpe ratio |
sortino |
Annualized Sortino ratio |
calmar |
Calmar ratio |
max_drawdown |
Peak-to-trough drawdown |
max_drawdown_pct |
Drawdown as percentage |
win_rate |
Winning trade fraction |
profit_factor |
Gross profit / gross loss |
return_pct |
Net return percentage |
Batch Execution¶
Run N backtests in parallel using C++ threads:
all_stats = engine.run_batch(
[signals_1, signals_2, ..., signals_n],
threads=0, # 0 = use all cores
symbol=1,
)
GIL released. Threads run independent copies, nothing shared.
Permutation Testing Example¶
import flox_py as flox
import numpy as np
engine = flox.Engine(initial_capital=100_000, fee_rate=0.0001)
engine.load_bars_df(timestamps, opens, highs, lows, closes, volumes)
rng = np.random.default_rng(42)
signal_sets = []
for _ in range(1000):
# Shuffle returns
rets = np.diff(np.log(closes))
rng.shuffle(rets)
shuffled = closes[0] * np.exp(np.cumsum(np.concatenate(([0], rets))))
# Your strategy logic here
sigs = my_strategy(shuffled, timestamps)
signal_sets.append(sigs)
# 1000 backtests in ~50ms
results = engine.run_batch(signal_sets)
pnls = [r["net_pnl"] for r in results]
p_value = np.mean([p >= results[0]["net_pnl"] for p in pnls])
Performance¶
Benchmarked on Apple M-series, 100K bars, MA cross strategy:
| Mode | Time | vs Python |
|---|---|---|
| Single run | 0.6ms | 33x |
| 1000 permutations | 53ms | 400x |
| Bar loading | 0.5ms | — |
See Also¶
- Python API Reference — complete Python API documentation
- Backtesting — C++ backtest guide
- Grid Search — parameter optimization