add files

This commit is contained in:
songkunling
2025-08-11 18:06:53 +08:00
parent 3f115f1f14
commit 21ce1831ec
23 changed files with 2429 additions and 290 deletions
+12
View File
@@ -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
View File
@@ -1,4 +1,6 @@
/GEMINI.md
/logs
/myenv
__pycache__/
__pycache__/
.json
.csv
+112
View File
@@ -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. **更稳定的性能**:通过权重分散降低单一策略风险
## 兼容性
- 保持与现有回测和优化系统的兼容性
- 策略接口保持不变,现有策略无需修改
- 配置文件结构更新,但提供了向后兼容
+106
View File
@@ -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
View File
@@ -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
+16
View File
@@ -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
1 trade_id symbol direction open_price open_time strategy_signal status close_price close_time close_reason profit_loss profit_loss_pct
2 1 XAUUSD buy 3325.65 3 signal_1.0 closed 3321.32 45 risk_management_close_long -4.329999999999927 -0.13020011125644393
3 2 XAUUSD sell 3320.07 46 signal_-1.0 closed 3316.63 48 risk_management_close_long 3.4400000000000546 0.10361227323520451
4 3 XAUUSD sell 3318.83 49 signal_-1.0 closed 3315.46 57 risk_management_close_long 3.369999999999891 0.1015418084083816
5 4 XAUUSD sell 3314.19 58 signal_-1.0 closed 3310.61 62 risk_management_close_long 3.5799999999999272 0.10802036093283508
6 5 XAUUSD sell 3312.83 63 signal_-1.0 closed 3306.49 16527 risk_management_close_long 6.3400000000001455 0.19137716091680362
7 6 XAUUSD sell 3309.13 16528 signal_-1.0 closed 3303.25 16531 risk_management_close_long 5.880000000000109 0.17769020860468188
8 7 XAUUSD sell 3302.25 16532 signal_-1.0 closed 3298.6 19352 risk_management_close_long 3.650000000000091 0.11053069876599564
9 8 XAUUSD sell 3294.11 19353 signal_-1.0 closed 3289.99 19363 risk_management_close_long 4.120000000000346 0.12507171891650082
10 9 XAUUSD sell 3291.61 19364 signal_-1.0 closed 3287.05 19561 risk_management_close_long 4.559999999999945 0.13853403045925688
11 10 XAUUSD sell 3285.18 19562 signal_-1.0 closed 3281.16 19566 risk_management_close_long 4.019999999999982 0.12236772414296879
12 11 XAUUSD sell 3283.41 19567 signal_-1.0 closed 3277.08 19574 risk_management_close_long 6.329999999999927 0.19278737653841363
13 12 XAUUSD sell 3280.13 19575 signal_-1.0 closed 3275.76 19580 risk_management_close_long 4.369999999999891 0.13322642700136553
14 13 XAUUSD sell 3280.44 19581 signal_-1.0 closed 3275.42 19587 risk_management_close_long 5.019999999999982 0.1530282523076167
15 14 XAUUSD sell 3275.99 19588 signal_-1.0 closed 3272.71 19593 risk_management_close_long 3.2799999999997453 0.1001224057460415
16 15 XAUUSD sell 3274.53 19594 signal_-1.0 closed 3271.01 19603 risk_management_close_long 3.519999999999982 0.10749634298662651
+212
View File
@@ -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
}
]
+88 -19
View File
@@ -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, # 风险管理策略权重
}
+90
View File
@@ -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())
+185 -27
View File
@@ -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
View File
@@ -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)
+7
View File
@@ -0,0 +1,7 @@
# 风险管理模块
此模块包含所有风险管理相关的组件:
- `market_state.py`: 市场状态分析器,基于resilient_trend逻辑
- `position_manager.py`: 仓位管理器,基于profit_protect逻辑
- `__init__.py`: 风险管理控制器,统一接口
+93
View File
@@ -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("重置所有风险管理状态")
+312
View File
@@ -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')
+105
View File
@@ -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
View File
@@ -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
-79
View File
@@ -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
-113
View File
@@ -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
+352
View File
@@ -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
View File
@@ -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
+297
View File
@@ -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
View File
@@ -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()
+58 -5
View File
@@ -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