Files
quant_engine/docs/strategy-research.md
T
2026-10-04 11:42:23 +08:00

15 KiB
Raw Blame History

Seven-strategy candidate research

The core owns the seven example strategies, sequential decisions, shared execution ledger, FIFO trade pairing and bounded exhaustive optimization. The platform supplies one selected asset's OHLC and displays results. The reserved asset identity CASH cannot be used as a security, preventing collision with projected cash positions. These pure functions perform no source reads, persistence, publication or order routing. decision_eligible remains false. Caller dates do not establish an exchange calendar or historical data availability.

Reuse and scope

Requirement: reproduce the seven platform examples with explicit causal timing, cash/cost accounting, benchmark status and testable results.

Existing capabilities: platform strategy definitions, core simulate_daily_ledger_with_audit, cash-constrained fills and metrics.summary/benchmark_summary. The shared ledger is reused, including fee calculation. The new policy input adapts stateful decisions to that ledger; there is no second accounting engine. FIFO pairing adds the previously missing entry-cost and completed-lot interpretation. No external package or new dependency is needed.

Financial mathematical behavior is L3. The candidate is isolated to the original core branch/PR. It neither changes formal source admission nor asserts exchange lot sizes, tradability, T+1, price-limit, volume or point-in-time coverage. Those require qualified caller facts and further integration. Existing independent execution-constraint helpers are not silently enabled by this research interface.

Input and timing contract

run_strategy_research(strategy, bars, *, asset, params=None, initial_cash=..., commission=..., stamp_duty=..., benchmark=None, min_trade_amount=0) consumes a single asset's complete daily OHLC DataFrame.

  • Its naive daily DatetimeIndex is unique, sorted and within 1900–2100. There are 2–5000 observations. OHLC must be real finite positive values with valid bounds. Missing high/low/open, strings, booleans, duplicate dates and malformed bars fail. Prices are never synthesized from close.
  • Parameters merge the seven original defaults before strict validation. Periods are native integers from 1 to 500; optional ATR period also allows zero. Fast is less than slow; RSI oversold is below overbought. Allocation is between zero and one. Multipliers are bounded by 100; positive multipliers cannot be zero, while DualThrust coefficients may be zero.
  • Initial capital is positive, finite and at most 1e12. Explicit fee inputs are fractions: 0.01 means 1%. Commission and additional sell fee are nonnegative and their sum cannot exceed one. Defaults preserve implementation values and are not current-market tax assertions. Fractional shares follow the pre-existing research ledger. Costs may reduce a full allocation to a cash-constrained partial fill.
  • All input, benchmark and history checks precede ledger execution. Insufficient initialization history is an error. Optional longer ATR and exit lookbacks do not delay an otherwise valid entry signal; their own conditions wait for their own available history.
  • At each supplied session's open, the ledger processes the previous close's pending target. It then values actual holdings at the current close. The policy sees a separate copy of actual post-fill holdings and cash. None means no order; it does not liquidate or rebalance existing holdings.
  • A close signal schedules only the next supplied session's open. A final-session signal records no_next_session with no execution date. There is no same-close fallback. A zero allocation while already flat is no_change; an unfilled exit that retains holdings is not_filled.

The shared ledger's optional decision_policy cannot be combined with a fixed target schedule. Its execution-price calendar must cover the full valuation calendar. Static schedule behavior remains supported. A caller policy can itself misuse future information; the supplied seven policies use only causal windows. Future-perturbation tests establish that implementation property, not real-source PIT qualification.

Strategy definitions

Name Entry while flat Exit while held Defaults
BuyAndHold First supplied close schedules the allocation once No automatic exit buy_pct=.95
SmaCross Fast SMA crosses strictly above slow SMA Fast SMA crosses strictly below slow SMA fast=5, slow=20
MACross Same SMA cross definition SMA cross down or optional trailing ATR stop fast=10, slow=30, atr_period=0, atr_mult=2
RSI Wilder RSI strictly below oversold Strictly above overbought period=14, oversold=30, overbought=70
BollingerBreakout Close strictly above current-window mean plus population standard deviation times multiplier Close strictly below current-window mean period=20, std_mult=2
DualThrust Close strictly above current open plus k1 times prior-window HH−LL Close strictly below current open minus k2 times prior-window HH−LL period=5, k1=.5, k2=.5
TurtleBreakout Close strictly above the preceding entry-window high Close strictly below the preceding exit-window low entry_period=20, exit_period=10

