Isolate legacy range-grid features

This commit is contained in:
2026-07-13 07:57:52 +02:00
parent 37836f8824
commit f7c2e35e29
7 changed files with 223 additions and 40 deletions
+13 -13
View File
@@ -144,6 +144,8 @@ SR_RESEARCH_VARIANTS = {
"legacy_geometry_neutral",
"legacy_pivots_only",
"legacy_traffic_grid_only",
"legacy_range_grid_touch",
"legacy_range_grid_neutral",
}
@@ -158,18 +160,7 @@ def _sr_research_variant() -> str:
def _apply_zone_strength_variant(zone_levels: list[Any], sr_variant: str) -> list[Any]:
"""Apply post-cluster research controls without changing zone geometry."""
if sr_variant == "legacy_geometry_neutral":
# Neutrality must be enforced after the shared zone cluster: its legacy
# sum mode can otherwise turn two 50-strength constituents back into a
# 100-strength target and silently invalidate the ablation.
for level in zone_levels:
level.strength = 50
return zone_levels
def _apply_zone_strength_variant(zone_levels: list[Any], sr_variant: str) -> list[Any]:
"""Apply post-cluster research controls without changing zone geometry."""
if sr_variant == "legacy_geometry_neutral":
if sr_variant in {"legacy_geometry_neutral", "legacy_range_grid_neutral"}:
# Neutrality must be enforced after the shared zone cluster: its legacy
# sum mode can otherwise turn two 50-strength constituents back into a
# 100-strength target and silently invalidate the ablation.
@@ -310,6 +301,16 @@ def _window_setups(
detected_levels = detect_sr_levels_legacy(
highs, lows, closes, volumes, include_pivots=False
)
elif sr_variant in {"legacy_range_grid_touch", "legacy_range_grid_neutral"}:
detected_levels = detect_sr_levels_legacy(
highs,
lows,
closes,
volumes,
include_pivots=False,
neutral_strength=sr_variant == "legacy_range_grid_neutral",
explicit_range_grid=True,
)
elif detector_variant in {"production_control", "rr_aligned_control"}:
detected_levels = detect_sr_levels_legacy(highs, lows, closes, volumes)
else:
@@ -351,7 +352,6 @@ def _window_setups(
strength_mode=zone_strength_mode,
)
zone_levels = _apply_zone_strength_variant(zone_levels, sr_variant)
zone_levels = _apply_zone_strength_variant(zone_levels, sr_variant)
targets = target_generator.generate_targets(direction, entry, stop, zone_levels, atr)
if not targets:
fallback_k = _atr_target_fallback_k()
+35 -1
View File
@@ -394,6 +394,34 @@ def _legacy_volume_profile_nodes(
return hvn + lvn
def _legacy_range_grid_nodes(
highs: list[float],
lows: list[float],
closes: list[float],
num_bins: int = 20,
) -> list[float]:
"""Return every deployed grid center without the irrelevant volume pass.
The legacy profile returns both its above-average (HVN) and below-average
(LVN) bins. Their union is therefore the complete range grid except for the
rare bin whose traffic equals the average exactly. This explicit helper
isolates that geometry from the misleading volume-profile label.
"""
if len(closes) < 20:
raise ValidationError(
f"Range grid requires at least 20 bars, got {len(closes)}"
)
price_min = min(lows)
price_max = max(highs)
if price_max == price_min:
price_max = price_min + 1.0
bin_width = (price_max - price_min) / num_bins
return [
round(price_min + (i + 0.5) * bin_width, 4)
for i in range(num_bins)
]
def detect_sr_levels_legacy(
highs: list[float],
lows: list[float],
@@ -404,6 +432,7 @@ def detect_sr_levels_legacy(
include_volume_profile: bool = True,
include_pivots: bool = True,
neutral_strength: bool = False,
explicit_range_grid: bool = False,
) -> list[dict]:
"""Deployed detector plus source-isolation controls for local research.
@@ -417,7 +446,12 @@ def detect_sr_levels_legacy(
candidates: list[tuple[float, str]] = []
if include_volume_profile:
try:
for price in _legacy_volume_profile_nodes(highs, lows, closes, volumes):
nodes = (
_legacy_range_grid_nodes(highs, lows, closes)
if explicit_range_grid
else _legacy_volume_profile_nodes(highs, lows, closes, volumes)
)
for price in nodes:
candidates.append((float(price), "volume_profile"))
except ValidationError:
pass
+43 -18
View File
@@ -481,13 +481,13 @@ into production as-is.
### Hidden-feature isolation
The remaining hypothesis is that the deployed detector accidentally measures
long-memory historical price traffic rather than genuine S/R. A dedicated matrix
holds the complete gate fixed and changes one legacy component at a time:
The deployed detector accidentally measures long-memory historical price traffic
rather than genuine S/R. A dedicated matrix holds the complete gate fixed and
changes one legacy component at a time:
- `legacy_geometry_neutral`: old locations, every merged strength fixed at 50;
- `legacy_pivots_only`: unfiltered full-history pivots, no VP grid;
- `legacy_traffic_grid_only`: old range-volume price grid, no pivots.
- `legacy_traffic_grid_only`: deployed HVN+LVN grid, no pivots.
Run on macOS:
@@ -498,29 +498,54 @@ Run on macOS:
Do not validate any traffic arm yet. First establish whether geometry, pivots, or
the range-occupancy grid reproduces production on pre-2024 training data.
Initial result:
Corrected training result:
| arm | Sharpe | CAGR | MaxDD | net avg R | ex-top-5% |
|---|---:|---:|---:|---:|---:|
| production control | 1.28 | 28.8% | 21.4% | 0.230 | 0.066 |
| pivots only | 1.38 | 32.9% | 25.2% | 0.236 | 0.076 |
| traffic grid only | **1.72** | **41.8%** | **16.8%** | **0.271** | **0.136** |
| arm | qualified | Sharpe | CAGR | MaxDD | net avg R | ex-top-5% |
|---|---:|---:|---:|---:|---:|---:|
| production control | 676 | 1.28 | 28.8% | 21.4% | 0.230 | 0.066 |
| corrected neutral geometry | 505 | 1.48 | 33.5% | 18.2% | 0.230 | 0.087 |
| pivots only | 717 | 1.38 | 32.9% | 25.2% | 0.236 | 0.076 |
| traffic grid only | 504 | **1.72** | **41.8%** | **16.8%** | **0.271** | **0.136** |
The traffic grid is the first research arm to beat control simultaneously on
Sharpe, CAGR, drawdown, and robust expectancy. It retains 264 production setups,
adds 240 positive-expectancy setups, and removes 412 weaker setups. This supports
the hypothesis that the useful legacy feature is the volume-weighted range-
occupancy grid, not pivots or visually meaningful S/R.
Sharpe, CAGR, drawdown, and robust expectancy. It retains 264 production setups
at +0.214R ex-top-5%, adds 240 at +0.051R, and removes 412 at only +0.010R.
Against corrected neutral geometry, only 239 qualified setups overlap; the 265
traffic-only setups return +0.140R ex-top-5% versus +0.073R for the 266
neutral-only setups. This is different selection, not merely a lower trade count.
The first geometry-neutral result is not interpretable: neutral strength was set
before the shared zone cluster, which summed multiple 50-strength constituents
back to 100. Neutrality is now enforced after clustering. Rerun only that arm:
The old `volume_profile` name is misleading. Its helper returns both HVN and LVN
bins, so their union retains almost every one of the 20 evenly spaced centers over
the expanding historical high-low range (19.987 levels on average in the audit).
The later strength calculation does not use volume; it counts bars whose ranges
cross each center. The candidate feature is therefore:
1. a normalized, expanding 20-bin price-range grid;
2. range-touch occupancy strength;
3. no full-history pivot ladder or pivot/grid strength saturation.
Two final training arms isolate the first two items explicitly:
- `legacy_range_grid_touch`: all 20 range centers, no volume calculation, legacy
touch strength;
- `legacy_range_grid_neutral`: identical centers, strength fixed at 50 after
clustering.
Run only these new arms on macOS:
```bash
.venv/bin/python scripts/run_sr_v2_matrix.py traffic \
--only-arm legacy_geometry_neutral --workers 14
--only-arm legacy_range_grid_touch --workers 14
.venv/bin/python scripts/run_sr_v2_matrix.py traffic \
--only-arm legacy_range_grid_neutral --workers 14
```
The touch arm should closely reproduce `legacy_traffic_grid_only`; that is the
volume-removal parity check. The touch-versus-neutral comparison then attributes
any remaining difference to occupancy strength. Freeze the winner before running
the post-2024-06-30 validation command; do not tune the bin count on training data.
**Next runs, if picked back up:**
- A **per-name target model** for clear-air setups instead of a constant k×ATR. This
+2 -1
View File
@@ -54,7 +54,8 @@ def _parse_args() -> argparse.Namespace:
"rewrite_legacy_primary", "soft_zones_legacy_primary",
"confirmed_rounds_legacy_primary", "gate_v2_legacy_primary",
"legacy_geometry_neutral", "legacy_pivots_only",
"legacy_traffic_grid_only",
"legacy_traffic_grid_only", "legacy_range_grid_touch",
"legacy_range_grid_neutral",
),
default=None,
help="Research-only S/R detector/gate arm.",
+8 -3
View File
@@ -22,12 +22,17 @@ TRAINING_ARMS = (
"confirmed_rounds_legacy_primary",
"gate_v2_legacy_primary",
)
LOCKABLE_ARMS = TRAINING_ARMS[1:]
RANGE_GRID_ARMS = (
"legacy_traffic_grid_only",
"legacy_range_grid_touch",
"legacy_range_grid_neutral",
)
LOCKABLE_ARMS = TRAINING_ARMS[1:] + RANGE_GRID_ARMS
TRAFFIC_ARMS = (
"production_control",
"legacy_geometry_neutral",
"legacy_pivots_only",
"legacy_traffic_grid_only",
*RANGE_GRID_ARMS,
)
@@ -47,7 +52,7 @@ def _args() -> argparse.Namespace:
_add_common(train)
traffic = commands.add_parser(
"traffic",
help="Isolate legacy geometry, pivots, and price-traffic grid on training data.",
help="Isolate pivots, range-grid geometry, and touch strength on training data.",
)
_add_common(traffic)
traffic.add_argument(
+58 -4
View File
@@ -759,22 +759,76 @@ def test_window_setups_too_short_returns_empty():
def test_sr_research_variant_is_explicit_and_validated(monkeypatch):
monkeypatch.setenv("BACKTEST_SR_VARIANT", "production_control")
assert bt._sr_research_variant() == "production_control"
for variant in (
"production_control",
"legacy_range_grid_touch",
"legacy_range_grid_neutral",
):
monkeypatch.setenv("BACKTEST_SR_VARIANT", variant)
assert bt._sr_research_variant() == variant
monkeypatch.setenv("BACKTEST_SR_VARIANT", "not-a-variant")
with pytest.raises(ValueError, match="Unknown BACKTEST_SR_VARIANT"):
bt._sr_research_variant()
def test_geometry_neutral_strength_is_enforced_after_zone_clustering():
@pytest.mark.parametrize(
("variant", "neutral_strength"),
[
("legacy_range_grid_touch", False),
("legacy_range_grid_neutral", True),
],
)
def test_window_setups_routes_explicit_range_grid(
monkeypatch,
variant,
neutral_strength,
):
captured = {}
def fake_detector(*args, **kwargs):
captured.update(kwargs)
return []
monkeypatch.setenv("BACKTEST_SR_VARIANT", 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 == {
"include_pivots": False,
"neutral_strength": neutral_strength,
"explicit_range_grid": True,
}
@pytest.mark.parametrize(
"variant",
["legacy_geometry_neutral", "legacy_range_grid_neutral"],
)
def test_neutral_strength_is_enforced_after_zone_clustering(variant):
levels = [
SimpleNamespace(strength=100),
SimpleNamespace(strength=75),
]
result = bt._apply_zone_strength_variant(levels, "legacy_geometry_neutral")
result = bt._apply_zone_strength_variant(levels, variant)
assert [level.strength for level in result] == [50, 50]
def test_touch_strength_survives_post_cluster_research_hook():
levels = [SimpleNamespace(strength=82), SimpleNamespace(strength=37)]
result = bt._apply_zone_strength_variant(levels, "legacy_range_grid_touch")
assert [level.strength for level in result] == [82, 37]
def test_backtest_entry_bounds_validate_dates(monkeypatch):
monkeypatch.setenv("BACKTEST_ENTRY_START", "2024-07-01")
monkeypatch.setenv("BACKTEST_ENTRY_END", "2024-12-31")
+64
View File
@@ -6,6 +6,8 @@ from app.services.sr_service import (
MAX_LEVELS,
_bar_respect_weight,
_cap_levels,
_legacy_range_grid_nodes,
_legacy_volume_profile_nodes,
_merge_levels,
_round_number_candidates,
_strength_from_respects,
@@ -267,3 +269,65 @@ class TestDetectSrLevels:
assert pivots and traffic
assert all(level["sources"] == ["pivot_point"] for level in pivots)
assert all(level["sources"] == ["volume_profile"] for level in traffic)
def test_legacy_profile_union_matches_explicit_range_grid(self):
highs, lows, closes, volumes = _make_series(n=500)
deployed = _legacy_volume_profile_nodes(highs, lows, closes, volumes)
explicit = _legacy_range_grid_nodes(highs, lows, closes)
assert sorted(deployed) == explicit
assert len(explicit) == 20
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)]
baseline = detect_sr_levels_legacy(
highs,
lows,
closes,
volumes,
include_pivots=False,
explicit_range_grid=True,
)
shifted = detect_sr_levels_legacy(
highs,
lows,
closes,
shifted_volumes,
include_pivots=False,
explicit_range_grid=True,
)
assert shifted == baseline
def test_explicit_range_grid_neutral_changes_only_strength(self):
highs, lows, closes, volumes = _make_series(n=500)
touch = detect_sr_levels_legacy(
highs,
lows,
closes,
volumes,
include_pivots=False,
explicit_range_grid=True,
)
neutral = detect_sr_levels_legacy(
highs,
lows,
closes,
volumes,
include_pivots=False,
neutral_strength=True,
explicit_range_grid=True,
)
touch_by_price = {level["price_level"]: level for level in touch}
neutral_by_price = {level["price_level"]: level for level in neutral}
assert neutral_by_price.keys() == touch_by_price.keys()
for price, neutral_level in neutral_by_price.items():
assert neutral_level["strength"] == 50
assert {
key: value
for key, value in neutral_level.items()
if key != "strength"
} == {
key: value
for key, value in touch_by_price[price].items()
if key != "strength"
}