pybroker-rotational-trading
GitHub构建基于PyBroker的轮动交易策略,通过信号排名、仓位上限控制及动态调仓实现投资组合管理。
Trigger Scenarios
Install
npx skills add edtechre/pybroker --skill pybroker-rotational-trading -g -y
SKILL.md
Frontmatter
{
"name": "pybroker-rotational-trading",
"description": "Build ranked-signal and rotational PyBroker strategies using the bundled PyBroker wiki references generated from the local docs. Use when an agent needs to rank symbols with ctx.long_score or ctx.short_score, cap positions with Strategy.set_max_long_positions or set_max_short_positions, rotate a portfolio into its top-ranked symbols with Strategy.enable_rotation and a worst_rank_held hold band, write a custom rotation sizer over RotationContext long_ranks and short_ranks, choose between ranked-cap prioritization and full rotation, carry stops and fill prices into rotation orders, handle unrankable NaN scores or long\/short overlap, screen a dynamic universe with a SymbolSelector before ranking, search position caps or worst_rank_held as hyperparams, migrate deprecated StrategyConfig.max_long_positions code, or debug rotation errors such as worst_rank_held below a position cap or a sizer without rotation enabled."
}
PyBroker Rotational Trading
Overview
Build rotational PyBroker strategies that hold the top-ranked symbols in a universe and rotate out names that fall from favor. Execution functions score symbols with ctx.long_score and ctx.short_score, cap positions with Strategy.set_max_long_positions and set_max_short_positions, and Strategy.enable_rotation(worst_rank_held=...) liquidates and refills slots each bar from the top-ranked candidates, optionally sized with a custom sizer over RotationContext. Also covers the simpler ranked-cap mode and dynamic universes via SymbolSelector.
Workflow
- Extract the rotation spec: the candidate universe (fixed list or a
SymbolSelectorscreen), the ranking signal for each side, long and/or short legs, position slots per side, hold band (worst_rank_held) versus ranked-cap prioritization only, sizing (default equal weight or a customsizer), stops and fill prices, backtest versus walkforward, and the desired deliverable file. - 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 rotation work, also readreferences/rotational-patterns.md. - Build a complete runnable rotation surface:
- start scripts with
pybroker.disable_progress_bar()andpybroker.enable_data_source_cache("<name>") - compute the ranking indicator as NumPy over
BarDataarrays and setctx.long_score/ctx.short_scorein the execution function - cap slots with
strategy.set_max_long_positions(n)/set_max_short_positions(n)(never the deprecatedStrategyConfigfields), then either stop there for ranked-cap mode or callstrategy.enable_rotation(worst_rank_held=..., sizer=...)for hold-band rotation - under rotation, let the execution function set only scores, stops, and fill prices — orders it places are ignored; in ranked-cap mode, keep placing orders normally with at most one order side per symbol per bar
- run
backtest/walkforwardwithwarmup=covering the ranking indicator's lookback and inspectresult.ordersto confirm rotation entries and hold-band exits
- start scripts with
- Validate the produced code as far as the environment allows. At minimum, run syntax checks for created Python files. When practical, run against a small local DataFrame and confirm
result.ordersshows entries capped at the position limits and exits for symbols that fall out of the hold band.
Implementation Rules
- Treat PyBroker as a backtesting framework, not a source of financial advice. State assumptions explicitly (universe, ranking signal, hold band, costs) and make no performance claims unsupported by the produced backtest.
- 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. - Two ranking modes exist. Ranked-cap mode (
set_max_*_positionsplus scores, noenable_rotation) keeps execution functions in charge of orders and uses scores only to prioritize signals when a cap binds; symbols that set no score sort as0.0and unrankable scores sort last. Rotation mode (enable_rotation) drives all trading from scores. Choose ranked-cap for prioritizing entry signals, rotation for hold-the-top-N portfolios. - Rank with
ctx.long_score(buy and cover signals) andctx.short_score(sell signals). Scores rank the whole portfolio across all executions, descending, with the symbol name as a deterministic tiebreak. strategy.set_max_long_positions(n)/set_max_short_positions(n)accept an int greater than 0, a searchableHyperparam, orNonefor unlimited. TheStrategyConfigfields of the same names are deprecated and the setters take precedence.- Rotation mechanics: each bar, held positions ranked worse than
worst_rank_held— or holding an unrankable score, even when another execution opened them — are liquidated, and the top-ranked candidates fill the remaining free slots at equal weight1 / (long slots + short slots). Candidates ranked outside the hold band are never entered.enable_rotation(None)disables rotation and clears the sizer. - Rotation is exclusive: orders placed by execution functions are discarded, but fill prices and stops (including
hold_bars) set during execution are kept and applied to the orders rotation places. Under rotation, the execution function's job is scores, stops, and fill prices only. - A
Noneor NaN score excludes the symbol from the rank map, which liquidates a held position. NaN indicator warmup is harmless before positions exist, but an indicator that goes NaN mid-series forces an exit — confirm that is intended. - A symbol picked by both the long and short leg goes to the side where it ranks better; ties go long. A symbol with no bar on the current date keeps its position slot, and in-flight pending orders hold their slots too.
- A rotation
sizeris aCallable[[RotationContext], None]invoked after rotation decides what to trade;long_ranks/short_ranksare 1-based with1the best. Override entry sizes withctx.buy_shares = ctx.calc_target_shares(weight)(orctx.sell_sharesfor short entries) guarded byif ctx.buy_shares is not None:, and never override the sell or cover signals rotation set. A sizer without rotation enabled raisesValueError. worst_rank_heldrequires at least one position cap and must be greater than or equal to every cap that is set. On any rotationValueError, match the message against the Common Errors table inreferences/rotational-patterns.mdbefore changing code.- To rotate within a screened universe, pass a
SymbolSelectorcallable as theadd_executionsymbols: it runs once per walkforward window on the window's training data, requires a DataFrame data source and a training window (backtestandtrain_size=0raiseValueError), and positions in symbols a later window drops are closed at that window's first bar. set_max_long_positions,set_max_short_positions, andenable_rotation(worst_rank_held=...)all accept apybroker.hyperparam(...), so slots and the hold band are searchable withStrategy.optimize.- 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. ASymbolSelectoris a sanctioned pandas boundary: it receives the DataFrame PyBroker hands it. - 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()when running many backtests. - Rotation's 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 score, soPriceType.CLOSEmeans the next bar's close. Fill prices set in the execution function survive rotation: assignctx.buy_fill_price/ctx.sell_fill_priceaPriceType(OPEN,HIGH,LOW,CLOSE,MIDDLE,AVERAGE), a number, or a(symbol, bar_data)callable, and note they read back asNonerather thanMIDDLEuntil set. Because a whole universe rotates on one bar, this choice moves every leg at once. StrategyConfig.exit_on_last_bardefaults toFalse. A rotational strategy is usually fully invested when the data ends, so leaving it off strands one open position per held slot: none of them becomeTrades, sotrade_count,win_rate,total_pnland every other trade-level metric silently exclude them while their P&L sits inunrealized_pnl. Setexit_on_last_bar=Truewhenever trade statistics are reported; exits fill atexit_sell_fill_price/exit_cover_fill_price, bothPriceType.MIDDLE.calc_bootstrapis abacktest/walkforwardparameter defaulting toFalse, not aStrategyConfigfield; passcalc_bootstrap=Trueto populateresult.bootstrapwith BCa confidence intervals for profit factor and Sharpe plus percentile bounds on max drawdown.- Report
result.metrics_dfas the human-readable summary, and inspectresult.ordersto confirm rotation behavior. When structured output is needed (agent parsing, saved report files, downstream tools), useresult.to_json()/result.to_json_str(): the default payload serializes metrics, trades, orders, and bootstrap capped atmax_rows=100rows per table,symbols=filters to specific tickers, andinclude=opts intoportfolio/positions/metrics_df/signals/stops. Do not replace themetrics_dfprint outright: the default JSON payload (trades plus orders) is usually larger than the metrics table. - 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=. In ranked-cap mode set at most one order side per symbol per bar; in rotation mode a symbol scored on both legs is resolved by rotation's overlap rule, never by placing both orders. - If exact API names, constructor parameters, or methods matter, read
references/api-public-surface.md. - For exact type signatures —
set_max_long_positions,set_max_short_positions, andenable_rotationinreferences/pybroker_strategy.pyi,RotationContextand thelong_score/short_scoreattributes inreferences/pybroker_context.pyi,SymbolSelectorinreferences/pybroker_types.pyi— read the matchingreferences/pybroker_*.pyistub. - If the user wants a standalone file, copy and adapt
assets/rotation_template.py.
Common Deliverables
- Standalone
.pyrotational backtest that ranks a universe and holds the top-N inside a hold band. - Ranked-cap prioritization (
long_score/short_scoreplus position caps) added to an existing multi-symbol strategy. - Custom rotation
sizerimplementing rank-weighted or otherwise non-equal entry allocation. - Long/short rotation with both legs, overlap handling, and stops carried into rotation orders.
- Migration of deprecated
StrategyConfig.max_long_positions/max_short_positionscode to the current API. - Debugging notes for rotation
ValueErrors, unrankable-score liquidations, and ignored execution-function orders.
Resources
references/wiki-index.md: start here for topic routing across the bundled references.references/wiki-10-rotational-trading.md: hold-band rotation withenable_rotationand custom position sizing with asizer.references/wiki-04-ranking-long-and-short-signals.md: ranking long and short signals with scores and position caps.references/wiki-18-dynamic-symbol-selection.md: screening a candidate universe with aSymbolSelector.references/rotational-patterns.md: load when writing nontrivial rotation code; the mode decision, rotation mechanics, sizer recipes, the rotation error table, and the validation 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(including 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/rotation_template.py: copy and adapt when creating a new standalone rotational trading script.
Version History
- db53c67 Current 2026-08-20 08:26