MACross remains a dual-moving-average example; it is not renamed MACD. ATR is the arithmetic mean of true ranges over its explicit window. Zero disables ATR. After an actual entry the historical reference starts at its execution open, then tracks observed closes while held. At each later close, the stop is the greater of the prior stop and historical peak minus the previous session's ATR times the multiplier. Close at or below that stop schedules the next open exit. The current close does not construct a stop that is then impossibly compared with itself. Stops reset only when actual holdings become flat. If both exit conditions occur together, the recorded reason is atr_stop.

RSI is 50 when average gain and loss are both zero, 100 when only loss is zero, and otherwise follows Wilder smoothing. Bollinger uses population standard deviation, with each observed window independently scaled and deviations translated before scaling. This prevents tiny-price squared variance underflow and huge-price overflow without using a future/global scale. Nonfinite indicators after warmup fail explicitly. DualThrust deliberately preserves the existing example's HH−LL variant; it does not silently replace it with another range definition.

Accounting, completed trades and metrics

Every fill is charged once by the shared ledger before that session's final NAV. Multi-asset same-session fills, final-session fills and both sides of rotation retain individual costs. Tiny fractional residual holdings are preserved; an explicit full exit consumes the exact held quantity, avoiding a rounded notional leaving a phantom lot.

pair_ledger_trades matches actual buy and sell fills FIFO per asset, using their net cash flows. Buy cost includes entry fees/slippage; net sell proceeds include exit fees/slippage. A match records allocated entry cost, exit proceeds and net PnL. A trade for win-rate purposes is one fully closed entry lot, even if exited in pieces. Partial exits contribute realized PnL but do not enter the completed-trade denominator. No completed lots yields None, not zero. An actual small residual is not treated as closed by relative tolerance. For a final asset fill followed by an exact flat ledger position, FIFO consumes every entry lot only when total quantity agrees within accumulated ULP resolution; this reconciles multi-entry subtraction rounding. The last matched lot receives the remaining net proceeds so the sell cash flow is conserved. Winning lots require PnL above 32 float64 ULPs at the cash magnitude; raw PnL is retained.

Daily returns and fees come from the shared ledger. Total return is final NAV divided by initial capital minus one. Complete loss is valid zero NAV, with no invented recovery; positive NAV whose return cannot be represented is rejected. Nonfinite returns are checked before general metrics, preventing generic cleaning from dropping an observation.

Other absolute measures reuse metrics.summary: 252 supplied trading sessions per year, CAGR-based annual return and Sharpe numerator, sample daily volatility, initial-capital-aware drawdown and daily-positive-return frequency. The latter is named daily_win_rate, distinct from FIFO trade_win_rate. Sharpe with zero volatility, Calmar with zero drawdown, Sortino with no downside and trade win rate with no closed lots are None with explicit reasons. Nonfinite derived metric outputs are marked unavailable; no default score is substituted.

Benchmark and optimization

BenchmarkInput has four caller-reported states. not_requested contains no data; empty contains an empty Series. present requires a complete positive-price Series with exactly the valuation dates; missing/extra/duplicate dates fail rather than inner join. source_error fails before any strategy execution. Benchmark normalization and returns must be representable, finite and compatible with positive prices; non-first missing returns are never filled as zero. Output preserves empty versus not-requested status. Relative metrics reuse the existing strict benchmark_summary; constant-benchmark regressions remain unavailable.

optimize_strategy_research validates the entire grid before any trial. It allows at most four axes, ten candidates per axis and 100 combinations. Empty axes, unknown keys, duplicate values, non-native numbers, invalid relationships and any insufficient history fail the whole request. The size bound precedes Cartesian expansion. Every trial runs a fresh real seven-strategy pipeline and ledger with explicit fees. Targets are total_return, sharpe_ratio or calmar_ratio. An unavailable objective fails the ranking instead of silently skipping a candidate. Stable descending sorting preserves canonical axis order and supplied candidate order for ties; zero and negative finite scores remain valid.

