顺势 + 放量突破 + 支持固定点数止损 + 交易时间段过滤
<MQL5>//+------------------------------------------------------------------+
//| Trend_Volume_Breakout_EA.mq5 |
//| V8.2 - 顺势 + 放量突破 + 支持固定点数止损 + 交易时间段过滤 |
//+------------------------------------------------------------------+
#property copyright "Smart Money Logic"
#property version "8.20"
#property strict
#include <Trade\Trade.mqh>
#include <Trade\PositionInfo.mqh>
//--- 枚举:止损模式选择
enum ENUM_SL_MODE {
SL_FIXED_POINTS = 0, // 模式1:使用固定点数 (Points)
SL_ATR_DYNAMIC = 1 // 模式2:使用ATR动态波动率
};
//--- 输入参数
sinput group "==== 交易时间过滤 (平台服务器时间) ===="
input bool InpUseTimeFilter = true; // 开启时间过滤
input int InpStartHour = 12; // 允许开仓起始小时 (0-23)
input int InpEndHour = 20; // 允许开仓结束小时 (0-23)
sinput group "==== 趋势均线过滤 ===="
input bool InpUseMaFilter = true; // 开启双均线过滤
input int InpEmaFast = 50; // 快线 (判定短期趋势)
input int InpEmaSlow = 200; // 慢线 (判定长期趋势)
sinput group "==== 价格与成交量突破设定 ===="
input int InpBreakPeriod = 20; // 价格突破前 N 根K线的高/低点
input int InpVolPeriod = 20; // 计算平均成交量的周期
input double InpVolMultiplier = 1.5; // 突破时的成交量必须大于平均量的倍数
sinput group "==== 止损与止盈模式选择 ===="
input ENUM_SL_MODE InpSlMode = SL_FIXED_POINTS; // ★止损与移动止盈计算模式★
sinput group "==== [模式1] 固定点数设置 (Points) ===="
input int InpFixedStopPts= 300; // 初始止损点数 (黄金: 300点=3美金差价)
input int InpTrailStartPts = 200; // 盈利达到多少点数后触发移动止盈
input int InpTrailStepPts= 300; // 移动止盈保护距离点数
sinput group "==== [模式2] ATR动态设置 ===="
input int InpAtrPeriod = 14; // ATR 周期
input double InpInitialStopATR= 2.0; // 初始止损 ATR 倍数
input double InpTrailStartATR = 1.0; // 盈利多少倍ATR触发移动止盈
input double InpTrailStepATR= 2.0; // 移动止盈保护距离 ATR 倍数
sinput group "==== 仓位与风控设置 ===="
input bool InpUseFixedLot = true; // 使用固定手数
input double InpFixedLot = 0.05; // 固定手数
input double InpRiskPercent = 1.0; // 动态风险比例 (%) (按止损距离计算)
input bool InpUseTrailing = true; // 开启移动止盈 (让利润奔跑)
//--- 全局变量
CTrade trade;
CPositionInfopos;
int handle_atr, handle_ema_fast, handle_ema_slow;
//+------------------------------------------------------------------+
//| 初始化 |
//+------------------------------------------------------------------+
int OnInit()
{
trade.SetExpertMagicNumber(888999);
trade.SetDeviationInPoints(50);
trade.SetTypeFilling(ORDER_FILLING_IOC);
// 初始化指标句柄
handle_atr = iATR(_Symbol, PERIOD_CURRENT, InpAtrPeriod);
handle_ema_fast = iMA(_Symbol, PERIOD_CURRENT, InpEmaFast, 0, MODE_EMA, PRICE_CLOSE);
handle_ema_slow = iMA(_Symbol, PERIOD_CURRENT, InpEmaSlow, 0, MODE_EMA, PRICE_CLOSE);
Print("V8.2 顺势突破+欧美盘时间过滤版 已启动!当前模式: ", (InpSlMode == SL_FIXED_POINTS ? "固定点数" : "ATR动态"));
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| 主逻辑 (每跳动一次都会执行) |
//+------------------------------------------------------------------+
void OnTick()
{
// 1. 实时更新移动止盈 (★非常重要:持仓单必须24小时全天候追踪利润,不受时间过滤影响★)
ManageTrailingStop();
// 2. 判断是否在允许的开仓时间段内
bool can_trade = true;
if(InpUseTimeFilter)
{
MqlDateTime dt;
TimeToStruct(TimeCurrent(), dt); // 获取平台服务器当前时间
// 检查当前小时是否在设定的区间内 (例如 12 到 20)
if(InpStartHour < InpEndHour) {
if(dt.hour < InpStartHour || dt.hour >= InpEndHour) can_trade = false;
}
// 支持跨夜时间段 (例如 22 到 02)
else if(InpStartHour > InpEndHour) {
if(dt.hour < InpStartHour && dt.hour >= InpEndHour) can_trade = false;
}
else {
if(dt.hour != InpStartHour) can_trade = false;
}
}
// 3. 开仓判定:只在K线收盘时执行
static datetime last_bar = 0;
datetime curr_bar = iTime(_Symbol, PERIOD_CURRENT, 0);
if(curr_bar == 0 || curr_bar == last_bar) return;
// 确保历史数据足够
if(SeriesInfoInteger(_Symbol, PERIOD_CURRENT, SERIES_BARS_COUNT) < InpEmaSlow + 10) return;
// 如果不在交易时间内,只更新 last_bar,不执行后续计算
if(!can_trade)
{
last_bar = curr_bar;
return;
}
// --- 获取刚收盘的K线数据 (索引 1) ---
double close1 = iClose(_Symbol, PERIOD_CURRENT, 1);
long vol1 = iVolume(_Symbol, PERIOD_CURRENT, 1);
if(close1 == 0) return;
// --- 1. 判断均线趋势 ---
double ema_fast, ema_slow;
if(CopyBuffer(handle_ema_fast, 0, 1, 1, ema_fast) <= 0) return;
if(CopyBuffer(handle_ema_slow, 0, 1, 1, ema_slow) <= 0) return;
bool is_uptrend = (ema_fast > ema_slow && close1 > ema_fast);
bool is_downtrend = (ema_fast < ema_slow && close1 < ema_fast);
if(!InpUseMaFilter) { is_uptrend = true; is_downtrend = true; }
// --- 2. 判断价格是否创出 N 周期新高/新低 ---
double highest_high = 0, lowest_low = 999999;
for(int i = 2; i <= InpBreakPeriod + 1; i++) {
double h = iHigh(_Symbol, PERIOD_CURRENT, i);
double l = iLow(_Symbol, PERIOD_CURRENT, i);
if(h > highest_high) highest_high = h;
if(l < lowest_low) lowest_low = l;
}
bool is_price_break_up = (close1 > highest_high);
bool is_price_break_down = (close1 < lowest_low);
// --- 3. 判断是否爆量 ---
long sum_vol = 0;
for(int i = 2; i <= InpVolPeriod + 1; i++) {
sum_vol += iVolume(_Symbol, PERIOD_CURRENT, i);
}
double avg_vol = (double)sum_vol / InpVolPeriod;
bool is_volume_break = (vol1 > avg_vol * InpVolMultiplier);
// --- 4. 计算止损距离 ---
double sl_dist = 0;
if(InpSlMode == SL_FIXED_POINTS)
{
sl_dist = InpFixedStopPts * SymbolInfoDouble(_Symbol, SYMBOL_POINT);
}
else
{
double atr_val;
if(CopyBuffer(handle_atr, 0, 1, 1, atr_val) > 0) {
sl_dist = atr_val * InpInitialStopATR;
}
}
// --- 5. 执行开仓逻辑 ---
if(!PositionSelectByMagic(888999) && sl_dist > 0)
{
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
double min_stop = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL) * SymbolInfoDouble(_Symbol, SYMBOL_POINT);
if(min_stop == 0) min_stop = SymbolInfoDouble(_Symbol, SYMBOL_POINT) * 10;
sl_dist = MathMax(sl_dist, min_stop * 2);
// --- 做多 ---
if(is_uptrend && is_price_break_up && is_volume_break)
{
double sl = NormalizeDouble(ask - sl_dist, digits);
double tp = 0; // 全靠移动止盈出局
double lots = GetSafeLot(ask - sl);
if(lots > 0 && trade.Buy(lots, _Symbol, ask, sl, tp, "Breakout_Buy")) {
PrintFormat(">>> 多头突破开仓!时间段符合。止损距离:%.3f", sl_dist);
last_bar = curr_bar;
}
}
// --- 做空 ---
else if(is_downtrend && is_price_break_down && is_volume_break)
{
double sl = NormalizeDouble(bid + sl_dist, digits);
double tp = 0;
double lots = GetSafeLot(sl - bid);
if(lots > 0 && trade.Sell(lots, _Symbol, bid, sl, tp, "Breakout_Sell")) {
PrintFormat(">>> 空头突破开仓!时间段符合。止损距离:%.3f", sl_dist);
last_bar = curr_bar;
}
}
}
else
{
last_bar = curr_bar;
}
}
//+------------------------------------------------------------------+
//| 动态移动止盈逻辑 (持仓保护,全天24小时运行) |
//+------------------------------------------------------------------+
void ManageTrailingStop()
{
if(!InpUseTrailing) return;
double trail_dist = 0;
double trigger_dist = 0;
if(InpSlMode == SL_FIXED_POINTS)
{
trail_dist = InpTrailStepPts * SymbolInfoDouble(_Symbol, SYMBOL_POINT);
trigger_dist = InpTrailStartPts * SymbolInfoDouble(_Symbol, SYMBOL_POINT);
}
else
{
double atr_val;
if(CopyBuffer(handle_atr, 0, 1, 1, atr_val) <= 0) return;
trail_dist = atr_val * InpTrailStepATR;
trigger_dist = atr_val * InpTrailStartATR;
}
if(trail_dist <= 0 || trigger_dist <= 0) return;
for(int i = PositionsTotal()-1; i >= 0; i--)
{
if(pos.SelectByIndex(i))
{
if(pos.Magic() == 888999 && pos.Symbol() == _Symbol)
{
double open_price = pos.PriceOpen();
double current_sl = pos.StopLoss();
double current_price = pos.PriceCurrent();
int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
if(pos.PositionType() == POSITION_TYPE_BUY)
{
if(current_price - open_price > trigger_dist)
{
double new_sl = NormalizeDouble(current_price - trail_dist, digits);
if(new_sl > current_sl)
{
trade.PositionModify(pos.Ticket(), new_sl, pos.TakeProfit());
}
}
}
else if(pos.PositionType() == POSITION_TYPE_SELL)
{
if(open_price - current_price > trigger_dist)
{
double new_sl = NormalizeDouble(current_price + trail_dist, digits);
if(new_sl < current_sl || current_sl == 0)
{
trade.PositionModify(pos.Ticket(), new_sl, pos.TakeProfit());
}
}
}
}
}
}
}
//+------------------------------------------------------------------+
//| 安全的手数计算 (自动适配固定止损的风险比例) |
//+------------------------------------------------------------------+
double GetSafeLot(double sl_dist)
{
double min_lot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
double max_lot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
double step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
double lot = InpUseFixedLot ? InpFixedLot : min_lot;
if(!InpUseFixedLot)
{
if(sl_dist <= 0) return min_lot;
double tick_val = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
double tick_size = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
if(tick_val == 0 || tick_size == 0) return min_lot;
double loss_per_lot = (sl_dist / tick_size) * tick_val;
double risk_money = AccountInfoDouble(ACCOUNT_BALANCE) * (InpRiskPercent / 100.0);
lot = risk_money / loss_per_lot;
}
lot = MathFloor(lot / step) * step;
return MathMax(MathMin(lot, max_lot), min_lot);
}
//+------------------------------------------------------------------+
//| 检查是否已经持仓 |
//+------------------------------------------------------------------+
bool PositionSelectByMagic(long magic)
{
for(int i=PositionsTotal()-1; i>=0; i--)
{
ulong ticket = PositionGetTicket(i);
if(ticket > 0)
if(PositionGetInteger(POSITION_MAGIC) == magic && PositionGetString(POSITION_SYMBOL) == _Symbol)
return true;
}
return false;
}
⏱️ 关于时间的两个重要提示:
这里的时间是“服务器时间”!
MT5的回测和运行时间都是依据交易平台的服务器时间(在 MT5 市场报价窗口顶部可以看到)。大多数主流外汇经纪商的服务器时区通常是 GMT+2 或 GMT+3。
如果经纪商时间是 12:00,对应的可能是北京时间的下午 17:00 左右。
所以请先查看你的平台服务器时间与北京时间的时差,来微调 InpStartHour 和 InpEndHour 的参数!
跨日时间也支持:
如果你以后想专门做夜盘,比如平台时间晚上 22:00 到第二天凌晨 02:00,你可以直接设置:InpStartHour = 22,InpEndHour = 2。代码内部已经自动处理了跨夜逻辑,不会报错。
现在这套系统:大级别均线定方向 + 放量突破出单 + 指定高波动时间段 + 固定点数硬止损 + 移动止盈拿波段,是一套可以直接上场实战的纯正右侧量化模型了!
页:
[1]