This commit is contained in:
薇薇安 2026-01-17 10:38:20 +08:00
parent 5d0b0a9974
commit ff22683da2
10 changed files with 1129 additions and 215 deletions

215
OPTIMIZATION_SUMMARY.md Normal file
View File

@ -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. ✅ **添加统计**:为后续优化提供数据支持
**核心改进**
- 策略更简单、更清晰
- 参数更合理、更稳定
- 判断更准确、更可靠
- 数据更完整、更有用
**下一步**
- 观察实际运行效果
- 收集交易统计数据
- 根据数据进一步优化

428
STRATEGY_ANALYSIS.md Normal file
View File

@ -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 性能问题
#### 问题1WebSocket连接过多
- 每个持仓一个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. 最后添加回测和统计功能(长期优化)

View File

@ -119,8 +119,8 @@ class ConfigManager:
'TOP_N_SYMBOLS': self.get('TOP_N_SYMBOLS', 10), 'TOP_N_SYMBOLS': self.get('TOP_N_SYMBOLS', 10),
# 风险控制 # 风险控制
'STOP_LOSS_PERCENT': self.get('STOP_LOSS_PERCENT', 0.08), # 默认8%(更宽松,避免被正常波动触发) 'STOP_LOSS_PERCENT': self.get('STOP_LOSS_PERCENT', 0.10), # 默认10%(更宽松,避免被小幅波动触发)
'TAKE_PROFIT_PERCENT': self.get('TAKE_PROFIT_PERCENT', 0.15), # 默认15%(给趋势更多空间 '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_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% '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), 'USE_DYNAMIC_LEVERAGE': self.get('USE_DYNAMIC_LEVERAGE', True),
'MAX_LEVERAGE': self.get('MAX_LEVERAGE', 20), 'MAX_LEVERAGE': self.get('MAX_LEVERAGE', 20),
'USE_TRAILING_STOP': self.get('USE_TRAILING_STOP', True), 'USE_TRAILING_STOP': self.get('USE_TRAILING_STOP', True),
'TRAILING_STOP_ACTIVATION': self.get('TRAILING_STOP_ACTIVATION', 0.01), 'TRAILING_STOP_ACTIVATION': self.get('TRAILING_STOP_ACTIVATION', 0.05), # 默认5%(避免过早触发)
'TRAILING_STOP_PROTECT': self.get('TRAILING_STOP_PROTECT', 0.01), 'TRAILING_STOP_PROTECT': self.get('TRAILING_STOP_PROTECT', 0.03), # 默认3%(更合理)
} }

View File

