Retire completed GTL tuning harnesses
This commit is contained in:
+8
-146
@@ -10,7 +10,6 @@ and persists to DB.
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import delete, select
|
||||
@@ -33,40 +32,6 @@ from app.services.price_service import query_ohlcv
|
||||
|
||||
DEFAULT_TOLERANCE = 0.005 # fallback when ATR unavailable; also API legacy default
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class GateTargetLadderConfig:
|
||||
"""Research controls for the transient Gate Target Ladder.
|
||||
|
||||
Live callers omit this object and retain the frozen legacy-parity path.
|
||||
The backtest may pass an explicit configuration to isolate one ladder
|
||||
mechanism at a time without changing Structural S/R.
|
||||
"""
|
||||
|
||||
lookback_bars: int | None = None
|
||||
grid_bins: int = 20
|
||||
include_pivots: bool = True
|
||||
pivot_window: int = 2
|
||||
touch_tolerance: float = DEFAULT_TOLERANCE
|
||||
merge_tolerance: float = DEFAULT_TOLERANCE
|
||||
strength_scale: float = 500.0
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if self.lookback_bars is not None and self.lookback_bars < 20:
|
||||
raise ValueError("GTL lookback_bars must be at least 20 or None")
|
||||
if self.grid_bins < 2:
|
||||
raise ValueError("GTL grid_bins must be at least 2")
|
||||
if self.pivot_window < 1:
|
||||
raise ValueError("GTL pivot_window must be at least 1")
|
||||
for name, value in (
|
||||
("touch_tolerance", self.touch_tolerance),
|
||||
("merge_tolerance", self.merge_tolerance),
|
||||
):
|
||||
if not 0.0 <= value < 0.10:
|
||||
raise ValueError(f"GTL {name} must be in [0, 0.10)")
|
||||
if self.strength_scale <= 0:
|
||||
raise ValueError("GTL strength_scale must be positive")
|
||||
|
||||
VP_LOOKBACK = 252
|
||||
TOUCH_LOOKBACK = 252
|
||||
PIVOT_LOOKBACK = 504
|
||||
@@ -562,8 +527,6 @@ def detect_gate_target_ladder(
|
||||
lows: list[float],
|
||||
closes: list[float],
|
||||
tolerance: float = DEFAULT_TOLERANCE,
|
||||
*,
|
||||
config: GateTargetLadderConfig | None = None,
|
||||
) -> list[dict]:
|
||||
"""Build the scanner's internal, volume-free target proposal ladder.
|
||||
|
||||
@@ -573,115 +536,14 @@ def detect_gate_target_ladder(
|
||||
profile calculation. The returned levels are transient and must not be
|
||||
persisted as chart S/R.
|
||||
"""
|
||||
if config is None:
|
||||
# Frozen live/default path. Keeping the established helper here makes
|
||||
# the absence of research configuration an exact compatibility promise.
|
||||
return detect_sr_levels_legacy(
|
||||
highs,
|
||||
lows,
|
||||
closes,
|
||||
[0] * len(closes),
|
||||
tolerance,
|
||||
explicit_range_grid=True,
|
||||
)
|
||||
|
||||
if tolerance != DEFAULT_TOLERANCE:
|
||||
raise ValueError("Pass either GTL config tolerances or tolerance, not both")
|
||||
if not closes:
|
||||
return []
|
||||
if not (len(highs) == len(lows) == len(closes)):
|
||||
raise ValueError("GTL highs, lows, and closes must have equal lengths")
|
||||
|
||||
if config.lookback_bars is not None:
|
||||
highs = highs[-config.lookback_bars:]
|
||||
lows = lows[-config.lookback_bars:]
|
||||
closes = closes[-config.lookback_bars:]
|
||||
|
||||
candidates: list[tuple[float, str]] = []
|
||||
try:
|
||||
candidates.extend(
|
||||
(float(price), "range_grid")
|
||||
for price in _legacy_range_grid_nodes(
|
||||
highs,
|
||||
lows,
|
||||
closes,
|
||||
num_bins=config.grid_bins,
|
||||
)
|
||||
)
|
||||
except ValidationError:
|
||||
pass
|
||||
|
||||
if config.include_pivots:
|
||||
try:
|
||||
pivots = compute_pivot_points(
|
||||
highs,
|
||||
lows,
|
||||
closes,
|
||||
window=config.pivot_window,
|
||||
)
|
||||
candidates.extend(
|
||||
(float(price), "pivot_point")
|
||||
for price in pivots.get("swing_highs", []) + pivots.get("swing_lows", [])
|
||||
)
|
||||
except ValidationError:
|
||||
pass
|
||||
if not candidates:
|
||||
return []
|
||||
|
||||
total_bars = len(closes)
|
||||
raw: list[dict] = []
|
||||
for price, method in candidates:
|
||||
touch_band = abs(price) * config.touch_tolerance if price else config.touch_tolerance
|
||||
touches = sum(
|
||||
1
|
||||
for low, high in zip(lows, highs, strict=False)
|
||||
if low - touch_band <= price <= high + touch_band
|
||||
)
|
||||
strength = max(
|
||||
0,
|
||||
min(100, int(round((touches / total_bars) * config.strength_scale))),
|
||||
)
|
||||
raw.append({
|
||||
"price_level": price,
|
||||
"strength": strength,
|
||||
"detection_method": method,
|
||||
"type": "",
|
||||
"sources": [method],
|
||||
"rejection_count": touches,
|
||||
"last_rejection_age": None,
|
||||
"weighted_respects": float(touches),
|
||||
})
|
||||
|
||||
merged: list[dict] = []
|
||||
for level in sorted(raw, key=lambda row: row["price_level"]):
|
||||
if not merged:
|
||||
merged.append(dict(level))
|
||||
continue
|
||||
last = merged[-1]
|
||||
ref = last["price_level"]
|
||||
merge_band = abs(ref) * config.merge_tolerance if ref else config.merge_tolerance
|
||||
if abs(level["price_level"] - ref) > merge_band:
|
||||
merged.append(dict(level))
|
||||
continue
|
||||
last["price_level"] = round(
|
||||
(last["price_level"] + level["price_level"]) / 2.0,
|
||||
4,
|
||||
)
|
||||
last["strength"] = min(100, last["strength"] + level["strength"])
|
||||
sources = set(last.get("sources") or [last["detection_method"]])
|
||||
sources |= set(level.get("sources") or [level["detection_method"]])
|
||||
last["sources"] = sorted(sources)
|
||||
last["detection_method"] = (
|
||||
next(iter(sources)) if len(sources) == 1 else "merged"
|
||||
)
|
||||
last["rejection_count"] = max(
|
||||
int(last.get("rejection_count", 0)),
|
||||
int(level.get("rejection_count", 0)),
|
||||
)
|
||||
|
||||
_tag_levels(merged, closes[-1])
|
||||
merged.sort(key=lambda row: row["strength"], reverse=True)
|
||||
return merged
|
||||
return detect_sr_levels_legacy(
|
||||
highs,
|
||||
lows,
|
||||
closes,
|
||||
[0] * len(closes),
|
||||
tolerance,
|
||||
explicit_range_grid=True,
|
||||
)
|
||||
|
||||
|
||||
def _merge_levels(
|
||||
|
||||
Reference in New Issue
Block a user