Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- from dataclasses import dataclass
- from typing import Dict, List, Optional, Tuple
- import math
- import time
- # --- Odds helpers ---
- def american_to_decimal(a: int) -> float:
- return 1 + (a / 100.0) if a >= 100 else 1 + (100.0 / abs(a))
- def implied_prob_from_decimal(d: float) -> float:
- return 1.0 / d
- def remove_vig_proportional(book_probs: List[float]) -> List[float]:
- m = sum(book_probs)
- return [p / m for p in book_probs]
- def decimal_from_prob(p: float) -> float:
- return 1.0 / p
- def ev_percent(p_fair: float, dec_price: float) -> float:
- ev = (p_fair * (dec_price - 1.0)) - ((1.0 - p_fair) * 1.0)
- return ev * 100.0
- # --- Domain model ---
- @dataclass
- class Quote:
- book: str
- decimal: float # normalized to decimal internally
- updated_ts: float
- @dataclass
- class OutcomeMarket:
- # outcome_key example: "HOME", "AWAY" or "HOME","DRAW","AWAY"
- outcome_key: str
- quotes: List[Quote] # same outcome across multiple books
- @dataclass
- class EventMarket:
- event_id: str
- sport: str
- league: str
- start_ts: float
- market: str # "MONEYLINE_2WAY" or "MONEYLINE_3WAY"
- outcomes: Dict[str, OutcomeMarket] # e.g. {"HOME": OutcomeMarket(...), "AWAY": ...}
- # --- Core scanning logic ---
- def consensus_fair_prob(outcome: OutcomeMarket) -> Optional[float]:
- """
- Build consensus fair probability by:
- 1) converting each book price -> implied prob (booked),
- 2) removing vig across sibling outcomes at the book-level is ideal,
- 3) averaging no-vig probs across books.
- Here we approximate by averaging booked probs then normalizing across outcomes.
- """
- if not outcome.quotes:
- return None
- # Step 1: collect booked probs per book for this outcome
- booked_probs = [implied_prob_from_decimal(q.decimal) for q in outcome.quotes]
- # We'll return the mean booked prob for this outcome; normalization happens outside.
- return sum(booked_probs) / len(booked_probs)
- def build_consensus_no_vig(event_market: EventMarket) -> Dict[str, float]:
- """
- Approximate consensus:
- - For each outcome, average booked probs across books.
- - Normalize across outcomes to remove vig (consensus-level).
- """
- avg_booked = {}
- for k, om in event_market.outcomes.items():
- p = consensus_fair_prob(om)
- if p is not None:
- avg_booked[k] = p
- # Normalize to remove vig
- probs = list(avg_booked.values())
- keys = list(avg_booked.keys())
- if not probs:
- return {}
- norm = remove_vig_proportional(probs)
- return {k: norm[i] for i, k in enumerate(keys)}
- def best_quote(outcome: OutcomeMarket) -> Optional[Quote]:
- if not outcome.quotes:
- return None
- # For backing an outcome, the HIGHEST decimal price is best.
- return max(outcome.quotes, key=lambda q: q.decimal)
- def scan_event(event_market: EventMarket, ev_threshold_percent: float = 1.0) -> List[Tuple[str, Quote, float, float]]:
- """
- Returns list of (outcome_key, best_quote, p_fair, ev%) that meet threshold.
- """
- results = []
- consensus = build_consensus_no_vig(event_market)
- for k, om in event_market.outcomes.items():
- if k not in consensus:
- continue
- p_fair = consensus[k]
- bq = best_quote(om)
- if not bq:
- continue
- evp = ev_percent(p_fair, bq.decimal)
- if evp >= ev_threshold_percent:
- results.append((k, bq, p_fair, evp))
- # Sort by EV descending
- return sorted(results, key=lambda t: t[3], reverse=True)
- # --- Example usage with mocked data ---
- def demo():
- now = time.time()
- market = EventMarket(
- event_id="NFL-12345",
- sport="football",
- league="NFL",
- start_ts=now + 3600,
- market="MONEYLINE_2WAY",
- outcomes={
- "HOME": OutcomeMarket("HOME", [
- Quote("BookA", 1.91, now), # -110
- Quote("BookB", 1.95, now),
- Quote("BookC", 1.88, now),
- ]),
- "AWAY": OutcomeMarket("AWAY", [
- Quote("BookA", 1.95, now),
- Quote("BookB", 1.91, now),
- Quote("BookC", 1.98, now), # best on AWAY
- ])
- }
- )
- picks = scan_event(market, ev_threshold_percent=1.0)
- for outcome_key, quote, p_fair, evp in picks:
- print(f"{outcome_key} @ {quote.book} {quote.decimal:.2f} | fair p={p_fair:.3f} | EV={evp:.2f}%")
- if __name__ == "__main__":
- demo()
Advertisement
Add Comment
Please, Sign In to add comment