@ -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`);

View File

@ -141,8 +141,8 @@ INSERT INTO `trading_config` (`config_key`, `config_value`, `config_type`, `cate
('MAX_SCAN_SYMBOLS', '500', 'number', 'scan', '扫描的最大交易对数量0表示扫描所有建议100-500'), ('MAX_SCAN_SYMBOLS', '500', 'number', 'scan', '扫描的最大交易对数量0表示扫描所有建议100-500'),
-- 风险控制 -- 风险控制
('STOP_LOSS_PERCENT', '0.08', 'number', 'risk', '止损:8%(相对于保证金,更宽松避免被正常波动触发)'), ('STOP_LOSS_PERCENT', '0.10', 'number', 'risk', '止损:10%(相对于保证金,更宽松避免被小幅波动触发)'),
('TAKE_PROFIT_PERCENT', '0.15', 'number', 'risk', '止盈:15%(相对于保证金,给趋势更多空间'), ('TAKE_PROFIT_PERCENT', '0.20', 'number', 'risk', '止盈:20%(相对于保证金,提高盈亏比'),
('MIN_STOP_LOSS_PRICE_PCT', '0.02', 'number', 'risk', '最小止损价格变动2%(防止止损过紧)'), ('MIN_STOP_LOSS_PRICE_PCT', '0.02', 'number', 'risk', '最小止损价格变动2%(防止止损过紧)'),
('MIN_TAKE_PROFIT_PRICE_PCT', '0.03', 'number', 'risk', '最小止盈价格变动3%(防止止盈过紧)'), ('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', '是否启用动态杠杆(根据信号强度调整杠杆倍数)'), ('USE_DYNAMIC_LEVERAGE', 'true', 'boolean', 'strategy', '是否启用动态杠杆(根据信号强度调整杠杆倍数)'),
('MAX_LEVERAGE', '20', 'number', 'strategy', '最大杠杆倍数(动态杠杆上限)'), ('MAX_LEVERAGE', '20', 'number', 'strategy', '最大杠杆倍数(动态杠杆上限)'),
('USE_TRAILING_STOP', 'true', 'boolean', 'strategy', '是否使用移动止损'), ('USE_TRAILING_STOP', 'true', 'boolean', 'strategy', '是否使用移动止损'),
('TRAILING_STOP_ACTIVATION', '0.01', 'number', 'strategy', '移动止损激活阈值(盈利1%后激活'), ('TRAILING_STOP_ACTIVATION', '0.05', 'number', 'strategy', '移动止损激活阈值(盈利5%后激活,避免过早触发'),
('TRAILING_STOP_PROTECT', '0.01', 'number', 'strategy', '移动止损保护利润(保护1%利润'), ('TRAILING_STOP_PROTECT', '0.03', 'number', 'strategy', '移动止损保护利润(保护3%利润,更合理'),
-- 持仓同步 -- 持仓同步
('POSITION_SYNC_INTERVAL', '300', 'number', 'scan', '持仓状态同步间隔默认5分钟用于同步币安实际持仓与数据库状态'), ('POSITION_SYNC_INTERVAL', '300', 'number', 'scan', '持仓状态同步间隔默认5分钟用于同步币安实际持仓与数据库状态'),

View File

