EA订单管理9大核心函数封装:开仓/平仓/移损/追踪一站式搞定
EA订单管理9大核心函数封装:开仓/平仓/移损/追踪一站式搞定
很多EA开发者都有过这样的经历——写策略逻辑只花了2小时,调订单操作却花了一整天。OrderSend参数总是配不对、错误码看不懂、网络超时怎么办、平仓平不掉、止损止盈改不了、手数精度不对……订单操作看起来简单,实则充满坑。
风险提示:本文内容仅为技术工具分享与原理探讨,不构成任何投资建议。本网站仅提供软件开发技术服务,不涉及任何交易平台运营或经纪业务。所有交易行为均由用户自行决策并承担相应风险。
订单操作是EA的"基础设施",不应该每个项目都从零造轮子。一套好的订单管理函数库,能让你把90%的精力放在策略逻辑上。本文把经过上百个EA项目验证的9大核心订单管理函数全部开源,每个函数都包含完整的错误处理、智能重试、滑点控制、日志输出,直接#include就能用。

一、OrderSend的8个常见坑
在讲函数封装之前,先把OrderSend常见的8个坑列出来。看看你踩过几个,全中的请举手。
| 坑号 | 坑描述 | 典型错误码 |
|---|---|---|
| 坑1 | ZeroMemory()忘记调用——结构体里有垃圾数据 | 10014(无效参数) |
| 坑2 | 价格精度不对——NormalizeDouble忘了用 | 10015(无效价格) |
| 坑3 | 手数精度不对——不同品种步长不同 | 10016(无效手数) |
| 坑4 | 止损止盈方向反了 | 10016(无效止损止盈) |
| 坑5 | 滑点设太大/太小——deviation参数不设 | 10020(价格已变) |
| 坑6 | 不检查返回值——错在哪都不知道 | - |
| 坑7 | 没有重试机制——网络抖动白白错过 | 10021(请求被拒绝) |
| 坑8 | 平仓价格用错——多单用Bid、空单用Ask搞反 | 滑点损失(无报错但亏钱) |
风险:坑8比较隐蔽——平仓价格用反了不会报错,因为价格是有效的,只是方向反了。比如平多单的时候用了Ask价而不是Bid价,结果就是你以为平仓成功了,实际上多付出了一个点差的成本。单笔看起来不多,但交易次数多了就是一笔可观的损失,而且很难发现。
二、9大核心函数逐一拆解
下面开始逐个讲解9个核心函数。每个函数都有明确的输入输出、内部逻辑说明和完整代码实现,你可以直接拷贝到自己的函数库中使用。

