arbintel / src /models /bayesian.py
AJAY KASU
Add root app.py for Streamlit GUI and dependencies
77fd2f6
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."""
# For simplicity, we can use scipy.stats.beta.interval, but to avoid slow imports inline
# we'll return a simple variance based bound.
mean = self.get_fair_value()
var = (self.alpha * self.beta) / (((self.alpha + self.beta) ** 2) * (self.alpha + self.beta + 1))
std = np.sqrt(var)
# Approximate 95% CI (roughly 1.96 std devs)
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 # Ignore garbage data
# The higher the volume and lower the noise, the more "weight" this update has
effective_weight = trade_volume * (1.0 - noise_factor)
# Add pseudo-counts
observed_alpha = market_implied_prob * effective_weight
observed_beta = (1.0 - market_implied_prob) * effective_weight
self.alpha += observed_alpha
self.beta += observed_beta
# To prevent the belief from becoming too stubborn over time (infinite confidence),
# we can decay older beliefs.
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()
# Only trade if the price is outside our confidence interval
if current_ask < lower:
# Market is underpricing YES
edge = fv - current_ask
return {"action": "BUY_YES", "edge": edge, "fair_value": fv}
if current_bid > upper:
# Market is overpricing YES
edge = current_bid - fv
return {"action": "SELL_YES", "edge": edge, "fair_value": fv}
return None