| | import logging |
| | from typing import Optional |
| | import numpy as np |
| |
|
| | logger = logging.getLogger(__name__) |
| |
|
| | class BayesianFairValue: |
| | def __init__(self, prior_prob: float = 0.5, prior_confidence: float = 10.0): |
| | """ |
| | Initialize the Bayesian belief. |
| | We model the probability as a Beta distribution Beta(alpha, beta). |
| | prior_prob: Initial guess for probability (e.g. 0.5) |
| | prior_confidence: Total weight of prior (alpha + beta). |
| | """ |
| | self.alpha = prior_prob * prior_confidence |
| | self.beta = (1.0 - prior_prob) * prior_confidence |
| | logger.info(f"Initialized Bayesian Model with Beta({self.alpha:.2f}, {self.beta:.2f})") |
| |
|
| | def get_fair_value(self) -> float: |
| | """Returns the current expected probability (mean of Beta dist).""" |
| | return self.alpha / (self.alpha + self.beta) |
| | |
| | def get_confidence_interval(self, percent: float = 0.95) -> tuple[float, float]: |
| | """Returns bounds for the true probability using a simple approximation.""" |
| | |
| | |
| | mean = self.get_fair_value() |
| | var = (self.alpha * self.beta) / (((self.alpha + self.beta) ** 2) * (self.alpha + self.beta + 1)) |
| | std = np.sqrt(var) |
| | |
| | |
| | z = 1.96 |
| | lower = max(0.0, mean - z * std) |
| | upper = min(1.0, mean + z * std) |
| | return (lower, upper) |
| |
|
| | def update(self, market_implied_prob: float, trade_volume: float, noise_factor: float = 0.1): |
| | """ |
| | Bayesian update based on new market observations. |
| | market_implied_prob: The price of the trade (0 to 1) |
| | trade_volume: Weight of this observation (higher volume = stronger signal) |
| | noise_factor: How much we trust the market (0.0 = perfect trust, 1.0 = ignore market) |
| | |
| | This translates an observation into pseudo-counts for the Beta distribution. |
| | """ |
| | if market_implied_prob <= 0 or market_implied_prob >= 1: |
| | return |
| | |
| | |
| | effective_weight = trade_volume * (1.0 - noise_factor) |
| | |
| | |
| | observed_alpha = market_implied_prob * effective_weight |
| | observed_beta = (1.0 - market_implied_prob) * effective_weight |
| | |
| | self.alpha += observed_alpha |
| | self.beta += observed_beta |
| | |
| | |
| | |
| | decay = 0.99 |
| | self.alpha *= decay |
| | self.beta *= decay |
| | |
| | logger.debug(f"Bayesian Update: observed={market_implied_prob:.4f}, new_fv={self.get_fair_value():.4f}") |
| |
|
| | def evaluate_opportunity(self, current_ask: float, current_bid: float) -> Optional[dict]: |
| | """ |
| | Compare market prices against our Bayesian fair value. |
| | If the market asks for much less than our FV, we buy YES. |
| | If the market bids much more than our FV, we sell YES. |
| | """ |
| | fv = self.get_fair_value() |
| | lower, upper = self.get_confidence_interval() |
| | |
| | |
| | if current_ask < lower: |
| | |
| | edge = fv - current_ask |
| | return {"action": "BUY_YES", "edge": edge, "fair_value": fv} |
| | |
| | if current_bid > upper: |
| | |
| | edge = current_bid - fv |
| | return {"action": "SELL_YES", "edge": edge, "fair_value": fv} |
| | |
| | return None |
| |
|