fix(options_flow): scan full universe before applying limit, rank by signal strength

Previously the scanner stopped as soon as self.limit candidates were found
from as_completed() futures. Since futures complete in non-deterministic
network-latency order, this was equivalent to random sampling — fast-to-
respond tickers won regardless of how strong their options signal was.

Fix: collect all candidates from the full universe, then sort by options_score
(unusual strike count weighted 1.5x for calls to favor bullish flow) before
applying the limit. The top-N strongest signals are now always returned.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Youssef Aitousarrah 2026-02-21 14:08:19 -08:00
parent 61b731ac28
commit 136fa47645
1 changed files with 9 additions and 5 deletions

View File

@ -85,14 +85,13 @@ class OptionsFlowScanner(BaseScanner):
result = future.result()
if result:
candidates.append(result)
if len(candidates) >= self.limit:
# Cancel remaining futures
for f in futures:
f.cancel()
break
except Exception:
continue
# Sort by signal quality: unusual strike count, then bullish bias
candidates.sort(key=lambda c: c.get("options_score", 0), reverse=True)
candidates = candidates[: self.limit]
logger.info(f"Found {len(candidates)} unusual options flows")
return candidates
@ -193,6 +192,10 @@ class OptionsFlowScanner(BaseScanner):
f"{total_unusual_calls} unusual calls / {total_unusual_puts} unusual puts"
)
# Scoring: unusual strike count + bullish call bias bonus
# Calls weighted 1.5x to favour bullish directional flow
options_score = total_unusual_puts + (total_unusual_calls * 1.5)
return {
"ticker": ticker,
"source": self.name,
@ -203,6 +206,7 @@ class OptionsFlowScanner(BaseScanner):
"unusual_calls": total_unusual_calls,
"unusual_puts": total_unusual_puts,
"best_expiration": best_expiration,
"options_score": options_score,
}
except Exception as e: