mirror of
https://github.com/silencesdg/mt5_python_ea_suite.git
synced 2026-08-08 16:37:55 +00:00
add files
This commit is contained in:
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"permissions": {
|
||||||
|
"allow": [
|
||||||
|
"Bash(pip show:*)",
|
||||||
|
"Bash(python:*)",
|
||||||
|
"Bash(myenvScriptspython.exe:*)",
|
||||||
|
"Bash(\"D:\\tool\\metaTrader5\\mt5_python_ea_suite\\myenv\\Scripts\\python.exe\":*)",
|
||||||
|
"Bash(D:/tool/metaTrader5/mt5_python_ea_suite/myenv/Scripts/python.exe main.py)"
|
||||||
|
],
|
||||||
|
"deny": []
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,3 +2,5 @@
|
|||||||
/logs
|
/logs
|
||||||
/myenv
|
/myenv
|
||||||
__pycache__/
|
__pycache__/
|
||||||
|
.json
|
||||||
|
.csv
|
||||||
+112
@@ -0,0 +1,112 @@
|
|||||||
|
# 架构重构说明
|
||||||
|
|
||||||
|
## 重构概述
|
||||||
|
|
||||||
|
本次重构将原有的单一策略架构分离为**策略层**和**风险管理层**,实现了更清晰的功能分离和动态权重配置。
|
||||||
|
|
||||||
|
## 新架构结构
|
||||||
|
|
||||||
|
### 1. 策略层 (strategies/)
|
||||||
|
包含纯粹的交易策略,每个策略只负责生成买入/卖出信号:
|
||||||
|
- `ma_cross.py` - 均线交叉策略
|
||||||
|
- `rsi.py` - RSI超买超卖策略
|
||||||
|
- `bollinger.py` - 布林带策略
|
||||||
|
- `macd.py` - MACD策略
|
||||||
|
- `kdj.py` - KDJ策略
|
||||||
|
- `turtle.py` - 海龟交易法则
|
||||||
|
- `daily_breakout.py` - 日内突破策略
|
||||||
|
- `mean_reversion.py` - 均值回归策略
|
||||||
|
- `momentum_breakout.py` - 动量突破策略
|
||||||
|
|
||||||
|
### 2. 风险管理层 (risk_management/)
|
||||||
|
- `market_state.py` - 市场状态分析器(基于原resilient_trend)
|
||||||
|
- `position_manager.py` - 仓位管理器(基于原profit_protect)
|
||||||
|
- `__init__.py` - 风险管理控制器
|
||||||
|
|
||||||
|
### 3. 动态权重系统
|
||||||
|
- `dynamic_weights.py` - 动态权重管理器
|
||||||
|
|
||||||
|
## 核心改进
|
||||||
|
|
||||||
|
### 1. 市场状态判断
|
||||||
|
- 将`resilient_trend`重构为市场状态分析器
|
||||||
|
- 判断市场状态:`uptrend`、`downtrend`、`ranging`、`none`
|
||||||
|
- 提供置信度评估(0.0-1.0)
|
||||||
|
|
||||||
|
### 2. 动态权重配置
|
||||||
|
根据市场状态自动调整策略权重:
|
||||||
|
- **上升趋势**:侧重趋势跟踪策略(均线交叉、动量突破)
|
||||||
|
- **下降趋势**:侧重趋势跟踪和反转策略
|
||||||
|
- **震荡市场**:侧重均值回归和超买超卖策略
|
||||||
|
- **无明确趋势**:使用平衡的权重配置
|
||||||
|
|
||||||
|
### 3. 综合风险管理
|
||||||
|
- **止损机制**:固定止损10%
|
||||||
|
- **止盈机制**:固定止盈20% + 追踪止损
|
||||||
|
- **仓位控制**:最大单笔仓位10%
|
||||||
|
- **日内限制**:最大日亏损5%
|
||||||
|
|
||||||
|
## 使用方法
|
||||||
|
|
||||||
|
### 1. 运行回测
|
||||||
|
```bash
|
||||||
|
python main.py
|
||||||
|
```
|
||||||
|
回测会使用当前市场状态对应的权重配置进行测试。
|
||||||
|
|
||||||
|
### 2. 运行实盘交易
|
||||||
|
```bash
|
||||||
|
# 在main.py中取消注释
|
||||||
|
run_realtime()
|
||||||
|
```
|
||||||
|
实盘交易会实时分析市场状态并动态调整权重。
|
||||||
|
|
||||||
|
### 3. 运行优化
|
||||||
|
```bash
|
||||||
|
# 在main.py中取消注释
|
||||||
|
run_optimizer()
|
||||||
|
```
|
||||||
|
优化器会寻找最佳的基础权重配置。
|
||||||
|
|
||||||
|
## 配置说明
|
||||||
|
|
||||||
|
### config.py 新配置项
|
||||||
|
```python
|
||||||
|
# 风险管理参数
|
||||||
|
RISK_CONFIG = {
|
||||||
|
"stop_loss_pct": -0.10, # 固定止损:亏损10%
|
||||||
|
"profit_retracement_pct": 0.30, # 利润回撤30%止盈
|
||||||
|
"min_profit_for_trailing": 0.05, # 追踪止损激活阈值:利润5%
|
||||||
|
"take_profit_pct": 0.20, # 固定止盈:盈利20%
|
||||||
|
"max_position_size": 0.1, # 最大仓位10%
|
||||||
|
"max_daily_loss": -0.05, # 最大日亏损5%
|
||||||
|
}
|
||||||
|
|
||||||
|
# 市场状态分析参数
|
||||||
|
MARKET_STATE_CONFIG = {
|
||||||
|
"trend_period": 50,
|
||||||
|
"retracement_tolerance": 0.30,
|
||||||
|
}
|
||||||
|
|
||||||
|
# 默认权重(低置信度时使用)
|
||||||
|
DEFAULT_WEIGHTS = {
|
||||||
|
"ma_cross": 1.5,
|
||||||
|
"rsi": 1.5,
|
||||||
|
"bollinger": 1.5,
|
||||||
|
# ... 其他策略
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 优势
|
||||||
|
|
||||||
|
1. **更清晰的功能分离**:策略专注于信号生成,风险管理专注于风险控制
|
||||||
|
2. **动态适应性**:根据市场状态自动调整策略权重
|
||||||
|
3. **更强的风险控制**:多层次的风险管理机制
|
||||||
|
4. **更好的可扩展性**:易于添加新策略和风险管理规则
|
||||||
|
5. **更稳定的性能**:通过权重分散降低单一策略风险
|
||||||
|
|
||||||
|
## 兼容性
|
||||||
|
|
||||||
|
- 保持与现有回测和优化系统的兼容性
|
||||||
|
- 策略接口保持不变,现有策略无需修改
|
||||||
|
- 配置文件结构更新,但提供了向后兼容
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
此文件为 Claude Code (claude.ai/code) 在此代码库中工作时提供指导。
|
||||||
|
|
||||||
|
## 项目概述
|
||||||
|
|
||||||
|
这是一个基于Python的MetaTrader 5智能交易系统(EA)套件,用于量化交易策略。系统通过加权投票和阈值过滤组合多个交易策略,生成稳健的交易决策。
|
||||||
|
|
||||||
|
## 架构设计
|
||||||
|
|
||||||
|
### 核心组件
|
||||||
|
|
||||||
|
- **主入口**: `main.py` 提供三种执行模式:
|
||||||
|
- `run_backtest()`: 使用当前配置权重进行单次回测
|
||||||
|
- `run_optimizer()`: 遗传算法优化寻找最佳权重
|
||||||
|
- `run_realtime()`: 与MT5集成的实时交易
|
||||||
|
|
||||||
|
- **配置文件**: `config.py` 包含:
|
||||||
|
- `STRATEGIES`: (策略实例, 权重) 元组列表
|
||||||
|
- 交易参数(品种、时间周期、初始资金)
|
||||||
|
|
||||||
|
- **回测引擎**: `backtest.py` 处理:
|
||||||
|
- 历史数据策略执行
|
||||||
|
- 使用加权和的信号组合
|
||||||
|
- 防止未来函数的收益计算
|
||||||
|
|
||||||
|
- **遗传优化器**: `optimizer.py`:
|
||||||
|
- 使用DEAP库进行遗传算法
|
||||||
|
- 优化策略权重以获得最大收益
|
||||||
|
- 支持并行处理提高性能
|
||||||
|
|
||||||
|
- **工具函数**: `utils.py` 提供MT5连接管理和交易功能
|
||||||
|
- **日志系统**: `logger.py` 处理控制台/文件双日志输出,支持UTF-8编码
|
||||||
|
|
||||||
|
### 策略架构
|
||||||
|
|
||||||
|
`strategies/` 目录中的所有策略都遵循一致的模式:
|
||||||
|
|
||||||
|
```python
|
||||||
|
class Strategy:
|
||||||
|
def __init__(self): # 初始化参数
|
||||||
|
def _calculate_indicators(self, df): # 计算技术指标
|
||||||
|
def generate_signal(self): # 实时交易信号生成
|
||||||
|
def run_backtest(self, df): # 回测信号生成
|
||||||
|
```
|
||||||
|
|
||||||
|
### 信号组合逻辑
|
||||||
|
|
||||||
|
系统通过以下方式组合策略信号:
|
||||||
|
1. 加权求和:`sum(信号 * 权重,针对所有策略)`
|
||||||
|
2. 信号决策:加权和大于0买入,小于0卖出
|
||||||
|
3. 返回用于回测的组合信号序列
|
||||||
|
|
||||||
|
## 开发命令
|
||||||
|
|
||||||
|
### 环境设置
|
||||||
|
```bash
|
||||||
|
# 创建并激活虚拟环境
|
||||||
|
python -m venv myenv
|
||||||
|
# Windows
|
||||||
|
myenv\Scripts\activate
|
||||||
|
# macOS/Linux
|
||||||
|
source myenv/bin/activate
|
||||||
|
|
||||||
|
# 安装依赖
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
### 运行模式
|
||||||
|
```bash
|
||||||
|
# 运行单次回测(使用config.py权重)
|
||||||
|
python main.py
|
||||||
|
|
||||||
|
# 运行遗传优化(寻找最佳权重)
|
||||||
|
python main.py # 取消注释 run_optimizer() 调用
|
||||||
|
|
||||||
|
# 运行实时交易(使用config.py权重)
|
||||||
|
python main.py # 取消注释 run_realtime() 调用
|
||||||
|
```
|
||||||
|
|
||||||
|
## 关键开发模式
|
||||||
|
|
||||||
|
### 添加新策略
|
||||||
|
1. 在 `strategies/` 目录创建新文件
|
||||||
|
2. 实现必需的Strategy类方法
|
||||||
|
3. 添加导入和策略实例到 `config.py:STRATEGIES`
|
||||||
|
|
||||||
|
### 配置管理
|
||||||
|
- 策略权重需要从优化器结果手动更新
|
||||||
|
- 所有交易参数集中在 `config.py` 中
|
||||||
|
|
||||||
|
### 错误处理
|
||||||
|
- 策略执行中的全面异常处理
|
||||||
|
- 日志文件使用UTF-8编码
|
||||||
|
- 操作前进行MT5连接验证
|
||||||
|
|
||||||
|
### 性能考虑
|
||||||
|
- 优化器预加载历史数据避免重复I/O
|
||||||
|
- 遗传算法启用并行处理
|
||||||
|
- 日志器使用单例模式防止重复处理器
|
||||||
|
|
||||||
|
## 语言要求
|
||||||
|
|
||||||
|
- **所有开发和文档必须使用中文**(根据GEMINI.md)
|
||||||
|
- 错误日志在 `logs/error.log`
|
||||||
|
- 策略日志在 `logs/strategy.log`
|
||||||
+119
-5
@@ -1,5 +1,8 @@
|
|||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
from trade_logger import trade_logger
|
||||||
|
from risk_management import RiskController
|
||||||
|
from config import SIGNAL_THRESHOLDS
|
||||||
|
from logger import logger
|
||||||
class BacktestEngine:
|
class BacktestEngine:
|
||||||
def __init__(self, df):
|
def __init__(self, df):
|
||||||
"""
|
"""
|
||||||
@@ -11,9 +14,15 @@ class BacktestEngine:
|
|||||||
"""
|
"""
|
||||||
执行策略的run_backtest,得到信号序列
|
执行策略的run_backtest,得到信号序列
|
||||||
"""
|
"""
|
||||||
return strategy.run_backtest(self.df)
|
signals = strategy.run_backtest(self.df)
|
||||||
|
# 记录策略信号统计
|
||||||
|
buy_count = (signals == 1).sum()
|
||||||
|
sell_count = (signals == -1).sum()
|
||||||
|
neutral_count = (signals == 0).sum()
|
||||||
|
logger.info(f"策略 {strategy.__class__.__module__} 信号统计 - 买入: {buy_count}, 卖出: {sell_count}, 中性: {neutral_count}")
|
||||||
|
return signals
|
||||||
|
|
||||||
def combine_signals(self, signals_list, weights, buy_threshold, sell_threshold):
|
def combine_signals(self, signals_list, weights):
|
||||||
"""
|
"""
|
||||||
多策略信号加权合成,并根据阈值生成最终信号
|
多策略信号加权合成,并根据阈值生成最终信号
|
||||||
返回合成信号序列
|
返回合成信号序列
|
||||||
@@ -23,14 +32,21 @@ class BacktestEngine:
|
|||||||
combined = weighted_signals.sum(axis=1)
|
combined = weighted_signals.sum(axis=1)
|
||||||
|
|
||||||
def apply_threshold(score):
|
def apply_threshold(score):
|
||||||
if score >= buy_threshold:
|
if score > SIGNAL_THRESHOLDS["buy_threshold"]:
|
||||||
return 1
|
return 1
|
||||||
elif score <= sell_threshold:
|
elif score < SIGNAL_THRESHOLDS["sell_threshold"]:
|
||||||
return -1
|
return -1
|
||||||
else:
|
else:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
combined_signal = combined.apply(apply_threshold)
|
combined_signal = combined.apply(apply_threshold)
|
||||||
|
|
||||||
|
# 记录信号统计
|
||||||
|
buy_signals = (combined_signal == 1).sum()
|
||||||
|
sell_signals = (combined_signal == -1).sum()
|
||||||
|
neutral_signals = (combined_signal == 0).sum()
|
||||||
|
logger.info(f"信号统计 - 买入: {buy_signals}, 卖出: {sell_signals}, 中性: {neutral_signals}")
|
||||||
|
|
||||||
return combined_signal
|
return combined_signal
|
||||||
|
|
||||||
def calc_returns(self, signals):
|
def calc_returns(self, signals):
|
||||||
@@ -43,3 +59,101 @@ class BacktestEngine:
|
|||||||
df['strategy_returns'] = df['signal'] * df['returns']
|
df['strategy_returns'] = df['signal'] * df['returns']
|
||||||
cum_ret = (1 + df['strategy_returns']).cumprod() - 1
|
cum_ret = (1 + df['strategy_returns']).cumprod() - 1
|
||||||
return cum_ret
|
return cum_ret
|
||||||
|
|
||||||
|
def calc_returns_with_trades(self, signals, symbol="XAUUSD"):
|
||||||
|
"""
|
||||||
|
根据信号计算策略回测收益率,并记录每一笔交易
|
||||||
|
使用RiskController进行风险管理,防止频繁交易
|
||||||
|
"""
|
||||||
|
df = self.df.copy()
|
||||||
|
df['signal'] = signals.shift(1).fillna(0) # 防止未来函数
|
||||||
|
|
||||||
|
# 初始化风险管理器
|
||||||
|
risk_controller = RiskController()
|
||||||
|
|
||||||
|
# 记录交易
|
||||||
|
in_position = False
|
||||||
|
position_direction = None
|
||||||
|
position_price = 0
|
||||||
|
position_time = None
|
||||||
|
last_trade_time = None
|
||||||
|
min_trade_interval = 0 # 取消最小交易间隔限制
|
||||||
|
|
||||||
|
for i in range(1, len(df)):
|
||||||
|
current_signal = df['signal'].iloc[i]
|
||||||
|
current_price = df['close'].iloc[i]
|
||||||
|
current_time = df.index[i] if hasattr(df.index, 'to_pydatetime') else i
|
||||||
|
|
||||||
|
# 取消交易间隔限制检查
|
||||||
|
can_trade = True
|
||||||
|
|
||||||
|
# 定期打印进度(每1000根K线)
|
||||||
|
if i % 1000 == 0:
|
||||||
|
logger.info(f"回测进度: {i}/{len(df)-1}")
|
||||||
|
|
||||||
|
# 开仓信号
|
||||||
|
if current_signal != 0 and not in_position and can_trade:
|
||||||
|
direction = 'buy' if current_signal == 1 else 'sell'
|
||||||
|
|
||||||
|
# 检查风险管理器是否允许交易
|
||||||
|
if risk_controller.should_allow_trade(direction):
|
||||||
|
trade_logger.open_position(symbol, direction, current_price, current_time, f"signal_{current_signal}")
|
||||||
|
in_position = True
|
||||||
|
position_direction = direction
|
||||||
|
position_price = current_price
|
||||||
|
position_time = current_time
|
||||||
|
last_trade_time = current_time
|
||||||
|
|
||||||
|
# 更新RiskController的持仓信息
|
||||||
|
position_type = "long" if direction == 'buy' else "short"
|
||||||
|
risk_controller.update_position_entry(current_price, position_type)
|
||||||
|
|
||||||
|
# 持仓时检查风险管理条件(无论信号如何)
|
||||||
|
if in_position:
|
||||||
|
# 使用RiskController检查风险管理条件
|
||||||
|
risk_action, risk_reason = risk_controller.check_risk_management(current_price)
|
||||||
|
|
||||||
|
if risk_action != "none":
|
||||||
|
trade_logger.close_position(symbol, current_price, current_time, f"risk_management_{risk_action}")
|
||||||
|
risk_controller.execute_risk_action(risk_action, risk_reason)
|
||||||
|
in_position = False
|
||||||
|
continue # 跳过后续信号处理
|
||||||
|
|
||||||
|
# 中性信号处理
|
||||||
|
elif current_signal == 0 and in_position:
|
||||||
|
trade_logger.close_position(symbol, current_price, current_time, "signal_to_neutral")
|
||||||
|
in_position = False
|
||||||
|
|
||||||
|
# 反向信号(取消交易间隔限制)
|
||||||
|
elif current_signal != 0 and in_position and can_trade:
|
||||||
|
# 只有当信号明显反向时才平仓并反向
|
||||||
|
if (current_signal == 1 and position_direction == 'sell') or \
|
||||||
|
(current_signal == -1 and position_direction == 'buy'):
|
||||||
|
# 先平仓
|
||||||
|
trade_logger.close_position(symbol, current_price, current_time, "signal_reverse")
|
||||||
|
in_position = False
|
||||||
|
|
||||||
|
# 立即开新仓(取消等待间隔)
|
||||||
|
direction = 'buy' if current_signal == 1 else 'sell'
|
||||||
|
if risk_controller.should_allow_trade(direction):
|
||||||
|
trade_logger.open_position(symbol, direction, current_price, current_time, f"signal_{current_signal}")
|
||||||
|
in_position = True
|
||||||
|
position_direction = direction
|
||||||
|
position_price = current_price
|
||||||
|
position_time = current_time
|
||||||
|
last_trade_time = current_time
|
||||||
|
|
||||||
|
# 更新RiskController的持仓信息
|
||||||
|
position_type = "long" if direction == 'buy' else "short"
|
||||||
|
risk_controller.update_position_entry(current_price, position_type)
|
||||||
|
|
||||||
|
# 持仓到最后
|
||||||
|
elif in_position and i == len(df) - 1:
|
||||||
|
trade_logger.close_position(symbol, current_price, current_time, "end_of_backtest")
|
||||||
|
in_position = False
|
||||||
|
|
||||||
|
# 计算收益率
|
||||||
|
df['returns'] = df['close'].pct_change()
|
||||||
|
df['strategy_returns'] = df['signal'] * df['returns']
|
||||||
|
cum_ret = (1 + df['strategy_returns']).cumprod() - 1
|
||||||
|
return cum_ret
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
trade_id,symbol,direction,open_price,open_time,strategy_signal,status,close_price,close_time,close_reason,profit_loss,profit_loss_pct
|
||||||
|
1,XAUUSD,buy,3325.65,3,signal_1.0,closed,3321.32,45,risk_management_close_long,-4.329999999999927,-0.13020011125644393
|
||||||
|
2,XAUUSD,sell,3320.07,46,signal_-1.0,closed,3316.63,48,risk_management_close_long,3.4400000000000546,0.10361227323520451
|
||||||
|
3,XAUUSD,sell,3318.83,49,signal_-1.0,closed,3315.46,57,risk_management_close_long,3.369999999999891,0.1015418084083816
|
||||||
|
4,XAUUSD,sell,3314.19,58,signal_-1.0,closed,3310.61,62,risk_management_close_long,3.5799999999999272,0.10802036093283508
|
||||||
|
5,XAUUSD,sell,3312.83,63,signal_-1.0,closed,3306.49,16527,risk_management_close_long,6.3400000000001455,0.19137716091680362
|
||||||
|
6,XAUUSD,sell,3309.13,16528,signal_-1.0,closed,3303.25,16531,risk_management_close_long,5.880000000000109,0.17769020860468188
|
||||||
|
7,XAUUSD,sell,3302.25,16532,signal_-1.0,closed,3298.6,19352,risk_management_close_long,3.650000000000091,0.11053069876599564
|
||||||
|
8,XAUUSD,sell,3294.11,19353,signal_-1.0,closed,3289.99,19363,risk_management_close_long,4.120000000000346,0.12507171891650082
|
||||||
|
9,XAUUSD,sell,3291.61,19364,signal_-1.0,closed,3287.05,19561,risk_management_close_long,4.559999999999945,0.13853403045925688
|
||||||
|
10,XAUUSD,sell,3285.18,19562,signal_-1.0,closed,3281.16,19566,risk_management_close_long,4.019999999999982,0.12236772414296879
|
||||||
|
11,XAUUSD,sell,3283.41,19567,signal_-1.0,closed,3277.08,19574,risk_management_close_long,6.329999999999927,0.19278737653841363
|
||||||
|
12,XAUUSD,sell,3280.13,19575,signal_-1.0,closed,3275.76,19580,risk_management_close_long,4.369999999999891,0.13322642700136553
|
||||||
|
13,XAUUSD,sell,3280.44,19581,signal_-1.0,closed,3275.42,19587,risk_management_close_long,5.019999999999982,0.1530282523076167
|
||||||
|
14,XAUUSD,sell,3275.99,19588,signal_-1.0,closed,3272.71,19593,risk_management_close_long,3.2799999999997453,0.1001224057460415
|
||||||
|
15,XAUUSD,sell,3274.53,19594,signal_-1.0,closed,3271.01,19603,risk_management_close_long,3.519999999999982,0.10749634298662651
|
||||||
|
@@ -0,0 +1,212 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"trade_id": 1,
|
||||||
|
"symbol": "XAUUSD",
|
||||||
|
"direction": "buy",
|
||||||
|
"open_price": 3325.65,
|
||||||
|
"open_time": 3,
|
||||||
|
"strategy_signal": "signal_1.0",
|
||||||
|
"status": "closed",
|
||||||
|
"close_price": 3321.32,
|
||||||
|
"close_time": 45,
|
||||||
|
"close_reason": "risk_management_close_long",
|
||||||
|
"profit_loss": -4.329999999999927,
|
||||||
|
"profit_loss_pct": -0.13020011125644393
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"trade_id": 2,
|
||||||
|
"symbol": "XAUUSD",
|
||||||
|
"direction": "sell",
|
||||||
|
"open_price": 3320.07,
|
||||||
|
"open_time": 46,
|
||||||
|
"strategy_signal": "signal_-1.0",
|
||||||
|
"status": "closed",
|
||||||
|
"close_price": 3316.63,
|
||||||
|
"close_time": 48,
|
||||||
|
"close_reason": "risk_management_close_long",
|
||||||
|
"profit_loss": 3.4400000000000546,
|
||||||
|
"profit_loss_pct": 0.10361227323520451
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"trade_id": 3,
|
||||||
|
"symbol": "XAUUSD",
|
||||||
|
"direction": "sell",
|
||||||
|
"open_price": 3318.83,
|
||||||
|
"open_time": 49,
|
||||||
|
"strategy_signal": "signal_-1.0",
|
||||||
|
"status": "closed",
|
||||||
|
"close_price": 3315.46,
|
||||||
|
"close_time": 57,
|
||||||
|
"close_reason": "risk_management_close_long",
|
||||||
|
"profit_loss": 3.369999999999891,
|
||||||
|
"profit_loss_pct": 0.1015418084083816
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"trade_id": 4,
|
||||||
|
"symbol": "XAUUSD",
|
||||||
|
"direction": "sell",
|
||||||
|
"open_price": 3314.19,
|
||||||
|
"open_time": 58,
|
||||||
|
"strategy_signal": "signal_-1.0",
|
||||||
|
"status": "closed",
|
||||||
|
"close_price": 3310.61,
|
||||||
|
"close_time": 62,
|
||||||
|
"close_reason": "risk_management_close_long",
|
||||||
|
"profit_loss": 3.5799999999999272,
|
||||||
|
"profit_loss_pct": 0.10802036093283508
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"trade_id": 5,
|
||||||
|
"symbol": "XAUUSD",
|
||||||
|
"direction": "sell",
|
||||||
|
"open_price": 3312.83,
|
||||||
|
"open_time": 63,
|
||||||
|
"strategy_signal": "signal_-1.0",
|
||||||
|
"status": "closed",
|
||||||
|
"close_price": 3306.49,
|
||||||
|
"close_time": 16527,
|
||||||
|
"close_reason": "risk_management_close_long",
|
||||||
|
"profit_loss": 6.3400000000001455,
|
||||||
|
"profit_loss_pct": 0.19137716091680362
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"trade_id": 6,
|
||||||
|
"symbol": "XAUUSD",
|
||||||
|
"direction": "sell",
|
||||||
|
"open_price": 3309.13,
|
||||||
|
"open_time": 16528,
|
||||||
|
"strategy_signal": "signal_-1.0",
|
||||||
|
"status": "closed",
|
||||||
|
"close_price": 3303.25,
|
||||||
|
"close_time": 16531,
|
||||||
|
"close_reason": "risk_management_close_long",
|
||||||
|
"profit_loss": 5.880000000000109,
|
||||||
|
"profit_loss_pct": 0.17769020860468188
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"trade_id": 7,
|
||||||
|
"symbol": "XAUUSD",
|
||||||
|
"direction": "sell",
|
||||||
|
"open_price": 3302.25,
|
||||||
|
"open_time": 16532,
|
||||||
|
"strategy_signal": "signal_-1.0",
|
||||||
|
"status": "closed",
|
||||||
|
"close_price": 3298.6,
|
||||||
|
"close_time": 19352,
|
||||||
|
"close_reason": "risk_management_close_long",
|
||||||
|
"profit_loss": 3.650000000000091,
|
||||||
|
"profit_loss_pct": 0.11053069876599564
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"trade_id": 8,
|
||||||
|
"symbol": "XAUUSD",
|
||||||
|
"direction": "sell",
|
||||||
|
"open_price": 3294.11,
|
||||||
|
"open_time": 19353,
|
||||||
|
"strategy_signal": "signal_-1.0",
|
||||||
|
"status": "closed",
|
||||||
|
"close_price": 3289.99,
|
||||||
|
"close_time": 19363,
|
||||||
|
"close_reason": "risk_management_close_long",
|
||||||
|
"profit_loss": 4.120000000000346,
|
||||||
|
"profit_loss_pct": 0.12507171891650082
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"trade_id": 9,
|
||||||
|
"symbol": "XAUUSD",
|
||||||
|
"direction": "sell",
|
||||||
|
"open_price": 3291.61,
|
||||||
|
"open_time": 19364,
|
||||||
|
"strategy_signal": "signal_-1.0",
|
||||||
|
"status": "closed",
|
||||||
|
"close_price": 3287.05,
|
||||||
|
"close_time": 19561,
|
||||||
|
"close_reason": "risk_management_close_long",
|
||||||
|
"profit_loss": 4.559999999999945,
|
||||||
|
"profit_loss_pct": 0.13853403045925688
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"trade_id": 10,
|
||||||
|
"symbol": "XAUUSD",
|
||||||
|
"direction": "sell",
|
||||||
|
"open_price": 3285.18,
|
||||||
|
"open_time": 19562,
|
||||||
|
"strategy_signal": "signal_-1.0",
|
||||||
|
"status": "closed",
|
||||||
|
"close_price": 3281.16,
|
||||||
|
"close_time": 19566,
|
||||||
|
"close_reason": "risk_management_close_long",
|
||||||
|
"profit_loss": 4.019999999999982,
|
||||||
|
"profit_loss_pct": 0.12236772414296879
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"trade_id": 11,
|
||||||
|
"symbol": "XAUUSD",
|
||||||
|
"direction": "sell",
|
||||||
|
"open_price": 3283.41,
|
||||||
|
"open_time": 19567,
|
||||||
|
"strategy_signal": "signal_-1.0",
|
||||||
|
"status": "closed",
|
||||||
|
"close_price": 3277.08,
|
||||||
|
"close_time": 19574,
|
||||||
|
"close_reason": "risk_management_close_long",
|
||||||
|
"profit_loss": 6.329999999999927,
|
||||||
|
"profit_loss_pct": 0.19278737653841363
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"trade_id": 12,
|
||||||
|
"symbol": "XAUUSD",
|
||||||
|
"direction": "sell",
|
||||||
|
"open_price": 3280.13,
|
||||||
|
"open_time": 19575,
|
||||||
|
"strategy_signal": "signal_-1.0",
|
||||||
|
"status": "closed",
|
||||||
|
"close_price": 3275.76,
|
||||||
|
"close_time": 19580,
|
||||||
|
"close_reason": "risk_management_close_long",
|
||||||
|
"profit_loss": 4.369999999999891,
|
||||||
|
"profit_loss_pct": 0.13322642700136553
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"trade_id": 13,
|
||||||
|
"symbol": "XAUUSD",
|
||||||
|
"direction": "sell",
|
||||||
|
"open_price": 3280.44,
|
||||||
|
"open_time": 19581,
|
||||||
|
"strategy_signal": "signal_-1.0",
|
||||||
|
"status": "closed",
|
||||||
|
"close_price": 3275.42,
|
||||||
|
"close_time": 19587,
|
||||||
|
"close_reason": "risk_management_close_long",
|
||||||
|
"profit_loss": 5.019999999999982,
|
||||||
|
"profit_loss_pct": 0.1530282523076167
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"trade_id": 14,
|
||||||
|
"symbol": "XAUUSD",
|
||||||
|
"direction": "sell",
|
||||||
|
"open_price": 3275.99,
|
||||||
|
"open_time": 19588,
|
||||||
|
"strategy_signal": "signal_-1.0",
|
||||||
|
"status": "closed",
|
||||||
|
"close_price": 3272.71,
|
||||||
|
"close_time": 19593,
|
||||||
|
"close_reason": "risk_management_close_long",
|
||||||
|
"profit_loss": 3.2799999999997453,
|
||||||
|
"profit_loss_pct": 0.1001224057460415
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"trade_id": 15,
|
||||||
|
"symbol": "XAUUSD",
|
||||||
|
"direction": "sell",
|
||||||
|
"open_price": 3274.53,
|
||||||
|
"open_time": 19594,
|
||||||
|
"strategy_signal": "signal_-1.0",
|
||||||
|
"status": "closed",
|
||||||
|
"close_price": 3271.01,
|
||||||
|
"close_time": 19603,
|
||||||
|
"close_reason": "risk_management_close_long",
|
||||||
|
"profit_loss": 3.519999999999982,
|
||||||
|
"profit_loss_pct": 0.10749634298662651
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -1,24 +1,93 @@
|
|||||||
from strategies import ma_cross, rsi, bollinger, mean_reversion, momentum_breakout, macd, kdj, turtle, daily_breakout, profit_protect, resilient_trend
|
# 交易配置
|
||||||
|
|
||||||
SYMBOL = "XAUUSD"
|
SYMBOL = "XAUUSD"
|
||||||
INTERVAL = 60 # 秒
|
INTERVAL = 60 # 秒
|
||||||
INITIAL_CAPITAL = 10000 # 初始资金
|
INITIAL_CAPITAL = 10000 # 初始资金
|
||||||
|
|
||||||
# 策略权重配置 (策略实例, 权重)
|
# 时间配置
|
||||||
STRATEGIES = [
|
TIMEFRAME = 1 # M1 (1分钟图)
|
||||||
(ma_cross.Strategy(), 2.28),
|
|
||||||
(rsi.Strategy(), 1.84),
|
|
||||||
(bollinger.Strategy(), 1.46),
|
|
||||||
(mean_reversion.Strategy(), 1.90),
|
|
||||||
(momentum_breakout.Strategy(), 2.77),
|
|
||||||
(macd.Strategy(), 1.70),
|
|
||||||
(kdj.Strategy(), 0.65),
|
|
||||||
(turtle.Strategy(), 0.68),
|
|
||||||
(daily_breakout.Strategy(), 0.66),
|
|
||||||
(profit_protect.Strategy(), 0.57),
|
|
||||||
(resilient_trend.Strategy(), 1.0), # 新增带容错的趋势策略
|
|
||||||
]
|
|
||||||
|
|
||||||
# 交易信号阈值
|
# 回测时间范围 (格式: "YYYY-MM-DD")
|
||||||
BUY_THRESHOLD = 1.5
|
# 注意:这些日期需要确保在MT5服务器上有可用数据
|
||||||
SELL_THRESHOLD = -1.5
|
BACKTEST_START_DATE = "2025-05-01"
|
||||||
|
BACKTEST_END_DATE = "2025-08-01"
|
||||||
|
|
||||||
|
# 优化器时间范围 (格式: "YYYY-MM-DD")
|
||||||
|
# 注意:确保与回测时间不重叠
|
||||||
|
OPTIMIZER_START_DATE = "2025-04-01"
|
||||||
|
OPTIMIZER_END_DATE = "2025-05-01"
|
||||||
|
|
||||||
|
# 安全设置:如果日期获取失败,自动回退到数据量模式
|
||||||
|
USE_DATE_RANGE = False # 设置为False可强制使用数据量模式
|
||||||
|
|
||||||
|
# 兼容性配置 (如果日期配置不可用,则使用数据量)
|
||||||
|
BACKTEST_COUNT = 30000 # 回测数据量
|
||||||
|
OPTIMIZER_COUNT = 5000 # 优化器数据量
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# 信号阈值配置
|
||||||
|
SIGNAL_THRESHOLDS = {
|
||||||
|
"buy_threshold": 0.5, # 买入信号阈值
|
||||||
|
"sell_threshold": -0.5, # 卖出信号阈值
|
||||||
|
}
|
||||||
|
|
||||||
|
# 风险管理参数
|
||||||
|
RISK_CONFIG = {
|
||||||
|
"stop_loss_pct": -0.001, # 固定止损:亏损0.1%
|
||||||
|
"profit_retracement_pct": 0.10, # 利润回撤10%止盈
|
||||||
|
"min_profit_for_trailing": 0.05, # 追踪止损激活阈值:利润0.5%
|
||||||
|
"take_profit_pct": 0.20, # 固定止盈:盈利20%
|
||||||
|
"max_position_size": 1, # 最大仓位100%
|
||||||
|
"max_daily_loss": -0.10, # 最大日亏损10%
|
||||||
|
}
|
||||||
|
|
||||||
|
# 市场状态分析参数
|
||||||
|
MARKET_STATE_CONFIG = {
|
||||||
|
"trend_period": 50,
|
||||||
|
"retracement_tolerance": 0.30,
|
||||||
|
"volume_period": 20,
|
||||||
|
"volume_ma_period": 10,
|
||||||
|
}
|
||||||
|
|
||||||
|
# 波浪理论策略配置
|
||||||
|
WAVE_THEORY_CONFIG = {
|
||||||
|
"daily_data_count": 30, # 日线数据天数
|
||||||
|
"ema_short": 5,
|
||||||
|
"ema_medium": 13,
|
||||||
|
"ema_long": 34,
|
||||||
|
"wave_period": 21,
|
||||||
|
"range_period": 20,
|
||||||
|
"adx_period": 14,
|
||||||
|
}
|
||||||
|
|
||||||
|
# 市场趋势判断权重配置
|
||||||
|
TREND_INDICATOR_WEIGHTS = {
|
||||||
|
"price_breakout": 0.35, # 价格突破权重
|
||||||
|
"volume_confirmation": 0.25, # 成交量确认权重
|
||||||
|
"momentum oscillator": 0.20, # 动量震荡指标权重
|
||||||
|
"moving_average": 0.20, # 移动平均线权重
|
||||||
|
}
|
||||||
|
|
||||||
|
# 趋势判断阈值
|
||||||
|
TREND_THRESHOLDS = {
|
||||||
|
"strong_trend": 0.7, # 强趋势阈值
|
||||||
|
"weak_trend": 0.4, # 弱趋势阈值
|
||||||
|
"volume_spike": 1.5, # 成交量突增倍数
|
||||||
|
"oversold": 30, # 超卖阈值 (RSI)
|
||||||
|
"overbought": 70, # 超买阈值 (RSI)
|
||||||
|
}
|
||||||
|
|
||||||
|
# 动态权重配置(当市场状态判断置信度低时使用的基础权重)
|
||||||
|
DEFAULT_WEIGHTS = {
|
||||||
|
"ma_cross": 1.31,
|
||||||
|
"rsi": 0.41,
|
||||||
|
"bollinger": 1.19,
|
||||||
|
"mean_reversion": 0.75,
|
||||||
|
"momentum_breakout": 1.5,
|
||||||
|
"macd": 0.8,
|
||||||
|
"kdj": 2.02,
|
||||||
|
"turtle": 0.11,
|
||||||
|
"daily_breakout": 0.28,
|
||||||
|
"wave_theory": 0.8, # 波浪理论策略基础权重
|
||||||
|
"risk_management": 2.0, # 风险管理策略权重
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,90 @@
|
|||||||
|
from strategies import ma_cross, rsi, bollinger, mean_reversion, momentum_breakout, macd, kdj, turtle, daily_breakout, wave_theory, risk_management
|
||||||
|
from config import DEFAULT_WEIGHTS
|
||||||
|
from logger import logger
|
||||||
|
class DynamicWeightManager:
|
||||||
|
"""
|
||||||
|
动态权重管理器,根据风险管理器的建议动态调整策略权重
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, risk_controller):
|
||||||
|
self.risk_controller = risk_controller
|
||||||
|
|
||||||
|
# 策略实例映射
|
||||||
|
self.strategy_instances = {
|
||||||
|
'ma_cross': ma_cross.Strategy(),
|
||||||
|
'rsi': rsi.Strategy(),
|
||||||
|
'bollinger': bollinger.Strategy(),
|
||||||
|
'mean_reversion': mean_reversion.Strategy(),
|
||||||
|
'momentum_breakout': momentum_breakout.Strategy(),
|
||||||
|
'macd': macd.Strategy(),
|
||||||
|
'kdj': kdj.Strategy(),
|
||||||
|
'turtle': turtle.Strategy(),
|
||||||
|
'daily_breakout': daily_breakout.Strategy(),
|
||||||
|
'wave_theory': wave_theory.Strategy(),
|
||||||
|
'risk_management': risk_management.Strategy()
|
||||||
|
}
|
||||||
|
|
||||||
|
# 当前权重配置
|
||||||
|
self.current_weights = None
|
||||||
|
self.current_market_state = "none"
|
||||||
|
self.current_confidence = 0.0
|
||||||
|
|
||||||
|
def get_current_strategies_and_weights(self):
|
||||||
|
"""
|
||||||
|
获取当前策略实例和权重配置
|
||||||
|
返回: [(strategy_instance, weight), ...]
|
||||||
|
"""
|
||||||
|
# 获取动态权重
|
||||||
|
dynamic_weights, market_state, confidence = self.risk_controller.get_dynamic_weights()
|
||||||
|
|
||||||
|
# 更新当前状态
|
||||||
|
self.current_weights = dynamic_weights
|
||||||
|
self.current_market_state = market_state
|
||||||
|
self.current_confidence = confidence
|
||||||
|
|
||||||
|
# 构建策略列表
|
||||||
|
strategies_with_weights = []
|
||||||
|
for strategy_name, weight in dynamic_weights.items():
|
||||||
|
if strategy_name in self.strategy_instances:
|
||||||
|
strategies_with_weights.append(
|
||||||
|
(self.strategy_instances[strategy_name], weight)
|
||||||
|
)
|
||||||
|
|
||||||
|
return strategies_with_weights
|
||||||
|
|
||||||
|
def get_weight_info(self):
|
||||||
|
"""
|
||||||
|
获取当前权重配置信息
|
||||||
|
"""
|
||||||
|
return {
|
||||||
|
'market_state': self.current_market_state,
|
||||||
|
'confidence': self.current_confidence,
|
||||||
|
'weights': self.current_weights
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_strategy_instance(self, strategy_name):
|
||||||
|
"""
|
||||||
|
获取指定策略的实例
|
||||||
|
"""
|
||||||
|
return self.strategy_instances.get(strategy_name)
|
||||||
|
|
||||||
|
def add_custom_strategy(self, strategy_name, strategy_instance, base_weight=1.0):
|
||||||
|
"""
|
||||||
|
添加自定义策略
|
||||||
|
"""
|
||||||
|
self.strategy_instances[strategy_name] = strategy_instance
|
||||||
|
logger.info(f"添加自定义策略: {strategy_name}, 基础权重: {base_weight}")
|
||||||
|
|
||||||
|
def remove_strategy(self, strategy_name):
|
||||||
|
"""
|
||||||
|
移除策略
|
||||||
|
"""
|
||||||
|
if strategy_name in self.strategy_instances:
|
||||||
|
del self.strategy_instances[strategy_name]
|
||||||
|
logger.info(f"移除策略: {strategy_name}")
|
||||||
|
|
||||||
|
def list_available_strategies(self):
|
||||||
|
"""
|
||||||
|
列出所有可用策略
|
||||||
|
"""
|
||||||
|
return list(self.strategy_instances.keys())
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
import importlib
|
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
from utils import initialize, shutdown, get_rates, close_all, send_order
|
from utils import initialize, shutdown, get_rates, close_all, send_order
|
||||||
from backtest import BacktestEngine
|
from backtest import BacktestEngine
|
||||||
from logger import logger
|
from logger import logger
|
||||||
from config import INITIAL_CAPITAL, STRATEGIES, BUY_THRESHOLD, SELL_THRESHOLD
|
from config import INITIAL_CAPITAL, SYMBOL, TIMEFRAME, BACKTEST_COUNT, BACKTEST_START_DATE, BACKTEST_END_DATE, USE_DATE_RANGE
|
||||||
|
from risk_management import RiskController
|
||||||
|
from dynamic_weights import DynamicWeightManager
|
||||||
|
from trade_logger import trade_logger
|
||||||
|
from logger import logger
|
||||||
# 导入优化器
|
# 导入优化器
|
||||||
from optimizer import run_optimizer
|
from optimizer import run_optimizer
|
||||||
|
|
||||||
@@ -13,78 +15,234 @@ def run_realtime():
|
|||||||
logger.error("MT5初始化失败")
|
logger.error("MT5初始化失败")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# 初始化风险管理和动态权重
|
||||||
|
risk_controller = RiskController()
|
||||||
|
weight_manager = DynamicWeightManager(risk_controller)
|
||||||
|
|
||||||
|
# 获取动态策略配置
|
||||||
|
strategies_with_weights = weight_manager.get_current_strategies_and_weights()
|
||||||
|
weight_info = weight_manager.get_weight_info()
|
||||||
|
|
||||||
|
logger.info(f"市场状态: {weight_info['market_state']}, 置信度: {weight_info['confidence']:.2f}")
|
||||||
|
|
||||||
|
# 执行策略信号生成
|
||||||
signals = []
|
signals = []
|
||||||
weights = []
|
weights = []
|
||||||
for strat, weight in STRATEGIES:
|
for strat, weight in strategies_with_weights:
|
||||||
try:
|
try:
|
||||||
logger.info(f"执行策略:{strat.__class__.__module__}")
|
logger.info(f"执行策略:{strat.__class__.__module__}, 权重: {weight:.2f}")
|
||||||
signal = strat.generate_signal()
|
|
||||||
|
# 特殊处理风险管理策略
|
||||||
|
if strat.__class__.__module__ == 'risk_management':
|
||||||
|
signal = strat.generate_signal_with_sync(risk_controller)
|
||||||
|
else:
|
||||||
|
signal = strat.generate_signal()
|
||||||
|
|
||||||
signals.append(signal)
|
signals.append(signal)
|
||||||
weights.append(weight)
|
weights.append(weight)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception(f"运行策略 {strat.__class__.__module__} 时出错:{e}")
|
logger.exception(f"运行策略 {strat.__class__.__module__} 时出错:{e}")
|
||||||
|
|
||||||
|
# 计算加权信号
|
||||||
weighted_signal_sum = sum(s * w for s, w in zip(signals, weights))
|
weighted_signal_sum = sum(s * w for s, w in zip(signals, weights))
|
||||||
|
logger.info(f"加权信号总和: {weighted_signal_sum:.2f}")
|
||||||
|
|
||||||
if weighted_signal_sum >= BUY_THRESHOLD:
|
# 获取当前价格用于风险管理
|
||||||
logger.info(f"加权信号总和 ({weighted_signal_sum:.2f}) 达到买入阈值 ({BUY_THRESHOLD}),发送买入信号")
|
current_price = get_current_price(SYMBOL)
|
||||||
close_all("XAUUSD")
|
current_time = pd.Timestamp.now()
|
||||||
send_order("XAUUSD", 'buy')
|
|
||||||
elif weighted_signal_sum <= SELL_THRESHOLD:
|
# 检查风险管理条件
|
||||||
logger.info(f"加权信号总和 ({weighted_signal_sum:.2f}) 达到卖出阈值 ({SELL_THRESHOLD}),发送卖出信号")
|
if current_price:
|
||||||
close_all("XAUUSD")
|
risk_action, risk_reason = risk_controller.check_risk_management(current_price)
|
||||||
send_order("XAUUSD", 'sell')
|
if risk_action != "none":
|
||||||
|
logger.info(f"触发风险管理: {risk_action}, 原因: {risk_reason}")
|
||||||
|
# 记录平仓交易
|
||||||
|
if trade_logger.current_position:
|
||||||
|
trade_logger.close_position(SYMBOL, current_price, current_time, f"risk_management_{risk_action}")
|
||||||
|
risk_controller.execute_risk_action(risk_action, risk_reason)
|
||||||
|
shutdown()
|
||||||
|
return
|
||||||
|
|
||||||
|
# 获取当前持仓状态,取消交易间隔限制
|
||||||
|
current_position = trade_logger.current_position
|
||||||
|
min_trade_interval_minutes = 0 # 取消最小交易间隔限制
|
||||||
|
|
||||||
|
# 取消交易间隔限制检查
|
||||||
|
can_trade = True
|
||||||
|
|
||||||
|
# 调试信息:打印当前状态
|
||||||
|
logger.info(f"当前持仓状态: {current_position is not None}")
|
||||||
|
if current_position:
|
||||||
|
time_since_open = (current_time - current_position['open_time']).total_seconds() / 60
|
||||||
|
logger.info(f"持仓信息: {current_position['direction']} @ {current_position['open_price']:.2f}, 持仓时间: {time_since_open:.1f}分钟")
|
||||||
|
|
||||||
|
# 执行交易决策
|
||||||
|
logger.info(f"加权信号总和: {weighted_signal_sum:.2f}, 买入阈值: 0, 卖出阈值: 0")
|
||||||
|
logger.info(f"风险管理允许买入: {risk_controller.should_allow_trade('buy')}, 允许卖出: {risk_controller.should_allow_trade('sell')}")
|
||||||
|
logger.info(f"允许交易: {can_trade}")
|
||||||
|
|
||||||
|
if weighted_signal_sum > 0:
|
||||||
|
logger.info(f"检测到买入信号: {weighted_signal_sum:.2f} > 0")
|
||||||
|
if risk_controller.should_allow_trade("buy") and can_trade:
|
||||||
|
# 检查是否已经有相同方向的持仓
|
||||||
|
if current_position and current_position['direction'] == 'buy':
|
||||||
|
logger.info(f"已持有多头仓位,信号强度: {weighted_signal_sum:.2f},不重复开仓")
|
||||||
|
else:
|
||||||
|
logger.info(f"=== 准备执行买入交易 ===")
|
||||||
|
logger.info(f"买入信号: 加权总和({weighted_signal_sum:.2f}) > 0")
|
||||||
|
logger.info(f"当前价格: {current_price:.2f}")
|
||||||
|
logger.info(f"风险管理允许: {risk_controller.should_allow_trade('buy')}")
|
||||||
|
logger.info(f"交易间隔检查通过: {can_trade}")
|
||||||
|
|
||||||
|
# 先平掉现有仓位(如果有)
|
||||||
|
if current_position:
|
||||||
|
close_all(SYMBOL)
|
||||||
|
trade_logger.close_position(SYMBOL, current_price, current_time, "close_before_buy")
|
||||||
|
|
||||||
|
# 开新仓
|
||||||
|
send_order(SYMBOL, 'buy')
|
||||||
|
trade_logger.open_position(SYMBOL, 'buy', current_price, current_time, f"weighted_signal_{weighted_signal_sum:.2f}")
|
||||||
|
|
||||||
|
if current_price:
|
||||||
|
risk_controller.update_position_entry(current_price, "long")
|
||||||
|
else:
|
||||||
|
if not can_trade:
|
||||||
|
logger.info(f"买入信号被阻止: 交易间隔限制")
|
||||||
|
else:
|
||||||
|
logger.info(f"买入信号被风险管理阻止: risk_controller.should_allow_trade('buy') = {risk_controller.should_allow_trade('buy')}")
|
||||||
|
|
||||||
|
elif weighted_signal_sum < 0:
|
||||||
|
logger.info(f"检测到卖出信号: {weighted_signal_sum:.2f} < 0")
|
||||||
|
if risk_controller.should_allow_trade("sell") and can_trade:
|
||||||
|
# 检查是否已经有相同方向的持仓
|
||||||
|
if current_position and current_position['direction'] == 'sell':
|
||||||
|
logger.info(f"已持有空头仓位,信号强度: {weighted_signal_sum:.2f},不重复开仓")
|
||||||
|
else:
|
||||||
|
logger.info(f"=== 准备执行卖出交易 ===")
|
||||||
|
logger.info(f"卖出信号: 加权总和({weighted_signal_sum:.2f}) < 0")
|
||||||
|
logger.info(f"当前价格: {current_price:.2f}")
|
||||||
|
logger.info(f"风险管理允许: {risk_controller.should_allow_trade('sell')}")
|
||||||
|
logger.info(f"交易间隔检查通过: {can_trade}")
|
||||||
|
|
||||||
|
# 先平掉现有仓位(如果有)
|
||||||
|
if current_position:
|
||||||
|
close_all(SYMBOL)
|
||||||
|
trade_logger.close_position(SYMBOL, current_price, current_time, "close_before_sell")
|
||||||
|
|
||||||
|
# 开新仓
|
||||||
|
send_order(SYMBOL, 'sell')
|
||||||
|
trade_logger.open_position(SYMBOL, 'sell', current_price, current_time, f"weighted_signal_{weighted_signal_sum:.2f}")
|
||||||
|
|
||||||
|
if current_price:
|
||||||
|
risk_controller.update_position_entry(current_price, "short")
|
||||||
|
else:
|
||||||
|
logger.info(f"卖出信号被阻止")
|
||||||
else:
|
else:
|
||||||
logger.info(f"加权信号总和 ({weighted_signal_sum:.2f}) 未达到交易阈值,无操作")
|
# 信号在中间区域,使用RiskController决定是否平仓
|
||||||
|
if current_position:
|
||||||
|
# 使用RiskController检查风险管理条件
|
||||||
|
risk_action, risk_reason = risk_controller.check_risk_management(current_price)
|
||||||
|
|
||||||
|
if risk_action != "none":
|
||||||
|
close_all(SYMBOL)
|
||||||
|
trade_logger.close_position(SYMBOL, current_price, current_time, f"signal_neutral_{risk_action}")
|
||||||
|
risk_controller.execute_risk_action(risk_action, risk_reason)
|
||||||
|
|
||||||
shutdown()
|
shutdown()
|
||||||
|
|
||||||
|
def get_current_price(symbol):
|
||||||
|
"""获取当前价格"""
|
||||||
|
try:
|
||||||
|
import MetaTrader5 as mt5
|
||||||
|
tick = mt5.symbol_info_tick(symbol)
|
||||||
|
return tick.bid if tick else None
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"获取当前价格失败: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
def run_backtest():
|
def run_backtest():
|
||||||
if not initialize():
|
if not initialize():
|
||||||
logger.error("MT5初始化失败")
|
logger.error("MT5初始化失败")
|
||||||
return
|
return
|
||||||
|
|
||||||
symbol = "XAUUSD"
|
# 重置交易日志记录器
|
||||||
timeframe = 1 # M1
|
trade_logger.__init__()
|
||||||
count = 50000
|
|
||||||
rates = get_rates(symbol, timeframe, count)
|
# 根据配置选择获取数据的方式
|
||||||
shutdown()
|
if USE_DATE_RANGE:
|
||||||
|
rates = get_rates(SYMBOL, TIMEFRAME, BACKTEST_COUNT, BACKTEST_START_DATE, BACKTEST_END_DATE)
|
||||||
|
else:
|
||||||
|
rates = get_rates(SYMBOL, TIMEFRAME, BACKTEST_COUNT)
|
||||||
|
|
||||||
if rates is None:
|
if rates is None:
|
||||||
logger.error("获取历史数据失败")
|
logger.error("获取历史数据失败")
|
||||||
|
shutdown()
|
||||||
return
|
return
|
||||||
|
|
||||||
logger.info(f"初始资金: {INITIAL_CAPITAL}")
|
logger.info(f"初始资金: {INITIAL_CAPITAL}")
|
||||||
|
|
||||||
df = pd.DataFrame(rates)
|
df = pd.DataFrame(rates)
|
||||||
engine = BacktestEngine(df)
|
engine = BacktestEngine(df)
|
||||||
|
|
||||||
|
# 使用动态权重管理器(需要在MT5连接状态下获取市场状态)
|
||||||
|
risk_controller = RiskController()
|
||||||
|
weight_manager = DynamicWeightManager(risk_controller)
|
||||||
|
|
||||||
|
# 获取当前策略配置(回测时使用固定权重或模拟动态权重)
|
||||||
|
strategies_with_weights = weight_manager.get_current_strategies_and_weights()
|
||||||
|
weight_info = weight_manager.get_weight_info()
|
||||||
|
|
||||||
|
logger.info(f"回测使用市场状态: {weight_info['market_state']}")
|
||||||
|
|
||||||
|
# 获取市场状态后关闭MT5连接
|
||||||
|
shutdown()
|
||||||
|
|
||||||
signals_list = []
|
signals_list = []
|
||||||
weights = []
|
weights = []
|
||||||
|
strategy_names = []
|
||||||
|
|
||||||
for strat, weight in STRATEGIES:
|
for strat, weight in strategies_with_weights:
|
||||||
try:
|
try:
|
||||||
logger.info(f"回测策略:{strat.__class__.__module__}")
|
logger.info(f"回测策略:{strat.__class__.__module__}, 权重: {weight:.2f}")
|
||||||
signals = engine.run_strategy(strat)
|
signals = engine.run_strategy(strat)
|
||||||
signals_list.append(signals)
|
signals_list.append(signals)
|
||||||
weights.append(weight)
|
weights.append(weight)
|
||||||
|
strategy_names.append(strat.__class__.__module__)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception(f"回测策略 {strat.__class__.__module__} 时出错:{e}")
|
logger.exception(f"回测策略 {strat.__class__.__module__} 时出错:{e}")
|
||||||
|
|
||||||
combined_signal = engine.combine_signals(signals_list, weights, BUY_THRESHOLD, SELL_THRESHOLD)
|
combined_signal = engine.combine_signals(signals_list, weights)
|
||||||
cum_ret = engine.calc_returns(combined_signal)
|
|
||||||
|
# 使用带交易记录的收益率计算
|
||||||
|
cum_ret = engine.calc_returns_with_trades(combined_signal, SYMBOL)
|
||||||
final_capital = INITIAL_CAPITAL * (1 + cum_ret.iloc[-1])
|
final_capital = INITIAL_CAPITAL * (1 + cum_ret.iloc[-1])
|
||||||
|
|
||||||
logger.info("策略组合回测完成")
|
logger.info("策略组合回测完成")
|
||||||
logger.info(f"最终资金: {final_capital:.2f}")
|
logger.info(f"最终资金: {final_capital:.2f}")
|
||||||
|
logger.info(f"总收益率: {(final_capital/INITIAL_CAPITAL - 1):.2%}")
|
||||||
|
logger.info("使用的策略权重:")
|
||||||
|
for name, weight in zip(strategy_names, weights):
|
||||||
|
logger.info(f" {name}: {weight:.2f}")
|
||||||
|
logger.info("最近收益率:")
|
||||||
logger.info(cum_ret.tail())
|
logger.info(cum_ret.tail())
|
||||||
|
|
||||||
|
# 打印详细的交易记录
|
||||||
|
logger.info("\n" + "="*50)
|
||||||
|
logger.info("详细交易记录:")
|
||||||
|
trade_logger.print_all_trades()
|
||||||
|
|
||||||
|
# 保存交易记录到文件
|
||||||
|
trade_logger.save_to_csv("backtest_trades.csv")
|
||||||
|
trade_logger.save_to_json("backtest_trades.json")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# --- 选择运行模式 ---
|
# --- 选择运行模式 ---
|
||||||
# 1. 运行一次回测 (使用config.py中的默认权重)
|
# 1. 运行一次回测 (使用config.py中的默认权重)
|
||||||
# run_backtest()
|
run_backtest()
|
||||||
|
|
||||||
# 2. 运行实盘交易 (使用config.py中的默认权重)
|
# 2. 运行实盘交易 (使用config.py中的默认权重)
|
||||||
# run_realtime()
|
# run_realtime()
|
||||||
|
|
||||||
# 3. 运行遗传算法优化,寻找最佳权重
|
# 3. 运行遗传算法优化,寻找最佳权重
|
||||||
run_optimizer()
|
# run_optimizer()
|
||||||
|
|||||||
+34
-14
@@ -8,12 +8,15 @@ import pandas as pd
|
|||||||
from utils import initialize, shutdown, get_rates
|
from utils import initialize, shutdown, get_rates
|
||||||
from backtest import BacktestEngine
|
from backtest import BacktestEngine
|
||||||
from logger import logger
|
from logger import logger
|
||||||
from config import INITIAL_CAPITAL, STRATEGIES, BUY_THRESHOLD, SELL_THRESHOLD
|
from config import INITIAL_CAPITAL, SYMBOL, TIMEFRAME, OPTIMIZER_COUNT, OPTIMIZER_START_DATE, OPTIMIZER_END_DATE, USE_DATE_RANGE
|
||||||
|
from risk_management import RiskController
|
||||||
|
from dynamic_weights import DynamicWeightManager
|
||||||
|
|
||||||
# 1. 定义适应度函数 (已优化)
|
# 1. 定义适应度函数 (已优化)
|
||||||
def evaluate_fitness(individual, df_data):
|
def evaluate_fitness(individual, df_data):
|
||||||
"""
|
"""
|
||||||
评估函数现在接收预先加载的DataFrame作为参数,避免了重复IO。
|
评估函数现在接收预先加载的DataFrame作为参数,避免了重复IO。
|
||||||
|
使用新的动态权重架构进行评估。
|
||||||
输入:
|
输入:
|
||||||
- individual: 一个代表策略权重的列表。
|
- individual: 一个代表策略权重的列表。
|
||||||
- df_data: 包含历史K线数据的Pandas DataFrame。
|
- df_data: 包含历史K线数据的Pandas DataFrame。
|
||||||
@@ -21,22 +24,33 @@ def evaluate_fitness(individual, df_data):
|
|||||||
"""
|
"""
|
||||||
weights = individual
|
weights = individual
|
||||||
|
|
||||||
# 直接使用传入的df_data,不再需要get_rates
|
# 使用新的架构进行评估
|
||||||
engine = BacktestEngine(df_data)
|
engine = BacktestEngine(df_data)
|
||||||
|
|
||||||
|
# 创建风险管理器和权重管理器
|
||||||
|
risk_controller = RiskController()
|
||||||
|
weight_manager = DynamicWeightManager(risk_controller)
|
||||||
|
|
||||||
|
# 获取策略实例
|
||||||
|
strategies_with_weights = weight_manager.get_current_strategies_and_weights()
|
||||||
|
|
||||||
|
# 使用优化器提供的权重替换动态权重
|
||||||
signals_list = []
|
signals_list = []
|
||||||
|
strategy_names = []
|
||||||
|
|
||||||
strategy_instances = [s for s, w in STRATEGIES]
|
for i, (strat, _) in enumerate(strategies_with_weights):
|
||||||
|
if i < len(weights):
|
||||||
|
signals = engine.run_strategy(strat)
|
||||||
|
signals_list.append(signals)
|
||||||
|
strategy_names.append(strat.__class__.__module__)
|
||||||
|
|
||||||
for strat in strategy_instances:
|
# 使用优化器权重进行组合
|
||||||
signals = engine.run_strategy(strat)
|
combined_signal = engine.combine_signals(signals_list, weights[:len(signals_list)])
|
||||||
signals_list.append(signals)
|
|
||||||
|
|
||||||
combined_signal = engine.combine_signals(signals_list, weights, BUY_THRESHOLD, SELL_THRESHOLD)
|
|
||||||
cum_ret = engine.calc_returns(combined_signal)
|
cum_ret = engine.calc_returns(combined_signal)
|
||||||
final_capital = INITIAL_CAPITAL * (1 + cum_ret.iloc[-1])
|
final_capital = INITIAL_CAPITAL * (1 + cum_ret.iloc[-1])
|
||||||
|
|
||||||
# 在优化过程中,可以注释掉这行日志以提高速度,因为它会大量输出
|
# 在优化过程中,可以注释掉这行日志以提高速度,因为它会大量输出
|
||||||
# logger.info(f"评估权重: {[f'{w:.2f}' for w in weights]} -> 最终资金: {final_capital:.2f}")
|
# logger.info(f"评估权重: {[f'{w:.2f}' for w in weights[:len(signals_list)]]} -> 最终资金: {final_capital:.2f}")
|
||||||
|
|
||||||
return (final_capital,)
|
return (final_capital,)
|
||||||
|
|
||||||
@@ -52,10 +66,11 @@ def run_optimizer():
|
|||||||
logger.error("MT5初始化失败,无法开始优化")
|
logger.error("MT5初始化失败,无法开始优化")
|
||||||
return
|
return
|
||||||
|
|
||||||
symbol = "XAUUSD"
|
# 根据配置选择获取数据的方式
|
||||||
timeframe = 1 # M1
|
if USE_DATE_RANGE:
|
||||||
count = 50000 # 使用与回测相同的数据量
|
rates = get_rates(SYMBOL, TIMEFRAME, OPTIMIZER_COUNT, OPTIMIZER_START_DATE, OPTIMIZER_END_DATE)
|
||||||
rates = get_rates(symbol, timeframe, count)
|
else:
|
||||||
|
rates = get_rates(SYMBOL, TIMEFRAME, OPTIMIZER_COUNT)
|
||||||
shutdown() # 获取数据后即可关闭连接
|
shutdown() # 获取数据后即可关闭连接
|
||||||
|
|
||||||
if rates is None:
|
if rates is None:
|
||||||
@@ -72,7 +87,12 @@ def run_optimizer():
|
|||||||
|
|
||||||
toolbox = base.Toolbox()
|
toolbox = base.Toolbox()
|
||||||
toolbox.register("attr_float", random.uniform, 0.1, 2.0)
|
toolbox.register("attr_float", random.uniform, 0.1, 2.0)
|
||||||
num_strategies = len(STRATEGIES)
|
|
||||||
|
# 使用动态权重管理器获取策略数量
|
||||||
|
risk_controller = RiskController()
|
||||||
|
weight_manager = DynamicWeightManager(risk_controller)
|
||||||
|
num_strategies = len(weight_manager.list_available_strategies())
|
||||||
|
|
||||||
toolbox.register("individual", tools.initRepeat, creator.Individual, toolbox.attr_float, n=num_strategies)
|
toolbox.register("individual", tools.initRepeat, creator.Individual, toolbox.attr_float, n=num_strategies)
|
||||||
toolbox.register("population", tools.initRepeat, list, toolbox.individual)
|
toolbox.register("population", tools.initRepeat, list, toolbox.individual)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# 风险管理模块
|
||||||
|
|
||||||
|
此模块包含所有风险管理相关的组件:
|
||||||
|
|
||||||
|
- `market_state.py`: 市场状态分析器,基于resilient_trend逻辑
|
||||||
|
- `position_manager.py`: 仓位管理器,基于profit_protect逻辑
|
||||||
|
- `__init__.py`: 风险管理控制器,统一接口
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
from .market_state import MarketStateAnalyzer
|
||||||
|
from .position_manager import PositionManager
|
||||||
|
from logger import logger
|
||||||
|
from config import RISK_CONFIG
|
||||||
|
|
||||||
|
class RiskController:
|
||||||
|
"""
|
||||||
|
风险管理控制器,统一管理市场状态分析和仓位管理
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.market_state_analyzer = MarketStateAnalyzer()
|
||||||
|
self.position_manager = PositionManager()
|
||||||
|
|
||||||
|
# 从配置文件读取风险控制参数
|
||||||
|
self.max_position_size = RISK_CONFIG.get("max_position_size", 0.1)
|
||||||
|
self.max_daily_loss = RISK_CONFIG.get("max_daily_loss", -0.05)
|
||||||
|
self.daily_pnl = 0.0 # 日内盈亏
|
||||||
|
|
||||||
|
def get_market_state(self):
|
||||||
|
"""
|
||||||
|
获取当前市场状态
|
||||||
|
"""
|
||||||
|
return self.market_state_analyzer.get_market_state()
|
||||||
|
|
||||||
|
def get_dynamic_weights(self):
|
||||||
|
"""
|
||||||
|
获取动态策略权重
|
||||||
|
"""
|
||||||
|
market_state, confidence = self.get_market_state()
|
||||||
|
logger.info(f"当前市场状态: {market_state}, 置信度: {confidence:.2f}")
|
||||||
|
|
||||||
|
weights = self.market_state_analyzer.get_strategy_weights(market_state, confidence)
|
||||||
|
logger.info(f"动态权重配置: {weights}")
|
||||||
|
|
||||||
|
return weights, market_state, confidence
|
||||||
|
|
||||||
|
def check_risk_management(self, current_price):
|
||||||
|
"""
|
||||||
|
检查风险管理条件
|
||||||
|
"""
|
||||||
|
return self.position_manager.check_risk_management(current_price)
|
||||||
|
|
||||||
|
def execute_risk_action(self, action, reason):
|
||||||
|
"""
|
||||||
|
执行风险管理操作
|
||||||
|
"""
|
||||||
|
self.position_manager.execute_risk_action(action, reason)
|
||||||
|
|
||||||
|
def update_position_entry(self, entry_price, position_type="long"):
|
||||||
|
"""
|
||||||
|
更新持仓入场信息
|
||||||
|
"""
|
||||||
|
self.position_manager.update_position_entry(entry_price, position_type)
|
||||||
|
|
||||||
|
def should_allow_trade(self, signal_type):
|
||||||
|
"""
|
||||||
|
根据风险控制决定是否允许交易
|
||||||
|
"""
|
||||||
|
# 检查日亏损限制
|
||||||
|
if self.daily_pnl <= self.max_daily_loss:
|
||||||
|
logger.warning(f"日亏损已达{self.daily_pnl:.2%},暂停交易")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 检查持仓数量限制
|
||||||
|
current_positions = len(self.position_manager.positions)
|
||||||
|
if current_positions >= 1: # 限制同时只持有一个仓位
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def update_daily_pnl(self, pnl_change):
|
||||||
|
"""
|
||||||
|
更新日内盈亏
|
||||||
|
"""
|
||||||
|
self.daily_pnl += pnl_change
|
||||||
|
logger.info(f"更新日内盈亏: {self.daily_pnl:.2%}")
|
||||||
|
|
||||||
|
def reset_daily_stats(self):
|
||||||
|
"""
|
||||||
|
重置日内统计
|
||||||
|
"""
|
||||||
|
self.daily_pnl = 0.0
|
||||||
|
logger.info("重置日内统计")
|
||||||
|
|
||||||
|
def reset_all_states(self):
|
||||||
|
"""
|
||||||
|
重置所有状态
|
||||||
|
"""
|
||||||
|
self.market_state_analyzer.reset_state()
|
||||||
|
self.position_manager.reset_positions()
|
||||||
|
self.reset_daily_stats()
|
||||||
|
logger.info("重置所有风险管理状态")
|
||||||
@@ -0,0 +1,312 @@
|
|||||||
|
import pandas as pd
|
||||||
|
import numpy as np
|
||||||
|
import MetaTrader5 as mt5
|
||||||
|
from logger import logger
|
||||||
|
from utils import get_rates
|
||||||
|
from config import MARKET_STATE_CONFIG, SYMBOL, DEFAULT_WEIGHTS, TREND_INDICATOR_WEIGHTS, TREND_THRESHOLDS
|
||||||
|
|
||||||
|
class MarketStateAnalyzer:
|
||||||
|
"""
|
||||||
|
市场状态分析器,基于resilient_trend逻辑
|
||||||
|
判断当前市场状态:uptrend, downtrend, none
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.symbol = SYMBOL
|
||||||
|
self.timeframe = mt5.TIMEFRAME_H1 # 使用小时线数据
|
||||||
|
self.hourly_data_count = 100 # 获取最近100小时的数据
|
||||||
|
self.trend_period = MARKET_STATE_CONFIG.get("trend_period", 50)
|
||||||
|
self.retracement_tolerance = MARKET_STATE_CONFIG.get("retracement_tolerance", 0.30)
|
||||||
|
self.volume_period = MARKET_STATE_CONFIG.get("volume_period", 20)
|
||||||
|
self.volume_ma_period = MARKET_STATE_CONFIG.get("volume_ma_period", 10)
|
||||||
|
|
||||||
|
# 市场状态变量
|
||||||
|
self.current_trend = "none" # none, uptrend, downtrend
|
||||||
|
self.trend_peak = 0.0
|
||||||
|
self.trend_trough = float('inf')
|
||||||
|
|
||||||
|
# 趋势指标权重
|
||||||
|
self.indicator_weights = TREND_INDICATOR_WEIGHTS
|
||||||
|
self.thresholds = TREND_THRESHOLDS
|
||||||
|
|
||||||
|
def calculate_volume_indicators(self, df):
|
||||||
|
"""计算成交量指标"""
|
||||||
|
# MT5使用tick_volume和real_volume,这里使用tick_volume作为成交量
|
||||||
|
df['volume'] = df['tick_volume'] # 将tick_volume映射为volume以便后续计算
|
||||||
|
|
||||||
|
# 成交量移动平均
|
||||||
|
df['volume_ma'] = df['volume'].rolling(self.volume_ma_period).mean()
|
||||||
|
|
||||||
|
# 成交量相对强度
|
||||||
|
df['volume_ratio'] = df['volume'] / df['volume_ma']
|
||||||
|
|
||||||
|
# 价量相关性 (最近n期)
|
||||||
|
volume_corr = df['close'].rolling(self.volume_period).corr(df['volume'])
|
||||||
|
|
||||||
|
return df, volume_corr.iloc[-1] if len(volume_corr) > 0 else 0
|
||||||
|
|
||||||
|
def calculate_momentum_indicators(self, df):
|
||||||
|
"""计算动量指标"""
|
||||||
|
# RSI
|
||||||
|
delta = df['close'].diff()
|
||||||
|
gain = (delta.where(delta > 0, 0)).rolling(window=14).mean()
|
||||||
|
loss = (-delta.where(delta < 0, 0)).rolling(window=14).mean()
|
||||||
|
rs = gain / loss
|
||||||
|
df['rsi'] = 100 - (100 / (1 + rs))
|
||||||
|
|
||||||
|
# MACD
|
||||||
|
exp1 = df['close'].ewm(span=12).mean()
|
||||||
|
exp2 = df['close'].ewm(span=26).mean()
|
||||||
|
df['macd'] = exp1 - exp2
|
||||||
|
df['macd_signal'] = df['macd'].ewm(span=9).mean()
|
||||||
|
df['macd_histogram'] = df['macd'] - df['macd_signal']
|
||||||
|
|
||||||
|
# 移动平均线
|
||||||
|
df['ma20'] = df['close'].rolling(20).mean()
|
||||||
|
df['ma50'] = df['close'].rolling(50).mean()
|
||||||
|
|
||||||
|
return df
|
||||||
|
|
||||||
|
def calculate_price_breakout_score(self, df):
|
||||||
|
"""计算价格突破得分 (0-1)"""
|
||||||
|
current_price = df['close'].iloc[-1]
|
||||||
|
high_period = df['high'].rolling(self.trend_period).max().iloc[-2]
|
||||||
|
low_period = df['low'].rolling(self.trend_period).min().iloc[-2]
|
||||||
|
|
||||||
|
# 计算价格在区间中的位置
|
||||||
|
price_position = (current_price - low_period) / (high_period - low_period)
|
||||||
|
|
||||||
|
# 突破确认
|
||||||
|
if current_price > high_period:
|
||||||
|
return 0.8 # 向上突破
|
||||||
|
elif current_price < low_period:
|
||||||
|
return 0.2 # 向下突破
|
||||||
|
else:
|
||||||
|
return price_position # 在区间内的位置
|
||||||
|
|
||||||
|
def calculate_volume_score(self, df, volume_corr):
|
||||||
|
"""计算成交量确认得分 (0-1)"""
|
||||||
|
current_volume = df['volume'].iloc[-1]
|
||||||
|
volume_ma = df['volume_ma'].iloc[-1]
|
||||||
|
volume_ratio = current_volume / volume_ma if volume_ma > 0 else 1
|
||||||
|
|
||||||
|
score = 0.5 # 基础分数
|
||||||
|
|
||||||
|
# 成交量突增确认
|
||||||
|
if volume_ratio > self.thresholds['volume_spike']:
|
||||||
|
score += 0.3
|
||||||
|
|
||||||
|
# 价量相关性
|
||||||
|
if volume_corr > 0.5: # 正相关,价量齐升
|
||||||
|
score += 0.2
|
||||||
|
elif volume_corr < -0.5: # 负相关,价跌量增
|
||||||
|
score -= 0.2
|
||||||
|
|
||||||
|
return np.clip(score, 0, 1)
|
||||||
|
|
||||||
|
def calculate_momentum_score(self, df):
|
||||||
|
"""计算动量指标得分 (0-1)"""
|
||||||
|
current_rsi = df['rsi'].iloc[-1] if not pd.isna(df['rsi'].iloc[-1]) else 50
|
||||||
|
current_macd_hist = df['macd_histogram'].iloc[-1] if not pd.isna(df['macd_histogram'].iloc[-1]) else 0
|
||||||
|
|
||||||
|
score = 0.5 # 基础分数
|
||||||
|
|
||||||
|
# RSI判断
|
||||||
|
if current_rsi > self.thresholds['overbought']:
|
||||||
|
score += 0.2 # 超买,可能继续上涨
|
||||||
|
elif current_rsi < self.thresholds['oversold']:
|
||||||
|
score -= 0.2 # 超卖,可能继续下跌
|
||||||
|
|
||||||
|
# MACD直方图判断
|
||||||
|
if current_macd_hist > 0:
|
||||||
|
score += 0.2 # MACD正向动能
|
||||||
|
else:
|
||||||
|
score -= 0.2 # MACD负向动能
|
||||||
|
|
||||||
|
return np.clip(score, 0, 1)
|
||||||
|
|
||||||
|
def calculate_ma_score(self, df):
|
||||||
|
"""计算移动平均线得分 (0-1)"""
|
||||||
|
current_price = df['close'].iloc[-1]
|
||||||
|
ma20 = df['ma20'].iloc[-1] if not pd.isna(df['ma20'].iloc[-1]) else current_price
|
||||||
|
ma50 = df['ma50'].iloc[-1] if not pd.isna(df['ma50'].iloc[-1]) else current_price
|
||||||
|
|
||||||
|
score = 0.5 # 基础分数
|
||||||
|
|
||||||
|
# 价格与均线关系
|
||||||
|
if current_price > ma20 > ma50:
|
||||||
|
score += 0.3 # 多头排列
|
||||||
|
elif current_price < ma20 < ma50:
|
||||||
|
score -= 0.3 # 空头排列
|
||||||
|
|
||||||
|
# 均线方向
|
||||||
|
if len(df) >= 3:
|
||||||
|
ma20_slope = df['ma20'].iloc[-1] - df['ma20'].iloc[-3]
|
||||||
|
ma50_slope = df['ma50'].iloc[-1] - df['ma50'].iloc[-3]
|
||||||
|
|
||||||
|
if ma20_slope > 0 and ma50_slope > 0:
|
||||||
|
score += 0.2 # 均线向上
|
||||||
|
elif ma20_slope < 0 and ma50_slope < 0:
|
||||||
|
score -= 0.2 # 均线向下
|
||||||
|
|
||||||
|
return np.clip(score, 0, 1)
|
||||||
|
|
||||||
|
def get_market_state(self):
|
||||||
|
"""
|
||||||
|
获取当前市场状态 - 使用加权多指标分析
|
||||||
|
返回: (state, confidence)
|
||||||
|
state: "uptrend", "downtrend", "none", "ranging"
|
||||||
|
confidence: 0.0-1.0 的置信度
|
||||||
|
"""
|
||||||
|
logger.info(f"开始获取市场状态数据: {self.symbol}, 时间周期: {self.timeframe}, 数据量: {self.hourly_data_count}")
|
||||||
|
|
||||||
|
rates = get_rates(self.symbol, self.timeframe, self.hourly_data_count)
|
||||||
|
|
||||||
|
# 检查数据获取状态
|
||||||
|
if rates is None:
|
||||||
|
logger.error(f"获取市场数据失败: {self.symbol}")
|
||||||
|
return "none", 0.0
|
||||||
|
|
||||||
|
logger.info(f"成功获取数据,数据量: {len(rates)}, 需要最小数据量: {max(self.trend_period, 50)}")
|
||||||
|
|
||||||
|
if len(rates) < max(self.trend_period, 50):
|
||||||
|
logger.warning(f"数据量不足: 当前{len(rates)}, 需要{max(self.trend_period, 50)}")
|
||||||
|
return "none", 0.0
|
||||||
|
|
||||||
|
df = pd.DataFrame(rates)
|
||||||
|
|
||||||
|
# 计算各项指标
|
||||||
|
df, volume_corr = self.calculate_volume_indicators(df)
|
||||||
|
df = self.calculate_momentum_indicators(df)
|
||||||
|
|
||||||
|
# 计算各项得分
|
||||||
|
price_score = self.calculate_price_breakout_score(df)
|
||||||
|
volume_score = self.calculate_volume_score(df, volume_corr)
|
||||||
|
momentum_score = self.calculate_momentum_score(df)
|
||||||
|
ma_score = self.calculate_ma_score(df)
|
||||||
|
|
||||||
|
logger.info(f"各项指标得分: 价格突破={price_score:.3f}, 成交量确认={volume_score:.3f}, "
|
||||||
|
f"动量指标={momentum_score:.3f}, 移动平均={ma_score:.3f}")
|
||||||
|
|
||||||
|
# 加权计算趋势强度
|
||||||
|
trend_strength = (
|
||||||
|
price_score * self.indicator_weights['price_breakout'] +
|
||||||
|
volume_score * self.indicator_weights['volume_confirmation'] +
|
||||||
|
momentum_score * self.indicator_weights['momentum oscillator'] +
|
||||||
|
ma_score * self.indicator_weights['moving_average']
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info(f"趋势强度计算结果: {trend_strength:.3f}")
|
||||||
|
logger.info(f"阈值参考: 强趋势={self.thresholds['strong_trend']}, 弱趋势={self.thresholds['weak_trend']}")
|
||||||
|
|
||||||
|
# 判断市场状态
|
||||||
|
if trend_strength >= self.thresholds['strong_trend']:
|
||||||
|
state = "uptrend"
|
||||||
|
confidence = min(0.9, trend_strength)
|
||||||
|
logger.info(f"判断为上涨趋势: 趋势强度{trend_strength:.3f} >= 强趋势阈值{self.thresholds['strong_trend']}")
|
||||||
|
elif trend_strength <= (1 - self.thresholds['strong_trend']):
|
||||||
|
state = "downtrend"
|
||||||
|
confidence = min(0.9, 1 - trend_strength)
|
||||||
|
logger.info(f"判断为下跌趋势: 趋势强度{trend_strength:.3f} <= {1 - self.thresholds['strong_trend']:.3f}")
|
||||||
|
elif trend_strength >= self.thresholds['weak_trend']:
|
||||||
|
state = "ranging"
|
||||||
|
confidence = 0.6
|
||||||
|
logger.info(f"判断为震荡市场: 趋势强度{trend_strength:.3f} 在弱趋势阈值{self.thresholds['weak_trend']}和强趋势阈值{self.thresholds['strong_trend']}之间")
|
||||||
|
else:
|
||||||
|
state = "none"
|
||||||
|
confidence = 0.4
|
||||||
|
logger.info(f"判断为无明确趋势: 趋势强度{trend_strength:.3f} 低于弱趋势阈值{self.thresholds['weak_trend']}")
|
||||||
|
|
||||||
|
# 更新趋势状态(用于状态机逻辑)
|
||||||
|
self.update_trend_state(df, trend_strength, state)
|
||||||
|
|
||||||
|
logger.debug(f"市场状态分析: {state}, 置信度: {confidence:.2f}, "
|
||||||
|
f"趋势强度: {trend_strength:.2f}, "
|
||||||
|
f"价格得分: {price_score:.2f}, 成交量得分: {volume_score:.2f}, "
|
||||||
|
f"动量得分: {momentum_score:.2f}, 均线得分: {ma_score:.2f}")
|
||||||
|
|
||||||
|
return state, confidence
|
||||||
|
|
||||||
|
def update_trend_state(self, df, trend_strength, new_state):
|
||||||
|
"""更新趋势状态(保持原有状态机逻辑的兼容性)"""
|
||||||
|
current_price = df['close'].iloc[-1]
|
||||||
|
|
||||||
|
if new_state == "uptrend":
|
||||||
|
self.current_trend = "uptrend"
|
||||||
|
self.trend_peak = max(self.trend_peak, current_price)
|
||||||
|
elif new_state == "downtrend":
|
||||||
|
self.current_trend = "downtrend"
|
||||||
|
self.trend_trough = min(self.trend_trough, current_price)
|
||||||
|
else:
|
||||||
|
# 检查是否需要反转趋势
|
||||||
|
if self.current_trend == "uptrend":
|
||||||
|
if current_price < self.trend_peak * (1 - self.retracement_tolerance):
|
||||||
|
self.current_trend = "none"
|
||||||
|
elif self.current_trend == "downtrend":
|
||||||
|
if current_price > self.trend_trough * (1 + self.retracement_tolerance):
|
||||||
|
self.current_trend = "none"
|
||||||
|
|
||||||
|
def get_strategy_weights(self, market_state, confidence):
|
||||||
|
"""
|
||||||
|
根据市场状态返回推荐的策略权重配置
|
||||||
|
"""
|
||||||
|
weight_configs = {
|
||||||
|
"uptrend": {
|
||||||
|
"ma_cross": 2.5,
|
||||||
|
"momentum_breakout": 2.2,
|
||||||
|
"turtle": 2.0,
|
||||||
|
"macd": 1.8,
|
||||||
|
"rsi": 1.2,
|
||||||
|
"bollinger": 1.0,
|
||||||
|
"kdj": 0.8,
|
||||||
|
"mean_reversion": 0.5,
|
||||||
|
"daily_breakout": 1.5,
|
||||||
|
"wave_theory": 1.8 # 趋势市中波浪理论权重适中
|
||||||
|
},
|
||||||
|
"downtrend": {
|
||||||
|
"ma_cross": 2.5,
|
||||||
|
"turtle": 2.2,
|
||||||
|
"momentum_breakout": 2.0,
|
||||||
|
"macd": 1.8,
|
||||||
|
"rsi": 1.5,
|
||||||
|
"bollinger": 1.2,
|
||||||
|
"kdj": 1.0,
|
||||||
|
"mean_reversion": 0.8,
|
||||||
|
"daily_breakout": 1.0,
|
||||||
|
"wave_theory": 1.8 # 趋势市中波浪理论权重适中
|
||||||
|
},
|
||||||
|
"ranging": {
|
||||||
|
"rsi": 2.5,
|
||||||
|
"bollinger": 2.2,
|
||||||
|
"mean_reversion": 2.0,
|
||||||
|
"kdj": 1.8,
|
||||||
|
"wave_theory": 3.0, # 震荡市中波浪理论权重最高
|
||||||
|
"ma_cross": 1.2,
|
||||||
|
"macd": 1.0,
|
||||||
|
"turtle": 0.8,
|
||||||
|
"momentum_breakout": 0.5,
|
||||||
|
"daily_breakout": 1.5
|
||||||
|
},
|
||||||
|
"none": DEFAULT_WEIGHTS
|
||||||
|
}
|
||||||
|
|
||||||
|
base_weights = weight_configs.get(market_state, weight_configs["none"])
|
||||||
|
|
||||||
|
# 根据置信度调整权重
|
||||||
|
if confidence > 0.7:
|
||||||
|
# 高置信度,使用推荐权重
|
||||||
|
return base_weights
|
||||||
|
elif confidence > 0.4:
|
||||||
|
# 中等置信度,混合默认权重
|
||||||
|
default_weights = weight_configs["none"]
|
||||||
|
return {k: (base_weights[k] * 0.7 + default_weights[k] * 0.3)
|
||||||
|
for k in base_weights}
|
||||||
|
else:
|
||||||
|
# 低置信度,使用默认权重
|
||||||
|
return weight_configs["none"]
|
||||||
|
|
||||||
|
def reset_state(self):
|
||||||
|
"""重置市场状态"""
|
||||||
|
self.current_trend = "none"
|
||||||
|
self.trend_peak = 0.0
|
||||||
|
self.trend_trough = float('inf')
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
import pandas as pd
|
||||||
|
from logger import logger
|
||||||
|
from utils import has_open_position, close_all
|
||||||
|
from config import RISK_CONFIG, SYMBOL
|
||||||
|
|
||||||
|
class PositionManager:
|
||||||
|
"""
|
||||||
|
仓位管理器,基于profit_protect逻辑
|
||||||
|
处理止损、止盈、追踪止损等风险管理
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.symbol = SYMBOL
|
||||||
|
|
||||||
|
# 从配置文件读取风险管理参数
|
||||||
|
self.stop_loss_pct = RISK_CONFIG.get("stop_loss_pct", -0.10)
|
||||||
|
self.profit_retracement_pct = RISK_CONFIG.get("profit_retracement_pct", 0.30)
|
||||||
|
self.min_profit_for_trailing = RISK_CONFIG.get("min_profit_for_trailing", 0.05)
|
||||||
|
self.take_profit_pct = RISK_CONFIG.get("take_profit_pct", 0.20)
|
||||||
|
|
||||||
|
# 持仓状态
|
||||||
|
self.positions = {} # symbol: {entry_price, entry_time, peak_profit}
|
||||||
|
|
||||||
|
def check_risk_management(self, current_price):
|
||||||
|
"""
|
||||||
|
检查是否需要触发风险管理操作
|
||||||
|
返回: action ("close_long", "close_short", "none"), reason
|
||||||
|
"""
|
||||||
|
# 在回测环境中,使用self.positions来判断持仓状态
|
||||||
|
# 而不是使用has_open_position()(该函数依赖MT5实时数据)
|
||||||
|
position_info = self.positions.get(self.symbol)
|
||||||
|
if not position_info:
|
||||||
|
return "none", "no_position"
|
||||||
|
|
||||||
|
entry_price = position_info['entry_price']
|
||||||
|
current_profit_pct = (current_price - entry_price) / entry_price
|
||||||
|
|
||||||
|
# 更新最高利润
|
||||||
|
self.positions[self.symbol]['peak_profit'] = max(
|
||||||
|
position_info.get('peak_profit', 0),
|
||||||
|
current_profit_pct
|
||||||
|
)
|
||||||
|
peak_profit = self.positions[self.symbol]['peak_profit']
|
||||||
|
|
||||||
|
# 检查止损条件 - 仅基于买入成本判断亏损,不考虑盈利回撤
|
||||||
|
if current_profit_pct < 0 and current_profit_pct <= self.stop_loss_pct:
|
||||||
|
reason = f"止损触发:当前亏损{current_profit_pct:.2%}"
|
||||||
|
action = "close_long" if current_profit_pct < 0 else "close_short"
|
||||||
|
return action, reason
|
||||||
|
|
||||||
|
# 检查固定止盈条件
|
||||||
|
if current_profit_pct >= self.take_profit_pct:
|
||||||
|
reason = f"止盈触发:当前盈利{current_profit_pct:.2%}"
|
||||||
|
action = "close_long" if current_profit_pct > 0 else "close_short"
|
||||||
|
return action, reason
|
||||||
|
|
||||||
|
# 检查追踪止损条件 - 只有在利润达到min_profit_for_trailing以上才激活
|
||||||
|
if peak_profit > self.min_profit_for_trailing:
|
||||||
|
retracement_from_peak = peak_profit - current_profit_pct
|
||||||
|
if peak_profit > 0:
|
||||||
|
retracement_pct = retracement_from_peak / peak_profit
|
||||||
|
if retracement_pct >= self.profit_retracement_pct:
|
||||||
|
reason = f"追踪止损:最高盈利{peak_profit:.2%},回撤{retracement_pct:.2%}"
|
||||||
|
action = "close_long" if current_profit_pct > 0 else "close_short"
|
||||||
|
return action, reason
|
||||||
|
|
||||||
|
return "none", "no_action_needed"
|
||||||
|
|
||||||
|
def execute_risk_action(self, action, reason):
|
||||||
|
"""
|
||||||
|
执行风险管理操作
|
||||||
|
"""
|
||||||
|
if action == "close_long":
|
||||||
|
logger.info(f"执行平多仓:{reason}")
|
||||||
|
close_all(self.symbol)
|
||||||
|
self.positions.pop(self.symbol, None)
|
||||||
|
elif action == "close_short":
|
||||||
|
logger.info(f"执行平空仓:{reason}")
|
||||||
|
close_all(self.symbol)
|
||||||
|
self.positions.pop(self.symbol, None)
|
||||||
|
|
||||||
|
def update_position_entry(self, entry_price, position_type="long"):
|
||||||
|
"""
|
||||||
|
更新持仓入场信息
|
||||||
|
"""
|
||||||
|
self.positions[self.symbol] = {
|
||||||
|
'entry_price': entry_price,
|
||||||
|
'entry_time': pd.Timestamp.now(),
|
||||||
|
'position_type': position_type,
|
||||||
|
'peak_profit': 0.0
|
||||||
|
}
|
||||||
|
logger.info(f"记录持仓入场:价格={entry_price:.2f}, 类型={position_type}")
|
||||||
|
|
||||||
|
def get_position_info(self):
|
||||||
|
"""
|
||||||
|
获取当前持仓信息
|
||||||
|
"""
|
||||||
|
return self.positions.get(self.symbol, None)
|
||||||
|
|
||||||
|
def reset_positions(self):
|
||||||
|
"""
|
||||||
|
重置所有持仓状态
|
||||||
|
"""
|
||||||
|
self.positions.clear()
|
||||||
|
logger.info("重置所有持仓状态")
|
||||||
+13
-11
@@ -9,8 +9,6 @@ class Strategy:
|
|||||||
self.symbol = "XAUUSD"
|
self.symbol = "XAUUSD"
|
||||||
self.timeframe = mt5.TIMEFRAME_M1
|
self.timeframe = mt5.TIMEFRAME_M1
|
||||||
self.kdj_period = 9
|
self.kdj_period = 9
|
||||||
self.kdj_buy_threshold = 10
|
|
||||||
self.kdj_sell_threshold = 90
|
|
||||||
|
|
||||||
def _calculate_indicators(self, df):
|
def _calculate_indicators(self, df):
|
||||||
"""
|
"""
|
||||||
@@ -27,7 +25,7 @@ class Strategy:
|
|||||||
def generate_signal(self):
|
def generate_signal(self):
|
||||||
"""
|
"""
|
||||||
KDJ策略实盘
|
KDJ策略实盘
|
||||||
J值小于10买入,大于90卖出
|
K线向上穿越D线(金叉)买入,K线向下穿越D线(死叉)卖出
|
||||||
"""
|
"""
|
||||||
rates = get_rates(self.symbol, self.timeframe, self.kdj_period + 30)
|
rates = get_rates(self.symbol, self.timeframe, self.kdj_period + 30)
|
||||||
if rates is None or len(rates) < self.kdj_period:
|
if rates is None or len(rates) < self.kdj_period:
|
||||||
@@ -35,26 +33,30 @@ class Strategy:
|
|||||||
df = pd.DataFrame(rates)
|
df = pd.DataFrame(rates)
|
||||||
df = self._calculate_indicators(df)
|
df = self._calculate_indicators(df)
|
||||||
|
|
||||||
if df['j'].iloc[-2] < self.kdj_buy_threshold:
|
# Golden cross
|
||||||
logger.info(f"J值小于{self.kdj_buy_threshold},产生买入信号: {self.symbol}")
|
if df['k'].iloc[-2] < df['d'].iloc[-2] and df['k'].iloc[-1] > df['d'].iloc[-1]:
|
||||||
|
logger.info(f"KDJ Golden Cross, creating buy signal: {self.symbol}")
|
||||||
return 1
|
return 1
|
||||||
elif df['j'].iloc[-2] > self.kdj_sell_threshold:
|
# Dead cross
|
||||||
logger.info(f"J值大于{self.kdj_sell_threshold},产生卖出信号: {self.symbol}")
|
elif df['k'].iloc[-2] > df['d'].iloc[-2] and df['k'].iloc[-1] < df['d'].iloc[-1]:
|
||||||
|
logger.info(f"KDJ Dead Cross, creating sell signal: {self.symbol}")
|
||||||
return -1
|
return -1
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def run_backtest(self, df):
|
def run_backtest(self, df):
|
||||||
"""
|
"""
|
||||||
KDJ回测方法
|
KDJ回测方法
|
||||||
根据J值极端生成信号
|
根据金叉和死叉生成信号
|
||||||
"""
|
"""
|
||||||
df = df.copy()
|
df = df.copy()
|
||||||
df = self._calculate_indicators(df)
|
df = self._calculate_indicators(df)
|
||||||
|
|
||||||
signals = pd.Series(0, index=df.index)
|
signals = pd.Series(0, index=df.index)
|
||||||
for i in range(self.kdj_period, len(df)):
|
for i in range(1, len(df)):
|
||||||
if df['j'].iloc[i-1] < self.kdj_buy_threshold:
|
# Golden cross
|
||||||
|
if df['k'].iloc[i-1] < df['d'].iloc[i-1] and df['k'].iloc[i] > df['d'].iloc[i]:
|
||||||
signals.iat[i] = 1
|
signals.iat[i] = 1
|
||||||
elif df['j'].iloc[i-1] > self.kdj_sell_threshold:
|
# Dead cross
|
||||||
|
elif df['k'].iloc[i-1] > df['d'].iloc[i-1] and df['k'].iloc[i] < df['d'].iloc[i]:
|
||||||
signals.iat[i] = -1
|
signals.iat[i] = -1
|
||||||
return signals
|
return signals
|
||||||
|
|||||||
@@ -1,79 +0,0 @@
|
|||||||
|
|
||||||
import pandas as pd
|
|
||||||
from logger import logger
|
|
||||||
|
|
||||||
class Strategy:
|
|
||||||
def __init__(self):
|
|
||||||
self.symbol = "XAUUSD"
|
|
||||||
# --- 策略核心参数 ---
|
|
||||||
# 固定止损线:亏损10%则卖出
|
|
||||||
self.stop_loss_pct = -0.10
|
|
||||||
# 利润回撤百分比:从最高利润点回撤30%则卖出
|
|
||||||
self.profit_retracement_pct = 0.30
|
|
||||||
# 追踪止损的激活阈值:当利润超过5%后,才开始启动追踪止损逻辑
|
|
||||||
self.min_profit_for_trailing = 0.05
|
|
||||||
|
|
||||||
def generate_signal(self):
|
|
||||||
"""
|
|
||||||
此策略为资金管理和退出策略,不产生独立的买入信号。
|
|
||||||
实盘逻辑应与其他策略结合,此处仅为框架完整性。
|
|
||||||
"""
|
|
||||||
logger.warning("ProfitProtect策略是一个退出策略,不应单独用于实盘产生信号。")
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def run_backtest(self, df):
|
|
||||||
"""
|
|
||||||
盈利保护策略回测:
|
|
||||||
- 固定止损:亏损10%卖出。
|
|
||||||
- 追踪止损:利润超过5%后启动,从最高利润点回撤30%卖出。
|
|
||||||
为了独立回测,本策略会在一开始买入,然后应用退出逻辑。
|
|
||||||
"""
|
|
||||||
df = df.copy()
|
|
||||||
signals = pd.Series(0, index=df.index)
|
|
||||||
|
|
||||||
if len(df) < 2:
|
|
||||||
return signals
|
|
||||||
|
|
||||||
# --- 回测状态变量 ---
|
|
||||||
position_open = False
|
|
||||||
entry_price = 0.0
|
|
||||||
peak_profit_pct = 0.0 # 记录达到的最高利润百分比
|
|
||||||
|
|
||||||
for i in range(len(df)):
|
|
||||||
# 如果没有持仓,就在第一个机会买入(用于独立回测)
|
|
||||||
if not position_open:
|
|
||||||
position_open = True
|
|
||||||
entry_price = df['close'].iloc[i]
|
|
||||||
signals.iat[i] = 1 # 买入信号
|
|
||||||
peak_profit_pct = 0.0 # 重置最高利润
|
|
||||||
continue
|
|
||||||
|
|
||||||
# 如果有持仓,则执行退出逻辑
|
|
||||||
if position_open:
|
|
||||||
current_price = df['close'].iloc[i]
|
|
||||||
current_profit_pct = (current_price - entry_price) / entry_price
|
|
||||||
|
|
||||||
# 1. 更新最高利润点
|
|
||||||
peak_profit_pct = max(peak_profit_pct, current_profit_pct)
|
|
||||||
|
|
||||||
# 2. 检查固定止损条件
|
|
||||||
if current_profit_pct <= self.stop_loss_pct:
|
|
||||||
logger.info(f"索引 {i}: 触发固定止损。入场价: {entry_price:.2f}, 当前价: {current_price:.2f}, 亏损: {current_profit_pct:.2%}")
|
|
||||||
signals.iat[i] = -1 # 卖出信号
|
|
||||||
position_open = False # 平仓
|
|
||||||
continue
|
|
||||||
|
|
||||||
# 3. 检查追踪止损条件
|
|
||||||
# 只有当最高利润超过了激活阈值,才开始计算回撤
|
|
||||||
if peak_profit_pct > self.min_profit_for_trailing:
|
|
||||||
retracement_from_peak = (peak_profit_pct - current_profit_pct)
|
|
||||||
|
|
||||||
# 避免除以零或负数的情况
|
|
||||||
if peak_profit_pct > 0:
|
|
||||||
retracement_pct = retracement_from_peak / peak_profit_pct
|
|
||||||
if retracement_pct >= self.profit_retracement_pct:
|
|
||||||
logger.info(f"索引 {i}: 触发追踪止损。最高利润: {peak_profit_pct:.2%}, 当前利润: {current_profit_pct:.2%}, 回撤超过30%")
|
|
||||||
signals.iat[i] = -1 # 卖出信号
|
|
||||||
position_open = False # 平仓
|
|
||||||
continue
|
|
||||||
return signals
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
|
|
||||||
import pandas as pd
|
|
||||||
from logger import logger
|
|
||||||
from utils import get_rates
|
|
||||||
|
|
||||||
class Strategy:
|
|
||||||
def __init__(self):
|
|
||||||
self.symbol = "XAUUSD"
|
|
||||||
# --- 策略核心参数 ---
|
|
||||||
self.trend_period = 50
|
|
||||||
self.retracement_tolerance = 0.30
|
|
||||||
|
|
||||||
# --- 策略状态变量 ---
|
|
||||||
self.current_trend = "none" # none, uptrend, downtrend
|
|
||||||
self.trend_peak = 0.0 # 上升趋势中的最高价
|
|
||||||
self.trend_trough = float('inf') # 下降趋势中的最低价
|
|
||||||
|
|
||||||
def generate_signal(self):
|
|
||||||
"""
|
|
||||||
带状态维护的实盘信号生成方法。
|
|
||||||
"""
|
|
||||||
# 获取足够的数据来计算滚动高低点
|
|
||||||
rates = get_rates(self.symbol, 1, self.trend_period + 5)
|
|
||||||
if rates is None or len(rates) < self.trend_period:
|
|
||||||
return 0 # 数据不足,不产生信号
|
|
||||||
|
|
||||||
df = pd.DataFrame(rates)
|
|
||||||
|
|
||||||
# 获取当前价格和用于判断突破的历史高低点
|
|
||||||
current_price = df['close'].iloc[-1]
|
|
||||||
high_period = df['high'].rolling(self.trend_period).max().iloc[-2]
|
|
||||||
low_period = df['low'].rolling(self.trend_period).min().iloc[-2]
|
|
||||||
|
|
||||||
signal = 0
|
|
||||||
|
|
||||||
# 状态 1: 当前无趋势,等待趋势开始
|
|
||||||
if self.current_trend == "none":
|
|
||||||
if current_price > high_period:
|
|
||||||
self.current_trend = "uptrend"
|
|
||||||
self.trend_peak = current_price
|
|
||||||
signal = 1
|
|
||||||
logger.info(f"实盘: 突破进入上升趋势,买入价: {current_price:.2f}")
|
|
||||||
elif current_price < low_period:
|
|
||||||
self.current_trend = "downtrend"
|
|
||||||
self.trend_trough = current_price
|
|
||||||
signal = -1
|
|
||||||
logger.info(f"实盘: 跌破进入下降趋势,卖出价: {current_price:.2f}")
|
|
||||||
|
|
||||||
# 状态 2: 当前处于上升趋势
|
|
||||||
elif self.current_trend == "uptrend":
|
|
||||||
if current_price < self.trend_peak * (1 - self.retracement_tolerance):
|
|
||||||
logger.info(f"实盘: 上升趋势结束。最高点: {self.trend_peak:.2f}, 当前价: {current_price:.2f}。平仓卖出。")
|
|
||||||
signal = -1
|
|
||||||
self.current_trend = "none" # 重置状态
|
|
||||||
else:
|
|
||||||
self.trend_peak = max(self.trend_peak, current_price)
|
|
||||||
|
|
||||||
# 状态 3: 当前处于下降趋势
|
|
||||||
elif self.current_trend == "downtrend":
|
|
||||||
if current_price > self.trend_trough * (1 + self.retracement_tolerance):
|
|
||||||
logger.info(f"实盘: 下降趋势结束。最低点: {self.trend_trough:.2f}, 当前价: {current_price:.2f}。平仓买入。")
|
|
||||||
signal = 1
|
|
||||||
self.current_trend = "none" # 重置状态
|
|
||||||
else:
|
|
||||||
self.trend_trough = min(self.trend_trough, current_price)
|
|
||||||
|
|
||||||
return signal
|
|
||||||
|
|
||||||
def run_backtest(self, df):
|
|
||||||
"""
|
|
||||||
带容错的趋势跟踪策略回测:
|
|
||||||
- 突破N周期高点,进入上升趋势,回撤30%则趋势结束。
|
|
||||||
- 跌破N周期低点,进入下降趋势,反弹30%则趋势结束。
|
|
||||||
"""
|
|
||||||
df = df.copy()
|
|
||||||
signals = pd.Series(0, index=df.index)
|
|
||||||
|
|
||||||
df['high_period'] = df['high'].rolling(self.trend_period).max().shift(1)
|
|
||||||
df['low_period'] = df['low'].rolling(self.trend_period).min().shift(1)
|
|
||||||
|
|
||||||
# 回测时使用局部变量来管理状态,避免干扰实盘状态
|
|
||||||
backtest_trend = "none"
|
|
||||||
backtest_peak = 0.0
|
|
||||||
backtest_trough = float('inf')
|
|
||||||
|
|
||||||
for i in range(self.trend_period, len(df)):
|
|
||||||
current_price = df['close'].iloc[i]
|
|
||||||
|
|
||||||
if backtest_trend == "none":
|
|
||||||
if current_price > df['high_period'].iloc[i]:
|
|
||||||
backtest_trend = "uptrend"
|
|
||||||
backtest_peak = current_price
|
|
||||||
signals.iat[i] = 1
|
|
||||||
elif current_price < df['low_period'].iloc[i]:
|
|
||||||
backtest_trend = "downtrend"
|
|
||||||
backtest_trough = current_price
|
|
||||||
signals.iat[i] = -1
|
|
||||||
|
|
||||||
elif backtest_trend == "uptrend":
|
|
||||||
if current_price < backtest_peak * (1 - self.retracement_tolerance):
|
|
||||||
signals.iat[i] = -1
|
|
||||||
backtest_trend = "none"
|
|
||||||
else:
|
|
||||||
backtest_peak = max(backtest_peak, current_price)
|
|
||||||
|
|
||||||
elif backtest_trend == "downtrend":
|
|
||||||
if current_price > backtest_trough * (1 + self.retracement_tolerance):
|
|
||||||
signals.iat[i] = 1
|
|
||||||
backtest_trend = "none"
|
|
||||||
else:
|
|
||||||
backtest_trough = min(backtest_trough, current_price)
|
|
||||||
|
|
||||||
return signals
|
|
||||||
@@ -0,0 +1,352 @@
|
|||||||
|
import MetaTrader5 as mt5
|
||||||
|
import pandas as pd
|
||||||
|
import numpy as np
|
||||||
|
from utils import get_rates, has_open_position, get_current_price
|
||||||
|
from logger import logger
|
||||||
|
from config import RISK_CONFIG, SYMBOL
|
||||||
|
|
||||||
|
class Strategy:
|
||||||
|
"""
|
||||||
|
风险管理策略
|
||||||
|
基于市场状态和持仓情况生成风险管理信号
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.symbol = SYMBOL
|
||||||
|
self.timeframe = mt5.TIMEFRAME_M1
|
||||||
|
self.data_count = 100 # 用于分析的数据量
|
||||||
|
|
||||||
|
# 从配置文件读取风险管理参数
|
||||||
|
self.stop_loss_pct = RISK_CONFIG.get("stop_loss_pct", -0.001)
|
||||||
|
self.profit_retracement_pct = RISK_CONFIG.get("profit_retracement_pct", 0.10)
|
||||||
|
self.min_profit_for_trailing = RISK_CONFIG.get("min_profit_for_trailing", 0.05)
|
||||||
|
self.take_profit_pct = RISK_CONFIG.get("take_profit_pct", 0.20)
|
||||||
|
self.max_daily_loss = RISK_CONFIG.get("max_daily_loss", -0.10)
|
||||||
|
|
||||||
|
# 策略参数
|
||||||
|
self.volatility_period = 20
|
||||||
|
self.trend_period = 50
|
||||||
|
self.rsi_period = 14
|
||||||
|
|
||||||
|
# 内部状态
|
||||||
|
self.positions = {}
|
||||||
|
self.daily_pnl = 0.0
|
||||||
|
self.last_position_time = None
|
||||||
|
self.min_trade_interval = 5 # 最小交易间隔(分钟)
|
||||||
|
|
||||||
|
def _calculate_indicators(self, df):
|
||||||
|
"""
|
||||||
|
计算技术指标
|
||||||
|
"""
|
||||||
|
# 计算移动平均线
|
||||||
|
df['ma_short'] = df['close'].rolling(10).mean()
|
||||||
|
df['ma_long'] = df['close'].rolling(30).mean()
|
||||||
|
|
||||||
|
# 计算RSI
|
||||||
|
df['rsi'] = self._calculate_rsi(df['close'], self.rsi_period)
|
||||||
|
|
||||||
|
# 计算波动率
|
||||||
|
df['volatility'] = df['close'].rolling(self.volatility_period).std() / df['close'].rolling(self.volatility_period).mean()
|
||||||
|
|
||||||
|
# 计算ATR
|
||||||
|
df['atr'] = self._calculate_atr(df, 14)
|
||||||
|
|
||||||
|
# 计算价格变化
|
||||||
|
df['price_change'] = df['close'].pct_change()
|
||||||
|
|
||||||
|
return df
|
||||||
|
|
||||||
|
def _calculate_rsi(self, prices, period):
|
||||||
|
"""
|
||||||
|
计算RSI指标
|
||||||
|
"""
|
||||||
|
delta = prices.diff()
|
||||||
|
gain = (delta.where(delta > 0, 0)).rolling(window=period).mean()
|
||||||
|
loss = (-delta.where(delta < 0, 0)).rolling(window=period).mean()
|
||||||
|
rs = gain / loss
|
||||||
|
rsi = 100 - (100 / (1 + rs))
|
||||||
|
return rsi
|
||||||
|
|
||||||
|
def _calculate_atr(self, df, period):
|
||||||
|
"""
|
||||||
|
计算ATR指标
|
||||||
|
"""
|
||||||
|
high = df['high']
|
||||||
|
low = df['low']
|
||||||
|
close = df['close']
|
||||||
|
|
||||||
|
tr1 = high - low
|
||||||
|
tr2 = abs(high - close.shift(1))
|
||||||
|
tr3 = abs(low - close.shift(1))
|
||||||
|
|
||||||
|
tr = pd.concat([tr1, tr2, tr3], axis=1).max(axis=1)
|
||||||
|
atr = tr.rolling(window=period).mean()
|
||||||
|
|
||||||
|
return atr
|
||||||
|
|
||||||
|
def _get_position_info(self):
|
||||||
|
"""
|
||||||
|
获取当前持仓信息
|
||||||
|
"""
|
||||||
|
return self.positions.get(self.symbol, None)
|
||||||
|
|
||||||
|
def _calculate_position_pnl(self, current_price, position_info):
|
||||||
|
"""
|
||||||
|
计算持仓盈亏
|
||||||
|
"""
|
||||||
|
if not position_info:
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
entry_price = position_info['entry_price']
|
||||||
|
position_type = position_info['position_type']
|
||||||
|
|
||||||
|
if position_type == 'long':
|
||||||
|
return (current_price - entry_price) / entry_price
|
||||||
|
else:
|
||||||
|
return (entry_price - current_price) / entry_price
|
||||||
|
|
||||||
|
def _check_risk_conditions(self, df):
|
||||||
|
"""
|
||||||
|
检查风险管理条件
|
||||||
|
"""
|
||||||
|
current_price = df['close'].iloc[-1]
|
||||||
|
current_volatility = df['volatility'].iloc[-1]
|
||||||
|
current_rsi = df['rsi'].iloc[-1]
|
||||||
|
current_atr = df['atr'].iloc[-1]
|
||||||
|
|
||||||
|
# 检查是否有持仓
|
||||||
|
position_info = self._get_position_info()
|
||||||
|
|
||||||
|
# 如果没有持仓,检查开仓条件
|
||||||
|
if not position_info:
|
||||||
|
return self._check_entry_conditions(df, current_price, current_volatility, current_rsi, current_atr)
|
||||||
|
|
||||||
|
# 如果有持仓,检查平仓条件
|
||||||
|
else:
|
||||||
|
return self._check_exit_conditions(df, current_price, position_info)
|
||||||
|
|
||||||
|
def _check_entry_conditions(self, df, current_price, volatility, rsi, atr):
|
||||||
|
"""
|
||||||
|
检查开仓条件
|
||||||
|
"""
|
||||||
|
# 检查日亏损限制
|
||||||
|
if self.daily_pnl <= self.max_daily_loss:
|
||||||
|
logger.info(f"日亏损已达{self.daily_pnl:.2%},禁止开仓")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
# 检查最小交易间隔
|
||||||
|
if self.last_position_time:
|
||||||
|
time_diff = (pd.Timestamp.now() - self.last_position_time).total_seconds() / 60
|
||||||
|
if time_diff < self.min_trade_interval:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
# 基于市场状态的开仓条件
|
||||||
|
ma_short = df['ma_short'].iloc[-1]
|
||||||
|
ma_long = df['ma_long'].iloc[-1]
|
||||||
|
|
||||||
|
# 低波动率且趋势明确时开仓
|
||||||
|
if volatility < 0.02: # 低波动率
|
||||||
|
if ma_short > ma_long and rsi < 70: # 上升趋势且未超买
|
||||||
|
logger.info(f"风险管理策略:低波动率上升趋势,买入信号")
|
||||||
|
return 1
|
||||||
|
elif ma_short < ma_long and rsi > 30: # 下降趋势且未超卖
|
||||||
|
logger.info(f"风险管理策略:低波动率下降趋势,卖出信号")
|
||||||
|
return -1
|
||||||
|
|
||||||
|
# 高波动率时等待机会
|
||||||
|
elif volatility > 0.05: # 高波动率
|
||||||
|
if rsi < 30: # 超卖反弹机会
|
||||||
|
logger.info(f"风险管理策略:高波动率超卖反弹,买入信号")
|
||||||
|
return 1
|
||||||
|
elif rsi > 70: # 超买回调机会
|
||||||
|
logger.info(f"风险管理策略:高波动率超买回调,卖出信号")
|
||||||
|
return -1
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def _check_exit_conditions(self, df, current_price, position_info):
|
||||||
|
"""
|
||||||
|
检查平仓条件
|
||||||
|
"""
|
||||||
|
position_type = position_info['position_type']
|
||||||
|
entry_price = position_info['entry_price']
|
||||||
|
current_pnl = self._calculate_position_pnl(current_price, position_info)
|
||||||
|
|
||||||
|
# 更新最高利润
|
||||||
|
self.positions[self.symbol]['peak_profit'] = max(
|
||||||
|
position_info.get('peak_profit', 0),
|
||||||
|
current_pnl
|
||||||
|
)
|
||||||
|
peak_profit = self.positions[self.symbol]['peak_profit']
|
||||||
|
|
||||||
|
# 检查止损 - 仅基于买入成本判断亏损,不考虑盈利回撤
|
||||||
|
if current_pnl < 0 and current_pnl <= self.stop_loss_pct:
|
||||||
|
logger.info(f"风险管理策略:止损触发,当前亏损{current_pnl:.2%}")
|
||||||
|
return -1 if position_type == 'long' else 1
|
||||||
|
|
||||||
|
# 检查止盈
|
||||||
|
if current_pnl >= self.take_profit_pct:
|
||||||
|
logger.info(f"风险管理策略:止盈触发,当前盈利{current_pnl:.2%}")
|
||||||
|
return -1 if position_type == 'long' else 1
|
||||||
|
|
||||||
|
# 检查追踪止损
|
||||||
|
if peak_profit > self.min_profit_for_trailing:
|
||||||
|
retracement_from_peak = peak_profit - current_pnl
|
||||||
|
if peak_profit > 0:
|
||||||
|
retracement_pct = retracement_from_peak / peak_profit
|
||||||
|
if retracement_pct >= self.profit_retracement_pct:
|
||||||
|
logger.info(f"风险管理策略:追踪止损触发,最高盈利{peak_profit:.2%},回撤{retracement_pct:.2%}")
|
||||||
|
return -1 if position_type == 'long' else 1
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def generate_signal(self):
|
||||||
|
"""
|
||||||
|
生成实时交易信号
|
||||||
|
"""
|
||||||
|
# 获取当前价格
|
||||||
|
current_price = get_current_price(self.symbol)
|
||||||
|
if current_price is None:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
# 获取历史数据
|
||||||
|
rates = get_rates(self.symbol, self.timeframe, self.data_count)
|
||||||
|
if rates is None or len(rates) < self.trend_period:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
df = pd.DataFrame(rates)
|
||||||
|
df = self._calculate_indicators(df)
|
||||||
|
|
||||||
|
# 检查风险管理条件
|
||||||
|
signal = self._check_risk_conditions(df)
|
||||||
|
|
||||||
|
return signal
|
||||||
|
|
||||||
|
def generate_signal_with_sync(self, risk_controller):
|
||||||
|
"""
|
||||||
|
生成实时交易信号(与主风险管理器同步)
|
||||||
|
"""
|
||||||
|
# 先同步状态
|
||||||
|
self.sync_with_risk_controller(risk_controller)
|
||||||
|
|
||||||
|
# 然后生成信号
|
||||||
|
return self.generate_signal()
|
||||||
|
|
||||||
|
def run_backtest(self, df):
|
||||||
|
"""
|
||||||
|
回测信号生成
|
||||||
|
"""
|
||||||
|
df = df.copy()
|
||||||
|
df = self._calculate_indicators(df)
|
||||||
|
|
||||||
|
signals = pd.Series(0, index=df.index)
|
||||||
|
|
||||||
|
# 模拟持仓状态
|
||||||
|
positions = {}
|
||||||
|
daily_pnl = 0.0
|
||||||
|
|
||||||
|
for i in range(self.trend_period, len(df)):
|
||||||
|
current_price = df['close'].iloc[i]
|
||||||
|
current_time = df.index[i]
|
||||||
|
|
||||||
|
# 获取当前持仓信息
|
||||||
|
position_info = positions.get(self.symbol)
|
||||||
|
|
||||||
|
if not position_info:
|
||||||
|
# 检查开仓条件
|
||||||
|
if self._should_open_position(df.iloc[:i+1], current_price):
|
||||||
|
# 简化的开仓逻辑
|
||||||
|
ma_short = df['ma_short'].iloc[i]
|
||||||
|
ma_long = df['ma_long'].iloc[i]
|
||||||
|
rsi = df['rsi'].iloc[i]
|
||||||
|
|
||||||
|
if ma_short > ma_long and rsi < 70:
|
||||||
|
signals.iat[i] = 1
|
||||||
|
positions[self.symbol] = {
|
||||||
|
'entry_price': current_price,
|
||||||
|
'position_type': 'long',
|
||||||
|
'peak_profit': 0.0
|
||||||
|
}
|
||||||
|
elif ma_short < ma_long and rsi > 30:
|
||||||
|
signals.iat[i] = -1
|
||||||
|
positions[self.symbol] = {
|
||||||
|
'entry_price': current_price,
|
||||||
|
'position_type': 'short',
|
||||||
|
'peak_profit': 0.0
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
# 检查平仓条件
|
||||||
|
position_type = position_info['position_type']
|
||||||
|
entry_price = position_info['entry_price']
|
||||||
|
current_pnl = self._calculate_position_pnl(current_price, position_info)
|
||||||
|
|
||||||
|
# 更新最高利润
|
||||||
|
position_info['peak_profit'] = max(position_info['peak_profit'], current_pnl)
|
||||||
|
|
||||||
|
# 检查止损止盈 - 止损仅基于买入成本判断亏损
|
||||||
|
if (current_pnl < 0 and current_pnl <= self.stop_loss_pct) or current_pnl >= self.take_profit_pct:
|
||||||
|
signals.iat[i] = -1 if position_type == 'long' else 1
|
||||||
|
positions.pop(self.symbol, None)
|
||||||
|
|
||||||
|
# 更新日内盈亏
|
||||||
|
daily_pnl += current_pnl
|
||||||
|
if daily_pnl <= self.max_daily_loss:
|
||||||
|
break # 日亏损达到限制,停止交易
|
||||||
|
|
||||||
|
return signals
|
||||||
|
|
||||||
|
def _should_open_position(self, df, current_price):
|
||||||
|
"""
|
||||||
|
判断是否应该开仓
|
||||||
|
"""
|
||||||
|
volatility = df['volatility'].iloc[-1]
|
||||||
|
|
||||||
|
# 低波动率且未达到日亏损限制
|
||||||
|
return volatility < 0.02
|
||||||
|
|
||||||
|
def update_position_entry(self, entry_price, position_type="long"):
|
||||||
|
"""
|
||||||
|
更新持仓入场信息
|
||||||
|
"""
|
||||||
|
self.positions[self.symbol] = {
|
||||||
|
'entry_price': entry_price,
|
||||||
|
'position_type': position_type,
|
||||||
|
'peak_profit': 0.0,
|
||||||
|
'entry_time': pd.Timestamp.now()
|
||||||
|
}
|
||||||
|
self.last_position_time = pd.Timestamp.now()
|
||||||
|
|
||||||
|
logger.info(f"风险管理策略记录持仓入场:价格={entry_price:.2f}, 类型={position_type}")
|
||||||
|
|
||||||
|
def close_position(self, current_price):
|
||||||
|
"""
|
||||||
|
平仓
|
||||||
|
"""
|
||||||
|
position_info = self.positions.get(self.symbol)
|
||||||
|
if position_info:
|
||||||
|
pnl = self._calculate_position_pnl(current_price, position_info)
|
||||||
|
self.daily_pnl += pnl
|
||||||
|
|
||||||
|
self.positions.pop(self.symbol, None)
|
||||||
|
logger.info(f"风险管理策略平仓:盈亏{pnl:.2%}")
|
||||||
|
|
||||||
|
def reset_daily_stats(self):
|
||||||
|
"""
|
||||||
|
重置日内统计
|
||||||
|
"""
|
||||||
|
self.daily_pnl = 0.0
|
||||||
|
logger.info("风险管理策略重置日内统计")
|
||||||
|
|
||||||
|
def sync_with_risk_controller(self, risk_controller):
|
||||||
|
"""
|
||||||
|
与主风险管理器同步状态
|
||||||
|
"""
|
||||||
|
# 从主风险管理器获取持仓信息
|
||||||
|
if hasattr(risk_controller, 'position_manager'):
|
||||||
|
position_info = risk_controller.position_manager.get_position_info()
|
||||||
|
if position_info:
|
||||||
|
self.positions[self.symbol] = position_info
|
||||||
|
|
||||||
|
# 同步日内盈亏
|
||||||
|
if hasattr(risk_controller, 'daily_pnl'):
|
||||||
|
self.daily_pnl = risk_controller.daily_pnl
|
||||||
+19
-13
@@ -9,8 +9,6 @@ class Strategy:
|
|||||||
self.symbol = "XAUUSD"
|
self.symbol = "XAUUSD"
|
||||||
self.timeframe = mt5.TIMEFRAME_M1
|
self.timeframe = mt5.TIMEFRAME_M1
|
||||||
self.rsi_period = 14
|
self.rsi_period = 14
|
||||||
self.rsi_buy_threshold = 30
|
|
||||||
self.rsi_sell_threshold = 70
|
|
||||||
|
|
||||||
def _calculate_indicators(self, df):
|
def _calculate_indicators(self, df):
|
||||||
"""
|
"""
|
||||||
@@ -25,8 +23,8 @@ class Strategy:
|
|||||||
|
|
||||||
def generate_signal(self):
|
def generate_signal(self):
|
||||||
"""
|
"""
|
||||||
RSI策略实盘:
|
RSI策略实盘
|
||||||
RSI < 30买入,RSI > 70卖出。
|
RSI上穿超卖线买入,下穿超买线卖出
|
||||||
"""
|
"""
|
||||||
rates = get_rates(self.symbol, self.timeframe, self.rsi_period + 30)
|
rates = get_rates(self.symbol, self.timeframe, self.rsi_period + 30)
|
||||||
if rates is None or len(rates) < self.rsi_period:
|
if rates is None or len(rates) < self.rsi_period:
|
||||||
@@ -34,26 +32,34 @@ class Strategy:
|
|||||||
df = pd.DataFrame(rates)
|
df = pd.DataFrame(rates)
|
||||||
df = self._calculate_indicators(df)
|
df = self._calculate_indicators(df)
|
||||||
|
|
||||||
if df['rsi'].iloc[-2] < self.rsi_buy_threshold:
|
oversold_level = 30
|
||||||
logger.info(f"RSI小于{self.rsi_buy_threshold},产生买入信号: {self.symbol}")
|
overbought_level = 70
|
||||||
|
# RSI crosses above oversold level
|
||||||
|
if df['rsi'].iloc[-2] < oversold_level and df['rsi'].iloc[-1] > oversold_level:
|
||||||
|
logger.info(f"RSI crosses above {oversold_level}, creating buy signal: {self.symbol}")
|
||||||
return 1
|
return 1
|
||||||
elif df['rsi'].iloc[-2] > self.rsi_sell_threshold:
|
# RSI crosses below overbought level
|
||||||
logger.info(f"RSI大于{self.rsi_sell_threshold},产生卖出信号: {self.symbol}")
|
elif df['rsi'].iloc[-2] > overbought_level and df['rsi'].iloc[-1] < overbought_level:
|
||||||
|
logger.info(f"RSI crosses below {overbought_level}, creating sell signal: {self.symbol}")
|
||||||
return -1
|
return -1
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def run_backtest(self, df):
|
def run_backtest(self, df):
|
||||||
"""
|
"""
|
||||||
RSI回测:
|
RSI回测方法
|
||||||
RSI < 30买入,RSI > 70卖出。
|
根据RSI穿越超买超卖线生成信号
|
||||||
"""
|
"""
|
||||||
df = df.copy()
|
df = df.copy()
|
||||||
df = self._calculate_indicators(df)
|
df = self._calculate_indicators(df)
|
||||||
|
|
||||||
signals = pd.Series(0, index=df.index)
|
signals = pd.Series(0, index=df.index)
|
||||||
for i in range(self.rsi_period, len(df)):
|
oversold_level = 30
|
||||||
if df['rsi'].iloc[i-1] < self.rsi_buy_threshold:
|
overbought_level = 70
|
||||||
|
for i in range(1, len(df)):
|
||||||
|
# RSI crosses above oversold level
|
||||||
|
if df['rsi'].iloc[i-1] < oversold_level and df['rsi'].iloc[i] > oversold_level:
|
||||||
signals.iat[i] = 1
|
signals.iat[i] = 1
|
||||||
elif df['rsi'].iloc[i-1] > self.rsi_sell_threshold:
|
# RSI crosses below overbought level
|
||||||
|
elif df['rsi'].iloc[i-1] > overbought_level and df['rsi'].iloc[i] < overbought_level:
|
||||||
signals.iat[i] = -1
|
signals.iat[i] = -1
|
||||||
return signals
|
return signals
|
||||||
|
|||||||
@@ -0,0 +1,297 @@
|
|||||||
|
import MetaTrader5 as mt5
|
||||||
|
import pandas as pd
|
||||||
|
import numpy as np
|
||||||
|
from utils import get_rates
|
||||||
|
from logger import logger
|
||||||
|
from config import WAVE_THEORY_CONFIG
|
||||||
|
|
||||||
|
|
||||||
|
class Strategy:
|
||||||
|
def __init__(self):
|
||||||
|
self.symbol = "XAUUSD"
|
||||||
|
self.timeframe = mt5.TIMEFRAME_M1 # 使用1分钟数据,与其他策略保持一致
|
||||||
|
|
||||||
|
# 从配置文件加载参数
|
||||||
|
config = WAVE_THEORY_CONFIG
|
||||||
|
self.daily_data_count = config.get("daily_data_count", 30)
|
||||||
|
self.ema_short = config.get("ema_short", 5)
|
||||||
|
self.ema_medium = config.get("ema_medium", 13)
|
||||||
|
self.ema_long = config.get("ema_long", 34)
|
||||||
|
self.wave_period = config.get("wave_period", 21)
|
||||||
|
self.range_period = config.get("range_period", 20)
|
||||||
|
self.adx_period = config.get("adx_period", 14)
|
||||||
|
|
||||||
|
# 波浪理论参数
|
||||||
|
self.retracement_levels = [0.236, 0.382, 0.5, 0.618, 0.786]
|
||||||
|
self.momentum_period = 14
|
||||||
|
|
||||||
|
# 震荡市检测参数(调整为适合1分钟数据)
|
||||||
|
self.range_threshold = 0.005 # 0.5%的价格波动范围,适合1分钟数据
|
||||||
|
self.adx_threshold = 25 # ADX小于25表示震荡市,适合1分钟数据
|
||||||
|
|
||||||
|
def _calculate_indicators(self, df):
|
||||||
|
"""
|
||||||
|
计算波浪理论相关指标
|
||||||
|
"""
|
||||||
|
# 计算EMA
|
||||||
|
df['ema_short'] = df['close'].ewm(span=self.ema_short).mean()
|
||||||
|
df['ema_medium'] = df['close'].ewm(span=self.ema_medium).mean()
|
||||||
|
df['ema_long'] = df['close'].ewm(span=self.ema_long).mean()
|
||||||
|
|
||||||
|
# 计算动量指标
|
||||||
|
df['momentum'] = df['close'].diff(self.momentum_period) / df['close'].shift(self.momentum_period) * 100
|
||||||
|
|
||||||
|
# 计算波动范围
|
||||||
|
df['high_max'] = df['high'].rolling(self.range_period).max()
|
||||||
|
df['low_min'] = df['low'].rolling(self.range_period).min()
|
||||||
|
df['range_pct'] = (df['high_max'] - df['low_min']) / df['close'] * 100
|
||||||
|
|
||||||
|
# 计算ADX(平均趋向指数)用于判断趋势强度
|
||||||
|
df['adx'] = self._calculate_adx(df)
|
||||||
|
|
||||||
|
# 识别潜在的波浪点
|
||||||
|
df['potential_wave_points'] = self._identify_wave_points(df)
|
||||||
|
|
||||||
|
# 计算斐波那契回撤位
|
||||||
|
df = self._calculate_fibonacci_levels(df)
|
||||||
|
|
||||||
|
return df
|
||||||
|
|
||||||
|
def _calculate_adx(self, df):
|
||||||
|
"""
|
||||||
|
计算ADX指标
|
||||||
|
"""
|
||||||
|
high = df['high']
|
||||||
|
low = df['low']
|
||||||
|
close = df['close']
|
||||||
|
|
||||||
|
# 计算真实波幅
|
||||||
|
df['tr'] = pd.concat([
|
||||||
|
high - low,
|
||||||
|
abs(high - close.shift(1)),
|
||||||
|
abs(low - close.shift(1))
|
||||||
|
], axis=1).max(axis=1)
|
||||||
|
|
||||||
|
# 计算方向移动
|
||||||
|
df['up_move'] = high - high.shift(1)
|
||||||
|
df['down_move'] = low.shift(1) - low
|
||||||
|
|
||||||
|
df['plus_dm'] = np.where((df['up_move'] > df['down_move']) & (df['up_move'] > 0), df['up_move'], 0)
|
||||||
|
df['minus_dm'] = np.where((df['down_move'] > df['up_move']) & (df['down_move'] > 0), df['down_move'], 0)
|
||||||
|
|
||||||
|
# 计算平滑值
|
||||||
|
df['plus_di'] = 100 * (df['plus_dm'].ewm(span=self.adx_period).mean() / df['tr'].ewm(span=self.adx_period).mean())
|
||||||
|
df['minus_di'] = 100 * (df['minus_dm'].ewm(span=self.adx_period).mean() / df['tr'].ewm(span=self.adx_period).mean())
|
||||||
|
|
||||||
|
# 计算DX
|
||||||
|
df['dx'] = 100 * abs(df['plus_di'] - df['minus_di']) / (df['plus_di'] + df['minus_di'])
|
||||||
|
|
||||||
|
# 计算ADX
|
||||||
|
adx = df['dx'].ewm(span=self.adx_period).mean()
|
||||||
|
|
||||||
|
return adx
|
||||||
|
|
||||||
|
def _identify_wave_points(self, df):
|
||||||
|
"""
|
||||||
|
识别潜在的波浪转折点
|
||||||
|
"""
|
||||||
|
wave_points = pd.Series(0, index=df.index)
|
||||||
|
|
||||||
|
for i in range(self.wave_period, len(df) - self.wave_period):
|
||||||
|
current_high = df['high'].iloc[i]
|
||||||
|
current_low = df['low'].iloc[i]
|
||||||
|
|
||||||
|
# 检查是否为波峰
|
||||||
|
is_peak = (current_high == df['high'].iloc[i-self.wave_period:i+self.wave_period].max())
|
||||||
|
|
||||||
|
# 检查是否为波谷
|
||||||
|
is_trough = (current_low == df['low'].iloc[i-self.wave_period:i+self.wave_period].min())
|
||||||
|
|
||||||
|
if is_peak:
|
||||||
|
wave_points.iloc[i] = 1 # 波峰
|
||||||
|
elif is_trough:
|
||||||
|
wave_points.iloc[i] = -1 # 波谷
|
||||||
|
|
||||||
|
return wave_points
|
||||||
|
|
||||||
|
def _calculate_fibonacci_levels(self, df):
|
||||||
|
"""
|
||||||
|
计算斐波那契回撤位
|
||||||
|
"""
|
||||||
|
# 为每个斐波那契级别创建单独的列
|
||||||
|
for level in self.retracement_levels:
|
||||||
|
df[f'fib_{level}'] = None
|
||||||
|
|
||||||
|
for i in range(self.wave_period * 2, len(df)):
|
||||||
|
# 寻找最近的波峰和波谷
|
||||||
|
wave_points = df['potential_wave_points'].iloc[:i+1]
|
||||||
|
peaks = wave_points[wave_points == 1]
|
||||||
|
troughs = wave_points[wave_points == -1]
|
||||||
|
|
||||||
|
if len(peaks) > 0 and len(troughs) > 0:
|
||||||
|
last_peak_idx = peaks.index[-1]
|
||||||
|
last_trough_idx = troughs.index[-1]
|
||||||
|
|
||||||
|
if last_peak_idx > last_trough_idx:
|
||||||
|
# 下降趋势,计算回撤位
|
||||||
|
high_price = df['high'].iloc[last_peak_idx]
|
||||||
|
low_price = df['low'].iloc[last_trough_idx]
|
||||||
|
price_range = high_price - low_price
|
||||||
|
|
||||||
|
for level in self.retracement_levels:
|
||||||
|
df.at[i, f'fib_{level}'] = high_price - price_range * level
|
||||||
|
else:
|
||||||
|
# 上升趋势,计算回撤位
|
||||||
|
low_price = df['low'].iloc[last_trough_idx]
|
||||||
|
high_price = df['high'].iloc[last_peak_idx]
|
||||||
|
price_range = high_price - low_price
|
||||||
|
|
||||||
|
for level in self.retracement_levels:
|
||||||
|
df.at[i, f'fib_{level}'] = low_price + price_range * level
|
||||||
|
|
||||||
|
return df
|
||||||
|
|
||||||
|
def _is_sideways_market(self, df):
|
||||||
|
"""
|
||||||
|
判断是否为震荡市
|
||||||
|
"""
|
||||||
|
if len(df) < self.range_period:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 使用ADX判断趋势强度
|
||||||
|
adx_value = df['adx'].iloc[-1]
|
||||||
|
is_low_adx = adx_value < self.adx_threshold
|
||||||
|
|
||||||
|
# 使用价格范围判断
|
||||||
|
range_pct = df['range_pct'].iloc[-1]
|
||||||
|
is_tight_range = range_pct < self.range_threshold * 100
|
||||||
|
|
||||||
|
# 结合两个条件
|
||||||
|
return is_low_adx and is_tight_range
|
||||||
|
|
||||||
|
def generate_signal(self):
|
||||||
|
"""
|
||||||
|
波浪理论策略实盘信号生成
|
||||||
|
"""
|
||||||
|
rates = get_rates(self.symbol, self.timeframe, self.daily_data_count)
|
||||||
|
if rates is None or len(rates) < self.wave_period * 3:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
df = pd.DataFrame(rates)
|
||||||
|
df = self._calculate_indicators(df)
|
||||||
|
|
||||||
|
# 判断市场状态
|
||||||
|
is_sideways = self._is_sideways_market(df)
|
||||||
|
|
||||||
|
# 获取最近的波浪点
|
||||||
|
recent_wave_points = df['potential_wave_points'].iloc[-self.wave_period:]
|
||||||
|
current_momentum = df['momentum'].iloc[-1]
|
||||||
|
current_price = df['close'].iloc[-1]
|
||||||
|
|
||||||
|
# 震荡市中的波浪理论信号
|
||||||
|
if is_sideways:
|
||||||
|
# 在震荡市中,寻找区间边界的反转机会
|
||||||
|
upper_bound = df['high_max'].iloc[-1]
|
||||||
|
lower_bound = df['low_min'].iloc[-1]
|
||||||
|
|
||||||
|
# 价格接近上边界且有转弱迹象
|
||||||
|
if current_price > upper_bound * 0.98 and current_momentum < 0:
|
||||||
|
logger.info(f"震荡市中价格接近上边界,产生卖出信号: {self.symbol}")
|
||||||
|
return -1
|
||||||
|
|
||||||
|
# 价格接近下边界且有转强迹象
|
||||||
|
elif current_price < lower_bound * 1.02 and current_momentum > 0:
|
||||||
|
logger.info(f"震荡市中价格接近下边界,产生买入信号: {self.symbol}")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
# 趋势市场中的波浪理论信号
|
||||||
|
else:
|
||||||
|
# 寻找波浪模式的确认信号
|
||||||
|
ema_alignment = (df['ema_short'].iloc[-1] > df['ema_medium'].iloc[-1] > df['ema_long'].iloc[-1])
|
||||||
|
|
||||||
|
# 上升趋势中的回调买入
|
||||||
|
if ema_alignment and current_momentum > 0:
|
||||||
|
# 检查是否在斐波那契回撤位附近
|
||||||
|
if f'fib_0.618' in df.columns and not pd.isna(df[f'fib_0.618'].iloc[-1]):
|
||||||
|
fib_618 = df[f'fib_0.618'].iloc[-1]
|
||||||
|
if abs(current_price - fib_618) / fib_618 < 0.01: # 1%误差范围内
|
||||||
|
logger.info(f"上升趋势中回调至斐波那契61.8%位,产生买入信号: {self.symbol}")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
# 下降趋势中的反弹卖出
|
||||||
|
elif not ema_alignment and current_momentum < 0:
|
||||||
|
# 检查是否在斐波那契回撤位附近
|
||||||
|
if f'fib_0.618' in df.columns and not pd.isna(df[f'fib_0.618'].iloc[-1]):
|
||||||
|
fib_618 = df[f'fib_0.618'].iloc[-1]
|
||||||
|
if abs(current_price - fib_618) / fib_618 < 0.01: # 1%误差范围内
|
||||||
|
logger.info(f"下降趋势中反弹至斐波那契61.8%位,产生卖出信号: {self.symbol}")
|
||||||
|
return -1
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def run_backtest(self, df):
|
||||||
|
"""
|
||||||
|
波浪理论策略回测
|
||||||
|
"""
|
||||||
|
df = df.copy()
|
||||||
|
df = self._calculate_indicators(df)
|
||||||
|
|
||||||
|
signals = pd.Series(0, index=df.index)
|
||||||
|
|
||||||
|
for i in range(self.wave_period * 3, len(df)):
|
||||||
|
current_df = df.iloc[:i+1]
|
||||||
|
|
||||||
|
# 判断市场状态
|
||||||
|
is_sideways = self._is_sideways_market(current_df)
|
||||||
|
|
||||||
|
# 获取当前时刻的数据
|
||||||
|
current_price = current_df['close'].iloc[-1]
|
||||||
|
current_momentum = current_df['momentum'].iloc[-1]
|
||||||
|
|
||||||
|
if is_sideways:
|
||||||
|
# 震荡市信号
|
||||||
|
upper_bound = current_df['high_max'].iloc[-1]
|
||||||
|
lower_bound = current_df['low_min'].iloc[-1]
|
||||||
|
|
||||||
|
if current_price > upper_bound * 0.98 and current_momentum < 0:
|
||||||
|
signals.iat[i] = -1
|
||||||
|
elif current_price < lower_bound * 1.02 and current_momentum > 0:
|
||||||
|
signals.iat[i] = 1
|
||||||
|
|
||||||
|
else:
|
||||||
|
# 趋势市信号
|
||||||
|
ema_alignment = (current_df['ema_short'].iloc[-1] > current_df['ema_medium'].iloc[-1] > current_df['ema_long'].iloc[-1])
|
||||||
|
|
||||||
|
if ema_alignment and current_momentum > 0:
|
||||||
|
# 检查斐波那契回撤位
|
||||||
|
if f'fib_0.618' in current_df.columns and not pd.isna(current_df[f'fib_0.618'].iloc[-1]):
|
||||||
|
fib_618 = current_df[f'fib_0.618'].iloc[-1]
|
||||||
|
if abs(current_price - fib_618) / fib_618 < 0.01:
|
||||||
|
signals.iat[i] = 1
|
||||||
|
|
||||||
|
elif not ema_alignment and current_momentum < 0:
|
||||||
|
# 检查斐波那契回撤位
|
||||||
|
if f'fib_0.618' in current_df.columns and not pd.isna(current_df[f'fib_0.618'].iloc[-1]):
|
||||||
|
fib_618 = current_df[f'fib_0.618'].iloc[-1]
|
||||||
|
if abs(current_price - fib_618) / fib_618 < 0.01:
|
||||||
|
signals.iat[i] = -1
|
||||||
|
|
||||||
|
return signals
|
||||||
|
|
||||||
|
def get_market_state(self, df):
|
||||||
|
"""
|
||||||
|
获取当前市场状态信息
|
||||||
|
"""
|
||||||
|
if len(df) < self.wave_period * 3:
|
||||||
|
return {"state": "insufficient_data", "confidence": 0}
|
||||||
|
|
||||||
|
is_sideways = self._is_sideways_market(df)
|
||||||
|
adx_value = df['adx'].iloc[-1]
|
||||||
|
range_pct = df['range_pct'].iloc[-1]
|
||||||
|
|
||||||
|
return {
|
||||||
|
"state": "sideways" if is_sideways else "trending",
|
||||||
|
"confidence": max(0, min(1, (30 - adx_value) / 30)), # ADX越低,震荡市置信度越高
|
||||||
|
"adx": adx_value,
|
||||||
|
"range_pct": range_pct
|
||||||
|
}
|
||||||
+193
@@ -0,0 +1,193 @@
|
|||||||
|
import pandas as pd
|
||||||
|
import numpy as np
|
||||||
|
from datetime import datetime
|
||||||
|
from logger import logger
|
||||||
|
import json
|
||||||
|
|
||||||
|
class TradeLogger:
|
||||||
|
"""
|
||||||
|
交易日志记录器,跟踪每一笔交易的详情
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.trades = [] # 存储所有交易记录
|
||||||
|
self.current_position = None # 当前持仓信息
|
||||||
|
self.trade_id = 0 # 交易ID计数器
|
||||||
|
|
||||||
|
def open_position(self, symbol, direction, price, timestamp, strategy_signal=""):
|
||||||
|
"""
|
||||||
|
开仓
|
||||||
|
"""
|
||||||
|
if self.current_position is not None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
self.trade_id += 1
|
||||||
|
self.current_position = {
|
||||||
|
'trade_id': self.trade_id,
|
||||||
|
'symbol': symbol,
|
||||||
|
'direction': direction, # 'buy' 或 'sell'
|
||||||
|
'open_price': price,
|
||||||
|
'open_time': timestamp,
|
||||||
|
'strategy_signal': strategy_signal,
|
||||||
|
'status': 'open'
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(f"开仓 #{self.trade_id}: {direction} {symbol} @ {price:.2f} 时间: {timestamp}")
|
||||||
|
return True
|
||||||
|
|
||||||
|
def close_position(self, symbol, price, timestamp, close_reason="signal"):
|
||||||
|
"""
|
||||||
|
平仓
|
||||||
|
"""
|
||||||
|
if self.current_position is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if self.current_position['symbol'] != symbol:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 计算盈亏
|
||||||
|
if self.current_position['direction'] == 'buy':
|
||||||
|
profit_loss = price - self.current_position['open_price']
|
||||||
|
profit_loss_pct = profit_loss / self.current_position['open_price'] * 100
|
||||||
|
else: # sell
|
||||||
|
profit_loss = self.current_position['open_price'] - price
|
||||||
|
profit_loss_pct = profit_loss / self.current_position['open_price'] * 100
|
||||||
|
|
||||||
|
# 完成交易记录
|
||||||
|
trade_record = self.current_position.copy()
|
||||||
|
trade_record.update({
|
||||||
|
'close_price': price,
|
||||||
|
'close_time': timestamp,
|
||||||
|
'close_reason': close_reason,
|
||||||
|
'profit_loss': profit_loss,
|
||||||
|
'profit_loss_pct': profit_loss_pct,
|
||||||
|
'status': 'closed'
|
||||||
|
})
|
||||||
|
|
||||||
|
self.trades.append(trade_record)
|
||||||
|
|
||||||
|
logger.info(f"平仓 #{self.trade_id}: {trade_record['direction']} {symbol} @ {price:.2f} | "
|
||||||
|
f"盈亏: {profit_loss:.2f} ({profit_loss_pct:+.2f}%) | 时间: {timestamp}")
|
||||||
|
|
||||||
|
self.current_position = None
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _calculate_profit_loss_pct(self, current_price, position):
|
||||||
|
"""
|
||||||
|
计算当前持仓的盈亏百分比
|
||||||
|
"""
|
||||||
|
if not position:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if position['direction'] == 'buy':
|
||||||
|
profit_loss = current_price - position['open_price']
|
||||||
|
else: # sell
|
||||||
|
profit_loss = position['open_price'] - current_price
|
||||||
|
|
||||||
|
return profit_loss / position['open_price'] * 100
|
||||||
|
|
||||||
|
def get_trade_summary(self):
|
||||||
|
"""
|
||||||
|
获取交易统计摘要
|
||||||
|
"""
|
||||||
|
if not self.trades:
|
||||||
|
return {
|
||||||
|
'total_trades': 0,
|
||||||
|
'winning_trades': 0,
|
||||||
|
'losing_trades': 0,
|
||||||
|
'win_rate': 0,
|
||||||
|
'total_profit_loss': 0,
|
||||||
|
'avg_profit_loss': 0,
|
||||||
|
'max_profit': 0,
|
||||||
|
'max_loss': 0
|
||||||
|
}
|
||||||
|
|
||||||
|
closed_trades = [t for t in self.trades if t['status'] == 'closed']
|
||||||
|
if not closed_trades:
|
||||||
|
return {
|
||||||
|
'total_trades': len(self.trades),
|
||||||
|
'winning_trades': 0,
|
||||||
|
'losing_trades': 0,
|
||||||
|
'win_rate': 0,
|
||||||
|
'total_profit_loss': 0,
|
||||||
|
'avg_profit_loss': 0,
|
||||||
|
'max_profit': 0,
|
||||||
|
'max_loss': 0
|
||||||
|
}
|
||||||
|
|
||||||
|
profits = [t['profit_loss'] for t in closed_trades]
|
||||||
|
winning_trades = len([p for p in profits if p > 0])
|
||||||
|
losing_trades = len([p for p in profits if p < 0])
|
||||||
|
|
||||||
|
return {
|
||||||
|
'total_trades': len(closed_trades),
|
||||||
|
'winning_trades': winning_trades,
|
||||||
|
'losing_trades': losing_trades,
|
||||||
|
'win_rate': winning_trades / len(closed_trades) * 100 if closed_trades else 0,
|
||||||
|
'total_profit_loss': sum(profits),
|
||||||
|
'avg_profit_loss': np.mean(profits) if profits else 0,
|
||||||
|
'max_profit': max(profits) if profits else 0,
|
||||||
|
'max_loss': min(profits) if profits else 0
|
||||||
|
}
|
||||||
|
|
||||||
|
def print_all_trades(self):
|
||||||
|
"""
|
||||||
|
打印所有交易记录
|
||||||
|
"""
|
||||||
|
if not self.trades:
|
||||||
|
logger.info("暂无交易记录")
|
||||||
|
return
|
||||||
|
|
||||||
|
logger.info("=" * 80)
|
||||||
|
logger.info("交易记录详情")
|
||||||
|
logger.info("=" * 80)
|
||||||
|
|
||||||
|
for trade in self.trades:
|
||||||
|
if trade['status'] == 'closed':
|
||||||
|
logger.info(f"交易 #{trade['trade_id']}:")
|
||||||
|
logger.info(f" {trade['direction'].upper()} {trade['symbol']}")
|
||||||
|
logger.info(f" 开仓: {trade['open_price']:.2f} @ {trade['open_time']}")
|
||||||
|
logger.info(f" 平仓: {trade['close_price']:.2f} @ {trade['close_time']}")
|
||||||
|
logger.info(f" 盈亏: {trade['profit_loss']:.2f} ({trade['profit_loss_pct']:+.2f}%)")
|
||||||
|
logger.info(f" 原因: {trade['close_reason']}")
|
||||||
|
logger.info("-" * 40)
|
||||||
|
|
||||||
|
# 打印统计摘要
|
||||||
|
summary = self.get_trade_summary()
|
||||||
|
logger.info("交易统计摘要:")
|
||||||
|
logger.info(f" 总交易次数: {summary['total_trades']}")
|
||||||
|
logger.info(f" 盈利次数: {summary['winning_trades']}")
|
||||||
|
logger.info(f" 亏损次数: {summary['losing_trades']}")
|
||||||
|
logger.info(f" 胜率: {summary['win_rate']:.1f}%")
|
||||||
|
logger.info(f" 总盈亏: {summary['total_profit_loss']:.2f}")
|
||||||
|
logger.info(f" 平均盈亏: {summary['avg_profit_loss']:.2f}")
|
||||||
|
logger.info(f" 最大盈利: {summary['max_profit']:.2f}")
|
||||||
|
logger.info(f" 最大亏损: {summary['max_loss']:.2f}")
|
||||||
|
logger.info("=" * 80)
|
||||||
|
|
||||||
|
def save_to_csv(self, filename="trades.csv"):
|
||||||
|
"""
|
||||||
|
保存交易记录到CSV文件
|
||||||
|
"""
|
||||||
|
if not self.trades:
|
||||||
|
logger.info("无交易记录可保存")
|
||||||
|
return
|
||||||
|
|
||||||
|
df = pd.DataFrame(self.trades)
|
||||||
|
df.to_csv(filename, index=False, encoding='utf-8-sig')
|
||||||
|
logger.info(f"交易记录已保存到 {filename}")
|
||||||
|
|
||||||
|
def save_to_json(self, filename="trades.json"):
|
||||||
|
"""
|
||||||
|
保存交易记录到JSON文件
|
||||||
|
"""
|
||||||
|
if not self.trades:
|
||||||
|
logger.info("无交易记录可保存")
|
||||||
|
return
|
||||||
|
|
||||||
|
with open(filename, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(self.trades, f, ensure_ascii=False, indent=2, default=str)
|
||||||
|
logger.info(f"交易记录已保存到 {filename}")
|
||||||
|
|
||||||
|
# 全局交易日志记录器实例
|
||||||
|
trade_logger = TradeLogger()
|
||||||
@@ -12,11 +12,55 @@ def initialize():
|
|||||||
def shutdown():
|
def shutdown():
|
||||||
mt5.shutdown()
|
mt5.shutdown()
|
||||||
|
|
||||||
def get_rates(symbol, timeframe, count):
|
def get_rates(symbol, timeframe, count, start_date=None, end_date=None):
|
||||||
rates = mt5.copy_rates_from_pos(symbol, timeframe, 0, count)
|
"""
|
||||||
if rates is None:
|
获取历史数据
|
||||||
logger.info(f"获取{symbol}历史数据失败")
|
参数:
|
||||||
return None
|
- symbol: 交易品种
|
||||||
|
- timeframe: 时间周期
|
||||||
|
- count: 数据量 (当start_date和end_date都为None时使用)
|
||||||
|
- start_date: 开始日期 (格式: "YYYY-MM-DD" 或 datetime对象)
|
||||||
|
- end_date: 结束日期 (格式: "YYYY-MM-DD" 或 datetime对象)
|
||||||
|
"""
|
||||||
|
if start_date is not None and end_date is not None:
|
||||||
|
# 使用日期范围获取数据
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
# 转换字符串为datetime对象
|
||||||
|
if isinstance(start_date, str):
|
||||||
|
start_date = datetime.datetime.strptime(start_date, "%Y-%m-%d")
|
||||||
|
if isinstance(end_date, str):
|
||||||
|
end_date = datetime.datetime.strptime(end_date, "%Y-%m-%d")
|
||||||
|
|
||||||
|
# MetaTrader5需要UTC时间,且copy_rates_range需要timezone-aware的datetime对象
|
||||||
|
# 转换为UTC timezone
|
||||||
|
utc_timezone = datetime.timezone.utc
|
||||||
|
|
||||||
|
start_utc = start_date.replace(tzinfo=utc_timezone)
|
||||||
|
end_utc = end_date.replace(hour=23, minute=59, second=59, tzinfo=utc_timezone)
|
||||||
|
|
||||||
|
try:
|
||||||
|
rates = mt5.copy_rates_range(symbol, timeframe, start_utc, end_utc)
|
||||||
|
if rates is None:
|
||||||
|
logger.info(f"获取{symbol}从{start_date.date()}到{end_date.date()}的历史数据失败")
|
||||||
|
return None
|
||||||
|
logger.info(f"成功获取{symbol}从{start_date.date()}到{end_date.date()}的历史数据,共{len(rates)}条")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"使用日期范围获取数据失败: {e}")
|
||||||
|
logger.info("回退到使用数据量获取数据")
|
||||||
|
rates = mt5.copy_rates_from_pos(symbol, timeframe, 0, count)
|
||||||
|
if rates is None:
|
||||||
|
logger.info(f"获取{symbol}历史数据失败")
|
||||||
|
return None
|
||||||
|
logger.info(f"成功获取{symbol}历史数据,共{len(rates)}条")
|
||||||
|
else:
|
||||||
|
# 使用数据量获取数据
|
||||||
|
rates = mt5.copy_rates_from_pos(symbol, timeframe, 0, count)
|
||||||
|
if rates is None:
|
||||||
|
logger.info(f"获取{symbol}历史数据失败")
|
||||||
|
return None
|
||||||
|
logger.info(f"成功获取{symbol}历史数据,共{len(rates)}条")
|
||||||
|
|
||||||
return rates
|
return rates
|
||||||
|
|
||||||
def has_open_position(symbol):
|
def has_open_position(symbol):
|
||||||
@@ -68,3 +112,12 @@ def send_order(symbol, order_type, volume=0.01):
|
|||||||
logger.error(f"下单失败,retcode={result.retcode}")
|
logger.error(f"下单失败,retcode={result.retcode}")
|
||||||
else:
|
else:
|
||||||
logger.info(f"下单成功: {order_type} {symbol} {volume}")
|
logger.info(f"下单成功: {order_type} {symbol} {volume}")
|
||||||
|
|
||||||
|
def get_current_price(symbol):
|
||||||
|
"""获取当前价格"""
|
||||||
|
try:
|
||||||
|
tick = mt5.symbol_info_tick(symbol)
|
||||||
|
return tick.bid if tick else None
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"获取当前价格失败: {e}")
|
||||||
|
return None
|
||||||
|
|||||||
Reference in New Issue
Block a user