Backtest Results
Load and analyze prior backtest runs from notebook scripts.
runner.run
runner.run(strategy, data, date_range, params, *, timeframe=None, starting_equity=100000, position_mode="sequential", slippage=0)
Run a strategy across a date range with one parameter set. Works for .py and .rs.
Runs the same engine the analyzer uses, returns a full BacktestResults with all analytics. Call from any Python context: editor Run, notebook cell. Works on Rust TBBO strategies too: you analyze the trades in Python while the strategy logic stays in Rust.
Runs through the optimization worker thread (separate Python subprocess), so it doesn't block the chart, the editor pool, or each other when called from inside an editor Run.
Parameters
strategy(str): Path to a .py or .rs file (relative to workspace strategies/ folder, or absolute)data(str): Path to a .parquet file (relative to workspace data/ folder, or absolute)date_range((str, str)): Inclusive ISO date tuple, e.g. ("2025-01-01", "2025-01-31")params(dict): Strategy parameter values. Names must match the strategy file declarations.
Returns
BacktestResults
Example
from quant_charts import runner
r = runner.run(
strategy='strategies/ma_cross.py',
data='ES.parquet',
date_range=('2025-01-01', '2025-01-31'),
params={'fast': 10, 'slow': 30},
)
print(r.summary())
r.plot_equity()
import matplotlib.pyplot as plt; plt.show()
Notes
- For .rs strategies the analyzer engine is invoked exactly as the chart-attached path; trade results are identical.
- Sharing _runLock with the analyzer: if the Analyzer is mid-optimize, runner.run() blocks until it finishes (a "runner: waiting for analyzer" log line is printed while waiting).
- Worked TBBO/Rust example: notebooks/built-in/multi_day.ipynb.
runner.optimize
runner.optimize(strategy, data, date_range, grid, *, metric="sharpe", timeframe=None)
Run a parameter grid sweep across a date range. Returns SweepResults.
The analyzer's full grid sweep, callable from Python. Each grid axis must be uniformly spaced (use list(range(...)) or numpy.linspace). Works for .py and .rs strategies; Rust strategies are routed through the same worker pool the Analyzer uses.
Parameters
strategy(str): Path to a .py or .rs filedata(str): Path to a .parquet data filedate_range((str, str)): Inclusive ISO date tuplegrid(dict[str, list]): Param-name to uniformly-spaced value listmetric(str, default"sharpe"): "sharpe", "profit_factor", "total_pnl", "win_rate", "avg_snr", "median_snr", "recovery_factor", "payoff_ratio"
Returns
SweepResults
Example
from quant_charts import runner
sweep = runner.optimize(
strategy='strategies/momentum.rs',
data='ES_TBBO.parquet',
date_range=('2025-01-01', '2025-01-31'),
grid={
'period': list(range(10, 31, 2)),
'threshold': [0.3, 0.5, 0.7],
},
metric='sharpe',
)
print(sweep.results_df.head())
sweep.scatter_3d('period', 'threshold', 'sharpeRatio')
sweep.best().plot_equity()
Notes
- Grid axes MUST be uniformly spaced. The engine sweeps min/max/step; arbitrary value lists are rejected.
- sweep.best() lazily fetches trades for the top-scoring combo from the in-memory trade store.
qc.run / qc.optimize / qc.wfa
qc.run / qc.optimize / qc.wfa(strategy, data, date_range, params|grid, ...)
Run a fresh backtest, sweep, or WFA inline from a notebook. Returns a BacktestResults (run) or SweepResults (optimize / wfa).
Inline backtest entry points. No UI involvement. Same engine the Analyzer uses; results are bit-for-bit identical.
For sweeps, grid axes must be uniformly spaced (use range(...) or numpy.linspace). For WFA, pass in_sample_days and out_of_sample_days plus a grid.
Returns
BacktestResults | SweepResults
Example
import quant_charts as qc
r = qc.run('strategies/foo.py',
data='ES.parquet',
date_range=('2026-04-15', '2026-04-15'),
params={'fast': 9, 'slow': 21})
r.summary()
opt = qc.optimize('strategies/foo.py',
data='ES.parquet',
date_range=('2026-04-15', '2026-04-15'),
grid={'fast': range(5, 21), 'slow': range(15, 50)})
opt.top(10)
qc.runs / load_run / save_run / delete_run
qc.runs / load_run / save_run / delete_run() | (name)
Saved-runs registry. List, load, save, or delete named exports.
Saved runs persist with an index.json registry. Names are alphanumeric plus _-.. Saved runs are explicit and never overwritten by future analyzer runs; call delete_run(name) to clean up.
Returns
pd.DataFrame | BacktestResults | dict | None
Example
import quant_charts as qc
qc.runs() # DataFrame: name, strategy, dateRange, kind, nTrades, savedAt
r = qc.load_run('ema_cross_a')
qc.save_run('my_combo') # persist whatever\'s in memory
qc.delete_run('my_combo')
qc.cancel
qc.cancel()
Cancel the in-flight runner call. Safe from a signal handler or another thread.
Sends a cancel signal to the runner bridge. The currently blocked qc.run / qc.optimize / qc.wfa call raises RunnerCancelled on its next poll. No-op when no call is in flight.
Use case: Jupyter cell that started a long sweep; press the stop button (which triggers cancel) without waiting for the worker to finish naturally.
Returns
None
Example
import quant_charts as qc
import threading
def stop_after(seconds):
import time
time.sleep(seconds)
qc.cancel()
threading.Thread(target=stop_after, args=(60,), daemon=True).start()
# Will be cancelled after 60s if still running:
opt = qc.optimize('strategies/foo.py', data='ES.parquet',
date_range=('2026-04-01', '2026-04-30'),
grid={'fast': range(5, 30), 'slow': range(20, 60)})
load_csv
load_csv(name_or_path?)
Load a CSV trade export into a BacktestResults for analysis.
Loads trades from a CSV file exported via the Analyzer's Export button. The CSV is parsed back into a full BacktestResults with equity curve, so all analysis methods (summary, plot_equity, monte_carlo, filtering) work the same as for an inline qc.run().
With no argument, loads the most recent CSV in the exports/ folder.
Parameters
name_or_path(str, defaultNone): CSV filename (e.g."my_backtest.csv"), full path, orNonefor most recent
Returns
BacktestResults
Example
from quant_charts.results import load_csv
r = load_csv() # most recent export
r = load_csv("MA_Cross_2024.csv") # specific file
r.summary()
r.longs().plot_equity()
r.monte_carlo(1000).plot()
Notes
- CSV files are saved to the exports/ folder when you click Export in the Analyzer top bar.
- Tags are preserved.
r.by_tag("morning")works on CSV-loaded results. - Use
list_exports()to see available CSV files.
list_exports
list_exports()
List available CSV trade exports with dates and sizes.
Prints a formatted table of all CSV files in the exports/ folder, sorted by modification time (newest first). Returns a list of filenames.
Returns
list[str]
Example
from quant_charts.results import list_exports
files = list_exports()
Output
>>> list_exports()
Filename Modified Size
----------------------------------------------------------------------
MA_Cross_2024-01-01_to_2024-06-30.csv 2024-07-15 14:32 12.3K
RSI_Scalper_2024-03-01_to_2024-03-31.csv 2024-07-14 09:18 4.1K
r.summary
r.summary()
Print comprehensive stats table.
Displays total trades, win rate, PnL, profit factor, Sharpe ratio, max drawdown, MAE/MFE, streaks, average duration, and more.
Returns
None (prints table)
Example
r.summary()
r.trades
All trades as a pandas DataFrame.
Columns: id, side, entryTime, exitTime, entryPrice, exitPrice, pnl, pnlPercent, mae, mfe, snr, duration, exitReason, tags, stopLoss, takeProfit. Also has entryTime_et / exitTime_et (Eastern Time).
Returns
pandas DataFrame
Example
# Standard pandas operations on trades
r.trades.groupby('side')['pnl'].describe()
r.trades[r.trades['duration'] < 60000]
r.trades.query('exitReason == "sl_hit"')['pnl'].hist()
r.winners / r.losers
r.winners / r.losers()
Filter by PnL sign. Returns a new BacktestResults.
All filter methods return a new BacktestResults. The original is never modified. The filtered result has its own rebuilt equity curve.
Returns
BacktestResults
Example
r.winners().summary()
r.losers().plot_equity()
r.longs / r.shorts
r.longs / r.shorts()
Filter by trade side.
Returns
BacktestResults
Example
r.longs().winners().summary() # winning longs
r.by_tag
r.by_tag(tag_name)
Filter to trades that have a specific tag.
Parameters
tag_name(str, defaultrequired): Tag name to filter by
Returns
BacktestResults
Example
r.by_tag("morning").summary()
r.where
r.where(**kwargs)
Django-style field lookups for flexible filtering.
Operators: field=value (exact), field__gt (>), field__lt (<), field__gte (>=), field__lte (<=), field__ne (!=), field__in (list), field__contains (string), field__hour (ET hour), field__dayofweek (0=Mon).
Three-part accessor for timestamps: entryTime__hour__gte=9
Returns
BacktestResults
Example
# Morning longs that won
r.longs().where(
entryTime__hour__gte=9,
entryTime__hour__lte=11,
pnl__gt=0
).summary()
r.plot_equity
r.plot_equity(by_trade?)
Plot the equity curve.
Parameters
by_trade(bool, defaultTrue):True= x-axis is trade number,False= time
Returns
matplotlib Figure
Example
r.plot_equity()
r.winners().plot_equity()
r.monte_carlo
r.monte_carlo(n?, method?)
Monte Carlo simulation for risk assessment.
Shuffles or bootstraps trade PnL to estimate range of outcomes. Returns a MonteCarloResult with .plot(), .summary(), .percentile(), .ruin_probability(), and .confidence_interval() methods.
Parameters
n(int, default1000): Number of simulationsmethod(str, default"bootstrap"):"bootstrap"or"shuffle"
Returns
MonteCarloResult
Example
mc = r.monte_carlo(1000)
mc.plot(ci=0.95)
mc.summary()
mc.ruin_probability(5000)
r.simulate_sl_tp
r.simulate_sl_tp(sl, tp)
Simulate different SL/TP using MAE/MFE data (instant, no re-run).
Parameters
sl(float, defaultrequired): Stop loss in dollarstp(float, defaultrequired): Take profit in dollars
Returns
BacktestResults
Example
sim = r.simulate_sl_tp(sl=30, tp=15)
print(f"Original: ${r.trades.pnl.sum():.2f}")
print(f"Simulated: ${sim.trades.pnl.sum():.2f}")
r.monthly_returns
r.monthly_returns()
Year x Month pivot table of PnL.
Returns
pandas DataFrame
Example
r.monthly_returns()