Add single-command GTL tuning matrix
This commit is contained in:
@@ -93,6 +93,7 @@ def _zone_representative_levels(
|
||||
entry_price: float,
|
||||
*,
|
||||
strength_mode: str = "sum",
|
||||
tolerance: float = _SR_ZONE_TOLERANCE,
|
||||
) -> list[Any]:
|
||||
"""Collapse near-duplicate S/R levels into one representative per zone.
|
||||
|
||||
@@ -124,7 +125,7 @@ def _zone_representative_levels(
|
||||
zones = cluster_sr_zones(
|
||||
level_dicts,
|
||||
entry_price,
|
||||
tolerance=_SR_ZONE_TOLERANCE,
|
||||
tolerance=tolerance,
|
||||
strength_mode=strength_mode,
|
||||
)
|
||||
|
||||
@@ -315,9 +316,16 @@ class TargetGenerator:
|
||||
stop_loss: float,
|
||||
sr_levels: list[SRLevel],
|
||||
atr_value: float,
|
||||
*,
|
||||
max_targets: int | None = 5,
|
||||
max_atr_multiple_override: float | None = None,
|
||||
) -> list[dict[str, Any]]:
|
||||
if atr_value <= 0:
|
||||
return []
|
||||
if max_targets is not None and max_targets < 1:
|
||||
raise ValueError("max_targets must be positive or None")
|
||||
if max_atr_multiple_override is not None and max_atr_multiple_override <= 0:
|
||||
raise ValueError("max_atr_multiple_override must be positive or None")
|
||||
|
||||
risk = abs(entry_price - stop_loss)
|
||||
if risk <= 0:
|
||||
@@ -326,11 +334,12 @@ class TargetGenerator:
|
||||
candidates: list[dict[str, Any]] = []
|
||||
atr_pct = atr_value / entry_price if entry_price > 0 else 0.0
|
||||
|
||||
max_atr_multiple: float | None = None
|
||||
if atr_pct > 0.05:
|
||||
max_atr_multiple = 10.0
|
||||
elif atr_pct < 0.02:
|
||||
max_atr_multiple = 3.0
|
||||
max_atr_multiple: float | None = max_atr_multiple_override
|
||||
if max_atr_multiple is None:
|
||||
if atr_pct > 0.05:
|
||||
max_atr_multiple = 10.0
|
||||
elif atr_pct < 0.02:
|
||||
max_atr_multiple = 3.0
|
||||
|
||||
for level in sr_levels:
|
||||
is_candidate = False
|
||||
@@ -383,6 +392,12 @@ class TargetGenerator:
|
||||
if not candidates:
|
||||
return []
|
||||
|
||||
if max_targets is None:
|
||||
candidates.sort(key=lambda row: row["distance_from_entry"])
|
||||
for target in candidates:
|
||||
target.pop("quality", None)
|
||||
return candidates
|
||||
|
||||
# Select up to 5 targets that SPAN the distance range, instead of the
|
||||
# top-5 by quality (which biases toward far, high-R:R levels and buries
|
||||
# every nearby target). Guarantees the nearest level plus a
|
||||
@@ -396,6 +411,8 @@ class TargetGenerator:
|
||||
selected_ids: set[int] = set()
|
||||
|
||||
def _add(candidate: dict[str, Any] | None) -> None:
|
||||
if len(selected) >= max_targets:
|
||||
return
|
||||
if candidate is not None and candidate["sr_level_id"] not in selected_ids:
|
||||
selected.append(candidate)
|
||||
selected_ids.add(candidate["sr_level_id"])
|
||||
@@ -408,7 +425,7 @@ class TargetGenerator:
|
||||
_add(max(bucket, key=lambda c: c["quality"]))
|
||||
# Fill remaining slots with the next-best by quality
|
||||
for candidate in sorted(candidates, key=lambda c: c["quality"], reverse=True):
|
||||
if len(selected) >= 5:
|
||||
if len(selected) >= max_targets:
|
||||
break
|
||||
_add(candidate)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user