ARTICLE · 1148055
源码公开:给 MT5 网格 EA 装上"驾驶舱",参数跑着改,风险看得见
“本文基于 GridPro Panel v1.00(MetaTrader 5 版,源码文件
GridPro_Panel_v1.mq5)。EA 仅支持对冲(Hedging)账户,文末附完整 MQL5 源码,可自行编译运行。
免责声明
本文所述内容仅供技术交流与学习参考,不构成任何投资建议。金融市场存在不确定性,过往表现不代表未来结果。投资者应根据自身情况独立判断,审慎决策,自行承担投资风险。
一、跑网格的人,多少都遇过这几件事 😤
网格 EA 挂上去,就"失联"了:现在攒了多少单、均价在哪、这一篮子浮亏多少,得切到终端列表里一单一单数,数完再切回图表看行情。
行情判断变了,想把加仓间距从 300 点放宽到 500 点:先移除 EA、重新加载、把十几个参数再填一遍——等填完,行情早走远了。
最不踏实的是用"黑盒"网格:手里只有一个 ex5 文件,里面怎么加仓、什么条件止损、风险藏在哪,全都看不到。马丁网格最怕的不是不赚钱,而是单边行情来了没人踩刹车。
GridPro Panel 想解决的就是这三件事:逻辑开源看得见、运行状态上面板、关键参数跑着改。
二、它是什么
GridPro Panel 是一款 MT5 网格策略 EA,自带一块暗色主题的可视化控制面板。一句话概括:网格逻辑全在源码里,读得到;持仓风险全在面板上,看得见;核心参数在面板里,回车就改。
几个基本设定先说清楚:
需要对冲(Hedging)账户:EA 启动时会自动检查账户类型,非对冲账户直接拒绝运行,避免在净持账户上多空对冲失效; 多空双向网格:可以只做多、只做空,也可以多空同时各跑各的篮子,互不干扰; 魔术号 + 品种隔离:只管理自己魔术号下的持仓,同一账户多个图表各挂一套也不会打架; 信号只管首单:技术信号只在首单入场时起作用,加仓节奏完全交给网格间距,不反复猜方向。

三、网格引擎:入场、加仓、出场
首单入场:五种方式任选。 默认"无信号",挂上即开首单;也可以从四种技术信号里选——均线金叉/死叉、RSI 超买超卖、布林带下轨回归/上轨回归、随机指标低位金叉/高位死叉,还支持一键反向。选信号的目的只有一个:让首单别在随便的位置进场。
加仓手数:三种模式。 固定手数(每层一样多)、马丁倍数(每层 ×1.5,倍数可调)、等差递增(每层 +0.01,增量可调)。另有"按余额自动放大首单"开关——每满 N 余额首单手数翻一档,不同资金规模的账户可以共用一套参数。
网格间距:固定或动态。 固定点数间距最直观;ATR 动态间距按 H1 周期 ATR(14) 乘以系数自适应波动率,行情剧烈时间距自动放宽。间距还支持逐层放大(比如每层 ×1.2),越逆势层距越宽,避免底部密集成仓。
篮子出场:按单边篮子整体计算。金额止盈(如整篮浮盈 +8 启动、回撤 $3 落袋,启用后接管金额止盈);另设篮子止损金额兜底。
四、九项风控:给网格系上"安全带"
马丁类加仓的风险必须直说:单边行情里网格会持续加仓,手数递增会同步放大敞口。所以这套 EA 把风控做成了默认可用的"硬约束",一共九项:
账户回撤止损%:净值回撤达到设定值(默认 30%),全部平仓并停止运行,面板直接显示触发原因; 日亏损上限%:当日亏损触及红线,全平并锁定,次日自动解除; 日盈利达标%:当日盈利达到目标,落袋锁定到次日,赚到"目标位"就收手; 篮子止损金额:单边浮亏触及设定金额,整篮出局; 最大总手数:全账户总仓位封顶,防止手数失控; 最低保证金比例%:保证金比例低于阈值停止开新仓,给极端行情留缓冲; 最大点差:点差异常(跳点、流动性稀薄)时暂停开仓,面板同步提示; 交易时段:首单只允许在设定时段入场(支持跨午夜时段); 周五定时全平:周五到点全部平仓并停止,不带单过周末。
另设"两单最小间隔秒数",防止瞬间行情里连环开单。
五、控制面板:把管理权搬回图上
面板自上而下五块区域:
状态栏:品种名 + 运行状态(运行中 / 已暂停 / 风控触发原因)+ 一键最小化; 账户卡片:余额、净值、浮盈、回撤%、当前点差、今日盈亏,六项数据实时刷新,浮盈红绿变色; 持仓卡片:多空两侧各自的单数、总手数、篮子浮盈,一行看全; 参数卡片:首单手数、间距、间距倍数、手数倍数、最大层数、止盈金额、止盈点数、回撤止损%——共 8 个参数运行时直接改、回车即生效,不用移除 EA 重新加载; 操作区:启动/暂停、立即买入、立即卖出、平多单、平空单、全部平仓六个按钮;图上还会自动画出多空两条均价虚线,价格离均价多远一眼可见。
顺带说一个实现上的小心思:MT5 原生控件并不支持圆角,面板里那些圆角卡片,是源码里 RoundRes() 函数逐像素用抗锯齿算法画出来的位图资源——对界面细节感兴趣的朋友,可以在文末源码里找到这段实现。
六、参数一览:七组输入,一张图看全
GridPro 的全部可调参数分七组,挂图前在 EA 输入窗口配置,运行后核心 8 项还能在面板上继续改。关键默认值先划重点:
| 基础设置 | |
| 手数设置 | |
| 网格设置 | |
| 入场信号 | |
| 止盈设置 | |
| 风控设置 | |
| 面板设置 |

