Files
PolyWeather/src/strategy/position_manager.py
T

154 lines
4.7 KiB
Python

from loguru import logger
class PositionManager:
"""
仓位管理 - Kelly公式动态仓位计算
"""
def __init__(self, config=None):
self.config = config or {}
self.max_position_ratio = self.config.get("max_position_ratio", 0.25) # 最大单笔25%
self.max_total_exposure = self.config.get("max_total_exposure", 0.80) # 最大总仓位80%
self.min_trade_size = self.config.get("min_trade_size", 10) # 最小交易额$10
logger.info("Initializing Position Manager...")
def kelly_criterion(self, win_prob: float, odds: float) -> float:
"""
Kelly公式计算最优投资比例
f = (bp - q) / b
f: 应投资的资金比例
b: 赔率 (盈利/亏损)
p: 胜率
q: 败率 (1-p)
Args:
win_prob: 预测胜率 (0-1)
odds: 赔率
Returns:
float: 建议投资比例 (0-1)
"""
if win_prob <= 0 or win_prob >= 1 or odds <= 0:
return 0.0
q = 1 - win_prob
f = (win_prob * odds - q) / odds
# 限制最大仓位
f = max(0, min(f, self.max_position_ratio))
logger.debug(f"Kelly ratio: {f:.4f} (win_prob={win_prob:.2f}, odds={odds:.2f})")
return f
def calculate_position_size(self,
total_capital: float,
win_prob: float,
market_price: float,
current_exposure: float = 0) -> dict:
"""
计算建议仓位大小
Args:
total_capital: 总资金
win_prob: 模型预测胜率
market_price: 当前市场价格 (0-1)
current_exposure: 当前已有仓位占比
Returns:
dict: 包含建议仓位大小和相关信息
"""
# 计算赔率
if market_price <= 0 or market_price >= 1:
return {"size": 0, "error": "Invalid market price"}
odds = (1 - market_price) / market_price
# Kelly计算
kelly_ratio = self.kelly_criterion(win_prob, odds)
# 检查总仓位限制
available_ratio = self.max_total_exposure - current_exposure
if available_ratio <= 0:
return {
"size": 0,
"kelly_ratio": kelly_ratio,
"reason": "Max exposure reached"
}
# 实际使用比例
actual_ratio = min(kelly_ratio, available_ratio)
# 计算金额
position_size = total_capital * actual_ratio
# 检查最小交易额
if position_size < self.min_trade_size:
return {
"size": 0,
"kelly_ratio": kelly_ratio,
"reason": f"Below minimum trade size (${self.min_trade_size})"
}
return {
"size": position_size,
"kelly_ratio": kelly_ratio,
"actual_ratio": actual_ratio,
"odds": odds,
"expected_return": (win_prob * odds - (1 - win_prob)) * position_size
}
def should_exit(self,
entry_price: float,
current_price: float,
current_prediction: float,
stop_loss: float = 0.15,
take_profit: float = 0.30) -> dict:
"""
判断是否应该平仓
Args:
entry_price: 入场价格
current_price: 当前价格
current_prediction: 当前模型预测
stop_loss: 止损比例
take_profit: 止盈比例
Returns:
dict: 退出建议
"""
if entry_price <= 0:
return {"should_exit": False}
pnl_ratio = (current_price - entry_price) / entry_price
# 止损
if pnl_ratio < -stop_loss:
return {
"should_exit": True,
"reason": "STOP_LOSS",
"pnl_ratio": pnl_ratio
}
# 止盈
if pnl_ratio > take_profit:
return {
"should_exit": True,
"reason": "TAKE_PROFIT",
"pnl_ratio": pnl_ratio
}
# 模型预测反转
if current_prediction < 0.4: # 预测胜率下降
return {
"should_exit": True,
"reason": "PREDICTION_REVERSAL",
"pnl_ratio": pnl_ratio,
"current_prediction": current_prediction
}
return {
"should_exit": False,
"pnl_ratio": pnl_ratio
}