Refactor: restructure market module with services, stores, and utils
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
策略服务模块
|
||||
"""
|
||||
|
||||
from .strategy_service import StrategyService
|
||||
from .risk_manager import RiskManager
|
||||
|
||||
__all__ = [
|
||||
'StrategyService',
|
||||
'RiskManager',
|
||||
]
|
||||
@@ -0,0 +1,257 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
风险管理服务
|
||||
"""
|
||||
|
||||
from typing import Dict, Optional
|
||||
from datetime import datetime
|
||||
|
||||
from ...models import TradingStrategy
|
||||
|
||||
|
||||
class RiskManager:
|
||||
"""风险管理服务"""
|
||||
|
||||
def __init__(self):
|
||||
# 账户信息(从外部更新)
|
||||
self._account_balance: float = 0.0
|
||||
self._account_equity: float = 0.0
|
||||
self._free_margin: float = 0.0
|
||||
|
||||
# 每日风险限制
|
||||
self._daily_risk_limit: float = 5.0 # 每日最大风险百分比
|
||||
self._daily_risk_used: float = 0.0 # 今日已使用风险
|
||||
|
||||
# 品种配置(点值、最小手数等)
|
||||
self._symbol_config: Dict[str, Dict] = {}
|
||||
|
||||
# 统计服务引用(用于获取账户信息)
|
||||
self._statistics_service = None
|
||||
|
||||
print("[RiskManager] 风险管理服务已初始化")
|
||||
|
||||
def set_statistics_service(self, service) -> None:
|
||||
"""设置统计服务引用"""
|
||||
self._statistics_service = service
|
||||
|
||||
def _refresh_account_info(self) -> None:
|
||||
"""从统计服务刷新账户信息"""
|
||||
if not self._statistics_service:
|
||||
return
|
||||
|
||||
try:
|
||||
account_info = self._statistics_service.get_account_info()
|
||||
if account_info:
|
||||
self._account_balance = account_info.get('balance', 0.0)
|
||||
self._account_equity = account_info.get('equity', 0.0)
|
||||
# free_margin 通常等于 equity - used_margin,这里用 equity 近似
|
||||
self._free_margin = account_info.get('equity', 0.0)
|
||||
except Exception as e:
|
||||
print(f"[RiskManager] 刷新账户信息失败: {e}")
|
||||
|
||||
# ==================== 账户信息 ====================
|
||||
|
||||
def update_account_info(self, balance: float, equity: float, free_margin: float) -> None:
|
||||
"""更新账户信息"""
|
||||
self._account_balance = balance
|
||||
self._account_equity = equity
|
||||
self._free_margin = free_margin
|
||||
|
||||
def get_account_balance(self) -> float:
|
||||
"""获取账户余额"""
|
||||
return self._account_balance
|
||||
|
||||
def get_account_equity(self) -> float:
|
||||
"""获取账户权益"""
|
||||
return self._account_equity
|
||||
|
||||
# ==================== 品种配置 ====================
|
||||
|
||||
def set_symbol_config(self, symbol: str, config: Dict) -> None:
|
||||
"""设置品种配置"""
|
||||
self._symbol_config[symbol] = config
|
||||
|
||||
def get_symbol_config(self, symbol: str) -> Dict:
|
||||
"""获取品种配置"""
|
||||
return self._symbol_config.get(symbol, {
|
||||
"point_value": 1.0, # 点值
|
||||
"min_volume": 0.01, # 最小手数
|
||||
"max_volume": 10.0, # 最大手数
|
||||
"volume_step": 0.01, # 手数步长
|
||||
})
|
||||
|
||||
# ==================== 手数计算 ====================
|
||||
|
||||
def calculate_volume(self, symbol: str, risk_points: float,
|
||||
strategy: TradingStrategy) -> float:
|
||||
"""
|
||||
计算交易手数
|
||||
|
||||
Args:
|
||||
symbol: 品种
|
||||
risk_points: 风险点数
|
||||
strategy: 策略配置
|
||||
|
||||
Returns:
|
||||
计算的手数
|
||||
"""
|
||||
config = self.get_symbol_config(symbol)
|
||||
point_value = config.get('point_value', 1.0)
|
||||
min_volume = config.get('min_volume', 0.01)
|
||||
max_volume = config.get('max_volume', 10.0)
|
||||
volume_step = config.get('volume_step', 0.01)
|
||||
|
||||
if strategy.volume_mode == "fixed":
|
||||
volume = strategy.fixed_volume
|
||||
elif strategy.volume_mode == "risk_percent":
|
||||
# 根据风险百分比计算手数
|
||||
risk_amount = self._account_balance * (strategy.risk_percent / 100)
|
||||
# 手数 = 风险金额 / (风险点数 * 点值)
|
||||
if risk_points > 0 and point_value > 0:
|
||||
volume = risk_amount / (risk_points * point_value)
|
||||
else:
|
||||
volume = min_volume
|
||||
else:
|
||||
volume = strategy.fixed_volume
|
||||
|
||||
# 应用最大风险点数限制
|
||||
if risk_points > strategy.max_risk_points:
|
||||
print(f"[RiskManager] 风险点数 {risk_points} 超过最大限制 {strategy.max_risk_points}")
|
||||
return 0.0
|
||||
|
||||
# 限制手数范围
|
||||
volume = max(min_volume, min(volume, max_volume))
|
||||
|
||||
# 按步长取整
|
||||
volume = round(volume / volume_step) * volume_step
|
||||
|
||||
return volume
|
||||
|
||||
# ==================== 风险检查 ====================
|
||||
|
||||
def check_risk(self, symbol: str, volume: float, risk_points: float) -> Dict:
|
||||
"""
|
||||
检查交易风险
|
||||
|
||||
Args:
|
||||
symbol: 品种
|
||||
volume: 手数
|
||||
risk_points: 风险点数
|
||||
|
||||
Returns:
|
||||
检查结果
|
||||
"""
|
||||
# 刷新账户信息
|
||||
self._refresh_account_info()
|
||||
|
||||
config = self.get_symbol_config(symbol)
|
||||
point_value = config.get('point_value', 1.0)
|
||||
|
||||
# 计算风险金额
|
||||
risk_amount = volume * risk_points * point_value
|
||||
risk_percent = (risk_amount / self._account_balance * 100) if self._account_balance > 0 else 0
|
||||
|
||||
# 检查每日风险限制
|
||||
remaining_risk = self._daily_risk_limit - self._daily_risk_used
|
||||
|
||||
allowed = True
|
||||
warnings = []
|
||||
|
||||
# 账户信息是否已初始化
|
||||
account_initialized = self._account_balance > 0 or self._free_margin > 0
|
||||
|
||||
if risk_percent > 5:
|
||||
allowed = False
|
||||
warnings.append(f"单笔风险 {risk_percent:.2f}% 超过5%")
|
||||
|
||||
if risk_percent + self._daily_risk_used > self._daily_risk_limit:
|
||||
allowed = False
|
||||
warnings.append(f"将超过每日风险限制 {self._daily_risk_limit}%")
|
||||
|
||||
# 只有账户信息已初始化时才检查保证金
|
||||
if account_initialized and self._free_margin < risk_amount:
|
||||
allowed = False
|
||||
warnings.append(f"保证金不足 (可用: {self._free_margin:.2f}, 需要: {risk_amount:.2f})")
|
||||
|
||||
if not account_initialized:
|
||||
warnings.append("账户信息未初始化,跳过保证金检查")
|
||||
|
||||
return {
|
||||
"allowed": allowed,
|
||||
"risk_amount": risk_amount,
|
||||
"risk_percent": round(risk_percent, 2),
|
||||
"daily_risk_used": self._daily_risk_used,
|
||||
"daily_risk_limit": self._daily_risk_limit,
|
||||
"remaining_risk": remaining_risk,
|
||||
"warnings": warnings,
|
||||
"account_initialized": account_initialized,
|
||||
}
|
||||
|
||||
# ==================== 持仓检查 ====================
|
||||
|
||||
def check_position_limit(self, symbol: str, strategy: TradingStrategy,
|
||||
current_positions: int, same_direction: int,
|
||||
opposite_direction: int, action: str) -> Dict:
|
||||
"""
|
||||
检查持仓限制
|
||||
|
||||
Args:
|
||||
symbol: 品种
|
||||
strategy: 策略配置
|
||||
current_positions: 当前持仓数
|
||||
same_direction: 同向持仓数
|
||||
opposite_direction: 反向持仓数
|
||||
action: 交易方向 buy/sell
|
||||
|
||||
Returns:
|
||||
检查结果
|
||||
"""
|
||||
allowed = True
|
||||
warnings = []
|
||||
|
||||
# 检查最大持仓数
|
||||
if current_positions >= strategy.max_positions:
|
||||
allowed = False
|
||||
warnings.append(f"已达到最大持仓数 {strategy.max_positions}")
|
||||
|
||||
# 检查同向持仓
|
||||
new_same_direction = same_direction + 1
|
||||
if new_same_direction > strategy.max_same_direction:
|
||||
allowed = False
|
||||
warnings.append(f"同向持仓将超过限制 {strategy.max_same_direction}")
|
||||
|
||||
# 检查持仓冲突策略
|
||||
if opposite_direction > 0:
|
||||
if strategy.position_conflict == "block":
|
||||
allowed = False
|
||||
warnings.append("有反向持仓,策略禁止新开仓")
|
||||
elif strategy.position_conflict == "allow_same":
|
||||
allowed = False
|
||||
warnings.append("有反向持仓,策略只允许同向加仓")
|
||||
elif strategy.position_conflict == "allow_opposite":
|
||||
# 允许反向
|
||||
pass
|
||||
|
||||
return {
|
||||
"allowed": allowed,
|
||||
"current_positions": current_positions,
|
||||
"same_direction": same_direction,
|
||||
"opposite_direction": opposite_direction,
|
||||
"max_positions": strategy.max_positions,
|
||||
"max_same_direction": strategy.max_same_direction,
|
||||
"warnings": warnings,
|
||||
}
|
||||
|
||||
# ==================== 状态 ====================
|
||||
|
||||
def get_status(self) -> Dict:
|
||||
"""获取状态"""
|
||||
return {
|
||||
"account_balance": self._account_balance,
|
||||
"account_equity": self._account_equity,
|
||||
"free_margin": self._free_margin,
|
||||
"daily_risk_limit": self._daily_risk_limit,
|
||||
"daily_risk_used": self._daily_risk_used,
|
||||
"symbol_count": len(self._symbol_config),
|
||||
}
|
||||
@@ -0,0 +1,456 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
策略决策服务
|
||||
综合信号、持仓、资金等做出交易决策
|
||||
"""
|
||||
|
||||
from typing import List, Dict, Optional
|
||||
from datetime import datetime
|
||||
import threading
|
||||
|
||||
from ...models import TradingSignal, TradingStrategy, TradingDecision
|
||||
from ...models import ConsistencyRequirement, ConflictResolution
|
||||
from ...models import StopLossMode, TakeProfitMode
|
||||
from ...store import StrategyStore
|
||||
from ..signal import SignalService
|
||||
from .risk_manager import RiskManager
|
||||
|
||||
|
||||
class StrategyService:
|
||||
"""策略决策服务"""
|
||||
|
||||
def __init__(self, strategy_store: StrategyStore = None,
|
||||
signal_service: SignalService = None,
|
||||
risk_manager: RiskManager = None):
|
||||
self.strategy_store = strategy_store or StrategyStore()
|
||||
self.signal_service = signal_service or SignalService()
|
||||
self.risk_manager = risk_manager or RiskManager()
|
||||
|
||||
# 持仓服务引用(外部设置)
|
||||
self._position_service = None
|
||||
|
||||
# 待确认订单服务引用(外部设置)
|
||||
self._pending_order_service = None
|
||||
|
||||
# 决策冷却
|
||||
self._decision_cooldowns: Dict[str, datetime] = {}
|
||||
self._cooldown_lock = threading.Lock()
|
||||
self.decision_cooldown = 60 # 60秒冷却
|
||||
|
||||
print("[StrategyService] 策略决策服务已初始化")
|
||||
|
||||
def set_position_service(self, service) -> None:
|
||||
"""设置持仓服务"""
|
||||
self._position_service = service
|
||||
|
||||
def set_pending_order_service(self, service) -> None:
|
||||
"""设置待确认订单服务"""
|
||||
self._pending_order_service = service
|
||||
|
||||
# ==================== 策略配置 ====================
|
||||
|
||||
def get_strategy(self, symbol: str) -> TradingStrategy:
|
||||
"""获取品种策略配置"""
|
||||
return self.strategy_store.get_or_create_strategy(symbol)
|
||||
|
||||
def update_strategy(self, symbol: str, data: Dict) -> TradingStrategy:
|
||||
"""更新策略配置"""
|
||||
return self.strategy_store.update_strategy(symbol, data)
|
||||
|
||||
def get_all_strategies(self) -> List[TradingStrategy]:
|
||||
"""获取所有策略"""
|
||||
return self.strategy_store.get_all_strategies()
|
||||
|
||||
# ==================== 信号综合分析 ====================
|
||||
|
||||
def analyze_signals(self, symbol: str, signals: List[TradingSignal],
|
||||
strategy: TradingStrategy) -> Dict:
|
||||
"""
|
||||
综合分析信号
|
||||
|
||||
Args:
|
||||
symbol: 品种
|
||||
signals: 信号列表
|
||||
strategy: 策略配置
|
||||
|
||||
Returns:
|
||||
分析结果
|
||||
"""
|
||||
if not signals:
|
||||
return {
|
||||
"total_count": 0,
|
||||
"buy_count": 0,
|
||||
"sell_count": 0,
|
||||
"buy_weighted_score": 0,
|
||||
"sell_weighted_score": 0,
|
||||
"consistency": 0,
|
||||
"direction": None,
|
||||
"action": "none",
|
||||
}
|
||||
|
||||
# 过滤掉未启用的信号
|
||||
filtered_signals = []
|
||||
for s in signals:
|
||||
period = s.source_period if s.source != "key_level" else None
|
||||
if strategy.is_signal_enabled(s.source, period):
|
||||
filtered_signals.append(s)
|
||||
|
||||
if not filtered_signals:
|
||||
return {
|
||||
"total_count": 0,
|
||||
"buy_count": 0,
|
||||
"sell_count": 0,
|
||||
"buy_weighted_score": 0,
|
||||
"sell_weighted_score": 0,
|
||||
"consistency": 0,
|
||||
"direction": None,
|
||||
"action": "none",
|
||||
"filtered_out": len(signals),
|
||||
}
|
||||
|
||||
buy_signals = [s for s in filtered_signals if s.action == "buy"]
|
||||
sell_signals = [s for s in filtered_signals if s.action == "sell"]
|
||||
|
||||
# 计算加权分数(使用新的周期级别权重)
|
||||
buy_score = sum(
|
||||
s.confidence * strategy.get_signal_weight(s.source, s.source_period) / 100
|
||||
for s in buy_signals
|
||||
)
|
||||
sell_score = sum(
|
||||
s.confidence * strategy.get_signal_weight(s.source, s.source_period) / 100
|
||||
for s in sell_signals
|
||||
)
|
||||
|
||||
# 计算一致性
|
||||
total = len(filtered_signals)
|
||||
majority_count = max(len(buy_signals), len(sell_signals))
|
||||
consistency = majority_count / total if total > 0 else 0
|
||||
|
||||
# 确定方向
|
||||
direction = None
|
||||
if buy_score > sell_score:
|
||||
direction = "buy"
|
||||
elif sell_score > buy_score:
|
||||
direction = "sell"
|
||||
|
||||
# 检查一致性要求
|
||||
action = "none"
|
||||
if direction:
|
||||
if strategy.consistency_requirement == ConsistencyRequirement.ANY:
|
||||
action = direction
|
||||
elif strategy.consistency_requirement == ConsistencyRequirement.MAJORITY:
|
||||
if consistency >= 0.5:
|
||||
action = direction
|
||||
elif strategy.consistency_requirement == ConsistencyRequirement.ALL:
|
||||
if consistency == 1.0:
|
||||
action = direction
|
||||
|
||||
return {
|
||||
"total_count": total,
|
||||
"buy_count": len(buy_signals),
|
||||
"sell_count": len(sell_signals),
|
||||
"buy_weighted_score": round(buy_score, 2),
|
||||
"sell_weighted_score": round(sell_score, 2),
|
||||
"consistency": round(consistency, 2),
|
||||
"direction": direction,
|
||||
"action": action,
|
||||
"buy_signals": [s.signal_id for s in buy_signals],
|
||||
"sell_signals": [s.signal_id for s in sell_signals],
|
||||
"filtered_out": len(signals) - len(filtered_signals),
|
||||
}
|
||||
|
||||
# ==================== 决策生成 ====================
|
||||
|
||||
def make_decision(self, symbol: str, current_price: float,
|
||||
force_signals: List[TradingSignal] = None) -> Optional[TradingDecision]:
|
||||
"""
|
||||
做出交易决策
|
||||
|
||||
Args:
|
||||
symbol: 品种
|
||||
current_price: 当前价格
|
||||
force_signals: 强制使用的信号(用于测试)
|
||||
|
||||
Returns:
|
||||
TradingDecision 或 None
|
||||
"""
|
||||
# 检查决策冷却
|
||||
if self._is_in_cooldown(symbol):
|
||||
return None
|
||||
|
||||
# 获取策略配置
|
||||
strategy = self.get_strategy(symbol)
|
||||
if not strategy.enabled:
|
||||
return None
|
||||
|
||||
# 获取信号
|
||||
signals = force_signals if force_signals else self.signal_service.get_active_signals(symbol)
|
||||
|
||||
# 过滤低置信度信号
|
||||
signals = [s for s in signals if s.confidence >= strategy.min_confidence]
|
||||
|
||||
if not signals:
|
||||
return None
|
||||
|
||||
# 分析信号
|
||||
analysis = self.analyze_signals(symbol, signals, strategy)
|
||||
|
||||
if analysis["action"] == "none":
|
||||
return None
|
||||
|
||||
action = analysis["action"]
|
||||
|
||||
# 选择最佳信号(用于止损止盈)
|
||||
best_signal = self._select_best_signal(signals, action, strategy)
|
||||
if not best_signal:
|
||||
return None
|
||||
|
||||
# 计算止损止盈
|
||||
entry_price = current_price
|
||||
sl, tp = self._calculate_sl_tp(entry_price, best_signal, strategy)
|
||||
|
||||
if not sl or not tp or sl == 0 or tp == 0:
|
||||
print(f"[StrategyService] 无效的止损止盈: sl={sl}, tp={tp}")
|
||||
return None
|
||||
|
||||
# 计算风险
|
||||
risk_points = abs(entry_price - sl)
|
||||
reward_points = abs(tp - entry_price)
|
||||
rr_ratio = reward_points / risk_points if risk_points > 0 else 0
|
||||
|
||||
# 检查风险回报比
|
||||
if rr_ratio < strategy.min_risk_reward:
|
||||
print(f"[StrategyService] 风险回报比 {rr_ratio:.2f} 低于最小要求 {strategy.min_risk_reward}")
|
||||
return None
|
||||
|
||||
# 动态止损范围(根据价格调整)
|
||||
# 最小止损 = 价格的 0.05% 或 5 点(取较大)
|
||||
# 最大止损 = 价格的 2% 或 100 点(取较小)
|
||||
price_min_sl = entry_price * 0.0005 # 价格的 0.05%
|
||||
price_max_sl = entry_price * 0.02 # 价格的 2%
|
||||
|
||||
# 确保 min <= max
|
||||
dynamic_min_sl = max(1.0, price_min_sl) # 最小至少 1 点
|
||||
dynamic_max_sl = max(dynamic_min_sl, price_max_sl) # 最大至少等于最小
|
||||
|
||||
# 如果动态范围不合理,跳过
|
||||
if dynamic_min_sl > dynamic_max_sl:
|
||||
print(f"[StrategyService] 动态止损范围无效: [{dynamic_min_sl:.2f}, {dynamic_max_sl:.2f}], 跳过决策")
|
||||
return None
|
||||
|
||||
# 检查止损点数
|
||||
if risk_points < dynamic_min_sl or risk_points > dynamic_max_sl:
|
||||
print(f"[StrategyService] 止损点数 {risk_points:.2f} 不在动态范围 [{dynamic_min_sl:.2f}, {dynamic_max_sl:.2f}] (价格={entry_price:.2f})")
|
||||
return None
|
||||
|
||||
# 计算手数
|
||||
volume = self.risk_manager.calculate_volume(symbol, risk_points, strategy)
|
||||
if volume <= 0:
|
||||
return None
|
||||
|
||||
# 检查持仓限制
|
||||
position_check = self._check_position_limits(symbol, strategy, action)
|
||||
|
||||
# 检查风险限制
|
||||
risk_check = self.risk_manager.check_risk(symbol, volume, risk_points)
|
||||
|
||||
# 如果检查不通过,返回拒绝的决策
|
||||
if not position_check.get("allowed", True) or not risk_check.get("allowed", True):
|
||||
# 即使被拒绝也要设置冷却,避免频繁推送
|
||||
self._set_cooldown(symbol)
|
||||
decision = TradingDecision(
|
||||
symbol=symbol,
|
||||
strategy_id=strategy.strategy_id,
|
||||
action="none",
|
||||
decision_type="rejected",
|
||||
signals=[s.to_dict() for s in signals],
|
||||
signal_summary=analysis,
|
||||
decision_reason="风控检查未通过",
|
||||
confidence_score=0,
|
||||
position_check=position_check,
|
||||
risk_check=risk_check,
|
||||
status="rejected",
|
||||
)
|
||||
return decision
|
||||
|
||||
# 设置决策冷却
|
||||
self._set_cooldown(symbol)
|
||||
|
||||
# 生成决策理由
|
||||
decision_reason = self._generate_decision_reason(analysis, best_signal)
|
||||
|
||||
# 创建决策
|
||||
decision = TradingDecision(
|
||||
symbol=symbol,
|
||||
strategy_id=strategy.strategy_id,
|
||||
action=action,
|
||||
decision_type="signal_combined" if len(signals) > 1 else "single_signal",
|
||||
signals=[s.to_dict() for s in signals],
|
||||
signal_summary=analysis,
|
||||
entry_price=entry_price,
|
||||
sl=round(sl, 2),
|
||||
tp=round(tp, 2),
|
||||
volume=volume,
|
||||
risk_points=round(risk_points, 2),
|
||||
reward_points=round(reward_points, 2),
|
||||
risk_reward_ratio=round(rr_ratio, 2),
|
||||
decision_reason=decision_reason,
|
||||
confidence_score=analysis["buy_weighted_score"] if action == "buy" else analysis["sell_weighted_score"],
|
||||
position_check=position_check,
|
||||
risk_check=risk_check,
|
||||
)
|
||||
|
||||
print(f"[StrategyService] 生成决策: {decision.decision_id} {action} {symbol} @ {entry_price}")
|
||||
|
||||
return decision
|
||||
|
||||
def _select_best_signal(self, signals: List[TradingSignal],
|
||||
action: str, strategy: TradingStrategy) -> Optional[TradingSignal]:
|
||||
"""选择最佳信号"""
|
||||
filtered = [s for s in signals if s.action == action]
|
||||
if not filtered:
|
||||
return None
|
||||
|
||||
if strategy.conflict_resolution == ConflictResolution.HIGHEST_CONFIDENCE:
|
||||
return max(filtered, key=lambda s: s.confidence)
|
||||
elif strategy.conflict_resolution == ConflictResolution.HIGHEST_WEIGHT:
|
||||
return max(filtered, key=lambda s: s.confidence * strategy.get_signal_weight(s.source, s.source_period))
|
||||
else:
|
||||
return filtered[0]
|
||||
|
||||
def _calculate_sl_tp(self, entry_price: float, signal: TradingSignal,
|
||||
strategy: TradingStrategy) -> tuple:
|
||||
"""计算止损止盈"""
|
||||
# 止损
|
||||
if strategy.sl_mode == StopLossMode.SIGNAL:
|
||||
sl = signal.suggested_sl
|
||||
elif strategy.sl_mode == StopLossMode.FIXED_POINTS:
|
||||
if signal.action == "buy":
|
||||
sl = entry_price - strategy.sl_fixed_points
|
||||
else:
|
||||
sl = entry_price + strategy.sl_fixed_points
|
||||
else:
|
||||
sl = signal.suggested_sl
|
||||
|
||||
# 止盈
|
||||
if strategy.tp_mode == TakeProfitMode.SIGNAL:
|
||||
tp = signal.suggested_tp
|
||||
elif strategy.tp_mode == TakeProfitMode.FIXED_POINTS:
|
||||
if signal.action == "buy":
|
||||
tp = entry_price + strategy.tp_fixed_points
|
||||
else:
|
||||
tp = entry_price - strategy.tp_fixed_points
|
||||
elif strategy.tp_mode == TakeProfitMode.RISK_REWARD:
|
||||
risk = abs(entry_price - sl)
|
||||
if signal.action == "buy":
|
||||
tp = entry_price + risk * strategy.tp_risk_reward
|
||||
else:
|
||||
tp = entry_price - risk * strategy.tp_risk_reward
|
||||
else:
|
||||
tp = signal.suggested_tp
|
||||
|
||||
return sl, tp
|
||||
|
||||
def _check_position_limits(self, symbol: str, strategy: TradingStrategy,
|
||||
action: str) -> Dict:
|
||||
"""检查持仓限制"""
|
||||
current_positions = 0
|
||||
same_direction = 0
|
||||
opposite_direction = 0
|
||||
|
||||
if self._position_service:
|
||||
positions = self._position_service.get_positions(symbol)
|
||||
current_positions = len(positions)
|
||||
for pos in positions:
|
||||
# PositionData.to_dict() 返回 direction 字段
|
||||
pos_direction = pos.get('direction', '')
|
||||
if pos_direction == action:
|
||||
same_direction += 1
|
||||
else:
|
||||
opposite_direction += 1
|
||||
|
||||
return self.risk_manager.check_position_limit(
|
||||
symbol, strategy, current_positions, same_direction, opposite_direction, action
|
||||
)
|
||||
|
||||
def _generate_decision_reason(self, analysis: Dict, signal: TradingSignal) -> str:
|
||||
"""生成决策理由"""
|
||||
reasons = []
|
||||
|
||||
total = analysis["total_count"]
|
||||
buy_count = analysis["buy_count"]
|
||||
sell_count = analysis["sell_count"]
|
||||
direction = analysis["direction"]
|
||||
|
||||
if total == 1:
|
||||
reasons.append(f"单一信号({signal.source})建议{direction}")
|
||||
else:
|
||||
reasons.append(f"{total}个信号中{buy_count}个买入、{sell_count}个卖出")
|
||||
|
||||
reasons.append(f"综合判断: {direction}")
|
||||
reasons.append(f"风险回报比: {signal.risk_reward_ratio:.2f}")
|
||||
|
||||
return " | ".join(reasons)
|
||||
|
||||
def _is_in_cooldown(self, symbol: str) -> bool:
|
||||
"""检查是否在冷却期"""
|
||||
with self._cooldown_lock:
|
||||
if symbol in self._decision_cooldowns:
|
||||
last_time = self._decision_cooldowns[symbol]
|
||||
elapsed = (datetime.now() - last_time).total_seconds()
|
||||
return elapsed < self.decision_cooldown
|
||||
return False
|
||||
|
||||
def _set_cooldown(self, symbol: str) -> None:
|
||||
"""设置冷却"""
|
||||
with self._cooldown_lock:
|
||||
self._decision_cooldowns[symbol] = datetime.now()
|
||||
|
||||
# ==================== 执行决策 ====================
|
||||
|
||||
def execute_decision(self, decision: TradingDecision) -> Optional[str]:
|
||||
"""
|
||||
执行决策(生成待确认订单)
|
||||
|
||||
Args:
|
||||
decision: 交易决策
|
||||
|
||||
Returns:
|
||||
订单ID 或 None
|
||||
"""
|
||||
if decision.action == "none":
|
||||
return None
|
||||
|
||||
if not self._pending_order_service:
|
||||
print("[StrategyService] 待确认订单服务未设置")
|
||||
return None
|
||||
|
||||
# 创建订单
|
||||
order_id = self._pending_order_service.create_order(
|
||||
symbol=decision.symbol,
|
||||
action=decision.action,
|
||||
price=decision.entry_price,
|
||||
mount=decision.volume,
|
||||
sl=decision.sl,
|
||||
tp=decision.tp,
|
||||
reason=decision.decision_reason,
|
||||
description=f"Strategy: {decision.strategy_id}",
|
||||
source="strategy_decision",
|
||||
)
|
||||
|
||||
decision.order_id = order_id
|
||||
decision.status = "confirmed"
|
||||
|
||||
print(f"[StrategyService] 决策已执行,订单ID: {order_id}")
|
||||
return order_id
|
||||
|
||||
# ==================== 状态 ====================
|
||||
|
||||
def get_status(self) -> Dict:
|
||||
"""获取服务状态"""
|
||||
return {
|
||||
"strategy_store": self.strategy_store.get_status(),
|
||||
"signal_service": self.signal_service.get_status(),
|
||||
"risk_manager": self.risk_manager.get_status(),
|
||||
}
|
||||
Reference in New Issue
Block a user