Isolate explicit S/R gate target ladder
This commit is contained in:
@@ -100,6 +100,7 @@ STRUCTURAL_OVERLAY_SOURCE_VARIANT = (
|
||||
)
|
||||
STRUCTURAL_OVERLAY_WEIGHT = 0.05
|
||||
STRUCTURAL_OVERLAY_SCORE_KEY = "structural_overlay_95_5_score"
|
||||
EXPLICIT_TARGET_LADDER_VARIANT = "explicit_target_ladder"
|
||||
RANGE_RESIDUAL_VARIANTS = {
|
||||
"rewrite_range504_structural_legacy_primary",
|
||||
"rewrite_range504_structural_primary2",
|
||||
@@ -163,6 +164,7 @@ SR_RESEARCH_VARIANTS = {
|
||||
"legacy_traffic_grid_only",
|
||||
"legacy_range_grid_touch",
|
||||
"legacy_range_grid_neutral",
|
||||
EXPLICIT_TARGET_LADDER_VARIANT,
|
||||
STRUCTURAL_OVERLAY_VARIANT,
|
||||
*RANGE_FACTOR_VARIANTS,
|
||||
}
|
||||
@@ -216,6 +218,7 @@ def _primary_min_rr_for_variant(sr_variant: str, activation: dict) -> float:
|
||||
sr_variant in {
|
||||
"production_control",
|
||||
"production_range504",
|
||||
EXPLICIT_TARGET_LADDER_VARIANT,
|
||||
STRUCTURAL_OVERLAY_VARIANT,
|
||||
}
|
||||
or sr_variant.endswith("_legacy_primary")
|
||||
@@ -371,6 +374,14 @@ def _window_setups(
|
||||
detected_levels = detect_sr_levels_legacy(
|
||||
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"}:
|
||||
detected_levels = detect_sr_levels_legacy(
|
||||
highs,
|
||||
|
||||
@@ -451,8 +451,9 @@ def detect_sr_levels_legacy(
|
||||
if explicit_range_grid
|
||||
else _legacy_volume_profile_nodes(highs, lows, closes, volumes)
|
||||
)
|
||||
method = "range_grid" if explicit_range_grid else "volume_profile"
|
||||
for price in nodes:
|
||||
candidates.append((float(price), "volume_profile"))
|
||||
candidates.append((float(price), method))
|
||||
except ValidationError:
|
||||
pass
|
||||
if include_pivots:
|
||||
|
||||
@@ -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
|
||||
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
|
||||
promotion holdout. These arms can isolate mechanism, but neither may ship without
|
||||
new future data or a separately pre-registered walk-forward protocol.
|
||||
|
||||
@@ -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
|
||||
pre-registered 5% clean-structure overlay; exploratory weight sweeps should not
|
||||
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.
|
||||
|
||||
@@ -60,6 +60,7 @@ def _parse_args() -> argparse.Namespace:
|
||||
"rewrite_range504_structural_legacy_primary",
|
||||
"rewrite_range504_structural_primary2",
|
||||
"production_structural_overlay",
|
||||
"explicit_target_ladder",
|
||||
),
|
||||
default=None,
|
||||
help="Research-only S/R detector/gate arm.",
|
||||
|
||||
@@ -105,6 +105,11 @@ def _args() -> argparse.Namespace:
|
||||
help="Test the frozen 5% clean-structure rank overlay on production breadth.",
|
||||
)
|
||||
_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",
|
||||
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.")
|
||||
|
||||
|
||||
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:
|
||||
args = _args()
|
||||
if args.command == "train":
|
||||
@@ -242,6 +277,8 @@ def main() -> None:
|
||||
_full(args)
|
||||
elif args.command == "overlay":
|
||||
_overlay(args)
|
||||
elif args.command == "ladder":
|
||||
_ladder(args)
|
||||
else:
|
||||
_validate(args)
|
||||
|
||||
|
||||
@@ -802,6 +802,7 @@ def test_sr_research_variant_is_explicit_and_validated(monkeypatch):
|
||||
"rewrite_range504_structural_legacy_primary",
|
||||
"rewrite_range504_structural_primary2",
|
||||
"production_structural_overlay",
|
||||
"explicit_target_ladder",
|
||||
):
|
||||
monkeypatch.setenv("BACKTEST_SR_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",
|
||||
activation,
|
||||
) == 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):
|
||||
@@ -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(
|
||||
"variant",
|
||||
["legacy_geometry_neutral", "legacy_range_grid_neutral"],
|
||||
|
||||
@@ -277,6 +277,30 @@ class TestDetectSrLevels:
|
||||
assert sorted(deployed) == explicit
|
||||
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):
|
||||
highs, lows, closes, volumes = _make_series(n=500)
|
||||
shifted_volumes = [volume * (i + 1) for i, volume in enumerate(volumes)]
|
||||
|
||||
Reference in New Issue
Block a user