函数1:OpenBuy / OpenSell —— 市价开仓
基础且常用的函数。别看简单,里面有很多细节:获取当前市价、计算SL/TP价格、精度对齐、构造请求、发送、重试、日志,一步都不能少。
// 市价开多单
// 返回值:成功返回ticket,失败返回-1
ulong OpenBuy(string symbol, double lots, double slPips, double tpPips,
int slippage, string comment, ulong magic)
{
MqlTradeRequest request;
MqlTradeResult result;
ZeroMemory(request);
ZeroMemory(result);
// 获取当前价格
double ask = SymbolInfoDouble(symbol, SYMBOL_ASK);
double point = SymbolInfoDouble(symbol, SYMBOL_POINT);
int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS);
// 计算止损止盈价格(注意方向:多单止损在下方,止盈在上方)
double sl = (slPips > 0) ? NormalizeDouble(ask - slPips * point, digits) : 0;
double tp = (tpPips > 0) ? NormalizeDouble(ask + tpPips * point, digits) : 0;
// 构造请求
request.action = TRADE_ACTION_DEAL;
request.symbol = symbol;
request.volume = NormalizeLots(symbol, lots); // 手数精度对齐
request.type = ORDER_TYPE_BUY;
request.price = NormalizeDouble(ask, digits);
request.sl = sl;
request.tp = tp;
request.deviation= slippage;
request.magic = magic;
request.comment = comment;
request.type_filling = ORDER_FILLING_FOK;
// 发送请求(带3次重试)
for(int i = 0; i < 3; i++)
{
if(OrderSend(request, result))
{
Print("开多成功 ticket=", result.order, " 价格=", result.price);
return result.order;
}
// 只有网络类错误才重试
if(result.retcode != 10020 && result.retcode != 10021)
break;
Sleep(100); // 等100ms再重试
}
Print("开多失败 retcode=", result.retcode, " ", result.comment);
return -1;
}
操作参考:使用建议:开仓函数的slippage(滑点)参数要根据品种特性来设定。波动大的品种(如黄金、镑日)滑点要设大一点(30-50点),波动小的品种(如主要直盘)可以设小一点(10-20点)。滑点设太小,行情波动大的时候根本成交不了;设太大又可能成交在很差的价位。根据历史滑点数据来设定比较合理。
函数2:ClosePosition —— 平指定仓位
平仓比开仓容易踩坑,因为方向容易搞反。记住口诀:平多单用Bid价,平空单用Ask价。搞反了就会多付一个点差。
// 平指定仓位
// 返回值:成功返回true,失败返回false
bool ClosePosition(ulong ticket, double closeLots = 0)
{
// 获取仓位信息
if(!PositionSelectByTicket(ticket))
{
Print("仓位不存在 ticket=", ticket);
return false;
}
string symbol = PositionGetString(POSITION_SYMBOL);
double totalLots = PositionGetDouble(POSITION_VOLUME);
ENUM_POSITION_TYPE posType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
double lots = (closeLots > 0) ? min(closeLots, totalLots) : totalLots;
// 确定平仓方向和价格
MqlTradeRequest request;
MqlTradeResult result;
ZeroMemory(request);
ZeroMemory(result);
double price;
if(posType == POSITION_TYPE_BUY)
{
request.type = ORDER_TYPE_SELL;
price = SymbolInfoDouble(symbol, SYMBOL_BID); // 平多单用Bid
}
else
{
request.type = ORDER_TYPE_BUY;
price = SymbolInfoDouble(symbol, SYMBOL_ASK); // 平空单用Ask
}
request.action = TRADE_ACTION_DEAL;
request.symbol = symbol;
request.volume = NormalizeLots(symbol, lots);
request.price = NormalizeDouble(price, _Digits);
request.position = ticket;
request.deviation= 30;
request.magic = PositionGetInteger(POSITION_MAGIC);
request.type_filling = ORDER_FILLING_FOK;
if(OrderSend(request, result))
{
Print("平仓成功 ticket=", ticket, " 手数=", lots);
return true;
}
Print("平仓失败 ticket=", ticket, " retcode=", result.retcode);
return false;
}
知识点:为什么平多单用Bid价、平空单用Ask价?因为点差的存在:Bid是买价(交易商买入价,也就是你卖出时的价格),Ask是卖价(交易商卖出价,也就是你买入时的价格)。你开多单时用Ask价买入,平仓(卖出)时用Bid价,中间的差价就是点差成本。方向搞反了的话,等于你用更高的价格买入、更低的价格卖出,损失两倍点差。
函数3:CloseAllBuy / CloseAllSell / CloseAll —— 批量平仓
批量平仓有一个常见的坑:从前往后遍历,每平一个仓位,数组长度就减少一个,导致后面的索引错位。正确的做法是从后往前遍历,或者每次都从0开始重新遍历。
// 平掉指定品种的所有仓位
// 返回值:成功平仓的数量
int CloseAll(string symbol = "", ulong magic = 0)
{
int count = 0;
// 从后往前遍历,避免索引错位
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(ticket == 0) continue;
if(!PositionSelectByTicket(ticket)) continue;
// 品种过滤
if(symbol != "" && PositionGetString(POSITION_SYMBOL) != symbol)
continue;
// 魔术数字过滤
if(magic > 0 && (ulong)PositionGetInteger(POSITION_MAGIC) != magic)
continue;
if(ClosePosition(ticket))
count++;
}
return count;
}
// 平所有多单
int CloseAllBuy(string symbol = "", ulong magic = 0)
{
int count = 0;
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(ticket == 0) continue;
if(!PositionSelectByTicket(ticket)) continue;
if(PositionGetInteger(POSITION_TYPE) != POSITION_TYPE_BUY) continue;
if(symbol != "" && PositionGetString(POSITION_SYMBOL) != symbol) continue;
if(magic > 0 && (ulong)PositionGetInteger(POSITION_MAGIC) != magic) continue;
if(ClosePosition(ticket)) count++;
}
return count;
}
重点:批量操作一定要从后往前遍历。这个坑不仅仅存在于订单操作中,任何"遍历+删除"的场景都适用。原理很简单:删除索引3的元素后,原来索引4的元素就变成了索引3,如果你继续遍历索引4,就跳过了一个元素。从后往前遍历就不会有这个问题——你删的元素都在当前索引的前面,不影响后面还没遍历到的。
函数4:ModifyPosition —— 修改止损止盈
动态调整SL/TP是很多策略的需求——保本止损、分批止盈、移动止损都离不开它。注意修改止损止盈的价格必须有效,不能在当前价的反方向。
// 修改仓位的止损止盈
// 传入0表示不修改该项
bool ModifyPosition(ulong ticket, double newSL = 0, double newTP = 0)
{
if(!PositionSelectByTicket(ticket))
{
Print("仓位不存在 ticket=", ticket);
return false;
}
string symbol = PositionGetString(POSITION_SYMBOL);
int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS);
ENUM_POSITION_TYPE posType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
double currentSL = PositionGetDouble(POSITION_SL);
double currentTP = PositionGetDouble(POSITION_TP);
// 如果传0,保持原值
double sl = (newSL > 0) ? NormalizeDouble(newSL, digits) : currentSL;
double tp = (newTP > 0) ? NormalizeDouble(newTP, digits) : currentTP;
// 价格没变就不用改
if(sl == currentSL && tp == currentTP)
return true;
MqlTradeRequest request;
MqlTradeResult result;
ZeroMemory(request);
ZeroMemory(result);
request.action = TRADE_ACTION_SLTP;
request.symbol = symbol;
request.position = ticket;
request.sl = sl;
request.tp = tp;
if(OrderSend(request, result))
{
Print("修改成功 ticket=", ticket, " SL=", sl, " TP=", tp);
return true;
}
Print("修改失败 ticket=", ticket, " retcode=", result.retcode);
return false;
}
进阶原理:TRADE_ACTION_SLTP 是专门用来修改止损止盈的操作类型。它和 TRADE_ACTION_MODIFY 的区别在于:SLTP专门用于改SL/TP,不需要传price参数;而MODIFY是通用修改操作,需要传完整的参数。使用SLTP操作更轻量、更安全,因为它只能修改止损止盈,不会误改其他参数。这是MQL5相对于MQL4的一个改进——操作类型更细分,出错的可能性更小。
函数5:TrailingStop —— 移动止损
移动止损是保护利润的利器。实现原理是:每根新K线检查浮盈是否超过触发线,超过则把止损向盈利方向移动。这里给一个常用的固定点数追踪版本。
// 固定点数移动止损
// trailPips: 追踪止损点数(止损距离当前价格的距离)
// triggerPips: 触发点数(浮盈达到多少点才启动追踪)
void TrailingStop(string symbol, ulong magic, double trailPips, double triggerPips = 0)
{
double point = SymbolInfoDouble(symbol, SYMBOL_POINT);
int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS);
double bid = SymbolInfoDouble(symbol, SYMBOL_BID);
double ask = SymbolInfoDouble(symbol, SYMBOL_ASK);
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(ticket == 0) continue;
if(!PositionSelectByTicket(ticket)) continue;
if(PositionGetString(POSITION_SYMBOL) != symbol) continue;
if((ulong)PositionGetInteger(POSITION_MAGIC) != magic) continue;
double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
double currentSL = PositionGetDouble(POSITION_SL);
ENUM_POSITION_TYPE posType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
if(posType == POSITION_TYPE_BUY)
{
// 多单:计算浮盈
double profitPips = (bid - openPrice) / point;
// 达到触发条件才启动
if(profitPips < triggerPips) continue;
// 计算新的止损位
double newSL = NormalizeDouble(bid - trailPips * point, digits);
// 只有新止损比旧止损高(更有利)才修改
if(newSL > currentSL || currentSL == 0)
ModifyPosition(ticket, newSL, 0);
}
else // POSITION_TYPE_SELL
{
double profitPips = (openPrice - ask) / point;
if(profitPips < triggerPips) continue;
double newSL = NormalizeDouble(ask + trailPips * point, digits);
if(newSL < currentSL || currentSL == 0)
ModifyPosition(ticket, newSL, 0);
}
}
}
操作参考:移动止损的调用时机很重要。不要每tick都调用,那样太频繁了,而且容易频繁修改订单导致被交易商限流。建议在新K线产生时调用一次就够了。对于波段策略,甚至可以在大周期(H1或H4)的新K线时才检查移动止损。移动止损的目的是保护利润,不是追求极致精准,频率太高反而容易被毛刺扫掉。
函数6-7:PlacePending / DeletePending —— 挂单与撤单
挂单是突破类、回调类策略的常用操作。MT5支持4种挂单类型:BuyLimit(低于现价买入)、SellLimit(高于现价卖出)、BuyStop(高于现价买入)、SellStop(低于现价卖出)。
// 放置挂单
// orderType: ORDER_TYPE_BUY_LIMIT / ORDER_TYPE_SELL_LIMIT /
// ORDER_TYPE_BUY_STOP / ORDER_TYPE_SELL_STOP
ulong PlacePending(string symbol, ENUM_ORDER_TYPE orderType, double lots,
double price, double slPips, double tpPips,
datetime expiration = 0, string comment = "", ulong magic = 0)
{
MqlTradeRequest request;
MqlTradeResult result;
ZeroMemory(request);
ZeroMemory(result);
double point = SymbolInfoDouble(symbol, SYMBOL_POINT);
int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS);
double normPrice = NormalizeDouble(price, digits);
// 计算止损止盈
double sl = 0, tp = 0;
if(orderType == ORDER_TYPE_BUY_LIMIT || orderType == ORDER_TYPE_BUY_STOP)
{
sl = (slPips > 0) ? NormalizeDouble(normPrice - slPips * point, digits) : 0;
tp = (tpPips > 0) ? NormalizeDouble(normPrice + tpPips * point, digits) : 0;
}
else
{
sl = (slPips > 0) ? NormalizeDouble(normPrice + slPips * point, digits) : 0;
tp = (tpPips > 0) ? NormalizeDouble(normPrice - tpPips * point, digits) : 0;
}
request.action = TRADE_ACTION_PENDING;
request.symbol = symbol;
request.volume = NormalizeLots(symbol, lots);
request.type = orderType;
request.price = normPrice;
request.sl = sl;
request.tp = tp;
request.magic = magic;
request.comment = comment;
if(expiration > 0)
{
request.type_time = ORDER_TIME_SPECIFIED;
request.expiration = expiration;
}
if(OrderSend(request, result))
{
Print("挂单成功 ticket=", result.order);
return result.order;
}
Print("挂单失败 retcode=", result.retcode, " ", result.comment);
return -1;
}
// 撤销挂单
bool DeletePending(ulong ticket)
{
MqlTradeRequest request;
MqlTradeResult result;
ZeroMemory(request);
ZeroMemory(result);
request.action = TRADE_ACTION_REMOVE;
request.order = ticket;
if(OrderSend(request, result))
{
Print("撤单成功 ticket=", ticket);
return true;
}
Print("撤单失败 ticket=", ticket, " retcode=", result.retcode);
return false;
}
风险:挂单有一个常见的坑:挂单价格距离当前价太近,被交易商拒绝。每个品种都有最小挂单距离(stops level),挂单位必须在当前价的stops level之外。可以用 SymbolInfoInteger(symbol, SYMBOL_TRADE_STOPS_LEVEL) 获取这个值(单位是点)。如果挂单价离当前价太近,要么调整挂单价,要么等价格走出去之后再挂。否则订单会一直失败,策略逻辑就卡住了。
函数8:GetPositionCount / GetPositionProfit —— 持仓查询
持仓查询函数是风控模块的基础。最大持仓数限制、每日盈亏限额检查、多空对冲检测都离不开它。
// 获取指定条件的持仓数量
int GetPositionCount(string symbol = "", ulong magic = 0,
ENUM_POSITION_TYPE posType = POSITION_TYPE_BUY)
{
int count = 0;
for(int i = 0; i < PositionsTotal(); i++)
{
ulong ticket = PositionGetTicket(i);
if(ticket == 0) continue;
if(!PositionSelectByTicket(ticket)) continue;
if(symbol != "" && PositionGetString(POSITION_SYMBOL) != symbol)
continue;
if(magic > 0 && (ulong)PositionGetInteger(POSITION_MAGIC) != magic)
continue;
if(PositionGetInteger(POSITION_TYPE) == posType)
count++;
}
return count;
}
// 获取指定条件的总浮盈(账户货币)
double GetPositionProfit(string symbol = "", ulong magic = 0)
{
double totalProfit = 0;
for(int i = 0; i < PositionsTotal(); i++)
{
ulong ticket = PositionGetTicket(i);
if(ticket == 0) continue;
if(!PositionSelectByTicket(ticket)) continue;
if(symbol != "" && PositionGetString(POSITION_SYMBOL) != symbol)
continue;
if(magic > 0 && (ulong)PositionGetInteger(POSITION_MAGIC) != magic)
continue;
totalProfit += PositionGetDouble(POSITION_PROFIT);
}
return totalProfit;
}
函数9:CheckMarginEnough —— 保证金检查
开仓前先检查保证金够不够,是一个好习惯。不然OrderSend返回10017(保证金不足),策略直接卡住。用OrderCalcMargin可以预先计算所需保证金。
// 检查保证金是否足够
// marginRatio: 可用保证金比例(0.5表示保留50%可用保证金后还够就返回true)
bool CheckMarginEnough(string symbol, ENUM_ORDER_TYPE orderType,
double lots, double marginRatio = 0.8)
{
double marginRequired = 0;
double price = 0;
// 获取当前价格
if(orderType == ORDER_TYPE_BUY || orderType == ORDER_TYPE_BUY_LIMIT ||
orderType == ORDER_TYPE_BUY_STOP)
price = SymbolInfoDouble(symbol, SYMBOL_ASK);
else
price = SymbolInfoDouble(symbol, SYMBOL_BID);
// 预计算所需保证金
if(!OrderCalcMargin(orderType, symbol, lots, price, marginRequired))
{
Print("保证金计算失败 error=", GetLastError());
return false;
}
double freeMargin = AccountInfoDouble(ACCOUNT_FREEMARGIN);
// 可用保证金需要大于所需保证金的指定比例
bool enough = (freeMargin * marginRatio) > marginRequired;
if(!enough)
{
Print("保证金不足 需要=", marginRequired, " 可用=", freeMargin);
}
return enough;
}
知识点:OrderCalcMargin 是MQL5的一个实用函数,它可以在不实际下单的情况下,预先计算开仓需要多少保证金。这样你就可以在开仓前判断保证金够不够,避免开仓失败。建议在保证金检查时预留20%-30%的安全余量,因为行情波动时价格在变,实际所需保证金可能和预估值有差异。预留余量可以确保不会因为价格小幅波动导致开仓失败。
三、如何在你的EA中使用
使用方法非常简单,四步搞定。不用改代码,复制过去#include就能用。
- 把 OrderManager.mqh 放到 MQL5/Include 目录下
- 在EA中 #include 引入头文件
- 设置全局参数(魔术数字、最大重试次数、默认滑点)
- 直接调用函数,就这么简单
一个最小示例:
#include// 全局参数 #define MAGIC_NUMBER 20260713 #define LOT_SIZE 0.1 #define STOP_LOSS 200 // 点 #define TAKE_PROFIT 400 // 点 #define SLIPPAGE 30 // 点 void OnTick() { // 新K线才检查信号 if(!IsNewBar()) return; // 有持仓就不新开 if(GetPositionCount(_Symbol, MAGIC_NUMBER, POSITION_TYPE_BUY) > 0 || GetPositionCount(_Symbol, MAGIC_NUMBER, POSITION_TYPE_SELL) > 0) return; if(ShouldBuySignal()) { if(CheckMarginEnough(_Symbol, ORDER_TYPE_BUY, LOT_SIZE)) { OpenBuy(_Symbol, LOT_SIZE, STOP_LOSS, TAKE_PROFIT, SLIPPAGE, "MyStrategy", MAGIC_NUMBER); } } else if(ShouldSellSignal()) { if(CheckMarginEnough(_Symbol, ORDER_TYPE_SELL, LOT_SIZE)) { OpenSell(_Symbol, LOT_SIZE, STOP_LOSS, TAKE_PROFIT, SLIPPAGE, "MyStrategy", MAGIC_NUMBER); } } // 移动止损 TrailingStop(_Symbol, MAGIC_NUMBER, 150, 200); }
重点:把订单操作封装成函数库之后,你的策略代码会变得非常清晰——信号判断是信号判断,订单操作是订单操作,各司其职。以前写EA的时候,策略逻辑里夹杂着一大堆OrderSend、错误处理、重试逻辑,看得人头疼。现在只需要调用OpenBuy/ClosePosition这些简洁的函数名,代码可读性和可维护性大幅提升。把基础设施封装好,你才能专注于策略本身。

