Isolate explicit S/R gate target ladder

This commit is contained in:
2026-07-13 10:49:44 +02:00
parent b8ddf14e7d
commit f0e6a8fc19
8 changed files with 144 additions and 1 deletions
+11
View File
@@ -100,6 +100,7 @@ STRUCTURAL_OVERLAY_SOURCE_VARIANT = (
) )
STRUCTURAL_OVERLAY_WEIGHT = 0.05 STRUCTURAL_OVERLAY_WEIGHT = 0.05
STRUCTURAL_OVERLAY_SCORE_KEY = "structural_overlay_95_5_score" STRUCTURAL_OVERLAY_SCORE_KEY = "structural_overlay_95_5_score"
EXPLICIT_TARGET_LADDER_VARIANT = "explicit_target_ladder"
RANGE_RESIDUAL_VARIANTS = { RANGE_RESIDUAL_VARIANTS = {
"rewrite_range504_structural_legacy_primary", "rewrite_range504_structural_legacy_primary",
"rewrite_range504_structural_primary2", "rewrite_range504_structural_primary2",
@@ -163,6 +164,7 @@ SR_RESEARCH_VARIANTS = {
"legacy_traffic_grid_only", "legacy_traffic_grid_only",
"legacy_range_grid_touch", "legacy_range_grid_touch",
"legacy_range_grid_neutral", "legacy_range_grid_neutral",
EXPLICIT_TARGET_LADDER_VARIANT,
STRUCTURAL_OVERLAY_VARIANT, STRUCTURAL_OVERLAY_VARIANT,
*RANGE_FACTOR_VARIANTS, *RANGE_FACTOR_VARIANTS,
} }
@@ -216,6 +218,7 @@ def _primary_min_rr_for_variant(sr_variant: str, activation: dict) -> float:
sr_variant in { sr_variant in {
"production_control", "production_control",
"production_range504", "production_range504",
EXPLICIT_TARGET_LADDER_VARIANT,
STRUCTURAL_OVERLAY_VARIANT, STRUCTURAL_OVERLAY_VARIANT,
} }
or sr_variant.endswith("_legacy_primary") or sr_variant.endswith("_legacy_primary")
@@ -371,6 +374,14 @@ def _window_setups(
detected_levels = detect_sr_levels_legacy( detected_levels = detect_sr_levels_legacy(
highs, lows, closes, volumes, include_pivots=False highs, lows, closes, volumes, include_pivots=False
) )
elif sr_variant == EXPLICIT_TARGET_LADDER_VARIANT:
detected_levels = detect_sr_levels_legacy(
highs,
lows,
closes,
volumes,
explicit_range_grid=True,
)
elif sr_variant in {"legacy_range_grid_touch", "legacy_range_grid_neutral"}: elif sr_variant in {"legacy_range_grid_touch", "legacy_range_grid_neutral"}:
detected_levels = detect_sr_levels_legacy( detected_levels = detect_sr_levels_legacy(
highs, highs,
+2 -1
View File
@@ -451,8 +451,9 @@ def detect_sr_levels_legacy(
if explicit_range_grid if explicit_range_grid
else _legacy_volume_profile_nodes(highs, lows, closes, volumes) else _legacy_volume_profile_nodes(highs, lows, closes, volumes)
) )
method = "range_grid" if explicit_range_grid else "volume_profile"
for price in nodes: for price in nodes:
candidates.append((float(price), "volume_profile")) candidates.append((float(price), method))
except ValidationError: except ValidationError:
pass pass
if include_pivots: if include_pivots:
+31
View File
@@ -675,6 +675,37 @@ at least 90% of production CAGR. The 1-year and 6-month rows must not both
deteriorate. This remains contaminated full-history research, not promotion deteriorate. This remains contaminated full-history research, not promotion
validation. validation.
Result: **reject the overlay**. Production parity is exact, but the overlay
reduces full-period Sharpe from 2.03 to 2.00 and CAGR from 50.0% to 48.4%; max
drawdown improves from 21.4% to 20.0%. Confirmation has real standalone quality
(+0.263R ex-top-5% versus +0.024R), but little marginal ranking value: confirmed
setups already average production rank 92.3 and 46.1% come from ten symbols. The
5% boost changes ordering on only 45 active dates and overweights the same
concentrated names.
### Explicit gate target ladder
The legacy detector's volume-profile label is misleading. It returns both HVN
and LVN bins, whose union is the complete 20-bin price-range grid, then adds
unfiltered pivots and touch strength. For the gate this behaves as a broad
target-proposal ladder, not human-facing support/resistance.
The `explicit_target_ladder` arm therefore replaces only the irrelevant volume
pass with the complete range grid. It retains pivots, touch strength, merge
geometry, primary selection, qualification, ranking, and exit behavior. Grid
levels are labelled `range_grid`, making the internal purpose explicit.
```bash
.venv/bin/python scripts/run_sr_v2_matrix.py ladder --workers 14
```
The arm advances only on exact parity with the full-period production control:
no added or removed qualified setups and identical production-book Sharpe,
CAGR, drawdown, and trade count. Passing parity supports a dual-purpose design:
clean structure for charts and alerts, explicit target ladder for the gate.
Failing parity means the supposedly irrelevant volume pass still affects an
edge case and must be located before any architectural change.
The post-2024 window has been opened and is now analysis data, not a valid final The post-2024 window has been opened and is now analysis data, not a valid final
promotion holdout. These arms can isolate mechanism, but neither may ship without promotion holdout. These arms can isolate mechanism, but neither may ship without
new future data or a separately pre-registered walk-forward protocol. new future data or a separately pre-registered walk-forward protocol.
+8
View File
@@ -46,3 +46,11 @@ The follow-up breadth-preserving ranking experiment writes
`backtest-sr-overlay-full.json`. Retain it only as the decision point for the `backtest-sr-overlay-full.json`. Retain it only as the decision point for the
pre-registered 5% clean-structure overlay; exploratory weight sweeps should not pre-registered 5% clean-structure overlay; exploratory weight sweeps should not
be committed. be committed.
The explicit gate-ladder parity run writes:
- `backtest-sr-full-explicit_target_ladder.json`
- `sr-explicit-target-ladder-comparison.json`
- `sr-explicit-target-ladder-cohorts.csv`
Retain these only if they establish the final parity decision.
+1
View File
@@ -60,6 +60,7 @@ def _parse_args() -> argparse.Namespace:
"rewrite_range504_structural_legacy_primary", "rewrite_range504_structural_legacy_primary",
"rewrite_range504_structural_primary2", "rewrite_range504_structural_primary2",
"production_structural_overlay", "production_structural_overlay",
"explicit_target_ladder",
), ),
default=None, default=None,
help="Research-only S/R detector/gate arm.", help="Research-only S/R detector/gate arm.",
+37
View File
@@ -105,6 +105,11 @@ def _args() -> argparse.Namespace:
help="Test the frozen 5% clean-structure rank overlay on production breadth.", help="Test the frozen 5% clean-structure rank overlay on production breadth.",
) )
_add_common(overlay) _add_common(overlay)
ladder = commands.add_parser(
"ladder",
help="Verify the explicit volume-free gate target ladder against production.",
)
_add_common(ladder)
validate = commands.add_parser( validate = commands.add_parser(
"validate", "validate",
help="Run production control and one locked arm from 2024-07-01.", help="Run production control and one locked arm from 2024-07-01.",
@@ -225,6 +230,36 @@ def _overlay(args: argparse.Namespace) -> None:
print("Full-period structural ranking overlay complete.") print("Full-period structural ranking overlay complete.")
def _ladder(args: argparse.Namespace) -> None:
control = ROOT / "reports" / "backtest-sr-full-production_control.json"
if not control.exists():
raise SystemExit(f"Full-period production control not found: {control}")
variant = ROOT / "reports" / "backtest-sr-full-explicit_target_ladder.json"
_run_arm(
"explicit_target_ladder",
args.snapshot,
args.workers,
output=variant,
)
subprocess.run(
[
sys.executable,
str(COMPARE),
str(control),
str(variant),
"--out-csv", str(
ROOT / "reports" / "sr-explicit-target-ladder-cohorts.csv"
),
"--out-json", str(
ROOT / "reports" / "sr-explicit-target-ladder-comparison.json"
),
],
cwd=ROOT,
check=True,
)
print("Explicit target-ladder production parity comparison complete.")
def main() -> None: def main() -> None:
args = _args() args = _args()
if args.command == "train": if args.command == "train":
@@ -242,6 +277,8 @@ def main() -> None:
_full(args) _full(args)
elif args.command == "overlay": elif args.command == "overlay":
_overlay(args) _overlay(args)
elif args.command == "ladder":
_ladder(args)
else: else:
_validate(args) _validate(args)
+30
View File
@@ -802,6 +802,7 @@ def test_sr_research_variant_is_explicit_and_validated(monkeypatch):
"rewrite_range504_structural_legacy_primary", "rewrite_range504_structural_legacy_primary",
"rewrite_range504_structural_primary2", "rewrite_range504_structural_primary2",
"production_structural_overlay", "production_structural_overlay",
"explicit_target_ladder",
): ):
monkeypatch.setenv("BACKTEST_SR_VARIANT", variant) monkeypatch.setenv("BACKTEST_SR_VARIANT", variant)
assert bt._sr_research_variant() == variant assert bt._sr_research_variant() == variant
@@ -856,6 +857,10 @@ def test_residual_arms_change_only_the_primary_rr_floor():
"production_structural_overlay", "production_structural_overlay",
activation, activation,
) == 1.5 ) == 1.5
assert bt._primary_min_rr_for_variant(
"explicit_target_ladder",
activation,
) == 1.5
def test_structural_overlay_tags_production_geometry_without_replacing_it(monkeypatch): def test_structural_overlay_tags_production_geometry_without_replacing_it(monkeypatch):
@@ -934,6 +939,31 @@ def test_window_setups_routes_explicit_range_grid(
} }
def test_window_setups_routes_full_explicit_target_ladder(monkeypatch):
captured = {}
def fake_detector(*args, **kwargs):
captured.update(kwargs)
return []
monkeypatch.setenv("BACKTEST_SR_VARIANT", bt.EXPLICIT_TARGET_LADDER_VARIANT)
monkeypatch.setattr(bt, "detect_sr_levels_legacy", fake_detector)
records = [
SimpleNamespace(
date=date(2024, 1, 1) + timedelta(days=i),
open=100.0,
high=101.0,
low=99.0,
close=100.0,
volume=1_000_000,
)
for i in range(bt.MIN_LOOKBACK)
]
assert bt._window_setups(records, {}, {}) == []
assert captured == {"explicit_range_grid": True}
@pytest.mark.parametrize( @pytest.mark.parametrize(
"variant", "variant",
["legacy_geometry_neutral", "legacy_range_grid_neutral"], ["legacy_geometry_neutral", "legacy_range_grid_neutral"],
+24
View File
@@ -277,6 +277,30 @@ class TestDetectSrLevels:
assert sorted(deployed) == explicit assert sorted(deployed) == explicit
assert len(explicit) == 20 assert len(explicit) == 20
def test_explicit_target_ladder_preserves_full_legacy_mechanics(self):
highs, lows, closes, volumes = _make_series(n=500)
deployed = detect_sr_levels_legacy(highs, lows, closes, volumes)
explicit = detect_sr_levels_legacy(
highs,
lows,
closes,
volumes,
explicit_range_grid=True,
)
def mechanics(level):
return {
key: value
for key, value in level.items()
if key not in {"detection_method", "sources"}
}
assert [mechanics(level) for level in explicit] == [
mechanics(level) for level in deployed
]
assert any("range_grid" in level["sources"] for level in explicit)
assert all("volume_profile" not in level["sources"] for level in explicit)
def test_explicit_range_grid_is_volume_independent(self): def test_explicit_range_grid_is_volume_independent(self):
highs, lows, closes, volumes = _make_series(n=500) highs, lows, closes, volumes = _make_series(n=500)
shifted_volumes = [volume * (i + 1) for i, volume in enumerate(volumes)] shifted_volumes = [volume * (i + 1) for i, volume in enumerate(volumes)]