Strategy artifact projection

strategy_artifact.build_strategy_research_artifact accepts an actual StrategyResearchResult or StrategyOptimizationResult and returns the existing schema1.1.0 ResearchRunArtifact. The calculation result retains detached OHLC and benchmark-return views and explicit cost inputs; optimization retains a detached grid snapshot. The builder projects the selected result's ledger, actual fees, next-session signal links, close-marked holdings and existing metrics. It does not run a second strategy, accounting system or performance formula.

params_json.strategy_report uses researchhub.strategy-research.v1. It covers every causal signal, including no_next_session, unfilled targets and partial fills; complete FIFO matches, closed/open lots and net PnL; daily versus completed-trade win rates; benchmark status; exact parameters/costs; and all ranked candidate summaries. A grid artifact stores the selected first-ranked ledger plus every candidate's parameters, objective score, metrics and missing reasons, up to100 trials. It does not repeat100 full ledgers. Report contents are covered by the artifact's canonical content digest. User metadata cannot overwrite this report or its performance explanation.

The legacy factor-signal table remains empty: strategy signals have no factor score. The report explicitly locates the real strategy signals and marks factor attribution and covariance risk as not computed. Actual fills link to report signal IDs. A zero-value portfolio retains its actual zero values and an undefined weight, with the exact dates and reason recorded; it is not assigned an invented zero or full-cash weight. Non-numerical performance values carry explicit reasons keyed to their fact-table columns, and win-rate basis is completed trades. Requested empty benchmark data retains its identity and empty status, while unrequested data has no identity.

This is a storage-neutral candidate. The platform's isolated durable-file adapter must validate this report and expose undefined weights before enabling this path. This projection does not validate a production database schema, execute SQL, admit real data, or grant decision eligibility. Existing factor-artifact consumers remain unchanged.

Verification scope

The missing public modules and sequential policy first failed actual tests. Further actual RED→GREEN regressions cover unfilled exits, legitimate zero NAV, independent ATR/exit windows, benchmark underflow, nonfinite portfolio returns, tiny residual holdings, Bollinger scale invariance, undefined Sortino, premature FIFO closure, full-exit quantity rounding and phantom residues after three accumulated entries at prices 3, 11 and 13.

Final focused verification is 93 new tests plus 85 existing execution tests, 178 passing in one run. The complete core suite then passed 1186 tests in 15.42 seconds, including governance, existing execution, pipeline, metrics and artifact contracts; 1168 existing-style pandas deprecation warnings remain. The new tests include seven default strategies, six separate trade/cash/NAV hand calculations plus BuyAndHold costs, a triggerable historical ATR stop, future perturbation for all seven, final signals without a next session, FIFO partial exits and same-session fees, and 100 actual optimization trials. Final targeted Ruff passed for all nine affected Python files, and strict typing passed for the four new source modules. Central clean-candidate validation and delivery state are separate receipts.

Two reused read-only reviewers independently exercised causal prefixes and cash/FIFO examples and found substantive defects subsequently turned into permanent regressions. The final independent FIFO review passed after reproducing three-lot full exits, retaining real tiny balances and reconciling fee-bearing add/partial-exit/full-exit PnL with final NAV. Saved revision and central delivery status are separate evidence. These tests do not establish real data coverage, source/PIT qualification, platform task publication or formal production availability.

The reviewed implementation was saved and pushed as 7d3e840483d7f3b5d7b2d987c95fb79a5dc4a63b to the existing PR #21 (#21), advancing the original 3c97102f candidate. This receipt-only checkpoint does not change the tested code. One explicit central candidate refresh and ship --ready --confirm-l3 is the next boundary; no prior CI wait was polled or resumed. Push is a save checkpoint, and does not establish merge or source admission.

Artifact increment verification: 26 new artifact tests plus81 strategy/optimizer and7 existing factor-artifact tests passed together (114). The final complete core suite passed1212 tests in15.59s with1170 pandas deprecation warnings. Four-file Ruff and three-module strict typing passed. The read-only reviewer confirmed the reserved-CASH rejection and fact-column explanation keys after actual regressions. The earlier5192370 central receipt remains CI pending without polling; this new increment is saved and gated separately on the same delivery.