|
|
"""Domain models for the Morning Momentum Intraday Backtester.
|
|
|
|
|
|
All models use Pydantic for validation and serialization.
|
|
|
No dependencies on the existing backtest system.
|
|
|
"""
|
|
|
from __future__ import annotations
|
|
|
|
|
|
from typing import Any
|
|
|
|
|
|
from pydantic import BaseModel, Field
|
|
|
|
|
|
|
|
|
# ── Strategy Parameters ────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
class StrategyParams(BaseModel):
|
|
|
"""Core strategy parameters controlling when to enter/exit."""
|
|
|
|
|
|
compound_returns: bool = False
|
|
|
"""When True, position sizing scales with current equity (compounding).
|
|
|
When False, position sizing uses min(initial_capital, current_equity) (simple returns,
|
|
|
capped at actual equity to avoid trading money that doesn't exist after drawdowns).
|
|
|
Momentum research defaults to simple returns to avoid late-period overweighting.
|
|
|
Ignored when daily_budget_reset is True."""
|
|
|
|
|
|
daily_budget_reset: bool = False
|
|
|
"""Research-only mode: every day resets sizing_capital and buying power to
|
|
|
initial_capital, so prior-day PnL does not compound into position budget.
|
|
|
Stateful strategy governors still apply when explicitly configured.
|
|
|
When True, takes precedence over compound_returns."""
|
|
|
|
|
|
entry_minutes_after_open: int = 30
|
|
|
"""Minutes after 9:30 AM ET to evaluate morning gainers and enter trades."""
|
|
|
|
|
|
confirmation_minutes_after_entry: int = 0
|
|
|
"""Optional extra confirmation delay after the primary entry time.
|
|
|
Example: 5 means evaluate leaders at +10min but only enter at +15min if
|
|
|
the confirmation rule is still satisfied."""
|
|
|
|
|
|
min_confirmation_return_pct: float | None = None
|
|
|
"""Minimum return between the primary entry bar close and the confirmation
|
|
|
bar close. Example: 0.0 = no fade allowed; 0.002 = require +0.2% follow-through.
|
|
|
Ignored when confirmation_minutes_after_entry <= 0."""
|
|
|
|
|
|
exit_minutes_before_close: int = 30
|
|
|
"""Minutes before 4:00 PM ET to force-close all positions."""
|
|
|
|
|
|
stop_loss_pct: float | None = -0.02
|
|
|
"""Fixed stop-loss threshold (e.g. -0.02 = -2%). None to disable."""
|
|
|
|
|
|
trailing_stop_pct: float | None = None
|
|
|
"""Trailing stop: if set, ratchet stop up as price rises. e.g. -0.03 = trail 3% below peak.
|
|
|
When both stop_loss_pct and trailing_stop_pct are set, trailing_stop_pct is used."""
|
|
|
|
|
|
atr_stop_multiplier: float | None = None
|
|
|
"""Catastrophic stop distance in ATR(14) units from the actual entry price.
|
|
|
Example: 0.5 means stop at entry - 0.5 x ATR. When set, this overrides
|
|
|
stop_loss_pct as the initial stop anchor."""
|
|
|
|
|
|
opening_range_stop_multiplier: float | None = None
|
|
|
"""Catastrophic stop distance in opening-range-width units from the actual
|
|
|
entry price. Example: 1.0 means stop at entry - 1.0 x opening range width.
|
|
|
Used as an alternative to ATR when the opening range itself is the better
|
|
|
volatility proxy."""
|
|
|
|
|
|
trailing_activation_gain_pct: float | None = None
|
|
|
"""Optional delayed trailing activation threshold.
|
|
|
When set, trailing_stop_pct does not turn on until peak return from entry
|
|
|
reaches this gain threshold. Before activation, only the catastrophic /
|
|
|
fixed stop is active."""
|
|
|
|
|
|
overextended_trailing_gain_pct: float | None = None
|
|
|
"""If set together with overextended_trailing_stop_pct, trades whose morning gain
|
|
|
at entry is at least this large use the alternate trailing stop instead of the
|
|
|
baseline trailing_stop_pct."""
|
|
|
|
|
|
overextended_trailing_stop_pct: float | None = None
|
|
|
"""Alternate trailing stop for overextended morning leaders.
|
|
|
Example: base trail -0.075 with overextended trail -0.065 tightens risk only for
|
|
|
names already up sharply by entry time."""
|
|
|
|
|
|
min_morning_gain_pct: float = 0.01
|
|
|
"""Minimum gain from open to entry time for a stock to qualify (e.g. 0.01 = 1%)."""
|
|
|
|
|
|
max_morning_gain_pct: float | None = None
|
|
|
"""Maximum morning gain allowed (e.g. 0.10 = 10%). Filters out extreme gap-ups
|
|
|
that tend to mean-revert quickly. None = no cap."""
|
|
|
|
|
|
min_entry_volume: int | None = None
|
|
|
"""Minimum cumulative volume by entry time (shares). Filters illiquid stocks.
|
|
|
E.g. 50000 = must have traded 50K shares in first 30 minutes."""
|
|
|
|
|
|
min_entry_dollar_volume: float | None = None
|
|
|
"""Minimum cumulative dollar volume by entry time.
|
|
|
Helps reject low-priced names that pass a raw share-volume filter but still
|
|
|
trade too little notional size for reliable execution."""
|
|
|
|
|
|
ticker_cooldown_days: int = 0
|
|
|
"""Blackout period after trading a ticker (calendar days).
|
|
|
E.g. 5 = same ticker can't be selected again within 5 days. 0 = disabled."""
|
|
|
|
|
|
top_n: int = 3
|
|
|
"""Number of top gainers to buy each day (equal-weight allocation)."""
|
|
|
|
|
|
min_positions_to_trade: int = 1
|
|
|
"""Minimum number of qualified picks required to trade the day at all.
|
|
|
Event-driven leader strategies often degrade when only one or two names pass.
|
|
|
Use this to explicitly allow no-trade days instead of forcing sparse baskets."""
|
|
|
|
|
|
max_positions_per_sector: int | None = None
|
|
|
"""Optional basket diversification cap.
|
|
|
When set, at most this many positions may be opened from the same sector
|
|
|
in the day's momentum basket. Unknown sectors are left uncapped."""
|
|
|
|
|
|
full_size_positions_threshold: int | None = None
|
|
|
"""When set, sparse days scale down total deployed capital instead of always
|
|
|
using the full daily book. Example: threshold=4 means 1-3 position days are
|
|
|
sized below 100% of the daily budget, while 4+ position days use full size."""
|
|
|
|
|
|
sparse_day_size_floor: float = 1.0
|
|
|
"""Minimum day-level size scaler when full_size_positions_threshold is active.
|
|
|
0.5 means even a 1-position day still deploys 50% of the normal daily budget."""
|
|
|
|
|
|
initial_capital: float = 10_000.0
|
|
|
"""Starting capital in USD."""
|
|
|
|
|
|
slippage_bps: float = 5.0
|
|
|
"""One-way slippage in basis points (applied to both entry and exit fills)."""
|
|
|
|
|
|
market_regime_spy_threshold: float | None = None
|
|
|
"""Skip trading if SPY's morning return (open to entry time) is below this threshold.
|
|
|
E.g. -0.005 = skip if SPY is down more than -0.5% by entry time. None = disabled."""
|
|
|
|
|
|
market_regime_gap_threshold: float | None = None
|
|
|
"""Skip trading if regime ticker's opening gap versus prior close is below this threshold.
|
|
|
This is a day-level guard that remains available even when the regime ticker is not
|
|
|
part of the intraday candidate set."""
|
|
|
|
|
|
market_regime_gap_ticker: str = "SPY"
|
|
|
"""Ticker used for the day-level opening-gap regime check. Default 'SPY'."""
|
|
|
|
|
|
min_candidate_breadth: float | None = None
|
|
|
"""Skip the day if fewer than this fraction of intraday candidates opened above
|
|
|
their prior close. None = disabled."""
|
|
|
|
|
|
min_gap_pct: float | None = None
|
|
|
"""Minimum opening gap versus prior close. None = disabled."""
|
|
|
|
|
|
max_gap_pct: float | None = None
|
|
|
"""Maximum opening gap versus prior close. None = disabled."""
|
|
|
|
|
|
min_volume_ratio_14d: float | None = None
|
|
|
"""Minimum cumulative volume by entry time divided by 14-day average daily volume.
|
|
|
Helps reject low-attention names that are up but not truly in play."""
|
|
|
|
|
|
min_ret_5d: float | None = None
|
|
|
"""Minimum prior 5-day return. None = disabled."""
|
|
|
|
|
|
min_entropy_20d: float | None = None
|
|
|
"""Minimum allowed entropy(20d). None = disabled."""
|
|
|
|
|
|
max_entropy_20d: float | None = None
|
|
|
"""Maximum allowed entropy(20d). None = disabled."""
|
|
|
|
|
|
max_vix: float | None = None
|
|
|
"""Skip the whole day if VIX closes above this threshold. None = disabled."""
|
|
|
|
|
|
vix_size_scale_low: float | None = None
|
|
|
"""VIX level where size scaling starts. None = disabled."""
|
|
|
|
|
|
vix_size_scale_high: float | None = None
|
|
|
"""VIX level where the day-size scaler reaches vix_size_scale_min."""
|
|
|
|
|
|
vix_size_scale_min: float = 1.0
|
|
|
"""Minimum position-size scaler once VIX reaches vix_size_scale_high."""
|
|
|
|
|
|
regime_size_scale_low: float | None = None
|
|
|
"""Regime gap at which the day-size scaler bottoms out. None = disabled."""
|
|
|
|
|
|
regime_size_scale_high: float | None = None
|
|
|
"""Regime gap at which the day-size scaler returns to 1.0."""
|
|
|
|
|
|
regime_size_scale_min: float = 1.0
|
|
|
"""Minimum regime day-size scaler once regime_size_scale_low is breached."""
|
|
|
|
|
|
regime_skip_below: float | None = None
|
|
|
"""Hard skip floor below regime_size_scale_low. None = no extra skip."""
|
|
|
|
|
|
soft_day_fallback_on_regime_skip: bool = False
|
|
|
"""When True, a legacy market-regime skip can become a small soft-day
|
|
|
fallback instead of a full no-trade day. Disabled by default to preserve
|
|
|
existing strategy behavior."""
|
|
|
|
|
|
soft_day_regime_skip_size_scale: float = 1.0
|
|
|
"""Day-level size scale used when soft_day_fallback_on_regime_skip converts
|
|
|
a market-regime skip into a fallback trading day."""
|
|
|
|
|
|
breadth_size_scale_low: float | None = None
|
|
|
"""Breadth ratio at which the day-size scaler bottoms out. None = disabled."""
|
|
|
|
|
|
breadth_size_scale_high: float | None = None
|
|
|
"""Breadth ratio at which the day-size scaler returns to 1.0."""
|
|
|
|
|
|
breadth_size_scale_min: float = 1.0
|
|
|
"""Minimum breadth day-size scaler once breadth_size_scale_low is breached."""
|
|
|
|
|
|
breadth_skip_below: float | None = None
|
|
|
"""Hard skip floor on candidate breadth. None = no extra skip."""
|
|
|
|
|
|
sector_concentration_scale_low: float | None = None
|
|
|
"""Sector concentration ratio where basket-level size scaling starts.
|
|
|
|
|
|
Concentration is measured as max_sector_count / selected_count using only
|
|
|
selected picks with known sectors.
|
|
|
"""
|
|
|
|
|
|
sector_concentration_scale_high: float | None = None
|
|
|
"""Sector concentration ratio where the basket-level sector scaler reaches
|
|
|
sector_concentration_scale_min."""
|
|
|
|
|
|
sector_concentration_scale_min: float = 1.0
|
|
|
"""Minimum basket-level sector-concentration scaler."""
|
|
|
|
|
|
soft_day_scaler_threshold: float = 1.0
|
|
|
"""Days whose combined regime/breadth scaler falls below this threshold are treated
|
|
|
as soft days for extra basket throttles."""
|
|
|
|
|
|
soft_day_max_trades: int | None = None
|
|
|
"""Maximum number of trades allowed on soft days. None = no extra cap."""
|
|
|
|
|
|
soft_day_sparse_max_trades: int | None = None
|
|
|
"""Optional extra scaler for sparse baskets on soft days.
|
|
|
|
|
|
When set, the soft-day sparse defense only considers days whose final
|
|
|
selected basket size is at or below this count.
|
|
|
"""
|
|
|
|
|
|
soft_day_sparse_require_no_event: bool = False
|
|
|
"""When True, do not apply the soft-day sparse scaler if the basket already
|
|
|
contains a supported event-qualified name."""
|
|
|
|
|
|
soft_day_sparse_exempt_largecap: bool = False
|
|
|
"""When True, do not apply the soft-day sparse scaler when the basket
|
|
|
includes a liquid large-cap candidate."""
|
|
|
|
|
|
soft_day_sparse_exempt_moderate_gap_liquid: bool = False
|
|
|
"""When True, do not apply the soft-day sparse scaler when the basket
|
|
|
includes a moderate-gap liquid follow-through candidate."""
|
|
|
|
|
|
soft_day_sparse_scale: float = 1.0
|
|
|
"""Extra day-size scaler applied to sparse soft-day baskets."""
|
|
|
|
|
|
event_sleeve_soft_day_max_trades: int | None = None
|
|
|
"""When set, only enable the soft-day event sleeve if the pre-event basket
|
|
|
has at most this many selected names."""
|
|
|
|
|
|
event_sleeve_soft_day_max_avg_quality: float | None = None
|
|
|
"""When set, only enable the soft-day event sleeve if the pre-event basket's
|
|
|
average quality score is at or below this threshold."""
|
|
|
|
|
|
event_sleeve_soft_day_require_no_existing_event: bool = False
|
|
|
"""When True, only enable the soft-day event sleeve if the pre-event basket
|
|
|
does not already contain an event-qualified pick."""
|
|
|
|
|
|
tail_risk_day_max_trades: int | None = None
|
|
|
"""Optional meta-layer for sparse, high-extension basket risk.
|
|
|
|
|
|
When set, the tail-risk defense only considers days whose final basket size
|
|
|
is at or below this count.
|
|
|
"""
|
|
|
|
|
|
tail_risk_day_min_max_gain_pct: float | None = None
|
|
|
"""Minimum maximum morning gain required to trigger the tail-risk day defense."""
|
|
|
|
|
|
tail_risk_day_max_avg_quality: float | None = None
|
|
|
"""Maximum average basket quality allowed to trigger the tail-risk day defense."""
|
|
|
|
|
|
tail_risk_day_max_support_score: float | None = None
|
|
|
"""Maximum same-day support score allowed to trigger the tail-risk defense.
|
|
|
|
|
|
Support blends prior liquidity, entry-time liquidity, and same-day
|
|
|
catalyst/attention proxies. Lower values indicate thin, weakly supported
|
|
|
moves more prone to failure on sparse single-name days.
|
|
|
"""
|
|
|
|
|
|
tail_risk_day_min_max_entropy_20d: float | None = None
|
|
|
"""Minimum highest selected entropy_20d required to trigger the tail-risk defense."""
|
|
|
|
|
|
tail_risk_day_min_max_confirmation_return_pct: float | None = None
|
|
|
"""Minimum highest selected confirmation return required to trigger the tail-risk defense."""
|
|
|
|
|
|
tail_risk_day_require_no_event: bool = False
|
|
|
"""When True, the tail-risk day defense only triggers if no selected pick has
|
|
|
an event-qualified catalyst."""
|
|
|
|
|
|
tail_risk_day_event_exemption_min_support_score: float | None = None
|
|
|
"""Minimum support score required for an event-qualified pick to exempt the
|
|
|
day from tail-risk defense.
|
|
|
|
|
|
This prevents weak catalysts on thin, single-name days from disabling the
|
|
|
sparse-day defense merely because an event flag exists.
|
|
|
"""
|
|
|
|
|
|
tail_risk_day_exempt_largecap: bool = False
|
|
|
"""When True, skip the tail-risk day defense whenever the selected basket
|
|
|
contains a liquid large-cap candidate."""
|
|
|
|
|
|
tail_risk_day_scale: float = 1.0
|
|
|
"""Minimum extra day-size scaler applied when the tail-risk defense triggers."""
|
|
|
|
|
|
low_momentum_single_name_max_gain_pct: float | None = None
|
|
|
"""Scale sparse single-name days when the only pick has weak morning gain.
|
|
|
|
|
|
This catches low-conviction continuation attempts that are not high-extension
|
|
|
tail-risk days but still concentrate the full day budget in one marginal name.
|
|
|
"""
|
|
|
|
|
|
low_momentum_single_name_require_no_event: bool = False
|
|
|
"""When True, do not apply the low-momentum single-name scaler if the pick
|
|
|
has a supported event-qualified catalyst."""
|
|
|
|
|
|
low_momentum_single_name_exempt_largecap: bool = False
|
|
|
"""When True, do not apply the low-momentum single-name scaler to liquid
|
|
|
large-cap candidates."""
|
|
|
|
|
|
low_momentum_single_name_scale: float = 1.0
|
|
|
"""Extra day-size scaler applied to low-momentum single-name days."""
|
|
|
|
|
|
basket_quality_relative_floor: float | None = None
|
|
|
"""Optional dynamic floor applied after basket selection.
|
|
|
|
|
|
When set, keep only picks whose day-level quality score is at least this
|
|
|
fraction of the best selected pick's score. This turns a fixed top-N basket
|
|
|
into an adaptive basket that can shrink on weak tail names.
|
|
|
"""
|
|
|
|
|
|
basket_quality_min_count: int = 0
|
|
|
"""Minimum number of picks to keep even when basket_quality_relative_floor prunes
|
|
|
weak tail names. 0 means no forced minimum beyond surviving the floor."""
|
|
|
|
|
|
basket_quality_prune_blend_only: bool = False
|
|
|
"""When True, apply the basket quality floor only to 'blend' tail fills and keep
|
|
|
sleeve-forced picks intact. This is useful when the goal is to stop overfilling
|
|
|
weak residual names without undoing the intended sleeve diversification."""
|
|
|
|
|
|
rolling_loss_days: int | None = None
|
|
|
"""Lookback window for a self-referential rolling loss pause. None = disabled."""
|
|
|
|
|
|
rolling_loss_threshold: float | None = None
|
|
|
"""Pause trading when the strategy's rolling return over rolling_loss_days drops
|
|
|
below this threshold. None = disabled."""
|
|
|
|
|
|
entropy_size_scale_low: float | None = None
|
|
|
"""Entropy level where trade-size scaling starts. None = disabled."""
|
|
|
|
|
|
entropy_size_scale_high: float | None = None
|
|
|
"""Entropy level where the trade-size scaler reaches entropy_size_scale_min."""
|
|
|
|
|
|
entropy_size_scale_min: float = 1.0
|
|
|
"""Minimum per-trade size scaler once entropy reaches entropy_size_scale_high."""
|
|
|
|
|
|
use_five_sleeves: bool = False
|
|
|
"""When True, build the daily basket from five sleeve rankings instead of one raw top-N list.
|
|
|
Sleeves: core gain, gap, volume surprise, low entropy, and prior trend."""
|
|
|
|
|
|
momentum_selection_mode: str = "standard"
|
|
|
"""How to build the execution basket from eligible momentum candidates.
|
|
|
|
|
|
- standard: existing sleeve/blend selection path
|
|
|
- liquid_continuation: prioritize moderate-gap liquid, liquid large-cap,
|
|
|
and sector breadth-confirmed continuation names in the core basket
|
|
|
"""
|
|
|
|
|
|
five_sleeve_force_count: int = 5
|
|
|
"""How many sleeve-specific picks to force before the weighted blend fill starts.
|
|
|
5 preserves the original behavior of taking one pick from each sleeve.
|
|
|
Lower values let the weighted blend dominate sooner and reduce quota-style overfitting."""
|
|
|
|
|
|
five_sleeve_core_weight: float = 0.40
|
|
|
"""Blend weight for the core momentum sleeve (morning gain)."""
|
|
|
|
|
|
five_sleeve_gap_weight: float = 0.20
|
|
|
"""Blend weight for the opening-gap sleeve."""
|
|
|
|
|
|
five_sleeve_volume_weight: float = 0.25
|
|
|
"""Blend weight for the volume-surprise sleeve."""
|
|
|
|
|
|
five_sleeve_entropy_weight: float = 0.05
|
|
|
"""Blend weight for the low-entropy sleeve."""
|
|
|
|
|
|
five_sleeve_trend_weight: float = 0.10
|
|
|
"""Blend weight for the prior-trend sleeve."""
|
|
|
|
|
|
use_event_sleeve: bool = False
|
|
|
"""When True, allow a dedicated same-day filing catalyst sleeve.
|
|
|
|
|
|
This sleeve only changes basket selection among already-tradable
|
|
|
intraday names; it does not bypass the base execution filters.
|
|
|
"""
|
|
|
|
|
|
event_weight: float = 0.0
|
|
|
"""Blend weight for the same-day filing catalyst sleeve."""
|
|
|
|
|
|
event_min_score: float | None = None
|
|
|
"""Minimum same-day filing event score required for event-sleeve eligibility."""
|
|
|
|
|
|
event_sleeve_soft_day_only: bool = False
|
|
|
"""Enable the event sleeve only on soft days.
|
|
|
|
|
|
Soft-day detection reuses the existing regime/breadth/sector scaler
|
|
|
machinery so event-driven substitutions only happen when the normal
|
|
|
basket looks weak.
|
|
|
"""
|
|
|
|
|
|
use_slow_ignite_sleeve: bool = False
|
|
|
"""When True, allow one extra sleeve for slower-starting but still high-attention leaders.
|
|
|
This is intended for names that are not yet above the primary morning-gain floor by the
|
|
|
standard entry time, but are showing strong confirmation, liquidity, and prior trend."""
|
|
|
|
|
|
slow_ignite_weight: float = 0.0
|
|
|
"""Blend weight for the slow-ignite sleeve. Ignored when use_slow_ignite_sleeve is False."""
|
|
|
|
|
|
slow_ignite_min_gain_pct: float | None = None
|
|
|
"""Lower gain floor for slow-ignite candidates. Typically below min_morning_gain_pct."""
|
|
|
|
|
|
slow_ignite_max_gain_pct: float | None = None
|
|
|
"""Upper gain cap for slow-ignite candidates. Keeps the sleeve focused on slower starters."""
|
|
|
|
|
|
slow_ignite_min_entry_dollar_volume: float | None = None
|
|
|
"""Minimum entry-time dollar volume required for slow-ignite candidates."""
|
|
|
|
|
|
slow_ignite_min_volume_ratio_14d: float | None = None
|
|
|
"""Minimum entry-time volume ratio required for slow-ignite candidates."""
|
|
|
|
|
|
slow_ignite_min_ret_5d: float | None = None
|
|
|
"""Minimum prior 5-day return required for slow-ignite candidates."""
|
|
|
|
|
|
slow_ignite_max_entropy_20d: float | None = None
|
|
|
"""Maximum entropy allowed for slow-ignite candidates."""
|
|
|
|
|
|
use_liquid_largecap_sleeve: bool = False
|
|
|
"""When True, allow a dedicated sleeve for liquid large-cap leaders.
|
|
|
This is designed for slower large-cap continuation names that may not rank highly
|
|
|
in the small/midcap-oriented momentum sleeves despite strong notional liquidity."""
|
|
|
|
|
|
liquid_largecap_weight: float = 0.0
|
|
|
"""Blend weight for the liquid large-cap sleeve. Ignored when disabled."""
|
|
|
|
|
|
liquid_largecap_min_gain_pct: float | None = None
|
|
|
"""Lower gain floor for liquid large-cap candidates."""
|
|
|
|
|
|
liquid_largecap_max_gain_pct: float | None = None
|
|
|
"""Upper gain cap for liquid large-cap candidates."""
|
|
|
|
|
|
liquid_largecap_min_confirmation_return_pct: float | None = None
|
|
|
"""Minimum follow-through required for liquid large-cap candidates."""
|
|
|
|
|
|
liquid_largecap_min_entry_dollar_volume: float | None = None
|
|
|
"""Minimum entry-time dollar volume required for liquid large-cap candidates."""
|
|
|
|
|
|
liquid_largecap_min_avg_dollar_vol_30d: float | None = None
|
|
|
"""Minimum prior 30-day average daily dollar volume required for liquid large-cap candidates."""
|
|
|
|
|
|
liquid_largecap_max_entropy_20d: float | None = None
|
|
|
"""Maximum entropy allowed for liquid large-cap candidates."""
|
|
|
|
|
|
use_moderate_gap_liquid_sleeve: bool = False
|
|
|
"""When True, enable a dedicated sleeve for moderate-gap liquid follow-through names.
|
|
|
|
|
|
This targets names that are too moderate to win the raw gap/momentum rank,
|
|
|
but have enough same-day follow-through and institutional liquidity to be
|
|
|
distinct from thin small-cap attention spikes.
|
|
|
"""
|
|
|
|
|
|
moderate_gap_liquid_weight: float = 0.0
|
|
|
"""Blend weight for the moderate-gap liquid follow-through sleeve."""
|
|
|
|
|
|
moderate_gap_liquid_min_gap_pct: float | None = None
|
|
|
"""Minimum opening gap for moderate-gap liquid candidates."""
|
|
|
|
|
|
moderate_gap_liquid_max_gap_pct: float | None = None
|
|
|
"""Maximum opening gap for moderate-gap liquid candidates."""
|
|
|
|
|
|
moderate_gap_liquid_min_gain_pct: float | None = None
|
|
|
"""Minimum entry-time gain for moderate-gap liquid candidates."""
|
|
|
|
|
|
moderate_gap_liquid_max_gain_pct: float | None = None
|
|
|
"""Maximum entry-time gain for moderate-gap liquid candidates."""
|
|
|
|
|
|
moderate_gap_liquid_min_confirmation_return_pct: float | None = None
|
|
|
"""Minimum confirmation-bar follow-through for moderate-gap liquid candidates."""
|
|
|
|
|
|
moderate_gap_liquid_min_entry_dollar_volume: float | None = None
|
|
|
"""Minimum entry-time cumulative dollar volume for moderate-gap liquid candidates."""
|
|
|
|
|
|
moderate_gap_liquid_min_avg_dollar_vol_30d: float | None = None
|
|
|
"""Minimum prior 30-day average daily dollar volume for moderate-gap liquid candidates."""
|
|
|
|
|
|
moderate_gap_liquid_max_avg_dollar_vol_30d: float | None = None
|
|
|
"""Optional upper bound on prior 30-day dollar volume.
|
|
|
|
|
|
This keeps the sleeve from duplicating the liquid-largecap sleeve when the
|
|
|
intended target is mid/liquid follow-through names such as TER/CRDO/CDNS/CPNG.
|
|
|
"""
|
|
|
|
|
|
moderate_gap_liquid_min_volume_ratio_14d: float | None = None
|
|
|
"""Minimum entry-time volume ratio for moderate-gap liquid candidates."""
|
|
|
|
|
|
moderate_gap_liquid_min_atr_pct: float | None = None
|
|
|
"""Minimum ATR(14) divided by today's open for moderate-gap liquid candidates."""
|
|
|
|
|
|
moderate_gap_liquid_max_entropy_20d: float | None = None
|
|
|
"""Maximum entropy allowed for moderate-gap liquid candidates."""
|
|
|
|
|
|
use_sector_thrust_sleeve: bool = False
|
|
|
"""When True, enable a sector breadth-confirmed thrust sleeve.
|
|
|
|
|
|
This is a PEAD-style synthetic breadth idea adapted to intraday momentum:
|
|
|
the sleeve only boosts names whose own early trend is supported by multiple
|
|
|
same-sector leaders showing synchronous confirmation and liquidity.
|
|
|
"""
|
|
|
|
|
|
sector_thrust_weight: float = 0.0
|
|
|
"""Blend weight for the sector breadth-confirmed thrust sleeve."""
|
|
|
|
|
|
sector_thrust_min_members: int = 2
|
|
|
"""Minimum number of same-sector names that must pass the thrust gate."""
|
|
|
|
|
|
sector_thrust_min_gain_pct: float | None = None
|
|
|
"""Minimum morning gain required for a ticker to contribute to sector thrust."""
|
|
|
|
|
|
sector_thrust_min_confirmation_return_pct: float | None = None
|
|
|
"""Minimum confirmation return required for sector thrust contributors."""
|
|
|
|
|
|
sector_thrust_min_entry_dollar_volume: float | None = None
|
|
|
"""Minimum entry-time dollar volume required for sector thrust contributors."""
|
|
|
|
|
|
sector_thrust_min_avg_dollar_vol_30d: float | None = None
|
|
|
"""Minimum prior 30-day average dollar volume required for sector thrust contributors."""
|
|
|
|
|
|
sector_thrust_min_sector_avg_confirmation_return_pct: float | None = None
|
|
|
"""Minimum average confirmation return across same-sector contributors."""
|
|
|
|
|
|
sector_thrust_min_sector_total_entry_dollar_volume: float | None = None
|
|
|
"""Minimum total entry-time dollar volume across same-sector contributors."""
|
|
|
|
|
|
use_liquid_cluster_engine: bool = False
|
|
|
"""When True, enable a separate post-allocation liquid-cluster stock engine.
|
|
|
|
|
|
Unlike sector_thrust, this does not change the main basket rank. It uses a
|
|
|
reserved fraction of the day budget to add a small number of liquid,
|
|
|
same-sector follow-through names after the core basket is selected.
|
|
|
"""
|
|
|
|
|
|
liquid_cluster_capital_fraction: float = 0.0
|
|
|
"""Fraction of the day budget reserved for the liquid-cluster engine."""
|
|
|
|
|
|
liquid_cluster_max_positions: int = 0
|
|
|
"""Maximum number of liquid-cluster stock positions to add."""
|
|
|
|
|
|
liquid_cluster_max_positions_per_sector: int = 1
|
|
|
"""Maximum number of liquid-cluster stock picks per sector."""
|
|
|
|
|
|
liquid_cluster_min_members: int = 2
|
|
|
"""Minimum number of same-sector names required to activate a cluster."""
|
|
|
|
|
|
liquid_cluster_min_gain_pct: float | None = None
|
|
|
"""Minimum morning gain required for a name to contribute to a liquid cluster."""
|
|
|
|
|
|
liquid_cluster_max_gain_pct: float | None = None
|
|
|
"""Maximum morning gain allowed for liquid-cluster contributors."""
|
|
|
|
|
|
liquid_cluster_min_confirmation_return_pct: float | None = None
|
|
|
"""Minimum confirmation return required for liquid-cluster contributors."""
|
|
|
|
|
|
liquid_cluster_min_entry_dollar_volume: float | None = None
|
|
|
"""Minimum entry-time dollar volume required for liquid-cluster contributors."""
|
|
|
|
|
|
liquid_cluster_min_avg_dollar_vol_30d: float | None = None
|
|
|
"""Minimum prior 30-day average dollar volume required for liquid-cluster contributors."""
|
|
|
|
|
|
liquid_cluster_max_avg_dollar_vol_30d: float | None = None
|
|
|
"""Optional upper bound on prior 30-day average dollar volume for cluster contributors."""
|
|
|
|
|
|
liquid_cluster_min_volume_ratio_14d: float | None = None
|
|
|
"""Minimum entry-time volume ratio required for liquid-cluster contributors."""
|
|
|
|
|
|
liquid_cluster_max_entropy_20d: float | None = None
|
|
|
"""Maximum entropy allowed for liquid-cluster contributors."""
|
|
|
|
|
|
liquid_cluster_min_sector_avg_confirmation_return_pct: float | None = None
|
|
|
"""Minimum average confirmation return across the activated liquid cluster."""
|
|
|
|
|
|
liquid_cluster_min_sector_total_entry_dollar_volume: float | None = None
|
|
|
"""Minimum combined entry-time dollar volume across the activated liquid cluster."""
|
|
|
|
|
|
liquid_cluster_require_special_liquidity_gate: bool = False
|
|
|
"""When True, contributors must already qualify as moderate-gap liquid or liquid large-cap."""
|
|
|
|
|
|
use_event_day_liquid_sleeve: bool = False
|
|
|
"""When True, activate a separate post-allocation liquid continuation sleeve on event-backed days.
|
|
|
|
|
|
This engine does not alter the core basket rank. It reserves a small slice
|
|
|
of the day budget to add liquid continuation names only when at least one
|
|
|
approved same-day event is also visible in the morning tape.
|
|
|
"""
|
|
|
|
|
|
event_day_liquid_capital_fraction: float = 0.0
|
|
|
"""Fraction of the day budget reserved for the event-day liquid sleeve."""
|
|
|
|
|
|
event_day_liquid_max_positions: int = 0
|
|
|
"""Maximum number of event-day liquid continuation names to add."""
|
|
|
|
|
|
event_day_liquid_soft_day_only: bool = False
|
|
|
"""Only activate the event-day liquid sleeve on soft days."""
|
|
|
|
|
|
event_day_liquid_min_event_names: int = 1
|
|
|
"""Minimum number of event-backed morning names required to activate the sleeve."""
|
|
|
|
|
|
event_day_liquid_allowed_event_types: list[str] = Field(default_factory=list)
|
|
|
"""Optional event types used only for event-day sleeve activation.
|
|
|
|
|
|
When empty, activation reuses the filtered event state already applied to
|
|
|
the core momentum strategy. When set, activation can see a broader set of
|
|
|
raw filing types without contaminating the core event sleeves.
|
|
|
"""
|
|
|
|
|
|
event_day_liquid_min_event_score: float | None = None
|
|
|
"""Minimum same-day event score required for activation contributors."""
|
|
|
|
|
|
event_day_liquid_min_event_support_score: float | None = None
|
|
|
"""Minimum support score required for activation contributors."""
|
|
|
|
|
|
event_day_liquid_min_total_event_entry_dollar_volume: float | None = None
|
|
|
"""Minimum combined entry-time dollar volume across activation contributors."""
|
|
|
|
|
|
event_day_liquid_min_gain_pct: float | None = None
|
|
|
"""Minimum morning gain required for added liquid continuation names."""
|
|
|
|
|
|
event_day_liquid_max_gain_pct: float | None = None
|
|
|
"""Maximum morning gain allowed for added liquid continuation names."""
|
|
|
|
|
|
event_day_liquid_min_confirmation_return_pct: float | None = None
|
|
|
"""Minimum confirmation return required for added liquid continuation names."""
|
|
|
|
|
|
event_day_liquid_min_entry_dollar_volume: float | None = None
|
|
|
"""Minimum entry-time dollar volume required for added liquid continuation names."""
|
|
|
|
|
|
event_day_liquid_min_avg_dollar_vol_30d: float | None = None
|
|
|
"""Minimum prior 30-day average dollar volume required for added liquid names."""
|
|
|
|
|
|
event_day_liquid_max_entropy_20d: float | None = None
|
|
|
"""Maximum 20-day entropy allowed for added liquid continuation names."""
|
|
|
|
|
|
event_day_liquid_min_support_score: float | None = None
|
|
|
"""Minimum blended support score required for added liquid continuation names."""
|
|
|
|
|
|
use_sector_etf_sleeve: bool = False
|
|
|
"""When True, allow a post-allocation sector ETF proxy sleeve.
|
|
|
|
|
|
This sleeve uses the same activated liquid-cluster sectors, but deploys a
|
|
|
reserved capital slice into sector ETFs instead of additional single-name
|
|
|
positions.
|
|
|
"""
|
|
|
|
|
|
sector_etf_capital_fraction: float = 0.0
|
|
|
"""Fraction of the day budget reserved for the sector ETF sleeve."""
|
|
|
|
|
|
sector_etf_max_positions: int = 1
|
|
|
"""Maximum number of sector ETF proxy positions to add."""
|
|
|
|
|
|
sector_etf_min_sector_score: float | None = None
|
|
|
"""Minimum liquid-cluster sector score required for ETF sleeve activation."""
|
|
|
|
|
|
use_gap_reclaim_sleeve: bool = False
|
|
|
"""Enable a high-gap reclaim sleeve for early flushes that stabilize below the open."""
|
|
|
|
|
|
gap_reclaim_weight: float = 0.0
|
|
|
"""Blend weight for the high-gap reclaim sleeve."""
|
|
|
|
|
|
gap_reclaim_min_gap_pct: float | None = None
|
|
|
"""Minimum opening gap required for a high-gap reclaim candidate."""
|
|
|
|
|
|
gap_reclaim_min_gain_pct: float | None = None
|
|
|
"""Minimum allowed entry-time gain for the reclaim sleeve (can be negative)."""
|
|
|
|
|
|
gap_reclaim_max_gain_pct: float | None = None
|
|
|
"""Maximum allowed entry-time gain for the reclaim sleeve."""
|
|
|
|
|
|
gap_reclaim_min_confirmation_return_pct: float | None = None
|
|
|
"""Minimum confirmation-bar return required for the reclaim sleeve."""
|
|
|
|
|
|
gap_reclaim_min_entry_dollar_volume: float | None = None
|
|
|
"""Minimum entry-time cumulative dollar volume required for the reclaim sleeve."""
|
|
|
|
|
|
gap_reclaim_min_recovery_from_opening_low_pct: float | None = None
|
|
|
"""Minimum rebound from the opening-range low required for the reclaim sleeve."""
|
|
|
|
|
|
fallback_liquid_largecap_slots: int = 0
|
|
|
"""Number of liquid large-cap fallback seats available after regular selection.
|
|
|
|
|
|
This does not change the main basket on normal days. It only allows a small
|
|
|
number of highly liquid large-cap names to fill otherwise sparse baskets.
|
|
|
"""
|
|
|
|
|
|
fallback_liquid_largecap_trigger_below: int = 0
|
|
|
"""Enable the liquid large-cap fallback only when regular picks are below this count.
|
|
|
|
|
|
Example: 2 means "only consider fallback seats when the main selection
|
|
|
found fewer than 2 names."
|
|
|
"""
|
|
|
|
|
|
candidate_source_mode: str = "daily_gap"
|
|
|
"""How to build the candidate universe before the final basket is selected.
|
|
|
|
|
|
- daily_gap: existing point-in-time opening-gap shortlist from daily bars
|
|
|
- intraday_first: build a broader daily seed list, fetch intraday for that
|
|
|
seed, then rank the final candidate shortlist using same-day entry-time
|
|
|
information only (still lookahead-free).
|
|
|
"""
|
|
|
|
|
|
candidate_seed_threshold: float = 0.0
|
|
|
"""Opening-gap threshold used only for the broader seed list when
|
|
|
candidate_source_mode='intraday_first'. Lower values widen the intraday
|
|
|
fetch universe without using same-day highs/closes."""
|
|
|
|
|
|
candidate_seed_max_per_day: int = 150
|
|
|
"""Maximum seed shortlist size per day when candidate_source_mode is
|
|
|
'intraday_first'. This bounds intraday fetch cost before the final
|
|
|
entry-time rerank."""
|
|
|
|
|
|
candidate_seed_liquid_overlay_slots: int = 0
|
|
|
"""Optional number of extra prior-day liquid large-cap seeds to add per day.
|
|
|
|
|
|
This is designed for names like TSLA/AVGO/NVDA that may not clear the main
|
|
|
opening-gap seed threshold but still deserve intraday-first evaluation
|
|
|
because of exceptional prior-day liquidity.
|
|
|
"""
|
|
|
|
|
|
candidate_seed_liquid_min_gap_pct: float | None = None
|
|
|
"""Minimum opening gap for the liquid overlay seed list.
|
|
|
|
|
|
Uses today's open vs prior close only, so it remains lookahead-free.
|
|
|
"""
|
|
|
|
|
|
candidate_seed_liquid_max_gap_pct: float | None = None
|
|
|
"""Maximum opening gap for the liquid overlay seed list."""
|
|
|
|
|
|
candidate_seed_liquid_min_avg_dollar_vol_30d: float | None = None
|
|
|
"""Minimum prior 30-day average dollar volume required for overlay seeds."""
|
|
|
|
|
|
candidate_seed_liquid_min_ret_5d: float | None = None
|
|
|
"""Minimum prior 5-day return required for overlay seeds."""
|
|
|
|
|
|
candidate_seed_liquid_max_entropy_20d: float | None = None
|
|
|
"""Maximum prior 20-day entropy allowed for overlay seeds."""
|
|
|
|
|
|
candidate_seed_leader_overlay_slots: int = 0
|
|
|
"""Optional number of extra liquid trend-leader seeds to add per day.
|
|
|
|
|
|
Unlike the liquid gap overlay, this path is meant to catch strong same-day
|
|
|
continuation names that did not gap enough to enter the main seed list but
|
|
|
already have exceptional prior trend, volatility, and liquidity.
|
|
|
"""
|
|
|
|
|
|
candidate_seed_leader_min_gap_pct: float | None = None
|
|
|
"""Minimum opening gap allowed for the trend-leader overlay."""
|
|
|
|
|
|
candidate_seed_leader_max_gap_pct: float | None = None
|
|
|
"""Maximum opening gap allowed for the trend-leader overlay."""
|
|
|
|
|
|
candidate_seed_leader_min_avg_dollar_vol_30d: float | None = None
|
|
|
"""Minimum prior 30-day average dollar volume for trend-leader overlay seeds."""
|
|
|
|
|
|
candidate_seed_leader_min_ret_5d: float | None = None
|
|
|
"""Minimum prior 5-day return required for trend-leader overlay seeds."""
|
|
|
|
|
|
candidate_seed_leader_min_atr_pct: float | None = None
|
|
|
"""Minimum ATR/open ratio required for trend-leader overlay seeds."""
|
|
|
|
|
|
candidate_seed_leader_max_entropy_20d: float | None = None
|
|
|
"""Maximum prior 20-day entropy allowed for trend-leader overlay seeds."""
|
|
|
|
|
|
candidate_seed_moderate_liquid_overlay_slots: int = 0
|
|
|
"""Number of extra moderate-gap liquid follow-through seeds to append per day.
|
|
|
|
|
|
This is intentionally separate from the base candidate rank. It widens the
|
|
|
intraday fetch set only for a bounded sleeve-specific profile, rather than
|
|
|
diluting the main gapper seed list.
|
|
|
"""
|
|
|
|
|
|
candidate_seed_moderate_liquid_min_gap_pct: float | None = None
|
|
|
"""Minimum opening gap for moderate-liquid overlay seeds."""
|
|
|
|
|
|
candidate_seed_moderate_liquid_max_gap_pct: float | None = None
|
|
|
"""Maximum opening gap for moderate-liquid overlay seeds."""
|
|
|
|
|
|
candidate_seed_moderate_liquid_min_avg_dollar_vol_30d: float | None = None
|
|
|
"""Minimum prior 30-day average dollar volume for moderate-liquid overlay seeds."""
|
|
|
|
|
|
candidate_seed_moderate_liquid_max_avg_dollar_vol_30d: float | None = None
|
|
|
"""Optional upper bound on prior 30-day dollar volume for moderate-liquid seeds."""
|
|
|
|
|
|
candidate_seed_moderate_liquid_min_ret_5d: float | None = None
|
|
|
"""Minimum prior 5-day return for moderate-liquid overlay seeds."""
|
|
|
|
|
|
candidate_seed_moderate_liquid_max_entropy_20d: float | None = None
|
|
|
"""Maximum prior 20-day entropy for moderate-liquid overlay seeds."""
|
|
|
|
|
|
candidate_seed_event_overlay_slots: int = 0
|
|
|
"""Number of actual filing-catalyst names to force into the seed shortlist.
|
|
|
|
|
|
This is a candidate-stage overlay, not a final-rank weight. It exists to
|
|
|
make sure same-day catalyst names are present in the intraday fetch set
|
|
|
even when pure gap/ret/entropy seed ranking would miss them.
|
|
|
"""
|
|
|
|
|
|
candidate_seed_event_min_score: float | None = None
|
|
|
"""Minimum same-day filing event score required for the event overlay."""
|
|
|
|
|
|
candidate_seed_event_min_gap_pct: float | None = None
|
|
|
"""Minimum opening gap for event-overlay names."""
|
|
|
|
|
|
candidate_seed_event_max_gap_pct: float | None = None
|
|
|
"""Maximum opening gap for event-overlay names."""
|
|
|
|
|
|
candidate_seed_event_min_avg_dollar_vol_30d: float | None = None
|
|
|
"""Minimum prior 30-day average dollar volume for event-overlay names."""
|
|
|
|
|
|
candidate_seed_event_min_ret_5d: float | None = None
|
|
|
"""Minimum prior 5-day return for event-overlay names."""
|
|
|
|
|
|
candidate_seed_event_max_entropy_20d: float | None = None
|
|
|
"""Maximum prior 20-day entropy allowed for event-overlay names."""
|
|
|
|
|
|
candidate_seed_ownership_overlay_slots: int = 0
|
|
|
"""Number of PIT-safe 13D/13G ownership-event names to add to the seed list.
|
|
|
|
|
|
This is a bounded candidate-stage overlay. It only makes eligible ownership
|
|
|
names available to the intraday engine; final ORB structure, ranking, and
|
|
|
risk gates still decide whether a trade is taken.
|
|
|
"""
|
|
|
|
|
|
candidate_seed_ownership_initial_only: bool = True
|
|
|
"""When True, ownership seed overlay only accepts initial-owner filings."""
|
|
|
|
|
|
candidate_seed_ownership_min_strength_score: float | None = None
|
|
|
"""Minimum ownership_strength_score required for ownership seed overlay."""
|
|
|
|
|
|
candidate_seed_ownership_min_gap_pct: float | None = None
|
|
|
"""Minimum opening gap for ownership-overlay names."""
|
|
|
|
|
|
candidate_seed_ownership_max_gap_pct: float | None = None
|
|
|
"""Maximum opening gap for ownership-overlay names."""
|
|
|
|
|
|
candidate_seed_ownership_min_avg_dollar_vol_30d: float | None = None
|
|
|
"""Minimum prior 30-day dollar volume for ownership-overlay names."""
|
|
|
|
|
|
candidate_seed_ownership_min_ret_5d: float | None = None
|
|
|
"""Minimum prior 5-day return for ownership-overlay names."""
|
|
|
|
|
|
candidate_seed_ownership_max_entropy_20d: float | None = None
|
|
|
"""Maximum prior 20-day entropy for ownership-overlay names."""
|
|
|
|
|
|
candidate_final_max_per_day: int = 30
|
|
|
"""Final candidate shortlist size per day after intraday-first reranking."""
|
|
|
|
|
|
candidate_intraday_rank_mode: str = "sleeves"
|
|
|
"""Final intraday-first shortlist ranking mode.
|
|
|
|
|
|
- sleeves: reuse the same five-sleeve / top-N basket logic used by the
|
|
|
execution engine, but at a wider candidate cutoff.
|
|
|
- weighted: rank entry-time candidates with a weighted quality score using
|
|
|
only same-day information known by the entry/confirmation bar.
|
|
|
"""
|
|
|
|
|
|
candidate_intraday_weight_gain: float = 0.0
|
|
|
"""Weighted-mode contribution from same-day morning gain at entry."""
|
|
|
|
|
|
candidate_intraday_weight_confirmation: float = 0.0
|
|
|
"""Weighted-mode contribution from confirmation-bar follow-through."""
|
|
|
|
|
|
candidate_intraday_weight_volume_ratio: float = 0.0
|
|
|
"""Weighted-mode contribution from entry-time volume ratio vs 14-day ADV."""
|
|
|
|
|
|
candidate_intraday_weight_entry_dollar_volume: float = 0.0
|
|
|
"""Weighted-mode contribution from entry-time cumulative dollar volume."""
|
|
|
|
|
|
candidate_intraday_weight_avg_dollar_vol_30d: float = 0.0
|
|
|
"""Weighted-mode contribution from prior 30-day average dollar volume."""
|
|
|
|
|
|
candidate_intraday_weight_support_score: float = 0.0
|
|
|
"""Weighted-mode contribution from same-day blended support score."""
|
|
|
|
|
|
candidate_intraday_weight_liquid_largecap: float = 0.0
|
|
|
"""Weighted-mode contribution from qualifying as a liquid large-cap name."""
|
|
|
|
|
|
candidate_intraday_weight_moderate_gap_liquid: float = 0.0
|
|
|
"""Weighted-mode contribution from qualifying as a moderate-gap liquid name."""
|
|
|
|
|
|
candidate_intraday_weight_gap: float = 0.0
|
|
|
"""Weighted-mode contribution from opening gap vs prior close."""
|
|
|
|
|
|
candidate_intraday_weight_ret_5d: float = 0.0
|
|
|
"""Weighted-mode contribution from prior 5-day return."""
|
|
|
|
|
|
candidate_intraday_weight_low_entropy: float = 0.0
|
|
|
"""Weighted-mode contribution from lower 20-day entropy."""
|
|
|
|
|
|
candidate_intraday_weight_sector_thrust: float = 0.0
|
|
|
"""Weighted-mode contribution from sector breadth-confirmed thrust."""
|
|
|
|
|
|
candidate_intraday_weight_event_score: float = 0.0
|
|
|
"""Weighted-mode contribution from same-day filing/event score."""
|
|
|
|
|
|
candidate_intraday_weight_attention_wiki: float = 0.0
|
|
|
"""Weighted-mode contribution from same-day wiki attention."""
|
|
|
|
|
|
candidate_intraday_weight_attention_news: float = 0.0
|
|
|
"""Weighted-mode contribution from same-day news/article attention."""
|
|
|
|
|
|
candidate_intraday_event_reserve_slots: int = 0
|
|
|
"""Number of same-day actual catalyst names to reserve inside the final shortlist.
|
|
|
|
|
|
Unlike candidate_seed_event_overlay_slots, this does not expand the
|
|
|
shortlist. It replaces weak tail picks inside candidate_final_max_per_day.
|
|
|
"""
|
|
|
|
|
|
candidate_intraday_event_reserve_min_score: float | None = None
|
|
|
"""Minimum same-day filing event score required for reserve-slot eligibility."""
|
|
|
|
|
|
candidate_intraday_event_reserve_soft_day_only: bool = False
|
|
|
"""Apply the final-shortlist catalyst reserve only on soft days."""
|
|
|
|
|
|
candidate_intraday_moderate_liquid_reserve_slots: int = 0
|
|
|
"""Number of moderate-gap liquid names to reserve inside the final intraday shortlist.
|
|
|
|
|
|
This reserve is applied after normal intraday ranking and replaces weak
|
|
|
non-event tail names. It ensures the downstream sleeve can see qualified
|
|
|
moderate-liquid follow-through names without changing the base rank formula.
|
|
|
"""
|
|
|
|
|
|
candidate_intraday_moderate_liquid_reserve_trigger_below: int = 0
|
|
|
"""Only apply moderate-liquid final reserve when the base shortlist would
|
|
|
produce fewer than this many execution picks. 0 means always allow reserve
|
|
|
when candidate_intraday_moderate_liquid_reserve_slots > 0.
|
|
|
"""
|
|
|
|
|
|
recent_live_scan_days: int = 0
|
|
|
"""When > 0, very recent backtests (window length <= this many trading days and ending
|
|
|
within this many calendar days of the latest completed backtest date) bypass the static
|
|
|
universe + daily pre-screen path and instead use a broad screener universe with
|
|
|
intraday-first candidate generation. Designed for same-day / recent sanity checks where
|
|
|
names like current Yahoo top gainers may not exist in the static research universe."""
|
|
|
|
|
|
recent_live_scan_min_price: float = 2.0
|
|
|
"""Minimum price for the recent live screener universe."""
|
|
|
|
|
|
recent_live_scan_avg_volume_min: int = 200_000
|
|
|
"""Minimum 3-month average volume for the recent live screener universe."""
|
|
|
|
|
|
recent_live_scan_market_cap_min: float = 100_000_000.0
|
|
|
"""Minimum market cap for the recent live screener universe."""
|
|
|
|
|
|
recent_live_scan_max_candidates_per_day: int = 150
|
|
|
"""Maximum daily shortlist size produced by the intraday-first recent scan."""
|
|
|
|
|
|
recent_live_scan_top_n: int | None = None
|
|
|
"""Optional top-N override used only for recent live-scan windows."""
|
|
|
|
|
|
recent_live_scan_min_morning_gain_pct: float | None = None
|
|
|
"""Optional morning-gain floor override used only for recent live-scan windows."""
|
|
|
|
|
|
recent_live_scan_max_morning_gain_pct: float | None = None
|
|
|
"""Optional morning-gain cap override used only for recent live-scan windows."""
|
|
|
|
|
|
recent_live_scan_min_confirmation_return_pct: float | None = None
|
|
|
"""Optional confirmation-return override used only for recent live-scan windows."""
|
|
|
|
|
|
recent_live_scan_min_entry_dollar_volume: float | None = None
|
|
|
"""Optional entry dollar-volume override used only for recent live-scan windows."""
|
|
|
|
|
|
recent_live_scan_max_gap_pct: float | None = None
|
|
|
"""Optional opening-gap cap override used only for recent live-scan windows."""
|
|
|
|
|
|
recent_live_scan_max_entropy_20d: float | None = None
|
|
|
"""Optional entropy cap override used only for recent live-scan windows."""
|
|
|
|
|
|
recent_live_scan_use_slow_ignite_sleeve: bool | None = None
|
|
|
"""Optional recent-window override for enabling the slow-ignite sleeve."""
|
|
|
|
|
|
recent_live_scan_slow_ignite_weight: float | None = None
|
|
|
"""Optional recent-window override for slow-ignite sleeve weight."""
|
|
|
|
|
|
recent_live_scan_slow_ignite_min_gain_pct: float | None = None
|
|
|
"""Optional recent-window override for slow-ignite minimum gain."""
|
|
|
|
|
|
recent_live_scan_slow_ignite_max_gain_pct: float | None = None
|
|
|
"""Optional recent-window override for slow-ignite maximum gain."""
|
|
|
|
|
|
recent_live_scan_slow_ignite_min_entry_dollar_volume: float | None = None
|
|
|
"""Optional recent-window override for slow-ignite minimum entry dollar volume."""
|
|
|
|
|
|
recent_live_scan_slow_ignite_max_entropy_20d: float | None = None
|
|
|
"""Optional recent-window override for slow-ignite maximum entropy."""
|
|
|
|
|
|
recent_live_scan_use_liquid_largecap_sleeve: bool | None = None
|
|
|
"""Optional recent-window override for enabling the liquid large-cap sleeve."""
|
|
|
|
|
|
recent_live_scan_liquid_largecap_weight: float | None = None
|
|
|
"""Optional recent-window override for liquid large-cap sleeve weight."""
|
|
|
|
|
|
recent_live_scan_liquid_largecap_min_gain_pct: float | None = None
|
|
|
"""Optional recent-window override for liquid large-cap minimum gain."""
|
|
|
|
|
|
recent_live_scan_liquid_largecap_max_gain_pct: float | None = None
|
|
|
"""Optional recent-window override for liquid large-cap maximum gain."""
|
|
|
|
|
|
recent_live_scan_liquid_largecap_min_confirmation_return_pct: float | None = None
|
|
|
"""Optional recent-window override for liquid large-cap confirmation return."""
|
|
|
|
|
|
recent_live_scan_liquid_largecap_min_entry_dollar_volume: float | None = None
|
|
|
"""Optional recent-window override for liquid large-cap minimum entry dollar volume."""
|
|
|
|
|
|
recent_live_scan_liquid_largecap_min_avg_dollar_vol_30d: float | None = None
|
|
|
"""Optional recent-window override for liquid large-cap minimum average dollar volume."""
|
|
|
|
|
|
recent_live_scan_liquid_largecap_max_entropy_20d: float | None = None
|
|
|
"""Optional recent-window override for liquid large-cap maximum entropy."""
|
|
|
|
|
|
candidate_require_event_flag: bool = False
|
|
|
"""When True, the daily candidate shortlist only includes names with a same-day
|
|
|
filing-based catalyst flag. This is stricter than using attention proxies alone
|
|
|
and is intended for event-driven research variants."""
|
|
|
|
|
|
candidate_min_event_score: float | None = None
|
|
|
"""Minimum same-day filing event score required at the candidate stage.
|
|
|
Ignored when no same-day event features are present."""
|
|
|
|
|
|
candidate_allowed_event_types: list[str] = Field(default_factory=list)
|
|
|
"""Optional same-day filing event types allowed at the candidate stage.
|
|
|
|
|
|
When non-empty, candidate-stage catalyst gates only treat these filing
|
|
|
types as valid. This lets momentum variants use actual catalysts such as
|
|
|
earnings/material events while excluding weaker attention-like filings.
|
|
|
"""
|
|
|
|
|
|
candidate_weight_event_score: float = 0.0
|
|
|
"""Ranking weight for same-day filing event score in momentum candidate selection."""
|
|
|
|
|
|
candidate_weight_attention_wiki: float = 0.0
|
|
|
"""Ranking weight for same-day Wikipedia attention in momentum candidate selection."""
|
|
|
|
|
|
candidate_weight_attention_news: float = 0.0
|
|
|
"""Ranking weight for same-day news/article attention in momentum candidate selection."""
|
|
|
|
|
|
candidate_min_attention_wiki_spike_10d: float | None = None
|
|
|
"""Minimum same-day wiki spike required at the candidate stage."""
|
|
|
|
|
|
candidate_min_attention_article_count_3d: int | None = None
|
|
|
"""Minimum same-day 3-day article count required at the candidate stage."""
|
|
|
|
|
|
candidate_min_attention_us_article_count_3d: int | None = None
|
|
|
"""Minimum same-day 3-day US article count required at the candidate stage."""
|
|
|
|
|
|
candidate_min_attention_resolver_confidence: float | None = None
|
|
|
"""Minimum same-day entity resolver confidence required at the candidate stage."""
|
|
|
|
|
|
|
|
|
class ORBStrategyParams(BaseModel):
|
|
|
"""Parameters for the Opening Range Breakout (ORB) strategy."""
|
|
|
|
|
|
engine_family: str = "quality_breakout"
|
|
|
"""Candidate engine family: classic_breakout | quality_breakout | compression_breakout | gainers_leader | leader_followthrough | stocks_in_play_dual_regime | orb_pullback_v1 | vwap_reclaim_v1 | hypergap_failure_v1.
|
|
|
quality_breakout is the backward-compatible default because it supports the
|
|
|
existing body-ratio / momentum extensions while leaving them disabled at 0 weight."""
|
|
|
|
|
|
live_readiness: str = "live_ready"
|
|
|
"""Research classification for the signal family: live_ready | research_only."""
|
|
|
|
|
|
# ORB window
|
|
|
orb_minutes: int = 5
|
|
|
"""Duration of the opening range in minutes. 5 = first 5-min candle (9:30–9:35 ET)."""
|
|
|
|
|
|
sim_bar_minutes: int = 5
|
|
|
"""Bar interval for breakout detection and stop management after the ORB candle.
|
|
|
5 = use raw 5-min bars (default). 30 = aggregate to 30-min bars (more realistic, fewer whipsaws).
|
|
|
The ORB candle itself always uses the first 5-min bar regardless of this setting."""
|
|
|
|
|
|
# Entry
|
|
|
entry_direction: str = "long_only"
|
|
|
"""Entry direction filter: 'long_only' (bullish candle only), 'candle' (both), 'both' (always)."""
|
|
|
|
|
|
order_timeout_minutes: int = 45
|
|
|
"""Cancel unfilled breakout order after this many minutes from open. Default = 45min = 10:15 ET."""
|
|
|
|
|
|
live_breakout_use_bar_high: bool = False
|
|
|
"""LIVE-ONLY toggle (no effect on backtest, which already uses bar high).
|
|
|
|
|
|
When True, the live ORB engine's run_breakout_check additionally fetches today's
|
|
|
5-min intraday bars and triggers an entry if any post-ORB bar's high (low for
|
|
|
short) crossed the breakout level inside order_timeout_minutes — even if the
|
|
|
current snapshot price has since retraced below the level. This catches
|
|
|
spike-and-retrace breakouts that the default snapshot-only check misses
|
|
|
(e.g., ARM 2026-05-07 09:35 ET high 232.19 with retrace to 224 by 09:40).
|
|
|
|
|
|
Entry price is still the snapshot price (market order), so a retraced fill
|
|
|
will be lower than backtest's assumed entry at max(breakout_level, bar.open).
|
|
|
Stops are placed relative to the actual fill, so live R-multiples may diverge
|
|
|
from backtest R-multiples on the same trade. The engine logs
|
|
|
`breakout_level - fill_price` on each bar-high-triggered entry for auditability.
|
|
|
"""
|
|
|
|
|
|
# Universe quality filters (applied during ORB pre-screening)
|
|
|
min_price: float = 10.0
|
|
|
"""Minimum stock price. $10 is the ORB paper's practical minimum."""
|
|
|
|
|
|
min_avg_dollar_volume: float = 25_000_000.0
|
|
|
"""Minimum 30-day average daily dollar volume ($25M). Ensures sufficient liquidity."""
|
|
|
|
|
|
min_atr_14: float = 0.50
|
|
|
"""Minimum ATR(14) in dollars ($0.50). Ensures sufficient intraday range to trade."""
|
|
|
|
|
|
min_atr_pct: float | None = None
|
|
|
"""Minimum ATR(14) as a fraction of prev_close (e.g. 0.04 = 4%). Filters out
|
|
|
low-volatility names where the ORB setup lacks explosive follow-through potential.
|
|
|
None disables (legacy behavior)."""
|
|
|
|
|
|
max_atr_pct: float | None = None
|
|
|
"""Maximum ATR(14) as a fraction of prev_close (e.g. 0.10 = 10%). Caps extreme-volatility
|
|
|
names that have large individual losses (MSTR, crypto stocks, micro-caps). None disables."""
|
|
|
|
|
|
# RVOL-based candidate selection
|
|
|
min_rvol: float | None = 1.0
|
|
|
"""Minimum approximate RVOL at open. RVOL = first_5min_vol / (avg_daily_vol / 78).
|
|
|
Note: this is an approximation — actual morning volume is 2–3× uniform rate,
|
|
|
so calibrate relative to that systematic bias. None disables the filter."""
|
|
|
|
|
|
iex_live_intraday_volume_multiplier: float = 1.0
|
|
|
"""LIVE-ONLY multiplier for IEX 5-minute intraday volume-derived filters.
|
|
|
|
|
|
Alpaca IEX bars usually report a small fraction of consolidated SIP volume,
|
|
|
while ORB research thresholds are calibrated on SIP-like historical bars.
|
|
|
Values above 1.0 scale IEX opening RVOL, first-bar dollar volume, and
|
|
|
premarket dollar volume for live filter decisions only. Stored diagnostics
|
|
|
keep the raw IEX values.
|
|
|
"""
|
|
|
|
|
|
max_candidates: int = 20
|
|
|
"""Maximum candidates to pass to intraday fetch and simulate per day."""
|
|
|
|
|
|
max_candidates_per_sector: int | None = None
|
|
|
"""Optional diversification cap after ranking.
|
|
|
Example: 2 = at most two names from the same sector in the day's final ORB list.
|
|
|
None disables the cap."""
|
|
|
|
|
|
sector_confirmation_enabled: bool = False
|
|
|
"""When True, compute a same-sector confirmation signal for ORB candidates.
|
|
|
|
|
|
The signal is based only on contemporaneous opening-range candidates from the
|
|
|
same sector and direction. It is disabled by default so existing strategies
|
|
|
are unchanged unless a config explicitly opts in.
|
|
|
"""
|
|
|
|
|
|
sector_confirmation_min_members: int = 2
|
|
|
"""Minimum same-sector, same-direction ORB candidates required to confirm a
|
|
|
sector cluster."""
|
|
|
|
|
|
sector_confirmation_min_avg_orb_return_pct: float | None = 0.0
|
|
|
"""Minimum average ORB candle return across the confirming sector cluster.
|
|
|
None disables the average-return gate."""
|
|
|
|
|
|
sector_confirmation_min_total_first_bar_dollar_vol: float | None = None
|
|
|
"""Minimum combined first-bar dollar volume across the confirming sector
|
|
|
cluster. None disables the liquidity gate."""
|
|
|
|
|
|
sector_confirmation_score_weight: float = 0.0
|
|
|
"""Additional ranking weight for sector-confirmed ORB clusters.
|
|
|
Values at 0.0 leave ranking unchanged."""
|
|
|
|
|
|
sector_confirmation_size_scale: float = 1.0
|
|
|
"""Position-size multiplier for sector-confirmed candidates.
|
|
|
Values above 1.0 boost confirmed clusters; values below 1.0 reduce them."""
|
|
|
|
|
|
sector_confirmation_liquid_min_premarket_dollar_vol: float | None = None
|
|
|
"""Optional candidate-level liquidity floor for a higher-quality
|
|
|
sector-confirmation sizing tier. None disables tiered sizing."""
|
|
|
|
|
|
sector_confirmation_liquid_size_scale: float | None = None
|
|
|
"""Position-size multiplier for sector-confirmed candidates that also pass
|
|
|
sector_confirmation_liquid_min_premarket_dollar_vol. None falls back to
|
|
|
sector_confirmation_size_scale."""
|
|
|
|
|
|
sector_confirmation_illiquid_size_scale: float | None = None
|
|
|
"""Position-size multiplier for sector-confirmed candidates that fail the
|
|
|
candidate-level liquidity tier. None falls back to sector_confirmation_size_scale."""
|
|
|
|
|
|
sector_confirmation_unconfirmed_size_scale: float = 1.0
|
|
|
"""Position-size multiplier for candidates without sector confirmation when
|
|
|
the sector-confirmation engine is enabled. 1.0 leaves them unchanged."""
|
|
|
|
|
|
soft_day_sector_confirmation_override_enabled: bool = False
|
|
|
"""When True, allow sector-confirmed primary ORB candidates to bypass the
|
|
|
normal soft-day primary gates. This keeps soft-day participation tied to
|
|
|
cross-sectional sector breadth instead of relaxing regime filters globally."""
|
|
|
|
|
|
soft_day_sector_confirmation_override_max_trades: int | None = 1
|
|
|
"""Maximum soft-day primary trades admitted through sector-confirmation override."""
|
|
|
|
|
|
soft_day_sector_confirmation_override_min_score_pct: float | None = None
|
|
|
"""Minimum daily candidate rank percentile for the soft-day sector override."""
|
|
|
|
|
|
soft_day_sector_confirmation_override_min_premarket_dollar_vol: float | None = None
|
|
|
"""Minimum candidate premarket dollar volume for the soft-day sector override."""
|
|
|
|
|
|
soft_day_sector_confirmation_override_allowed_reason_parts: list[str] | None = None
|
|
|
"""Optional soft-day reason allowlist. A reason matches when any listed part
|
|
|
appears in the '+'-separated soft_day_reason, e.g. 'breadth'."""
|
|
|
|
|
|
soft_day_sector_confirmation_override_allowed_trigger_types: list[str] | None = None
|
|
|
"""Optional trigger allowlist for the soft-day sector override."""
|
|
|
|
|
|
soft_day_sector_confirmation_override_size_scale: float = 1.0
|
|
|
"""Additional position-size multiplier for trades admitted by the soft-day
|
|
|
sector-confirmation override. Day-level soft regime scalers still apply."""
|
|
|
|
|
|
soft_day_sector_confirmation_override_min_day_size_scale: float | None = None
|
|
|
"""Optional day-level sizing floor for trades admitted by the soft-day
|
|
|
sector-confirmation override. This lets a confirmed sector sleeve deploy a
|
|
|
small probe even when market-ORB quality would otherwise scale to zero."""
|
|
|
|
|
|
soft_day_sector_confirmation_override_loss_cap_pct: float | None = None
|
|
|
"""Optional fixed stop distance for trades admitted by the soft-day sector
|
|
|
override. This contains weak-regime probes without changing normal ORB exits."""
|
|
|
|
|
|
entry_market_guard_enabled: bool = False
|
|
|
"""When True, scale or skip entries if the selected market ETF has weakened
|
|
|
by the candidate's entry time. Uses only market data available at the entry
|
|
|
bar open to avoid same-bar close lookahead."""
|
|
|
|
|
|
entry_market_guard_ticker: str | None = None
|
|
|
"""Market ETF used for the entry-time guard. None falls back to
|
|
|
market_regime_ticker, then QQQ."""
|
|
|
|
|
|
entry_market_guard_min_return_pct: float | None = None
|
|
|
"""Minimum intraday market return from the first regular-session open to
|
|
|
the candidate entry time. Below this level the guard becomes active."""
|
|
|
|
|
|
entry_market_guard_size_scale: float = 1.0
|
|
|
"""Defensive position-size multiplier when the entry-time market guard is
|
|
|
active and entry_market_guard_skip_trade is False."""
|
|
|
|
|
|
entry_market_guard_skip_trade: bool = False
|
|
|
"""When True, skip guarded entries instead of scaling them down."""
|
|
|
|
|
|
entry_market_guard_apply_to_soft_day: bool = True
|
|
|
"""Whether the entry-time market guard also applies to soft-day fallback
|
|
|
entries."""
|
|
|
|
|
|
min_candidates_to_trade: int = 3
|
|
|
"""Skip the day entirely if fewer than this many candidates pass all filters."""
|
|
|
|
|
|
full_size_positions_threshold: int | None = None
|
|
|
"""When set, sparse ORB days scale down total deployed capital instead of
|
|
|
always using the full day budget.
|
|
|
|
|
|
Example: threshold=3 means days with only 1-2 entry-ready names are sized
|
|
|
below 100% of the normal day budget, while 3+ names use full size.
|
|
|
"""
|
|
|
|
|
|
sparse_day_size_floor: float = 1.0
|
|
|
"""Minimum day-level size scaler when full_size_positions_threshold is active.
|
|
|
0.5 means even a one-name day still deploys 50% of the normal day budget."""
|
|
|
|
|
|
basket_quality_relative_floor: float | None = None
|
|
|
"""Optional dynamic floor applied after ORB candidate ranking.
|
|
|
|
|
|
When set, keep only names whose composite score is at least this fraction
|
|
|
of the day's best score. This lets the basket shrink on weak residual tails
|
|
|
instead of always carrying the full ranked list into chronological entry
|
|
|
allocation.
|
|
|
"""
|
|
|
|
|
|
basket_quality_min_count: int = 0
|
|
|
"""Minimum number of ranked names to keep even when the relative floor prunes
|
|
|
the tail. 0 means no forced minimum beyond the score floor survivors."""
|
|
|
|
|
|
# Composite ranking weights
|
|
|
weight_rvol: float = 0.60
|
|
|
"""RVOL weight in composite ranking score (50% from paper + 10% from spread, which is unavailable)."""
|
|
|
|
|
|
weight_gap: float = 0.25
|
|
|
"""Gap% weight (proxy for premarket activity, which is unavailable)."""
|
|
|
|
|
|
weight_dollar_vol: float = 0.15
|
|
|
"""First-5-min dollar volume weight."""
|
|
|
|
|
|
weight_premarket_dollar_vol: float = 0.0
|
|
|
"""Premarket dollar-volume weight. Serves as a same-day catalyst / attention proxy
|
|
|
when dedicated news data is unavailable."""
|
|
|
|
|
|
min_volume_attention_rank_pct: float | None = None
|
|
|
"""Minimum same-day cross-sectional volume-attention percentile.
|
|
|
This uses percentile ranks of opening-range dollar volume, opening RVOL, and
|
|
|
premarket dollar volume instead of absolute SIP-calibrated dollar thresholds,
|
|
|
so it remains usable when live intraday bars come from IEX."""
|
|
|
|
|
|
volume_attention_rank_weight_rvol: float = 0.45
|
|
|
"""Blend weight for opening RVOL inside volume_attention_rank_pct."""
|
|
|
|
|
|
volume_attention_rank_weight_opening_dollar_vol: float = 0.45
|
|
|
"""Blend weight for opening-range dollar-volume percentile inside
|
|
|
volume_attention_rank_pct."""
|
|
|
|
|
|
volume_attention_rank_weight_premarket_dollar_vol: float = 0.10
|
|
|
"""Blend weight for premarket dollar-volume percentile inside
|
|
|
volume_attention_rank_pct. Keep this small for IEX live mode because
|
|
|
premarket prints can be sparse."""
|
|
|
|
|
|
volume_attention_rank_weight_global_context: float = 1.0
|
|
|
"""Blend weight for the normal all-candidate volume-attention percentile."""
|
|
|
|
|
|
volume_attention_rank_weight_sector_context: float = 0.0
|
|
|
"""Blend weight for sector-relative volume-attention percentile.
|
|
|
This helps a ticker that is unusually active for its sector without relying
|
|
|
on absolute SIP-calibrated volume thresholds."""
|
|
|
|
|
|
volume_attention_rank_weight_price_context: float = 0.0
|
|
|
"""Blend weight for price-bucket-relative volume-attention percentile.
|
|
|
This prevents very high-dollar-price leaders from dominating purely because
|
|
|
equal share volume creates larger dollar volume."""
|
|
|
|
|
|
volume_attention_context_min_bucket_size: int = 3
|
|
|
"""Minimum candidates required before sector/price contextual ranks replace
|
|
|
the global fallback rank for that bucket."""
|
|
|
|
|
|
weight_volume_attention_rank: float = 0.0
|
|
|
"""Composite ranking weight for the cross-sectional volume-attention rank.
|
|
|
Unlike raw dollar volume, this is scale-insensitive across SIP backtests and
|
|
|
IEX live bars."""
|
|
|
|
|
|
weight_body_ratio: float = 0.0
|
|
|
"""ORB candle directional conviction: (close-open)/(high-low) for longs, reversed for shorts.
|
|
|
High value = first candle decisively moved in the breakout direction."""
|
|
|
|
|
|
weight_close_location: float = 0.0
|
|
|
"""First ORB candle close location within its range: (close-low)/(high-low).
|
|
|
leader_followthrough typically rewards closes that finish near the candle high,
|
|
|
even if the opening bar is slightly red (red-to-green reclaim behavior)."""
|
|
|
|
|
|
weight_momentum: float = 0.0
|
|
|
"""5-day prior price momentum weight. Positive = stock already trending in breakout direction."""
|
|
|
|
|
|
weight_entropy: float = 0.0
|
|
|
"""Entropy(20d) ranking weight. compression_breakout typically rewards lower entropy."""
|
|
|
|
|
|
weight_atr_ratio: float = 0.0
|
|
|
"""Recent ATR(10) / ATR(60) ranking weight."""
|
|
|
|
|
|
weight_obv_slope: float = 0.0
|
|
|
"""OBV accumulation slope (20d) ranking weight. Positive OBV = smart-money accumulation pre-breakout."""
|
|
|
|
|
|
weight_obv_slope_5: float = 0.0
|
|
|
"""OBV accumulation slope (5d) ranking weight. Short-term accumulation signal, orthogonal to 20d slope."""
|
|
|
|
|
|
weight_gap_zscore: float = 0.0
|
|
|
"""Opening-gap z-score ranking weight relative to prior 20 sessions."""
|
|
|
|
|
|
weight_red_to_green_gap: float = 0.0
|
|
|
"""Extra ranking weight for downside gaps that reclaim into long ORB breakouts.
|
|
|
This is separate from weight_gap, which uses absolute gap size in gainers
|
|
|
engines. A positive value prioritizes red-to-green continuation candidates."""
|
|
|
|
|
|
red_to_green_reserved_slots: int = 0
|
|
|
"""Reserve up to this many final ORB basket slots for downside-gap reclaim
|
|
|
candidates. Unlike weight_red_to_green_gap, this is a basket-construction
|
|
|
overlay: it can include a qualifying red-to-green setup even when its
|
|
|
composite score would otherwise fall just outside max_candidates."""
|
|
|
|
|
|
candidate_seed_overlay_reserved_slots: int = 0
|
|
|
"""Reserve up to this many final ORB basket slots for candidate-seed overlay
|
|
|
names. The overlay still has to pass the ORB candle direction and breakout
|
|
|
simulation, but this lets explicitly seeded leaders avoid being buried by
|
|
|
the standard gap/RVOL-heavy rank."""
|
|
|
|
|
|
red_to_green_min_abs_gap_pct: float | None = None
|
|
|
"""Minimum absolute downside gap required for a reserved red-to-green slot.
|
|
|
None accepts any negative gap."""
|
|
|
|
|
|
red_to_green_min_body_ratio: float | None = None
|
|
|
"""Minimum ORB body ratio required for a reserved red-to-green slot."""
|
|
|
|
|
|
red_to_green_min_close_location: float | None = None
|
|
|
"""Minimum ORB close location required for a reserved red-to-green slot."""
|
|
|
|
|
|
red_to_green_min_orb_return: float | None = None
|
|
|
"""Minimum first ORB-bar return required for a reserved red-to-green slot.
|
|
|
|
|
|
This separates true opening acceleration from downside gaps that merely
|
|
|
close well inside a narrow first range.
|
|
|
"""
|
|
|
|
|
|
red_to_green_min_rvol: float | None = None
|
|
|
"""Minimum opening RVOL required for a reserved red-to-green slot."""
|
|
|
|
|
|
weight_event_catalyst: float = 0.0
|
|
|
"""Same-day catalyst weight from actual filing events.
|
|
|
Used by stocks_in_play_dual_regime to reward names with a concrete event
|
|
|
instead of relying only on attention proxies."""
|
|
|
|
|
|
prior_event_lookback_days: int = 0
|
|
|
"""Calendar days to look back for prior earnings/guidance events in DB.
|
|
|
0=off (default, V24 parity). 7=V46. When >0 AND weight_event_catalyst>0,
|
|
|
uses DB events table path instead of Oracle REST API for event_flag/event_score.
|
|
|
Marks each trading day within this window after qualifying events as
|
|
|
event_flag=True, event_score=1.0. Event types controlled by prior_event_types."""
|
|
|
|
|
|
prior_event_types: list[str] = Field(
|
|
|
default_factory=lambda: ["earnings_release", "guidance_update"]
|
|
|
)
|
|
|
"""DB event types to include in prior_event_lookback_days signal.
|
|
|
Default matches V46 (earnings_release + guidance_update).
|
|
|
Set to ['earnings_release'] for earnings-only variant."""
|
|
|
|
|
|
prior_event_decay_half_life_days: float | None = None
|
|
|
"""Optional prior-event recency half-life.
|
|
|
None preserves the legacy binary prior-event score. When set, D-1 events
|
|
|
keep full score and older events decay by this half-life before ranking."""
|
|
|
|
|
|
prior_event_decay_min_score: float | None = None
|
|
|
"""Optional floor for decayed prior-event scores.
|
|
|
When set, prior events below the floor no longer count as event_flag=True."""
|
|
|
|
|
|
prior_event_guidance_score_scale: float = 1.0
|
|
|
"""Type score multiplier for guidance_update prior events when event decay
|
|
|
is enabled. Earnings events remain at 1.0."""
|
|
|
|
|
|
prior_event_snapshot_id: str | None = None
|
|
|
"""Optional frozen dataset id for prior-event enrichment.
|
|
|
When set, runs materialize/read request shards under a stable snapshot
|
|
|
family instead of an anonymous ad-hoc cache path. This is the reproducible
|
|
|
mode for ORB research that depends on prior_event_lookback_days."""
|
|
|
|
|
|
daily_bar_snapshot_id: str | None = None
|
|
|
"""Optional frozen dataset id for daily OHLCV bars.
|
|
|
When set, runs read ticker parquet files from a stable daily snapshot
|
|
|
directory instead of the mutable shared daily cache."""
|
|
|
|
|
|
daily_bar_snapshot_overlay_enabled: bool = False
|
|
|
"""Allow a mutable daily overlay on top of daily_bar_snapshot_id.
|
|
|
This is disabled by default because frozen research runs must not change
|
|
|
when cache overlays are rebuilt later."""
|
|
|
|
|
|
weight_attention_wiki: float = 0.0
|
|
|
"""Wikipedia attention weight for actual stocks-in-play / gainers ranking."""
|
|
|
|
|
|
weight_attention_news: float = 0.0
|
|
|
"""News/article attention weight for actual stocks-in-play / gainers ranking."""
|
|
|
|
|
|
ownership_13dg_lookback_days: int = 0
|
|
|
"""Calendar days to look back for PIT-safe SC 13D/13G ownership events.
|
|
|
0 disables the ownership overlay. Filing dates must be strictly before the
|
|
|
trade date, so same-day filings never affect same-day ORB entries."""
|
|
|
|
|
|
ownership_13dg_reference_path: str = "data/reference/ownership_13d13g_events_pit.parquet"
|
|
|
"""Local PIT parquet used for 13D/13G ownership enrichment."""
|
|
|
|
|
|
weight_ownership_13dg: float = 0.0
|
|
|
"""Ranking weight for any recent 13D/13G ownership filing."""
|
|
|
|
|
|
weight_ownership_initial_13dg: float = 0.0
|
|
|
"""Ranking weight for recent initial-owner 13D/13G filings."""
|
|
|
|
|
|
ownership_initial_size_scale: float = 1.0
|
|
|
"""Position-size multiplier for recent initial-owner 13D/13G setups.
|
|
|
Values at or below 1.0 disable the sizing overlay."""
|
|
|
|
|
|
ownership_initial_min_score_rank_pct: float | None = None
|
|
|
"""Optional minimum ORB candidate rank percentile before applying the
|
|
|
initial-owner ownership size boost."""
|
|
|
|
|
|
ownership_initial_allowed_trigger_types: list[str] | None = None
|
|
|
"""Optional trigger allowlist for the initial-owner ownership size boost."""
|
|
|
|
|
|
ownership_initial_ignore_scaled_risk_overlays: bool = True
|
|
|
"""When True, do not boost ownership names already reduced by defensive
|
|
|
risk overlays."""
|
|
|
|
|
|
form4_lookback_days: int = 0
|
|
|
"""Calendar days to look back for PIT-safe SEC Form 4 purchase clusters.
|
|
|
0 disables the Form 4 overlay. Filing dates must be strictly before the
|
|
|
trade date."""
|
|
|
|
|
|
form4_reference_path: str = "data/reference/form4_daily_events_pit.parquet"
|
|
|
"""Local PIT parquet used for Form 4 insider purchase enrichment."""
|
|
|
|
|
|
form4_size_scale: float = 1.0
|
|
|
"""Position-size multiplier for qualified recent Form 4 insider-buy setups."""
|
|
|
|
|
|
form4_min_total_value: float | None = None
|
|
|
"""Minimum aggregated purchase value inside the Form 4 lookback window."""
|
|
|
|
|
|
form4_min_owner_count: int | None = None
|
|
|
"""Minimum max owner_count inside the Form 4 lookback window."""
|
|
|
|
|
|
form4_min_c_suite_count: int | None = None
|
|
|
"""Minimum max C-suite buyer count inside the Form 4 lookback window."""
|
|
|
|
|
|
form4_require_cluster_or_csuite: bool = False
|
|
|
"""When True, accept Form 4 sizing only when owner_count or C-suite gates pass."""
|
|
|
|
|
|
form4_allowed_trigger_types: list[str] | None = None
|
|
|
"""Optional trigger allowlist for Form 4 sizing."""
|
|
|
|
|
|
form4_ignore_scaled_risk_overlays: bool = True
|
|
|
"""When True, do not boost Form 4 names already reduced by defensive overlays."""
|
|
|
|
|
|
min_body_ratio: float = 0.0
|
|
|
"""Minimum ORB candle body/range conviction. 0 disables the filter."""
|
|
|
|
|
|
min_close_location: float = 0.0
|
|
|
"""Minimum ORB candle close-location filter for leader_followthrough.
|
|
|
Example: 0.50 means the candle must close in the upper half of its range."""
|
|
|
|
|
|
max_close_location_short: float = 1.0
|
|
|
"""Maximum ORB candle close-location filter for short setups.
|
|
|
Example: 0.40 means a failed-ORB short must close in the lower 40% of the
|
|
|
opening range. 1.0 disables the filter."""
|
|
|
|
|
|
allow_doji_breakout: bool = False
|
|
|
"""When True, doji first bars are still allowed to trade via ORB high/low breakout.
|
|
|
Useful for gainers-style leader chasing or leader followthrough setups where the
|
|
|
opening 5-min candle can pause before a strong trend day. Default False to preserve
|
|
|
classic ORB behavior."""
|
|
|
|
|
|
allow_red_to_green_breakout: bool = False
|
|
|
"""When True, gainers_leader / leader_followthrough may trade long ORB-high breakouts
|
|
|
even if the first ORB candle closes red. This is meant for leader-followthrough days
|
|
|
where a strong name briefly dips after the open before reclaiming the ORB high."""
|
|
|
|
|
|
require_event_flag: bool = False
|
|
|
"""Require a same-day filing-based catalyst flag for candidate inclusion.
|
|
|
Designed for stocks_in_play_dual_regime, where attention alone is not enough."""
|
|
|
|
|
|
allowed_event_types: list[str] = Field(default_factory=list)
|
|
|
"""Optional whitelist of filing event types that count as catalysts.
|
|
|
Empty list means any same-day filing event is accepted."""
|
|
|
|
|
|
allow_failed_orb_short: bool = False
|
|
|
"""Allow failed gap-up ORB shorts in dual-regime mode."""
|
|
|
|
|
|
require_vwap_confirmation: bool = False
|
|
|
"""Require the ORB candle close to confirm against VWAP:
|
|
|
longs must close above VWAP, failed-ORB shorts must close below VWAP."""
|
|
|
|
|
|
attention_min_wiki_spike_10d: float | None = None
|
|
|
"""Minimum wiki spike to accept a stocks-in-play candidate."""
|
|
|
|
|
|
attention_min_wiki_zscore_20d: float | None = None
|
|
|
"""Minimum wiki z-score to accept a stocks-in-play candidate."""
|
|
|
|
|
|
attention_min_article_count_3d: int | None = None
|
|
|
"""Minimum article count to accept a stocks-in-play candidate."""
|
|
|
|
|
|
attention_min_us_article_count_3d: int | None = None
|
|
|
"""Minimum US article count to accept a stocks-in-play candidate."""
|
|
|
|
|
|
attention_min_resolver_confidence: float | None = None
|
|
|
"""Minimum entity-resolution confidence for attention data usage."""
|
|
|
|
|
|
min_sector_relative_strength: float | None = None
|
|
|
"""Minimum ORB return minus sector ORB return for continuation longs.
|
|
|
Positive values force the name to outperform its own sector in the opening range."""
|
|
|
|
|
|
min_entropy: float | None = None
|
|
|
"""Minimum allowed entropy_20d. None disables the lower bound."""
|
|
|
|
|
|
max_entropy: float | None = None
|
|
|
"""Maximum allowed entropy_20d. None disables the upper bound."""
|
|
|
|
|
|
compression_ratio_max: float | None = None
|
|
|
"""Maximum allowed recent range compression ratio (10d / 60d). Lower = tighter setup.
|
|
|
None disables the filter."""
|
|
|
|
|
|
max_gap_zscore_20d: float | None = None
|
|
|
"""Maximum allowed gap z-score (relative to prior 20 sessions). Rejects anomalous gap-up days
|
|
|
where short-sellers are already leaning against the name. Low gap_zscore = routine gap = better ORB.
|
|
|
None disables the filter. Gainers_leader / vwap_reclaim_v1 only."""
|
|
|
|
|
|
conditional_gap_zscore_reject_above: float | None = None
|
|
|
"""Conditional stretched-gap rejection threshold.
|
|
|
When set with conditional_gap_zscore_reject_ret_5d_below, reject candidates whose
|
|
|
gap_zscore_20d exceeds this value only if their prior 5-day return is also weak."""
|
|
|
|
|
|
conditional_gap_zscore_reject_ret_5d_below: float | None = None
|
|
|
"""Prior 5-day return threshold paired with conditional_gap_zscore_reject_above.
|
|
|
Example: 0.0 rejects unusually stretched gap-ups only when ret_5d is negative."""
|
|
|
|
|
|
crowded_gap_reject_min_gap_pct: float | None = None
|
|
|
"""Reject crowded positive gap continuation candidates when the opening setup
|
|
|
is already extended. This guards against late ORB chase entries in names that
|
|
|
have already run before the open. None disables the filter."""
|
|
|
|
|
|
crowded_gap_reject_min_ret_5d: float | None = None
|
|
|
"""Minimum prior 5-day return paired with crowded_gap_reject_min_gap_pct."""
|
|
|
|
|
|
crowded_gap_reject_min_body_ratio: float | None = None
|
|
|
"""Minimum ORB body ratio paired with the crowded-gap exhaustion reject."""
|
|
|
|
|
|
crowded_gap_reject_min_close_location: float | None = None
|
|
|
"""Minimum ORB close location paired with the crowded-gap exhaustion reject."""
|
|
|
|
|
|
crowded_gap_max_premarket_dollar_vol: float | None = None
|
|
|
"""Optional upper premarket-dollar-volume bound for crowded-gap handling.
|
|
|
When set, the crowded-gap overlay only applies to lower-attention crowded
|
|
|
setups; high-participation crowded gaps remain eligible for normal sizing."""
|
|
|
|
|
|
crowded_gap_action: str = "reject"
|
|
|
"""Action for crowded positive-gap exhaustion candidates.
|
|
|
'reject' removes them from the ORB basket; 'confirm' keeps them but forces
|
|
|
a confirmation-bar entry before capital is committed; 'scale' keeps the
|
|
|
setup but applies crowded_gap_size_scale to position sizing; 'confirm_scale'
|
|
|
combines confirmation-bar entry with the size scale."""
|
|
|
|
|
|
crowded_gap_size_scale: float = 1.0
|
|
|
"""Position-size multiplier for crowded positive-gap candidates when
|
|
|
crowded_gap_action='scale'. Values below 1.0 reduce exposure without
|
|
|
changing ranking or entry/exit logic."""
|
|
|
|
|
|
countertrend_gap_min_gap_pct: float | None = None
|
|
|
"""Minimum positive gap for weak-prior-trend gap-up risk handling.
|
|
|
Targets bullish gap-up breakouts after weak 5-day momentum."""
|
|
|
|
|
|
countertrend_gap_max_ret_5d: float | None = None
|
|
|
"""Maximum prior 5-day return for countertrend-gap risk handling.
|
|
|
Example: 0.0 targets gap-up breakouts after a negative 5-day return."""
|
|
|
|
|
|
countertrend_gap_min_body_ratio: float | None = None
|
|
|
"""Minimum ORB body ratio paired with the countertrend-gap risk overlay."""
|
|
|
|
|
|
countertrend_gap_min_close_location: float | None = None
|
|
|
"""Minimum ORB close location paired with the countertrend-gap risk overlay."""
|
|
|
|
|
|
countertrend_gap_action: str = "reject"
|
|
|
"""Action for weak-prior-trend positive-gap candidates.
|
|
|
'reject' removes them; 'confirm' forces a confirmation-bar entry; 'scale'
|
|
|
keeps the setup but applies countertrend_gap_size_scale to position sizing;
|
|
|
'confirm_scale' combines confirmation-bar entry with the size scale."""
|
|
|
|
|
|
countertrend_gap_size_scale: float = 1.0
|
|
|
"""Position-size multiplier for countertrend-gap candidates when
|
|
|
countertrend_gap_action='scale'."""
|
|
|
|
|
|
distressed_reclaim_min_abs_gap_pct: float | None = None
|
|
|
"""Minimum downside gap magnitude for distressed red-to-green reclaim handling.
|
|
|
Example: 0.05 targets stocks gapping down at least 5%."""
|
|
|
|
|
|
distressed_reclaim_max_ret_5d: float | None = None
|
|
|
"""Maximum prior 5-day return for distressed reclaim handling.
|
|
|
Example: -0.10 targets names already down at least 10% over five days."""
|
|
|
|
|
|
distressed_reclaim_min_premarket_dollar_vol: float | None = None
|
|
|
"""Minimum premarket dollar volume for distressed reclaim handling.
|
|
|
This keeps the overlay focused on crowded, high-attention downside gaps."""
|
|
|
|
|
|
distressed_reclaim_max_close_location: float | None = None
|
|
|
"""Maximum ORB close location for distressed reclaim handling.
|
|
|
Values below 1.0 identify weak reclaims that did not close near the ORB high."""
|
|
|
|
|
|
distressed_reclaim_max_obv_slope_20d: float | None = None
|
|
|
"""Optional 20-day OBV slope ceiling for distressed reclaim handling.
|
|
|
Example: 0.0 limits the overlay to downside-gap reclaims with recent
|
|
|
distribution rather than accumulation."""
|
|
|
|
|
|
distressed_reclaim_max_obv_slope_5d: float | None = None
|
|
|
"""Optional 5-day OBV slope ceiling for distressed reclaim handling.
|
|
|
This can separate weak distribution-driven reclaims from high-attention
|
|
|
washouts that already show short-term accumulation."""
|
|
|
|
|
|
distressed_reclaim_action: str = "reject"
|
|
|
"""Action for crowded downside-gap reclaim candidates.
|
|
|
'reject' removes them; 'confirm' forces a confirmation-bar entry; 'scale'
|
|
|
keeps the setup but applies distressed_reclaim_size_scale to position sizing;
|
|
|
'confirm_scale' combines confirmation-bar entry with the size scale;
|
|
|
'skip' keeps the candidate in the selected basket but skips the trade, so
|
|
|
lower-ranked replacement candidates do not backfill the slot;
|
|
|
'skip_reserve' also reserves the candidate's intended cash for the day so
|
|
|
skipped risk slots are not reallocated to later candidates;
|
|
|
'scale_reserve' and 'confirm_scale_reserve' trade the scaled position while
|
|
|
reserving the unscaled slot cash for the rest of the day;
|
|
|
'loss_cap' keeps normal entry/sizing but applies distressed_reclaim_loss_cap_pct
|
|
|
as a fixed intraday stop."""
|
|
|
|
|
|
distressed_reclaim_size_scale: float = 1.0
|
|
|
"""Position-size multiplier for distressed downside-gap reclaim candidates
|
|
|
when distressed_reclaim_action='scale' or 'confirm_scale'."""
|
|
|
|
|
|
distressed_reclaim_loss_cap_pct: float | None = None
|
|
|
"""Fixed stop distance as a fraction of entry price for distressed reclaim
|
|
|
candidates when distressed_reclaim_action='loss_cap' or when otherwise set.
|
|
|
Example: 0.02 uses a 2% fixed stop from entry."""
|
|
|
|
|
|
distressed_reclaim_streak_loss_on_trigger: bool = False
|
|
|
"""When True, a distressed reclaim overlay trigger appends a synthetic losing
|
|
|
outcome to streak sizing after the day. This preserves the post-risk-event
|
|
|
sizing reset without booking synthetic PnL."""
|
|
|
|
|
|
isolated_downside_loss_cap_min_abs_gap_pct: float | None = None
|
|
|
"""Minimum downside gap magnitude for isolated downside-reclaim loss capping.
|
|
|
The overlay is applied after sector confirmation and only targets candidates
|
|
|
without same-sector confirmation."""
|
|
|
|
|
|
isolated_downside_loss_cap_max_ret_5d: float | None = None
|
|
|
"""Optional maximum prior 5-day return for isolated downside loss capping."""
|
|
|
|
|
|
isolated_downside_loss_cap_min_premarket_dollar_vol: float | None = None
|
|
|
"""Optional minimum premarket dollar volume for isolated downside loss capping."""
|
|
|
|
|
|
isolated_downside_loss_cap_max_premarket_dollar_vol: float | None = None
|
|
|
"""Optional maximum premarket dollar volume for isolated downside loss capping."""
|
|
|
|
|
|
isolated_downside_loss_cap_max_body_ratio: float | None = None
|
|
|
"""Optional maximum ORB body ratio for isolated downside loss capping."""
|
|
|
|
|
|
isolated_downside_loss_cap_min_body_ratio: float | None = None
|
|
|
"""Optional minimum ORB body ratio for isolated downside loss capping."""
|
|
|
|
|
|
isolated_downside_loss_cap_max_close_location: float | None = None
|
|
|
"""Optional maximum ORB close location for isolated downside loss capping."""
|
|
|
|
|
|
isolated_downside_loss_cap_min_close_location: float | None = None
|
|
|
"""Optional minimum ORB close location for isolated downside loss capping."""
|
|
|
|
|
|
isolated_downside_loss_cap_min_orb_return: float | None = None
|
|
|
"""Optional minimum first ORB-bar return for isolated downside loss capping."""
|
|
|
|
|
|
isolated_downside_loss_cap_max_score_rank_pct: float | None = None
|
|
|
"""Optional maximum same-day score-rank percentile for isolated downside loss capping."""
|
|
|
|
|
|
isolated_downside_loss_cap_allowed_trigger_types: list[str] | None = None
|
|
|
"""Optional trigger allowlist for isolated downside loss capping.
|
|
|
None keeps the overlay available to every trigger type."""
|
|
|
|
|
|
isolated_downside_size_scale: float = 1.0
|
|
|
"""Position-size multiplier for isolated downside reclaim candidates.
|
|
|
Values below 1.0 reduce exposure and suppress high-conviction boost overlays."""
|
|
|
|
|
|
isolated_downside_loss_cap_pct: float | None = None
|
|
|
"""Fixed stop distance as a fraction of entry price for isolated downside
|
|
|
reclaim candidates. Example: 0.02 uses a 2% fixed stop from entry."""
|
|
|
|
|
|
isolated_downside_pressure_min_abs_gap_pct: float | None = None
|
|
|
"""Minimum downside gap magnitude for isolated high-volume opening-pressure scaling."""
|
|
|
|
|
|
isolated_downside_pressure_max_ret_5d: float | None = None
|
|
|
"""Optional maximum prior 5-day return for isolated opening-pressure scaling."""
|
|
|
|
|
|
isolated_downside_pressure_min_premarket_dollar_vol: float | None = None
|
|
|
"""Minimum premarket dollar volume for isolated opening-pressure scaling."""
|
|
|
|
|
|
isolated_downside_pressure_max_body_ratio: float | None = None
|
|
|
"""Optional maximum ORB body ratio for isolated opening-pressure scaling."""
|
|
|
|
|
|
isolated_downside_pressure_max_close_location: float | None = None
|
|
|
"""Optional maximum ORB close location for isolated opening-pressure scaling."""
|
|
|
|
|
|
isolated_downside_pressure_allowed_trigger_types: list[str] | None = None
|
|
|
"""Optional trigger allowlist for isolated opening-pressure scaling."""
|
|
|
|
|
|
isolated_downside_pressure_size_scale: float = 1.0
|
|
|
"""Position-size multiplier for isolated high-volume opening-pressure candidates."""
|
|
|
|
|
|
overextended_downside_reclaim_min_abs_gap_pct: float | None = None
|
|
|
"""Minimum downside gap magnitude for overextended downside-reclaim scaling.
|
|
|
This targets names that were already crowded over the prior week and then
|
|
|
attempt an ORB reclaim after a deep gap down."""
|
|
|
|
|
|
overextended_downside_reclaim_min_ret_5d: float | None = None
|
|
|
"""Minimum prior 5-day return for overextended downside-reclaim scaling."""
|
|
|
|
|
|
overextended_downside_reclaim_min_premarket_dollar_vol: float | None = None
|
|
|
"""Optional minimum premarket dollar volume for overextended downside reclaim."""
|
|
|
|
|
|
overextended_downside_reclaim_allowed_trigger_types: list[str] | None = None
|
|
|
"""Optional trigger allowlist for overextended downside-reclaim scaling."""
|
|
|
|
|
|
overextended_downside_reclaim_size_scale: float = 1.0
|
|
|
"""Position-size multiplier for overextended downside-reclaim candidates."""
|
|
|
|
|
|
mid_attention_exhaustion_min_rvol: float | None = None
|
|
|
"""Minimum opening RVOL for mid-attention exhaustion scaling.
|
|
|
This targets crowded intraday attention that is elevated, but not extreme
|
|
|
enough to indicate a broad liquid repricing event."""
|
|
|
|
|
|
mid_attention_exhaustion_max_rvol: float | None = None
|
|
|
"""Maximum opening RVOL for mid-attention exhaustion scaling."""
|
|
|
|
|
|
mid_attention_exhaustion_min_premarket_dollar_vol: float | None = None
|
|
|
"""Minimum premarket dollar volume for mid-attention exhaustion scaling."""
|
|
|
|
|
|
mid_attention_exhaustion_max_premarket_dollar_vol: float | None = None
|
|
|
"""Maximum premarket dollar volume for mid-attention exhaustion scaling."""
|
|
|
|
|
|
mid_attention_exhaustion_allowed_trigger_types: list[str] | None = None
|
|
|
"""Optional trigger allowlist for mid-attention exhaustion scaling."""
|
|
|
|
|
|
mid_attention_exhaustion_size_scale: float = 1.0
|
|
|
"""Position-size multiplier for mid-attention exhaustion candidates."""
|
|
|
|
|
|
mid_liquidity_fragility_size_scale: float = 1.0
|
|
|
"""Position-size multiplier for fragile mid-liquidity participation setups.
|
|
|
This governor targets candidates that are liquid enough to trade but lack
|
|
|
the broad participation profile of true liquid leaders."""
|
|
|
|
|
|
mid_liquidity_fragility_allowed_trigger_types: list[str] | None = None
|
|
|
"""Optional trigger allowlist for mid-liquidity fragility scaling."""
|
|
|
|
|
|
mid_liquidity_fragility_thin_min_premarket_dollar_vol: float | None = None
|
|
|
"""Lower premarket-dollar-volume bound for the thin mid-liquidity profile."""
|
|
|
|
|
|
mid_liquidity_fragility_thin_max_premarket_dollar_vol: float | None = None
|
|
|
"""Upper premarket-dollar-volume bound for the thin mid-liquidity profile."""
|
|
|
|
|
|
mid_liquidity_fragility_thin_max_rvol: float | None = None
|
|
|
"""Maximum opening RVOL that marks weak participation in the thin profile."""
|
|
|
|
|
|
mid_liquidity_fragility_thin_min_ret_5d: float | None = None
|
|
|
"""Minimum prior 5-day return for the thin-profile drift clause."""
|
|
|
|
|
|
mid_liquidity_fragility_thin_max_ret_5d: float | None = None
|
|
|
"""Maximum prior 5-day return for the thin-profile drift clause."""
|
|
|
|
|
|
mid_liquidity_fragility_thin_min_body_ratio: float | None = None
|
|
|
"""Minimum ORB body ratio for the thin-profile exhaustion clause."""
|
|
|
|
|
|
mid_liquidity_fragility_mid_min_premarket_dollar_vol: float | None = None
|
|
|
"""Lower premarket-dollar-volume bound for the mid-liquidity body profile."""
|
|
|
|
|
|
mid_liquidity_fragility_mid_max_premarket_dollar_vol: float | None = None
|
|
|
"""Upper premarket-dollar-volume bound for the mid-liquidity body profile."""
|
|
|
|
|
|
mid_liquidity_fragility_mid_min_body_ratio: float | None = None
|
|
|
"""Minimum ORB body ratio for the mid-liquidity body profile."""
|
|
|
|
|
|
mid_liquidity_fragility_mid_max_body_ratio: float | None = None
|
|
|
"""Maximum ORB body ratio for the mid-liquidity body profile."""
|
|
|
|
|
|
orphan_thin_attention_max_premarket_dollar_vol: float | None = None
|
|
|
"""Maximum premarket dollar volume for unsupported thin-attention scaling.
|
|
|
This targets ORB candidates that only survived via attention/rank overrides
|
|
|
while lacking sector confirmation."""
|
|
|
|
|
|
orphan_thin_attention_allowed_trigger_types: list[str] | None = None
|
|
|
"""Optional trigger allowlist for unsupported thin-attention scaling."""
|
|
|
|
|
|
orphan_thin_attention_size_scale: float = 1.0
|
|
|
"""Position-size multiplier for unsupported thin-attention candidates."""
|
|
|
|
|
|
orphan_thin_attention_reserve_full_cash: bool = False
|
|
|
"""When True, reserve the unscaled slot after scaling unsupported thin names.
|
|
|
This lowers exposure without allowing weaker lower-ranked candidates to use
|
|
|
the freed cash."""
|
|
|
|
|
|
gap_up_fill_trap_max_orb_return: float | None = None
|
|
|
"""Maximum ORB-bar return for positive-gap candidates that already qualify
|
|
|
for gap-fill exit protection. These setups have gap-fill risk but did not
|
|
|
show exceptional first-bar expansion, so they can be downscaled before
|
|
|
entry instead of relying only on an exit after the gap starts filling."""
|
|
|
|
|
|
gap_up_fill_trap_allowed_trigger_types: list[str] | None = None
|
|
|
"""Optional trigger allowlist for positive-gap fill-trap scaling."""
|
|
|
|
|
|
gap_up_fill_trap_size_scale: float = 1.0
|
|
|
"""Position-size multiplier for positive-gap fill-trap candidates."""
|
|
|
|
|
|
gap_up_fill_trap_reserve_full_cash: bool = False
|
|
|
"""When True, reserve the unscaled slot after scaling gap-fill traps.
|
|
|
This prevents the released cash from being recycled into weaker alternates."""
|
|
|
|
|
|
low_candidate_quality_max_score: float | None = None
|
|
|
"""Maximum composite candidate score eligible for low-quality scaling.
|
|
|
This cross-sectional quality governor reduces weak-ranked setups even when
|
|
|
they have sector confirmation or raw liquidity."""
|
|
|
|
|
|
low_candidate_quality_allowed_trigger_types: list[str] | None = None
|
|
|
"""Optional trigger allowlist for low-candidate-quality scaling."""
|
|
|
|
|
|
low_candidate_quality_size_scale: float = 1.0
|
|
|
"""Position-size multiplier for low composite-score candidates."""
|
|
|
|
|
|
low_candidate_quality_reserve_full_cash: bool = False
|
|
|
"""When True, reserve the unscaled slot after scaling low-quality candidates."""
|
|
|
|
|
|
hot_reclaim_min_abs_gap_pct: float | None = None
|
|
|
"""Minimum downside gap magnitude for hot-pullback reclaim handling.
|
|
|
Example: 0.05 targets stocks gapping down at least 5%."""
|
|
|
|
|
|
hot_reclaim_min_ret_5d: float | None = None
|
|
|
"""Minimum prior 5-day return for hot-pullback reclaim handling.
|
|
|
Example: 0.30 targets stocks already up at least 30% over five days."""
|
|
|
|
|
|
hot_reclaim_max_premarket_dollar_vol: float | None = None
|
|
|
"""Optional maximum premarket dollar volume for hot-pullback reclaim handling.
|
|
|
This keeps the overlay focused on thinner gap-down reclaims rather than
|
|
|
broad, highly liquid washouts."""
|
|
|
|
|
|
hot_reclaim_min_body_ratio: float | None = None
|
|
|
"""Minimum ORB directional body ratio for hot-pullback reclaim handling."""
|
|
|
|
|
|
hot_reclaim_min_close_location: float | None = None
|
|
|
"""Minimum ORB close location for hot-pullback reclaim handling.
|
|
|
Values near 1.0 identify candles closing at the ORB high."""
|
|
|
|
|
|
hot_reclaim_action: str = "reject"
|
|
|
"""Action for hot-pullback downside-gap reclaim candidates.
|
|
|
'reject' removes them; 'confirm' forces a confirmation-bar entry; 'scale'
|
|
|
keeps the setup but applies hot_reclaim_size_scale to position sizing;
|
|
|
'confirm_scale' combines confirmation-bar entry with the size scale;
|
|
|
'skip' keeps the selected basket slot but skips the trade;
|
|
|
'skip_reserve' also reserves the intended cash for the day;
|
|
|
'scale_reserve' and 'confirm_scale_reserve' trade the scaled position while
|
|
|
reserving the unscaled slot cash;
|
|
|
'loss_cap' keeps normal sizing but applies hot_reclaim_loss_cap_pct as a
|
|
|
fixed intraday stop."""
|
|
|
|
|
|
hot_reclaim_size_scale: float = 1.0
|
|
|
"""Position-size multiplier for hot-pullback reclaim candidates when
|
|
|
hot_reclaim_action='scale' or 'confirm_scale'."""
|
|
|
|
|
|
hot_reclaim_loss_cap_pct: float | None = None
|
|
|
"""Fixed stop distance as a fraction of entry price for hot-pullback reclaim
|
|
|
candidates. Example: 0.02 uses a 2% fixed stop from entry."""
|
|
|
|
|
|
weak_downside_reclaim_min_abs_gap_pct: float | None = None
|
|
|
"""Minimum downside gap magnitude for weak low-participation reclaim handling.
|
|
|
Targets red-to-green attempts that gap down materially but fail to show a
|
|
|
strong opening-range candle."""
|
|
|
|
|
|
weak_downside_reclaim_min_ret_5d: float | None = None
|
|
|
"""Optional lower bound for prior 5-day return in weak downside reclaim handling.
|
|
|
This avoids conflating quiet weak reclaims with severe multi-day panic moves."""
|
|
|
|
|
|
weak_downside_reclaim_max_ret_5d: float | None = None
|
|
|
"""Optional upper bound for prior 5-day return in weak downside reclaim handling."""
|
|
|
|
|
|
weak_downside_reclaim_max_premarket_dollar_vol: float | None = None
|
|
|
"""Maximum premarket dollar volume for weak downside reclaim handling.
|
|
|
Low premarket participation makes weak ORB reclaim candles less reliable."""
|
|
|
|
|
|
weak_downside_reclaim_max_body_ratio: float | None = None
|
|
|
"""Maximum ORB body ratio for weak downside reclaim handling.
|
|
|
Low values isolate doji-like or indecisive reclaim attempts."""
|
|
|
|
|
|
weak_downside_reclaim_max_close_location: float | None = None
|
|
|
"""Maximum close location inside the ORB range for weak downside reclaim handling."""
|
|
|
|
|
|
weak_downside_reclaim_max_obv_slope_5d: float | None = None
|
|
|
"""Optional 5-day OBV slope ceiling for weak downside reclaim handling."""
|
|
|
|
|
|
weak_downside_reclaim_action: str = "reject"
|
|
|
"""Action for weak low-participation downside-gap reclaim candidates.
|
|
|
'reject' removes them; 'confirm' forces a confirmation-bar entry; 'scale'
|
|
|
keeps the setup but applies weak_downside_reclaim_size_scale to position
|
|
|
sizing; 'confirm_scale' combines confirmation-bar entry with the size scale;
|
|
|
'skip' keeps the selected basket slot but skips the trade; 'skip_reserve'
|
|
|
also reserves the intended cash for the day; 'scale_reserve' and
|
|
|
'confirm_scale_reserve' trade the scaled position while reserving the
|
|
|
unscaled slot cash; 'loss_cap' keeps normal sizing but applies
|
|
|
weak_downside_reclaim_loss_cap_pct as a fixed intraday stop."""
|
|
|
|
|
|
weak_downside_reclaim_size_scale: float = 1.0
|
|
|
"""Position-size multiplier for weak downside reclaim candidates when
|
|
|
weak_downside_reclaim_action='scale' or 'confirm_scale'."""
|
|
|
|
|
|
weak_downside_reclaim_loss_cap_pct: float | None = None
|
|
|
"""Fixed stop distance as a fraction of entry price for weak downside
|
|
|
reclaim candidates. Example: 0.02 uses a 2% fixed stop from entry."""
|
|
|
|
|
|
quiet_downside_reclaim_min_abs_gap_pct: float | None = None
|
|
|
"""Minimum downside gap magnitude for quiet weak-reclaim handling.
|
|
|
This is independent of weak_downside_reclaim so hot-pullback guards can be
|
|
|
combined with low-participation downside-gap guards."""
|
|
|
|
|
|
quiet_downside_reclaim_min_ret_5d: float | None = None
|
|
|
"""Optional lower bound for prior 5-day return in quiet downside reclaim handling."""
|
|
|
|
|
|
quiet_downside_reclaim_max_ret_5d: float | None = None
|
|
|
"""Optional upper bound for prior 5-day return in quiet downside reclaim handling."""
|
|
|
|
|
|
quiet_downside_reclaim_max_premarket_dollar_vol: float | None = None
|
|
|
"""Optional maximum premarket dollar volume for quiet downside reclaim handling."""
|
|
|
|
|
|
quiet_downside_reclaim_max_body_ratio: float | None = None
|
|
|
"""Optional maximum ORB body ratio for quiet downside reclaim handling."""
|
|
|
|
|
|
quiet_downside_reclaim_max_close_location: float | None = None
|
|
|
"""Maximum ORB close location for quiet downside reclaim handling."""
|
|
|
|
|
|
quiet_downside_reclaim_max_obv_slope_5d: float | None = None
|
|
|
"""Optional 5-day OBV slope ceiling for quiet downside reclaim handling."""
|
|
|
|
|
|
quiet_downside_reclaim_action: str = "reject"
|
|
|
"""Action for quiet low-participation downside-gap reclaim candidates.
|
|
|
Supports the same actions as weak_downside_reclaim, including 'loss_cap'."""
|
|
|
|
|
|
quiet_downside_reclaim_size_scale: float = 1.0
|
|
|
"""Position-size multiplier for quiet downside reclaim candidates when
|
|
|
quiet_downside_reclaim_action='scale' or 'confirm_scale'."""
|
|
|
|
|
|
quiet_downside_reclaim_loss_cap_pct: float | None = None
|
|
|
"""Fixed stop distance as a fraction of entry price for quiet downside
|
|
|
reclaim candidates. Example: 0.03 uses a 3% fixed stop from entry."""
|
|
|
|
|
|
stale_obv_reversal_min_ret_5d: float | None = None
|
|
|
"""Optional lower bound for prior 5-day return in stale-OBV reversal handling.
|
|
|
This targets names that are not deeply washed out but still have persistent
|
|
|
20-day distribution."""
|
|
|
|
|
|
stale_obv_reversal_max_ret_5d: float | None = None
|
|
|
"""Optional upper bound for prior 5-day return in stale-OBV reversal handling."""
|
|
|
|
|
|
stale_obv_reversal_max_obv_slope_20d: float | None = None
|
|
|
"""Maximum 20-day OBV slope for stale-OBV reversal handling.
|
|
|
Negative values isolate breakouts fighting persistent distribution."""
|
|
|
|
|
|
stale_obv_reversal_min_gap_pct: float | None = None
|
|
|
"""Optional minimum positive gap_pct for stale-OBV reversal handling."""
|
|
|
|
|
|
stale_obv_reversal_max_rvol: float | None = None
|
|
|
"""Optional maximum opening RVOL for stale-OBV reversal handling."""
|
|
|
|
|
|
stale_obv_reversal_max_body_ratio: float | None = None
|
|
|
"""Optional maximum ORB body_ratio for stale-OBV reversal handling."""
|
|
|
|
|
|
stale_obv_reversal_max_close_location: float | None = None
|
|
|
"""Optional maximum ORB close location for stale-OBV reversal handling."""
|
|
|
|
|
|
stale_obv_reversal_max_orb_return: float | None = None
|
|
|
"""Optional maximum first ORB candle return for stale-OBV reversal handling."""
|
|
|
|
|
|
stale_obv_reversal_action: str = "reject"
|
|
|
"""Action for stale-OBV reversal candidates.
|
|
|
Supports the same actions as weak_downside_reclaim, including 'loss_cap'."""
|
|
|
|
|
|
stale_obv_reversal_size_scale: float = 1.0
|
|
|
"""Position-size multiplier for stale-OBV reversal candidates when
|
|
|
stale_obv_reversal_action='scale' or 'confirm_scale'."""
|
|
|
|
|
|
stale_obv_reversal_loss_cap_pct: float | None = None
|
|
|
"""Fixed stop distance as a fraction of entry price for stale-OBV reversal
|
|
|
candidates. Example: 0.025 uses a 2.5% fixed stop from entry."""
|
|
|
|
|
|
stalled_gap_up_min_gap_pct: float | None = None
|
|
|
"""Minimum positive gap for stalled gap-up risk handling.
|
|
|
Targets names that gap up but fail to show strong opening confirmation."""
|
|
|
|
|
|
stalled_gap_up_min_ret_5d: float | None = None
|
|
|
"""Optional lower bound for prior 5-day return in stalled gap-up handling.
|
|
|
Use this to avoid catching deep countertrend rebound setups."""
|
|
|
|
|
|
stalled_gap_up_max_ret_5d: float | None = None
|
|
|
"""Optional upper bound for prior 5-day return in stalled gap-up handling.
|
|
|
This keeps the overlay focused on weak-to-neutral continuation attempts."""
|
|
|
|
|
|
stalled_gap_up_max_premarket_dollar_vol: float | None = None
|
|
|
"""Optional maximum premarket dollar volume for stalled gap-up handling.
|
|
|
Low premarket participation makes weak ORB confirmation less trustworthy."""
|
|
|
|
|
|
stalled_gap_up_max_close_location: float | None = None
|
|
|
"""Maximum ORB close location for stalled gap-up handling.
|
|
|
Values below 1.0 identify gap-ups that did not close near the ORB high."""
|
|
|
|
|
|
stalled_gap_up_max_obv_slope_5d: float | None = None
|
|
|
"""Optional 5-day OBV slope ceiling for stalled gap-up handling.
|
|
|
Negative values require recent short-term distribution."""
|
|
|
|
|
|
stalled_gap_up_action: str = "reject"
|
|
|
"""Action for stalled positive-gap candidates.
|
|
|
'reject' removes them; 'confirm' forces a confirmation-bar entry; 'scale'
|
|
|
keeps the setup but applies stalled_gap_up_size_scale to position sizing;
|
|
|
'confirm_scale' combines confirmation-bar entry with the size scale;
|
|
|
'skip' keeps the selected basket slot but skips the trade;
|
|
|
'skip_reserve' also reserves the intended cash for the day;
|
|
|
'scale_reserve' and 'confirm_scale_reserve' trade the scaled position while
|
|
|
reserving the unscaled slot cash;
|
|
|
'loss_cap' keeps normal sizing but applies stalled_gap_up_loss_cap_pct as a
|
|
|
fixed intraday stop."""
|
|
|
|
|
|
stalled_gap_up_size_scale: float = 1.0
|
|
|
"""Position-size multiplier for stalled gap-up candidates when
|
|
|
stalled_gap_up_action='scale' or 'confirm_scale'."""
|
|
|
|
|
|
stalled_gap_up_loss_cap_pct: float | None = None
|
|
|
"""Fixed stop distance as a fraction of entry price for stalled gap-up
|
|
|
candidates. Example: 0.02 uses a 2% fixed stop from entry."""
|
|
|
|
|
|
liquid_stalled_gap_up_min_gap_pct: float | None = None
|
|
|
"""Minimum positive gap for liquid stalled gap-up loss handling.
|
|
|
This overlay is separate from stalled_gap_up so the existing thin-liquidity
|
|
|
guard can remain active while high-participation stalls get their own rule."""
|
|
|
|
|
|
liquid_stalled_gap_up_min_ret_5d: float | None = None
|
|
|
"""Optional lower bound for prior 5-day return in liquid stalled gap-up handling."""
|
|
|
|
|
|
liquid_stalled_gap_up_max_ret_5d: float | None = None
|
|
|
"""Optional upper bound for prior 5-day return in liquid stalled gap-up handling."""
|
|
|
|
|
|
liquid_stalled_gap_up_min_premarket_dollar_vol: float | None = None
|
|
|
"""Optional minimum premarket dollar volume for liquid stalled gap-up handling."""
|
|
|
|
|
|
liquid_stalled_gap_up_max_premarket_dollar_vol: float | None = None
|
|
|
"""Optional maximum premarket dollar volume for liquid stalled gap-up handling."""
|
|
|
|
|
|
liquid_stalled_gap_up_max_body_ratio: float | None = None
|
|
|
"""Optional maximum ORB body ratio for liquid stalled gap-up handling."""
|
|
|
|
|
|
liquid_stalled_gap_up_max_close_location: float | None = None
|
|
|
"""Maximum ORB close location for liquid stalled gap-up handling."""
|
|
|
|
|
|
liquid_stalled_gap_up_max_obv_slope_5d: float | None = None
|
|
|
"""Optional 5-day OBV slope ceiling for liquid stalled gap-up handling."""
|
|
|
|
|
|
liquid_stalled_gap_up_action: str = "reject"
|
|
|
"""Action for liquid stalled positive-gap candidates.
|
|
|
Supports the same actions as stalled_gap_up, including 'loss_cap'."""
|
|
|
|
|
|
liquid_stalled_gap_up_size_scale: float = 1.0
|
|
|
"""Position-size multiplier for liquid stalled gap-up candidates when
|
|
|
liquid_stalled_gap_up_action='scale' or 'confirm_scale'."""
|
|
|
|
|
|
liquid_stalled_gap_up_loss_cap_pct: float | None = None
|
|
|
"""Fixed stop distance as a fraction of entry price for liquid stalled
|
|
|
gap-up candidates. Example: 0.025 uses a 2.5% fixed stop from entry."""
|
|
|
|
|
|
thin_gap_up_loss_cap_min_gap_pct: float | None = None
|
|
|
"""Minimum positive gap for thin gap-up fixed-loss handling.
|
|
|
This targets low-premarket-participation gap-ups that can look strong on
|
|
|
the ORB candle but fail hard intraday."""
|
|
|
|
|
|
thin_gap_up_loss_cap_min_ret_5d: float | None = None
|
|
|
"""Optional lower bound for prior 5-day return in thin gap-up loss handling."""
|
|
|
|
|
|
thin_gap_up_loss_cap_max_ret_5d: float | None = None
|
|
|
"""Optional upper bound for prior 5-day return in thin gap-up loss handling."""
|
|
|
|
|
|
thin_gap_up_loss_cap_max_premarket_dollar_vol: float | None = None
|
|
|
"""Maximum premarket dollar volume for thin gap-up loss handling."""
|
|
|
|
|
|
thin_gap_up_loss_cap_min_body_ratio: float | None = None
|
|
|
"""Minimum ORB body ratio for thin gap-up loss handling."""
|
|
|
|
|
|
thin_gap_up_loss_cap_min_close_location: float | None = None
|
|
|
"""Minimum close location inside the ORB range for thin gap-up loss handling."""
|
|
|
|
|
|
thin_gap_up_loss_cap_pct: float | None = None
|
|
|
"""Fixed stop distance as a fraction of entry price for qualifying thin
|
|
|
gap-up candidates. Example: 0.02 uses a 2% fixed stop from entry."""
|
|
|
|
|
|
moderate_downside_loss_cap_min_abs_gap_pct: float | None = None
|
|
|
"""Minimum downside gap magnitude for moderate downside fixed-loss handling."""
|
|
|
|
|
|
moderate_downside_loss_cap_max_abs_gap_pct: float | None = None
|
|
|
"""Maximum downside gap magnitude for moderate downside fixed-loss handling.
|
|
|
This avoids applying the overlay to deep washout leaders that have historically
|
|
|
carried much of the strategy's upside."""
|
|
|
|
|
|
moderate_downside_loss_cap_min_ret_5d: float | None = None
|
|
|
"""Optional lower bound for prior 5-day return in moderate downside handling."""
|
|
|
|
|
|
moderate_downside_loss_cap_max_ret_5d: float | None = None
|
|
|
"""Optional upper bound for prior 5-day return in moderate downside handling."""
|
|
|
|
|
|
moderate_downside_loss_cap_max_premarket_dollar_vol: float | None = None
|
|
|
"""Optional maximum premarket dollar volume for moderate downside handling."""
|
|
|
|
|
|
moderate_downside_loss_cap_max_body_ratio: float | None = None
|
|
|
"""Maximum ORB body ratio for moderate downside handling."""
|
|
|
|
|
|
moderate_downside_loss_cap_max_close_location: float | None = None
|
|
|
"""Maximum close location inside the ORB range for moderate downside handling."""
|
|
|
|
|
|
moderate_downside_loss_cap_pct: float | None = None
|
|
|
"""Fixed stop distance as a fraction of entry price for qualifying moderate
|
|
|
downside-gap candidates. Example: 0.01 uses a 1% fixed stop from entry."""
|
|
|
|
|
|
min_obv_slope_20d: float | None = None
|
|
|
"""Minimum allowed OBV slope (20d). 0.0 = require positive accumulation (net up-volume days).
|
|
|
Negative OBV slope = institutional distribution — filters those out when set.
|
|
|
None disables the filter. Gainers_leader / vwap_reclaim_v1 only."""
|
|
|
|
|
|
# ATR-based stop management
|
|
|
atr_stop_multiplier: float = 0.10
|
|
|
"""Initial stop distance = ATR(14) × this multiplier. Paper uses 10% (0.10)."""
|
|
|
|
|
|
breakeven_at_r: float = 1.0
|
|
|
"""Move stop to breakeven (entry price) when trade reaches this R-multiple."""
|
|
|
|
|
|
trailing_at_r: float = 2.0
|
|
|
"""Activate trailing stop (using recent bar lows) when trade reaches this R-multiple."""
|
|
|
|
|
|
# Risk-based position sizing
|
|
|
risk_per_trade_pct: float = 0.0025
|
|
|
"""Risk dollars per trade = equity × this. 0.0025 = 0.25% per trade."""
|
|
|
|
|
|
max_position_pct: float = 0.20
|
|
|
"""Maximum single position as fraction of equity. 0.20 = 20%."""
|
|
|
|
|
|
daily_max_loss_pct: float = 0.0125
|
|
|
"""Stop trading for the day if cumulative loss exceeds this. 0.0125 = 1.25%."""
|
|
|
|
|
|
max_stops_per_day: int = 3
|
|
|
"""Stop trading for the day after this many full-R stop losses."""
|
|
|
|
|
|
same_bar_stop_confirmation_enabled: bool = False
|
|
|
"""When True, ambiguous 5-minute ORB bars whose range contains both the
|
|
|
breakout entry and initial stop must confirm on the next bar. The trade then
|
|
|
enters at the confirmation bar close, avoiding same-bar low/high lookahead."""
|
|
|
|
|
|
same_bar_stop_confirmation_allowed_trigger_types: list[str] | None = None
|
|
|
"""Optional trigger allowlist for same_bar_stop_confirmation_enabled.
|
|
|
Example: ['orb'] limits the confirmation rule to primary ORB breakouts."""
|
|
|
|
|
|
# Exit
|
|
|
exit_minutes_before_close: int = 5
|
|
|
"""Minutes before 4:00 PM ET to force-close. Default 5 = 15:55 ET."""
|
|
|
|
|
|
# Execution
|
|
|
slippage_bps: float = 5.0
|
|
|
"""One-way slippage in basis points (applied to both entry and exit fills)."""
|
|
|
|
|
|
initial_capital: float = 10_000.0
|
|
|
"""Starting capital in USD."""
|
|
|
|
|
|
ticker_cooldown_days: int = 0
|
|
|
"""Blackout period after trading a ticker (same as momentum strategy). 0 = disabled."""
|
|
|
|
|
|
settlement_days: int = 0
|
|
|
"""Cash account settlement delay (trading days).
|
|
|
0 = disabled (all equity always available — original behavior, allows over-deployment).
|
|
|
1 = T+1 (sale proceeds settle next trading day; also enforces within-day settled-cash cap).
|
|
|
2 = T+2 (legacy US rule pre-May 2024).
|
|
|
GFV context: unsettled proceeds can buy but not same-day sell (ORB always exits same day,
|
|
|
so only settled cash is usable)."""
|
|
|
|
|
|
orb_idle_sleeve_enabled: bool = False
|
|
|
"""When True, deploy a fallback basket only on days where the primary ORB
|
|
|
book opens no positions. The entry/exit schedule is controlled by the idle
|
|
|
sleeve timing fields so strategies can test same-day or overnight sleeves."""
|
|
|
|
|
|
orb_idle_sleeve_total_budget_pct: float = 0.25
|
|
|
"""Fraction of the ORB sizing capital allocated to the idle fallback basket."""
|
|
|
|
|
|
orb_idle_sleeve_normalize_weights: bool = True
|
|
|
"""When True, active idle-sleeve weights are normalized to consume the
|
|
|
full idle budget. When False, each weight is treated as an absolute fraction
|
|
|
of the idle budget and unused capital remains idle."""
|
|
|
|
|
|
orb_idle_sleeve_max_positions: int = 5
|
|
|
"""Maximum number of fallback positions to open across all idle sleeves."""
|
|
|
|
|
|
orb_idle_sleeve_exit_timing: str = "next_open"
|
|
|
"""Exit timing for idle fallback positions: 'same_day_close', 'next_open',
|
|
|
or 'next_close'."""
|
|
|
|
|
|
orb_idle_sleeve_entry_timing: str = "close"
|
|
|
"""Entry timing for idle fallback positions: 'close' or 'minutes_after_open'.
|
|
|
'minutes_after_open' is intended for morning no-ORB fallback books."""
|
|
|
|
|
|
orb_idle_sleeve_entry_minutes_after_open: int = 60
|
|
|
"""Entry bar offset for orb_idle_sleeve_entry_timing='minutes_after_open'."""
|
|
|
|
|
|
orb_idle_sleeve_same_day_stop_loss_pct: float | None = None
|
|
|
"""Optional stop-loss for same-day idle fallback positions.
|
|
|
Example: -0.03 exits an intraday-only idle sleeve when it falls 3% below
|
|
|
the idle entry price. Only applies when orb_idle_sleeve_exit_timing is
|
|
|
same_day_close/day_close/close."""
|
|
|
|
|
|
orb_idle_sleeve_weights: dict[str, float] = Field(
|
|
|
default_factory=lambda: {
|
|
|
"parking": 0.25,
|
|
|
"idle_alpha": 0.35,
|
|
|
"form4": 0.15,
|
|
|
"ownership": 0.15,
|
|
|
"risk_off_alpha": 0.10,
|
|
|
}
|
|
|
)
|
|
|
"""Capital weights for the five PEAD-inspired ORB idle sleeves."""
|
|
|
|
|
|
orb_idle_sleeve_parking_symbols: list[str] = Field(default_factory=lambda: ["QQQ", "SPY", "IWM"])
|
|
|
"""Risk-on ETF candidates for the parking sleeve."""
|
|
|
|
|
|
orb_idle_sleeve_parking_mode: str = "best"
|
|
|
"""Parking sleeve selector. 'best' picks the strongest configured ETF by
|
|
|
same-day close stats. 'pead_like' uses QQQM as the default risk-on parking
|
|
|
asset, TQQQ only on strong market closes, and SGOV as the defensive
|
|
|
fallback."""
|
|
|
|
|
|
orb_idle_sleeve_parking_base_symbol: str = "QQQM"
|
|
|
"""Default risk-on parking symbol for orb_idle_sleeve_parking_mode='pead_like'."""
|
|
|
|
|
|
orb_idle_sleeve_parking_overlay_symbol: str | None = "TQQQ"
|
|
|
"""Aggressive parking overlay symbol used only when the market close is strong."""
|
|
|
|
|
|
orb_idle_sleeve_parking_defensive_symbol: str = "SGOV"
|
|
|
"""Defensive parking fallback when risk-on parking is not allowed or unavailable."""
|
|
|
|
|
|
orb_idle_sleeve_parking_overlay_min_market_return_pct: float = 0.005
|
|
|
"""Minimum same-day market return required to use the aggressive overlay."""
|
|
|
|
|
|
orb_idle_sleeve_parking_overlay_min_market_close_location: float = 0.70
|
|
|
"""Minimum same-day market close location required to use the aggressive overlay."""
|
|
|
|
|
|
orb_idle_sleeve_force_defensive_fallback: bool = False
|
|
|
"""When True, open the defensive parking symbol if no other idle sleeve
|
|
|
produces an order, so completely idle days still park capital."""
|
|
|
|
|
|
orb_idle_sleeve_defensive_fill_unused_budget: bool = False
|
|
|
"""When True, any idle-sleeve budget not used by active risky sleeves is
|
|
|
filled with the defensive parking symbol. This lets ORB no-trade days stay
|
|
|
mostly invested without forcing weak single-name exposure."""
|
|
|
|
|
|
orb_idle_sleeve_risk_off_symbols: list[str] = Field(default_factory=lambda: ["GLD", "SGOV"])
|
|
|
"""Defensive candidates for the risk-off alpha sleeve."""
|
|
|
|
|
|
orb_idle_sleeve_sector_rotation_symbols: list[str] = Field(default_factory=list)
|
|
|
"""Sector/theme ETF candidates for ORB idle-day rotation."""
|
|
|
|
|
|
orb_idle_sleeve_sector_rotation_min_day_return_pct: float | None = 0.0
|
|
|
"""Minimum same-day return required for sector/theme ETF rotation."""
|
|
|
|
|
|
orb_idle_sleeve_sector_rotation_min_close_location: float | None = 0.55
|
|
|
"""Minimum close location required for sector/theme ETF rotation."""
|
|
|
|
|
|
orb_idle_sleeve_market_ticker: str = "QQQ"
|
|
|
"""Market ticker used to decide whether the risk-on parking sleeve is allowed."""
|
|
|
|
|
|
orb_idle_sleeve_min_market_day_return_pct: float | None = -0.005
|
|
|
"""Minimum open-to-close return of orb_idle_sleeve_market_ticker required
|
|
|
for the risk-on parking sleeve. None disables this gate."""
|
|
|
|
|
|
orb_idle_sleeve_min_market_close_location: float | None = None
|
|
|
"""Minimum open-to-entry close location of orb_idle_sleeve_market_ticker
|
|
|
required for risk-on idle sleeves. This avoids buying QQQM/TQQQ when the
|
|
|
market is positive but fading from its morning range."""
|
|
|
|
|
|
orb_idle_sleeve_idle_min_day_return_pct: float = 0.01
|
|
|
"""Minimum regular-session open-to-close return for the liquid idle-alpha stock sleeve."""
|
|
|
|
|
|
orb_idle_sleeve_idle_min_close_location: float = 0.60
|
|
|
"""Minimum regular-session close location for the liquid idle-alpha stock sleeve."""
|
|
|
|
|
|
orb_idle_sleeve_idle_min_day_dollar_vol: float = 10_000_000.0
|
|
|
"""Minimum same-day regular-session dollar volume for idle-alpha stock candidates."""
|
|
|
|
|
|
orb_idle_sleeve_idle_min_avg_dollar_vol: float = 40_000_000.0
|
|
|
"""Minimum prior average daily dollar volume for idle-alpha stock candidates."""
|
|
|
|
|
|
orb_idle_sleeve_idle_min_score: float | None = None
|
|
|
"""Minimum idle-alpha conviction score required before carrying a single
|
|
|
stock overnight. None disables the score gate."""
|
|
|
|
|
|
orb_idle_sleeve_idle_max_day_return_pct: float | None = None
|
|
|
"""Maximum regular-session open-to-entry return for idle-alpha stock candidates.
|
|
|
Useful for avoiding already-overextended morning runners."""
|
|
|
|
|
|
orb_idle_sleeve_idle_min_gap_pct: float | None = None
|
|
|
"""Minimum opening gap for idle-alpha stock candidates."""
|
|
|
|
|
|
orb_idle_sleeve_idle_max_gap_pct: float | None = None
|
|
|
"""Maximum opening gap for idle-alpha stock candidates."""
|
|
|
|
|
|
orb_idle_sleeve_idle_min_ret_5d: float | None = None
|
|
|
"""Minimum prior 5-day return for idle-alpha stock candidates."""
|
|
|
|
|
|
orb_idle_sleeve_idle_max_ret_5d: float | None = None
|
|
|
"""Maximum prior 5-day return for idle-alpha stock candidates."""
|
|
|
|
|
|
orb_idle_sleeve_reclaim_checkpoint_minutes: int = 60
|
|
|
"""Minutes after the open used to identify weak-open/late-reclaim setups."""
|
|
|
|
|
|
orb_idle_sleeve_reclaim_min_day_return_pct: float = 0.004
|
|
|
"""Minimum open-to-entry return for late-reclaim stock candidates."""
|
|
|
|
|
|
orb_idle_sleeve_reclaim_max_early_return_pct: float = 0.004
|
|
|
"""Maximum open-to-checkpoint return for late-reclaim stock candidates.
|
|
|
This keeps the sleeve focused on names that were not already morning ORB
|
|
|
runners."""
|
|
|
|
|
|
orb_idle_sleeve_reclaim_min_late_return_pct: float = 0.006
|
|
|
"""Minimum checkpoint-to-entry return for late-reclaim stock candidates."""
|
|
|
|
|
|
orb_idle_sleeve_reclaim_min_close_location: float = 0.70
|
|
|
"""Minimum open-to-entry close location for late-reclaim candidates."""
|
|
|
|
|
|
orb_idle_sleeve_reclaim_min_day_dollar_vol: float = 20_000_000.0
|
|
|
"""Minimum same-day dollar volume for late-reclaim candidates."""
|
|
|
|
|
|
orb_idle_sleeve_reclaim_min_avg_dollar_vol: float = 80_000_000.0
|
|
|
"""Minimum prior average daily dollar volume for late-reclaim candidates."""
|
|
|
|
|
|
orb_idle_sleeve_reclaim_min_score: float | None = None
|
|
|
"""Minimum late-reclaim conviction score required before carrying a single
|
|
|
stock overnight. None disables the score gate."""
|
|
|
|
|
|
orb_idle_sleeve_reclaim_min_gap_pct: float | None = None
|
|
|
"""Minimum opening gap for late-reclaim candidates."""
|
|
|
|
|
|
orb_idle_sleeve_reclaim_max_gap_pct: float | None = None
|
|
|
"""Maximum opening gap for late-reclaim candidates."""
|
|
|
|
|
|
orb_idle_sleeve_reclaim_min_ret_5d: float | None = None
|
|
|
"""Minimum prior 5-day return for late-reclaim candidates."""
|
|
|
|
|
|
orb_idle_sleeve_reclaim_max_ret_5d: float | None = None
|
|
|
"""Maximum prior 5-day return for late-reclaim candidates."""
|
|
|
|
|
|
orb_idle_sleeve_event_min_day_return_pct: float = -0.005
|
|
|
"""Minimum day return for Form 4 and ownership fallback candidates."""
|
|
|
|
|
|
orb_idle_sleeve_event_min_close_location: float = 0.50
|
|
|
"""Minimum open-to-entry close location for Form 4 and ownership fallback
|
|
|
candidates. ORB uses event flags only when the tape confirms intraday."""
|
|
|
|
|
|
orb_idle_sleeve_event_min_day_dollar_vol: float = 0.0
|
|
|
"""Minimum same-day regular-session dollar volume for Form 4 and ownership
|
|
|
fallback candidates."""
|
|
|
|
|
|
orb_idle_sleeve_event_min_avg_dollar_vol: float = 0.0
|
|
|
"""Minimum prior average daily dollar volume for Form 4 and ownership
|
|
|
fallback candidates."""
|
|
|
|
|
|
orb_idle_sleeve_event_min_gap_pct: float | None = None
|
|
|
"""Minimum opening gap for Form 4 and ownership fallback candidates."""
|
|
|
|
|
|
orb_idle_sleeve_event_max_gap_pct: float | None = None
|
|
|
"""Maximum opening gap for Form 4 and ownership fallback candidates."""
|
|
|
|
|
|
orb_idle_sleeve_event_min_ret_5d: float | None = None
|
|
|
"""Minimum prior 5-day return for Form 4 and ownership fallback candidates."""
|
|
|
|
|
|
orb_idle_sleeve_event_max_ret_5d: float | None = None
|
|
|
"""Maximum prior 5-day return for Form 4 and ownership fallback candidates."""
|
|
|
|
|
|
orb_idle_sleeve_event_max_days_since: int | None = None
|
|
|
"""Maximum age of Form 4 / ownership event flags for ORB idle sleeves."""
|
|
|
|
|
|
orb_idle_sleeve_min_market_day_return_for_stock_sleeves_pct: float | None = None
|
|
|
"""Minimum market-ticker open-to-close return required before the stock
|
|
|
sleeves (idle_alpha, Form 4, ownership) can deploy. This keeps the close
|
|
|
fallback from buying single-name strength against a weak tape."""
|
|
|
|
|
|
max_gap_pct: float | None = None
|
|
|
"""Maximum opening gap (open vs prev_close) allowed for ORB candidates.
|
|
|
Stocks that gap >10% at open are over-extended and prone to reversal — they have a low
|
|
|
ORB breakout continuation rate. None = no cap (allow any gap). E.g. 0.10 = 10% cap."""
|
|
|
|
|
|
min_abs_gap_pct: float | None = None
|
|
|
"""Minimum absolute opening gap required to treat the session as catalyst-like.
|
|
|
Useful proxy when explicit same-day news labels are unavailable. None = disabled."""
|
|
|
|
|
|
min_premarket_dollar_vol: float | None = None
|
|
|
"""Minimum premarket dollar volume (04:00-09:30 ET) required for candidate inclusion.
|
|
|
Acts as a same-day attention/liquidity filter. None = disabled."""
|
|
|
|
|
|
small_gap_attention_override_premarket_dollar_vol: float | None = None
|
|
|
"""For gainers_leader / leader_followthrough: allow candidates below min_abs_gap_pct when premarket
|
|
|
dollar volume is exceptionally high. This is meant for mega-cap / leader followthrough
|
|
|
days that do not gap much but clearly dominate premarket attention."""
|
|
|
|
|
|
small_gap_attention_override_rvol: float | None = None
|
|
|
"""Optional RVOL floor paired with small_gap_attention_override_premarket_dollar_vol.
|
|
|
When set, small-gap candidates must also show at least this opening-range RVOL to
|
|
|
bypass min_abs_gap_pct."""
|
|
|
|
|
|
max_small_gap_attention_candidates: int | None = None
|
|
|
"""Optional daily cap on candidates admitted via the small-gap attention override.
|
|
|
Useful to keep exceptional followthrough names from diluting the core gap-driven
|
|
|
gainers basket. None disables the cap."""
|
|
|
|
|
|
# Market regime
|
|
|
market_regime_spy_threshold: float | None = None
|
|
|
"""Skip trading if regime ticker's opening gap is below this threshold. None = disabled.
|
|
|
E.g. -0.005 = skip if regime ticker gaps down >0.5% at open."""
|
|
|
|
|
|
market_regime_ticker: str = "SPY"
|
|
|
"""Ticker used for the gap-based market regime check. Default 'SPY'.
|
|
|
IWM often works better for ORB (mid/small-cap universe matches ORB candidates).
|
|
|
Only used when market_regime_spy_threshold is not None."""
|
|
|
|
|
|
min_candidate_breadth: float | None = None
|
|
|
"""Skip day if fewer than this fraction of intraday tickers opened above prev close.
|
|
|
E.g. 0.30 = skip if <30% of day's candidates gapped up.
|
|
|
Sweep result: 0.30 gives Sharpe 19.86 (vs 18.31 no filter), 0.50 gives 20.27.
|
|
|
This is more robust than single-ETF regime checks because it measures the actual
|
|
|
candidate pool's sentiment. None = disabled."""
|
|
|
|
|
|
compound_returns: bool = True
|
|
|
"""When True (default), position sizing scales with current equity (compounding).
|
|
|
When False, position sizing always uses initial_capital (simple/단리 mode).
|
|
|
Simple mode prevents late-period bias where larger equity dominates the return metric.
|
|
|
Ignored when daily_budget_reset is True."""
|
|
|
|
|
|
daily_budget_reset: bool = False
|
|
|
"""Research-only mode: every day resets sizing_capital and buying power to
|
|
|
initial_capital, so prior-day PnL does not compound into position budget.
|
|
|
Stateful strategy governors still apply when explicitly configured.
|
|
|
When True, takes precedence over compound_returns."""
|
|
|
|
|
|
trailing_stop_atr_multiplier: float = 0.0
|
|
|
"""ATR-based trailing stop distance from peak price. 0 = disabled (use swing-low mode).
|
|
|
When > 0: trailing_stop = peak_price - atr * this_value. Bar-size independent.
|
|
|
E.g. 1.5 = trail 1.5×ATR(14) below the running peak. Activates at trailing_at_r.
|
|
|
Swing-low mode (0.0) ties trailing sensitivity to sim_bar_minutes — ATR mode removes that dependency."""
|
|
|
|
|
|
trailing_tighten_at_r: float | None = None
|
|
|
"""Two-stage trailing: when R reaches this level, switch to trailing_stop_atr_multiplier_tight.
|
|
|
None = single-stage trailing (no tightening). Requires trailing_stop_atr_multiplier > 0.
|
|
|
E.g. trailing_at_r=1.5 (wide trail) + trailing_tighten_at_r=3.0 (tight trail)."""
|
|
|
|
|
|
trailing_stop_atr_multiplier_tight: float = 0.0
|
|
|
"""ATR multiplier for the tighter second-stage trailing stop (used when trailing_tighten_at_r is hit).
|
|
|
0.0 = fall back to trailing_stop_atr_multiplier (effectively disables tightening)."""
|
|
|
|
|
|
max_simultaneous_entries: int | None = None
|
|
|
"""Maximum trades that can enter on the same bar timestamp. None = unlimited.
|
|
|
Prevents the 9:35 burst where all candidates break out simultaneously, overwhelming
|
|
|
the kill switch and creating uncontrolled correlated risk. Top-ranked candidates are taken first."""
|
|
|
|
|
|
entry_tie_break_rank_by_score: bool = False
|
|
|
"""When multiple candidates enter on the same timestamp, rank them by the
|
|
|
candidate score before applying cash and simultaneous-entry constraints.
|
|
|
This is especially important when auxiliary sleeves are merged after the
|
|
|
primary list, otherwise high-score auxiliary leaders can be starved by
|
|
|
lower-score primary candidates on the same opening breakout bar."""
|
|
|
|
|
|
max_trades_per_day: int | None = None
|
|
|
"""Maximum total ORB trades allowed per day. None = unlimited.
|
|
|
This is a day-level concentration cap, distinct from max_simultaneous_entries:
|
|
|
it stops lower-priority later fills after the day has already deployed enough
|
|
|
names, even when those fills occur on different bars."""
|
|
|
|
|
|
late_trade_size_scale_after_n: int | None = None
|
|
|
"""Apply late_trade_size_scale after this many accepted trades on the same day.
|
|
|
None = disabled. For example, 4 scales the 5th and later ORB fills without
|
|
|
hard-skipping them, preserving more path information than max_trades_per_day."""
|
|
|
|
|
|
late_trade_size_scale: float = 1.0
|
|
|
"""Position-size multiplier for trades after late_trade_size_scale_after_n.
|
|
|
Values are clamped to [0, 1]."""
|
|
|
|
|
|
rank_rvol_pressure_min_rvol: float | None = None
|
|
|
"""Minimum opening RVOL for the rank/RVOL pressure size overlay.
|
|
|
None disables the overlay. This targets noisy high-attention names that
|
|
|
are not top-ranked enough to justify full risk."""
|
|
|
|
|
|
rank_rvol_pressure_max_score_rank_pct: float | None = None
|
|
|
"""Maximum score_rank_pct eligible for the rank/RVOL pressure overlay.
|
|
|
1.0 is top ranked; lower values target weaker-ranked candidates."""
|
|
|
|
|
|
rank_rvol_pressure_max_ret_5d: float | None = None
|
|
|
"""Optional maximum prior 5-day return for the rank/RVOL pressure overlay.
|
|
|
Useful for isolating high-RVOL repair attempts without penalizing true
|
|
|
positive-momentum leaders. None ignores prior 5-day return."""
|
|
|
|
|
|
rank_rvol_pressure_size_scale: float = 1.0
|
|
|
"""Position-size multiplier for rank/RVOL pressure candidates.
|
|
|
Values are clamped to [0, 1]."""
|
|
|
|
|
|
gap_exhaustion_pressure_min_gap_zscore: float | None = None
|
|
|
"""Minimum gap_zscore_20d for the gap-exhaustion pressure overlay.
|
|
|
None disables the overlay. This targets stretched positive gaps whose
|
|
|
opening-range close does not confirm strong control."""
|
|
|
|
|
|
gap_exhaustion_pressure_max_close_location: float | None = None
|
|
|
"""Maximum ORB close_location eligible for the gap-exhaustion pressure overlay."""
|
|
|
|
|
|
gap_exhaustion_pressure_min_ret_5d: float | None = None
|
|
|
"""Optional minimum prior 5-day return for the gap-exhaustion pressure overlay.
|
|
|
Use this to isolate already-advanced names rather than fresh repair gaps."""
|
|
|
|
|
|
gap_exhaustion_pressure_min_gap_pct: float | None = None
|
|
|
"""Optional minimum positive gap_pct for the gap-exhaustion pressure overlay."""
|
|
|
|
|
|
gap_exhaustion_pressure_size_scale: float = 1.0
|
|
|
"""Position-size multiplier for gap-exhaustion pressure candidates.
|
|
|
Values are clamped to [0, 1]."""
|
|
|
|
|
|
stale_obv_rvol_pressure_max_rvol: float | None = None
|
|
|
"""Maximum opening RVOL for the stale-OBV/RVOL pressure overlay.
|
|
|
None disables the overlay. This targets stale OBV candidates that are
|
|
|
breaking out without enough relative-volume confirmation."""
|
|
|
|
|
|
stale_obv_rvol_pressure_max_obv_slope_20d: float | None = None
|
|
|
"""Maximum 20-day OBV slope for the stale-OBV/RVOL pressure overlay."""
|
|
|
|
|
|
stale_obv_rvol_pressure_max_ret_5d: float | None = None
|
|
|
"""Optional maximum prior 5-day return for stale-OBV/RVOL pressure."""
|
|
|
|
|
|
stale_obv_rvol_pressure_max_score_rank_pct: float | None = None
|
|
|
"""Optional maximum score_rank_pct for stale-OBV/RVOL pressure.
|
|
|
1.0 is top ranked; lower values target weaker-ranked candidates."""
|
|
|
|
|
|
stale_obv_rvol_pressure_size_scale: float = 1.0
|
|
|
"""Position-size multiplier for stale-OBV/RVOL pressure candidates.
|
|
|
Values are clamped to [0, 1]."""
|
|
|
|
|
|
stale_obv_rvol_pressure_reserve_full_cash: bool = False
|
|
|
"""When True, scaled stale-OBV/RVOL trades reserve the unscaled cash slot.
|
|
|
This reduces exposure without allowing freed cash to backfill into lower
|
|
|
priority same-day trades."""
|
|
|
|
|
|
unboosted_primary_fragility_size_scale: float = 1.0
|
|
|
"""Position-size multiplier for weak primary ORB setups that did not qualify
|
|
|
for any high-conviction booster. Values are clamped to [0, 1]."""
|
|
|
|
|
|
unboosted_primary_fragility_allowed_trigger_types: list[str] | None = None
|
|
|
"""Optional entry trigger allowlist for unboosted-primary fragility scaling.
|
|
|
None keeps the overlay available to every primary trigger."""
|
|
|
|
|
|
unboosted_primary_fragility_crowded_min_gap_pct: float | None = None
|
|
|
"""Minimum positive gap for the crowded-liquid stall fragility profile."""
|
|
|
|
|
|
unboosted_primary_fragility_crowded_min_premarket_dollar_vol: float | None = None
|
|
|
"""Minimum premarket dollar volume for the crowded-liquid stall profile."""
|
|
|
|
|
|
unboosted_primary_fragility_crowded_max_close_location: float | None = None
|
|
|
"""Maximum ORB close_location for the crowded-liquid stall profile."""
|
|
|
|
|
|
unboosted_primary_fragility_weak_max_rvol: float | None = None
|
|
|
"""Maximum opening RVOL for the weak-attention fragility profile."""
|
|
|
|
|
|
unboosted_primary_fragility_weak_max_ret_5d: float | None = None
|
|
|
"""Maximum prior 5-day return for the weak-attention fragility profile."""
|
|
|
|
|
|
unsupported_attention_size_scale: float = 1.0
|
|
|
"""Position-size multiplier for unsupported attention spikes. This defensive
|
|
|
governor targets candidates with heavy attention/liquidity but without
|
|
|
sector confirmation or leader-quality support. Values are clamped to [0, 1]."""
|
|
|
|
|
|
unsupported_attention_reserve_full_cash: bool = False
|
|
|
"""When True, scaled unsupported-attention trades reserve the unscaled cash
|
|
|
slot. This reduces exposure without letting freed cash backfill into weaker
|
|
|
same-day candidates."""
|
|
|
|
|
|
unsupported_attention_allowed_trigger_types: list[str] | None = None
|
|
|
"""Optional trigger allowlist for the unsupported-attention governor."""
|
|
|
|
|
|
unsupported_attention_ignore_high_conviction: bool = True
|
|
|
"""When True, do not scale trades that already received a high-conviction
|
|
|
allocator boost such as liquid-leader or opening-burst sizing."""
|
|
|
|
|
|
unsupported_attention_liquid_min_premarket_dollar_vol: float | None = None
|
|
|
"""Minimum premarket dollar volume for the liquid unsupported-attention profile."""
|
|
|
|
|
|
unsupported_attention_liquid_max_candidate_score: float | None = None
|
|
|
"""Maximum candidate score for the liquid unsupported-attention profile."""
|
|
|
|
|
|
unsupported_attention_liquid_require_no_sector_confirmation: bool = True
|
|
|
"""When True, the liquid unsupported-attention profile only applies to
|
|
|
candidates without same-sector confirmation."""
|
|
|
|
|
|
unsupported_attention_thin_max_premarket_dollar_vol: float | None = None
|
|
|
"""Maximum premarket dollar volume for the thin positive-gap attention profile."""
|
|
|
|
|
|
unsupported_attention_thin_max_first_bar_dollar_vol: float | None = None
|
|
|
"""Optional maximum first-bar dollar volume for the thin positive-gap profile.
|
|
|
This narrows the guard to names that are thin both before the open and in
|
|
|
the opening range, instead of penalizing every low-premarket setup."""
|
|
|
|
|
|
unsupported_attention_thin_min_gap_pct: float | None = None
|
|
|
"""Minimum positive opening gap for the thin attention profile."""
|
|
|
|
|
|
unsupported_attention_thin_min_rvol: float | None = None
|
|
|
"""Minimum opening RVOL for the thin attention profile."""
|
|
|
|
|
|
unsupported_attention_thin_max_rvol: float | None = None
|
|
|
"""Optional RVOL ceiling for the thin attention profile."""
|
|
|
|
|
|
positive_gap_rebound_failure_size_scale: float = 1.0
|
|
|
"""Position-size multiplier for high-attention positive-gap rebound failures.
|
|
|
|
|
|
This defensive governor targets long ORB candidates that gap up after a
|
|
|
sharp 5-day selloff but fail to produce a positive opening-range return.
|
|
|
Values are clamped to [0, 1].
|
|
|
"""
|
|
|
|
|
|
positive_gap_rebound_failure_allowed_trigger_types: list[str] | None = None
|
|
|
"""Optional trigger allowlist for the positive-gap rebound-failure governor."""
|
|
|
|
|
|
positive_gap_rebound_failure_reserve_full_cash: bool = False
|
|
|
"""When True, scaled rebound-failure trades reserve the unscaled cash slot.
|
|
|
This reduces trap exposure without letting freed cash backfill into lower
|
|
|
priority same-day candidates.
|
|
|
"""
|
|
|
|
|
|
positive_gap_rebound_failure_min_gap_pct: float | None = None
|
|
|
"""Minimum positive opening gap for the rebound-failure profile."""
|
|
|
|
|
|
positive_gap_rebound_failure_max_ret_5d: float | None = None
|
|
|
"""Maximum prior 5-day return for the rebound-failure profile."""
|
|
|
|
|
|
positive_gap_rebound_failure_max_orb_return: float | None = None
|
|
|
"""Maximum first ORB candle return for the rebound-failure profile."""
|
|
|
|
|
|
positive_gap_rebound_failure_min_volume_attention_rank_pct: float | None = None
|
|
|
"""Minimum volume-attention percentile for the rebound-failure profile."""
|
|
|
|
|
|
red_to_green_acceleration_min_abs_gap_pct: float | None = None
|
|
|
"""Minimum downside gap magnitude for the red-to-green acceleration booster.
|
|
|
Example: 0.05 targets candidates opening at least 5% below prev close."""
|
|
|
|
|
|
red_to_green_acceleration_min_orb_return: float | None = None
|
|
|
"""Minimum first ORB candle return required for the acceleration booster."""
|
|
|
|
|
|
red_to_green_acceleration_min_score_rank_pct: float | None = None
|
|
|
"""Minimum score_rank_pct required for the acceleration booster."""
|
|
|
|
|
|
red_to_green_acceleration_min_candidate_score: float | None = None
|
|
|
"""Minimum composite candidate score required for the acceleration booster."""
|
|
|
|
|
|
red_to_green_acceleration_min_premarket_dollar_vol: float | None = None
|
|
|
"""Optional minimum premarket dollar volume required for the booster."""
|
|
|
|
|
|
red_to_green_acceleration_ignore_scaled_risk_overlays: bool = True
|
|
|
"""When True, do not boost candidates already reduced by defensive overlays."""
|
|
|
|
|
|
red_to_green_acceleration_size_scale: float = 1.0
|
|
|
"""Position-size multiplier for qualifying red-to-green acceleration setups.
|
|
|
Values at or below 1.0 disable the booster; values above 1.0 increase size."""
|
|
|
|
|
|
liquid_leader_conviction_min_candidate_score: float | None = None
|
|
|
"""Minimum composite score for the liquid-leader conviction booster.
|
|
|
None disables the candidate-score gate."""
|
|
|
|
|
|
liquid_leader_conviction_min_score_rank_pct: float | None = None
|
|
|
"""Minimum score_rank_pct for the liquid-leader conviction booster."""
|
|
|
|
|
|
liquid_leader_conviction_min_premarket_dollar_vol: float | None = None
|
|
|
"""Minimum premarket dollar volume for the liquid-leader conviction booster."""
|
|
|
|
|
|
liquid_leader_conviction_min_rvol: float | None = None
|
|
|
"""Optional minimum opening RVOL for the liquid-leader conviction booster."""
|
|
|
|
|
|
liquid_leader_conviction_min_abs_gap_pct: float | None = None
|
|
|
"""Optional minimum absolute gap magnitude for the liquid-leader conviction booster."""
|
|
|
|
|
|
liquid_leader_conviction_allowed_trigger_types: list[str] | None = None
|
|
|
"""Optional entry trigger allowlist for the liquid-leader conviction booster.
|
|
|
None keeps the prior behavior and allows every trigger type."""
|
|
|
|
|
|
liquid_leader_conviction_secondary_requires_sector_confirmation: bool = False
|
|
|
"""When True, non-ORB liquid-leader boosts require sector confirmation.
|
|
|
ORB entries can still qualify on single-name leadership."""
|
|
|
|
|
|
liquid_leader_conviction_secondary_min_body_ratio: float | None = None
|
|
|
"""Optional minimum opening-bar body ratio for non-ORB liquid-leader boosts.
|
|
|
ORB entries are not affected by this secondary-entry quality gate."""
|
|
|
|
|
|
liquid_leader_conviction_ignore_scaled_risk_overlays: bool = True
|
|
|
"""When True, do not boost candidates already reduced by defensive overlays."""
|
|
|
|
|
|
liquid_leader_conviction_size_scale: float = 1.0
|
|
|
"""Position-size multiplier for qualifying liquid high-conviction leaders.
|
|
|
Values at or below 1.0 disable the booster; values above 1.0 increase size."""
|
|
|
|
|
|
opening_burst_liquid_size_scale: float = 1.0
|
|
|
"""Position-size multiplier for liquid leaders that trigger in the opening burst.
|
|
|
This is a time-of-entry allocator: it only boosts names that both attract
|
|
|
substantial premarket dollar volume and break out immediately after the
|
|
|
opening range, where same-day attention is most concentrated."""
|
|
|
|
|
|
opening_burst_liquid_max_entry_minutes_after_open: int | None = None
|
|
|
"""Latest entry timestamp, in minutes after 09:30 ET, eligible for the
|
|
|
opening-burst liquid allocator. None disables the time gate."""
|
|
|
|
|
|
opening_burst_liquid_min_premarket_dollar_vol: float | None = None
|
|
|
"""Minimum premarket dollar volume required for opening-burst liquid sizing."""
|
|
|
|
|
|
opening_burst_liquid_min_gap_pct: float | None = None
|
|
|
"""Optional minimum opening gap required for opening-burst liquid sizing.
|
|
|
None keeps the legacy behavior. Use 0.0 to restrict the boost to flat/up-gap
|
|
|
names and avoid allocating extra capital to downside-gap reclaim attempts."""
|
|
|
|
|
|
opening_burst_liquid_min_score_rank_pct: float | None = None
|
|
|
"""Optional minimum daily candidate rank percentile for opening-burst liquid sizing."""
|
|
|
|
|
|
opening_burst_liquid_min_candidate_score: float | None = None
|
|
|
"""Optional minimum composite candidate score for opening-burst liquid sizing.
|
|
|
This prevents a name from receiving extra early-burst capital solely because
|
|
|
it is top-ranked on a weak candidate day."""
|
|
|
|
|
|
opening_burst_liquid_allowed_trigger_types: list[str] | None = None
|
|
|
"""Optional trigger allowlist for opening-burst liquid sizing.
|
|
|
None = allow all trigger types, though ORB-only is usually cleaner."""
|
|
|
|
|
|
opening_burst_liquid_ignore_scaled_risk_overlays: bool = True
|
|
|
"""When True, do not boost candidates already reduced by defensive overlays."""
|
|
|
|
|
|
partial_exit_at_r: float | None = None
|
|
|
"""Lock in partial profits when R-multiple reaches this level. None = disabled.
|
|
|
E.g. 1.0 = sell partial_exit_pct of the position at 1R, let remainder run."""
|
|
|
|
|
|
partial_exit_pct: float = 0.50
|
|
|
"""Fraction of position to exit at partial_exit_at_r. 0.50 = sell half the position."""
|
|
|
|
|
|
market_regime_spy_trend_days: int | None = None
|
|
|
"""Multi-day SPY trend filter: look back this many trading days for cumulative return.
|
|
|
None = disabled. Requires market_regime_spy_trend_threshold to also be set.
|
|
|
E.g. 5 = look at SPY's 5-day cumulative return ending yesterday."""
|
|
|
|
|
|
market_regime_spy_trend_threshold: float | None = None
|
|
|
"""Skip day if SPY's cumulative return over market_regime_spy_trend_days is below this.
|
|
|
E.g. -0.03 = skip if SPY down >3% over the past N days.
|
|
|
Protects against sustained bear-market weeks (single-day gap filter misses these)."""
|
|
|
|
|
|
rolling_loss_days: int | None = None
|
|
|
"""Self-referential rolling loss filter: look back this many trading days for strategy P&L.
|
|
|
None = disabled. Requires rolling_loss_threshold to also be set.
|
|
|
E.g. 5 = compute strategy's cumulative return over past 5 trading days."""
|
|
|
|
|
|
rolling_loss_threshold: float | None = None
|
|
|
"""Skip day if strategy's own rolling return (past rolling_loss_days) is below this.
|
|
|
E.g. -0.05 = pause trading if strategy lost >5% of initial capital in past 5 days.
|
|
|
Protects against cascading losses in regimes where the strategy stops working."""
|
|
|
|
|
|
market_orb_quality_veto_rolling_loss_pct: float | None = None
|
|
|
"""Synthetic rolling-loss PnL applied only to the rolling loss governor when
|
|
|
a market ORB quality veto blocks all trades for the day.
|
|
|
This does not change reported PnL/equity; it prevents an avoided high-risk
|
|
|
day from immediately re-enabling risk after the veto suppressed real losses.
|
|
|
E.g. -0.05 records -5% of initial_capital in the rolling loss window."""
|
|
|
|
|
|
# VIX regime filter and position size scaling
|
|
|
max_vix: float | None = None
|
|
|
"""Skip the whole day if VIX closes above this threshold. None = disabled.
|
|
|
E.g. 30.0 = skip days when VIX > 30 (high fear regime)."""
|
|
|
|
|
|
vix_size_scale_low: float | None = None
|
|
|
"""VIX level where position size scaling starts (scaler = 1.0 below this). None = disabled."""
|
|
|
|
|
|
vix_size_scale_high: float | None = None
|
|
|
"""VIX level where the position-size scaler reaches vix_size_scale_min."""
|
|
|
|
|
|
vix_size_scale_min: float = 1.0
|
|
|
"""Minimum position-size scaler once VIX reaches vix_size_scale_high.
|
|
|
E.g. 0.50 = halve position sizes when VIX is at or above vix_size_scale_high."""
|
|
|
|
|
|
# V20: soft regime/breadth scalers (all default to V19 binary-skip behavior)
|
|
|
regime_size_scale_low: float | None = None
|
|
|
"""QQQ gap at which regime scaler bottoms out. None = V19 binary skip."""
|
|
|
regime_size_scale_high: float | None = None
|
|
|
"""QQQ gap at which regime scaler = 1.0. Binary skip fires when gap < market_regime_spy_threshold."""
|
|
|
regime_size_scale_min: float = 1.0
|
|
|
"""Minimum regime scaler. 1.0 = V19 behavior."""
|
|
|
regime_skip_below: float | None = None
|
|
|
"""Hard skip floor below regime_size_scale_low. None = no extra skip."""
|
|
|
|
|
|
soft_day_fallback_on_regime_skip: bool = False
|
|
|
"""When True, a legacy market-regime skip can become a small soft-day
|
|
|
fallback instead of a full no-trade day. Disabled by default to preserve
|
|
|
existing ORB strategy behavior."""
|
|
|
|
|
|
soft_day_regime_skip_size_scale: float = 1.0
|
|
|
"""Day-level size scale used when soft_day_fallback_on_regime_skip converts
|
|
|
a market-regime skip into a fallback trading day."""
|
|
|
|
|
|
market_orb_quality_ticker: str | None = None
|
|
|
"""Ticker used for the market first-5min ORB quality scaler.
|
|
|
None = fall back to market_regime_ticker, then SPY."""
|
|
|
|
|
|
market_orb_quality_secondary_ticker: str | None = None
|
|
|
"""Optional second ticker used for divergence-aware market ORB quality guards."""
|
|
|
|
|
|
market_orb_quality_size_scale_low: float | None = None
|
|
|
"""First-bar close_location where the market ORB quality scaler bottoms out.
|
|
|
None disables the scaler."""
|
|
|
|
|
|
market_orb_quality_size_scale_high: float | None = None
|
|
|
"""First-bar close_location where the market ORB quality scaler reaches
|
|
|
market_orb_quality_size_scale_max."""
|
|
|
|
|
|
market_orb_quality_size_scale_min: float = 1.0
|
|
|
"""Minimum market ORB quality scaler when close_location is at or below
|
|
|
market_orb_quality_size_scale_low."""
|
|
|
|
|
|
market_orb_quality_size_scale_max: float = 1.0
|
|
|
"""Maximum market ORB quality scaler when close_location is at or above
|
|
|
market_orb_quality_size_scale_high."""
|
|
|
|
|
|
market_orb_quality_primary_strong_above: float | None = None
|
|
|
"""Primary market ORB close_location threshold that counts as 'strong' for the
|
|
|
divergence guard. Used with market_orb_quality_secondary_weak_below."""
|
|
|
|
|
|
market_orb_quality_secondary_weak_below: float | None = None
|
|
|
"""Secondary market ORB close_location threshold that counts as 'weak' for the
|
|
|
divergence guard. Used with market_orb_quality_primary_strong_above."""
|
|
|
|
|
|
market_orb_quality_secondary_weak_above: float | None = None
|
|
|
"""Optional lower bound for the secondary market ORB close_location on
|
|
|
divergence-guard days. Useful for targeting mildly weak split-tape opens
|
|
|
without affecting deeply risk-off openings."""
|
|
|
|
|
|
market_orb_quality_divergence_scale: float | None = None
|
|
|
"""Extra day-level size multiplier applied when the primary market ORB is strong
|
|
|
but the secondary market ORB is weak. None = disabled."""
|
|
|
|
|
|
market_orb_quality_divergence_max_trades: int | None = None
|
|
|
"""Optional total trade cap applied only on divergence-guard days.
|
|
|
Useful for preventing multi-entry opening fades when the market tape is split."""
|
|
|
|
|
|
market_orb_quality_primary_weak_below: float | None = None
|
|
|
"""Primary market ORB close_location threshold for weak-primary / strong-secondary split tape.
|
|
|
Used with market_orb_quality_secondary_strong_above."""
|
|
|
|
|
|
market_orb_quality_primary_weak_above: float | None = None
|
|
|
"""Optional lower bound for weak-primary / strong-secondary split-tape days.
|
|
|
Useful for targeting weak but non-panic primary-index opens."""
|
|
|
|
|
|
market_orb_quality_secondary_strong_above: float | None = None
|
|
|
"""Secondary market ORB close_location threshold that counts as strong when the
|
|
|
primary market proxy is weak."""
|
|
|
|
|
|
market_orb_quality_primary_weak_secondary_strong_scale: float | None = None
|
|
|
"""Extra day-level size multiplier applied when the primary market ORB is weak
|
|
|
while the secondary market ORB is strong. None = disabled."""
|
|
|
|
|
|
market_orb_quality_primary_weak_secondary_strong_max_trades: int | None = None
|
|
|
"""Optional total trade cap for weak-primary / strong-secondary split-tape days."""
|
|
|
|
|
|
market_orb_quality_primary_lag_above: float | None = None
|
|
|
"""Optional lower bound for primary-lag / secondary-lead split-tape days.
|
|
|
This targets non-confirmed secondary-index leadership where the primary
|
|
|
market proxy is neither deeply weak nor strongly confirming."""
|
|
|
|
|
|
market_orb_quality_primary_lag_below: float | None = None
|
|
|
"""Upper bound for the primary market ORB close_location on primary-lag /
|
|
|
secondary-lead split-tape days."""
|
|
|
|
|
|
market_orb_quality_secondary_lead_above: float | None = None
|
|
|
"""Lower bound for the secondary market ORB close_location on primary-lag /
|
|
|
secondary-lead split-tape days."""
|
|
|
|
|
|
market_orb_quality_secondary_lead_below: float | None = None
|
|
|
"""Optional upper bound for the secondary market ORB close_location.
|
|
|
Useful for targeting unconfirmed but not euphoric secondary-index leadership."""
|
|
|
|
|
|
market_orb_quality_primary_lag_secondary_lead_scale: float | None = None
|
|
|
"""Extra day-level size multiplier for primary-lag / secondary-lead split tape."""
|
|
|
|
|
|
market_orb_quality_primary_lag_secondary_lead_max_trades: int | None = None
|
|
|
"""Optional total trade cap for primary-lag / secondary-lead split-tape days."""
|
|
|
|
|
|
market_orb_quality_joint_weak_primary_below: float | None = None
|
|
|
"""Primary market ORB close_location threshold that counts as jointly weak.
|
|
|
Used with market_orb_quality_joint_weak_secondary_below to target broad
|
|
|
risk-off opens where both market proxies finish their first bar near the low."""
|
|
|
|
|
|
market_orb_quality_joint_weak_primary_above: float | None = None
|
|
|
"""Optional lower bound for the primary market ORB close_location on jointly weak days.
|
|
|
Useful for targeting mildly weak opens without also firing on panic-gap flushes."""
|
|
|
|
|
|
market_orb_quality_joint_weak_secondary_below: float | None = None
|
|
|
"""Secondary market ORB close_location threshold that counts as jointly weak.
|
|
|
Requires market_orb_quality_secondary_ticker and the paired primary threshold."""
|
|
|
|
|
|
market_orb_quality_joint_weak_secondary_above: float | None = None
|
|
|
"""Optional lower bound for the secondary market ORB close_location on jointly weak days.
|
|
|
Useful for narrowing the guard to a mild weak-open band."""
|
|
|
|
|
|
market_orb_quality_joint_weak_scale: float | None = None
|
|
|
"""Extra day-level size multiplier applied when both primary and secondary
|
|
|
market ORB bars are weak. None = disabled."""
|
|
|
|
|
|
market_orb_quality_joint_weak_max_trades: int | None = None
|
|
|
"""Optional total trade cap applied only on jointly weak market-ORB days."""
|
|
|
|
|
|
market_orb_quality_joint_weak_require_confirmation: bool = False
|
|
|
"""Force require_confirmation_bar on jointly weak market-ORB days.
|
|
|
Useful for filtering early false-breakouts during broad weak opens."""
|
|
|
|
|
|
market_orb_quality_joint_panic_primary_below: float | None = None
|
|
|
"""Primary market ORB close_location threshold for panic-low opens.
|
|
|
Unlike the joint-weak band, this has no lower bound and targets deeply
|
|
|
weak first bars that may still produce tradable single-name leaders."""
|
|
|
|
|
|
market_orb_quality_joint_panic_secondary_below: float | None = None
|
|
|
"""Secondary market ORB close_location threshold for panic-low opens.
|
|
|
Requires market_orb_quality_secondary_ticker and the paired primary threshold."""
|
|
|
|
|
|
market_orb_quality_joint_panic_scale: float | None = None
|
|
|
"""Extra day-level size multiplier applied on joint panic-low market ORB days.
|
|
|
None leaves sizing unchanged while confirmation/max-trade guards can still apply."""
|
|
|
|
|
|
market_orb_quality_joint_panic_max_trades: int | None = None
|
|
|
"""Optional total trade cap applied only on joint panic-low market ORB days."""
|
|
|
|
|
|
market_orb_quality_joint_panic_require_confirmation: bool = False
|
|
|
"""Force require_confirmation_bar on joint panic-low market ORB days.
|
|
|
This keeps panic-open winners eligible but requires post-breakout repair."""
|
|
|
|
|
|
market_thrust_breadth_override_enabled: bool = False
|
|
|
"""When True, a strong index opening thrust can override breadth-only
|
|
|
soft-day sizing. This is intended for days where the candidate universe
|
|
|
opens mixed but SPY/QQQ show clear opening-range risk-on confirmation."""
|
|
|
|
|
|
market_thrust_breadth_override_min_primary_close_location: float | None = None
|
|
|
"""Minimum primary market ORB close_location required for breadth override."""
|
|
|
|
|
|
market_thrust_breadth_override_min_secondary_close_location: float | None = None
|
|
|
"""Minimum secondary market ORB close_location required for breadth override."""
|
|
|
|
|
|
market_thrust_breadth_override_min_primary_return_pct: float | None = None
|
|
|
"""Minimum primary market first-bar return required for breadth override."""
|
|
|
|
|
|
market_thrust_breadth_override_min_secondary_return_pct: float | None = None
|
|
|
"""Minimum secondary market first-bar return required for breadth override."""
|
|
|
|
|
|
market_thrust_breadth_override_min_regime_gap_pct: float | None = None
|
|
|
"""Minimum configured regime-ticker opening gap required for breadth override."""
|
|
|
|
|
|
market_thrust_breadth_override_min_breadth_ratio: float | None = None
|
|
|
"""Minimum opening breadth ratio required before the override can fire.
|
|
|
This prevents a strong index bar from overriding genuinely broken breadth."""
|
|
|
|
|
|
market_thrust_breadth_override_size_scale_floor: float = 1.0
|
|
|
"""Minimum breadth scaler applied when the market-thrust override fires."""
|
|
|
|
|
|
market_thrust_breadth_override_clear_soft_day: bool = False
|
|
|
"""When True, remove breadth-only soft-day status after the override.
|
|
|
Default False keeps soft-day selection/caps and only raises sizing."""
|
|
|
|
|
|
market_thrust_opening_breadth_override_enabled: bool = False
|
|
|
"""When True, use same-day first-bar universe breadth as an additional
|
|
|
market-thrust override. This is a point-in-time proxy for broad intraday
|
|
|
participation when index gaps or open-gap breadth understate the tape."""
|
|
|
|
|
|
market_thrust_opening_breadth_override_min_total_count: int = 100
|
|
|
"""Minimum number of liquid first bars needed before opening-breadth override can fire."""
|
|
|
|
|
|
market_thrust_opening_breadth_override_min_first_bar_dollar_vol: float | None = None
|
|
|
"""Optional minimum first-bar dollar volume per ticker included in opening-breadth stats."""
|
|
|
|
|
|
market_thrust_opening_breadth_override_min_positive_ratio: float | None = None
|
|
|
"""Minimum fraction of included tickers with positive first-bar returns."""
|
|
|
|
|
|
market_thrust_opening_breadth_override_min_avg_return_pct: float | None = None
|
|
|
"""Minimum average first-bar return across included tickers."""
|
|
|
|
|
|
market_thrust_opening_breadth_override_strong_close_location: float = 0.65
|
|
|
"""Close-location threshold used to count strong first bars in the universe."""
|
|
|
|
|
|
market_thrust_opening_breadth_override_min_strong_close_location_ratio: float | None = None
|
|
|
"""Minimum fraction of included tickers closing above the strong close-location threshold."""
|
|
|
|
|
|
market_thrust_opening_breadth_override_allow_regime_soft_day: bool = True
|
|
|
"""Allow opening-breadth thrust to override market-regime soft days."""
|
|
|
|
|
|
market_thrust_opening_breadth_override_allow_breadth_soft_day: bool = True
|
|
|
"""Allow opening-breadth thrust to override ordinary breadth soft days."""
|
|
|
|
|
|
market_thrust_opening_breadth_override_allow_hard_breadth: bool = False
|
|
|
"""Allow opening-breadth thrust on hard-breadth fallback days. Disabled by
|
|
|
default because hard-breadth days are usually broad risk-off gaps."""
|
|
|
|
|
|
market_thrust_opening_breadth_override_regime_size_scale_floor: float = 1.0
|
|
|
"""Minimum regime scaler applied when opening-breadth override fires."""
|
|
|
|
|
|
market_thrust_opening_breadth_override_breadth_size_scale_floor: float = 1.0
|
|
|
"""Minimum breadth scaler applied when opening-breadth override fires."""
|
|
|
|
|
|
market_thrust_opening_breadth_override_clear_soft_day: bool = False
|
|
|
"""When True, remove soft-day reason parts allowed by the opening-breadth override."""
|
|
|
|
|
|
market_thrust_opening_breadth_override_activate_liquid_continuation: bool = True
|
|
|
"""When False, opening-breadth-only thrust does not activate the existing
|
|
|
market_thrust_liquid_continuation sleeve. This lets a strategy use broad
|
|
|
first-bar participation only for specialized auxiliary sleeves such as
|
|
|
opening-impulse reclaim, while keeping the liquid opening-burst sleeve tied
|
|
|
to explicit SPY/QQQ thrust."""
|
|
|
|
|
|
conditional_confirmation_ticker: str | None = None
|
|
|
"""Ticker used for day-level conditional confirmation-bar activation.
|
|
|
None falls back to market_regime_ticker, then market_orb_quality_ticker, then QQQ."""
|
|
|
|
|
|
conditional_confirmation_below_return_pct: float | None = None
|
|
|
"""Force require_confirmation_bar for the day when the conditional_confirmation_ticker's
|
|
|
first regular-session bar return is at or below this threshold.
|
|
|
None = disabled. E.g. 0.0 = require confirmation when the market opens red."""
|
|
|
|
|
|
breadth_size_scale_low: float | None = None
|
|
|
"""Breadth ratio at which breadth scaler bottoms out. None = V19 binary skip."""
|
|
|
breadth_size_scale_high: float | None = None
|
|
|
"""Breadth ratio at which breadth scaler = 1.0."""
|
|
|
breadth_size_scale_min: float = 1.0
|
|
|
"""Minimum breadth scaler. 1.0 = V19 behavior."""
|
|
|
breadth_skip_below: float | None = None
|
|
|
"""Hard skip floor on breadth. None = no extra skip."""
|
|
|
|
|
|
hard_breadth_soft_fallback_enabled: bool = False
|
|
|
"""When True, a breadth_skip_below day can become a tiny soft-day fallback
|
|
|
instead of a full skip if the breadth ratio is still above
|
|
|
hard_breadth_soft_fallback_min_breadth. Disabled by default."""
|
|
|
|
|
|
hard_breadth_soft_fallback_min_breadth: float | None = None
|
|
|
"""Lowest candidate breadth ratio allowed for hard-breadth soft fallback.
|
|
|
None allows all breadth_skip_below days to fall back when enabled."""
|
|
|
|
|
|
hard_breadth_soft_fallback_size_scale: float = 0.03
|
|
|
"""Day-level size scale used when hard-breadth soft fallback converts a
|
|
|
hard breadth skip into a defensive micro-exposure day."""
|
|
|
|
|
|
soft_day_fallback_on_breadth_skip: bool = False
|
|
|
"""When True, a legacy candidate-breadth skip can become a small soft-day
|
|
|
fallback instead of a full no-trade day. Disabled by default to preserve
|
|
|
existing strategy behavior."""
|
|
|
|
|
|
soft_day_breadth_skip_size_scale: float = 1.0
|
|
|
"""Day-level size scale used when soft_day_fallback_on_breadth_skip converts
|
|
|
a candidate-breadth skip into a fallback trading day."""
|
|
|
|
|
|
soft_day_combined_size_scale_floor: float | None = None
|
|
|
"""Minimum combined regime*breadth size scale for explicit skip-day fallback.
|
|
|
None preserves raw multiplicative scaling."""
|
|
|
|
|
|
# V20: regime-adaptive stops
|
|
|
soft_day_scaler_threshold: float = 1.0
|
|
|
"""combined_scaler (regime*breadth) below this triggers soft-day stop adjustments."""
|
|
|
atr_stop_multiplier_weak: float | None = None
|
|
|
"""Replaces atr_stop_multiplier on soft days. None = no change."""
|
|
|
breakeven_at_r_weak: float | None = None
|
|
|
"""Replaces breakeven_at_r on soft days. None = no change."""
|
|
|
|
|
|
# V20: soft-day selection bar
|
|
|
soft_day_max_trades: int | None = None
|
|
|
"""Max trades per soft day (top-N by rank). None = no cap."""
|
|
|
soft_day_min_score_pct: float | None = None
|
|
|
"""Min composite score rank_pct on soft days. None = no filter."""
|
|
|
|
|
|
soft_day_min_candidate_score: float | None = None
|
|
|
"""Minimum absolute ORB candidate score on soft days. None = no filter.
|
|
|
Unlike soft_day_min_score_pct, this rejects weak one-name fallback baskets
|
|
|
even when the candidate is rank #1 by default."""
|
|
|
|
|
|
soft_day_min_ret_5d: float | None = None
|
|
|
"""Minimum prior 5-day return for soft-day fallback trades. None = no filter."""
|
|
|
|
|
|
soft_day_max_rvol: float | None = None
|
|
|
"""Maximum opening-range RVOL for soft-day fallback trades. None = no filter."""
|
|
|
|
|
|
soft_day_setup_profile: str = "none"
|
|
|
"""Optional named setup filter for soft-day fallback trades.
|
|
|
'none' keeps scalar threshold behavior. Named profiles can apply
|
|
|
reason-specific, disjunctive filters for former no-trade days."""
|
|
|
|
|
|
soft_day_rank_before_time: bool = False
|
|
|
"""When True, soft-day candidates are allocated by score before entry time.
|
|
|
This avoids a low-conviction early trigger consuming the one-trade fallback
|
|
|
sleeve ahead of a stronger later trigger."""
|
|
|
|
|
|
soft_day_exclude_from_streak: bool = False
|
|
|
"""When True, trades taken on soft days do not update streak sizing state.
|
|
|
This lets defensive fallback sleeves add small exposure without contaminating
|
|
|
the main strong-regime allocator."""
|
|
|
|
|
|
soft_day_exclude_from_settlement: bool = False
|
|
|
"""When True, soft-day fallback trades do not update settled-cash state.
|
|
|
This lets auxiliary fallback sleeves add PnL without changing the next
|
|
|
normal day's capital availability in daily-reset research mode."""
|
|
|
|
|
|
# Breakout volume confirmation
|
|
|
min_breakout_rel_vol: float | None = None
|
|
|
"""Minimum relative volume on the breakout bar vs average post-ORB bar volume.
|
|
|
Filters low-conviction breakouts where price touches the level on thin volume.
|
|
|
None = disabled (any volume accepted). E.g. 1.5 = breakout bar must have 1.5× avg bar volume."""
|
|
|
|
|
|
# Time-decay trailing stop tightening
|
|
|
time_decay_start_minutes: int | None = None
|
|
|
"""Minutes after market open (9:30 ET) to start tightening the trailing stop.
|
|
|
None = disabled. E.g. 180 = start tightening at 12:30 PM ET."""
|
|
|
|
|
|
time_decay_factor: float = 0.5
|
|
|
"""By close, the trailing ATR multiplier shrinks to this fraction of its base value.
|
|
|
E.g. 0.5 = trail width halves linearly from time_decay_start_minutes to close."""
|
|
|
|
|
|
# Running VWAP trailing exit
|
|
|
vwap_exit_mode: str = "none"
|
|
|
"""VWAP-based exit mode:
|
|
|
- 'none': disabled (default)
|
|
|
- 'exit': exit when bar close crosses below running VWAP (longs) or above (shorts)
|
|
|
- 'floor': use VWAP - buffer as trailing stop floor (can't trail above VWAP for longs)
|
|
|
Running VWAP is computed from cumulative (typical_price × volume) / cumulative(volume)
|
|
|
starting from market open."""
|
|
|
|
|
|
vwap_exit_buffer_atr: float = 0.0
|
|
|
"""Buffer below VWAP (in ATR units) for 'floor' mode.
|
|
|
E.g. 0.3 = trailing stop can't go below VWAP - 0.3×ATR.
|
|
|
For 'exit' mode: exit only when close < VWAP - buffer×ATR (allows noise)."""
|
|
|
|
|
|
vwap_exit_after_r: float = 0.0
|
|
|
"""Only activate VWAP exit after reaching this R-multiple.
|
|
|
0.0 = active from entry. 1.0 = only after trade reaches 1R.
|
|
|
Prevents premature VWAP exits on initial pullbacks after breakout."""
|
|
|
|
|
|
# Score-based position sizing
|
|
|
score_sizing_multiplier: float | None = None
|
|
|
"""Scale risk_per_trade_pct by candidate rank. Top candidate gets this multiplier,
|
|
|
bottom gets 1.0x (linear interpolation). None = disabled (equal sizing).
|
|
|
E.g. 2.0 = top pick risks 2× base, bottom pick risks 1×. Requires score rank
|
|
|
to be passed from simulate_orb_day."""
|
|
|
|
|
|
score_sizing_floor: float | None = None
|
|
|
"""Scale lower-ranked ORB candidates down by score rank.
|
|
|
Top candidate keeps full base size; bottom candidate receives this fraction.
|
|
|
None disables rank-downsizing. E.g. 0.50 means the weakest ranked name uses
|
|
|
half size while the top ranked name uses full size."""
|
|
|
|
|
|
ranked_downside_gap_min_abs_gap_pct: float | None = None
|
|
|
"""Minimum downside gap magnitude for the rank-aware downside-gap overlay.
|
|
|
This overlay runs after candidate ranking, so it can target lower-ranked
|
|
|
red-to-green attempts without changing the candidate list."""
|
|
|
|
|
|
ranked_downside_gap_max_premarket_dollar_vol: float | None = None
|
|
|
"""Maximum premarket dollar volume for the rank-aware downside-gap overlay."""
|
|
|
|
|
|
ranked_downside_gap_max_score_rank_pct: float | None = None
|
|
|
"""Maximum score_rank_pct eligible for the rank-aware downside-gap overlay.
|
|
|
1.0 is the top candidate; lower values target weaker-ranked candidates."""
|
|
|
|
|
|
ranked_downside_gap_min_market_secondary_close_location: float | None = None
|
|
|
"""Minimum secondary market ORB close location required for the overlay.
|
|
|
This is used to isolate stock-specific weak downside gaps when QQQ's own
|
|
|
opening range is not in panic mode."""
|
|
|
|
|
|
ranked_downside_gap_action: str = "none"
|
|
|
"""Action for the rank-aware downside-gap overlay.
|
|
|
'none' disables it; 'scale' applies ranked_downside_gap_size_scale;
|
|
|
'scale_reserve' scales the trade while reserving the unscaled cash slot;
|
|
|
'loss_cap' keeps normal sizing but applies ranked_downside_gap_loss_cap_pct."""
|
|
|
|
|
|
ranked_downside_gap_size_scale: float = 1.0
|
|
|
"""Position-size multiplier for rank-aware downside-gap candidates."""
|
|
|
|
|
|
ranked_downside_gap_loss_cap_pct: float | None = None
|
|
|
"""Fixed stop distance as a fraction of entry price for rank-aware
|
|
|
downside-gap candidates when ranked_downside_gap_action='loss_cap'."""
|
|
|
|
|
|
# Confirmation bar requirement
|
|
|
require_confirmation_bar: bool = False
|
|
|
"""After breakout, require the NEXT bar to close above entry price (long) or
|
|
|
below (short) to confirm. If the confirmation bar fails, skip the trade.
|
|
|
Filters false breakouts where price barely touches the level and reverses."""
|
|
|
|
|
|
# Gap fill protection
|
|
|
exit_on_gap_fill: bool = False
|
|
|
"""Exit immediately if price drops below prev_close (long) or rises above (short).
|
|
|
A gap fill means the original catalyst is being rejected by the market.
|
|
|
Uses bar close for the check (not intra-bar low)."""
|
|
|
|
|
|
gap_up_fill_exit_min_gap_pct: float | None = None
|
|
|
"""Enable candidate-level gap-fill exit for long positive-gap candidates
|
|
|
whose opening gap is at least this value. None disables the overlay."""
|
|
|
|
|
|
gap_up_fill_exit_max_gap_pct: float | None = None
|
|
|
"""Optional upper bound for candidate-level positive-gap fill exits."""
|
|
|
|
|
|
gap_up_fill_exit_min_ret_5d: float | None = None
|
|
|
"""Optional minimum prior 5-day return for positive-gap fill exits."""
|
|
|
|
|
|
gap_up_fill_exit_max_ret_5d: float | None = None
|
|
|
"""Optional maximum prior 5-day return for positive-gap fill exits."""
|
|
|
|
|
|
gap_up_fill_exit_max_premarket_dollar_vol: float | None = None
|
|
|
"""Optional premarket dollar-volume ceiling for positive-gap fill exits."""
|
|
|
|
|
|
gap_up_fill_exit_min_body_ratio: float | None = None
|
|
|
"""Optional minimum ORB body ratio for positive-gap fill exits."""
|
|
|
|
|
|
gap_up_fill_exit_max_body_ratio: float | None = None
|
|
|
"""Optional maximum ORB body ratio for positive-gap fill exits."""
|
|
|
|
|
|
gap_up_fill_exit_max_close_location: float | None = None
|
|
|
"""Optional ORB close-location ceiling for positive-gap fill exits."""
|
|
|
|
|
|
# Max hold time
|
|
|
max_hold_minutes: int | None = None
|
|
|
"""Maximum minutes to hold a position. None = hold until exit_minutes_before_close.
|
|
|
E.g. 120 = exit 2 hours after entry regardless of profit/loss.
|
|
|
Useful for capturing morning momentum without afternoon reversal risk."""
|
|
|
|
|
|
time_stop_minutes: int | None = None
|
|
|
"""Minutes after entry to evaluate a conditional time stop.
|
|
|
None disables it. Unlike max_hold_minutes, this exits only if the trade has
|
|
|
not made enough progress."""
|
|
|
|
|
|
time_stop_exit_below_r: float = 0.0
|
|
|
"""Exit at the time stop when current R-multiple is at or below this value."""
|
|
|
|
|
|
time_stop_max_peak_r: float | None = None
|
|
|
"""Optional peak-R ceiling for time-stop exits.
|
|
|
When set, a trade that already reached this R-multiple is allowed to continue."""
|
|
|
|
|
|
early_failure_exit_minutes: int | None = None
|
|
|
"""Minutes after entry to monitor for immediate structural failure.
|
|
|
None disables it. This exits false breakouts that quickly lose their
|
|
|
breakout/entry/VWAP support before enough favorable excursion develops."""
|
|
|
|
|
|
early_failure_exit_level: str = "breakout"
|
|
|
"""Support level used by the early-failure exit:
|
|
|
'breakout' = original ORB breakout level, 'entry' = filled entry anchor,
|
|
|
'vwap' = running session VWAP at the evaluation bar."""
|
|
|
|
|
|
early_failure_exit_buffer_pct: float = 0.0
|
|
|
"""Tolerance around the selected support level before early-failure exit.
|
|
|
Longs exit below level × (1 - buffer); shorts exit above level × (1 + buffer)."""
|
|
|
|
|
|
early_failure_exit_max_peak_r: float | None = None
|
|
|
"""Optional peak-R ceiling for early-failure exits.
|
|
|
When set, a trade that already reached this R-multiple is allowed to continue."""
|
|
|
|
|
|
early_failure_exit_trigger_types: list[str] | None = None
|
|
|
"""Optional trigger allowlist for early-failure exits.
|
|
|
None = apply to all trigger types."""
|
|
|
|
|
|
# Bar close confirmation entry
|
|
|
entry_on_bar_close: bool = False
|
|
|
"""Require breakout bar's CLOSE to be above breakout level (long) or below (short),
|
|
|
not just the bar's HIGH/LOW. Enter at the bar's close price.
|
|
|
Filters wick-only breakouts where price barely touches the ORB level and reverses.
|
|
|
The trader waits for the 5-min bar to complete, then enters at the close price.
|
|
|
Same-bar stop is skipped (trader was not in position during the bar)."""
|
|
|
|
|
|
# ── Pyramiding (add to winners) ──
|
|
|
pyramid_at_r: float | None = None
|
|
|
"""Add to winning position when R-multiple reaches this level. None = disabled.
|
|
|
E.g. 1.0 = add pyramid_add_pct of original position when trade reaches 1R.
|
|
|
Stop is moved to at least breakeven on the blended cost after adding."""
|
|
|
|
|
|
pyramid_add_pct: float = 0.50
|
|
|
"""Fraction of original position size to add at each pyramid level.
|
|
|
0.50 = add 50% of original shares (100 shares → add 50 → 150 total)."""
|
|
|
|
|
|
pyramid_max_adds: int = 1
|
|
|
"""Maximum number of pyramid additions per trade. 1 = single add-on.
|
|
|
Each subsequent add triggers at pyramid_at_r + n * pyramid_at_r (staggered)."""
|
|
|
|
|
|
pyramid_allowed_trigger_types: list[str] | None = None
|
|
|
"""Optional trigger allowlist for pyramid additions.
|
|
|
None = allow all trigger types that can open trades."""
|
|
|
|
|
|
pyramid_min_score_rank_pct: float | None = None
|
|
|
"""Optional minimum candidate rank percentile required before pyramiding.
|
|
|
1.0 = top-ranked candidate of the day, 0.0 = bottom-ranked candidate."""
|
|
|
|
|
|
pyramid_min_rvol: float | None = None
|
|
|
"""Optional minimum opening relative volume required before pyramiding."""
|
|
|
|
|
|
pyramid_max_rvol: float | None = None
|
|
|
"""Optional maximum opening relative volume allowed before pyramiding."""
|
|
|
|
|
|
pyramid_require_sector_confirmation: bool = False
|
|
|
"""When True, pyramid add-ons are allowed only for candidates that passed
|
|
|
the sector-confirmation engine. This keeps add-on risk reserved for winners
|
|
|
with both price follow-through and cross-sectional sector participation."""
|
|
|
|
|
|
# ── Re-entry after stop-out ──
|
|
|
reentry_after_stop: bool = False
|
|
|
"""Allow re-entry on a ticker that was stopped out earlier in the same day.
|
|
|
The ticker must re-break the ORB level with volume >= reentry_min_volume_ratio
|
|
|
times the original breakout volume. Simulates the 'shakeout then real move' pattern."""
|
|
|
|
|
|
reentry_min_volume_ratio: float = 1.5
|
|
|
"""Minimum volume ratio (vs original breakout bar) required for re-entry.
|
|
|
1.5 = re-breakout bar must have 50% more volume than original breakout bar."""
|
|
|
|
|
|
reentry_max_per_ticker: int = 1
|
|
|
"""Maximum re-entries allowed per ticker per day."""
|
|
|
|
|
|
# ── Portfolio deployment cap ──
|
|
|
max_total_deployment_pct: float | None = None
|
|
|
"""Maximum total capital deployed across all concurrent positions as fraction of equity.
|
|
|
None = no limit (original behavior). E.g. 0.80 = never deploy more than 80% of equity.
|
|
|
Prevents over-concentration when settlement_days=0 allows unlimited deployment."""
|
|
|
|
|
|
# ── Drawdown governor ──
|
|
|
drawdown_governor_threshold: float | None = None
|
|
|
"""Enable drawdown governor when equity drops this fraction below peak.
|
|
|
None = disabled. E.g. 0.05 = start reducing sizing when equity is 5% below peak.
|
|
|
Linearly scales sizing from 1.0 at threshold to drawdown_governor_min_scale at 2× threshold."""
|
|
|
|
|
|
drawdown_governor_min_scale: float = 0.30
|
|
|
"""Minimum sizing scale at maximum drawdown governor activation.
|
|
|
0.30 = reduce position sizes to 30% of normal at 2× drawdown_governor_threshold."""
|
|
|
|
|
|
# ── Streak-based sizing ──
|
|
|
streak_sizing_win_bonus: float | None = None
|
|
|
"""Bonus sizing multiplier per consecutive win in recent trade history.
|
|
|
None = disabled. E.g. 0.15 = add 15% sizing per consecutive win.
|
|
|
3 consecutive wins → 1.0 + 3*0.15 = 1.45x sizing.
|
|
|
Computed at start of each day from previous days' trade outcomes."""
|
|
|
|
|
|
streak_sizing_loss_penalty: float | None = None
|
|
|
"""Reduce sizing per consecutive loss. None = no penalty (only reward wins).
|
|
|
E.g. 0.10 = subtract 10% per consecutive loss.
|
|
|
2 consecutive losses → 1.0 - 2*0.10 = 0.80x sizing."""
|
|
|
|
|
|
streak_sizing_max: float = 2.0
|
|
|
"""Cap on streak-based sizing multiplier. Prevents excessive leverage on long streaks."""
|
|
|
|
|
|
streak_sizing_min: float = 0.50
|
|
|
"""Floor on streak-based sizing multiplier. Prevents sizing from going too low."""
|
|
|
|
|
|
# ── Rolling performance sizing ──
|
|
|
rolling_wr_sizing_window: int | None = None
|
|
|
"""Window of recent trades for rolling win-rate sizing bonus.
|
|
|
None = disabled. E.g. 15 = compute WR over last 15 trades.
|
|
|
Applied AFTER streak sizing (multiplicative)."""
|
|
|
|
|
|
rolling_wr_sizing_threshold: float = 0.55
|
|
|
"""WR above this threshold triggers the bonus multiplier.
|
|
|
E.g. 0.55 = if rolling WR > 55%, apply rolling_wr_sizing_bonus."""
|
|
|
|
|
|
rolling_wr_sizing_bonus: float = 0.30
|
|
|
"""Bonus multiplier when rolling WR exceeds threshold.
|
|
|
E.g. 0.30 = size 1.30x when rolling WR is above threshold."""
|
|
|
|
|
|
rolling_wr_sizing_penalty_threshold: float | None = None
|
|
|
"""WR below this triggers a sizing reduction. None = no penalty.
|
|
|
E.g. 0.40 = if rolling WR < 40%, reduce sizing by rolling_wr_sizing_penalty."""
|
|
|
|
|
|
rolling_wr_sizing_penalty: float = 0.20
|
|
|
"""Penalty reduction when rolling WR is below penalty threshold.
|
|
|
E.g. 0.20 = size 0.80x when rolling WR is below penalty threshold."""
|
|
|
|
|
|
# ── Gap-adaptive trailing ──
|
|
|
gap_trail_wide_threshold: float | None = None
|
|
|
"""Gap% above which trailing uses wider ATR multiplier. None = disabled.
|
|
|
E.g. 0.05 = gaps > 5% get wider trailing (strong catalyst = longer trend).
|
|
|
Uses gap_trail_wide_atr_multiplier instead of trailing_stop_atr_multiplier."""
|
|
|
|
|
|
gap_trail_wide_atr_multiplier: float = 1.2
|
|
|
"""Trailing ATR multiplier for large-gap stocks (gap > gap_trail_wide_threshold).
|
|
|
Wider trail lets strong catalyst stocks run further before stopping out."""
|
|
|
|
|
|
gap_trail_tight_atr_multiplier: float | None = None
|
|
|
"""Optional tighter trailing for small-gap stocks (gap <= gap_trail_wide_threshold).
|
|
|
None = use default trailing_stop_atr_multiplier. E.g. 0.5 = tight trail for small gaps."""
|
|
|
|
|
|
conviction_runner_trail_tighten_at_r: float | None = None
|
|
|
"""Optional second-stage trailing threshold for high-conviction runner setups.
|
|
|
When active, it overrides trailing_tighten_at_r only for trades that pass the
|
|
|
conviction runner gates below."""
|
|
|
|
|
|
conviction_runner_trail_gap_atr_multiplier: float | None = None
|
|
|
"""Optional ATR trailing multiplier for high-conviction runner setups before
|
|
|
second-stage tightening. None keeps the normal/gap-adaptive ATR multiplier."""
|
|
|
|
|
|
conviction_runner_trail_min_abs_gap_pct: float | None = None
|
|
|
"""Minimum absolute opening gap required for the conviction runner trail."""
|
|
|
|
|
|
conviction_runner_trail_min_candidate_score: float | None = None
|
|
|
"""Minimum absolute composite candidate score for conviction runner trailing."""
|
|
|
|
|
|
conviction_runner_trail_min_score_rank_pct: float | None = None
|
|
|
"""Minimum daily score rank percentile for conviction runner trailing."""
|
|
|
|
|
|
conviction_runner_trail_allowed_trigger_types: list[str] | None = None
|
|
|
"""Optional trigger allowlist for conviction runner trailing."""
|
|
|
|
|
|
# ── Pullback continuation entry ──
|
|
|
pullback_entry: bool = False
|
|
|
"""Enable pullback continuation entry mode. Instead of entering immediately on
|
|
|
ORB breakout, wait for a pullback after breakout and enter on continuation.
|
|
|
Filters false breakouts and gives better entry prices with tighter stops."""
|
|
|
|
|
|
pullback_max_bars: int = 6
|
|
|
"""Maximum bars to wait for pullback-continuation pattern after initial breakout.
|
|
|
If no valid pullback+continuation within this window, skip the trade."""
|
|
|
|
|
|
pullback_min_retracement_pct: float = 0.30
|
|
|
"""Minimum retracement of the breakout move to qualify as a pullback.
|
|
|
0.30 = price must pull back at least 30% of (post-breakout peak - breakout level)."""
|
|
|
|
|
|
pullback_stop_at_low: bool = True
|
|
|
"""Set stop at the pullback low instead of ATR-based stop.
|
|
|
Gives naturally tighter stops based on actual price structure."""
|
|
|
|
|
|
# ── orb_pullback_v1 extended pullback controls ──
|
|
|
pullback_impulse_window_end_min: int | None = None
|
|
|
"""Minutes from market open (9:30 ET) by which the post-breakout impulse peak
|
|
|
must form. E.g. 25 = peak must occur by 9:55 ET. None disables (legacy behavior)."""
|
|
|
|
|
|
pullback_impulse_min_move_atr: float | None = None
|
|
|
"""Minimum impulse size from breakout level to peak as a multiple of ATR(14).
|
|
|
E.g. 0.5 = peak must be at least 0.5 × ATR above breakout level. None disables."""
|
|
|
|
|
|
pullback_depth_max_pct: float | None = None
|
|
|
"""Maximum pullback depth as a fraction of the impulse move. Works in conjunction
|
|
|
with pullback_min_retracement_pct. E.g. 0.50 = pullback must retrace at most 50%
|
|
|
of (peak - breakout). None disables (only min floor applied)."""
|
|
|
|
|
|
pullback_volume_contraction_ratio: float | None = None
|
|
|
"""Require pullback-phase average bar volume < impulse-phase average × this ratio.
|
|
|
E.g. 0.7 = pullback must occur on 70% or less of impulse volume. None disables."""
|
|
|
|
|
|
pullback_vwap_floor: bool = False
|
|
|
"""If True, abort the pullback setup if any bar during pullback breaches below
|
|
|
the running session VWAP by more than pullback_vwap_floor_tolerance_pct."""
|
|
|
|
|
|
pullback_vwap_floor_tolerance_pct: float = 0.003
|
|
|
"""Tolerance below running VWAP before pullback_vwap_floor fires. 0.003 = -0.3%."""
|
|
|
|
|
|
pullback_stop_mode: str = "atr"
|
|
|
"""Stop distance mode after pullback continuation entry.
|
|
|
'atr' = use ATR × atr_stop_multiplier (or pullback_low if pullback_stop_at_low=True),
|
|
|
'pullback_low' = always use pullback extreme as stop,
|
|
|
'vwap_lower' = use running VWAP − pullback_stop_vwap_buffer_pct as stop."""
|
|
|
|
|
|
pullback_stop_vwap_buffer_pct: float = 0.002
|
|
|
"""Buffer below running VWAP for 'vwap_lower' stop mode. 0.002 = −0.2%."""
|
|
|
|
|
|
pullback_reclaim_confirm_rel_vol: float | None = None
|
|
|
"""Minimum relative volume on the reclaim/continuation bar. Computed as
|
|
|
bar_volume / (avg_post_orb_bar_vol). E.g. 1.2 = bar must have 1.2× average
|
|
|
post-ORB volume. None disables."""
|
|
|
|
|
|
pullback_require_breakout_retake: bool = False
|
|
|
"""If True, the continuation bar must close back through the original breakout
|
|
|
level after the pullback. This prevents entering on weak green bars that still
|
|
|
sit below the ORB high / above the ORB low."""
|
|
|
|
|
|
pullback_breakout_retake_clearance_pct: float = 0.0
|
|
|
"""Optional clearance beyond the breakout level required for the pullback
|
|
|
continuation entry. 0.001 = close must reclaim the breakout by 0.1%."""
|
|
|
|
|
|
# ── vwap_reclaim_v1 engine ──
|
|
|
vwap_reclaim_window_start_min: int = 30
|
|
|
"""Minutes from market open (9:30 ET) when to start scanning for VWAP reclaim entries.
|
|
|
30 = 10:00 ET. Only used by engine_family: vwap_reclaim_v1."""
|
|
|
|
|
|
vwap_reclaim_window_end_min: int = 120
|
|
|
"""Minutes from market open when to stop accepting new VWAP reclaim entries.
|
|
|
120 = 11:30 ET. Only used by engine_family: vwap_reclaim_v1."""
|
|
|
|
|
|
vwap_reclaim_require_prior_dip: bool = False
|
|
|
"""If True, require that at least one bar before the reclaim window had close < running VWAP
|
|
|
(for long). Selects only true VWAP reclaim setups (failed ORB then recovered), not stocks
|
|
|
that drifted above VWAP all morning. Creates orthogonality with V23 (V23 winners never dip)."""
|
|
|
|
|
|
vwap_reclaim_min_clearance_pct: float = 0.0
|
|
|
"""Minimum % that entry bar's close must be above the running VWAP (for long).
|
|
|
E.g. 0.003 = close must be at least 0.3% above VWAP. Filters marginal reclaims."""
|
|
|
|
|
|
vwap_reclaim_require_orb_open_retake: bool = False
|
|
|
"""If True, the reclaim bar must also recover the ORB open (for long) or lose it
|
|
|
again (for short). Filters weak VWAP-only bounces that never repair the opening fade."""
|
|
|
|
|
|
vwap_reclaim_confirm_rel_vol: float | None = None
|
|
|
"""Minimum relative volume on the reclaim bar, computed versus the average volume
|
|
|
of prior post-ORB bars seen so far. E.g. 1.2 = reclaim bar volume must be at
|
|
|
least 20% above the earlier post-ORB average. None disables."""
|
|
|
|
|
|
vwap_reclaim_stop_mode: str = "atr"
|
|
|
"""Stop distance mode for vwap_reclaim_v1 entries.
|
|
|
'atr' = ATR × atr_stop_multiplier (default),
|
|
|
'vwap' = distance from entry_price to VWAP − buffer (structural floor stop)."""
|
|
|
|
|
|
vwap_reclaim_stop_vwap_buffer_pct: float = 0.002
|
|
|
"""Buffer below VWAP for vwap stop mode. 0.002 = stop at VWAP × (1 - 0.2%)."""
|
|
|
|
|
|
reclaim_entry_attention_enabled: bool = False
|
|
|
"""When True, require VWAP/reclaim-style fallback entries to show same-day
|
|
|
entry-bar participation relative to other timed candidates. The signal uses
|
|
|
percentile ranks, not absolute SIP-calibrated volume, so it is compatible
|
|
|
with IEX live intraday bars."""
|
|
|
|
|
|
reclaim_entry_attention_trigger_types: list[str] | None = None
|
|
|
"""Trigger types guarded by reclaim_entry_attention_enabled.
|
|
|
None defaults to ['vwap_reclaim', 'soft_day_vwap_reclaim']."""
|
|
|
|
|
|
reclaim_entry_attention_min_rank_pct: float | None = None
|
|
|
"""Minimum blended entry-participation percentile required for guarded
|
|
|
reclaim triggers. None disables the hard gate."""
|
|
|
|
|
|
reclaim_entry_attention_min_entry_rel_volume: float | None = None
|
|
|
"""Minimum entry-bar volume relative to earlier post-ORB bars for guarded
|
|
|
reclaim triggers. This is scale-free and can be used without SIP volume."""
|
|
|
|
|
|
reclaim_entry_attention_weight_entry_bar_dollar_vol: float = 0.45
|
|
|
"""Blend weight for the entry signal bar's dollar-volume percentile."""
|
|
|
|
|
|
reclaim_entry_attention_weight_cumulative_dollar_vol: float = 0.35
|
|
|
"""Blend weight for cumulative same-day dollar-volume percentile through
|
|
|
the entry signal bar."""
|
|
|
|
|
|
reclaim_entry_attention_weight_entry_rel_vol: float = 0.20
|
|
|
"""Blend weight for entry-bar relative volume versus earlier post-ORB bars."""
|
|
|
|
|
|
# ── No-fill fallback trigger ──
|
|
|
nofill_vwap_reclaim_enabled: bool = False
|
|
|
"""Enable a VWAP-reclaim fallback trigger for days/candidates that do not produce
|
|
|
a normal ORB/momentum entry. This is intended to attack traded_no_fill days
|
|
|
without changing the primary ORB signal path."""
|
|
|
|
|
|
nofill_vwap_reclaim_only_when_no_primary_entries: bool = True
|
|
|
"""When True, only add VWAP fallback candidates if the day has zero primary
|
|
|
ORB/momentum timed entries. This preserves existing normal trade days."""
|
|
|
|
|
|
nofill_vwap_reclaim_max_trades: int | None = 1
|
|
|
"""Maximum VWAP fallback trades per day. None disables the cap."""
|
|
|
|
|
|
nofill_vwap_reclaim_min_score_pct: float | None = 0.8
|
|
|
"""Minimum rank percentile for VWAP fallback candidates. 1.0 = top ranked."""
|
|
|
|
|
|
nofill_vwap_reclaim_size_scale: float = 0.25
|
|
|
"""Position-size multiplier applied only to VWAP fallback trades."""
|
|
|
|
|
|
# ── Late breakout fallback trigger ──
|
|
|
late_breakout_enabled: bool = False
|
|
|
"""Enable a late ORB-high/low breakout fallback after the normal order timeout.
|
|
|
|
|
|
This is a separate auxiliary engine for candidates that never filled the
|
|
|
primary ORB/momentum path. It waits for a later bar close through the opening
|
|
|
range with optional VWAP and volume confirmation, then enters at that close.
|
|
|
"""
|
|
|
|
|
|
late_breakout_only_when_no_primary_entries: bool = True
|
|
|
"""When True, add late-breakout candidates only if the day has zero primary
|
|
|
ORB/momentum timed entries."""
|
|
|
|
|
|
late_breakout_only_when_no_primary_trades: bool = False
|
|
|
"""When True, process late-breakout candidates after primary candidates and
|
|
|
reject them if any primary ORB/momentum trade was actually taken."""
|
|
|
|
|
|
late_breakout_only_when_no_existing_trades: bool = False
|
|
|
"""When True, reject late-breakout candidates if any earlier trade was
|
|
|
already taken that day. This makes the sleeve a true no-trade fallback."""
|
|
|
|
|
|
late_breakout_max_trades: int | None = 1
|
|
|
"""Maximum late-breakout fallback trades per day. None disables the cap."""
|
|
|
|
|
|
late_breakout_min_score_pct: float | None = 0.85
|
|
|
"""Minimum rank percentile for late-breakout fallback candidates."""
|
|
|
|
|
|
late_breakout_size_scale: float = 0.15
|
|
|
"""Position-size multiplier applied only to late-breakout fallback trades."""
|
|
|
|
|
|
late_breakout_sizing_floor_pct: float | None = None
|
|
|
"""Optional minimum sizing-capital floor for late-breakout fallback trades,
|
|
|
expressed as a fraction of the day's unscaled sizing capital.
|
|
|
|
|
|
This lets the auxiliary engine take a small controlled probe on heavily
|
|
|
scaled soft days where the normal day-level scaler would otherwise round
|
|
|
the position down to zero shares. None keeps the inherited day scaler only.
|
|
|
"""
|
|
|
|
|
|
late_breakout_window_start_min: int = 30
|
|
|
"""Earliest late-breakout evaluation time, minutes after market open."""
|
|
|
|
|
|
late_breakout_window_end_min: int = 150
|
|
|
"""Latest late-breakout evaluation time, minutes after market open."""
|
|
|
|
|
|
late_breakout_min_clearance_pct: float = 0.0
|
|
|
"""Minimum close-through clearance beyond the ORB breakout level."""
|
|
|
|
|
|
late_breakout_require_vwap_confirmation: bool = True
|
|
|
"""When True, late-breakout entries must also close on the correct side of
|
|
|
running session VWAP."""
|
|
|
|
|
|
late_breakout_confirm_rel_vol: float | None = 1.2
|
|
|
"""Minimum late-breakout bar volume relative to prior post-ORB average volume."""
|
|
|
|
|
|
# ── Soft-day auxiliary VWAP trigger ──
|
|
|
soft_day_vwap_reclaim_enabled: bool = False
|
|
|
"""Enable a separate VWAP-reclaim micro sleeve only on soft days. This sleeve
|
|
|
bypasses the named soft_day_setup_profile and uses its own stricter gates, so
|
|
|
former no-trade days can be probed without relaxing the primary ORB path."""
|
|
|
|
|
|
soft_day_vwap_reclaim_only_when_no_primary_entries: bool = False
|
|
|
"""When True, only add soft-day VWAP auxiliary candidates if the day has zero
|
|
|
primary ORB/momentum timed entries. This lets the auxiliary sleeve target
|
|
|
missed no-trade days without changing normal ORB allocation."""
|
|
|
|
|
|
soft_day_vwap_reclaim_only_when_no_primary_trades: bool = False
|
|
|
"""When True, process soft-day VWAP auxiliary candidates only after the primary
|
|
|
ORB/momentum path and reject them if any primary trade was actually taken.
|
|
|
This targets days that the normal ORB path ultimately did not trade."""
|
|
|
|
|
|
soft_day_vwap_reclaim_only_when_no_existing_trades: bool = False
|
|
|
"""When True, process soft-day VWAP auxiliary candidates only after all
|
|
|
non-soft-day-VWAP triggers and reject them if any trade was already taken.
|
|
|
This makes the sleeve a true no-trade-day fallback."""
|
|
|
|
|
|
soft_day_vwap_reclaim_max_trades: int | None = 1
|
|
|
"""Maximum soft-day auxiliary VWAP trades per day. None disables the cap."""
|
|
|
|
|
|
soft_day_vwap_reclaim_min_score_pct: float | None = 0.9
|
|
|
"""Minimum rank percentile for soft-day auxiliary VWAP candidates."""
|
|
|
|
|
|
soft_day_vwap_reclaim_min_score_pct_market_regime: float | None = None
|
|
|
"""Optional score floor override for market-regime-only soft-day VWAP probes."""
|
|
|
|
|
|
soft_day_vwap_reclaim_min_score_pct_breadth: float | None = None
|
|
|
"""Optional score floor override for breadth-only soft-day VWAP probes."""
|
|
|
|
|
|
soft_day_vwap_reclaim_min_score_pct_joint: float | None = None
|
|
|
"""Optional score floor override when both market regime and breadth are soft."""
|
|
|
|
|
|
soft_day_vwap_reclaim_min_score_pct_hard_breadth: float | None = None
|
|
|
"""Optional score floor override for hard-breadth fallback VWAP probes."""
|
|
|
|
|
|
soft_day_vwap_reclaim_allowed_reason_parts: list[str] | None = None
|
|
|
"""Optional allowlist of soft-day reason parts for auxiliary VWAP probes.
|
|
|
When set, at least one part of soft_day_reason must be present. Example:
|
|
|
['hard_breadth'] enables the sleeve only on hard-breadth fallback days."""
|
|
|
|
|
|
soft_day_vwap_reclaim_min_premarket_dollar_vol: float | None = 10_000_000.0
|
|
|
"""Minimum premarket dollar volume for soft-day auxiliary VWAP candidates."""
|
|
|
|
|
|
soft_day_vwap_reclaim_min_premarket_dollar_vol_market_regime: float | None = None
|
|
|
"""Optional premarket dollar-volume floor override for market-regime-only probes."""
|
|
|
|
|
|
soft_day_vwap_reclaim_min_premarket_dollar_vol_breadth: float | None = None
|
|
|
"""Optional premarket dollar-volume floor override for breadth-only probes."""
|
|
|
|
|
|
soft_day_vwap_reclaim_min_premarket_dollar_vol_joint: float | None = None
|
|
|
"""Optional premarket dollar-volume floor override when both regime and breadth are soft."""
|
|
|
|
|
|
soft_day_vwap_reclaim_min_premarket_dollar_vol_hard_breadth: float | None = None
|
|
|
"""Optional premarket dollar-volume floor override for hard-breadth fallback probes."""
|
|
|
|
|
|
soft_day_vwap_reclaim_min_ret_5d: float | None = None
|
|
|
"""Minimum prior 5-day return for soft-day auxiliary VWAP candidates."""
|
|
|
|
|
|
soft_day_vwap_reclaim_min_ret_5d_market_regime: float | None = None
|
|
|
"""Optional prior 5-day return floor override for market-regime-only probes."""
|
|
|
|
|
|
soft_day_vwap_reclaim_min_ret_5d_breadth: float | None = None
|
|
|
"""Optional prior 5-day return floor override for breadth-only probes."""
|
|
|
|
|
|
soft_day_vwap_reclaim_min_ret_5d_joint: float | None = None
|
|
|
"""Optional prior 5-day return floor override when both regime and breadth are soft."""
|
|
|
|
|
|
soft_day_vwap_reclaim_min_ret_5d_hard_breadth: float | None = None
|
|
|
"""Optional prior 5-day return floor override for hard-breadth fallback probes."""
|
|
|
|
|
|
soft_day_vwap_reclaim_gap_down_max_ret_5d: float | None = None
|
|
|
"""Reject soft-day VWAP probes that gap down after exceeding this prior 5-day return."""
|
|
|
|
|
|
soft_day_vwap_reclaim_weak_participation_max_rvol_rank_pct: float | None = None
|
|
|
"""Reject soft-day VWAP probes whose opening-range RVOL rank is at or below
|
|
|
this percentile when paired with a weak ORB body. This targets fallback
|
|
|
reclaims that are not participating strongly versus the same-day candidate
|
|
|
slate."""
|
|
|
|
|
|
soft_day_vwap_reclaim_weak_participation_max_body_ratio: float | None = None
|
|
|
"""Maximum ORB body ratio paired with
|
|
|
soft_day_vwap_reclaim_weak_participation_max_rvol_rank_pct."""
|
|
|
|
|
|
soft_day_vwap_reclaim_min_rvol: float | None = None
|
|
|
"""Minimum opening-range RVOL for soft-day auxiliary VWAP candidates."""
|
|
|
|
|
|
soft_day_vwap_reclaim_min_rvol_market_regime: float | None = None
|
|
|
"""Optional opening-range RVOL floor override for market-regime-only probes."""
|
|
|
|
|
|
soft_day_vwap_reclaim_min_rvol_breadth: float | None = None
|
|
|
"""Optional opening-range RVOL floor override for breadth-only probes."""
|
|
|
|
|
|
soft_day_vwap_reclaim_min_rvol_joint: float | None = None
|
|
|
"""Optional opening-range RVOL floor override when both regime and breadth are soft."""
|
|
|
|
|
|
soft_day_vwap_reclaim_min_rvol_hard_breadth: float | None = None
|
|
|
"""Optional opening-range RVOL floor override for hard-breadth fallback probes."""
|
|
|
|
|
|
soft_day_vwap_reclaim_max_rvol: float | None = 35.0
|
|
|
"""Maximum opening-range RVOL for soft-day auxiliary VWAP candidates."""
|
|
|
|
|
|
soft_day_vwap_reclaim_max_rvol_market_regime: float | None = None
|
|
|
"""Optional opening-range RVOL cap override for market-regime-only probes."""
|
|
|
|
|
|
soft_day_vwap_reclaim_max_rvol_breadth: float | None = None
|
|
|
"""Optional opening-range RVOL cap override for breadth-only probes."""
|
|
|
|
|
|
soft_day_vwap_reclaim_max_rvol_joint: float | None = None
|
|
|
"""Optional opening-range RVOL cap override when both regime and breadth are soft."""
|
|
|
|
|
|
soft_day_vwap_reclaim_max_rvol_hard_breadth: float | None = None
|
|
|
"""Optional opening-range RVOL cap override for hard-breadth fallback probes."""
|
|
|
|
|
|
soft_day_vwap_reclaim_min_body_ratio: float | None = None
|
|
|
"""Minimum ORB body ratio for soft-day auxiliary VWAP candidates."""
|
|
|
|
|
|
soft_day_vwap_reclaim_min_body_ratio_market_regime: float | None = None
|
|
|
"""Optional ORB body-ratio floor override for market-regime-only probes."""
|
|
|
|
|
|
soft_day_vwap_reclaim_min_body_ratio_breadth: float | None = None
|
|
|
"""Optional ORB body-ratio floor override for breadth-only probes."""
|
|
|
|
|
|
soft_day_vwap_reclaim_min_body_ratio_joint: float | None = None
|
|
|
"""Optional ORB body-ratio floor override when both regime and breadth are soft."""
|
|
|
|
|
|
soft_day_vwap_reclaim_min_body_ratio_hard_breadth: float | None = None
|
|
|
"""Optional ORB body-ratio floor override for hard-breadth fallback probes."""
|
|
|
|
|
|
soft_day_vwap_reclaim_min_close_location: float | None = None
|
|
|
"""Minimum ORB close location for soft-day auxiliary VWAP candidates."""
|
|
|
|
|
|
soft_day_vwap_reclaim_min_close_location_market_regime: float | None = None
|
|
|
"""Optional ORB close-location floor override for market-regime-only probes."""
|
|
|
|
|
|
soft_day_vwap_reclaim_min_close_location_breadth: float | None = None
|
|
|
"""Optional ORB close-location floor override for breadth-only probes."""
|
|
|
|
|
|
soft_day_vwap_reclaim_min_close_location_joint: float | None = None
|
|
|
"""Optional ORB close-location floor override when both regime and breadth are soft."""
|
|
|
|
|
|
soft_day_vwap_reclaim_min_close_location_hard_breadth: float | None = None
|
|
|
"""Optional ORB close-location floor override for hard-breadth fallback probes."""
|
|
|
|
|
|
soft_day_vwap_reclaim_size_scale: float = 0.05
|
|
|
"""Additional position-size multiplier applied only to soft-day auxiliary
|
|
|
VWAP trades after the day-level soft-day scaler."""
|
|
|
|
|
|
soft_day_vwap_reclaim_size_scale_market_regime: float | None = None
|
|
|
"""Optional size-scale override for market-regime-only soft-day VWAP probes."""
|
|
|
|
|
|
soft_day_vwap_reclaim_size_scale_breadth: float | None = None
|
|
|
"""Optional size-scale override for breadth-only soft-day VWAP probes."""
|
|
|
|
|
|
soft_day_vwap_reclaim_size_scale_joint: float | None = None
|
|
|
"""Optional size-scale override when both market regime and breadth are soft."""
|
|
|
|
|
|
soft_day_vwap_reclaim_size_scale_hard_breadth: float | None = None
|
|
|
"""Optional size-scale override for hard-breadth fallback VWAP probes."""
|
|
|
|
|
|
# ── Broad gap-up continuation engine ──
|
|
|
broad_gapup_continuation_enabled: bool = False
|
|
|
"""Enable a bounded high-gap continuation sleeve.
|
|
|
|
|
|
This does not relax the normal ORB max_gap_pct globally. It only admits
|
|
|
positive-gap candidates that exceed max_gap_pct when they pass separate
|
|
|
liquidity, opening-structure, and prior-trend gates.
|
|
|
"""
|
|
|
|
|
|
broad_gapup_continuation_min_gap_pct: float | None = None
|
|
|
"""Minimum positive opening gap required for the high-gap continuation sleeve."""
|
|
|
|
|
|
broad_gapup_continuation_max_gap_pct: float | None = None
|
|
|
"""Maximum positive opening gap accepted by the high-gap continuation sleeve."""
|
|
|
|
|
|
broad_gapup_continuation_min_rvol: float | None = None
|
|
|
"""Minimum opening-range RVOL required for high-gap continuation candidates."""
|
|
|
|
|
|
broad_gapup_continuation_min_premarket_dollar_vol: float | None = None
|
|
|
"""Minimum premarket dollar volume required for high-gap continuation candidates."""
|
|
|
|
|
|
broad_gapup_continuation_min_first_bar_dollar_vol: float | None = None
|
|
|
"""Minimum opening-range dollar volume required for high-gap continuation candidates."""
|
|
|
|
|
|
broad_gapup_continuation_min_avg_dollar_vol: float | None = None
|
|
|
"""Minimum 30-day average dollar volume required for high-gap continuation candidates."""
|
|
|
|
|
|
broad_gapup_continuation_min_body_ratio: float | None = None
|
|
|
"""Minimum ORB candle body ratio required for high-gap continuation candidates."""
|
|
|
|
|
|
broad_gapup_continuation_min_close_location: float | None = None
|
|
|
"""Minimum ORB close location required for high-gap continuation candidates."""
|
|
|
|
|
|
broad_gapup_continuation_min_ret_5d: float | None = None
|
|
|
"""Minimum prior 5-day return required for high-gap continuation candidates."""
|
|
|
|
|
|
broad_gapup_continuation_max_ret_5d: float | None = None
|
|
|
"""Maximum prior 5-day return allowed for high-gap continuation candidates."""
|
|
|
|
|
|
broad_gapup_continuation_max_gap_zscore_20d: float | None = None
|
|
|
"""Maximum 20-day gap z-score allowed for high-gap continuation candidates."""
|
|
|
|
|
|
broad_gapup_continuation_max_candidates: int | None = None
|
|
|
"""Maximum selected candidates from the high-gap continuation sleeve per day."""
|
|
|
|
|
|
broad_gapup_continuation_max_trades: int | None = None
|
|
|
"""Maximum filled trades from the high-gap continuation sleeve per day."""
|
|
|
|
|
|
broad_gapup_continuation_only_when_no_primary_entries: bool = False
|
|
|
"""Only use high-gap continuation when no normal primary ORB entry is available."""
|
|
|
|
|
|
broad_gapup_continuation_only_when_no_primary_trades: bool = False
|
|
|
"""Skip high-gap continuation after any normal primary ORB trade fills."""
|
|
|
|
|
|
broad_gapup_continuation_min_score_pct: float | None = None
|
|
|
"""Minimum selected-candidate rank percentile for high-gap continuation trades."""
|
|
|
|
|
|
broad_gapup_continuation_size_scale: float = 0.25
|
|
|
"""Position-size multiplier applied only to high-gap continuation trades."""
|
|
|
|
|
|
broad_gapup_continuation_fixed_loss_pct: float | None = None
|
|
|
"""Optional fixed stop distance for high-gap continuation trades.
|
|
|
Example: 0.015 uses a 1.5% fixed loss cap if tighter than the ATR stop.
|
|
|
"""
|
|
|
|
|
|
broad_gapup_continuation_entry_mode: str = "breakout"
|
|
|
"""Entry mode for high-gap continuation candidates.
|
|
|
'breakout' keeps the original immediate ORB-breakout path; 'late_breakout'
|
|
|
waits for a later close-confirmed ORB reclaim using the late_breakout_* gates;
|
|
|
'vwap_reclaim' waits for the vwap_reclaim_* recovery path.
|
|
|
"""
|
|
|
|
|
|
# ── Market-thrust liquid continuation engine ──
|
|
|
market_thrust_liquid_continuation_enabled: bool = False
|
|
|
"""Enable a separate liquid normal-gap continuation sleeve on strong
|
|
|
SPY/QQQ opening-thrust days. The sleeve is activated by the day-level
|
|
|
market_thrust_breadth_override signal and does not relax RVOL globally."""
|
|
|
|
|
|
market_thrust_liquid_continuation_require_market_thrust: bool = True
|
|
|
"""Require the day-level market-thrust signal before scanning this sleeve.
|
|
|
Set False only for stock-specific liquid leader impulse probes that should
|
|
|
be independent of the index/opening-breadth thrust gate.
|
|
|
"""
|
|
|
|
|
|
market_thrust_liquid_continuation_only_when_no_primary_entries: bool = False
|
|
|
"""Only keep liquid-continuation entries when no normal primary entry exists.
|
|
|
This lets the sleeve act as a no-trade-day repair path instead of competing
|
|
|
with the baseline ORB book.
|
|
|
"""
|
|
|
|
|
|
market_thrust_liquid_continuation_only_when_no_primary_trades: bool = False
|
|
|
"""Skip liquid-continuation fills after any normal primary trade has filled."""
|
|
|
|
|
|
market_thrust_liquid_continuation_no_thrust_min_gap_pct: float | None = None
|
|
|
"""Optional gap floor used only by the independent no-thrust repair scan."""
|
|
|
|
|
|
market_thrust_liquid_continuation_no_thrust_max_gap_pct: float | None = None
|
|
|
"""Optional gap ceiling used only by the independent no-thrust repair scan."""
|
|
|
|
|
|
market_thrust_liquid_continuation_no_thrust_min_first_bar_return_pct: float | None = None
|
|
|
"""Optional first-bar return floor used only by the no-thrust repair scan."""
|
|
|
|
|
|
market_thrust_liquid_continuation_no_thrust_min_first_bar_dollar_vol: float | None = None
|
|
|
"""Optional first-bar dollar-volume floor used only by the no-thrust repair scan."""
|
|
|
|
|
|
market_thrust_liquid_continuation_no_thrust_min_avg_dollar_vol: float | None = None
|
|
|
"""Optional prior ADV floor used only by the independent no-thrust repair scan."""
|
|
|
|
|
|
market_thrust_liquid_continuation_no_thrust_max_candidates: int | None = None
|
|
|
"""Optional candidate cap used only by the independent no-thrust repair scan."""
|
|
|
|
|
|
market_thrust_liquid_continuation_no_thrust_max_trades: int | None = None
|
|
|
"""Optional trade cap used only by independent no-thrust liquid repair fills."""
|
|
|
|
|
|
market_thrust_liquid_continuation_no_thrust_size_scale: float | None = None
|
|
|
"""Optional position-size multiplier for independent no-thrust repair fills."""
|
|
|
|
|
|
market_thrust_liquid_continuation_no_thrust_rank_mode: str | None = None
|
|
|
"""Optional candidate ranking mode for independent no-thrust repair scans.
|
|
|
None preserves the normal ORB score order. 'opening_impulse' ranks by
|
|
|
first-bar return, then first-bar dollar volume.
|
|
|
"""
|
|
|
|
|
|
market_thrust_liquid_continuation_no_thrust_priority_min_first_bar_return_pct: float | None = None
|
|
|
"""Optional priority tier for independent no-thrust repair scans.
|
|
|
Candidates at or above this first-bar return are ranked ahead of lower-
|
|
|
impulse repair candidates, while preserving normal score order within each
|
|
|
tier.
|
|
|
"""
|
|
|
|
|
|
market_thrust_liquid_continuation_min_gap_pct: float | None = None
|
|
|
"""Minimum positive opening gap required for market-thrust liquid candidates."""
|
|
|
|
|
|
market_thrust_liquid_continuation_max_gap_pct: float | None = None
|
|
|
"""Maximum positive opening gap accepted by the market-thrust liquid sleeve."""
|
|
|
|
|
|
market_thrust_liquid_continuation_min_rvol: float | None = None
|
|
|
"""Optional opening-range RVOL floor for market-thrust liquid candidates."""
|
|
|
|
|
|
market_thrust_liquid_continuation_min_first_bar_return_pct: float | None = None
|
|
|
"""Minimum first ORB bar open-to-close return for market-thrust liquid candidates."""
|
|
|
|
|
|
market_thrust_liquid_continuation_min_first_bar_dollar_vol: float | None = None
|
|
|
"""Minimum opening-range dollar volume for market-thrust liquid candidates."""
|
|
|
|
|
|
market_thrust_liquid_continuation_min_avg_dollar_vol: float | None = None
|
|
|
"""Minimum 30-day average dollar volume for market-thrust liquid candidates."""
|
|
|
|
|
|
market_thrust_liquid_continuation_min_body_ratio: float | None = None
|
|
|
"""Minimum ORB candle body ratio for market-thrust liquid candidates."""
|
|
|
|
|
|
market_thrust_liquid_continuation_min_close_location: float | None = None
|
|
|
"""Minimum ORB close location for market-thrust liquid candidates."""
|
|
|
|
|
|
market_thrust_liquid_continuation_min_ret_5d: float | None = None
|
|
|
"""Minimum prior 5-day return for market-thrust liquid candidates."""
|
|
|
|
|
|
market_thrust_liquid_continuation_max_ret_5d: float | None = None
|
|
|
"""Maximum prior 5-day return for market-thrust liquid candidates."""
|
|
|
|
|
|
market_thrust_liquid_continuation_max_candidates: int | None = None
|
|
|
"""Maximum selected candidates from the market-thrust liquid sleeve per day."""
|
|
|
|
|
|
market_thrust_liquid_continuation_max_trades: int | None = None
|
|
|
"""Maximum filled trades from the market-thrust liquid sleeve per day."""
|
|
|
|
|
|
market_thrust_liquid_continuation_entry_mode: str = "breakout"
|
|
|
"""Entry mode for market-thrust liquid candidates.
|
|
|
'breakout' waits for a normal ORB breakout; 'opening_burst' enters on the
|
|
|
first post-ORB bar open after the ORB candle has closed; 'opening_followthrough'
|
|
|
waits for the first post-ORB bar to confirm continuation and enters at the
|
|
|
next bar open.
|
|
|
"""
|
|
|
|
|
|
market_thrust_liquid_continuation_followthrough_min_return_pct: float | None = 0.0
|
|
|
"""Minimum first post-ORB bar open-to-close return for opening_followthrough mode."""
|
|
|
|
|
|
market_thrust_liquid_continuation_followthrough_min_close_location: float | None = 0.5
|
|
|
"""Minimum first post-ORB bar close location for opening_followthrough mode."""
|
|
|
|
|
|
market_thrust_liquid_continuation_followthrough_require_orb_breakout: bool = True
|
|
|
"""Require first post-ORB bar to close through the ORB breakout level before entry."""
|
|
|
|
|
|
market_thrust_liquid_continuation_min_score_pct: float | None = None
|
|
|
"""Minimum selected-candidate rank percentile for market-thrust liquid trades."""
|
|
|
|
|
|
market_thrust_liquid_continuation_size_scale: float = 0.20
|
|
|
"""Position-size multiplier applied only to market-thrust liquid trades."""
|
|
|
|
|
|
market_thrust_liquid_continuation_fixed_loss_pct: float | None = None
|
|
|
"""Optional fixed stop distance for market-thrust liquid trades."""
|
|
|
|
|
|
# ── Market-thrust opening impulse reclaim engine ──
|
|
|
market_thrust_opening_impulse_reclaim_enabled: bool = False
|
|
|
"""Enable a market-thrust auxiliary sleeve for stocks with small/negative
|
|
|
opening gaps but a strong first ORB-bar impulse. The sleeve is meant to catch
|
|
|
broad-market up days where leaders emerge after the open rather than via gap.
|
|
|
"""
|
|
|
|
|
|
market_thrust_opening_impulse_reclaim_require_market_thrust: bool = True
|
|
|
"""Require the day-level market-thrust signal before scanning this sleeve.
|
|
|
Set False only for independent no-thrust repair probes on no-primary days."""
|
|
|
|
|
|
market_thrust_opening_impulse_reclaim_only_when_no_primary_entries: bool = False
|
|
|
"""Only keep no-thrust impulse-reclaim entries when no normal primary entry exists."""
|
|
|
|
|
|
market_thrust_opening_impulse_reclaim_only_when_no_primary_trades: bool = False
|
|
|
"""Skip no-thrust impulse-reclaim fills after any normal primary trade has filled."""
|
|
|
|
|
|
market_thrust_opening_impulse_reclaim_no_thrust_min_gap_pct: float | None = None
|
|
|
"""Optional gap floor used only by the independent no-thrust impulse scan."""
|
|
|
|
|
|
market_thrust_opening_impulse_reclaim_no_thrust_max_gap_pct: float | None = None
|
|
|
"""Optional gap ceiling used only by the independent no-thrust impulse scan."""
|
|
|
|
|
|
market_thrust_opening_impulse_reclaim_no_thrust_min_first_bar_return_pct: float | None = None
|
|
|
"""Optional first-bar return floor used only by the no-thrust impulse scan."""
|
|
|
|
|
|
market_thrust_opening_impulse_reclaim_no_thrust_min_first_bar_dollar_vol: float | None = None
|
|
|
"""Optional first-bar dollar-volume floor used only by the no-thrust impulse scan."""
|
|
|
|
|
|
market_thrust_opening_impulse_reclaim_no_thrust_min_avg_dollar_vol: float | None = None
|
|
|
"""Optional prior ADV floor used only by the no-thrust impulse scan."""
|
|
|
|
|
|
market_thrust_opening_impulse_reclaim_no_thrust_min_body_ratio: float | None = None
|
|
|
"""Optional ORB-body floor used only by the no-thrust impulse scan."""
|
|
|
|
|
|
market_thrust_opening_impulse_reclaim_no_thrust_min_close_location: float | None = None
|
|
|
"""Optional ORB close-location floor used only by the no-thrust impulse scan."""
|
|
|
|
|
|
market_thrust_opening_impulse_reclaim_no_thrust_min_ret_5d: float | None = None
|
|
|
"""Optional prior 5-day return floor used only by the no-thrust impulse scan."""
|
|
|
|
|
|
market_thrust_opening_impulse_reclaim_no_thrust_max_ret_5d: float | None = None
|
|
|
"""Optional prior 5-day return ceiling used only by the no-thrust impulse scan."""
|
|
|
|
|
|
market_thrust_opening_impulse_reclaim_no_thrust_max_candidates: int | None = None
|
|
|
"""Optional candidate cap used only by the independent no-thrust impulse scan."""
|
|
|
|
|
|
market_thrust_opening_impulse_reclaim_no_thrust_max_trades: int | None = None
|
|
|
"""Optional trade cap used only by independent no-thrust impulse fills."""
|
|
|
|
|
|
market_thrust_opening_impulse_reclaim_no_thrust_size_scale: float | None = None
|
|
|
"""Optional position-size multiplier for independent no-thrust impulse fills."""
|
|
|
|
|
|
market_thrust_opening_impulse_reclaim_min_gap_pct: float | None = None
|
|
|
"""Minimum opening gap accepted by the impulse-reclaim sleeve."""
|
|
|
|
|
|
market_thrust_opening_impulse_reclaim_max_gap_pct: float | None = None
|
|
|
"""Maximum opening gap accepted by the impulse-reclaim sleeve."""
|
|
|
|
|
|
market_thrust_opening_impulse_reclaim_min_first_bar_return_pct: float | None = None
|
|
|
"""Minimum first ORB bar open-to-close return for impulse-reclaim candidates."""
|
|
|
|
|
|
market_thrust_opening_impulse_reclaim_min_first_bar_dollar_vol: float | None = None
|
|
|
"""Minimum opening-range dollar volume for impulse-reclaim candidates."""
|
|
|
|
|
|
market_thrust_opening_impulse_reclaim_min_avg_dollar_vol: float | None = None
|
|
|
"""Minimum 30-day average dollar volume for impulse-reclaim candidates."""
|
|
|
|
|
|
market_thrust_opening_impulse_reclaim_min_body_ratio: float | None = None
|
|
|
"""Minimum ORB candle body ratio for impulse-reclaim candidates."""
|
|
|
|
|
|
market_thrust_opening_impulse_reclaim_min_close_location: float | None = None
|
|
|
"""Minimum ORB close location for impulse-reclaim candidates."""
|
|
|
|
|
|
market_thrust_opening_impulse_reclaim_min_ret_5d: float | None = None
|
|
|
"""Minimum prior 5-day return for impulse-reclaim candidates."""
|
|
|
|
|
|
market_thrust_opening_impulse_reclaim_max_ret_5d: float | None = None
|
|
|
"""Maximum prior 5-day return for impulse-reclaim candidates."""
|
|
|
|
|
|
market_thrust_opening_impulse_reclaim_max_candidates: int | None = None
|
|
|
"""Maximum selected candidates from the impulse-reclaim sleeve per day."""
|
|
|
|
|
|
market_thrust_opening_impulse_reclaim_max_trades: int | None = None
|
|
|
"""Maximum filled trades from the impulse-reclaim sleeve per day."""
|
|
|
|
|
|
market_thrust_opening_impulse_reclaim_entry_mode: str = "vwap_reclaim"
|
|
|
"""Entry mode for impulse-reclaim candidates.
|
|
|
'vwap_reclaim' waits for a later VWAP reclaim; 'late_breakout' waits for a
|
|
|
close-confirmed ORB-high reclaim; 'opening_burst' enters on the first post-ORB open.
|
|
|
"""
|
|
|
|
|
|
market_thrust_opening_impulse_reclaim_min_score_pct: float | None = None
|
|
|
"""Minimum selected-candidate rank percentile for impulse-reclaim trades."""
|
|
|
|
|
|
market_thrust_opening_impulse_reclaim_size_scale: float = 0.10
|
|
|
"""Position-size multiplier applied only to impulse-reclaim trades."""
|
|
|
|
|
|
market_thrust_opening_impulse_reclaim_fixed_loss_pct: float | None = None
|
|
|
"""Optional fixed stop distance for impulse-reclaim trades."""
|
|
|
|
|
|
# ── Intraday continuation reclaim engine ──
|
|
|
intraday_continuation_reclaim_enabled: bool = False
|
|
|
"""Enable a late-morning continuation sleeve for stocks that were not clean
|
|
|
opening-gap ORB candidates but became intraday top gainers after the first
|
|
|
30-60 minutes. The sleeve uses only bars available by its signal time and
|
|
|
enters at the next bar open."""
|
|
|
|
|
|
intraday_continuation_reclaim_signal_minutes: int = 30
|
|
|
"""Number of minutes after the open used to confirm continuation strength."""
|
|
|
|
|
|
intraday_continuation_reclaim_min_signal_return_pct: float | None = None
|
|
|
"""Minimum open-to-signal close return required for continuation candidates."""
|
|
|
|
|
|
intraday_continuation_reclaim_min_signal_dollar_vol: float | None = None
|
|
|
"""Minimum cumulative dollar volume through the signal window."""
|
|
|
|
|
|
intraday_continuation_reclaim_min_signal_close_location: float | None = None
|
|
|
"""Minimum signal-window close location inside the window high-low range."""
|
|
|
|
|
|
intraday_continuation_reclaim_require_signal_above_vwap: bool = True
|
|
|
"""Require the signal-window close to be above running session VWAP."""
|
|
|
|
|
|
intraday_continuation_reclaim_min_first_bar_return_pct: float | None = None
|
|
|
"""Optional minimum first ORB bar open-to-close return."""
|
|
|
|
|
|
intraday_continuation_reclaim_min_first_bar_dollar_vol: float | None = None
|
|
|
"""Optional minimum first ORB bar dollar volume."""
|
|
|
|
|
|
intraday_continuation_reclaim_min_gap_pct: float | None = None
|
|
|
"""Minimum opening gap accepted by the intraday continuation sleeve."""
|
|
|
|
|
|
intraday_continuation_reclaim_max_gap_pct: float | None = None
|
|
|
"""Maximum opening gap accepted by the intraday continuation sleeve."""
|
|
|
|
|
|
intraday_continuation_reclaim_min_avg_dollar_vol: float | None = None
|
|
|
"""Minimum 30-day average dollar volume for continuation candidates."""
|
|
|
|
|
|
intraday_continuation_reclaim_min_atr_pct: float | None = None
|
|
|
"""Optional ATR/previous-close floor for continuation candidates.
|
|
|
This is separate from the base ORB min_atr_pct so the continuation sleeve can
|
|
|
explicitly admit low-volatility liquid leaders without weakening primary ORB.
|
|
|
"""
|
|
|
|
|
|
intraday_continuation_reclaim_max_atr_pct: float | None = None
|
|
|
"""Optional ATR/previous-close ceiling for continuation candidates."""
|
|
|
|
|
|
intraday_continuation_reclaim_min_ret_5d: float | None = None
|
|
|
"""Minimum prior 5-day return for continuation candidates."""
|
|
|
|
|
|
intraday_continuation_reclaim_max_ret_5d: float | None = None
|
|
|
"""Maximum prior 5-day return for continuation candidates."""
|
|
|
|
|
|
intraday_continuation_reclaim_max_gap_zscore_20d: float | None = None
|
|
|
"""Maximum 20-day opening gap z-score allowed for continuation candidates."""
|
|
|
|
|
|
intraday_continuation_reclaim_max_candidates: int | None = None
|
|
|
"""Maximum selected candidates from the continuation sleeve per day."""
|
|
|
|
|
|
intraday_continuation_reclaim_min_cluster_count: int | None = None
|
|
|
"""Minimum number of continuation-qualified names required before the
|
|
|
sleeve can trade. This avoids reacting to a single isolated mover on weak
|
|
|
market days."""
|
|
|
|
|
|
intraday_continuation_reclaim_min_cluster_avg_signal_return_pct: float | None = None
|
|
|
"""Minimum average open-to-signal return across all continuation-qualified
|
|
|
names before candidate caps are applied."""
|
|
|
|
|
|
intraday_continuation_reclaim_min_cluster_total_signal_dollar_vol: float | None = None
|
|
|
"""Minimum combined signal-window dollar volume across continuation-
|
|
|
qualified names before candidate caps are applied."""
|
|
|
|
|
|
intraday_continuation_reclaim_max_trades: int | None = None
|
|
|
"""Maximum filled trades from the continuation sleeve per day."""
|
|
|
|
|
|
intraday_continuation_reclaim_only_when_no_primary_trades: bool = True
|
|
|
"""Skip continuation trades after any normal primary ORB trade has filled."""
|
|
|
|
|
|
intraday_continuation_reclaim_min_score_pct: float | None = None
|
|
|
"""Minimum selected-candidate rank percentile for continuation trades."""
|
|
|
|
|
|
intraday_continuation_reclaim_size_scale: float = 0.05
|
|
|
"""Position-size multiplier applied only to continuation-reclaim trades."""
|
|
|
|
|
|
intraday_continuation_reclaim_fixed_loss_pct: float | None = None
|
|
|
"""Optional fixed stop distance for continuation-reclaim trades."""
|
|
|
|
|
|
# ── Profit target ──
|
|
|
profit_target_r: float | None = None
|
|
|
"""Exit at market when R-multiple reaches this level. None = disabled.
|
|
|
E.g. 3.0 = exit when trade reaches 3R profit. Locks in gains before
|
|
|
trailing stop gives back profits."""
|
|
|
|
|
|
# ── Fixed dollar exits ──
|
|
|
fixed_profit_dollars: float | None = None
|
|
|
"""Exit when trade P&L reaches this profit in dollars. Overrides ATR-based profit target. None = disabled."""
|
|
|
|
|
|
fixed_loss_dollars: float | None = None
|
|
|
"""Exit when trade loss reaches this amount in dollars (positive = max loss allowed). Overrides ATR stop. None = disabled."""
|
|
|
|
|
|
fixed_loss_pct: float | None = None
|
|
|
"""Exit when trade loss reaches this fraction of entry price.
|
|
|
Example: 0.02 uses a 2% fixed stop from entry. Overrides ATR stop when
|
|
|
tighter than the ATR stop. None = disabled."""
|
|
|
|
|
|
fixed_loss_preserve_trailing: bool = False
|
|
|
"""When fixed_loss_pct/fixed_loss_dollars is set, keep breakeven and
|
|
|
trailing-stop management active instead of treating the fixed stop as the
|
|
|
only active stop path. Defaults to False to preserve existing strategy
|
|
|
behavior."""
|
|
|
|
|
|
# ── ORB range quality filter ──
|
|
|
orb_range_atr_min: float | None = None
|
|
|
"""Minimum ORB candle range as fraction of ATR(14). None = disabled.
|
|
|
Filters stocks with too-narrow opening ranges (likely noise).
|
|
|
E.g. 0.3 = ORB range must be at least 30% of ATR."""
|
|
|
|
|
|
orb_range_atr_max: float | None = None
|
|
|
"""Maximum ORB candle range as fraction of ATR(14). None = disabled.
|
|
|
Filters stocks whose opening range already consumed the day's move.
|
|
|
E.g. 1.5 = ORB range must be at most 150% of ATR."""
|
|
|
|
|
|
# ── SPY intraday guard ──
|
|
|
spy_intraday_guard_pct: float | None = None
|
|
|
"""Tighten trailing stop when SPY drops this % from its open intraday. None = disabled.
|
|
|
E.g. -0.005 = if SPY drops 0.5% from open, tighten trail.
|
|
|
Applied during Phase 2 exit management."""
|
|
|
|
|
|
spy_intraday_guard_tighten: float = 0.5
|
|
|
"""Factor to multiply trailing ATR multiplier when SPY guard triggers.
|
|
|
0.5 = trail becomes 50% tighter (e.g., 0.8 ATR → 0.4 ATR)."""
|
|
|
|
|
|
# ── Single-trade loss cap ──
|
|
|
single_trade_loss_cap_pct: float | None = None
|
|
|
"""Maximum loss allowed from a single trade as a fraction of initial_capital.
|
|
|
None = disabled (default; streak/governor boosts apply without loss ceiling).
|
|
|
When set, sizing_capital is clamped after all boosts (drawdown governor, streak,
|
|
|
rolling WR) so that risk_per_trade_pct × sizing_capital ≤ this cap × initial_capital.
|
|
|
E.g. 0.05 with risk_per_trade_pct=0.05 → max single-trade risk = $500 on $10k initial,
|
|
|
regardless of streak multiplier.
|
|
|
Fixes the structural misalignment where streak_sizing_max=2.5 allows a single -1R
|
|
|
trade to exceed daily_max_loss_pct when both are computed on different capital bases."""
|
|
|
|
|
|
single_trade_loss_cap_basis: str = "initial"
|
|
|
"""Capital base for single_trade_loss_cap_pct: 'initial' or 'equity'.
|
|
|
'initial' preserves historical behavior and caps loss against initial_capital.
|
|
|
'equity' is intended for compound_returns=True strategies where the cap should
|
|
|
grow and shrink with account equity instead of suppressing compounding after
|
|
|
the account has grown."""
|
|
|
|
|
|
# ── Dual-trigger momentum path (hybrid) ──
|
|
|
dual_trigger_enabled: bool = False
|
|
|
"""Enable momentum-confirmation as an alternate entry trigger alongside ORB breakout.
|
|
|
When True, for each candidate compute both the ORB breakout time and the 09:45
|
|
|
momentum confirmation time; use whichever fires first within the entry window.
|
|
|
Post-entry management (ATR stop, BE, trail, tighten) is identical regardless of
|
|
|
which trigger fired. min_breakout_rel_vol gate applies only on the ORB path."""
|
|
|
|
|
|
momo_entry_minutes_after_open: int = 10
|
|
|
"""Minutes after market open for the first momentum evaluation bar (09:40 close)."""
|
|
|
|
|
|
momo_confirmation_minutes_after_entry: int = 5
|
|
|
"""Minutes after momo_entry_minutes_after_open for the confirmation close (09:45)."""
|
|
|
|
|
|
momo_confirm_window_minutes: int = 55
|
|
|
"""Latest allowable momentum confirm, measured as minutes after ORB end (09:35).
|
|
|
09:35 + 55 min = 10:30 ET. Confirmation signals after this are ignored."""
|
|
|
|
|
|
momo_min_confirmation_return_pct: float = 0.005
|
|
|
"""Minimum return from 09:40 close to 09:45 close to count as momentum confirmed."""
|
|
|
|
|
|
momo_min_morning_gain_pct: float = 0.015
|
|
|
"""Minimum gain from open to 09:45 close for momentum confirm trigger."""
|
|
|
|
|
|
momo_max_morning_gain_pct: float = 0.06
|
|
|
"""Maximum gain from open to 09:45 close (rejects over-extended names)."""
|
|
|
|
|
|
# ── Candidate overlay (Leader + Liquid) for ORB path ──
|
|
|
candidate_seed_leader_overlay_slots: int = 0
|
|
|
"""Number of Leader overlay slots appended after ORB pre-screen.
|
|
|
Leader overlay targets trend leaders with high ret_5d + low entropy + high ATR,
|
|
|
even when their opening gap is muted (captured via momo confirm trigger).
|
|
|
0 = disabled."""
|
|
|
|
|
|
candidate_seed_leader_min_gap_pct: float | None = None
|
|
|
"""Minimum gap for Leader overlay candidates (can be negative for flat-open leaders)."""
|
|
|
|
|
|
candidate_seed_leader_max_gap_pct: float | None = None
|
|
|
"""Maximum gap for Leader overlay candidates."""
|
|
|
|
|
|
candidate_seed_leader_min_avg_dollar_vol_30d: float | None = None
|
|
|
"""Minimum 30-day avg dollar volume for Leader overlay."""
|
|
|
|
|
|
candidate_seed_leader_min_ret_5d: float | None = None
|
|
|
"""Minimum 5-day prior return for Leader overlay (e.g. 0.15 = +15%)."""
|
|
|
|
|
|
candidate_seed_leader_min_atr_pct: float | None = None
|
|
|
"""Minimum ATR/open for Leader overlay (e.g. 0.06 = 6%)."""
|
|
|
|
|
|
candidate_seed_leader_max_entropy_20d: float | None = None
|
|
|
"""Maximum entropy_20d for Leader overlay (e.g. 0.75)."""
|
|
|
|
|
|
candidate_seed_liquid_overlay_slots: int = 0
|
|
|
"""Number of Liquid overlay slots appended after ORB pre-screen.
|
|
|
Targets highly liquid names with moderate gaps that ORB pre-screen may miss.
|
|
|
0 = disabled."""
|
|
|
|
|
|
candidate_seed_liquid_min_gap_pct: float | None = None
|
|
|
"""Minimum gap for Liquid overlay candidates."""
|
|
|
|
|
|
candidate_seed_liquid_max_gap_pct: float | None = None
|
|
|
"""Maximum gap for Liquid overlay candidates."""
|
|
|
|
|
|
candidate_seed_liquid_min_avg_dollar_vol_30d: float | None = None
|
|
|
"""Minimum 30-day avg dollar volume for Liquid overlay."""
|
|
|
|
|
|
candidate_seed_liquid_min_ret_5d: float | None = None
|
|
|
"""Minimum 5-day prior return for Liquid overlay."""
|
|
|
|
|
|
candidate_seed_liquid_max_entropy_20d: float | None = None
|
|
|
"""Maximum entropy_20d for Liquid overlay."""
|
|
|
|
|
|
candidate_seed_ownership_overlay_slots: int = 0
|
|
|
"""Number of PIT-safe 13D/13G ownership-event names to add after ORB pre-screen.
|
|
|
|
|
|
The overlay widens the intraday fetch set for ownership-shock names without
|
|
|
changing the default strategy path. Candidates still need to pass ORB candle
|
|
|
and execution/risk checks before trading.
|
|
|
"""
|
|
|
|
|
|
candidate_seed_ownership_initial_only: bool = True
|
|
|
"""When True, ownership seed overlay only accepts initial-owner filings."""
|
|
|
|
|
|
candidate_seed_ownership_min_strength_score: float | None = None
|
|
|
"""Minimum ownership_strength_score required for ownership seed overlay."""
|
|
|
|
|
|
candidate_seed_ownership_min_gap_pct: float | None = None
|
|
|
"""Minimum opening gap for ownership-overlay names."""
|
|
|
|
|
|
candidate_seed_ownership_max_gap_pct: float | None = None
|
|
|
"""Maximum opening gap for ownership-overlay names."""
|
|
|
|
|
|
candidate_seed_ownership_min_avg_dollar_vol_30d: float | None = None
|
|
|
"""Minimum prior 30-day dollar volume for ownership-overlay names."""
|
|
|
|
|
|
candidate_seed_ownership_min_ret_5d: float | None = None
|
|
|
"""Minimum prior 5-day return for ownership-overlay names."""
|
|
|
|
|
|
candidate_seed_ownership_max_entropy_20d: float | None = None
|
|
|
"""Maximum prior 20-day entropy for ownership-overlay names."""
|
|
|
|
|
|
candidate_seed_form4_overlay_slots: int = 0
|
|
|
"""Number of PIT-safe Form 4 insider-buy names to add after ORB pre-screen.
|
|
|
|
|
|
This widens the intraday fetch set for insider-supported names without
|
|
|
changing the normal daily pre-screen. Final ORB structure and risk gates
|
|
|
still decide whether a trade is taken.
|
|
|
"""
|
|
|
|
|
|
candidate_seed_form4_min_total_value: float | None = None
|
|
|
"""Minimum aggregated recent Form 4 open-market purchase value."""
|
|
|
|
|
|
candidate_seed_form4_min_owner_count: int | None = None
|
|
|
"""Minimum unique owner count for Form 4 seed overlay."""
|
|
|
|
|
|
candidate_seed_form4_min_c_suite_count: int | None = None
|
|
|
"""Minimum C-suite buyer count for Form 4 seed overlay."""
|
|
|
|
|
|
candidate_seed_form4_require_cluster_or_csuite: bool = False
|
|
|
"""When True, accept Form 4 seed names with either owner cluster or C-suite support."""
|
|
|
|
|
|
candidate_seed_form4_min_gap_pct: float | None = None
|
|
|
"""Minimum opening gap for Form 4 seed-overlay names."""
|
|
|
|
|
|
candidate_seed_form4_max_gap_pct: float | None = None
|
|
|
"""Maximum opening gap for Form 4 seed-overlay names."""
|
|
|
|
|
|
candidate_seed_form4_min_avg_dollar_vol_30d: float | None = None
|
|
|
"""Minimum prior 30-day dollar volume for Form 4 seed-overlay names."""
|
|
|
|
|
|
candidate_seed_form4_min_ret_5d: float | None = None
|
|
|
"""Minimum prior 5-day return for Form 4 seed-overlay names."""
|
|
|
|
|
|
candidate_seed_form4_max_entropy_20d: float | None = None
|
|
|
"""Maximum prior 20-day entropy for Form 4 seed-overlay names."""
|
|
|
|
|
|
# ── Entropy-based per-candidate size scaler (from momentum strategy) ──
|
|
|
entropy_size_scale_low: float | None = None
|
|
|
"""Entropy level below which the per-candidate size scaler is 1.0 (no reduction).
|
|
|
None = disabled (entropy scaler inactive for ORB path)."""
|
|
|
|
|
|
entropy_size_scale_high: float | None = None
|
|
|
"""Entropy level at or above which size scaler = entropy_size_scale_min."""
|
|
|
|
|
|
entropy_size_scale_min: float = 0.6
|
|
|
"""Minimum size scaler at entropy_size_scale_high (e.g. 0.6 = 60% of normal size)."""
|
|
|
|
|
|
|
|
|
class UniverseParams(BaseModel):
|
|
|
"""Parameters controlling which stocks to scan."""
|
|
|
|
|
|
source: str = "sp500"
|
|
|
"""Universe source: 'sp500', 'nasdaq100', 'broad', 'midlarge', 'largecap', 'midcap',
|
|
|
'smallmid', 'yaml', or 'screener'."""
|
|
|
|
|
|
symbols_file: str | None = None
|
|
|
"""Path to YAML symbols file (required if source='yaml')."""
|
|
|
|
|
|
market_cap_min: float | None = None
|
|
|
"""Minimum market cap filter (USD). Overrides screener default when set."""
|
|
|
|
|
|
avg_volume_min: int | None = None
|
|
|
"""Minimum 3-month average daily volume filter."""
|
|
|
|
|
|
sector_exclude: list[str] = Field(default_factory=list)
|
|
|
"""Sectors to exclude (e.g. ['Energy', 'Utilities']). Not applied for index sources."""
|
|
|
|
|
|
min_price: float = 5.0
|
|
|
"""Minimum stock price. Filters out very cheap stocks."""
|
|
|
|
|
|
|
|
|
class BacktestParams(BaseModel):
|
|
|
"""Backtest period and pre-screening parameters."""
|
|
|
|
|
|
start_date: str | None = None
|
|
|
"""Backtest start date (YYYY-MM-DD). None = auto (today - lookback_trading_days)."""
|
|
|
|
|
|
end_date: str | None = None
|
|
|
"""Backtest end date (YYYY-MM-DD). None = today."""
|
|
|
|
|
|
lookback_trading_days: int = 200
|
|
|
"""Number of trading days to backtest when start_date is None."""
|
|
|
|
|
|
pre_screen_threshold: float = 0.015
|
|
|
"""Phase 1 pre-screening threshold: (today_open - prev_close) / prev_close >= this.
|
|
|
Uses only open-time information plus prior-day data (no lookahead)."""
|
|
|
|
|
|
|
|
|
class CacheParams(BaseModel):
|
|
|
"""Intraday data disk cache configuration."""
|
|
|
|
|
|
enabled: bool = True
|
|
|
"""Whether to use the disk cache for intraday bars."""
|
|
|
|
|
|
dir: str = "data/cache/intraday"
|
|
|
"""Root directory for Parquet cache files."""
|
|
|
|
|
|
|
|
|
class OutputParams(BaseModel):
|
|
|
"""Output and reporting configuration."""
|
|
|
|
|
|
dir: str = "runs/intraday"
|
|
|
"""Directory for writing result JSON files."""
|
|
|
|
|
|
verbose: bool = False
|
|
|
"""Show detailed per-day output during simulation."""
|
|
|
|
|
|
|
|
|
class IntradayConfig(BaseModel):
|
|
|
"""Full configuration for one intraday backtest run.
|
|
|
|
|
|
Maps 1:1 to the YAML config file format.
|
|
|
"""
|
|
|
|
|
|
strategy_mode: str = "momentum"
|
|
|
"""Strategy to use: 'momentum' (morning gainers) or 'orb' (opening range breakout)."""
|
|
|
|
|
|
strategy: StrategyParams = Field(default_factory=StrategyParams)
|
|
|
"""Momentum strategy parameters (used when strategy_mode='momentum')."""
|
|
|
|
|
|
orb_strategy: ORBStrategyParams | None = None
|
|
|
"""ORB strategy parameters (used when strategy_mode='orb'). None = use defaults."""
|
|
|
|
|
|
universe: UniverseParams = Field(default_factory=UniverseParams)
|
|
|
backtest: BacktestParams = Field(default_factory=BacktestParams)
|
|
|
cache: CacheParams = Field(default_factory=CacheParams)
|
|
|
output: OutputParams = Field(default_factory=OutputParams)
|
|
|
|
|
|
|
|
|
# ── Trade Results ──────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
class IntradayTrade(BaseModel):
|
|
|
"""One completed intraday trade."""
|
|
|
|
|
|
date: str
|
|
|
"""Trading date (YYYY-MM-DD)."""
|
|
|
|
|
|
ticker: str
|
|
|
"""Stock symbol."""
|
|
|
|
|
|
entry_price: float
|
|
|
"""Fill price at entry (after slippage)."""
|
|
|
|
|
|
exit_price: float
|
|
|
"""Fill price at exit (after slippage)."""
|
|
|
|
|
|
entry_time: str
|
|
|
"""Entry bar timestamp (ISO 8601, ET)."""
|
|
|
|
|
|
exit_time: str
|
|
|
"""Exit bar timestamp (ISO 8601, ET)."""
|
|
|
|
|
|
shares: float
|
|
|
"""Number of shares held."""
|
|
|
|
|
|
pnl: float
|
|
|
"""Dollar P&L (after slippage costs)."""
|
|
|
|
|
|
pnl_pct: float
|
|
|
"""Percentage P&L: (exit_price - entry_price) / entry_price (before slippage adj)."""
|
|
|
|
|
|
exit_reason: str
|
|
|
"""How the trade was closed: 'close' or 'stop_loss'."""
|
|
|
|
|
|
morning_gain_pct: float = 0.0
|
|
|
"""Stock's gain from open to entry time (momentum signal). 0.0 for ORB trades."""
|
|
|
|
|
|
slippage_cost: float = 0.0
|
|
|
"""Total slippage cost in USD (entry + exit)."""
|
|
|
|
|
|
trade_sleeve: str | None = None
|
|
|
"""Selection sleeve label for momentum strategies. None for ORB trades."""
|
|
|
|
|
|
gap_pct: float | None = None
|
|
|
"""Opening gap used by momentum candidate selection. None when unavailable."""
|
|
|
|
|
|
confirmation_return_pct: float | None = None
|
|
|
"""Return from primary entry bar to confirmation bar for momentum confirmation."""
|
|
|
|
|
|
entry_dollar_volume: float | None = None
|
|
|
"""Cumulative dollar volume through the momentum entry/confirmation bar."""
|
|
|
|
|
|
avg_dollar_vol_30d: float | None = None
|
|
|
"""Prior 30-day average dollar volume used by liquidity/support gates."""
|
|
|
|
|
|
entropy_20d: float | None = None
|
|
|
"""Prior 20-day entropy feature used by candidate and size scaling."""
|
|
|
|
|
|
ret_5d: float | None = None
|
|
|
"""Prior 5-day return feature used by leader/continuation gates."""
|
|
|
|
|
|
event_score: float | None = None
|
|
|
"""Same-day filing/event score when available."""
|
|
|
|
|
|
ownership_13dg_flag: bool | None = None
|
|
|
"""True when a PIT-safe 13D/13G filing exists in the configured lookback."""
|
|
|
|
|
|
ownership_13dg_initial_flag: bool | None = None
|
|
|
"""True when the recent 13D/13G set contains an initial-owner filing."""
|
|
|
|
|
|
ownership_13dg_days_since: int | None = None
|
|
|
"""Calendar days since the most recent qualifying 13D/13G filing."""
|
|
|
|
|
|
ownership_13dg_strength_score: float | None = None
|
|
|
"""Maximum ownership_strength_score among qualifying 13D/13G events."""
|
|
|
|
|
|
ownership_initial_size_scale: float | None = None
|
|
|
"""Per-trade size boost applied by the 13D/13G initial-owner overlay."""
|
|
|
|
|
|
form4_flag: bool | None = None
|
|
|
"""True when a PIT-safe Form 4 purchase cluster exists in the configured lookback."""
|
|
|
|
|
|
form4_days_since: int | None = None
|
|
|
"""Calendar days since the most recent qualifying Form 4 filing."""
|
|
|
|
|
|
form4_total_value: float | None = None
|
|
|
"""Aggregated purchase value in the Form 4 lookback window."""
|
|
|
|
|
|
form4_owner_count: int | None = None
|
|
|
"""Maximum owner_count among qualifying Form 4 events."""
|
|
|
|
|
|
form4_c_suite_count: int | None = None
|
|
|
"""Maximum C-suite buyer count among qualifying Form 4 events."""
|
|
|
|
|
|
form4_size_scale: float | None = None
|
|
|
"""Per-trade size boost applied by the Form 4 overlay."""
|
|
|
|
|
|
support_score: float | None = None
|
|
|
"""Blended liquidity/attention/catalyst support score used by tail defense."""
|
|
|
|
|
|
is_liquid_largecap: bool | None = None
|
|
|
"""True when the trade qualified through the liquid large-cap sleeve/gate."""
|
|
|
|
|
|
is_moderate_gap_liquid: bool | None = None
|
|
|
"""True when the trade qualified through the moderate-gap liquid sleeve/gate."""
|
|
|
|
|
|
is_sector_thrust: bool | None = None
|
|
|
"""True when the trade qualified through the sector breadth-confirmed thrust sleeve/gate."""
|
|
|
|
|
|
sector_thrust_member_count: int | None = None
|
|
|
"""Number of same-sector names supporting the trade's sector-thrust state."""
|
|
|
|
|
|
sector_thrust_total_entry_dollar_volume: float | None = None
|
|
|
"""Combined entry-time dollar volume across supporting same-sector names."""
|
|
|
|
|
|
is_liquid_cluster: bool | None = None
|
|
|
"""True when the trade qualified through the separate liquid-cluster engine."""
|
|
|
|
|
|
liquid_cluster_member_count: int | None = None
|
|
|
"""Number of same-sector names supporting the liquid-cluster trade."""
|
|
|
|
|
|
liquid_cluster_total_entry_dollar_volume: float | None = None
|
|
|
"""Combined entry-time dollar volume across the liquid cluster."""
|
|
|
|
|
|
liquid_cluster_sector: str | None = None
|
|
|
"""Resolved sector label used by the liquid-cluster engine / ETF sleeve."""
|
|
|
|
|
|
liquid_cluster_sector_score: float | None = None
|
|
|
"""Sector-level cluster score used for post-allocation overlays."""
|
|
|
|
|
|
sector_proxy_ticker: str | None = None
|
|
|
"""Mapped sector ETF proxy ticker when the trade comes from ETF sleeve logic."""
|
|
|
|
|
|
sector_confirmation_active: bool | None = None
|
|
|
"""True when an ORB trade belongs to a same-sector confirmation cluster."""
|
|
|
|
|
|
sector_confirmation_member_count: int | None = None
|
|
|
"""Number of same-sector ORB candidates supporting the confirmation cluster."""
|
|
|
|
|
|
sector_confirmation_avg_orb_return: float | None = None
|
|
|
"""Average ORB candle return across the same-sector confirmation cluster."""
|
|
|
|
|
|
sector_confirmation_total_first_bar_dollar_vol: float | None = None
|
|
|
"""Combined first-bar dollar volume across the same-sector confirmation cluster."""
|
|
|
|
|
|
sector_confirmation_score: float | None = None
|
|
|
"""Raw sector-confirmation score before cross-candidate normalization."""
|
|
|
|
|
|
sector_confirmation_size_scale: float | None = None
|
|
|
"""Position-size multiplier applied by the ORB sector-confirmation engine."""
|
|
|
|
|
|
entry_market_guard_active: bool | None = None
|
|
|
"""True when the entry-time market guard scaled this trade."""
|
|
|
|
|
|
entry_market_guard_return_pct: float | None = None
|
|
|
"""Market ETF intraday return observed at the trade's entry bar."""
|
|
|
|
|
|
entry_market_guard_size_scale: float | None = None
|
|
|
"""Position-size multiplier applied by the entry-time market guard."""
|
|
|
|
|
|
# ORB-specific fields (optional, None for momentum trades)
|
|
|
orb_direction: str | None = None
|
|
|
"""ORB trade direction: 'long' or 'short'. None for momentum trades."""
|
|
|
|
|
|
rvol: float | None = None
|
|
|
"""Approximate RVOL at entry time. None for momentum trades."""
|
|
|
|
|
|
atr_at_entry: float | None = None
|
|
|
"""ATR(14) value used for stop sizing. None for momentum trades."""
|
|
|
|
|
|
r_multiple_at_exit: float | None = None
|
|
|
"""Final R-multiple at exit: (exit_price - entry_price) / initial_risk. None for momentum."""
|
|
|
|
|
|
stop_level_at_exit: str | None = None
|
|
|
"""Stop level active when the trade exited: 'initial', 'breakeven', or 'trailing'.
|
|
|
None for momentum trades. Helps diagnose whether winners were protected before exiting."""
|
|
|
|
|
|
partial_exit_r: float | None = None
|
|
|
"""R-multiple at which the partial exit fired, if partial_exit_at_r was set. None otherwise."""
|
|
|
|
|
|
pyramid_adds: int = 0
|
|
|
"""Number of pyramid additions executed during this trade. 0 = no pyramiding."""
|
|
|
|
|
|
pyramid_pnl: float = 0.0
|
|
|
"""Dollar P&L contributed by pyramid add-on shares. 0.0 = no pyramid or no pyramid PnL."""
|
|
|
|
|
|
is_reentry: bool = False
|
|
|
"""True if this trade is a re-entry after a prior stop-out on the same ticker same day."""
|
|
|
|
|
|
trigger_type: str = "orb"
|
|
|
"""Entry trigger: 'orb' (ORB breakout), 'momentum_confirm' (09:45 momentum gate),
|
|
|
'vwap_reclaim' (no-fill VWAP fallback), 'soft_day_vwap_reclaim', or
|
|
|
'late_breakout' (post-timeout close-confirmed ORB breakout fallback), or
|
|
|
'broad_gapup_continuation' (bounded high-gap ORB continuation sleeve)."""
|
|
|
|
|
|
total_capital_deployed: float = 0.0
|
|
|
"""Total capital deployed including pyramid additions.
|
|
|
Computed as original_shares * entry_price + sum(pyramid_shares * pyramid_entry).
|
|
|
Used for accurate portfolio deployment tracking."""
|
|
|
|
|
|
# ORB candidate diagnostics
|
|
|
candidate_score: float | None = None
|
|
|
"""Composite ORB candidate score after cross-sectional normalization."""
|
|
|
|
|
|
score_rank_pct: float | None = None
|
|
|
"""Candidate rank within the selected daily basket. 1.0 = top ranked, 0.0 = bottom."""
|
|
|
|
|
|
premarket_dollar_vol: float | None = None
|
|
|
"""Premarket dollar volume used by ORB candidate filtering/ranking."""
|
|
|
|
|
|
first_bar_dollar_vol: float | None = None
|
|
|
"""Dollar volume in the opening-range bar/window."""
|
|
|
|
|
|
volume_attention_rank_pct: float | None = None
|
|
|
"""Same-day cross-sectional volume-attention percentile used by IEX-safe ORB ranking."""
|
|
|
|
|
|
volume_attention_global_rank_pct: float | None = None
|
|
|
"""All-candidate volume-attention percentile before contextual blending."""
|
|
|
|
|
|
volume_attention_sector_rank_pct: float | None = None
|
|
|
"""Sector-relative volume-attention percentile used by contextual blending."""
|
|
|
|
|
|
volume_attention_price_rank_pct: float | None = None
|
|
|
"""Price-bucket-relative volume-attention percentile used by contextual blending."""
|
|
|
|
|
|
opening_dollar_vol_rank_pct: float | None = None
|
|
|
"""Same-day percentile rank for opening-range dollar volume."""
|
|
|
|
|
|
rvol_rank_pct: float | None = None
|
|
|
"""Same-day percentile rank for opening-range RVOL."""
|
|
|
|
|
|
premarket_dollar_vol_rank_pct: float | None = None
|
|
|
"""Same-day percentile rank for premarket dollar volume."""
|
|
|
|
|
|
reclaim_entry_attention_rank_pct: float | None = None
|
|
|
"""Blended entry-bar participation percentile for reclaim-style triggers."""
|
|
|
|
|
|
entry_bar_dollar_vol_rank_pct: float | None = None
|
|
|
"""Same-day percentile rank for the actual entry signal bar's dollar volume."""
|
|
|
|
|
|
entry_cumulative_dollar_vol_rank_pct: float | None = None
|
|
|
"""Same-day percentile rank for cumulative dollar volume through entry."""
|
|
|
|
|
|
entry_rel_volume_rank_pct: float | None = None
|
|
|
"""Same-day percentile rank for entry-bar relative volume."""
|
|
|
|
|
|
entry_rel_volume: float | None = None
|
|
|
"""Entry signal bar volume divided by earlier post-ORB average bar volume."""
|
|
|
|
|
|
body_ratio: float | None = None
|
|
|
"""Directional body ratio of the ORB candle."""
|
|
|
|
|
|
close_location: float | None = None
|
|
|
"""Close location inside the ORB candle range, where 1.0 is the high."""
|
|
|
|
|
|
gap_zscore_20d: float | None = None
|
|
|
"""Today's opening gap z-score versus the prior 20 trading days."""
|
|
|
|
|
|
obv_slope_20: float | None = None
|
|
|
"""Prior 20-day OBV slope feature used by ORB ranking."""
|
|
|
|
|
|
obv_slope_5: float | None = None
|
|
|
"""Prior 5-day OBV slope feature used by ORB ranking."""
|
|
|
|
|
|
orb_return: float | None = None
|
|
|
"""Return of the ORB candle itself."""
|
|
|
|
|
|
crowded_gap_requires_confirmation: bool = False
|
|
|
"""True when the candidate matched the crowded-gap exhaustion setup and was
|
|
|
allowed only after confirmation."""
|
|
|
|
|
|
crowded_gap_size_scale: float | None = None
|
|
|
"""Per-trade size scale applied by the crowded-gap risk overlay, if any."""
|
|
|
|
|
|
countertrend_gap_requires_confirmation: bool = False
|
|
|
"""True when the candidate matched the weak-prior-trend gap-up setup and
|
|
|
was allowed only after confirmation."""
|
|
|
|
|
|
countertrend_gap_size_scale: float | None = None
|
|
|
"""Per-trade size scale applied by the countertrend-gap risk overlay, if any."""
|
|
|
|
|
|
distressed_reclaim_requires_confirmation: bool = False
|
|
|
"""True when the candidate matched the distressed downside-gap reclaim setup
|
|
|
and was allowed only after confirmation."""
|
|
|
|
|
|
distressed_reclaim_size_scale: float | None = None
|
|
|
"""Per-trade size scale applied by the distressed reclaim overlay, if any."""
|
|
|
|
|
|
hot_reclaim_requires_confirmation: bool = False
|
|
|
"""True when the candidate matched the hot-pullback reclaim setup and was
|
|
|
allowed only after confirmation."""
|
|
|
|
|
|
hot_reclaim_size_scale: float | None = None
|
|
|
"""Per-trade size scale applied by the hot-pullback reclaim overlay, if any."""
|
|
|
|
|
|
weak_downside_reclaim_requires_confirmation: bool = False
|
|
|
"""True when the candidate matched the weak downside reclaim setup and was
|
|
|
allowed only after confirmation."""
|
|
|
|
|
|
weak_downside_reclaim_size_scale: float | None = None
|
|
|
"""Per-trade size scale applied by the weak downside reclaim overlay, if any."""
|
|
|
|
|
|
quiet_downside_reclaim_requires_confirmation: bool = False
|
|
|
"""True when the candidate matched the quiet downside reclaim setup and was
|
|
|
allowed only after confirmation."""
|
|
|
|
|
|
quiet_downside_reclaim_size_scale: float | None = None
|
|
|
"""Per-trade size scale applied by the quiet downside reclaim overlay, if any."""
|
|
|
|
|
|
stalled_gap_up_requires_confirmation: bool = False
|
|
|
"""True when the candidate matched the stalled gap-up setup and was allowed
|
|
|
only after confirmation."""
|
|
|
|
|
|
stalled_gap_up_size_scale: float | None = None
|
|
|
"""Per-trade size scale applied by the stalled gap-up overlay, if any."""
|
|
|
|
|
|
liquid_stalled_gap_up_requires_confirmation: bool = False
|
|
|
"""True when the candidate matched the liquid stalled gap-up setup and was
|
|
|
allowed only after confirmation."""
|
|
|
|
|
|
liquid_stalled_gap_up_size_scale: float | None = None
|
|
|
"""Per-trade size scale applied by the liquid stalled gap-up overlay, if any."""
|
|
|
|
|
|
stale_obv_reversal_requires_confirmation: bool = False
|
|
|
"""True when the candidate matched the stale-OBV reversal setup and was
|
|
|
allowed only after confirmation."""
|
|
|
|
|
|
stale_obv_reversal_size_scale: float | None = None
|
|
|
"""Per-trade size scale applied by the stale-OBV reversal overlay, if any."""
|
|
|
|
|
|
thin_gap_up_loss_cap_active: bool = False
|
|
|
"""True when the trade matched the thin gap-up fixed-loss overlay."""
|
|
|
|
|
|
moderate_downside_loss_cap_active: bool = False
|
|
|
"""True when the trade matched the moderate downside-gap fixed-loss overlay."""
|
|
|
|
|
|
isolated_downside_loss_cap_active: bool = False
|
|
|
"""True when the trade matched the isolated downside-reclaim fixed-loss overlay."""
|
|
|
|
|
|
isolated_downside_loss_cap_pct: float | None = None
|
|
|
"""Fixed stop distance applied by the isolated downside-reclaim overlay."""
|
|
|
|
|
|
isolated_downside_size_scale: float | None = None
|
|
|
"""Per-trade size scale applied by the isolated downside-reclaim overlay."""
|
|
|
|
|
|
isolated_downside_pressure_active: bool = False
|
|
|
"""True when the trade matched the isolated high-volume downside pressure overlay."""
|
|
|
|
|
|
isolated_downside_pressure_size_scale: float | None = None
|
|
|
"""Per-trade size scale applied by the isolated high-volume downside pressure overlay."""
|
|
|
|
|
|
overextended_downside_reclaim_active: bool = False
|
|
|
"""True when the trade matched the overextended downside-reclaim overlay."""
|
|
|
|
|
|
overextended_downside_reclaim_size_scale: float | None = None
|
|
|
"""Per-trade size scale applied by the overextended downside-reclaim overlay."""
|
|
|
|
|
|
mid_attention_exhaustion_active: bool = False
|
|
|
"""True when the trade matched the mid-attention exhaustion overlay."""
|
|
|
|
|
|
mid_attention_exhaustion_size_scale: float | None = None
|
|
|
"""Per-trade size scale applied by the mid-attention exhaustion overlay."""
|
|
|
|
|
|
mid_liquidity_fragility_active: bool = False
|
|
|
"""True when the trade matched the mid-liquidity fragility governor."""
|
|
|
|
|
|
mid_liquidity_fragility_size_scale: float | None = None
|
|
|
"""Per-trade size scale applied by the mid-liquidity fragility governor."""
|
|
|
|
|
|
orphan_thin_attention_active: bool = False
|
|
|
"""True when the trade matched the unsupported thin-attention overlay."""
|
|
|
|
|
|
orphan_thin_attention_size_scale: float | None = None
|
|
|
"""Per-trade size scale applied by the unsupported thin-attention overlay."""
|
|
|
|
|
|
gap_up_fill_trap_active: bool = False
|
|
|
"""True when the trade matched the positive-gap fill-trap governor."""
|
|
|
|
|
|
gap_up_fill_trap_size_scale: float | None = None
|
|
|
"""Per-trade size scale applied by the positive-gap fill-trap governor."""
|
|
|
|
|
|
low_candidate_quality_active: bool = False
|
|
|
"""True when the trade matched the low composite-score quality governor."""
|
|
|
|
|
|
low_candidate_quality_size_scale: float | None = None
|
|
|
"""Per-trade size scale applied by the low candidate-quality governor."""
|
|
|
|
|
|
late_trade_size_scale: float | None = None
|
|
|
"""Per-trade size scale applied because this was a late same-day fill."""
|
|
|
|
|
|
rank_rvol_pressure_size_scale: float | None = None
|
|
|
"""Per-trade size scale applied by the rank/RVOL pressure overlay."""
|
|
|
|
|
|
gap_exhaustion_pressure_size_scale: float | None = None
|
|
|
"""Per-trade size scale applied by the gap-exhaustion pressure overlay."""
|
|
|
|
|
|
stale_obv_rvol_pressure_size_scale: float | None = None
|
|
|
"""Per-trade size scale applied by the stale-OBV/RVOL pressure overlay."""
|
|
|
|
|
|
unboosted_primary_fragility_size_scale: float | None = None
|
|
|
"""Per-trade size scale applied by the unboosted-primary fragility overlay."""
|
|
|
|
|
|
unsupported_attention_size_scale: float | None = None
|
|
|
"""Per-trade size scale applied by the unsupported-attention governor."""
|
|
|
|
|
|
positive_gap_rebound_failure_size_scale: float | None = None
|
|
|
"""Per-trade size scale applied by the positive-gap rebound-failure governor."""
|
|
|
|
|
|
broad_gapup_continuation: bool = False
|
|
|
"""True when the trade came from the bounded high-gap continuation sleeve."""
|
|
|
|
|
|
broad_gapup_continuation_size_scale: float | None = None
|
|
|
"""Per-trade size scale applied by the high-gap continuation sleeve."""
|
|
|
|
|
|
intraday_continuation_reclaim: bool = False
|
|
|
"""True when the trade came from the intraday continuation-reclaim sleeve."""
|
|
|
|
|
|
intraday_continuation_reclaim_size_scale: float | None = None
|
|
|
"""Per-trade size scale applied by the intraday continuation-reclaim sleeve."""
|
|
|
|
|
|
market_thrust_liquid_continuation: bool = False
|
|
|
"""True when the trade came from the market-thrust liquid continuation sleeve."""
|
|
|
|
|
|
market_thrust_liquid_continuation_size_scale: float | None = None
|
|
|
"""Per-trade size scale applied by the market-thrust liquid continuation sleeve."""
|
|
|
|
|
|
market_thrust_opening_burst: bool = False
|
|
|
"""True when the trade used the market-thrust opening-burst entry mode."""
|
|
|
|
|
|
market_thrust_opening_followthrough: bool = False
|
|
|
"""True when the trade used the delayed market-thrust opening-followthrough entry."""
|
|
|
|
|
|
orb_idle_sleeve_overnight: bool = False
|
|
|
"""True when the trade came from the overnight idle fallback sleeve."""
|
|
|
|
|
|
idle_entry_day_return_pct: float | None = None
|
|
|
"""Idle-sleeve entry-day open-to-entry return for diagnostics."""
|
|
|
|
|
|
idle_entry_close_location: float | None = None
|
|
|
"""Idle-sleeve entry-day close location inside the regular-session range."""
|
|
|
|
|
|
idle_reclaim_early_return_pct: float | None = None
|
|
|
"""Late-reclaim sleeve open-to-checkpoint return used for diagnostics."""
|
|
|
|
|
|
idle_reclaim_late_return_pct: float | None = None
|
|
|
"""Late-reclaim sleeve checkpoint-to-entry return used for diagnostics."""
|
|
|
|
|
|
idle_market_day_return_pct: float | None = None
|
|
|
"""Market ticker open-to-entry return observed when the idle sleeve opened."""
|
|
|
|
|
|
idle_market_close_location: float | None = None
|
|
|
"""Market ticker close location observed when the idle sleeve opened."""
|
|
|
|
|
|
red_to_green_acceleration_size_scale: float | None = None
|
|
|
"""Per-trade size scale applied by the red-to-green acceleration booster."""
|
|
|
|
|
|
liquid_leader_conviction_size_scale: float | None = None
|
|
|
"""Per-trade size scale applied by the liquid-leader conviction booster."""
|
|
|
|
|
|
opening_burst_liquid_size_scale: float | None = None
|
|
|
"""Per-trade size scale applied by the opening-burst liquid allocator."""
|
|
|
|
|
|
soft_day_sector_confirmation_override_size_scale: float | None = None
|
|
|
"""Per-trade size scale applied by the soft-day sector-confirmation override."""
|
|
|
|
|
|
soft_day_sector_confirmation_override_min_day_size_scale: float | None = None
|
|
|
"""Day-level sizing floor applied by the soft-day sector-confirmation override."""
|
|
|
|
|
|
red_to_green_reserved: bool = False
|
|
|
"""True when the trade came from a reserved downside-gap reclaim basket slot."""
|
|
|
|
|
|
candidate_seed_overlay: bool = False
|
|
|
"""True when the candidate matched a candidate-seed overlay profile."""
|
|
|
|
|
|
candidate_seed_overlay_reserved: bool = False
|
|
|
"""True when the trade came from a reserved candidate-seed overlay basket slot."""
|
|
|
|
|
|
soft_day_trade: bool = False
|
|
|
"""True when the trade was entered on a soft-regime day."""
|
|
|
|
|
|
soft_day_reason: str | None = None
|
|
|
"""Why the trade was classified as soft-day exposure, if known."""
|
|
|
|
|
|
gap_up_fill_exit_active: bool = False
|
|
|
"""True when a positive-gap candidate used candidate-level gap-fill exit."""
|
|
|
|
|
|
|
|
|
class DayResult(BaseModel):
|
|
|
"""Simulation result for one trading day."""
|
|
|
|
|
|
date: str
|
|
|
trades: list[IntradayTrade] = Field(default_factory=list)
|
|
|
daily_pnl: float = 0.0
|
|
|
daily_return_pct: float = 0.0
|
|
|
candidates_found: int = 0
|
|
|
"""Number of stocks that met the morning gain threshold."""
|
|
|
|
|
|
# Settlement / GFV tracking (ORB-only; 0 when settlement_days=0 or momentum)
|
|
|
capital_deployed: float = 0.0
|
|
|
"""Total capital deployed in positions this day (sum of shares × entry_price)."""
|
|
|
available_cash_start: float = 0.0
|
|
|
"""Settled cash available at start of this trading day (before any trades)."""
|
|
|
skipped_insufficient_cash: int = 0
|
|
|
"""Candidates skipped because available settled cash was exhausted."""
|
|
|
|
|
|
# Diagnostic fields (populated by simulate_orb_day / run_orb_simulation_with_state)
|
|
|
rolling_loss_synthetic_pnl: float | None = None
|
|
|
"""Synthetic PnL inserted into the rolling loss governor for this day.
|
|
|
Does not affect reported daily_pnl, equity, or metrics."""
|
|
|
|
|
|
skip_reason: str | None = None
|
|
|
"""Why this day had no trades. One of: 'vix_gate', 'market_regime', 'breadth',
|
|
|
'rolling_loss', 'spy_trend', 'no_candidates', 'below_min_candidates'. None = traded."""
|
|
|
candidate_filter_stats: dict | None = None
|
|
|
"""Per-filter drop counts from compute_orb_candidates: {gap, rvol, atr, dolvol, dir,
|
|
|
no_bars, late, price}. Present on all days (traded and skipped-after-candidates)."""
|
|
|
entry_diagnostics: dict | None = None
|
|
|
"""Post-candidate entry diagnostics for no-trade analysis. Includes counts of
|
|
|
primary, no-fill VWAP, soft-day VWAP, and pass-2 rejection reasons."""
|
|
|
|
|
|
# V20 diagnostics
|
|
|
regime_scaler: float | None = None
|
|
|
"""Regime size scaler for this day (1.0 = full size or V19 path)."""
|
|
|
regime_gap_pct: float | None = None
|
|
|
"""Opening gap of the configured market regime ticker, when available."""
|
|
|
breadth_scaler: float | None = None
|
|
|
"""Breadth size scaler for this day (1.0 = full size or V19 path)."""
|
|
|
breadth_ratio: float | None = None
|
|
|
"""Fraction of intraday-loaded tickers opening above prior close."""
|
|
|
breadth_positive_count: int | None = None
|
|
|
"""Number of intraday-loaded tickers opening above prior close."""
|
|
|
breadth_total_count: int | None = None
|
|
|
"""Number of intraday-loaded tickers with enough data for breadth."""
|
|
|
sparse_day_scaler: float | None = None
|
|
|
"""Sparse-basket size scaler for this day (1.0 = full size)."""
|
|
|
market_orb_quality_scaler: float | None = None
|
|
|
"""Market first-bar ORB quality size scaler for this day (1.0 = disabled/no change)."""
|
|
|
market_orb_quality_close_location: float | None = None
|
|
|
"""Close location of the market quality ticker's first regular-session bar."""
|
|
|
market_orb_quality_return_pct: float | None = None
|
|
|
"""Return of the market quality ticker's first regular-session bar."""
|
|
|
market_orb_quality_secondary_close_location: float | None = None
|
|
|
"""Close location of the secondary market quality ticker's first regular-session bar."""
|
|
|
market_orb_quality_secondary_return_pct: float | None = None
|
|
|
"""Return of the secondary market quality ticker's first regular-session bar."""
|
|
|
market_thrust_breadth_override_active: bool = False
|
|
|
"""True when strong market ORB thrust overrode breadth-only soft-day sizing."""
|
|
|
market_thrust_opening_breadth_override_active: bool = False
|
|
|
"""True when universe first-bar breadth activated the market-thrust override."""
|
|
|
market_thrust_opening_breadth_positive_ratio: float | None = None
|
|
|
"""Fraction of included tickers with positive first-bar returns."""
|
|
|
market_thrust_opening_breadth_avg_return_pct: float | None = None
|
|
|
"""Average first-bar return across included tickers."""
|
|
|
market_thrust_opening_breadth_strong_close_location_ratio: float | None = None
|
|
|
"""Fraction of included tickers with strong first-bar close location."""
|
|
|
market_thrust_opening_breadth_total_count: int | None = None
|
|
|
"""Number of included tickers in first-bar breadth stats."""
|
|
|
market_orb_quality_divergence_active: bool = False
|
|
|
"""True when the primary/secondary ORB divergence guard reduced day size."""
|
|
|
market_orb_quality_divergence_max_trades_active: bool = False
|
|
|
"""True when a divergence-day trade cap was activated."""
|
|
|
market_orb_quality_primary_weak_secondary_strong_active: bool = False
|
|
|
"""True when the weak-primary / strong-secondary split-tape guard changed day behavior."""
|
|
|
market_orb_quality_primary_weak_secondary_strong_max_trades_active: bool = False
|
|
|
"""True when a weak-primary / strong-secondary split-tape trade cap was activated."""
|
|
|
market_orb_quality_primary_lag_secondary_lead_active: bool = False
|
|
|
"""True when the primary-lag / secondary-lead split-tape guard changed day behavior."""
|
|
|
market_orb_quality_primary_lag_secondary_lead_max_trades_active: bool = False
|
|
|
"""True when a primary-lag / secondary-lead split-tape trade cap was activated."""
|
|
|
market_orb_quality_joint_weak_active: bool = False
|
|
|
"""True when the joint weak-open market ORB guard changed day behavior."""
|
|
|
market_orb_quality_joint_weak_max_trades_active: bool = False
|
|
|
"""True when a joint weak-open trade cap was activated."""
|
|
|
market_orb_quality_joint_panic_active: bool = False
|
|
|
"""True when the joint panic-low market ORB guard changed day behavior."""
|
|
|
market_orb_quality_joint_panic_max_trades_active: bool = False
|
|
|
"""True when a joint panic-low trade cap was activated."""
|
|
|
conditional_confirmation_active: bool = False
|
|
|
"""True when the day's market tape activated the conditional confirmation rule."""
|
|
|
sector_scaler: float | None = None
|
|
|
"""Basket sector-concentration scaler for this day (1.0 = no extra concentration penalty)."""
|
|
|
tail_risk_scaler: float | None = None
|
|
|
"""Extra meta-layer scaler for sparse high-extension tail-risk days."""
|
|
|
soft_day_sparse_scaler: float | None = None
|
|
|
"""Extra meta-layer scaler for sparse soft-day baskets lacking supportive sleeves."""
|
|
|
is_soft_day: bool = False
|
|
|
"""True when combined_scaler < soft_day_scaler_threshold (soft-regime day)."""
|
|
|
soft_day_excluded_from_streak: bool = False
|
|
|
"""True when this soft day intentionally did not update streak sizing state."""
|
|
|
soft_day_reason: str | None = None
|
|
|
"""Why the day was classified as soft-day exposure, if known."""
|
|
|
event_day_liquid_active: bool = False
|
|
|
"""True when the event-day liquid sleeve activation gate passed for the day."""
|
|
|
event_day_liquid_event_count: int | None = None
|
|
|
"""Number of morning event contributors that qualified the event-day liquid gate."""
|
|
|
event_day_liquid_total_event_entry_dollar_volume: float | None = None
|
|
|
"""Combined entry-time dollar volume across event-day liquid activation contributors."""
|
|
|
|
|
|
|
|
|
# ── Aggregate Metrics ──────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
class IntradayMetrics(BaseModel):
|
|
|
"""Summary performance metrics for a complete backtest run."""
|
|
|
|
|
|
# Identity
|
|
|
run_id: str = ""
|
|
|
params_hash: str = ""
|
|
|
|
|
|
# Period
|
|
|
start_date: str = ""
|
|
|
end_date: str = ""
|
|
|
trading_days: int = 0
|
|
|
days_with_trades: int = 0
|
|
|
days_with_activity: int = 0
|
|
|
"""Days with either completed trades or same-day close-entry idle sleeve opens."""
|
|
|
|
|
|
# Trade counts
|
|
|
total_trades: int = 0
|
|
|
stop_loss_exits: int = 0
|
|
|
idle_sleeve_entry_days: int = 0
|
|
|
"""Days where ORB idle sleeve positions were opened at/near the close."""
|
|
|
idle_sleeve_positions_opened: int = 0
|
|
|
"""Total close-entry ORB idle sleeve positions opened during the run."""
|
|
|
|
|
|
# Trade-level metrics
|
|
|
win_rate: float | None = None
|
|
|
avg_win_pct: float | None = None
|
|
|
avg_loss_pct: float | None = None
|
|
|
profit_factor: float | None = None
|
|
|
expectancy_pct: float | None = None
|
|
|
|
|
|
# Return metrics
|
|
|
total_return_pct: float | None = None
|
|
|
annualized_return_pct: float | None = None
|
|
|
avg_daily_return_pct: float | None = None
|
|
|
|
|
|
# Risk metrics
|
|
|
max_drawdown_pct: float | None = None
|
|
|
sharpe_ratio: float | None = None
|
|
|
sortino_ratio: float | None = None
|
|
|
calmar_ratio: float | None = None
|
|
|
loss_day_rate: float | None = None
|
|
|
"""Fraction of trading days with negative PnL."""
|
|
|
avg_loss_day_pct: float | None = None
|
|
|
"""Average return across negative-PnL days only."""
|
|
|
tail_loss_20_pct: float | None = None
|
|
|
"""Average return of the worst 20% of losing days."""
|
|
|
worst_day_return_pct: float | None = None
|
|
|
"""Worst single-day return."""
|
|
|
loss_containment_score: float | None = None
|
|
|
"""0-100 score favoring strategies that lose small amounts on bad days."""
|
|
|
|
|
|
# Intraday-specific
|
|
|
avg_hold_minutes: float | None = None
|
|
|
stop_loss_exit_pct: float | None = None
|
|
|
"""Fraction of trades exited via stop loss."""
|
|
|
|
|
|
# Capital
|
|
|
initial_capital: float = 10_000.0
|
|
|
final_equity: float = 0.0
|
|
|
|
|
|
|
|
|
class SweepResult(BaseModel):
|
|
|
"""One parameter combination result from a grid sweep."""
|
|
|
|
|
|
params: dict[str, Any]
|
|
|
metrics: IntradayMetrics
|
|
|
objective_score: float | None = None
|