四、进阶技巧与实践经验
掌握了基本函数之后,再分享几个实战中总结出来的进阶技巧。这些都是在大量项目中踩坑踩出来的经验。
统一MagicNumber管理
每个策略一个魔术数字,用前缀+年份的方式生成,避免冲突。比如策略A用20260001,策略B用20260002。这样一看ticket的magic就知道是哪个策略开的仓,排查问题很方便。
订单注释规范
注释里写清楚策略名称、开仓原因,方便后期统计分析。比如"MultiTF_H4Breakout",一看就知道是多周期突破策略开的仓。注释虽小,后期做策略复盘的时候能帮大忙。
重试策略分级
不是所有错误都要重试。网络类错误(10020价格已变、10021请求被拒绝)可以重试3次,每次间隔100ms;参数类错误(10014无效参数、10015无效价格)不重试,直接报错——重试多少次都是同一个结果,白浪费时间。
进阶原理:为什么网络类错误要间隔100ms再重试?因为交易服务器处理请求需要时间,而且网络抖动通常是短暂的。立即重试大概率还是失败,稍微等一下成功率更高。但也不能等太久,不然行情都走完了。100ms是一个经验值——既给了服务器喘息的时间,又不会错过太多行情。
订单状态确认
OrderSend返回成功后,建议再用PositionSelect确认一下仓位是否真的存在。因为MT5的订单执行是异步的——返回成功只是"请求已受理",仓位真正建立可能需要几毫秒。极少数情况下会出现"返回成功但仓位没建上"的情况,多一层确认更保险。
操作参考:紧急全平机制是每个EA都应该有的风控保护机制。建议设置一个"一键全平"的全局变量或者外部输入参数,遇到异常情况(如连续亏损、网络故障、策略逻辑异常)时,手动把这个变量设为true,EA检测到之后立即平掉所有仓位。留得青山在,不怕没柴烧。任何时候,控制风险都是第一位的。
写在最后
订单操作是EA的基础设施,不值得每个项目都从零写。一套好的函数库,一次封装,终身受用。把这些琐碎但重要的细节封装好,你才能把精力集中在策略逻辑上——那才是真正产生价值的地方。
建议你今天就把自己的订单操作代码整理成函数库。不用一下子做9个,先把常用的开仓、平仓、修改止损止盈这三个封装好,剩下的慢慢补充。用不了一个小时,就能让你以后写每个EA都省掉半天的调试时间。
如果你的EA订单操作经常出问题,或者需要更复杂的订单管理(如多账户、分级加仓、批量风控),欢迎和我们交流。我们团队有成熟的订单管理框架和风控模块,能帮你快速搭建稳定可靠的EA执行层。
风险提示:本文内容仅为技术工具分享与原理探讨,不构成任何投资建议。本网站仅提供软件开发技术服务,不涉及任何交易平台运营或经纪业务。所有交易行为均由用户自行决策并承担相应风险。
🎬 关注晓辉编程视频号
MT4/MT5 EA开发实战 | 技术方法探讨 | 编程技巧干货

微信搜索:晓辉编程
💬 添加晓辉为好友
一对一交流EA开发 | 定制需求咨询 | 进技术交流群

微信号:XiaoHuiProgramming