diff --git a/docs/iterations/LEARNINGS.md b/docs/iterations/LEARNINGS.md index d0f175d1..66fe1587 100644 --- a/docs/iterations/LEARNINGS.md +++ b/docs/iterations/LEARNINGS.md @@ -1,6 +1,6 @@ # Learnings Index -**Last analyzed run:** 2026-04-12 +**Last analyzed run:** 2026-04-13 | Domain | File | Last Updated | One-line Summary | |--------|------|--------------|-----------------| @@ -13,7 +13,7 @@ | early_accumulation | scanners/early_accumulation.md | 2026-04-12 | Sub-threshold (score=60); no catalyst → structurally score-capped by ranker | | social_dd | scanners/social_dd.md | 2026-04-12 | Sub-threshold (score=56); BUT 55% 30d win rate — diverges from social_hype; ranker may be suppressing it incorrectly | | volume_accumulation | scanners/volume_accumulation.md | — | No data yet | -| short_squeeze | scanners/short_squeeze.md | — | No data yet — new scanner, research: high SI (>20%) + catalyst = squeeze risk; not a directional signal alone | +| short_squeeze | scanners/short_squeeze.md | 2026-04-13 | First real data: 60% 7d win rate, +2.15% avg 7d (n=10) — best 7d performer; DTC now surfaced in context | ## Research diff --git a/docs/iterations/scanners/short_squeeze.md b/docs/iterations/scanners/short_squeeze.md index 2bdd3784..4a1d9f78 100644 --- a/docs/iterations/scanners/short_squeeze.md +++ b/docs/iterations/scanners/short_squeeze.md @@ -3,18 +3,35 @@ ## Current Understanding Identifies stocks with structurally high short interest (>15% of float by default, CRITICAL at >30%) where short sellers are vulnerable to forced covering on any positive catalyst. The scanner uses -Finviz for discovery (screener filters) + Yahoo Finance for exact SI% verification. +Finviz for discovery (screener filters) + Yahoo Finance for exact SI% and days-to-cover (shortRatio) +verification. Key distinction: High SI alone predicts *negative* long-term returns on average (academic consensus). -The scanner is a squeeze-risk flag, not a directional buy signal. Value comes from cross-scanner -confluence: a stock appearing here AND in options_flow or earnings_calendar is significantly stronger -than either signal alone. +However, first real P&L data (n=10) shows 60% 7d win rate and +2.15% avg 7d — best 7d performer in +the pipeline. This may reflect that discovery-pipeline filtering (technical confirmation, enrichment) +already adds the catalyst signal needed to convert squeeze-risk into a directional trade. Cross-scanner +confluence (short_squeeze + options_flow or earnings_calendar) remains a stronger signal than either +alone and is the primary confluence hypothesis under test. ## Evidence Log -_(populated by /iterate runs)_ +### 2026-04-13 — P&L review (first real outcome data) +- 10 tracked recommendations, 5/10 1d wins (50% win rate), 6/10 7d wins (60% win rate). +- Avg 7d return: +2.15%. This makes short_squeeze the **best 7d performer** among scanners with ≥5 samples. +- Outperforms analyst_upgrade (50% 7d), insider_buying (46.4% 7d), options_flow (45.6% 7d). +- The scanner is producing positive outcomes as a standalone signal, not only as a cross-scanner modifier. +- However, ranker prompt says "Focus on days to cover" but context string only shows SI%. DTC value is available in Yahoo Finance (`shortRatio`) but was not being fetched or passed through — gap confirmed. +- Confidence: medium (small sample n=10; 30d data will be more conclusive; DTC gap has been fixed) + +### 2026-04-13 — Code fix: days_to_cover surfaced in context +- Added `days_to_cover` extraction (`shortRatio` from Yahoo Finance) to `finviz_scraper.py`. +- Applied `min_days_to_cover` filter (previously accepted as parameter but never enforced). +- Updated `short_squeeze.py` context string to include DTC value so ranker can use "days to cover" criterion. +- Confidence: high (this is a clear context gap between ranker criteria and available data) ## Pending Hypotheses - [ ] Does short_squeeze + options_flow confluence produce better 7d win rate than either scanner alone? - [ ] Does short_squeeze + earnings_calendar (SI>20%) produce better outcomes than earnings alone? (See earnings_calendar.md pending hypothesis) - [ ] Is there a volume threshold (e.g., market cap <$2B small-cap) that sharpens the signal? +- [ ] Does DTC >5 (now surfaced in context) predict better outcomes than DTC 2-5 within the scanner? +- [ ] Does standalone short_squeeze (no cross-scanner confluence) continue to outperform at 7d as sample grows? diff --git a/tradingagents/dataflows/discovery/scanners/short_squeeze.py b/tradingagents/dataflows/discovery/scanners/short_squeeze.py index ab532a64..4c36f037 100644 --- a/tradingagents/dataflows/discovery/scanners/short_squeeze.py +++ b/tradingagents/dataflows/discovery/scanners/short_squeeze.py @@ -67,6 +67,7 @@ class ShortSqueezeScanner(BaseScanner): continue si_pct = item.get("short_interest_pct", 0) + dtc = item.get("days_to_cover", 0.0) signal = item.get("signal", "low_squeeze_potential") label = _SIGNAL_LABELS.get(signal, signal) @@ -78,8 +79,9 @@ class ShortSqueezeScanner(BaseScanner): else: priority = Priority.MEDIUM.value + dtc_str = f"{dtc:.1f}" if dtc else "N/A" context = ( - f"Short interest {si_pct:.1f}% of float — {label}" + f"Short interest {si_pct:.1f}% of float, {dtc_str} days to cover — {label}" " | squeeze risk elevates if catalyst arrives" ) @@ -91,6 +93,7 @@ class ShortSqueezeScanner(BaseScanner): "priority": priority, "strategy": self.strategy, "short_interest_pct": si_pct, + "days_to_cover": dtc, "squeeze_signal": signal, } ) diff --git a/tradingagents/dataflows/finviz_scraper.py b/tradingagents/dataflows/finviz_scraper.py index 30b14c6b..82f7e646 100644 --- a/tradingagents/dataflows/finviz_scraper.py +++ b/tradingagents/dataflows/finviz_scraper.py @@ -115,6 +115,15 @@ def get_short_interest( market_cap = info.get("marketCap", 0) volume = info.get("volume", info.get("regularMarketVolume", 0)) + # Days to cover (short ratio): shares short / avg daily volume + days_to_cover = info.get("shortRatio") + if days_to_cover is None or not isinstance(days_to_cover, (int, float)): + days_to_cover = 0.0 + + # Apply days-to-cover filter + if days_to_cover < min_days_to_cover: + return None + # Categorize squeeze potential if short_pct >= 30: signal = "extreme_squeeze_risk" @@ -131,6 +140,7 @@ def get_short_interest( "market_cap": market_cap, "volume": volume, "short_interest_pct": short_pct, + "days_to_cover": days_to_cover, "signal": signal, } except Exception: