Selection refinements from clean-universe replay of the trader's criteria

Tested the source trader's numeric selection playbook on the unbiased
(open-knowable) year universe, n=2,643 point-in-time ORB entries:
- Dead band RE-CONFIRMED on clean data (35-75% +0.006, 75-150% -0.066)
  — CLRO/VTAK were anecdotes; band exclusion + squeeze exception stand.
- His $5-10 price sweet spot inverts for our system: edge RISES with
  price ($10-20 best at +0.143); the $1-2 bucket is negative (-0.031,
  n=609). SHIPPED: price floor $1->$2 (population gate band now
  env-driven TRADING_PRICE_MIN/MAX; watcher floor raised to match).
- His rvol>=5 floor is backwards for us: rvol 2-5x at entry +0.346R vs
  >=10x -0.047 (n=818) — crowded spotlight names hurt our entries.
  NOT shipped (conflicts with the squeeze exception's rvol needs;
  logged for forward validation via the new per-signal relative_volume).
- SHIPPED: buyout/merger headlines never rank-boost (price is pegged —
  the CRNX class); keyword suppression in the catalyst multiplier.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
momentum devandClaude Opus 4.8 committed 2026-07-08 18:09:54 -04:00
1 parent 722add6815
commit ff3a4b46fb
3 files changed
+23 -3

No files matched your search

+11
View File
@@ -759,7 +759,18 @@ def main(argv: list[str] | None = None) -> int:
news_rank_gain = float(os.environ.get("NEWS_RANK_BOOST_GAIN", "2.0"))
dilutive_penalty = float(os.environ.get("NEWS_DILUTIVE_PENALTY", "0.1"))
_PEGGED = ("acqui", "merger", "buyout", "to be acquired", "take-private")
def _headline_pegged(sym: str) -> bool:
"""Buyout/merger headlines PEG the price (CRNX 2026-07-07: pinned at
$83.50 by an $85 buyout) — the opposite of a momentum catalyst. Never
rank-boost them."""
h = (news_map.get(sym) or "").lower()
return any(k in h for k in _PEGGED)
def _cat_mult(sym: str, has_headline: bool) -> float:
if has_headline and _headline_pegged(sym):
return 1.0
if not ollama_cfg.catalyst_score_enabled:
return news_boost if has_headline else 1.0
from research.ingestion.news_enrichment import catalyst_score as _cscore
+2 -1
View File
@@ -851,7 +851,8 @@ def test_population_gate_full_band_semantics():
assert ok(gap_pct=2.78, rvol=40.0) is None # LHSW: verified squeeze
assert ok(gap_pct=2.78, rvol=0.4) is not None # split fake: rejected
assert ok(gap_pct=2.78) is not None # unverified squeeze: rejected
assert ok(gap_pct=0.20, price=0.98) is not None # SKIN: below $1
assert ok(gap_pct=0.20, price=0.98) is not None # SKIN: below the floor
assert ok(gap_pct=0.20, price=1.50) is not None # $1-2 bucket: -0.031R/609 unbiased entries
assert ok(gap_pct=0.20, price=21.5) is not None # above $20
assert ok(gap_pct=0.20, price=5.0) is None
assert ok(gap_pct=None) is None # fail-open on missing gap
+10 -2
View File
@@ -113,6 +113,11 @@ class ExecutionSettings:
# 0 = disabled. Fail-open: a signal missing gap/volume data is never blocked.
min_entry_gap_pct: float = 0.0
min_day_volume: float = 0.0
# tradeable price band. Floor raised $1->$2 2026-07-09: the $1-2 bucket ran
# -0.031R over 609 unbiased year-scale entries (worst bucket; spreads are
# brutal sub-$2, and the source trader's own cutoff is ~$2).
price_band_min: float = 2.0
price_band_max: float = 20.0
# UNIFIED ENTRY: run the fast path's shared anti-chase gates (over_extended,
# day-extension, halt) on the LIVE auto path too. These previously ran ONLY on
# the fast trigger path, leaving live auto entries unprotected (see the
@@ -276,6 +281,8 @@ class ExecutionSettings:
entry_confirm_by_minute=int(values.get("TRADING_ENTRY_CONFIRM_BY_MINUTE", "0")),
min_entry_gap_pct=_qfloat(values.get("TRADING_MIN_ENTRY_GAP_PCT", "0.0")),
min_day_volume=float(values.get("TRADING_MIN_DAY_VOLUME", "0.0")),
price_band_min=float(values.get("TRADING_PRICE_MIN", "2.0")),
price_band_max=float(values.get("TRADING_PRICE_MAX", "20.0")),
unified_entry=flag("TRADING_UNIFIED_ENTRY", "1"),
entry_fill_model=values.get("TRADING_ENTRY_FILL_MODEL", "resting"),
liquidity_max_volume_pct=float(
@@ -705,8 +712,9 @@ class TradingExecutionService:
if price is not None:
try:
px = float(price)
if px > 0 and not (1.0 <= px <= 20.0):
return f"price {px:.2f} outside $1-20 band"
lo, hi = self.settings.price_band_min, self.settings.price_band_max
if px > 0 and not (lo <= px <= hi):
return f"price {px:.2f} outside ${lo:g}-{hi:g} band"
except (TypeError, ValueError):
pass
if self.settings.min_day_volume > 0 and cum_volume is not None: