初步完成项目,可以监控给出入场信号
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
"""Kelly position sizer with Favorite-Longshot bias correction.
|
||||
|
||||
Kelly formula (half-Kelly by default for safety):
|
||||
p = estimated win probability (from signal strength)
|
||||
b = payoff ratio: (1 - price)/price for BUY, price/(1-price) for SELL
|
||||
q = 1 - p
|
||||
f* = (b * p - q) / b
|
||||
use = f* * kelly_fraction (default 0.5 → half-Kelly)
|
||||
|
||||
Favorite-Longshot bias correction (research-driven):
|
||||
factor = (1 - 2 * |price - 0.5|)^beta
|
||||
Edge at extreme prices (<0.10 or >0.90) is reduced.
|
||||
"""
|
||||
import math
|
||||
|
||||
from src.config import get_settings
|
||||
|
||||
|
||||
class KellySizer:
|
||||
"""Position sizing per the research framework."""
|
||||
|
||||
def __init__(self):
|
||||
self.settings = get_settings()
|
||||
|
||||
def win_probability(self, strength: float) -> float:
|
||||
"""Map signal strength [0,1] → win probability.
|
||||
|
||||
strength=0.5 → p=0.55 (baseline)
|
||||
strength=1.0 → p=0.85 (strong consensus)
|
||||
strength=0.0 → p=0.50 (coin flip)
|
||||
"""
|
||||
strength = max(0.0, min(1.0, strength))
|
||||
return 0.50 + 0.35 * strength
|
||||
|
||||
def payoff_ratio(self, price: float, side: str) -> float:
|
||||
"""How much we win vs how much we risk."""
|
||||
p = max(0.01, min(0.99, price))
|
||||
if side == "BUY":
|
||||
return (1.0 - p) / p # win=(1-p), risk=p
|
||||
# SELL: assume we already hold the position at avg price p, hedge at current
|
||||
return p / (1.0 - p)
|
||||
|
||||
def favorite_longshot_correction(self, price: float, beta: float = 1.5) -> float:
|
||||
"""Smooth penalty for extreme prices. 1.0 at price=0.5, ~0 at extremes."""
|
||||
return (1.0 - 2.0 * abs(price - 0.5)) ** beta
|
||||
|
||||
def fraction(
|
||||
self,
|
||||
signal_strength: float,
|
||||
price: float,
|
||||
side: str,
|
||||
beta: float = 1.5,
|
||||
) -> float:
|
||||
"""Compute Kelly fraction (capped 0..1) for a single signal."""
|
||||
p = self.win_probability(signal_strength)
|
||||
q = 1.0 - p
|
||||
b = self.payoff_ratio(price, side)
|
||||
|
||||
f_star = max(0.0, (b * p - q) / b)
|
||||
f_star *= self.favorite_longshot_correction(price, beta)
|
||||
f_star *= self.settings.kelly_fraction
|
||||
|
||||
return min(f_star, self.settings.max_position_pct)
|
||||
|
||||
def position_usd(self, fraction: float, capital: float) -> float:
|
||||
"""Translate fraction → dollar size, capped by per-trade position limit."""
|
||||
size = fraction * capital
|
||||
max_size = capital * self.settings.max_position_pct
|
||||
return min(size, max_size)
|
||||
Reference in New Issue
Block a user