@ -110,7 +110,7 @@ class Trade:
return db.execute_one("SELECT LAST_INSERT_ID() as id")['id'] return db.execute_one("SELECT LAST_INSERT_ID() as id")['id']
@staticmethod @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: Args:
@ -141,12 +141,23 @@ class Trade:
f"跳过更新 exit_order_id只更新其他字段" f"跳过更新 exit_order_id只更新其他字段"
) )
# 只更新其他字段,不更新 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( db.execute_update(
"""UPDATE trades f"UPDATE trades SET {', '.join(update_fields)} WHERE id = %s",
SET exit_price = %s, exit_time = %s, tuple(update_values)
exit_reason = %s, pnl = %s, pnl_percent = %s, status = 'closed'
WHERE id = %s""",
(exit_price, exit_time, exit_reason, pnl, pnl_percent, trade_id)
) )
return return
except Exception as e: except Exception as e:
@ -155,13 +166,24 @@ class Trade:
# 正常更新(包括 exit_order_id # 正常更新(包括 exit_order_id
try: 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( db.execute_update(
"""UPDATE trades f"UPDATE trades SET {', '.join(update_fields)} WHERE id = %s",
SET exit_price = %s, exit_time = %s, tuple(update_values)
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)
) )
except Exception as e: except Exception as e:
# 如果更新失败(可能是唯一约束冲突),尝试不更新 exit_order_id # 如果更新失败(可能是唯一约束冲突),尝试不更新 exit_order_id
@ -172,12 +194,23 @@ class Trade:
f"尝试不更新 exit_order_id" f"尝试不更新 exit_order_id"
) )
# 只更新其他字段,不更新 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( db.execute_update(
"""UPDATE trades f"UPDATE trades SET {', '.join(update_fields)} WHERE id = %s",
SET exit_price = %s, exit_time = %s, tuple(update_values)
exit_reason = %s, pnl = %s, pnl_percent = %s, status = 'closed'
WHERE id = %s""",
(exit_price, exit_time, exit_reason, pnl, pnl_percent, trade_id)
) )
else: else:
# 其他错误,重新抛出 # 其他错误,重新抛出

69
new_suggestion20260117.md Normal file
View File

@ -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
}
每周或每天汇总胜率、盈亏比、每笔平均盈利/亏损。
一句话总结
“先做对,再做好”
目前你的系统复杂度较高,建议先简化策略、优化止损止盈、做好统计,再逐步迭代。不要追求完美,先追求稳定。

View File

@ -168,8 +168,8 @@ def _get_trading_config():
'MIN_CHANGE_PERCENT': 0.5, # 降低到0.5%以获取更多推荐(推荐系统可以更宽松) 'MIN_CHANGE_PERCENT': 0.5, # 降低到0.5%以获取更多推荐(推荐系统可以更宽松)
'TOP_N_SYMBOLS': 50, # 每次扫描后处理的交易对数量增加到50以获取更多推荐 'TOP_N_SYMBOLS': 50, # 每次扫描后处理的交易对数量增加到50以获取更多推荐
'MAX_SCAN_SYMBOLS': 500, # 扫描的最大交易对数量0表示扫描所有 'MAX_SCAN_SYMBOLS': 500, # 扫描的最大交易对数量0表示扫描所有
'STOP_LOSS_PERCENT': 0.08, # 止损百分比(相对于保证金),默认8%(更宽松,避免被正常波动触发) 'STOP_LOSS_PERCENT': 0.10, # 止损百分比(相对于保证金),默认10%(更宽松,避免被小幅波动触发)
'TAKE_PROFIT_PERCENT': 0.15, # 止盈百分比相对于保证金默认15%(更宽松,给趋势更多空间 'TAKE_PROFIT_PERCENT': 0.20, # 止盈百分比相对于保证金默认20%(提高盈亏比
'MIN_STOP_LOSS_PRICE_PCT': 0.02, # 最小止损价格变动百分比如0.02表示2%防止止损过紧默认2% 'MIN_STOP_LOSS_PRICE_PCT': 0.02, # 最小止损价格变动百分比如0.02表示2%防止止损过紧默认2%
'MIN_TAKE_PROFIT_PRICE_PCT': 0.03, # 最小止盈价格变动百分比如0.03表示3%防止止盈过紧默认3% 'MIN_TAKE_PROFIT_PRICE_PCT': 0.03, # 最小止盈价格变动百分比如0.03表示3%防止止盈过紧默认3%
'SCAN_INTERVAL': 3600, 'SCAN_INTERVAL': 3600,
@ -179,13 +179,13 @@ def _get_trading_config():
'ENTRY_INTERVAL': '15m', 'ENTRY_INTERVAL': '15m',
'MIN_VOLUME_24H': 5000000, # 降低到500万以获取更多推荐推荐系统可以更宽松 'MIN_VOLUME_24H': 5000000, # 降低到500万以获取更多推荐推荐系统可以更宽松
'MIN_VOLATILITY': 0.02, 'MIN_VOLATILITY': 0.02,
'MIN_SIGNAL_STRENGTH': 3, # 降低至3以获取更多推荐推荐系统使用实际交易仍可设置更高阈值 'MIN_SIGNAL_STRENGTH': 7, # 提高至7只交易高质量信号简化策略后
'LEVERAGE': 10, # 基础杠杆倍数 'LEVERAGE': 10, # 基础杠杆倍数
'USE_DYNAMIC_LEVERAGE': True, # 是否启用动态杠杆(根据信号强度调整) 'USE_DYNAMIC_LEVERAGE': True, # 是否启用动态杠杆(根据信号强度调整)
'MAX_LEVERAGE': 20, # 最大杠杆倍数(动态杠杆上限) 'MAX_LEVERAGE': 20, # 最大杠杆倍数(动态杠杆上限)
'USE_TRAILING_STOP': True, 'USE_TRAILING_STOP': True,
'TRAILING_STOP_ACTIVATION': 0.01, 'TRAILING_STOP_ACTIVATION': 0.05, # 移动止损激活从1%改为5%(避免过早触发)
'TRAILING_STOP_PROTECT': 0.01, 'TRAILING_STOP_PROTECT': 0.03, # 保护利润从1%提高到3%(更合理)
'POSITION_SYNC_INTERVAL': 300, # 持仓状态同步间隔默认5分钟 'POSITION_SYNC_INTERVAL': 300, # 持仓状态同步间隔默认5分钟
} }

View File

@ -315,6 +315,7 @@ class PositionManager:
take_profit_2 = take_profit_price take_profit_2 = take_profit_price
# 记录持仓信息(包含动态止损止盈和分步止盈) # 记录持仓信息(包含动态止损止盈和分步止盈)
from datetime import datetime
position_info = { position_info = {
'symbol': symbol, 'symbol': symbol,
'side': side, 'side': side,
@ -332,6 +333,8 @@ class PositionManager:
'initialStopLoss': stop_loss_price, # 初始止损(用于移动止损) 'initialStopLoss': stop_loss_price, # 初始止损(用于移动止损)
'leverage': leverage, 'leverage': leverage,
'entryReason': entry_reason, 'entryReason': entry_reason,
'entryTime': datetime.now(), # 记录入场时间(用于计算持仓持续时间)
'strategyType': 'trend_following', # 策略类型(简化后只有趋势跟踪)
'atr': atr, 'atr': atr,
'maxProfit': 0.0, # 记录最大盈利(用于移动止损) 'maxProfit': 0.0, # 记录最大盈利(用于移动止损)
'trailingStopActivated': False # 移动止损是否已激活 'trailingStopActivated': False # 移动止损是否已激活
@ -437,13 +440,33 @@ class PositionManager:
pnl_percent = ((entry_price - exit_price) / entry_price) * 100 pnl_percent = ((entry_price - exit_price) / entry_price) * 100
# 同步平仓时没有订单号设为None # 同步平仓时没有订单号设为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.update_exit(
trade_id=trade_id, trade_id=trade_id,
exit_price=exit_price, exit_price=exit_price,
exit_reason=reason, exit_reason=reason,
pnl=pnl, pnl=pnl,
pnl_percent=pnl_percent, pnl_percent=pnl_percent,
exit_order_id=None # 同步平仓时没有订单号 exit_order_id=None, # 同步平仓时没有订单号
strategy_type=strategy_type,
duration_minutes=duration_minutes
) )
logger.info(f"{symbol} [平仓] ✓ 数据库状态已更新") logger.info(f"{symbol} [平仓] ✓ 数据库状态已更新")
updated = True updated = True
@ -561,13 +584,34 @@ class PositionManager:
if exit_order_id: if exit_order_id:
logger.info(f"{symbol} [平仓] 币安订单号: {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.update_exit(
trade_id=trade_id, trade_id=trade_id,
exit_price=exit_price_float, exit_price=exit_price_float,
exit_reason=reason, exit_reason=reason,
pnl=pnl, pnl=pnl,
pnl_percent=pnl_percent, 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( logger.info(
f"{symbol} [平仓] ✓ 数据库记录已更新 " f"{symbol} [平仓] ✓ 数据库记录已更新 "
@ -716,36 +760,66 @@ class PositionManager:
) )
else: else:
# 盈利超过阈值后,止损移至保护利润位(基于保证金) # 盈利超过阈值后,止损移至保护利润位(基于保证金)
# 计算需要保护的利润金额 # 如果已经部分止盈,使用剩余仓位计算
protect_amount = margin * trailing_protect if position_info.get('partialProfitTaken', False):
# 计算对应的止损价 remaining_quantity = position_info.get('remainingQuantity', quantity)
if position_info['side'] == 'BUY': remaining_margin = (entry_price * remaining_quantity) / leverage if leverage > 0 else (entry_price * remaining_quantity)
# 保护利润:当前盈亏 - 保护金额 = (止损价 - 开仓价) × 数量 protect_amount = remaining_margin * trailing_protect
# 所以:止损价 = 开仓价 + (当前盈亏 - 保护金额) / 数量
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)"
)
# 检查止损(使用更新后的止损价) # 计算剩余仓位的盈亏
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:
# 未部分止盈,使用原始仓位计算
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') stop_loss = position_info.get('stopLoss')
if stop_loss is None: if stop_loss is None:
logger.warning(f"{symbol} 止损价未设置,跳过止损检查") logger.warning(f"{symbol} 止损价未设置,跳过止损检查")
# 检查止损(基于保证金收益比) elif stop_loss is not None:
stop_loss = position_info.get('stopLoss')
if stop_loss is not None:
# 计算止损对应的保证金百分比目标 # 计算止损对应的保证金百分比目标
if position_info['side'] == 'BUY': if position_info['side'] == 'BUY':
stop_loss_amount = (entry_price - stop_loss) * quantity stop_loss_amount = (entry_price - stop_loss) * quantity
@ -768,12 +842,33 @@ class PositionManager:
trade_id = position_info.get('tradeId') trade_id = position_info.get('tradeId')
if trade_id: if trade_id:
try: 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.update_exit(
trade_id=trade_id, trade_id=trade_id,
exit_price=current_price, exit_price=current_price,
exit_reason=exit_reason, exit_reason=exit_reason,
pnl=pnl_amount, 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: except Exception as e:
logger.warning(f"更新止损记录失败: {e}") logger.warning(f"更新止损记录失败: {e}")
@ -826,29 +921,6 @@ class PositionManager:
logger.info(f"{symbol} 剩余仓位止损移至成本价,配合移动止损博取更大利润") logger.info(f"{symbol} 剩余仓位止损移至成本价,配合移动止损博取更大利润")
except Exception as e: except Exception as e:
logger.error(f"{symbol} 部分止盈失败: {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: if partial_profit_taken and take_profit_2 is not None:
@ -872,20 +944,47 @@ class PositionManager:
logger.info( logger.info(
f"{symbol} 触发第二目标止盈(基于保证金): " f"{symbol} 触发第二目标止盈(基于保证金): "
f"剩余仓位盈亏={remaining_pnl_pct_margin:.2f}% of margin >= 目标={take_profit_2_pct_margin:.2f}% of margin | " 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' 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: if DB_AVAILABLE:
trade_id = position_info.get('tradeId') trade_id = position_info.get('tradeId')
if trade_id: if trade_id:
try: 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.update_exit(
trade_id=trade_id, trade_id=trade_id,
exit_price=current_price, exit_price=current_price,
exit_reason=exit_reason, exit_reason=exit_reason,
pnl=pnl_amount, pnl=total_pnl_amount,
pnl_percent=pnl_percent_margin pnl_percent=total_pnl_percent,
strategy_type=strategy_type,
duration_minutes=duration_minutes
) )
except Exception as e: except Exception as e:
logger.warning(f"更新止盈记录失败: {e}") logger.warning(f"更新止盈记录失败: {e}")
@ -916,13 +1015,33 @@ class PositionManager:
trade_id = position_info.get('tradeId') trade_id = position_info.get('tradeId')
if trade_id: if trade_id:
try: try:
Trade.update_exit( # 计算持仓持续时间和策略类型
trade_id=trade_id, entry_time = position_info.get('entryTime')
exit_price=current_price, duration_minutes = None
exit_reason=exit_reason, if entry_time:
pnl=pnl_amount, try:
pnl_percent=pnl_percent_margin 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: except Exception as e:
logger.warning(f"更新止盈记录失败: {e}") logger.warning(f"更新止盈记录失败: {e}")
if await self.close_position(symbol, reason=exit_reason): if await self.close_position(symbol, reason=exit_reason):
@ -1183,13 +1302,33 @@ class PositionManager:
# 使用 try-except 包裹,确保异常被正确处理 # 使用 try-except 包裹,确保异常被正确处理
try: 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.update_exit(
trade_id=trade_id, trade_id=trade_id,
exit_price=exit_price, exit_price=exit_price,
exit_reason='sync', # 标记为同步更新 exit_reason='sync', # 标记为同步更新
pnl=pnl, pnl=pnl,
pnl_percent=pnl_percent, 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: except Exception as update_error:
# update_exit 内部已经有异常处理,但如果仍然失败,记录错误但不中断同步流程 # update_exit 内部已经有异常处理,但如果仍然失败,记录错误但不中断同步流程
@ -1759,12 +1898,32 @@ class PositionManager:
pnl = (entry_price - current_price_float) * quantity pnl = (entry_price - current_price_float) * quantity
logger.info(f"{symbol} [自动平仓] 更新数据库记录 (ID: {trade_id})...") 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.update_exit(
trade_id=trade_id, trade_id=trade_id,
exit_price=current_price_float, exit_price=current_price_float,
exit_reason=exit_reason, exit_reason=exit_reason,
pnl=pnl, 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)") logger.info(f"{symbol} [自动平仓] ✓ 数据库记录已更新 (盈亏: {pnl:.2f} USDT, {pnl_percent_margin:.2f}% of margin)")
except Exception as e: except Exception as e:

View File

@ -301,19 +301,14 @@ class TradingStrategy:
ema20 = symbol_info.get('ema20') ema20 = symbol_info.get('ema20')
ema50 = symbol_info.get('ema50') ema50 = symbol_info.get('ema50')
# 多周期共振检查4H定方向 # 多周期共振检查4H定方向(使用多指标投票机制)
price_4h = symbol_info.get('price_4h', current_price) price_4h = symbol_info.get('price_4h', current_price)
ema20_4h = symbol_info.get('ema20_4h') ema20_4h = symbol_info.get('ema20_4h')
ema50_4h = symbol_info.get('ema50_4h')
macd_4h = symbol_info.get('macd_4h')
# 判断4H周期趋势方向 # 判断4H周期趋势方向多指标投票
trend_4h = None # 'up', 'down', 'neutral' trend_4h = self._judge_trend_4h(price_4h, ema20_4h, ema50_4h, macd_4h)
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'
signal_strength = 0 signal_strength = 0
reasons = [] reasons = []
@ -326,136 +321,84 @@ class TradingStrategy:
elif trend_4h == 'down': elif trend_4h == 'down':
# 4H趋势向下只允许做空信号 # 4H趋势向下只允许做空信号
logger.debug(f"{symbol} 4H趋势向下只允许做空信号") logger.debug(f"{symbol} 4H趋势向下只允许做空信号")
elif trend_4h is None: elif trend_4h == 'neutral':
# 无法判断4H趋势记录警告 # 4H趋势中性,记录警告
logger.warning(f"{symbol} 无法判断4H趋势可能影响信号质量") logger.warning(f"{symbol} 4H趋势中性,信号质量可能降低")
# 策略1均值回归震荡市场高胜率 # 简化策略:只做趋势跟踪,移除均值回归策略(避免信号冲突)
if market_regime == 'ranging': # 策略权重配置(根据文档建议)
# RSI超卖做多信号需4H趋势向上或中性 TREND_SIGNAL_WEIGHTS = {
if rsi and rsi < 30: 'macd_cross': 5, # MACD金叉/死叉
if trend_4h in ('up', 'neutral', None): 'ema_cross': 4, # EMA20上穿/下穿EMA50
signal_strength += 4 'price_above_ema20': 3, # 价格在EMA20之上/下
reasons.append(f"RSI超卖({rsi:.1f})") '4h_trend_confirmation': 2, # 4H趋势确认
if direction is None: }
direction = 'BUY'
else:
reasons.append(f"RSI超卖但4H趋势向下禁止逆势做多")
# RSI超买做空信号需4H趋势向下或中性 # 趋势跟踪策略(不再区分市场状态,统一使用趋势指标)
elif rsi and rsi > 70: # MACD金叉/死叉(权重最高)
if trend_4h in ('down', 'neutral', None): if macd and macd['macd'] > macd['signal'] and macd['histogram'] > 0:
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趋势向上禁止逆势做空")
# 策略2趋势跟踪趋势市场
elif market_regime == 'trending':
# MACD金叉做多信号需4H趋势向上或中性 # 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): if trend_4h in ('up', 'neutral', None):
signal_strength += 3 signal_strength += TREND_SIGNAL_WEIGHTS['ema_cross']
reasons.append("MACD金叉") reasons.append("EMA20上穿EMA50上升趋势")
if direction is None: if direction is None:
direction = 'BUY' direction = 'BUY'
else: else:
reasons.append("MACD金叉但4H趋势向下禁止逆势做多") reasons.append("1H均线向上但4H趋势向下禁止逆势做多")
elif current_price < ema20 < ema50: # 下降趋势
# MACD死叉做空信号需4H趋势向下或中性
elif macd and macd['macd'] < macd['signal'] and macd['histogram'] < 0:
if trend_4h in ('down', 'neutral', None): if trend_4h in ('down', 'neutral', None):
signal_strength += 3 signal_strength += TREND_SIGNAL_WEIGHTS['ema_cross']
reasons.append("MACD死叉") reasons.append("EMA20下穿EMA50下降趋势")
if direction is None: if direction is None:
direction = 'SELL' direction = 'SELL'
else: else:
reasons.append("MACD死叉但4H趋势向上禁止逆势做空") reasons.append("1H均线向下但4H趋势向上禁止逆势做空")
# 均线系统 # 价格与EMA20关系
if ema20 and ema50: if ema20:
if current_price > ema20 > ema50: # 上升趋势 if current_price > ema20:
if trend_4h in ('up', 'neutral', None): if trend_4h in ('up', 'neutral', None) and direction == 'BUY':
signal_strength += 2 signal_strength += TREND_SIGNAL_WEIGHTS['price_above_ema20']
reasons.append("价格在均线之上") reasons.append("价格在EMA20之上")
if direction is None: elif current_price < ema20:
direction = 'BUY' if trend_4h in ('down', 'neutral', None) and direction == 'SELL':
else: signal_strength += TREND_SIGNAL_WEIGHTS['price_above_ema20']
reasons.append("1H均线向上但4H趋势向下禁止逆势做多") reasons.append("价格在EMA20之下")
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趋势向上禁止逆势做空")
# 策略3综合信号提高胜率 # 4H趋势确认加分
# 多个指标同时确认时,信号更强
confirmations = 0
# 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趋势与信号方向一致增加信号强度
if direction and trend_4h: if direction and trend_4h:
if (direction == 'BUY' and trend_4h == 'up') or (direction == 'SELL' and trend_4h == 'down'): 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周期共振确认") reasons.append("4H周期共振确认")
elif (direction == 'BUY' and trend_4h == 'down') or (direction == 'SELL' and trend_4h == 'up'): elif (direction == 'BUY' and trend_4h == 'down') or (direction == 'SELL' and trend_4h == 'up'):
# 逆势信号,降低信号强度或直接拒绝 # 逆势信号,直接拒绝
signal_strength -= 3 signal_strength = 0
reasons.append("⚠️ 逆4H趋势信号强度降低") reasons.append("❌ 逆4H趋势拒绝交易")
# 判断是否应该交易(信号强度 >= 7 才交易,提高胜率) # 判断是否应该交易(信号强度 >= 7 才交易,提高胜率)
min_signal_strength = config.TRADING_CONFIG.get('MIN_SIGNAL_STRENGTH', 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趋势相反直接拒绝交易 # 如果信号方向与4H趋势相反直接拒绝交易
if direction and trend_4h: if direction and trend_4h:
@ -464,16 +407,72 @@ class TradingStrategy:
reasons.append("❌ 禁止逆4H趋势交易") reasons.append("❌ 禁止逆4H趋势交易")
if not should_trade and direction: if not should_trade and direction:
reasons.append(f"信号强度不足({signal_strength}/10)") reasons.append(f"信号强度不足({signal_strength}/10,需要≥{min_signal_strength})")
return { return {
'should_trade': should_trade, 'should_trade': should_trade,
'direction': direction, 'direction': direction,
'reason': ', '.join(reasons) if reasons else '无明确信号', 'reason': ', '.join(reasons) if reasons else '无明确信号',
'strength': signal_strength, '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
# 指标2EMA20 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
# 指标3MACD 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): async def _periodic_sync_positions(self):
""" """
定期同步币安持仓状态独立任务不依赖市场扫描 定期同步币安持仓状态独立任务不依赖市场扫描