pybroker-optimize
GitHub指导使用PyBroker和Optuna进行量化策略超参数优化,涵盖网格/TPE采样、滚动窗口验证、评分函数编写及结果解析,确保回测无未来数据泄露。
Trigger Scenarios
Install
npx skills add edtechre/pybroker --skill pybroker-optimize -g -y
SKILL.md
Frontmatter
{
"name": "pybroker-optimize",
"description": "Tune PyBroker strategy hyperparameters with Optuna-backed search using the bundled PyBroker wiki references generated from the local docs. Use when an agent needs to declare tunable values with pybroker.hyperparam, run Strategy.optimize with grid, TPE, or random samplers, choose n_trials, direction, train_size, or seed, write score functions over TestResult metrics, wire hyperparams into indicator kwargs or ctx.hyperparam via add_execution(hyperparams=...), pass custom Optuna samplers or a supplied study, inspect OptimizeResult, WindowOptimizeResult, or study.trials_dataframe(), run walkforward optimization with windows, pin winning values with backtest(params=...), or debug failed trials and grid explosions."
}
PyBroker Optimizer
Overview
Tune PyBroker strategy hyperparameters with Strategy.optimize by declaring tunable values with pybroker.hyperparam, wiring them into indicators and execution functions, and scoring each candidate combination on a training window before the winning values are replayed on held-out test data. Covers grid, TPE, and random sampling through the integrated Optuna backend, custom Optuna samplers and studies, walkforward optimization across multiple windows, and reading results from OptimizeResult and the underlying optuna.Study.
Workflow
- Extract the optimization spec: which values to tune with their
low/high/stepranges and numeric types, the score metric anddirection, sampler and trial budget,train_size,windows,seed, caching, and whether the strategy uses models (trainable models are unsupported byoptimize). - Ask only for missing blockers. If details are absent but noncritical, make conservative assumptions and state them in the final answer or code comments.
- Read
references/wiki-index.mdto choose the smallest relevant wiki page. For nontrivial optimization work, also readreferences/optimization-patterns.md. - Build a complete runnable optimization surface:
- declare tunable values with
pybroker.hyperparam(name, default=..., low=..., high=..., step=...), usinglow == highto pin a value without searching it - attach each hyperparam where it is consumed: as an indicator keyword argument, through
add_execution(..., hyperparams=[...])read withctx.hyperparam(name), or intoset_max_long_positions/set_max_short_positions/enable_rotation(worst_rank_held=...) - write a
score_fn(result: TestResult) -> floatoverresult.metricswithNone-guards for metrics that can be undefined - run
strategy.optimize(score_fn, sampler=..., n_trials=..., seed=..., train_size=..., windows=...), with trial parallelism configured throughpybroker.set_parallel(n_jobs=...) - read
OptimizeResult:best_params,best_score,result(the held-out testTestResult),study, andwindows; pin winning values into later runs withbacktest(params=...)orwalkforward(..., params=...)
- declare tunable values with
- Validate the produced code as far as the environment allows. At minimum, run syntax checks for created Python files. Run a small-grid optimize on small local data when the repo and data make that practical.
Implementation Rules
- Treat PyBroker as a backtesting framework, not a source of financial advice. State assumptions explicitly, and never present the in-sample
best_scoreas expected performance; report the held-out test metrics fromOptimizeResult.result. - Use completed historical bar data only. Indicator logic must be lookahead-free: never index a full-length array with a negative index (it silently wraps to the end of the series, the future) and never shift future values backward; a value at bar
imay depend only on inputs at indexiand earlier. Self-test novel indicator logic with the bump-last-bar check: change only the final input bar and assert every earlier output is unchanged. - A
Hyperparam'sdefault,low,high, andstepmust all share one numeric type (all int or all float; bools are rejected),stepmust be positive,lowcannot exceedhigh, andhigh - lowmust be an exact multiple ofstep. Candidate values run fromlowtohighinclusive;low == highdeclares a fixed hyperparam that resolves in backtests and appears inbest_paramsbut is excluded from the search. - The search-space key is the hyperparam name, not the consuming keyword: with
lookback = pybroker.hyperparam("lookback", ...),pybroker.indicator("sma", sma, period=lookback)searches"lookback". ctx.hyperparam("name")works only when the same registeredHyperparamobject is passed inadd_execution(..., hyperparams=[...]); reading an unattached name raisesValueError.- Write
score_fn(result: TestResult) -> floatoverresult.metricsfields and guardOptionalmetrics (lambda r: r.metrics.sharpe if r.metrics.sharpe is not None else 0.0); aNoneor NaN score marks that trial FAILED instead of aborting the study. Usedirection="minimize"for objectives such as drawdown. - Choose the sampler by search-space size:
"grid"(the default) exhaustively enumerates every lattice combination and evaluates trials in parallel;"random"also parallelizes;"tpe"adapts to earlier trials and therefore runs sequentially. Anyoptuna.samplers.BaseSamplerinstance is also accepted; it is deep-copied and reseeded per window and must be picklable whenwindows > 1. n_trialsis required for every sampler except"grid", where it defaults to the full grid size and a smaller value samples that many combinations at random. Heed the grid-explosion warning whengrid_size * windowsexceeds 1000: coarsenstep, fix values withlow == high, or switch to"tpe"/"random"with an explicitn_trials.- Pass
seed=for reproducible optimization; it seeds the sampler and bootstrap metrics, and windowiderivesseed + i. Unlikebacktest/walkforward(defaultseed=42),optimizedefaults toseed=None, which does not reproduce. - Every trial backtests only the training split (
train_size, exclusive of0and1, default0.5); the winning combination is then replayed once on the held-out test window, whichscore_fnnever sees, producingOptimizeResult.result. - With
windows > 1, each walkforward window is tuned by its own study and the per-window winners are replayed into one continuous stitched result with cash and positions carried across window boundaries.best_params,best_score, andstudydescribe the last window only; report per-window values fromOptimizeResult.windows(WindowOptimizeResultholdsparams,study,train_score, and the window dates, but no per-window test result).study=is rejected whenwindows > 1. optimizerejects trainable model sources; pretrained models (pybroker.model(..., pretrained=True)) are supported and loaded once per train window, then reused across that window's trials. Tune trainable models insidetrain_fnwith a validation split over the train window instead.- Trial and window parallelism come only from the global
pybroker.set_parallel(n_jobs=...);optimizehas non_jobsparameter. pruner=is passed through to the created Optuna study but never triggers, because each trial is one complete backtest with no intermediate values to report; do not rely on pruning for budget control.- Pin tuned values outside of optimization with
strategy.backtest(params={...})orstrategy.walkforward(..., params=...); hyperparam defaults apply whenparamsis omitted. - Never use pandas to implement indicator or execution logic: write indicators as vectorized NumPy over
BarDataarrays (Numba@njitfor explicit loops) and readctx.*NumPy arrays in execution functions — nopd.Series/pd.DataFrameconstruction and no.rolling/.ewm/.shift/.applyin either. - An indicator returns one full-length one-dimensional array with one value per input bar, warmup left-padded with NaN — never a shortened array.
- Keep feature data out-of-band: never widen or mutate the user's input DataFrame.
- Enable caching while iterating:
pybroker.enable_data_source_cache(name)to skip refetching data, orpybroker.enable_caches(name)to also cache indicators. Callpybroker.disable_progress_bar()in agent-run scripts, and addpybroker.disable_logging()for optimize runs, which backtest once per trial and would otherwise flood context with per-run logs. StrategyConfig.exit_on_last_bardefaults toFalse, which leaves the position still open at the end of each trial's window out oftrade_count,win_rate,total_pnland every other trade-level metric, with its P&L stranded inunrealized_pnl. Ascore_fnthat reads realized P&L or trade counts therefore ranks trials on an unclosed book unlessexit_on_last_bar=Trueis set; bar-level scores (sharpe,max_drawdown,profit_factor) come from per-bar market value and are barely affected.optimizescopes this deliberately: each tuning trial liquidates at the end of its own window, while the stitchedopt.resultuses the whole dataset so it matches an equivalentwalkforward()run.- Orders fill at
PriceType.MIDDLE— the midpoint of the low and high of the execution bar, which under the defaultbuy_delay/sell_delayof1is the bar after the signal, soPriceType.CLOSEmeans the next bar's close. Override withctx.buy_fill_price/ctx.sell_fill_price, which take aPriceType(OPEN,HIGH,LOW,CLOSE,MIDDLE,AVERAGE), a number, or a(symbol, bar_data)callable. A limit price only gates the fill: the order still fills at the fill price, never at the limit. calc_bootstrapis anoptimize/backtest/walkforwardparameter defaulting toFalse, not aStrategyConfigfield (theStrategyConfigknob isbootstrap_samples, default10_000). Onlyopt.resultcan carry bootstrap metrics: the per-trial train replays hardcodecalc_bootstrap=False, so ascore_fnnever sees them and cannot rank on a confidence interval. Passcalc_bootstrap=Trueto populateopt.result.bootstrapwithconf_intervals(BCa — bias corrected and accelerated — intervals for profit factor and Sharpe; 6x2, MultiIndexed onnamethenconf, columnslower/upper) anddrawdown_conf(percentile bounds on max drawdown; 4x2, indexed onconf, columnsamount/percent). It changes nometrics_dfvalue, so it never alters a score, and it is paid once on the final result rather than per trial.- Report
opt.result.metrics_dfas the human-readable summary. When structured output is needed (agent parsing, saved report files, downstream tools), useopt.to_json()/opt.to_json_str(): they serializebest_params, a study summary, the held-out test result (with the sameinclude=/max_rows=/symbols=controls asTestResult.to_json), and per-window results including selector-resolved symbols. The in-samplebest_scorestill must never be presented as performance. - On a Numba compilation or typing error in an
@njitindicator, re-run once with the environment variableNUMBA_DISABLE_JIT=1to get a readable Python traceback, fix the error, then re-run with JIT enabled. Never leave JIT disabled in the final script. - Guard lookbacks with
ctx.barsorwarmup, and set at most one order side per symbol per bar. Use current API only: rank withctx.long_score/ctx.short_scoreand cap positions withstrategy.set_max_long_positions(n)/set_max_short_positions(n), not the deprecatedStrategyConfigfields. - If exact API names, constructor parameters, or methods matter, read
references/api-public-surface.md. - For exact type signatures —
Strategy.optimize,Hyperparam,OptimizeResult, andWindowOptimizeResultinreferences/pybroker_strategy.pyi,ctx.hyperparamand the writable order/stop attributes inreferences/pybroker_context.pyi— read the matchingreferences/pybroker_*.pyistub. - If the user wants a standalone file, copy and adapt
assets/optimize_template.py.
Common Deliverables
- Standalone
.pyoptimization script reportingbest_paramsand held-out test metrics. - Conversion of a hard-coded strategy to
pybroker.hyperparam-driven values plus an optimize run. - Walkforward optimization (
windows > 1) with per-window parameter reporting. - Optuna study analysis:
trials_dataframe()summaries, custom sampler configuration, supplied studies. - Notebook-ready PyBroker optimization cells.
Resources
references/wiki-index.md: start here for topic routing across the bundled references.references/wiki-12-parameter-optimization.md: declaring hyperparameters, grid search, TPE and other samplers, and walkforward optimization.references/wiki-11-configuring-parallelization.md: worker counts withset_parallel, parallel indicators, and the Ray backend.references/wiki-03-evaluating-with-bootstrap-metrics.md: evaluation metrics, bootstrap confidence intervals, and maximum drawdown.references/optimization-patterns.md: load when writing nontrivial optimization code; score-function recipes, Optuna integration, walkforward windows, and the optimization checklist.references/api-public-surface.md: generated public API signatures and first docstring sentences from local source.references/pybroker_strategy.pyi: generated type stubs forStrategy,StrategyConfig,TestResult, and the optimization types.references/pybroker_context.pyi: generated type stubs forExecContext(includingctx.hyperparamand its writable order/stop attributes),IntervalContext,RotationContext,ExecResult, and the slippage models.references/pybroker_model.pyi: generated type stubs formodel(),indicator(), vector helpers, data sources, and top-level module functions.references/pybroker_types.pyi: generated type stubs for enums,BarData,Portfolio, order/trade/position records, and evaluation result types.assets/optimize_template.py: copy and adapt when creating a new standalone optimization script.
Version History
- db53c67 Current 2026-08-20 08:26


