You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
74 lines
2.3 KiB
Python
74 lines
2.3 KiB
Python
"""TGTC pre-screen: filter Yahoo snapshot candidates using prior-day enrichment."""
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
|
|
from libs.tgtc.domain import TGTCFilterParams
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
def pre_screen_candidates(
|
|
enrichment: dict[str, dict],
|
|
filters: TGTCFilterParams,
|
|
) -> set[str]:
|
|
"""Phase 1 pre-screen using prior-day enrichment (no intraday data).
|
|
|
|
Args:
|
|
enrichment: {symbol: {atr_14, avg_dollar_vol_30d, prev_close, ...}}
|
|
filters: TGTCFilterParams instance
|
|
|
|
Returns:
|
|
Set of symbols passing the pre-screen.
|
|
"""
|
|
passed: set[str] = set()
|
|
for sym, enr in enrichment.items():
|
|
prev_close = enr.get("prev_close") or 0.0
|
|
if prev_close < filters.min_price:
|
|
continue
|
|
avg_dv = enr.get("avg_dollar_vol_30d") or enr.get("avg_dollar_vol_20d") or 0.0
|
|
if avg_dv < filters.min_avg_dollar_volume_20d:
|
|
continue
|
|
passed.add(sym)
|
|
|
|
log.debug("TGTC pre-screen: %d/%d passed", len(passed), len(enrichment))
|
|
return passed
|
|
|
|
|
|
def apply_10am_hard_filters(
|
|
candidates: list[dict],
|
|
filters: TGTCFilterParams,
|
|
) -> list[dict]:
|
|
"""Phase 2 hard filters applied at 10:00 ET using intraday data.
|
|
|
|
candidates: list of dicts with keys:
|
|
symbol, pct_change_at_10, price_at_10, vwap_at_10, above_vwap,
|
|
hod_at_10, avg_dv, market_cap (optional)
|
|
|
|
Returns filtered list.
|
|
"""
|
|
out = []
|
|
for c in candidates:
|
|
price = c.get("price_at_10", 0.0) or 0.0
|
|
pct = c.get("pct_change_at_10", 0.0) or 0.0
|
|
above_vwap = c.get("above_vwap", False)
|
|
hod = c.get("hod_at_10", price) or price
|
|
avg_dv = c.get("avg_dv", 0.0) or 0.0
|
|
market_cap = c.get("market_cap")
|
|
|
|
if price < filters.min_price:
|
|
continue
|
|
if pct < filters.min_day_change_at_10 or pct > filters.max_day_change_at_10:
|
|
continue
|
|
if filters.must_be_above_vwap and not above_vwap:
|
|
continue
|
|
if market_cap is not None and market_cap < filters.min_market_cap:
|
|
continue
|
|
elif market_cap is None and avg_dv < filters.min_avg_dollar_volume_20d:
|
|
continue
|
|
if hod > 0 and price > 0 and (hod - price) / hod > filters.max_pullback_from_hod:
|
|
continue
|
|
|
|
out.append(c)
|
|
return out
|