七、上手五步
打开 MT5 的 MetaEditor,新建 EA,粘贴文末完整源码,按 F7 编译(0 错误 0 警告); 确认交易账户为对冲(Hedging)模式(netting 账户会拒绝启动); 把 EA 拖到目标图表,勾选"允许算法交易"; 先在模拟盘跑默认参数:首单 0.01 手、间距 300 点、马丁 ×1.5、单边最多 10 层、篮子止盈 $10、回撤止损 30%,对照上文观察面板各卡片的变化; 熟悉逻辑后,再结合品种波动调整间距与层数;持仓期间随时可在面板里改参数,回车生效。
八、几句实话
它是一套策略框架 + 管理面板,不是赚钱的保证:网格不是无风险策略,马丁加仓在单边趋势里会放大回撤,任何参数都无法保证盈利。面板和风控的作用,是把风险约束在你设定的规则内,把账户状态摆在你眼前——决策与责任,始终在使用者自己。请务必先在模拟账户充分测试,理解每一项参数的含义后再考虑其他用途。
九、完整源码(GridPro_Panel_v1.mq5)
以下为完整源码(含版权声明),复制到 MetaEditor 编译即可使用。参数的含义文中都已讲到,源码里另有中文注释对照阅读:
//+------------------------------------------------------------------+//| GridPro_Panel.mq5 |//| MT5 网格策略 EA + 可视化控制面板(暗色主题,运行时可改参数) |//| 需要【对冲账户】(Hedging) |//| |//| 版权所有 (C) 2026 超哥量化 |//| 微信技术交流:fxquant |//| 微信公众号:超哥量化(回复 GRID 获取最新版本与更多量化工具) |//| |//| 本源码随公众号文章公开,供量化爱好者学习交流使用; |//| 转载请保留本版权声明,请勿用于商业转售。 |//+------------------------------------------------------------------+#property copyright "版权所有 (C) 2026 超哥量化 · 微信 fxquant · 公众号:超哥量化"#property link "微信公众号:超哥量化"#property version "1.00"#property description "可定制网格 EA:多信号入场 / 灵活加仓 / 多重风控 / 实时面板"#include<Trade/Trade.mqh>//--- 枚举 ---------------------------------------------------------enum ENUM_DIR { DIR_BOTH=0, // 双向(多空同时) DIR_BUY=1, // 只做多 DIR_SELL=2 };// 只做空enum ENUM_SIG { SIG_NONE=0, // 无信号(立即入场) SIG_MA=1, // 均线金叉/死叉 SIG_RSI=2, // RSI 超买超卖 SIG_BOLL=3, // 布林带突破回归 SIG_STOCH=4 };// 随机指标enum ENUM_LOTMODE { LOT_FIXED=0, // 固定手数 LOT_MARTI=1, // 倍数递增(马丁) LOT_ARITH=2 };// 等差递增enum ENUM_STEPMODE { STEP_FIXED=0,// 固定点数间距 STEP_ATR=1 };// ATR 动态间距//--- 输入参数 -----------------------------------------------------input group "═══ 基础设置 ═══"input long InpMagic = 20260101; // 魔术号input string InpComment = "GridPro"; // 订单备注input ENUM_DIR InpDir = DIR_BOTH; // 交易方向input int InpSlippage = 30; // 最大滑点(点)input int InpMinInterval = 1; // 两单最小间隔(秒)input group "═══ 手数设置 ═══"input double InpFirstLot = 0.01; // 首单手数input double InpAutoLotBal = 0; // 自动手数: 每N余额放大一倍首单(0=关)input ENUM_LOTMODE InpLotMode = LOT_MARTI; // 加仓手数模式input double InpLotMult = 1.5; // 加仓手数倍数(马丁)input double InpLotAdd = 0.01; // 加仓手数增量(等差)input double InpMaxLot = 1.0; // 单笔最大手数input group "═══ 网格设置 ═══"input ENUM_STEPMODE InpStepMode = STEP_FIXED; // 间距模式input double InpStepPts = 300; // 加仓间距(点)input double InpStepMult = 1.0; // 间距倍数(每层放大)input int InpAtrPeriod = 14; // ATR 周期input ENUM_TIMEFRAMES InpAtrTf = PERIOD_H1; // ATR 周期框架input double InpAtrMult = 1.0; // ATR 间距系数input int InpMaxLevels = 10; // 单边最大层数(含首单)input group "═══ 入场信号(仅作用于首单)═══"input ENUM_SIG InpSignal = SIG_NONE; // 入场信号input ENUM_TIMEFRAMES InpSigTf = PERIOD_M15; // 信号周期input bool InpReverse = false; // 反向信号input int InpMAFast = 10; // MA 快线input int InpMASlow = 30; // MA 慢线input ENUM_MA_METHOD InpMAMethod = MODE_EMA; // MA 类型input int InpRSIPeriod = 14; // RSI 周期input double InpRSIBuy = 30; // RSI 买入线(≤)input double InpRSISell = 70; // RSI 卖出线(≥)input int InpBBPeriod = 20; // 布林周期input double InpBBDev = 2.0; // 布林偏差input int InpStochK = 5; // 随机 %Kinput int InpStochD = 3; // 随机 %Dinput int InpStochSlow = 3; // 随机 Slowinginput double InpStochLow = 20; // 随机超卖线input double InpStochHigh = 80; // 随机超买线input group "═══ 止盈设置(按单边篮子计算)═══"input double InpTPMoney = 10.0; // 篮子止盈金额($, 0=关)input double InpTPPoints = 0; // 均价止盈点数(0=关)input bool InpTrailOn = false; // 启用篮子移动止盈(启用后忽略金额止盈)input double InpTrailStart = 8.0; // 移动止盈启动盈利($)input double InpTrailGap = 3.0; // 移动止盈回撤($)input group "═══ 风控设置 ═══"input double InpBasketSL = 0; // 篮子止损金额($, 0=关)input double InpMaxDDPct = 30.0; // 账户回撤止损%(0=关)input double InpDailyLoss = 0; // 日亏损上限%(0=关)input double InpDailyProfit = 0; // 日盈利目标%(0=关)input double InpMaxSpread = 0; // 最大点差(点, 0=不限)input double InpMaxTotalLots= 0; // 最大总手数(0=不限)input double InpMinMarginLvl= 300; // 最低保证金比例%(低于则停止加仓, 0=关)input bool InpUseTime = false; // 启用交易时段(仅限制首单)input int InpStartHour = 8; // 开始小时(服务器时间)input int InpEndHour = 22; // 结束小时(服务器时间)input int InpFriCloseHr = 0; // 周五几点后全平并停止(0=关)input group "═══ 面板设置 ═══"input bool InpShowPanel = true; // 显示面板input int InpPanelX = 15; // 面板 Xinput int InpPanelY = 25; // 面板 Yinput color InpAccent = C'64,156,255';// 主题强调色input bool InpDrawLines = true; // 在图上绘制均价线//--- 常量 ---------------------------------------------------------#define P "GP_"#define PW 330conststring FONT = "Microsoft YaHei";//--- 配色 ---------------------------------------------------------color C_BG = C'16,20,30';color C_HEAD = C'26,32,48';color C_CARD = C'24,29,42';color C_TEXT = C'214,222,238';color C_MUTED = C'124,134,156';color C_GREEN = C'38,200,130';color C_RED = C'240,84,100';color C_AMBER = C'255,184,64';color C_EDIT = C'34,41,60';//--- 统计结构 -----------------------------------------------------structSideStat {int n;double lots;double profit;double avg;double ext; // 多单: 最低开仓价, 空单: 最高开仓价double lastLot; datetime lastTime; };//--- 全局状态 -----------------------------------------------------CTrade trade;SideStat g_b, g_s;int hFast=INVALID_HANDLE, hSlow=INVALID_HANDLE, hRSI=INVALID_HANDLE, hBB=INVALID_HANDLE, hStoch=INVALID_HANDLE, hATR=INVALID_HANDLE;// 运行时可修改参数(面板可调)double g_firstLot, g_stepPts, g_stepMult, g_lotMult, g_tpMoney, g_tpPts, g_ddPct;int g_maxLevels;bool g_running = true;bool g_minimized = false;string g_halt = ""; // 风控停止原因bool g_haltDaily = false; // 是否为日内风控(次日自动解除)string g_note = "";datetime g_dayKey = 0;double g_dayStartEq= 0;double g_peakB=0, g_peakS=0;datetime g_lastOpen = 0;int g_lotDigits = 2;int g_runStyle = -1;string g_res[];#define BTNW ((PW-16-12)/3)//+------------------------------------------------------------------+//| 初始化 |//+------------------------------------------------------------------+intOnInit(){if((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE)!=ACCOUNT_MARGIN_MODE_RETAIL_HEDGING) { Alert("网格 EA 需要【对冲(Hedging)】账户,当前账户类型不支持!");return INIT_FAILED; } trade.SetExpertMagicNumber(InpMagic); trade.SetDeviationInPoints(InpSlippage); trade.SetTypeFillingBySymbol(_Symbol);double vs=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP); g_lotDigits=0;while(g_lotDigits<8 && MathAbs(vs*MathPow(10,g_lotDigits)-MathRound(vs*MathPow(10,g_lotDigits)))>1e-9) g_lotDigits++; g_firstLot=InpFirstLot; g_stepPts=InpStepPts; g_stepMult=InpStepMult; g_lotMult=InpLotMult; g_tpMoney=InpTPMoney; g_tpPts=InpTPPoints; g_ddPct=InpMaxDDPct; g_maxLevels=InpMaxLevels;// 指标句柄switch(InpSignal) {case SIG_MA: hFast=iMA(_Symbol,InpSigTf,InpMAFast,0,InpMAMethod,PRICE_CLOSE); hSlow=iMA(_Symbol,InpSigTf,InpMASlow,0,InpMAMethod,PRICE_CLOSE);if(hFast==INVALID_HANDLE||hSlow==INVALID_HANDLE) return INIT_FAILED;break;case SIG_RSI: hRSI=iRSI(_Symbol,InpSigTf,InpRSIPeriod,PRICE_CLOSE);if(hRSI==INVALID_HANDLE) return INIT_FAILED;break;case SIG_BOLL: hBB=iBands(_Symbol,InpSigTf,InpBBPeriod,0,InpBBDev,PRICE_CLOSE);if(hBB==INVALID_HANDLE) return INIT_FAILED;break;case SIG_STOCH: hStoch=iStochastic(_Symbol,InpSigTf,InpStochK,InpStochD,InpStochSlow,MODE_SMA,STO_LOWHIGH);if(hStoch==INVALID_HANDLE) return INIT_FAILED;break;default: break; }if(InpStepMode==STEP_ATR) { hATR=iATR(_Symbol,InpAtrTf,InpAtrPeriod);if(hATR==INVALID_HANDLE) return INIT_FAILED; } RollDay();if(InpShowPanel) BuildPanel(); EventSetMillisecondTimer(500);return INIT_SUCCEEDED; }voidOnDeinit(constint reason){ EventKillTimer();if(hFast!=INVALID_HANDLE) IndicatorRelease(hFast);if(hSlow!=INVALID_HANDLE) IndicatorRelease(hSlow);if(hRSI!=INVALID_HANDLE) IndicatorRelease(hRSI);if(hBB!=INVALID_HANDLE) IndicatorRelease(hBB);if(hStoch!=INVALID_HANDLE) IndicatorRelease(hStoch);if(hATR!=INVALID_HANDLE) IndicatorRelease(hATR); ObjectsDeleteAll(0,P);for(int i=0;i<ArraySize(g_res);i++) ResourceFree("::"+g_res[i]); ChartRedraw(); }//+------------------------------------------------------------------+//| 主循环 |//+------------------------------------------------------------------+voidOnTick(){ RollDay(); GetStats(POSITION_TYPE_BUY,g_b); GetStats(POSITION_TYPE_SELL,g_s);// 1) 账户级风控if(CheckGlobalRisk()) { UpdatePanel(); return; }// 2) 篮子出场bool closed=false;if(ManageExit(true,g_b,g_peakB)) closed=true;if(ManageExit(false,g_s,g_peakS)) closed=true;if(closed) { GetStats(POSITION_TYPE_BUY,g_b); GetStats(POSITION_TYPE_SELL,g_s); }// 3) 入场 / 加仓 ManageEntries(true,g_b); ManageEntries(false,g_s); }voidOnTimer(){ UpdatePanel(); DrawLines(); }//+------------------------------------------------------------------+//| 统计 |//+------------------------------------------------------------------+voidGetStats(const ENUM_POSITION_TYPE type,SideStat &s){ s.n=0; s.lots=0; s.profit=0; s.avg=0; s.ext=0; s.lastLot=0; s.lastTime=0;double pv=0;for(int i=PositionsTotal()-1;i>=0;i--) { ulong t=PositionGetTicket(i);if(t==0) continue;if(PositionGetString(POSITION_SYMBOL)!=_Symbol) continue;if(PositionGetInteger(POSITION_MAGIC)!=InpMagic) continue;if((ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE)!=type) continue;double v=PositionGetDouble(POSITION_VOLUME);double p=PositionGetDouble(POSITION_PRICE_OPEN); datetime tm=(datetime)PositionGetInteger(POSITION_TIME); s.n++; s.lots+=v; pv+=p*v; s.profit+=PositionGetDouble(POSITION_PROFIT)+PositionGetDouble(POSITION_SWAP);if(type==POSITION_TYPE_BUY) { if(s.ext==0 || p<s.ext) s.ext=p; }else { if(p>s.ext) s.ext=p; }if(tm>=s.lastTime) { s.lastTime=tm; s.lastLot=v; } }if(s.lots>0) s.avg=pv/s.lots; }//+------------------------------------------------------------------+//| 手数计算 |//+------------------------------------------------------------------+doubleNormLot(double lot){double step=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP);double mn=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN);double mx=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MAX);if(InpMaxLot>0) mx=MathMin(mx,InpMaxLot); lot=MathFloor(lot/step+1e-8)*step; lot=MathMax(mn,MathMin(mx,lot));return NormalizeDouble(lot,g_lotDigits); }doubleFirstLot(){double lot=g_firstLot;if(InpAutoLotBal>0) {double k=MathMax(1.0,MathFloor(AccountInfoDouble(ACCOUNT_BALANCE)/InpAutoLotBal)); lot*=k; }return lot; }doubleCalcLot(int n,double lastLot){double lot;if(n==0) lot=FirstLot();else {switch(InpLotMode) {case LOT_MARTI: lot=lastLot*g_lotMult; break;case LOT_ARITH: lot=FirstLot()+n*InpLotAdd; break;default: lot=FirstLot(); break; } }return NormLot(lot); }//+------------------------------------------------------------------+//| 间距(点) |//+------------------------------------------------------------------+doubleStepDistance(int n){double base=g_stepPts;if(InpStepMode==STEP_ATR && hATR!=INVALID_HANDLE) {double a[]; ArraySetAsSeries(a,true);if(CopyBuffer(hATR,0,1,1,a)==1 && a[0]>0) base=a[0]*InpAtrMult/_Point; }return base*MathPow(g_stepMult,MathMax(0,n-1)); }//+------------------------------------------------------------------+//| 入场信号: 1=买 -1=卖 0=无 |//+------------------------------------------------------------------+boolBuf(int h,int idx,double &arr[],int cnt=3){ ArraySetAsSeries(arr,true);return CopyBuffer(h,idx,0,cnt,arr)==cnt; }intGetSignal(){int sig=0;double a[],b[],c[];switch(InpSignal) {case SIG_NONE: return0;case SIG_MA:if(Buf(hFast,0,a) && Buf(hSlow,0,b)) {if(a[2]<=b[2] && a[1]>b[1]) sig=1;if(a[2]>=b[2] && a[1]<b[1]) sig=-1; }break;case SIG_RSI:if(Buf(hRSI,0,a)) {if(a[1]<=InpRSIBuy) sig=1;if(a[1]>=InpRSISell) sig=-1; }break;case SIG_BOLL:if(Buf(hBB,1,a) && Buf(hBB,2,b)) {double cl=iClose(_Symbol,InpSigTf,1);if(cl<b[1]) sig=1;if(cl>a[1]) sig=-1; }break;case SIG_STOCH:if(Buf(hStoch,0,a) && Buf(hStoch,1,b)) {if(a[1]<=InpStochLow && a[1]>b[1]) sig=1;if(a[1]>=InpStochHigh && a[1]<b[1]) sig=-1; }break; }if(InpReverse) sig=-sig;return sig; }//+------------------------------------------------------------------+//| 过滤条件 |//+------------------------------------------------------------------+boolTimeOK(){ MqlDateTime t; TimeToStruct(TimeCurrent(),t);if(InpFriCloseHr>0 && t.day_of_week==5 && t.hour>=InpFriCloseHr) returnfalse;if(!InpUseTime) returntrue;if(InpStartHour<=InpEndHour) return (t.hour>=InpStartHour && t.hour<InpEndHour);return (t.hour>=InpStartHour || t.hour<InpEndHour); // 跨午夜 }boolEntryAllowed(){if(!g_running) { returnfalse; }if(g_halt!="") { returnfalse; }if(TimeCurrent()-g_lastOpen<InpMinInterval) returnfalse;if(InpMaxSpread>0 && SymbolInfoInteger(_Symbol,SYMBOL_SPREAD)>InpMaxSpread) { g_note="点差过大,暂停开仓"; returnfalse; }double ml=AccountInfoDouble(ACCOUNT_MARGIN_LEVEL);if(InpMinMarginLvl>0 && ml>0 && ml<InpMinMarginLvl) { g_note="保证金比例过低,停止加仓"; returnfalse; }returntrue; }//+------------------------------------------------------------------+//| 开仓 |//+------------------------------------------------------------------+boolOpenOrder(bool isBuy,double lot){if(lot<=0) returnfalse;if(InpMaxTotalLots>0 && g_b.lots+g_s.lots+lot>InpMaxTotalLots+1e-8) { g_note="已达最大总手数"; returnfalse; }double price=isBuy?SymbolInfoDouble(_Symbol,SYMBOL_ASK):SymbolInfoDouble(_Symbol,SYMBOL_BID);double m;if(OrderCalcMargin(isBuy?ORDER_TYPE_BUY:ORDER_TYPE_SELL,_Symbol,lot,price,m) && m>AccountInfoDouble(ACCOUNT_MARGIN_FREE)) { g_note="可用保证金不足"; returnfalse; }bool ok=isBuy?trade.Buy(lot,_Symbol,0,0,0,InpComment):trade.Sell(lot,_Symbol,0,0,0,InpComment);if(ok) { g_lastOpen=TimeCurrent(); g_note=""; }else { g_note="开仓失败: "+IntegerToString(trade.ResultRetcode()); Print(g_note," ",trade.ResultRetcodeDescription()); }return ok; }voidManageEntries(bool isBuy,SideStat &s){if(isBuy && InpDir==DIR_SELL) return;if(!isBuy && InpDir==DIR_BUY) return;if(!EntryAllowed()) return;if(s.n==0) // 首单 {if(!TimeOK()) return;if(InpSignal!=SIG_NONE) {int sig=GetSignal();if(isBuy && sig!=1) return;if(!isBuy && sig!=-1) return; } OpenOrder(isBuy,CalcLot(0,0));return; }if(s.n>=g_maxLevels) return; // 加仓double dist =StepDistance(s.n)*_Point;double price=isBuy?SymbolInfoDouble(_Symbol,SYMBOL_ASK):SymbolInfoDouble(_Symbol,SYMBOL_BID);bool hit=isBuy?(price<=s.ext-dist):(price>=s.ext+dist);if(hit) OpenOrder(isBuy,CalcLot(s.n,s.lastLot)); }//+------------------------------------------------------------------+//| 平仓 |//+------------------------------------------------------------------+voidCloseSide(bool isBuy){ ENUM_POSITION_TYPE type=isBuy?POSITION_TYPE_BUY:POSITION_TYPE_SELL;for(int i=PositionsTotal()-1;i>=0;i--) { ulong t=PositionGetTicket(i);if(t==0) continue;if(PositionGetString(POSITION_SYMBOL)!=_Symbol) continue;if(PositionGetInteger(POSITION_MAGIC)!=InpMagic) continue;if((ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE)!=type) continue; trade.PositionClose(t); } }voidCloseAll(){ CloseSide(true); CloseSide(false); }boolManageExit(bool isBuy,SideStat &s,double &peak){if(s.n==0) { peak=0; returnfalse; }bool close=false;if(InpBasketSL>0 && s.profit<=-InpBasketSL) close=true;elseif(InpTrailOn) {if(s.profit>=InpTrailStart) { peak=MathMax(peak,s.profit);if(s.profit<=peak-InpTrailGap) close=true; } }elseif(g_tpMoney>0 && s.profit>=g_tpMoney) close=true;if(!close && g_tpPts>0) {double bid=SymbolInfoDouble(_Symbol,SYMBOL_BID);double ask=SymbolInfoDouble(_Symbol,SYMBOL_ASK);if(isBuy && bid>=s.avg+g_tpPts*_Point) close=true;if(!isBuy && ask<=s.avg-g_tpPts*_Point) close=true; }if(close) { CloseSide(isBuy); peak=0; returntrue; }returnfalse; }//+------------------------------------------------------------------+//| 风控 |//+------------------------------------------------------------------+voidRollDay(){ datetime d=iTime(_Symbol,PERIOD_D1,0);if(d!=g_dayKey) { g_dayKey=d; g_dayStartEq=AccountInfoDouble(ACCOUNT_EQUITY);if(g_haltDaily) { g_halt=""; g_haltDaily=false; } } }boolCheckGlobalRisk(){double bal=AccountInfoDouble(ACCOUNT_BALANCE);double eq =AccountInfoDouble(ACCOUNT_EQUITY);bool has=(g_b.n+g_s.n)>0;// 周五定时全平if(InpFriCloseHr>0 && has) { MqlDateTime t; TimeToStruct(TimeCurrent(),t);if(t.day_of_week==5 && t.hour>=InpFriCloseHr) { CloseAll(); g_note="周五定时全平"; returntrue; } }if(has && g_ddPct>0 && bal>0 && (bal-eq)/bal*100.0>=g_ddPct) { CloseAll(); g_halt="回撤止损触发"; g_haltDaily=false; g_running=false; Alert("GridPro: 账户回撤达到 ",DoubleToString(g_ddPct,1),"%,已全平并停止");returntrue; }if(g_dayStartEq>0) {if(InpDailyLoss>0 && (g_dayStartEq-eq)/g_dayStartEq*100.0>=InpDailyLoss && g_halt=="") {if(has) CloseAll(); g_halt="日亏损上限"; g_haltDaily=true;return has; }if(InpDailyProfit>0 && (eq-g_dayStartEq)/g_dayStartEq*100.0>=InpDailyProfit && g_halt=="") {if(has) CloseAll(); g_halt="日盈利达标"; g_haltDaily=true;return has; } }returnfalse; }//+------------------------------------------------------------------+//| 图上均价线 |//+------------------------------------------------------------------+voidDrawLine(string name,double price,color clr){ name=P+name;if(!InpDrawLines || price<=0) { ObjectDelete(0,name); return; }if(ObjectFind(0,name)<0) { ObjectCreate(0,name,OBJ_HLINE,0,0,price); ObjectSetInteger(0,name,OBJPROP_STYLE,STYLE_DASH); ObjectSetInteger(0,name,OBJPROP_COLOR,clr); ObjectSetInteger(0,name,OBJPROP_SELECTABLE,false); ObjectSetInteger(0,name,OBJPROP_HIDDEN,true); } ObjectSetDouble(0,name,OBJPROP_PRICE,price); }voidDrawLines(){ SideStat b,s; GetStats(POSITION_TYPE_BUY,b); GetStats(POSITION_TYPE_SELL,s); DrawLine("L_buy",b.avg,C_GREEN); DrawLine("L_sell",s.avg,C_RED); }//+------------------------------------------------------------------+//| ============================ 面板 ============================== |//+------------------------------------------------------------------+voidRegRes(string name){for(int i=0;i<ArraySize(g_res);i++) if(g_res[i]==name) return;int n=ArraySize(g_res); ArrayResize(g_res,n+1); g_res[n]=name; }// 生成抗锯齿圆角矩形位图资源(带 1px 描边)voidRoundRes(string name,int w,int h,int r,color fill,color border){ uint px[]; ArrayResize(px,w*h);double fr=(double)((uint)fill&0xFF), fg=(double)(((uint)fill>>8)&0xFF), fb=(double)(((uint)fill>>16)&0xFF);double br=(double)((uint)border&0xFF), bg=(double)(((uint)border>>8)&0xFF), bb=(double)(((uint)border>>16)&0xFF);double hw=w/2.0, hh=h/2.0;for(int y=0;y<h;y++)for(int x=0;x<w;x++) {double qx=MathAbs(x+0.5-hw)-(hw-r);double qy=MathAbs(y+0.5-hh)-(hh-r);double ox=MathMax(qx,0.0), oy=MathMax(qy,0.0);double d=MathSqrt(ox*ox+oy*oy)+MathMin(MathMax(qx,qy),0.0)-r;double ao=MathMax(0.0,MathMin(1.0,0.5-d));double ai=MathMax(0.0,MathMin(1.0,0.5-(d+1.0))); uint rr=(uint)MathRound(br+(fr-br)*ai); uint gg=(uint)MathRound(bg+(fg-bg)*ai); uint b2=(uint)MathRound(bb+(fb-bb)*ai); px[y*w+x]=((uint)MathRound(ao*255.0)<<24)|(rr<<16)|(gg<<8)|b2; } ResourceCreate("::"+name,px,w,h,0,0,w,COLOR_FORMAT_ARGB_NORMALIZE); RegRes(name); }voidSetBmp(string objName,string resName){ ObjectSetString(0,objName,OBJPROP_BMPFILE,0,"::"+resName); ObjectSetString(0,objName,OBJPROP_BMPFILE,1,"::"+resName); }// 圆角矩形(位图实现,MT5 原生矩形不支持圆角)voidRect(string id,int x,int y,int w,int h,color bg,color border,int r=10){string n=P+id;string res="GP_"+id; RoundRes(res,w,h,r,bg,border); ObjectCreate(0,n,OBJ_BITMAP_LABEL,0,0,0); ObjectSetInteger(0,n,OBJPROP_XDISTANCE,InpPanelX+x); ObjectSetInteger(0,n,OBJPROP_YDISTANCE,InpPanelY+y); SetBmp(n,res); ObjectSetInteger(0,n,OBJPROP_BACK,false); ObjectSetInteger(0,n,OBJPROP_SELECTABLE,false); ObjectSetInteger(0,n,OBJPROP_HIDDEN,true); ObjectSetInteger(0,n,OBJPROP_ZORDER,0); }voidLabel(string id,int x,int y,string txt,color clr,int size=9){string n=P+id; ObjectCreate(0,n,OBJ_LABEL,0,0,0); ObjectSetInteger(0,n,OBJPROP_XDISTANCE,InpPanelX+x); ObjectSetInteger(0,n,OBJPROP_YDISTANCE,InpPanelY+y); ObjectSetInteger(0,n,OBJPROP_COLOR,clr); ObjectSetInteger(0,n,OBJPROP_FONTSIZE,size); ObjectSetString(0,n,OBJPROP_FONT,FONT); ObjectSetString(0,n,OBJPROP_TEXT,txt); ObjectSetInteger(0,n,OBJPROP_SELECTABLE,false); ObjectSetInteger(0,n,OBJPROP_HIDDEN,true); ObjectSetInteger(0,n,OBJPROP_ZORDER,1); }voidButton(string id,int x,int y,int w,int h,string txt,color bg,color fg=clrWhite){ Rect("r_bb_"+id,x,y,w,h,bg,bg,8); // 圆角底板string n=P+id; ObjectCreate(0,n,OBJ_BUTTON,0,0,0); // 原生按钮内缩 3px 叠在底板上,负责点击 ObjectSetInteger(0,n,OBJPROP_XDISTANCE,InpPanelX+x+3); ObjectSetInteger(0,n,OBJPROP_YDISTANCE,InpPanelY+y+3); ObjectSetInteger(0,n,OBJPROP_XSIZE,w-6); ObjectSetInteger(0,n,OBJPROP_YSIZE,h-6); ObjectSetInteger(0,n,OBJPROP_BGCOLOR,bg); ObjectSetInteger(0,n,OBJPROP_BORDER_COLOR,bg); ObjectSetInteger(0,n,OBJPROP_COLOR,fg); ObjectSetInteger(0,n,OBJPROP_FONTSIZE,9); ObjectSetString(0,n,OBJPROP_FONT,FONT); ObjectSetString(0,n,OBJPROP_TEXT,txt); ObjectSetInteger(0,n,OBJPROP_SELECTABLE,false); ObjectSetInteger(0,n,OBJPROP_HIDDEN,true); ObjectSetInteger(0,n,OBJPROP_ZORDER,2); }voidEditBox(string id,int x,int y,int w,int h,string txt){ Rect("r_eb_"+id,x,y,w,h,C_EDIT,InpAccent,7); // 圆角描边底板string n=P+id; ObjectCreate(0,n,OBJ_EDIT,0,0,0); ObjectSetInteger(0,n,OBJPROP_XDISTANCE,InpPanelX+x+3); ObjectSetInteger(0,n,OBJPROP_YDISTANCE,InpPanelY+y+3); ObjectSetInteger(0,n,OBJPROP_XSIZE,w-6); ObjectSetInteger(0,n,OBJPROP_YSIZE,h-6); ObjectSetInteger(0,n,OBJPROP_BGCOLOR,C_EDIT); ObjectSetInteger(0,n,OBJPROP_BORDER_COLOR,C_EDIT); ObjectSetInteger(0,n,OBJPROP_COLOR,C_TEXT); ObjectSetInteger(0,n,OBJPROP_ALIGN,ALIGN_CENTER); ObjectSetInteger(0,n,OBJPROP_FONTSIZE,9); ObjectSetString(0,n,OBJPROP_FONT,FONT); ObjectSetString(0,n,OBJPROP_TEXT,txt); ObjectSetInteger(0,n,OBJPROP_SELECTABLE,false); ObjectSetInteger(0,n,OBJPROP_HIDDEN,true); ObjectSetInteger(0,n,OBJPROP_ZORDER,2); }voidParamRow(string key,string title,int lx,int ex,int y,int ew){ Label("t_"+key,lx,y+5,title,C_MUTED,8); EditBox("E_"+key,ex,y,ew,24,""); }voidBuildPanel(){ ObjectsDeleteAll(0,P+"E_"); ObjectsDeleteAll(0,P+"b_"); ObjectsDeleteAll(0,P+"t_"); ObjectsDeleteAll(0,P+"v_"); ObjectsDeleteAll(0,P+"r_"); g_runStyle=-1;int H=g_minimized?36:408; Rect("r_bg",0,0,PW,H,C_BG,InpAccent,16); Rect("r_head",3,3,PW-6,30,C_HEAD,C_HEAD,13); Rect("r_bar",12,11,4,14,InpAccent,InpAccent,2); Label("t_title",24,9,"◆ GRID PRO "+_Symbol,C_TEXT,10); Label("v_status",196,11,"● 运行中",C_GREEN,8); Button("b_min",PW-38,4,28,28,g_minimized?"+":"–",C_EDIT,C_TEXT);if(g_minimized) { UpdatePanel(); ChartRedraw(); return; }// 账户卡片 Rect("r_c1",8,42,PW-16,68,C_CARD,C_CARD); Label("t_bal",16,50,"余额",C_MUTED,8); Label("v_bal",54,49,"-",C_TEXT,9); Label("t_eq",172,50,"净值",C_MUTED,8); Label("v_eq",210,49,"-",C_TEXT,9); Label("t_pl",16,72,"浮盈",C_MUTED,8); Label("v_pl",54,71,"-",C_TEXT,9); Label("t_dd",172,72,"回撤",C_MUTED,8); Label("v_dd",210,71,"-",C_TEXT,9); Label("t_sp",16,94,"点差",C_MUTED,8); Label("v_sp",54,93,"-",C_TEXT,9); Label("t_day",172,94,"今日",C_MUTED,8); Label("v_day",210,93,"-",C_TEXT,9);// 持仓卡片 Rect("r_c2",8,116,PW-16,46,C_CARD,C_CARD); Label("v_b",16,122,"-",C_GREEN,9); Label("v_s",16,142,"-",C_RED,9);// 参数卡片 Rect("r_c3",8,168,PW-16,142,C_CARD,C_CARD); Label("t_ptitle",16,174,"参数(回车生效,实时可改)",InpAccent,8); ParamRow("lot", "首单手数",16, 84,194,64); ParamRow("step", "间距(点)",172,238,194,72); ParamRow("smult","间距倍数",16, 84,222,64); ParamRow("lmult","手数倍数",172,238,222,72); ParamRow("maxlv","最大层数",16, 84,250,64); ParamRow("tp", "止盈($)", 172,238,250,72); ParamRow("tppts","止盈(点)",16, 84,278,64); ParamRow("dd", "回撤止损%",172,238,278,72); SyncEdits();// 按钮int bw=BTNW; Button("b_run", 8, 318,bw,30,"",C_GREEN); Button("b_buy", 8+bw+6, 318,bw,30,"立即买入",C'30,110,200'); Button("b_sell",8+2*(bw+6),318,bw,30,"立即卖出",C'200,70,90'); Button("b_cb", 8, 354,bw,30,"平多单",C_EDIT,C_TEXT); Button("b_cs", 8+bw+6, 354,bw,30,"平空单",C_EDIT,C_TEXT); Button("b_ca", 8+2*(bw+6),354,bw,30,"全部平仓",C'150,50,60'); Label("v_note",14,390,"",C_AMBER,8); UpdatePanel(); ChartRedraw(); }voidSetT(string id,string txt,color clr=clrNONE){string n=P+id; ObjectSetString(0,n,OBJPROP_TEXT,txt);if(clr!=clrNONE) ObjectSetInteger(0,n,OBJPROP_COLOR,clr); }color PnlColor(double v){ return v>0?C_GREEN:(v<0?C_RED:C_TEXT); }voidSyncEdits(){ ObjectSetString(0,P+"E_lot", OBJPROP_TEXT,DoubleToString(g_firstLot,g_lotDigits)); ObjectSetString(0,P+"E_step", OBJPROP_TEXT,DoubleToString(g_stepPts,0)); ObjectSetString(0,P+"E_smult",OBJPROP_TEXT,DoubleToString(g_stepMult,2)); ObjectSetString(0,P+"E_lmult",OBJPROP_TEXT,DoubleToString(g_lotMult,2)); ObjectSetString(0,P+"E_maxlv",OBJPROP_TEXT,IntegerToString(g_maxLevels)); ObjectSetString(0,P+"E_tp", OBJPROP_TEXT,DoubleToString(g_tpMoney,2)); ObjectSetString(0,P+"E_tppts",OBJPROP_TEXT,DoubleToString(g_tpPts,0)); ObjectSetString(0,P+"E_dd", OBJPROP_TEXT,DoubleToString(g_ddPct,1)); }voidUpdatePanel(){if(!InpShowPanel) return; SideStat b,s; GetStats(POSITION_TYPE_BUY,b); GetStats(POSITION_TYPE_SELL,s);double bal=AccountInfoDouble(ACCOUNT_BALANCE);double eq =AccountInfoDouble(ACCOUNT_EQUITY);double pl =b.profit+s.profit;double dd =(bal>0)?MathMax(0.0,(bal-eq)/bal*100.0):0.0;double day=eq-g_dayStartEq;// 状态if(g_halt!="") SetT("v_status","● "+g_halt,C_RED);elseif(!g_running) SetT("v_status","● 已暂停",C_AMBER);else SetT("v_status","● 运行中",C_GREEN);if(g_minimized) return; SetT("v_bal",DoubleToString(bal,2)); SetT("v_eq", DoubleToString(eq,2)); SetT("v_pl", (pl>=0?"+":"")+DoubleToString(pl,2),PnlColor(pl)); SetT("v_dd", DoubleToString(dd,2)+"%",dd>=10?C_RED:C_TEXT); SetT("v_sp", IntegerToString((int)SymbolInfoInteger(_Symbol,SYMBOL_SPREAD))); SetT("v_day",(day>=0?"+":"")+DoubleToString(day,2),PnlColor(day)); SetT("v_b","▲ 多单 "+IntegerToString(b.n)+" 单 | "+DoubleToString(b.lots,g_lotDigits)+" 手 | "+ (b.profit>=0?"+":"")+DoubleToString(b.profit,2)); SetT("v_s","▼ 空单 "+IntegerToString(s.n)+" 单 | "+DoubleToString(s.lots,g_lotDigits)+" 手 | "+ (s.profit>=0?"+":"")+DoubleToString(s.profit,2));// 运行按钮(圆角底板随状态换色)int rs=g_running?1:0; color rc=g_running?C_AMBER:C_GREEN;if(rs!=g_runStyle) { g_runStyle=rs; RoundRes("GP_r_bb_b_run",BTNW,30,8,rc,rc); SetBmp(P+"r_bb_b_run","GP_r_bb_b_run"); ObjectSetInteger(0,P+"b_run",OBJPROP_BGCOLOR,rc); ObjectSetInteger(0,P+"b_run",OBJPROP_BORDER_COLOR,rc); } ObjectSetString(0,P+"b_run",OBJPROP_TEXT,g_running?"⏸ 暂停":"▶ 启动"); ObjectSetInteger(0,P+"b_run",OBJPROP_COLOR,C'20,20,20'); SetT("v_note",g_note); ChartRedraw(); }//+------------------------------------------------------------------+//| 面板事件 |//+------------------------------------------------------------------+voidManualOpen(bool isBuy){ GetStats(POSITION_TYPE_BUY,g_b); GetStats(POSITION_TYPE_SELL,g_s); SideStat s=isBuy?g_b:g_s; OpenOrder(isBuy,CalcLot(s.n,s.lastLot)); }voidApplyEdit(string name){string key=StringSubstr(name,StringLen(P)+2); // 去掉 "GP_E_"double v=StringToDouble(ObjectGetString(0,name,OBJPROP_TEXT));if(key=="lot" && v>0) g_firstLot=v;if(key=="step" && v>0) g_stepPts=v;if(key=="smult" && v>0) g_stepMult=v;if(key=="lmult" && v>0) g_lotMult=v;if(key=="maxlv" && v>=1) g_maxLevels=(int)v;if(key=="tp" && v>=0) g_tpMoney=v;if(key=="tppts" && v>=0) g_tpPts=v;if(key=="dd" && v>=0) g_ddPct=v; SyncEdits(); ChartRedraw(); }voidOnChartEvent(constint id,constlong &lparam,constdouble &dparam,conststring &sparam){if(!InpShowPanel) return;if(id==CHARTEVENT_OBJECT_ENDEDIT && StringFind(sparam,P+"E_")==0) { ApplyEdit(sparam); return; }if(id!=CHARTEVENT_OBJECT_CLICK) return;if(StringFind(sparam,P+"b_")!=0) return;if(sparam==P+"b_min") { g_minimized=!g_minimized; BuildPanel(); return; }if(sparam==P+"b_run") { g_running=!g_running;if(g_running) { g_halt=""; g_haltDaily=false; g_note=""; } }elseif(sparam==P+"b_buy") ManualOpen(true);elseif(sparam==P+"b_sell") ManualOpen(false);elseif(sparam==P+"b_cb") CloseSide(true);elseif(sparam==P+"b_cs") CloseSide(false);elseif(sparam==P+"b_ca") CloseAll(); ObjectSetInteger(0,sparam,OBJPROP_STATE,false); // 按钮弹起 UpdatePanel(); }//+------------------------------------------------------------------+🎁 获取方式
完整源码就在上文第九节,复制到 MetaEditor 按 F7 编译即可使用——不设门槛,无需任何形式的"提取"或"解密"。
觉得有用的话,欢迎关注公众号「超哥量化」:GridPro 后续版本的升级动态、参数进阶玩法,以及更多量化策略源码与交易工具,都会第一时间在那里发布。
回复关键词 FTA,还可以获取另一款 MT5 图上快速交易工具 Fast Trade Assistant(市价下单、鼠标挂单、批量平仓、仓位保护一应俱全)。
⚠️ 风险提示
杠杆交易(如外汇、贵金属、差价合约)具有高风险,可能导致损失全部本金,请充分了解相关风险后再参与交易。 网格策略在单边行情中会持续加仓,马丁类手数递增会同步放大仓位与回撤,极端行情下可能造成较大亏损,请务必理解策略原理并配置风控后再使用。 本文源码仅供技术交流与学习参考,不构成任何投资建议,不生成交易信号;所有交易决策由使用者自行做出。 新策略请先在模拟账户(Demo)上充分测试,确认逻辑与风险可控后再考虑用于实盘;任何参数设置都无法保证盈利。 过往表现不代表未来结果,历史回测结论存在失效风险。
欢迎关注公众号"超哥量化",获取更多量化交易技术分析内容与智能交易工具资讯。如需量化学习资料、技术交流或个性化指标定制服务,可通过公众号留言与我们取得联系。软件定制开发,请联系技术支持。
