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_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,
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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.",
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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"],
|
||||||
|
|||||||
@@ -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)]
|
||||||
|
|||||||
Reference in New Issue
Block a user