60 lines
1.4 KiB
Python
60 lines
1.4 KiB
Python
|
|
"""Position sizing utilities.
|
||
|
|
|
||
|
|
Provides various sizing methods beyond the basic risk-percentage approach.
|
||
|
|
"""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
from enum import Enum
|
||
|
|
|
||
|
|
|
||
|
|
class SizingMethod(str, Enum):
|
||
|
|
FIXED_RISK = "fixed_risk"
|
||
|
|
FIXED_AMOUNT = "fixed_amount"
|
||
|
|
KELLY = "kelly"
|
||
|
|
|
||
|
|
|
||
|
|
def fixed_risk_size(
|
||
|
|
balance: float,
|
||
|
|
entry_price: float,
|
||
|
|
stop_loss: float,
|
||
|
|
risk_pct: float = 0.02,
|
||
|
|
) -> float:
|
||
|
|
"""Position size = (balance * risk%) / |entry - SL|."""
|
||
|
|
price_risk = abs(entry_price - stop_loss)
|
||
|
|
if price_risk == 0:
|
||
|
|
return 0.0
|
||
|
|
return round((balance * risk_pct) / price_risk, 8)
|
||
|
|
|
||
|
|
|
||
|
|
def fixed_amount_size(
|
||
|
|
amount_usd: float,
|
||
|
|
entry_price: float,
|
||
|
|
) -> float:
|
||
|
|
"""Fixed USD amount converted to asset quantity."""
|
||
|
|
if entry_price <= 0:
|
||
|
|
return 0.0
|
||
|
|
return round(amount_usd / entry_price, 8)
|
||
|
|
|
||
|
|
|
||
|
|
def kelly_size(
|
||
|
|
win_rate: float,
|
||
|
|
avg_win: float,
|
||
|
|
avg_loss: float,
|
||
|
|
balance: float,
|
||
|
|
entry_price: float,
|
||
|
|
fraction: float = 0.5,
|
||
|
|
) -> float:
|
||
|
|
"""Half-Kelly sizing for conservative edge exploitation.
|
||
|
|
|
||
|
|
Kelly% = W - (1-W)/R where R = avg_win/avg_loss
|
||
|
|
We use fraction (default 0.5) of full Kelly for safety.
|
||
|
|
"""
|
||
|
|
if avg_loss == 0 or entry_price <= 0:
|
||
|
|
return 0.0
|
||
|
|
|
||
|
|
r = avg_win / avg_loss
|
||
|
|
kelly_pct = win_rate - (1 - win_rate) / r
|
||
|
|
kelly_pct = max(0.0, min(kelly_pct * fraction, 0.1)) # cap at 10%
|
||
|
|
return round((balance * kelly_pct) / entry_price, 8)
|