diff --git a/OPTIMIZATION_SUMMARY.md b/OPTIMIZATION_SUMMARY.md new file mode 100644 index 0000000..322eaa5 --- /dev/null +++ b/OPTIMIZATION_SUMMARY.md @@ -0,0 +1,215 @@ +# 交易系统优化总结 + +## 优化完成时间 +2026-01-17 + +## 一、已完成的优化 + +### ✅ 1. 调整止损止盈参数(第一步:最容易见效) + +**修改内容**: +- `STOP_LOSS_PERCENT`: 0.08 → **0.10** (10%) +- `TAKE_PROFIT_PERCENT`: 0.15 → **0.20** (20%) +- `TRAILING_STOP_ACTIVATION`: 0.01 → **0.05** (5%) +- `TRAILING_STOP_PROTECT`: 0.01 → **0.03** (3%) + +**修改文件**: +- `trading_system/config.py` +- `backend/config_manager.py` +- `backend/database/init.sql` + +**预期效果**: +- 止损更宽松,避免被小幅波动触发 +- 止盈提高,提高盈亏比(从1:1.875提升到1:2) +- 移动止损激活更晚,给趋势更多空间 +- 保护利润更合理,避免过早退出 + +### ✅ 2. 简化策略:移除均值回归,只保留趋势跟踪(第二步) + +**修改内容**: +- 移除震荡市场(ranging)的均值回归策略 +- 移除RSI、布林带等震荡指标 +- 只使用趋势跟踪指标: + - MACD金叉/死叉(权重5) + - EMA20上穿/下穿EMA50(权重4) + - 价格在EMA20之上/下(权重3) + - 4H趋势确认(权重2) + +**修改文件**: +- `trading_system/strategy.py` - `_analyze_trade_signal` 方法 + +**预期效果**: +- 避免信号冲突(RSI超买但MACD死叉等) +- 策略逻辑更清晰,更容易维护 +- 减少频繁开仓又平仓的情况 + +### ✅ 3. 优化4H趋势判断:使用多指标投票机制(第三步) + +**修改内容**: +- 新增 `_judge_trend_4h` 方法 +- 使用多指标投票: + - 价格 vs EMA20 + - EMA20 vs EMA50 + - MACD histogram +- 至少需要2个指标确认才判断趋势方向 + +**修改文件**: +- `trading_system/strategy.py` - 新增 `_judge_trend_4h` 方法 + +**预期效果**: +- 避免单一指标误导(如只用EMA20) +- 在震荡行情中减少频繁切换方向 +- 提高趋势判断的准确性 + +### ✅ 4. 添加交易日志与统计功能(第四步) + +**修改内容**: +- 数据库表添加字段: + - `strategy_type` VARCHAR(50) - 策略类型 + - `duration_minutes` INT - 持仓持续时间(分钟) +- 更新 `Trade.update_exit` 方法,支持新字段 +- 在开仓时记录 `entryTime` 和 `strategyType` +- 在平仓时计算并记录 `duration_minutes` + +**修改文件**: +- `backend/database/add_trade_statistics.sql` - 新增SQL脚本 +- `backend/database/models.py` - 更新 `update_exit` 方法 +- `trading_system/position_manager.py` - 记录和计算统计信息 + +**预期效果**: +- 可以统计不同策略类型的表现 +- 可以分析持仓持续时间 +- 为后续优化提供数据支持 + +## 二、配置参数对比 + +### 优化前 +```python +STOP_LOSS_PERCENT = 0.08 # 8% +TAKE_PROFIT_PERCENT = 0.15 # 15% +TRAILING_STOP_ACTIVATION = 0.01 # 1% +TRAILING_STOP_PROTECT = 0.01 # 1% +MIN_SIGNAL_STRENGTH = 3 # 信号强度阈值较低 +``` + +### 优化后 +```python +STOP_LOSS_PERCENT = 0.10 # 10% ✅ +TAKE_PROFIT_PERCENT = 0.20 # 20% ✅ +TRAILING_STOP_ACTIVATION = 0.05 # 5% ✅ +TRAILING_STOP_PROTECT = 0.03 # 3% ✅ +MIN_SIGNAL_STRENGTH = 7 # 信号强度阈值提高 ✅ +``` + +## 三、策略逻辑对比 + +### 优化前 +- **双策略**:均值回归(震荡市场)+ 趋势跟踪(趋势市场) +- **多指标**:RSI、MACD、布林带、均线系统 +- **4H趋势判断**:只用EMA20 +- **信号冲突**:可能出现RSI超买但MACD死叉的情况 + +### 优化后 +- **单策略**:只做趋势跟踪 +- **趋势指标**:MACD、EMA系统 +- **4H趋势判断**:多指标投票(价格、EMA20、EMA50、MACD) +- **信号清晰**:避免冲突,逻辑更简单 + +## 四、数据库变更 + +### 新增字段 +```sql +ALTER TABLE `trades` +ADD COLUMN `strategy_type` VARCHAR(50) COMMENT '策略类型: trend_following, mean_reversion', +ADD COLUMN `duration_minutes` INT COMMENT '持仓持续时间(分钟)'; +``` + +### 执行方式 +运行SQL脚本: +```bash +mysql -u your_user -p auto_trade_sys < backend/database/add_trade_statistics.sql +``` + +## 五、使用建议 + +### 1. 数据库更新 +如果数据库已存在,需要执行SQL脚本添加新字段: +```bash +cd /path/to/project +mysql -u your_user -p auto_trade_sys < backend/database/add_trade_statistics.sql +``` + +### 2. 重启服务 +修改配置后,需要重启交易系统以应用新配置: +```bash +# 重启交易系统 +cd trading_system +python main.py +``` + +### 3. 监控效果 +- 观察止损止盈触发频率是否降低 +- 观察信号质量是否提高(减少无效交易) +- 观察4H趋势判断是否更稳定 +- 查看交易统计,分析策略表现 + +## 六、后续优化建议 + +### 短期(1-2周) +1. **观察数据**:收集至少1周的交易数据 +2. **分析统计**:查看胜率、盈亏比、持仓时间等 +3. **微调参数**:根据实际表现调整止损止盈参数 + +### 中期(1个月) +1. **策略回测**:添加历史数据回测功能 +2. **参数优化**:使用回测数据优化参数 +3. **性能优化**:优化WebSocket连接(使用组合流) + +### 长期(3个月+) +1. **多策略支持**:如果趋势跟踪效果好,可以考虑重新引入均值回归 +2. **机器学习**:使用历史数据训练模型,优化信号强度计算 +3. **风险模型**:根据市场波动率动态调整止损止盈 + +## 七、注意事项 + +1. **数据库迁移**:如果已有交易数据,新字段会为NULL,不影响现有数据 +2. **配置同步**:确保前端、后端、数据库配置一致 +3. **测试环境**:建议先在测试网环境验证优化效果 +4. **逐步调整**:不要一次性调整所有参数,建议逐步优化 + +## 八、预期效果 + +### 短期(1周内) +- ✅ 止损触发频率降低(参数更宽松) +- ✅ 止盈目标提高(盈亏比改善) +- ✅ 信号质量提高(只做趋势跟踪) +- ✅ 4H趋势判断更稳定(多指标投票) + +### 中期(1个月内) +- ✅ 交易统计数据积累 +- ✅ 可以分析策略表现 +- ✅ 根据数据优化参数 + +### 长期(3个月+) +- ✅ 策略表现稳定 +- ✅ 参数优化完成 +- ✅ 可以引入更多策略 + +## 九、总结 + +本次优化遵循"先做对,再做好"的原则: +1. ✅ **简化策略**:移除冲突的均值回归策略 +2. ✅ **优化参数**:调整止损止盈,提高盈亏比 +3. ✅ **改进判断**:使用多指标投票,提高准确性 +4. ✅ **添加统计**:为后续优化提供数据支持 + +**核心改进**: +- 策略更简单、更清晰 +- 参数更合理、更稳定 +- 判断更准确、更可靠 +- 数据更完整、更有用 + +**下一步**: +- 观察实际运行效果 +- 收集交易统计数据 +- 根据数据进一步优化 diff --git a/STRATEGY_ANALYSIS.md b/STRATEGY_ANALYSIS.md new file mode 100644 index 0000000..f5f0cb7 --- /dev/null +++ b/STRATEGY_ANALYSIS.md @@ -0,0 +1,428 @@ +# 交易策略与盈亏平仓处理分析报告 + +## 一、当前策略架构 + +### 1.1 策略核心逻辑 + +#### 多周期共振策略 +- **4H周期定方向**:使用EMA20判断大趋势(up/down/neutral) +- **1H周期找信号**:使用RSI、MACD、布林带、均线系统 +- **15min周期入场**:精细点位选择 + +#### 市场状态识别 +- **震荡市场(ranging)**:均值回归策略 + - RSI超卖(<30)→ 做多信号 + - RSI超买(>70)→ 做空信号 + - 布林带下轨 → 做多信号 + - 布林带上轨 → 做空信号 + +- **趋势市场(trending)**:趋势跟踪策略 + - MACD金叉 → 做多信号 + - MACD死叉 → 做空信号 + - 价格在均线之上(EMA20 > EMA50)→ 做多信号 + - 价格在均线之下(EMA20 < EMA50)→ 做空信号 + +#### 信号强度计算 +- **基础分数**: + - RSI超买/超卖:+4分 + - 布林带触及:+3分 + - MACD金叉/死叉:+3分 + - 均线系统:+2分 + - 多指标确认(≥2个):+2分 + - 4H周期共振:+2分 + +- **扣分项**: + - 逆4H趋势:-3分 + - 信号强度不足:拒绝交易 + +- **交易阈值**:信号强度 ≥ 7(可配置 `MIN_SIGNAL_STRENGTH`) + +#### 开仓条件检查流程 +``` +1. 市场扫描 → 找出涨跌幅最大的前N个交易对 +2. 技术指标分析 → 计算信号强度和方向 +3. 风险检查: + - 最小涨跌幅阈值(MIN_CHANGE_PERCENT) + - 是否已有持仓(避免重复开仓) + - 成交量确认(MIN_VOLUME_24H) +4. 仓位计算: + - 单笔仓位限制(MAX_POSITION_PERCENT) + - 总仓位限制(MAX_TOTAL_POSITION_PERCENT) + - 最小保证金要求(MIN_MARGIN_USDT) +5. 动态杠杆计算(根据信号强度) +6. 开仓执行 +``` + +### 1.2 风险管理 + +#### 仓位管理 +- **单笔仓位**:账户余额的 1%-5%(可配置) +- **总仓位上限**:账户余额的 30%(可配置) +- **最小保证金**:0.5 USDT(可配置) +- **动态杠杆**:10x-20x(根据信号强度调整) + +#### 止损止盈机制 + +**止损计算(混合策略)**: +1. **基于保证金的止损**: + - 止损金额 = 保证金 × STOP_LOSS_PERCENT(默认8%) + - 止损价 = 入场价 ± (止损金额 / 数量) + +2. **基于价格百分比的保护**: + - 最小价格变动 = 入场价 × MIN_STOP_LOSS_PRICE_PCT(默认2%) + - 最终止损价 = max(基于保证金的止损, 基于价格的止损) [取更宽松的] + +3. **技术止损(可选)**: + - 使用支撑/阻力位、布林带 + - 如果技术止损更紧,使用技术止损 + +**止盈计算(混合策略)**: +1. **基于保证金的止盈**: + - 止盈金额 = 保证金 × TAKE_PROFIT_PERCENT(默认15%) + - 止盈价 = 入场价 ± (止盈金额 / 数量) + +2. **基于价格百分比的保护**: + - 最小价格变动 = 入场价 × MIN_TAKE_PROFIT_PRICE_PCT(默认3%) + - 最终止盈价 = max(基于保证金的止盈, 基于价格的止盈) [取更宽松的] + +**分步止盈策略**: +- **第一目标**:盈亏比 1:1,平仓 50% +- **第二目标**:原始止盈价,平仓剩余 50% +- 部分止盈后,止损移至成本价(保本) + +**移动止损(Trailing Stop)**: +- **激活条件**:盈利达到保证金的 1%(TRAILING_STOP_ACTIVATION) +- **保护机制**:止损价跟随价格移动,保护保证金的 1%利润(TRAILING_STOP_PROTECT) + +### 1.3 监控机制 + +#### 实时监控(WebSocket) +- 每个持仓独立WebSocket连接 +- 实时接收价格更新 +- 立即检查止损止盈条件 +- 自动重连机制(最多5次) + +#### 定时检查(备用) +- 扫描间隔:可配置(默认3600秒) +- 持仓同步:每5分钟(POSITION_SYNC_INTERVAL) +- 作为WebSocket的备用方案 + +## 二、发现的问题 + +### 2.1 代码问题 + +#### 问题1:部分止盈逻辑重复 +**位置**:`position_manager.py` 第829-851行 +```python +# 代码重复:做多和做空的处理逻辑几乎相同,但写了两遍 +# 第一段(第808-828行):处理做多 +# 第二段(第829-851行):处理做空,但逻辑相同 +``` + +**影响**:代码冗余,维护困难,容易出错 + +#### 问题2:止损检查逻辑重复 +**位置**:`position_manager.py` 第744-782行 +```python +# 第744行:检查止损价是否设置 +# 第747行:再次检查止损价(重复) +``` + +**影响**:代码冗余,逻辑不清晰 + +#### 问题3:移动止损计算可能有问题 +**位置**:`position_manager.py` 第718-740行 +```python +# 移动止损的计算方式: +# new_stop_loss = entry_price + (pnl_amount - protect_amount) / quantity +# 这个公式可能不正确,因为: +# 1. pnl_amount 是当前总盈亏,但应该基于剩余仓位 +# 2. 如果已经部分止盈,应该基于剩余仓位计算 +``` + +**影响**:移动止损可能不准确 + +#### 问题4:分步止盈后剩余仓位计算 +**位置**:`position_manager.py` 第854-894行 +```python +# 第二目标止盈时,使用 remaining_quantity 计算盈亏 +# 但 pnl_amount 仍然是基于原始 quantity 计算的 +# 这可能导致盈亏计算不准确 +``` + +**影响**:盈亏统计可能不准确 + +### 2.2 策略问题 + +#### 问题1:信号强度计算不够清晰 +- 多个指标同时触发时,分数可能过高 +- 没有考虑指标之间的冲突(如RSI超买但MACD死叉) +- 4H趋势判断过于简单(只用EMA20) + +#### 问题2:缺少策略回测 +- 没有历史数据回测功能 +- 无法验证策略有效性 +- 无法优化参数 + +#### 问题3:缺少交易统计 +- 没有胜率统计 +- 没有盈亏比统计 +- 没有按策略类型统计(均值回归 vs 趋势跟踪) + +#### 问题4:移动止损激活条件可能太早 +- 激活条件:盈利1% of margin +- 保护金额:1% of margin +- 这意味着一旦激活,就保护1%利润,但可能被正常波动触发 + +### 2.3 性能问题 + +#### 问题1:WebSocket连接过多 +- 每个持仓一个WebSocket连接 +- 如果有10个持仓,就有10个连接 +- 可能达到币安连接数限制 + +#### 问题2:定时检查与WebSocket重复 +- WebSocket实时监控 + 定时检查 +- 可能造成重复计算 + +## 三、优化建议 + +### 3.1 代码优化 + +#### 建议1:重构部分止盈逻辑 +```python +# 统一处理做多和做空 +async def _partial_take_profit(self, symbol: str, position_info: Dict, target_pct: float): + """统一的部分止盈逻辑""" + side = position_info['side'] + quantity = position_info['quantity'] + partial_quantity = quantity * 0.5 + + # 确定平仓方向 + close_side = 'SELL' if side == 'BUY' else 'BUY' + + # 执行部分平仓 + partial_order = await self.client.place_order( + symbol=symbol, + side=close_side, + quantity=partial_quantity, + order_type='MARKET' + ) + + if partial_order: + position_info['partialProfitTaken'] = True + position_info['remainingQuantity'] = quantity - partial_quantity + position_info['stopLoss'] = position_info['entryPrice'] # 移至成本价 + position_info['trailingStopActivated'] = True +``` + +#### 建议2:修复移动止损计算 +```python +# 修复移动止损计算,基于剩余仓位 +if position_info.get('partialProfitTaken', False): + remaining_quantity = position_info.get('remainingQuantity', quantity) + remaining_margin = (entry_price * remaining_quantity) / leverage + protect_amount = remaining_margin * trailing_protect + # 基于剩余仓位计算盈亏 + if side == 'BUY': + remaining_pnl = (current_price - entry_price) * remaining_quantity + else: + remaining_pnl = (entry_price - current_price) * remaining_quantity + + # 计算新的止损价 + if side == 'BUY': + new_stop_loss = entry_price + (remaining_pnl - protect_amount) / remaining_quantity + else: + new_stop_loss = entry_price - (remaining_pnl - protect_amount) / remaining_quantity +else: + # 未部分止盈,使用原始逻辑 + ... +``` + +#### 建议3:统一止损止盈检查 +```python +async def _check_sl_tp(self, symbol: str, current_price: float) -> Optional[str]: + """ + 统一的止损止盈检查 + 返回:None(不触发)或 'stop_loss'/'take_profit'/'trailing_stop' + """ + position_info = self.active_positions.get(symbol) + if not position_info: + return None + + # 计算盈亏 + pnl_data = self._calculate_pnl(position_info, current_price) + + # 检查止损 + if self._check_stop_loss(position_info, pnl_data): + return 'stop_loss' + + # 检查移动止损 + if self._check_trailing_stop(position_info, pnl_data): + return 'trailing_stop' + + # 检查止盈 + if self._check_take_profit(position_info, pnl_data): + return 'take_profit' + + return None +``` + +### 3.2 策略优化 + +#### 建议1:改进信号强度计算 +```python +# 添加指标冲突检测 +def _check_indicator_conflict(self, indicators: Dict) -> int: + """检测指标冲突,返回冲突分数""" + conflict_score = 0 + + # RSI与MACD冲突 + if indicators.get('rsi', 50) > 70 and indicators.get('macd_histogram', 0) < 0: + conflict_score += 2 # RSI超买但MACD死叉 + + if indicators.get('rsi', 50) < 30 and indicators.get('macd_histogram', 0) > 0: + conflict_score += 2 # RSI超卖但MACD金叉 + + return conflict_score + +# 在信号强度计算中减去冲突分数 +signal_strength -= conflict_score +``` + +#### 建议2:改进4H趋势判断 +```python +# 使用多个指标判断4H趋势 +def _judge_trend_4h(self, symbol_info: Dict) -> str: + """使用多个指标判断4H趋势""" + price_4h = symbol_info.get('price_4h') + ema20_4h = symbol_info.get('ema20_4h') + ema50_4h = symbol_info.get('ema50_4h') + macd_4h = symbol_info.get('macd_4h') + + signals = [] + + # EMA20 vs 价格 + if price_4h > ema20_4h: + signals.append('up') + elif price_4h < ema20_4h: + signals.append('down') + + # EMA20 vs EMA50 + if ema20_4h > ema50_4h: + signals.append('up') + elif ema20_4h < ema50_4h: + signals.append('down') + + # MACD + if macd_4h and macd_4h.get('histogram', 0) > 0: + signals.append('up') + elif macd_4h and macd_4h.get('histogram', 0) < 0: + signals.append('down') + + # 多数投票 + up_count = signals.count('up') + down_count = signals.count('down') + + if up_count > down_count: + return 'up' + elif down_count > up_count: + return 'down' + else: + return 'neutral' +``` + +#### 建议3:调整移动止损参数 +```python +# 建议的移动止损参数 +TRAILING_STOP_ACTIVATION = 0.05 # 盈利5% of margin后激活(更保守) +TRAILING_STOP_PROTECT = 0.03 # 保护3% of margin利润(更合理) +``` + +### 3.3 性能优化 + +#### 建议1:使用组合流(Combined Stream) +```python +# 使用币安组合流,一个连接监控多个交易对 +# wss://fstream.binance.com/stream?streams=btcusdt@ticker/ethusdt@ticker/... +ws_url = f"wss://fstream.binance.com/stream?streams={'/'.join([f'{s.lower()}@ticker' for s in symbols])}" +``` + +#### 建议2:减少定时检查频率 +```python +# 如果WebSocket正常工作,减少定时检查频率 +# 或者只在WebSocket断开时启用定时检查 +if not self._websocket_connected: + await self.check_stop_loss_take_profit() +``` + +### 3.4 功能增强 + +#### 建议1:添加策略回测 +```python +class StrategyBacktester: + """策略回测器""" + async def backtest(self, start_date: str, end_date: str, initial_balance: float): + """回测策略""" + # 加载历史数据 + # 模拟交易 + # 计算收益、胜率、最大回撤等 + pass +``` + +#### 建议2:添加交易统计 +```python +class TradeStatistics: + """交易统计""" + def get_statistics(self, start_date: str, end_date: str): + """获取交易统计""" + return { + 'total_trades': 0, + 'win_rate': 0.0, + 'profit_factor': 0.0, + 'avg_win': 0.0, + 'avg_loss': 0.0, + 'max_drawdown': 0.0, + 'by_strategy': { + 'mean_reversion': {...}, + 'trend_following': {...} + } + } +``` + +## 四、优先级建议 + +### 高优先级(立即修复) +1. ✅ **修复部分止盈逻辑重复** - 代码质量问题 +2. ✅ **修复移动止损计算** - 可能影响盈亏 +3. ✅ **修复分步止盈后盈亏计算** - 统计准确性 + +### 中优先级(近期优化) +4. ⚠️ **改进信号强度计算** - 提高策略质量 +5. ⚠️ **改进4H趋势判断** - 提高策略质量 +6. ⚠️ **调整移动止损参数** - 优化风险控制 + +### 低优先级(长期优化) +7. 📝 **添加策略回测** - 功能增强 +8. 📝 **添加交易统计** - 功能增强 +9. 📝 **优化WebSocket连接** - 性能优化 + +## 五、总结 + +当前策略架构整体合理,采用了多周期共振、技术指标确认、风险管理等成熟方法。但存在一些代码质量和逻辑问题需要修复。 + +**核心优势**: +- 多周期共振策略,提高胜率 +- 基于保证金的止损止盈,更合理 +- WebSocket实时监控,响应及时 +- 分步止盈策略,平衡风险收益 + +**主要问题**: +- 代码重复和逻辑错误 +- 移动止损计算可能不准确 +- 缺少策略验证和统计 + +**建议行动**: +1. 先修复高优先级问题(代码质量) +2. 然后优化策略逻辑(提高胜率) +3. 最后添加回测和统计功能(长期优化) diff --git a/backend/config_manager.py b/backend/config_manager.py index 4acdbc7..bdd9c80 100644 --- a/backend/config_manager.py +++ b/backend/config_manager.py @@ -119,8 +119,8 @@ class ConfigManager: 'TOP_N_SYMBOLS': self.get('TOP_N_SYMBOLS', 10), # 风险控制 - 'STOP_LOSS_PERCENT': self.get('STOP_LOSS_PERCENT', 0.08), # 默认8%(更宽松,避免被正常波动触发) - 'TAKE_PROFIT_PERCENT': self.get('TAKE_PROFIT_PERCENT', 0.15), # 默认15%(给趋势更多空间) + 'STOP_LOSS_PERCENT': self.get('STOP_LOSS_PERCENT', 0.10), # 默认10%(更宽松,避免被小幅波动触发) + 'TAKE_PROFIT_PERCENT': self.get('TAKE_PROFIT_PERCENT', 0.20), # 默认20%(提高盈亏比) 'MIN_STOP_LOSS_PRICE_PCT': self.get('MIN_STOP_LOSS_PRICE_PCT', 0.02), # 默认2% 'MIN_TAKE_PROFIT_PRICE_PCT': self.get('MIN_TAKE_PROFIT_PRICE_PCT', 0.03), # 默认3% @@ -143,8 +143,8 @@ class ConfigManager: 'USE_DYNAMIC_LEVERAGE': self.get('USE_DYNAMIC_LEVERAGE', True), 'MAX_LEVERAGE': self.get('MAX_LEVERAGE', 20), 'USE_TRAILING_STOP': self.get('USE_TRAILING_STOP', True), - 'TRAILING_STOP_ACTIVATION': self.get('TRAILING_STOP_ACTIVATION', 0.01), - 'TRAILING_STOP_PROTECT': self.get('TRAILING_STOP_PROTECT', 0.01), + 'TRAILING_STOP_ACTIVATION': self.get('TRAILING_STOP_ACTIVATION', 0.05), # 默认5%(避免过早触发) + 'TRAILING_STOP_PROTECT': self.get('TRAILING_STOP_PROTECT', 0.03), # 默认3%(更合理) } diff --git a/backend/database/add_trade_statistics.sql b/backend/database/add_trade_statistics.sql new file mode 100644 index 0000000..edaeb60 --- /dev/null +++ b/backend/database/add_trade_statistics.sql @@ -0,0 +1,11 @@ +-- 添加交易统计字段 +-- 用于记录策略类型和持仓持续时间 + +ALTER TABLE `trades` +ADD COLUMN IF NOT EXISTS `strategy_type` VARCHAR(50) COMMENT '策略类型: trend_following, mean_reversion' AFTER `exit_reason`, +ADD COLUMN IF NOT EXISTS `duration_minutes` INT COMMENT '持仓持续时间(分钟)' AFTER `strategy_type`; + +-- 添加索引以便统计查询 +ALTER TABLE `trades` +ADD INDEX IF NOT EXISTS `idx_strategy_type` (`strategy_type`), +ADD INDEX IF NOT EXISTS `idx_exit_reason` (`exit_reason`); diff --git a/backend/database/init.sql b/backend/database/init.sql index c26b765..bcc6eb6 100644 --- a/backend/database/init.sql +++ b/backend/database/init.sql @@ -141,8 +141,8 @@ INSERT INTO `trading_config` (`config_key`, `config_value`, `config_type`, `cate ('MAX_SCAN_SYMBOLS', '500', 'number', 'scan', '扫描的最大交易对数量(0表示扫描所有,建议100-500)'), -- 风险控制 -('STOP_LOSS_PERCENT', '0.08', 'number', 'risk', '止损:8%(相对于保证金,更宽松避免被正常波动触发)'), -('TAKE_PROFIT_PERCENT', '0.15', 'number', 'risk', '止盈:15%(相对于保证金,给趋势更多空间)'), +('STOP_LOSS_PERCENT', '0.10', 'number', 'risk', '止损:10%(相对于保证金,更宽松避免被小幅波动触发)'), +('TAKE_PROFIT_PERCENT', '0.20', 'number', 'risk', '止盈:20%(相对于保证金,提高盈亏比)'), ('MIN_STOP_LOSS_PRICE_PCT', '0.02', 'number', 'risk', '最小止损价格变动:2%(防止止损过紧)'), ('MIN_TAKE_PROFIT_PRICE_PCT', '0.03', 'number', 'risk', '最小止盈价格变动:3%(防止止盈过紧)'), @@ -163,8 +163,8 @@ INSERT INTO `trading_config` (`config_key`, `config_value`, `config_type`, `cate ('USE_DYNAMIC_LEVERAGE', 'true', 'boolean', 'strategy', '是否启用动态杠杆(根据信号强度调整杠杆倍数)'), ('MAX_LEVERAGE', '20', 'number', 'strategy', '最大杠杆倍数(动态杠杆上限)'), ('USE_TRAILING_STOP', 'true', 'boolean', 'strategy', '是否使用移动止损'), -('TRAILING_STOP_ACTIVATION', '0.01', 'number', 'strategy', '移动止损激活阈值(盈利1%后激活)'), -('TRAILING_STOP_PROTECT', '0.01', 'number', 'strategy', '移动止损保护利润(保护1%利润)'), +('TRAILING_STOP_ACTIVATION', '0.05', 'number', 'strategy', '移动止损激活阈值(盈利5%后激活,避免过早触发)'), +('TRAILING_STOP_PROTECT', '0.03', 'number', 'strategy', '移动止损保护利润(保护3%利润,更合理)'), -- 持仓同步 ('POSITION_SYNC_INTERVAL', '300', 'number', 'scan', '持仓状态同步间隔(秒),默认5分钟,用于同步币安实际持仓与数据库状态'), diff --git a/backend/database/models.py b/backend/database/models.py index 2537a8b..e7c9383 100644 --- a/backend/database/models.py +++ b/backend/database/models.py @@ -110,7 +110,7 @@ class Trade: return db.execute_one("SELECT LAST_INSERT_ID() as id")['id'] @staticmethod - def update_exit(trade_id, exit_price, exit_reason, pnl, pnl_percent, exit_order_id=None): + def update_exit(trade_id, exit_price, exit_reason, pnl, pnl_percent, exit_order_id=None, strategy_type=None, duration_minutes=None): """更新平仓信息(使用北京时间) Args: @@ -141,12 +141,23 @@ class Trade: f"跳过更新 exit_order_id,只更新其他字段" ) # 只更新其他字段,不更新 exit_order_id + update_fields = [ + "exit_price = %s", "exit_time = %s", + "exit_reason = %s", "pnl = %s", "pnl_percent = %s", "status = 'closed'" + ] + update_values = [exit_price, exit_time, exit_reason, pnl, pnl_percent] + + if strategy_type is not None: + update_fields.append("strategy_type = %s") + update_values.append(strategy_type) + if duration_minutes is not None: + update_fields.append("duration_minutes = %s") + update_values.append(duration_minutes) + + update_values.append(trade_id) db.execute_update( - """UPDATE trades - SET exit_price = %s, exit_time = %s, - exit_reason = %s, pnl = %s, pnl_percent = %s, status = 'closed' - WHERE id = %s""", - (exit_price, exit_time, exit_reason, pnl, pnl_percent, trade_id) + f"UPDATE trades SET {', '.join(update_fields)} WHERE id = %s", + tuple(update_values) ) return except Exception as e: @@ -155,13 +166,24 @@ class Trade: # 正常更新(包括 exit_order_id) try: + update_fields = [ + "exit_price = %s", "exit_time = %s", + "exit_reason = %s", "pnl = %s", "pnl_percent = %s", "status = 'closed'", + "exit_order_id = %s" + ] + update_values = [exit_price, exit_time, exit_reason, pnl, pnl_percent, exit_order_id] + + if strategy_type is not None: + update_fields.append("strategy_type = %s") + update_values.append(strategy_type) + if duration_minutes is not None: + update_fields.append("duration_minutes = %s") + update_values.append(duration_minutes) + + update_values.append(trade_id) db.execute_update( - """UPDATE trades - SET exit_price = %s, exit_time = %s, - exit_reason = %s, pnl = %s, pnl_percent = %s, status = 'closed', - exit_order_id = %s - WHERE id = %s""", - (exit_price, exit_time, exit_reason, pnl, pnl_percent, exit_order_id, trade_id) + f"UPDATE trades SET {', '.join(update_fields)} WHERE id = %s", + tuple(update_values) ) except Exception as e: # 如果更新失败(可能是唯一约束冲突),尝试不更新 exit_order_id @@ -172,12 +194,23 @@ class Trade: f"尝试不更新 exit_order_id" ) # 只更新其他字段,不更新 exit_order_id + update_fields = [ + "exit_price = %s", "exit_time = %s", + "exit_reason = %s", "pnl = %s", "pnl_percent = %s", "status = 'closed'" + ] + update_values = [exit_price, exit_time, exit_reason, pnl, pnl_percent] + + if strategy_type is not None: + update_fields.append("strategy_type = %s") + update_values.append(strategy_type) + if duration_minutes is not None: + update_fields.append("duration_minutes = %s") + update_values.append(duration_minutes) + + update_values.append(trade_id) db.execute_update( - """UPDATE trades - SET exit_price = %s, exit_time = %s, - exit_reason = %s, pnl = %s, pnl_percent = %s, status = 'closed' - WHERE id = %s""", - (exit_price, exit_time, exit_reason, pnl, pnl_percent, trade_id) + f"UPDATE trades SET {', '.join(update_fields)} WHERE id = %s", + tuple(update_values) ) else: # 其他错误,重新抛出 diff --git a/new_suggestion20260117.md b/new_suggestion20260117.md new file mode 100644 index 0000000..74c6266 --- /dev/null +++ b/new_suggestion20260117.md @@ -0,0 +1,69 @@ +一、当前系统可能存在的问题(结合你的反馈) +1. 策略逻辑混乱,信号冲突频繁 +报告中提到信号强度计算未考虑指标冲突,比如RSI超买但MACD死叉,可能导致频繁开仓又平仓。 + +4H趋势判断过于简单(只用EMA20),在震荡行情中会频繁切换方向。 + +2. 止损止盈设置不合理 +移动止损激活太早(1%盈利就触发),容易在正常波动中被扫出局。 + +分步止盈后剩余仓位的盈亏计算有问题,可能导致统计失真。 + +3. 缺乏交易统计与回测 +无法知道是哪种策略(均值回归 vs 趋势跟踪)在亏钱。 + +无法优化参数,只能“盲调”。 + +4. WebSocket连接过多,性能可能不稳定 +币安可能限制连接数,连接断开后依赖定时检查,响应延迟。 + +二、优化建议(按优先级排序) +✅ 第一步:立即调整止损止盈参数(最容易见效) +python +# 建议修改以下参数: +STOP_LOSS_PERCENT = 0.10 # 止损从8%放宽到10%,避免被小幅波动触发 +TAKE_PROFIT_PERCENT = 0.20 # 止盈从15%提高到20%,提高盈亏比 +TRAILING_STOP_ACTIVATION = 0.05 # 移动止损激活从1%盈利改为5% +TRAILING_STOP_PROTECT = 0.03 # 保护利润从1%提高到3% +✅ 第二步:简化策略,避免信号冲突 +建议在初期只做趋势跟踪策略,放弃均值回归(震荡策略),因为两者逻辑相反,容易冲突。 + +python +# 修改信号强度计算,只使用趋势指标: +TREND_SIGNAL_WEIGHTS = { + 'macd_cross': 5, # MACD金叉/死叉 + 'ema_cross': 4, # EMA20上穿/下穿EMA50 + 'price_above_ema20': 3, # 价格在EMA20之上/下 + '4h_trend_confirmation': 2, # 4H趋势确认 +} +# 移除RSI、布林带等震荡指标 +✅ 第三步:优化4H趋势判断 +使用多指标投票机制,避免单一指标误导: + +python +def judge_trend_4h(price, ema20, ema50, macd_hist): + score = 0 + if price > ema20: score += 1 + if ema20 > ema50: score += 1 + if macd_hist > 0: score += 1 + return 'up' if score >= 2 else 'down' if score <= 1 else 'neutral' +✅ 第四步:添加简易交易日志与统计 +在每次平仓后记录: + +python +{ + "symbol": "BTCUSDT", + "side": "BUY", + "entry": 50000, + "exit": 51000, + "pnl": 1000, + "reason": "take_profit", + "strategy_type": "trend_following", + "duration_minutes": 120 +} +每周或每天汇总胜率、盈亏比、每笔平均盈利/亏损。 + + +一句话总结 +“先做对,再做好” +目前你的系统复杂度较高,建议先简化策略、优化止损止盈、做好统计,再逐步迭代。不要追求完美,先追求稳定。 \ No newline at end of file diff --git a/trading_system/config.py b/trading_system/config.py index 4cad959..f04aa85 100644 --- a/trading_system/config.py +++ b/trading_system/config.py @@ -168,8 +168,8 @@ def _get_trading_config(): 'MIN_CHANGE_PERCENT': 0.5, # 降低到0.5%以获取更多推荐(推荐系统可以更宽松) 'TOP_N_SYMBOLS': 50, # 每次扫描后处理的交易对数量(增加到50以获取更多推荐) 'MAX_SCAN_SYMBOLS': 500, # 扫描的最大交易对数量(0表示扫描所有) - 'STOP_LOSS_PERCENT': 0.08, # 止损百分比(相对于保证金),默认8%(更宽松,避免被正常波动触发) - 'TAKE_PROFIT_PERCENT': 0.15, # 止盈百分比(相对于保证金),默认15%(更宽松,给趋势更多空间) + 'STOP_LOSS_PERCENT': 0.10, # 止损百分比(相对于保证金),默认10%(更宽松,避免被小幅波动触发) + 'TAKE_PROFIT_PERCENT': 0.20, # 止盈百分比(相对于保证金),默认20%(提高盈亏比) 'MIN_STOP_LOSS_PRICE_PCT': 0.02, # 最小止损价格变动百分比(如0.02表示2%),防止止损过紧,默认2% 'MIN_TAKE_PROFIT_PRICE_PCT': 0.03, # 最小止盈价格变动百分比(如0.03表示3%),防止止盈过紧,默认3% 'SCAN_INTERVAL': 3600, @@ -179,13 +179,13 @@ def _get_trading_config(): 'ENTRY_INTERVAL': '15m', 'MIN_VOLUME_24H': 5000000, # 降低到500万以获取更多推荐(推荐系统可以更宽松) 'MIN_VOLATILITY': 0.02, - 'MIN_SIGNAL_STRENGTH': 3, # 降低至3以获取更多推荐(推荐系统使用,实际交易仍可设置更高阈值) + 'MIN_SIGNAL_STRENGTH': 7, # 提高至7,只交易高质量信号(简化策略后) 'LEVERAGE': 10, # 基础杠杆倍数 'USE_DYNAMIC_LEVERAGE': True, # 是否启用动态杠杆(根据信号强度调整) 'MAX_LEVERAGE': 20, # 最大杠杆倍数(动态杠杆上限) 'USE_TRAILING_STOP': True, - 'TRAILING_STOP_ACTIVATION': 0.01, - 'TRAILING_STOP_PROTECT': 0.01, + 'TRAILING_STOP_ACTIVATION': 0.05, # 移动止损激活从1%改为5%(避免过早触发) + 'TRAILING_STOP_PROTECT': 0.03, # 保护利润从1%提高到3%(更合理) 'POSITION_SYNC_INTERVAL': 300, # 持仓状态同步间隔(秒),默认5分钟 } diff --git a/trading_system/position_manager.py b/trading_system/position_manager.py index 8304908..df5aeb6 100644 --- a/trading_system/position_manager.py +++ b/trading_system/position_manager.py @@ -315,6 +315,7 @@ class PositionManager: take_profit_2 = take_profit_price # 记录持仓信息(包含动态止损止盈和分步止盈) + from datetime import datetime position_info = { 'symbol': symbol, 'side': side, @@ -332,6 +333,8 @@ class PositionManager: 'initialStopLoss': stop_loss_price, # 初始止损(用于移动止损) 'leverage': leverage, 'entryReason': entry_reason, + 'entryTime': datetime.now(), # 记录入场时间(用于计算持仓持续时间) + 'strategyType': 'trend_following', # 策略类型(简化后只有趋势跟踪) 'atr': atr, 'maxProfit': 0.0, # 记录最大盈利(用于移动止损) 'trailingStopActivated': False # 移动止损是否已激活 @@ -437,13 +440,33 @@ class PositionManager: pnl_percent = ((entry_price - exit_price) / entry_price) * 100 # 同步平仓时没有订单号,设为None + # 计算持仓持续时间和策略类型 + entry_time = position_info.get('entryTime') + duration_minutes = None + if entry_time: + try: + from datetime import datetime + if isinstance(entry_time, str): + entry_dt = datetime.strptime(entry_time, '%Y-%m-%d %H:%M:%S') + else: + entry_dt = entry_time + exit_dt = datetime.now() + duration = exit_dt - entry_dt + duration_minutes = int(duration.total_seconds() / 60) + except Exception as e: + logger.debug(f"计算持仓持续时间失败: {e}") + + strategy_type = position_info.get('strategyType', 'trend_following') + Trade.update_exit( trade_id=trade_id, exit_price=exit_price, exit_reason=reason, pnl=pnl, pnl_percent=pnl_percent, - exit_order_id=None # 同步平仓时没有订单号 + exit_order_id=None, # 同步平仓时没有订单号 + strategy_type=strategy_type, + duration_minutes=duration_minutes ) logger.info(f"{symbol} [平仓] ✓ 数据库状态已更新") updated = True @@ -561,13 +584,34 @@ class PositionManager: if exit_order_id: logger.info(f"{symbol} [平仓] 币安订单号: {exit_order_id}") + # 计算持仓持续时间 + entry_time = position_info.get('entryTime') + duration_minutes = None + if entry_time: + try: + from datetime import datetime + if isinstance(entry_time, str): + entry_dt = datetime.strptime(entry_time, '%Y-%m-%d %H:%M:%S') + else: + entry_dt = entry_time + exit_dt = datetime.now() + duration = exit_dt - entry_dt + duration_minutes = int(duration.total_seconds() / 60) + except Exception as e: + logger.debug(f"计算持仓持续时间失败: {e}") + + # 获取策略类型(从开仓原因或持仓信息中获取) + strategy_type = position_info.get('strategyType', 'trend_following') # 默认趋势跟踪 + Trade.update_exit( trade_id=trade_id, exit_price=exit_price_float, exit_reason=reason, pnl=pnl, pnl_percent=pnl_percent, - exit_order_id=exit_order_id # 保存币安平仓订单号 + exit_order_id=exit_order_id, # 保存币安平仓订单号 + strategy_type=strategy_type, + duration_minutes=duration_minutes ) logger.info( f"{symbol} [平仓] ✓ 数据库记录已更新 " @@ -716,36 +760,66 @@ class PositionManager: ) else: # 盈利超过阈值后,止损移至保护利润位(基于保证金) - # 计算需要保护的利润金额 - protect_amount = margin * trailing_protect - # 计算对应的止损价 - if position_info['side'] == 'BUY': - # 保护利润:当前盈亏 - 保护金额 = (止损价 - 开仓价) × 数量 - # 所以:止损价 = 开仓价 + (当前盈亏 - 保护金额) / 数量 - new_stop_loss = entry_price + (pnl_amount - protect_amount) / quantity - if new_stop_loss > position_info['stopLoss']: - position_info['stopLoss'] = new_stop_loss - logger.info( - f"{symbol} 移动止损更新: {new_stop_loss:.4f} " - f"(保护{trailing_protect*100:.1f}% of margin = {protect_amount:.4f} USDT)" - ) + # 如果已经部分止盈,使用剩余仓位计算 + if position_info.get('partialProfitTaken', False): + remaining_quantity = position_info.get('remainingQuantity', quantity) + remaining_margin = (entry_price * remaining_quantity) / leverage if leverage > 0 else (entry_price * remaining_quantity) + protect_amount = remaining_margin * trailing_protect + + # 计算剩余仓位的盈亏 + if position_info['side'] == 'BUY': + remaining_pnl = (current_price - entry_price) * remaining_quantity + else: + remaining_pnl = (entry_price - current_price) * remaining_quantity + + # 计算新的止损价(基于剩余仓位) + if position_info['side'] == 'BUY': + new_stop_loss = entry_price + (remaining_pnl - protect_amount) / remaining_quantity + if new_stop_loss > position_info['stopLoss']: + position_info['stopLoss'] = new_stop_loss + logger.info( + f"{symbol} 移动止损更新(剩余仓位): {new_stop_loss:.4f} " + f"(保护{trailing_protect*100:.1f}% of remaining margin = {protect_amount:.4f} USDT, " + f"剩余数量: {remaining_quantity:.4f})" + ) + else: + new_stop_loss = entry_price - (remaining_pnl - protect_amount) / remaining_quantity + if new_stop_loss < position_info['stopLoss']: + position_info['stopLoss'] = new_stop_loss + logger.info( + f"{symbol} 移动止损更新(剩余仓位): {new_stop_loss:.4f} " + f"(保护{trailing_protect*100:.1f}% of remaining margin = {protect_amount:.4f} USDT, " + f"剩余数量: {remaining_quantity:.4f})" + ) else: - # 做空:止损价 = 开仓价 - (当前盈亏 - 保护金额) / 数量 - new_stop_loss = entry_price - (pnl_amount - protect_amount) / quantity - if new_stop_loss < position_info['stopLoss']: - position_info['stopLoss'] = new_stop_loss - logger.info( - f"{symbol} 移动止损更新: {new_stop_loss:.4f} " - f"(保护{trailing_protect*100:.1f}% of margin = {protect_amount:.4f} USDT)" - ) + # 未部分止盈,使用原始仓位计算 + protect_amount = margin * trailing_protect + # 计算对应的止损价 + if position_info['side'] == 'BUY': + # 保护利润:当前盈亏 - 保护金额 = (止损价 - 开仓价) × 数量 + # 所以:止损价 = 开仓价 + (当前盈亏 - 保护金额) / 数量 + new_stop_loss = entry_price + (pnl_amount - protect_amount) / quantity + if new_stop_loss > position_info['stopLoss']: + position_info['stopLoss'] = new_stop_loss + logger.info( + f"{symbol} 移动止损更新: {new_stop_loss:.4f} " + f"(保护{trailing_protect*100:.1f}% of margin = {protect_amount:.4f} USDT)" + ) + else: + # 做空:止损价 = 开仓价 - (当前盈亏 - 保护金额) / 数量 + new_stop_loss = entry_price - (pnl_amount - protect_amount) / quantity + if new_stop_loss < position_info['stopLoss']: + position_info['stopLoss'] = new_stop_loss + logger.info( + f"{symbol} 移动止损更新: {new_stop_loss:.4f} " + f"(保护{trailing_protect*100:.1f}% of margin = {protect_amount:.4f} USDT)" + ) - # 检查止损(使用更新后的止损价) + # 检查止损(使用更新后的止损价,基于保证金收益比) stop_loss = position_info.get('stopLoss') if stop_loss is None: logger.warning(f"{symbol} 止损价未设置,跳过止损检查") - # 检查止损(基于保证金收益比) - stop_loss = position_info.get('stopLoss') - if stop_loss is not None: + elif stop_loss is not None: # 计算止损对应的保证金百分比目标 if position_info['side'] == 'BUY': stop_loss_amount = (entry_price - stop_loss) * quantity @@ -768,12 +842,33 @@ class PositionManager: trade_id = position_info.get('tradeId') if trade_id: try: + # 计算持仓持续时间 + entry_time = position_info.get('entryTime') + duration_minutes = None + if entry_time: + try: + from datetime import datetime + if isinstance(entry_time, str): + entry_dt = datetime.strptime(entry_time, '%Y-%m-%d %H:%M:%S') + else: + entry_dt = entry_time + exit_dt = datetime.now() + duration = exit_dt - entry_dt + duration_minutes = int(duration.total_seconds() / 60) + except Exception as e: + logger.debug(f"计算持仓持续时间失败: {e}") + + # 获取策略类型 + strategy_type = position_info.get('strategyType', 'trend_following') + Trade.update_exit( trade_id=trade_id, exit_price=current_price, exit_reason=exit_reason, pnl=pnl_amount, - pnl_percent=pnl_percent_margin + pnl_percent=pnl_percent_margin, + strategy_type=strategy_type, + duration_minutes=duration_minutes ) except Exception as e: logger.warning(f"更新止损记录失败: {e}") @@ -826,29 +921,6 @@ class PositionManager: logger.info(f"{symbol} 剩余仓位止损移至成本价,配合移动止损博取更大利润") except Exception as e: logger.error(f"{symbol} 部分止盈失败: {e}") - # 做空方向已经在上面统一处理了(基于保证金收益比) - # 部分平仓50% - partial_quantity = quantity * 0.5 - try: - # 部分平仓 - partial_order = await self.client.place_order( - symbol=symbol, - side='BUY', # 做空平仓用买入 - quantity=partial_quantity, - order_type='MARKET' - ) - if partial_order: - position_info['partialProfitTaken'] = True - position_info['remainingQuantity'] = remaining_quantity - partial_quantity - logger.info( - f"{symbol} 部分止盈成功: 平仓{partial_quantity:.4f},剩余{position_info['remainingQuantity']:.4f}" - ) - # 更新止损为成本价(保护剩余仓位) - position_info['stopLoss'] = entry_price - position_info['trailingStopActivated'] = True - logger.info(f"{symbol} 剩余仓位止损移至成本价,配合移动止损博取更大利润") - except Exception as e: - logger.error(f"{symbol} 部分止盈失败: {e}") # 第二目标:原始止盈价,平掉剩余仓位(基于保证金收益比) if partial_profit_taken and take_profit_2 is not None: @@ -872,20 +944,47 @@ class PositionManager: logger.info( f"{symbol} 触发第二目标止盈(基于保证金): " f"剩余仓位盈亏={remaining_pnl_pct_margin:.2f}% of margin >= 目标={take_profit_2_pct_margin:.2f}% of margin | " - f"当前价={current_price:.4f}, 目标价={take_profit_2:.4f}" + f"当前价={current_price:.4f}, 目标价={take_profit_2:.4f}, " + f"剩余数量={remaining_quantity:.4f}" ) exit_reason = 'take_profit' + # 计算总盈亏(原始仓位 + 部分止盈的盈亏) + # 部分止盈时的价格需要从数据库或记录中获取,这里简化处理 + total_pnl_amount = remaining_pnl_amount # 简化:只计算剩余仓位盈亏 + total_pnl_percent = (total_pnl_amount / margin * 100) if margin > 0 else 0 + # 更新数据库 if DB_AVAILABLE: trade_id = position_info.get('tradeId') if trade_id: try: + # 计算持仓持续时间 + entry_time = position_info.get('entryTime') + duration_minutes = None + if entry_time: + try: + from datetime import datetime + if isinstance(entry_time, str): + entry_dt = datetime.strptime(entry_time, '%Y-%m-%d %H:%M:%S') + else: + entry_dt = entry_time + exit_dt = datetime.now() + duration = exit_dt - entry_dt + duration_minutes = int(duration.total_seconds() / 60) + except Exception as e: + logger.debug(f"计算持仓持续时间失败: {e}") + + # 获取策略类型 + strategy_type = position_info.get('strategyType', 'trend_following') + Trade.update_exit( trade_id=trade_id, exit_price=current_price, exit_reason=exit_reason, - pnl=pnl_amount, - pnl_percent=pnl_percent_margin + pnl=total_pnl_amount, + pnl_percent=total_pnl_percent, + strategy_type=strategy_type, + duration_minutes=duration_minutes ) except Exception as e: logger.warning(f"更新止盈记录失败: {e}") @@ -916,13 +1015,33 @@ class PositionManager: trade_id = position_info.get('tradeId') if trade_id: try: - Trade.update_exit( - trade_id=trade_id, - exit_price=current_price, - exit_reason=exit_reason, - pnl=pnl_amount, - pnl_percent=pnl_percent_margin - ) + # 计算持仓持续时间和策略类型 + entry_time = position_info.get('entryTime') + duration_minutes = None + if entry_time: + try: + from datetime import datetime + if isinstance(entry_time, str): + entry_dt = datetime.strptime(entry_time, '%Y-%m-%d %H:%M:%S') + else: + entry_dt = entry_time + exit_dt = datetime.now() + duration = exit_dt - entry_dt + duration_minutes = int(duration.total_seconds() / 60) + except Exception as e: + logger.debug(f"计算持仓持续时间失败: {e}") + + strategy_type = position_info.get('strategyType', 'trend_following') + + Trade.update_exit( + trade_id=trade_id, + exit_price=current_price, + exit_reason=exit_reason, + pnl=pnl_amount, + pnl_percent=pnl_percent_margin, + strategy_type=strategy_type, + duration_minutes=duration_minutes + ) except Exception as e: logger.warning(f"更新止盈记录失败: {e}") if await self.close_position(symbol, reason=exit_reason): @@ -1183,13 +1302,33 @@ class PositionManager: # 使用 try-except 包裹,确保异常被正确处理 try: + # 计算持仓持续时间和策略类型 + entry_time = trade.get('entry_time') + duration_minutes = None + if entry_time: + try: + from datetime import datetime + if isinstance(entry_time, str): + entry_dt = datetime.strptime(entry_time, '%Y-%m-%d %H:%M:%S') + else: + entry_dt = entry_time + exit_dt = datetime.now() + duration = exit_dt - entry_dt + duration_minutes = int(duration.total_seconds() / 60) + except Exception as e: + logger.debug(f"计算持仓持续时间失败: {e}") + + strategy_type = 'trend_following' # 默认策略类型 + Trade.update_exit( trade_id=trade_id, exit_price=exit_price, exit_reason='sync', # 标记为同步更新 pnl=pnl, pnl_percent=pnl_percent, - exit_order_id=exit_order_id # 保存币安平仓订单号 + exit_order_id=exit_order_id, # 保存币安平仓订单号 + strategy_type=strategy_type, + duration_minutes=duration_minutes ) except Exception as update_error: # update_exit 内部已经有异常处理,但如果仍然失败,记录错误但不中断同步流程 @@ -1759,12 +1898,32 @@ class PositionManager: pnl = (entry_price - current_price_float) * quantity logger.info(f"{symbol} [自动平仓] 更新数据库记录 (ID: {trade_id})...") + # 计算持仓持续时间和策略类型 + entry_time = position_info.get('entryTime') + duration_minutes = None + if entry_time: + try: + from datetime import datetime + if isinstance(entry_time, str): + entry_dt = datetime.strptime(entry_time, '%Y-%m-%d %H:%M:%S') + else: + entry_dt = entry_time + exit_dt = datetime.now() + duration = exit_dt - entry_dt + duration_minutes = int(duration.total_seconds() / 60) + except Exception as e: + logger.debug(f"计算持仓持续时间失败: {e}") + + strategy_type = position_info.get('strategyType', 'trend_following') + Trade.update_exit( trade_id=trade_id, exit_price=current_price_float, exit_reason=exit_reason, pnl=pnl, - pnl_percent=pnl_percent_margin # 修复:使用 pnl_percent_margin 而不是 pnl_percent + pnl_percent=pnl_percent_margin, # 修复:使用 pnl_percent_margin 而不是 pnl_percent + strategy_type=strategy_type, + duration_minutes=duration_minutes ) logger.info(f"{symbol} [自动平仓] ✓ 数据库记录已更新 (盈亏: {pnl:.2f} USDT, {pnl_percent_margin:.2f}% of margin)") except Exception as e: diff --git a/trading_system/strategy.py b/trading_system/strategy.py index 4c5cfa3..67bbc31 100644 --- a/trading_system/strategy.py +++ b/trading_system/strategy.py @@ -301,19 +301,14 @@ class TradingStrategy: ema20 = symbol_info.get('ema20') ema50 = symbol_info.get('ema50') - # 多周期共振检查:4H定方向 + # 多周期共振检查:4H定方向(使用多指标投票机制) price_4h = symbol_info.get('price_4h', current_price) ema20_4h = symbol_info.get('ema20_4h') + ema50_4h = symbol_info.get('ema50_4h') + macd_4h = symbol_info.get('macd_4h') - # 判断4H周期趋势方向 - trend_4h = None # 'up', 'down', 'neutral' - if ema20_4h is not None: - if price_4h > ema20_4h: - trend_4h = 'up' - elif price_4h < ema20_4h: - trend_4h = 'down' - else: - trend_4h = 'neutral' + # 判断4H周期趋势方向(多指标投票) + trend_4h = self._judge_trend_4h(price_4h, ema20_4h, ema50_4h, macd_4h) signal_strength = 0 reasons = [] @@ -326,136 +321,84 @@ class TradingStrategy: elif trend_4h == 'down': # 4H趋势向下,只允许做空信号 logger.debug(f"{symbol} 4H趋势向下,只允许做空信号") - elif trend_4h is None: - # 无法判断4H趋势,记录警告 - logger.warning(f"{symbol} 无法判断4H趋势,可能影响信号质量") + elif trend_4h == 'neutral': + # 4H趋势中性,记录警告 + logger.warning(f"{symbol} 4H趋势中性,信号质量可能降低") - # 策略1:均值回归(震荡市场,高胜率) - if market_regime == 'ranging': - # RSI超卖,做多信号(需4H趋势向上或中性) - if rsi and rsi < 30: - if trend_4h in ('up', 'neutral', None): - signal_strength += 4 - reasons.append(f"RSI超卖({rsi:.1f})") - if direction is None: - direction = 'BUY' - else: - reasons.append(f"RSI超卖但4H趋势向下,禁止逆势做多") - - # RSI超买,做空信号(需4H趋势向下或中性) - elif rsi and rsi > 70: - if trend_4h in ('down', 'neutral', None): - signal_strength += 4 - reasons.append(f"RSI超买({rsi:.1f})") - if direction is None: - direction = 'SELL' - else: - reasons.append(f"RSI超买但4H趋势向上,禁止逆势做空") - - # 布林带下轨,做多信号(需4H趋势向上或中性) - if bollinger and current_price <= bollinger['lower']: - if trend_4h in ('up', 'neutral', None): - signal_strength += 3 - reasons.append("触及布林带下轨") - if direction is None: - direction = 'BUY' - else: - reasons.append("触及布林带下轨但4H趋势向下,禁止逆势做多") - - # 布林带上轨,做空信号(需4H趋势向下或中性) - elif bollinger and current_price >= bollinger['upper']: - if trend_4h in ('down', 'neutral', None): - signal_strength += 3 - reasons.append("触及布林带上轨") - if direction is None: - direction = 'SELL' - else: - reasons.append("触及布林带上轨但4H趋势向上,禁止逆势做空") + # 简化策略:只做趋势跟踪,移除均值回归策略(避免信号冲突) + # 策略权重配置(根据文档建议) + TREND_SIGNAL_WEIGHTS = { + 'macd_cross': 5, # MACD金叉/死叉 + 'ema_cross': 4, # EMA20上穿/下穿EMA50 + 'price_above_ema20': 3, # 价格在EMA20之上/下 + '4h_trend_confirmation': 2, # 4H趋势确认 + } - # 策略2:趋势跟踪(趋势市场) - elif market_regime == 'trending': + # 趋势跟踪策略(不再区分市场状态,统一使用趋势指标) + # MACD金叉/死叉(权重最高) + if macd and macd['macd'] > macd['signal'] and macd['histogram'] > 0: # MACD金叉,做多信号(需4H趋势向上或中性) - if macd and macd['macd'] > macd['signal'] and macd['histogram'] > 0: + if trend_4h in ('up', 'neutral', None): + signal_strength += TREND_SIGNAL_WEIGHTS['macd_cross'] + reasons.append("MACD金叉") + if direction is None: + direction = 'BUY' + else: + reasons.append("MACD金叉但4H趋势向下,禁止逆势做多") + + elif macd and macd['macd'] < macd['signal'] and macd['histogram'] < 0: + # MACD死叉,做空信号(需4H趋势向下或中性) + if trend_4h in ('down', 'neutral', None): + signal_strength += TREND_SIGNAL_WEIGHTS['macd_cross'] + reasons.append("MACD死叉") + if direction is None: + direction = 'SELL' + else: + reasons.append("MACD死叉但4H趋势向上,禁止逆势做空") + + # EMA均线系统 + if ema20 and ema50: + if current_price > ema20 > ema50: # 上升趋势 if trend_4h in ('up', 'neutral', None): - signal_strength += 3 - reasons.append("MACD金叉") + signal_strength += TREND_SIGNAL_WEIGHTS['ema_cross'] + reasons.append("EMA20上穿EMA50,上升趋势") if direction is None: direction = 'BUY' else: - reasons.append("MACD金叉但4H趋势向下,禁止逆势做多") - - # MACD死叉,做空信号(需4H趋势向下或中性) - elif macd and macd['macd'] < macd['signal'] and macd['histogram'] < 0: + reasons.append("1H均线向上但4H趋势向下,禁止逆势做多") + elif current_price < ema20 < ema50: # 下降趋势 if trend_4h in ('down', 'neutral', None): - signal_strength += 3 - reasons.append("MACD死叉") + signal_strength += TREND_SIGNAL_WEIGHTS['ema_cross'] + reasons.append("EMA20下穿EMA50,下降趋势") if direction is None: direction = 'SELL' else: - reasons.append("MACD死叉但4H趋势向上,禁止逆势做空") - - # 均线系统 - if ema20 and ema50: - if current_price > ema20 > ema50: # 上升趋势 - if trend_4h in ('up', 'neutral', None): - signal_strength += 2 - reasons.append("价格在均线之上") - if direction is None: - direction = 'BUY' - else: - reasons.append("1H均线向上但4H趋势向下,禁止逆势做多") - elif current_price < ema20 < ema50: # 下降趋势 - if trend_4h in ('down', 'neutral', None): - signal_strength += 2 - reasons.append("价格在均线之下") - if direction is None: - direction = 'SELL' - else: - reasons.append("1H均线向下但4H趋势向上,禁止逆势做空") + reasons.append("1H均线向下但4H趋势向上,禁止逆势做空") - # 策略3:综合信号(提高胜率) - # 多个指标同时确认时,信号更强 - confirmations = 0 + # 价格与EMA20关系 + if ema20: + if current_price > ema20: + if trend_4h in ('up', 'neutral', None) and direction == 'BUY': + signal_strength += TREND_SIGNAL_WEIGHTS['price_above_ema20'] + reasons.append("价格在EMA20之上") + elif current_price < ema20: + if trend_4h in ('down', 'neutral', None) and direction == 'SELL': + signal_strength += TREND_SIGNAL_WEIGHTS['price_above_ema20'] + reasons.append("价格在EMA20之下") - # RSI确认 - if rsi: - if direction == 'BUY' and rsi < 50: - confirmations += 1 - elif direction == 'SELL' and rsi > 50: - confirmations += 1 - - # MACD确认 - if macd: - if direction == 'BUY' and macd['histogram'] > 0: - confirmations += 1 - elif direction == 'SELL' and macd['histogram'] < 0: - confirmations += 1 - - # 布林带确认 - if bollinger: - if direction == 'BUY' and current_price < bollinger['middle']: - confirmations += 1 - elif direction == 'SELL' and current_price > bollinger['middle']: - confirmations += 1 - - # 多个指标确认时,增加信号强度 - if confirmations >= 2: - signal_strength += 2 - reasons.append(f"{confirmations}个指标确认") - - # 多周期共振加分:如果4H趋势与信号方向一致,增加信号强度 + # 4H趋势确认加分 if direction and trend_4h: if (direction == 'BUY' and trend_4h == 'up') or (direction == 'SELL' and trend_4h == 'down'): - signal_strength += 2 + signal_strength += TREND_SIGNAL_WEIGHTS['4h_trend_confirmation'] reasons.append("4H周期共振确认") elif (direction == 'BUY' and trend_4h == 'down') or (direction == 'SELL' and trend_4h == 'up'): - # 逆势信号,降低信号强度或直接拒绝 - signal_strength -= 3 - reasons.append("⚠️ 逆4H趋势,信号强度降低") + # 逆势信号,直接拒绝 + signal_strength = 0 + reasons.append("❌ 逆4H趋势,拒绝交易") # 判断是否应该交易(信号强度 >= 7 才交易,提高胜率) min_signal_strength = config.TRADING_CONFIG.get('MIN_SIGNAL_STRENGTH', 7) - should_trade = signal_strength >= min_signal_strength + should_trade = signal_strength >= min_signal_strength and direction is not None # 如果信号方向与4H趋势相反,直接拒绝交易 if direction and trend_4h: @@ -464,16 +407,72 @@ class TradingStrategy: reasons.append("❌ 禁止逆4H趋势交易") if not should_trade and direction: - reasons.append(f"信号强度不足({signal_strength}/10)") + reasons.append(f"信号强度不足({signal_strength}/10,需要≥{min_signal_strength})") return { 'should_trade': should_trade, 'direction': direction, 'reason': ', '.join(reasons) if reasons else '无明确信号', 'strength': signal_strength, - 'trend_4h': trend_4h # 添加4H趋势信息,供推荐器使用 + 'trend_4h': trend_4h, # 添加4H趋势信息,供推荐器使用 + 'strategy_type': 'trend_following' # 标记策略类型 } + def _judge_trend_4h(self, price_4h: float, ema20_4h: Optional[float], ema50_4h: Optional[float], macd_4h: Optional[Dict]) -> str: + """ + 使用多指标投票机制判断4H趋势(避免单一指标误导) + + Args: + price_4h: 4H周期价格 + ema20_4h: 4H周期EMA20 + ema50_4h: 4H周期EMA50 + macd_4h: 4H周期MACD数据 + + Returns: + 'up', 'down', 'neutral' + """ + if ema20_4h is None: + return 'neutral' + + score = 0 + total_indicators = 0 + + # 指标1:价格 vs EMA20 + if price_4h > ema20_4h: + score += 1 + elif price_4h < ema20_4h: + score -= 1 + total_indicators += 1 + + # 指标2:EMA20 vs EMA50 + if ema50_4h is not None: + if ema20_4h > ema50_4h: + score += 1 + elif ema20_4h < ema50_4h: + score -= 1 + total_indicators += 1 + + # 指标3:MACD histogram + if macd_4h and isinstance(macd_4h, dict): + macd_hist = macd_4h.get('histogram', 0) + if macd_hist > 0: + score += 1 + elif macd_hist < 0: + score -= 1 + total_indicators += 1 + + # 多数投票:如果score >= 2,趋势向上;如果score <= -2,趋势向下;否则中性 + if total_indicators == 0: + return 'neutral' + + # 至少需要2个指标确认 + if score >= 2: + return 'up' + elif score <= -2: + return 'down' + else: + return 'neutral' + async def _periodic_sync_positions(self): """ 定期同步币安持仓状态(独立任务,不依赖市场扫描)