mirror of
https://github.com/silencesdg/mt5_python_ea_suite.git
synced 2026-07-28 03:07:48 +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": []
|
||||
}
|
||||
}
|
||||
+3
-1
@@ -1,4 +1,6 @@
|
||||
/GEMINI.md
|
||||
/logs
|
||||
/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
|
||||
|
||||
from trade_logger import trade_logger
|
||||
from risk_management import RiskController
|
||||
from config import SIGNAL_THRESHOLDS
|
||||
from logger import logger
|
||||
class BacktestEngine:
|
||||
def __init__(self, df):
|
||||
"""
|
||||
@@ -11,9 +14,15 @@ class BacktestEngine:
|
||||
"""
|
||||
执行策略的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)
|
||||
|
||||
def apply_threshold(score):
|
||||
if score >= buy_threshold:
|
||||
if score > SIGNAL_THRESHOLDS["buy_threshold"]:
|
||||
return 1
|
||||
elif score <= sell_threshold:
|
||||
elif score < SIGNAL_THRESHOLDS["sell_threshold"]:
|
||||
return -1
|
||||
else:
|
||||
return 0
|
||||
|
||||
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
|
||||
|
||||
def calc_returns(self, signals):
|
||||
@@ -43,3 +59,101 @@ class BacktestEngine:
|
||||
df['strategy_returns'] = df['signal'] * df['returns']
|
||||
cum_ret = (1 + df['strategy_returns']).cumprod() - 1
|
||||
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"
|
||||
INTERVAL = 60 # 秒
|
||||
INITIAL_CAPITAL = 10000 # 初始资金
|
||||
|
||||
# 策略权重配置 (策略实例, 权重)
|
||||
STRATEGIES = [
|
||||
(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), # 新增带容错的趋势策略
|
||||
]
|
||||
# 时间配置
|
||||
TIMEFRAME = 1 # M1 (1分钟图)
|
||||
|
||||
# 交易信号阈值
|
||||
BUY_THRESHOLD = 1.5
|
||||
SELL_THRESHOLD = -1.5
|
||||
# 回测时间范围 (格式: "YYYY-MM-DD")
|
||||
# 注意:这些日期需要确保在MT5服务器上有可用数据
|
||||
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
|
||||
from utils import initialize, shutdown, get_rates, close_all, send_order
|
||||
from backtest import BacktestEngine
|
||||
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
|
||||
|
||||
@@ -13,78 +15,234 @@ def run_realtime():
|
||||
logger.error("MT5初始化失败")
|
||||
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 = []
|
||||
weights = []
|
||||
for strat, weight in STRATEGIES:
|
||||
for strat, weight in strategies_with_weights:
|
||||
try:
|
||||
logger.info(f"执行策略:{strat.__class__.__module__}")
|
||||
signal = strat.generate_signal()
|
||||
logger.info(f"执行策略:{strat.__class__.__module__}, 权重: {weight:.2f}")
|
||||
|
||||
# 特殊处理风险管理策略
|
||||
if strat.__class__.__module__ == 'risk_management':
|
||||
signal = strat.generate_signal_with_sync(risk_controller)
|
||||
else:
|
||||
signal = strat.generate_signal()
|
||||
|
||||
signals.append(signal)
|
||||
weights.append(weight)
|
||||
except Exception as e:
|
||||
logger.exception(f"运行策略 {strat.__class__.__module__} 时出错:{e}")
|
||||
|
||||
# 计算加权信号
|
||||
weighted_signal_sum = sum(s * w for s, w in zip(signals, weights))
|
||||
|
||||
if weighted_signal_sum >= BUY_THRESHOLD:
|
||||
logger.info(f"加权信号总和 ({weighted_signal_sum:.2f}) 达到买入阈值 ({BUY_THRESHOLD}),发送买入信号")
|
||||
close_all("XAUUSD")
|
||||
send_order("XAUUSD", 'buy')
|
||||
elif weighted_signal_sum <= SELL_THRESHOLD:
|
||||
logger.info(f"加权信号总和 ({weighted_signal_sum:.2f}) 达到卖出阈值 ({SELL_THRESHOLD}),发送卖出信号")
|
||||
close_all("XAUUSD")
|
||||
send_order("XAUUSD", 'sell')
|
||||
logger.info(f"加权信号总和: {weighted_signal_sum:.2f}")
|
||||
|
||||
# 获取当前价格用于风险管理
|
||||
current_price = get_current_price(SYMBOL)
|
||||
current_time = pd.Timestamp.now()
|
||||
|
||||
# 检查风险管理条件
|
||||
if current_price:
|
||||
risk_action, risk_reason = risk_controller.check_risk_management(current_price)
|
||||
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:
|
||||
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()
|
||||
|
||||
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():
|
||||
if not initialize():
|
||||
logger.error("MT5初始化失败")
|
||||
return
|
||||
|
||||
symbol = "XAUUSD"
|
||||
timeframe = 1 # M1
|
||||
count = 50000
|
||||
rates = get_rates(symbol, timeframe, count)
|
||||
shutdown()
|
||||
# 重置交易日志记录器
|
||||
trade_logger.__init__()
|
||||
|
||||
# 根据配置选择获取数据的方式
|
||||
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:
|
||||
logger.error("获取历史数据失败")
|
||||
shutdown()
|
||||
return
|
||||
|
||||
logger.info(f"初始资金: {INITIAL_CAPITAL}")
|
||||
|
||||
df = pd.DataFrame(rates)
|
||||
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 = []
|
||||
weights = []
|
||||
strategy_names = []
|
||||
|
||||
for strat, weight in STRATEGIES:
|
||||
for strat, weight in strategies_with_weights:
|
||||
try:
|
||||
logger.info(f"回测策略:{strat.__class__.__module__}")
|
||||
logger.info(f"回测策略:{strat.__class__.__module__}, 权重: {weight:.2f}")
|
||||
signals = engine.run_strategy(strat)
|
||||
signals_list.append(signals)
|
||||
weights.append(weight)
|
||||
strategy_names.append(strat.__class__.__module__)
|
||||
except Exception as e:
|
||||
logger.exception(f"回测策略 {strat.__class__.__module__} 时出错:{e}")
|
||||
|
||||
combined_signal = engine.combine_signals(signals_list, weights, BUY_THRESHOLD, SELL_THRESHOLD)
|
||||
cum_ret = engine.calc_returns(combined_signal)
|
||||
combined_signal = engine.combine_signals(signals_list, weights)
|
||||
|
||||
# 使用带交易记录的收益率计算
|
||||
cum_ret = engine.calc_returns_with_trades(combined_signal, SYMBOL)
|
||||
final_capital = INITIAL_CAPITAL * (1 + cum_ret.iloc[-1])
|
||||
|
||||
logger.info("策略组合回测完成")
|
||||
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("\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__":
|
||||
# --- 选择运行模式 ---
|
||||
# 1. 运行一次回测 (使用config.py中的默认权重)
|
||||
# run_backtest()
|
||||
run_backtest()
|
||||
|
||||
# 2. 运行实盘交易 (使用config.py中的默认权重)
|
||||
# run_realtime()
|
||||
|
||||
# 3. 运行遗传算法优化,寻找最佳权重
|
||||
run_optimizer()
|
||||
# run_optimizer()
|
||||
|
||||
+36
-16
@@ -8,12 +8,15 @@ import pandas as pd
|
||||
from utils import initialize, shutdown, get_rates
|
||||
from backtest import BacktestEngine
|
||||
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. 定义适应度函数 (已优化)
|
||||
def evaluate_fitness(individual, df_data):
|
||||
"""
|
||||
评估函数现在接收预先加载的DataFrame作为参数,避免了重复IO。
|
||||
使用新的动态权重架构进行评估。
|
||||
输入:
|
||||
- individual: 一个代表策略权重的列表。
|
||||
- df_data: 包含历史K线数据的Pandas DataFrame。
|
||||
@@ -21,22 +24,33 @@ def evaluate_fitness(individual, df_data):
|
||||
"""
|
||||
weights = individual
|
||||
|
||||
# 直接使用传入的df_data,不再需要get_rates
|
||||
# 使用新的架构进行评估
|
||||
engine = BacktestEngine(df_data)
|
||||
|
||||
# 创建风险管理器和权重管理器
|
||||
risk_controller = RiskController()
|
||||
weight_manager = DynamicWeightManager(risk_controller)
|
||||
|
||||
# 获取策略实例
|
||||
strategies_with_weights = weight_manager.get_current_strategies_and_weights()
|
||||
|
||||
# 使用优化器提供的权重替换动态权重
|
||||
signals_list = []
|
||||
|
||||
strategy_instances = [s for s, w in STRATEGIES]
|
||||
|
||||
for strat in strategy_instances:
|
||||
signals = engine.run_strategy(strat)
|
||||
signals_list.append(signals)
|
||||
|
||||
combined_signal = engine.combine_signals(signals_list, weights, BUY_THRESHOLD, SELL_THRESHOLD)
|
||||
strategy_names = []
|
||||
|
||||
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__)
|
||||
|
||||
# 使用优化器权重进行组合
|
||||
combined_signal = engine.combine_signals(signals_list, weights[:len(signals_list)])
|
||||
cum_ret = engine.calc_returns(combined_signal)
|
||||
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,)
|
||||
|
||||
@@ -52,10 +66,11 @@ def run_optimizer():
|
||||
logger.error("MT5初始化失败,无法开始优化")
|
||||
return
|
||||
|
||||
symbol = "XAUUSD"
|
||||
timeframe = 1 # M1
|
||||
count = 50000 # 使用与回测相同的数据量
|
||||
rates = get_rates(symbol, timeframe, count)
|
||||
# 根据配置选择获取数据的方式
|
||||
if USE_DATE_RANGE:
|
||||
rates = get_rates(SYMBOL, TIMEFRAME, OPTIMIZER_COUNT, OPTIMIZER_START_DATE, OPTIMIZER_END_DATE)
|
||||
else:
|
||||
rates = get_rates(SYMBOL, TIMEFRAME, OPTIMIZER_COUNT)
|
||||
shutdown() # 获取数据后即可关闭连接
|
||||
|
||||
if rates is None:
|
||||
@@ -72,7 +87,12 @@ def run_optimizer():
|
||||
|
||||
toolbox = base.Toolbox()
|
||||
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("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("重置所有持仓状态")
|
||||
+14
-12
@@ -9,9 +9,7 @@ class Strategy:
|
||||
self.symbol = "XAUUSD"
|
||||
self.timeframe = mt5.TIMEFRAME_M1
|
||||
self.kdj_period = 9
|
||||
self.kdj_buy_threshold = 10
|
||||
self.kdj_sell_threshold = 90
|
||||
|
||||
|
||||
def _calculate_indicators(self, df):
|
||||
"""
|
||||
计算KDJ指标
|
||||
@@ -27,7 +25,7 @@ class Strategy:
|
||||
def generate_signal(self):
|
||||
"""
|
||||
KDJ策略实盘
|
||||
J值小于10买入,大于90卖出
|
||||
K线向上穿越D线(金叉)买入,K线向下穿越D线(死叉)卖出
|
||||
"""
|
||||
rates = get_rates(self.symbol, self.timeframe, self.kdj_period + 30)
|
||||
if rates is None or len(rates) < self.kdj_period:
|
||||
@@ -35,26 +33,30 @@ class Strategy:
|
||||
df = pd.DataFrame(rates)
|
||||
df = self._calculate_indicators(df)
|
||||
|
||||
if df['j'].iloc[-2] < self.kdj_buy_threshold:
|
||||
logger.info(f"J值小于{self.kdj_buy_threshold},产生买入信号: {self.symbol}")
|
||||
# Golden cross
|
||||
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
|
||||
elif df['j'].iloc[-2] > self.kdj_sell_threshold:
|
||||
logger.info(f"J值大于{self.kdj_sell_threshold},产生卖出信号: {self.symbol}")
|
||||
# Dead cross
|
||||
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 0
|
||||
|
||||
def run_backtest(self, df):
|
||||
"""
|
||||
KDJ回测方法
|
||||
根据J值极端生成信号
|
||||
根据金叉和死叉生成信号
|
||||
"""
|
||||
df = df.copy()
|
||||
df = self._calculate_indicators(df)
|
||||
|
||||
signals = pd.Series(0, index=df.index)
|
||||
for i in range(self.kdj_period, len(df)):
|
||||
if df['j'].iloc[i-1] < self.kdj_buy_threshold:
|
||||
for i in range(1, len(df)):
|
||||
# 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
|
||||
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
|
||||
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.timeframe = mt5.TIMEFRAME_M1
|
||||
self.rsi_period = 14
|
||||
self.rsi_buy_threshold = 30
|
||||
self.rsi_sell_threshold = 70
|
||||
|
||||
def _calculate_indicators(self, df):
|
||||
"""
|
||||
@@ -25,8 +23,8 @@ class Strategy:
|
||||
|
||||
def generate_signal(self):
|
||||
"""
|
||||
RSI策略实盘:
|
||||
RSI < 30买入,RSI > 70卖出。
|
||||
RSI策略实盘
|
||||
RSI上穿超卖线买入,下穿超买线卖出
|
||||
"""
|
||||
rates = get_rates(self.symbol, self.timeframe, self.rsi_period + 30)
|
||||
if rates is None or len(rates) < self.rsi_period:
|
||||
@@ -34,26 +32,34 @@ class Strategy:
|
||||
df = pd.DataFrame(rates)
|
||||
df = self._calculate_indicators(df)
|
||||
|
||||
if df['rsi'].iloc[-2] < self.rsi_buy_threshold:
|
||||
logger.info(f"RSI小于{self.rsi_buy_threshold},产生买入信号: {self.symbol}")
|
||||
oversold_level = 30
|
||||
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
|
||||
elif df['rsi'].iloc[-2] > self.rsi_sell_threshold:
|
||||
logger.info(f"RSI大于{self.rsi_sell_threshold},产生卖出信号: {self.symbol}")
|
||||
# RSI crosses below overbought level
|
||||
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 0
|
||||
|
||||
def run_backtest(self, df):
|
||||
"""
|
||||
RSI回测:
|
||||
RSI < 30买入,RSI > 70卖出。
|
||||
RSI回测方法
|
||||
根据RSI穿越超买超卖线生成信号
|
||||
"""
|
||||
df = df.copy()
|
||||
df = self._calculate_indicators(df)
|
||||
|
||||
signals = pd.Series(0, index=df.index)
|
||||
for i in range(self.rsi_period, len(df)):
|
||||
if df['rsi'].iloc[i-1] < self.rsi_buy_threshold:
|
||||
oversold_level = 30
|
||||
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
|
||||
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
|
||||
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():
|
||||
mt5.shutdown()
|
||||
|
||||
def get_rates(symbol, timeframe, count):
|
||||
rates = mt5.copy_rates_from_pos(symbol, timeframe, 0, count)
|
||||
if rates is None:
|
||||
logger.info(f"获取{symbol}历史数据失败")
|
||||
return None
|
||||
def get_rates(symbol, timeframe, count, start_date=None, end_date=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
|
||||
|
||||
def has_open_position(symbol):
|
||||
@@ -68,3 +112,12 @@ def send_order(symbol, order_type, volume=0.01):
|
||||
logger.error(f"下单失败,retcode={result.retcode}")
|
||||
else:
|
||||
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