diff --git a/tradingagents/dataflows/discovery/scanners/insider_buying.py b/tradingagents/dataflows/discovery/scanners/insider_buying.py index 838421a7..eaac64ba 100644 --- a/tradingagents/dataflows/discovery/scanners/insider_buying.py +++ b/tradingagents/dataflows/discovery/scanners/insider_buying.py @@ -90,6 +90,9 @@ class InsiderBuyingScanner(BaseScanner): else: context = f"{title} {insider_name} purchased {value_str} of {ticker}" + # Scoring: cluster buys > C-suite > dollar value + insider_score = value + (num_insiders * 500_000) + (1_000_000 if is_c_suite else 0) + candidates.append( { "ticker": ticker, @@ -101,11 +104,13 @@ class InsiderBuyingScanner(BaseScanner): "insider_title": title, "transaction_value": value, "num_insiders_buying": num_insiders, + "insider_score": insider_score, } ) - if len(candidates) >= self.limit: - break + # Sort by signal quality, then limit + candidates.sort(key=lambda c: c.get("insider_score", 0), reverse=True) + candidates = candidates[: self.limit] logger.info(f"Insider buying: {len(candidates)} candidates") return candidates diff --git a/tradingagents/dataflows/discovery/scanners/sector_rotation.py b/tradingagents/dataflows/discovery/scanners/sector_rotation.py index 15cf70b1..c35ed9ee 100644 --- a/tradingagents/dataflows/discovery/scanners/sector_rotation.py +++ b/tradingagents/dataflows/discovery/scanners/sector_rotation.py @@ -123,18 +123,20 @@ class SectorRotationScanner(BaseScanner): continue # Step 3: Only call get_ticker_info() for laggard tickers (< 2% 5d move). - # This dramatically reduces API calls from max_tickers down to ~20-30%. - candidates = [] - for ticker, ret_5d in ticker_returns.items(): - if ret_5d > 2.0: - continue # Already moved — not a laggard + # Sort by most-negative return first — best laggards checked before limit. + laggards = [(t, r) for t, r in ticker_returns.items() if r <= 2.0] + laggards.sort(key=lambda x: x[1]) - if len(candidates) >= self.limit: + candidates = [] + api_calls = 0 + max_api_calls = self.limit * 3 # budget for get_ticker_info calls + for ticker, ret_5d in laggards: + if len(candidates) >= self.limit or api_calls >= max_api_calls: break + api_calls += 1 result = self._check_sector_laggard(ticker, accelerating_sectors, get_ticker_info) if result: - # Overwrite ret_5d with the value we already computed result["stock_5d_return"] = round(ret_5d, 2) candidates.append(result) diff --git a/tradingagents/dataflows/discovery/scanners/technical_breakout.py b/tradingagents/dataflows/discovery/scanners/technical_breakout.py index 3c22134b..4dfa03c1 100644 --- a/tradingagents/dataflows/discovery/scanners/technical_breakout.py +++ b/tradingagents/dataflows/discovery/scanners/technical_breakout.py @@ -85,12 +85,12 @@ class TechnicalBreakoutScanner(BaseScanner): result = self._check_breakout(ticker, data) if result: candidates.append(result) - if len(candidates) >= self.limit * 2: - break + # Sort by strongest breakout signal, then limit candidates.sort(key=lambda c: c.get("volume_multiple", 0), reverse=True) + candidates = candidates[: self.limit] logger.info(f"Technical breakouts: {len(candidates)} candidates") - return candidates[: self.limit] + return candidates def _check_breakout(self, ticker: str, data: pd.DataFrame) -> Optional[Dict[str, Any]]: """Check if ticker has a volume-confirmed breakout."""