openquant.research
Pure Python. Signatures are as written in the source; descriptions are the docstrings. All Python modules.
Classes
ResearchDataset
class ResearchDatasetInputs for one research-flywheel run, as parallel per-bar lists.
Built by make_synthetic_futures_dataset and consumed by run_flywheel_iteration and run_flywheel_grid, which pass the fields to openquant.pipeline.run_mid_frequency_pipeline_frames.
Attributes
timestamps: list[str]Bar timestamps as
"%Y-%m-%d %H:%M:%S"strings.close: list[float]Close price of the primary traded instrument per bar.
model_probabilities: list[float]Model probability per bar.
model_sides: list[float]Model side per bar (+1.0 or -1.0).
asset_prices: list[list[float]]One row per bar, one column per asset, in
asset_namesorder.asset_names: list[str]Asset names; the first is the primary instrument.
periods_per_year: float or None, default NoneBars a year, used to annualise Sharpe ratios and volatility. None derives it from the spacing of
timestamps(openquant.pipeline.infer_periods_per_year).make_synthetic_futures_datasetsets it to 98,280 (one-minute bars, 390 a session, 252 sessions a year).
Functions
make_synthetic_futures_dataset
make_synthetic_futures_dataset(
n_bars: int = 192,
seed: int = 7,
asset_names: list[str] | None = None,
) -> ResearchDatasetBuild a deterministic synthetic multi-asset futures dataset.
The first asset is treated as the primary traded instrument (e.g., crude oil), while the other assets provide cross-asset context for allocation/risk.
The bars are **one minute** apart, starting 2024-01-01 09:30:00, so the dataset's periods_per_year is openquant.pipeline.MINUTE_BARS_PER_YEAR (98,280 = 390 bars a 6.5-hour session times 252 sessions); every annualised figure the research loop reports on it uses that factor, not the daily 252.
research_run_manifest
research_run_manifest(
config: dict[str, Any],
dataset_meta: dict[str, Any] | None = None,
) -> dict[str, Any]Build deterministic run metadata for notebook/script parity.
run_flywheel_grid
run_flywheel_grid(
dataset: ResearchDataset,
configs: list[dict[str, Any]],
*,
run_names: list[str] | None = None,
) -> dict[str, Any]Execute multiple flywheel configs and return a ranked leaderboard.
run_flywheel_iteration
run_flywheel_iteration(
dataset: ResearchDataset,
*,
config: dict[str, Any] | None = None,
) -> dict[str, Any]Run the mid-frequency pipeline once and add cost estimates and promotion checks.
Runs openquant.pipeline.run_mid_frequency_pipeline_frames on dataset, then estimates trading costs from the backtest frame and evaluates promotion gates. These cost and Sharpe figures are heuristics for comparing configurations, not a cost model:
turnoveris the sum of absolute position changes between bars.periods_per_yearisconfig["periods_per_year"]if given, elsedataset.periods_per_year, else derived fromdataset.timestamps(openquant.pipeline.infer_periods_per_year). It is passed to the pipeline, so it also annualisesrisk["realized_sharpe"]and the portfolio stage.bar_volis the sample standard deviation of the per-bar strategy returns, andrealized_volisbar_vol * sqrt(periods_per_year).cost_per_turnis(commission_bps + spread_bps) * 1e-4 + slippage_vol_mult * bar_vol * sqrt(252) * 1e-3, and the estimated total cost isturnover * cost_per_turn. The slippage term scales with the per-bar volatility and does not depend onperiods_per_year: it is the formula the loop has always used (on daily bars it equalsslippage_vol_mult * realized_vol * 1e-3).gross_total_returnis the final equity minus 1.0;net_total_returnsubtracts the estimated total cost.net_sharpeis the Sharpe ratio of the per-bar returns net of costs: each bar's strategy return minusabs(position change into that bar) * cost_per_turn(the first bar pays nothing), so the per-bar costs add up to the estimated total cost. It is the mean over the sample standard deviation of those net returns, timessqrt(periods_per_year), and 0 when the deviation is 0.
Parameters
dataset: ResearchDatasetPipeline inputs.
config: dict[str, Any] or None, default NoneOverrides for the defaults:
cusum_threshold(0.001),num_classes(2),step_size(0.1),risk_free_rate(0.0, annual),confidence_level(0.05) andperiods_per_year(None: from the dataset, see above), passed to the pipeline;commission_bps(1.5),spread_bps(2.0) andslippage_vol_mult(8.0) for the cost estimate;min_net_sharpe(0.30) andmin_realized_sharpe(0.25) for promotion. Unknown keys are ignored.
Returns
- dict[str, Any]
The pipeline output (
events,signals,portfolio,risk,backtest,leakage_checks,frames) plus:costs:turnover,periods_per_year,bar_vol,realized_vol,cost_per_turn,estimated_total_cost,gross_total_return,net_total_returnandnet_sharpe.promotion: booleanspassed_realized_sharpe(pipeline realized Sharpe>= min_realized_sharpe),passed_net_sharpe,passed_alignment_guard,passed_event_order_guardandpromote_candidate(all of them).summary: the one-rowopenquant.pipeline.summarize_pipelineframe with columnsturnover,realized_vol,estimated_cost,gross_total_return,net_total_returnandnet_sharpeadded.