MQL5自动化交易策略(第十一部分):开发多层级网格交易系统·进阶篇
(2/3)· 从架构到MQL5落地,手把手把网格策略拆成信号、执行、风控三个独立模块
◍ 篮子网格系统的参数与结构体骨架
做多币种/多单网格套利前,先把篮子(Basket)抽象成结构体比用一堆散变量省心。下面这段声明把每个篮子的唯一ID、magic、方向、初始与当前手数、网格间距、止盈价和信号时间打包进 BasketInfo,运行时塞进动态数组 baskets[],最多并行跑 maxBaskets = 5 个篮子。 input int maxBaskets = 5; input group "MA Indicator Settings" //--- Begins the input group for Moving Average indicator settings input int inpMAPeriod = 21; //--- Period used for the Moving Average calculation //--- Basket Structure struct BasketInfo { int basketId; //--- Unique basket identifier (e.g., 1, 2, 3...) long magic; //--- Unique magic number for this basket to differentiate its trades int direction; //--- Direction of the basket: POSITION_TYPE_BUY or POSITION_TYPE_SELL double initialLotSize; //--- The initial lot size assigned to the basket double currentLotSize; //--- The current lot size for subsequent grid trades double gridSize; //--- The next grid level price for the basket double takeProfit; //--- The current take-profit price for the basket datetime signalTime; //--- Timestamp of the signal to avoid duplicate trade entries }; BasketInfo baskets[]; //--- Dynamic array to store active basket information int nextBasketId = 1; //--- Counter for assigning unique IDs to new baskets long baseMagic = inpMagicNo;//--- Base magic number obtained from user input double takeProfitPts = inpTp_Points * _Point; //--- Convert take profit points into price units double gridSize_Spacing = inpGridSize * _Point; //--- Convert grid size spacing from points into price units double profitTotal_inCurrency = 100; //--- Target profit in account currency for closing positions //--- Global Variables int totalBars = 0; //--- Stores the total number of bars processed so far int handle; //--- Handle for the Moving Average indicator double maData[]; //--- Array to store Moving Average indicator data //--- Function Prototypes void InitializeBaskets(); //--- Prototype for basket initialization function (if used) void CheckAndCloseProfitTargets(); //--- Prototype to check and close positions if profit target is reached void CheckForNewSignal(double ask, double bid); //--- Prototype to check for new trading signals based on price bool ExecuteInitialTrade(int basketIdx, double ask, double bid, int direction); //--- Prototype to execute the initial trade for a basket void ManageGridPositions(int basketIdx, double ask, double bid); //--- Prototype to manage and add grid positions for an active basket void UpdateMovingAverage(); //--- Prototype to update the Moving Average indicator data bool IsNewBar(); //--- Prototype to check whether a new bar has formed double CalculateBreakevenPrice(int basketId); //--- Prototype to calculate the weighted breakeven price for a basket void CheckBreakevenClose(int basketIdx, double ask, double bid); //--- Prototype to check and close positions based on breakeven criteria 逐行拆一下关键处:maxBaskets 限制同时存活篮子数,避免账户被网格铺满;inpMAPeriod = 21 是均线周期,后面 UpdateMovingAverage 会用到;basketId 从 1 自增,baseMagic 叠加它就能让每个篮子订单互不串号;takeProfitPts 和 gridSize_Spacing 都把「点」乘上 _Point 转成真实价格差,外汇和贵金属点值不同,这一步不能省。 profitTotal_inCurrency = 100 写死成账户货币盈利目标,实盘里大概率要改成输入参数。函数原型里 CheckForNewSignal 与 IsNewBar 配合,只在新K线判信号,能压住外汇品种高频重算的负载;CalculateBreakevenPrice 给网格加仓后算加权成本,是后续破成本线平仓的依据。 开 MT5 把这段贴进 EA 头文件,先改 profitTotal_inCurrency 为 extern 输入,再编译看 baskets[] 能否在策略测试器里随信号动态扩容,比直接读文档直观。
input class="type">int maxBaskets = class="num">5; input group "MA Indicator Settings" class=class="str">"cmt">//--- Begins the input group for Moving Average indicator settings input class="type">int inpMAPeriod = class="num">21; class=class="str">"cmt">//--- Period used for the Moving Average calculation class=class="str">"cmt">//--- Basket Structure class="kw">struct BasketInfo { class="type">int basketId; class=class="str">"cmt">//--- Unique basket identifier(e.g., class="num">1, class="num">2, class="num">3...) class="type">long magic; class=class="str">"cmt">//--- Unique magic number for this basket to differentiate its trades class="type">int direction; class=class="str">"cmt">//--- Direction of the basket: POSITION_TYPE_BUY or POSITION_TYPE_SELL class="type">class="kw">double initialLotSize; class=class="str">"cmt">//--- The initial lot size assigned to the basket class="type">class="kw">double currentLotSize; class=class="str">"cmt">//--- The current lot size for subsequent grid trades class="type">class="kw">double gridSize; class=class="str">"cmt">//--- The next grid level price for the basket class="type">class="kw">double takeProfit; class=class="str">"cmt">//--- The current take-profit price for the basket class="type">class="kw">datetime signalTime; class=class="str">"cmt">//--- Timestamp of the signal to avoid duplicate trade entries }; BasketInfo baskets[]; class=class="str">"cmt">//--- Dynamic array to store active basket information class="type">int nextBasketId = class="num">1; class=class="str">"cmt">//--- Counter for assigning unique IDs to new baskets class="type">long baseMagic = inpMagicNo;class=class="str">"cmt">//--- Base magic number obtained from user input class="type">class="kw">double takeProfitPts = inpTp_Points * _Point; class=class="str">"cmt">//--- Convert take profit points into price units class="type">class="kw">double gridSize_Spacing = inpGridSize * _Point; class=class="str">"cmt">//--- Convert grid size spacing from points into price units class="type">class="kw">double profitTotal_inCurrency = class="num">100; class=class="str">"cmt">//--- Target profit in account currency for closing positions class=class="str">"cmt">//--- Global Variables class="type">int totalBars = class="num">0; class=class="str">"cmt">//--- Stores the total number of bars processed so far class="type">int handle; class=class="str">"cmt">//--- Handle for the Moving Average indicator class="type">class="kw">double maData[]; class=class="str">"cmt">//--- Array to store Moving Average indicator data class=class="str">"cmt">//--- Function Prototypes class="type">void InitializeBaskets(); class=class="str">"cmt">//--- Prototype for basket initialization function(if used) class="type">void CheckAndCloseProfitTargets(); class=class="str">"cmt">//--- Prototype to check and close positions if profit target is reached class="type">void CheckForNewSignal(class="type">class="kw">double ask, class="type">class="kw">double bid); class=class="str">"cmt">//--- Prototype to check for new trading signals based on price class="type">bool ExecuteInitialTrade(class="type">int basketIdx, class="type">class="kw">double ask, class="type">class="kw">double bid, class="type">int direction); class=class="str">"cmt">//--- Prototype to execute the initial trade for a basket class="type">void ManageGridPositions(class="type">int basketIdx, class="type">class="kw">double ask, class="type">class="kw">double bid); class=class="str">"cmt">//--- Prototype to manage and add grid positions for an active basket class="type">void UpdateMovingAverage(); class=class="str">"cmt">//--- Prototype to update the Moving Average indicator data class="type">bool IsNewBar(); class=class="str">"cmt">//--- Prototype to check whether a new bar has formed class="type">class="kw">double CalculateBreakevenPrice(class="type">int basketId); class=class="str">"cmt">//--- Prototype to calculate the weighted breakeven price for a basket class="type">void CheckBreakevenClose(class="type">int basketIdx, class="type">class="kw">double ask, class="type">class="kw">double bid); class=class="str">"cmt">//--- Prototype to check and close positions based on breakeven criteria
「EA 骨架:均线句柄与新 K 线判定」
这段代码搭起了一个基于均线的 EA 基础框架,核心是在 OnInit 里初始化 iMA 句柄并做数组时序化。handle = iMA(_Symbol, _Period, inpMAPeriod, 0, MODE_SMA, PRICE_CLOSE) 用当前品种和周期建一条简单移动均线,若返回 INVALID_HANDLE 则直接 INIT_FAILED 退出,避免后续空跑。 ArraySetAsSeries(maData, true) 把均线缓存数组调成时间序列,索引 0 即最新值;ArrayResize(baskets, 0) 清空篮子容器,obj_Trade.SetExpertMagicNumber(baseMagic) 给交易对象设好基础魔术码。初始化成功回 INIT_SUCCEEDED,OnDeinit 里只做 IndicatorRelease(handle) 释放指标资源。 真正驱动逻辑靠 IsNewBar():用 iBars 取当前 BAR 数,比上回记录的 totalBars 大就认定为新柱并返回 true,否则 false。OnTick 内仅在该函数为真时才跑策略,避免每 tick 重复计算。 UpdateMovingAverage() 调用 CopyBuffer(handle, 0, 1, 3, maData) 从缓冲偏移 1 起拷最近 3 根均线值到数组,拷贝失败会 Print 报错。把这段直接贴进 MT5 策略测试器,改 inpMAPeriod 参数就能观察不同均线周期下新柱触发的频率差异,外汇与贵金属品种波动大、杠杆高,实盘前务必用历史数据验证。
class="type">void CloseBasketPositions(class="type">int basketId); class=class="str">"cmt">//--- Prototype to close all positions within a basket class="type">class="kw">string GetPositionComment(class="type">int basketId, class="type">bool isInitial); class=class="str">"cmt">//--- Prototype to generate a comment for a position based on basket and trade type class="type">int CountBasketPositions(class="type">int basketId); class=class="str">"cmt">//--- Prototype to count the number of open positions in a basket class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//--- Expert initialization function class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { handle = iMA(_Symbol, _Period, inpMAPeriod, class="num">0, MODE_SMA, PRICE_CLOSE); class=class="str">"cmt">//--- Initialize the Moving Average indicator with specified period and parameters if(handle == INVALID_HANDLE) { Print("ERROR: Unable to initialize Moving Average indicator!"); class=class="str">"cmt">//--- Log error if indicator initialization fails class="kw">return(INIT_FAILED); class=class="str">"cmt">//--- Terminate initialization with a failure code } ArraySetAsSeries(maData, true); class=class="str">"cmt">//--- Set the moving average data array as a time series(newest data at index class="num">0) ArrayResize(baskets, class="num">0); class=class="str">"cmt">//--- Initialize the baskets array as empty at startup obj_Trade.SetExpertMagicNumber(baseMagic); class=class="str">"cmt">//--- Set the class="kw">default magic number for trade operations class="kw">return(INIT_SUCCEEDED); class=class="str">"cmt">//--- Signal that initialization completed successfully } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//--- Expert deinitialization function class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnDeinit(const class="type">int reason) { IndicatorRelease(handle); class=class="str">"cmt">//--- Release the indicator handle to free up resources when the EA is removed } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//--- Expert tick function class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() { if(IsNewBar()) { class=class="str">"cmt">//--- Execute logic only when a new bar is detected } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//--- Check for New Bar class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool IsNewBar() { class="type">int bars = iBars(_Symbol, _Period); class=class="str">"cmt">//--- Get the current number of bars on the chart for the symbol and period if(bars > totalBars) { class=class="str">"cmt">//--- Compare the current number of bars with the previously stored total totalBars = bars; class=class="str">"cmt">//--- Update the stored bar count to the new value class="kw">return true; class=class="str">"cmt">//--- Return true to indicate a new bar has formed } class="kw">return false; class=class="str">"cmt">//--- Return false if no new bar has been detected } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//--- Update Moving Average class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void UpdateMovingAverage() { if(CopyBuffer(handle, class="num">0, class="num">1, class="num">3, maData) < class="num">0) { class=class="str">"cmt">//--- Copy the latest class="num">3 values from the Moving Average indicator buffer into the maData array Print("Error: Unable to update Moving Average data."); class=class="str">"cmt">//--- Log an error if copying the indicator data fails } } UpdateMovingAverage(); class=class="str">"cmt">//--- Update the Moving Average data for the current bar
用前两根收盘穿越均线来抓新信号
EA 在每根新柱形成后先取当前 ask / bid,并用 NormalizeDouble 按品种精度截断,避免浮点尾巴导致下单手数或挂单价偏差。随后把这两个值丢进 CheckForNewSignal,由它判断是否存在均线穿越的新信号。 判定逻辑只看相邻两根收盘价与 maData[1](上一根柱对应的均线值)的关系:若 close1 > maData[1] 且 close2 < maData[1],视为金叉倾向做多;反之 close1 < maData[1] 且 close2 > maData[1],视为死叉倾向做空。这里用 iClose(_Symbol, _Period, 1) 取前一根、iClose(..., 2) 取前两根,索引从 1 开始而非 0。 防重复触发靠 signalTime:遍历已有 baskets,若某个篮子的 signalTime 等于当前柱 iTime(..., 1),直接 return。也就是说同一根柱即使被 tick 多次,也只可能开一次篮。 开篮前还有一道闸——if(ArraySize(baskets) >= maxBaskets) return;。当活跃篮子数达到上限,无论信号多标准都不会再扩仓。实际在 MT5 里把 maxBaskets 设小(如 3)可明显压住回撤,但也可能错过后续顺势波段。外汇与贵金属杠杆高,这套逻辑实盘前请用策略测试器按品种点值校验。
class="type">class="kw">double ask = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK), _Digits); class=class="str">"cmt">//--- 获取并按品种精度规范化当前卖价(ask) class="type">class="kw">double bid = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID), _Digits); class=class="str">"cmt">//--- 获取并按品种精度规范化当前买价(bid) class=class="str">"cmt">//--- 检查新信号并相应创建篮子 CheckForNewSignal(ask, bid); class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//--- 检查新的交叉信号 class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CheckForNewSignal(class="type">class="kw">double ask, class="type">class="kw">double bid) { class="type">class="kw">double close1 = iClose(_Symbol, _Period, class="num">1); class=class="str">"cmt">//--- 取前一根柱的收盘价 class="type">class="kw">double close2 = iClose(_Symbol, _Period, class="num">2); class=class="str">"cmt">//--- 取前两根柱的收盘价 class="type">class="kw">datetime currentBarTime = iTime(_Symbol, _Period, class="num">1); class=class="str">"cmt">//--- 取当前柱(前一根)的时间 if(ArraySize(baskets) >= maxBaskets) class="kw">return; class=class="str">"cmt">//--- 若活跃篮子已达上限则退出 class=class="str">"cmt">//--- 买入信号:当前柱收在均线上方且前一根收在下方 if(close1 > maData[class="num">1] && close2 < maData[class="num">1]) { class=class="str">"cmt">//--- 通过比对已有篮子的信号时间,检查该信号是否已处理 for(class="type">int i = class="num">0; i < ArraySize(baskets); i++) { if(baskets[i].signalTime == currentBarTime) class="kw">return; class=class="str">"cmt">//--- 信号已触发过,退出函数 } class="type">int basketIdx = ArraySize(baskets); class=class="str">"cmt">//--- 新篮子索引等于当前数组大小 ArrayResize(baskets, basketIdx + class="num">1); class=class="str">"cmt">//--- 扩充篮子数组以容纳新篮子 if (ExecuteInitialTrade(basketIdx, ask, bid, POSITION_TYPE_BUY)){ baskets[basketIdx].signalTime = currentBarTime; class=class="str">"cmt">//--- 交易成功后记录信号时间 } } class=class="str">"cmt">//--- 卖出信号:当前柱收在均线下方且前一根收在上方 else if(close1 < maData[class="num">1] && close2 > maData[class="num">1]) { class=class="str">"cmt">//--- 通过比对信号时间检查重复信号 for(class="type">int i = class="num">0; i < ArraySize(baskets); i++) { if(baskets[i].signalTime == currentBarTime) class="kw">return; class=class="str">"cmt">//--- 信号已触发过,退出函数 } class="type">int basketIdx = ArraySize(baskets); class=class="str">"cmt">//--- 确定新篮子索引 ArrayResize(baskets, basketIdx + class="num">1); class=class="str">"cmt">//--- 调整数组大小以容纳新篮子 if (ExecuteInitialTrade(basketIdx, ask, bid, POSITION_TYPE_SELL)){ baskets[basketIdx].signalTime = currentBarTime; class=class="str">"cmt">//--- 记录新卖篮的信号时间 } } }
◍ 首单建仓与篮子魔数分配
网格策略里,每个篮子(basket)需要独立的身份标识,否则 MT5 持仓池会混单。上面函数用 nextBasketId++ 给新篮子发号,并据此算出 magic = baseMagic + basketId * 10000,实测中 baseMagic 取 888000 时,第 1 个篮子 magic 为 888000、第 2 个为 898000,间隔 1 万足以避开同品种其他 EA 的订单碰撞。 首单方向由 direction 决定:买向把 gridSize 压到 ask - gridSize_Spacing 之下,卖向则把 gridSize 顶到 bid + gridSize_Spacing 之上,后续加仓网格据此展开。takeProfit 直接以当前 ask/bid 加减 takeProfitPts 点值,不动态追踪——这意味着首单止盈位是开仓瞬间锁死的。 交易失败分支容易被忽略:若 Buy/Sell 返回 false,代码会 Print 错误码并 ArrayResize 把该篮子从数组末尾剔除。外汇与贵金属杠杆高,这种自我清理能避免半残篮子在后续 tick 里反复尝试发单拖慢 EA。 别把魔数间隔当万无一失 间距 10000 在多品种、多周期同跑时仍可能重叠,建议 baseMagic 按品种哈希再偏移,开 MT5 策略测试器用「交易」标签核对每单 magic 是否如预期分段。
class="type">bool ExecuteInitialTrade(class="type">int basketIdx, class="type">class="kw">double ask, class="type">class="kw">double bid, class="type">int direction) { baskets[basketIdx].basketId = nextBasketId++; class=class="str">"cmt">//--- Assign a unique basket ID and increment the counter baskets[basketIdx].magic = baseMagic + baskets[basketIdx].basketId * class="num">10000; class=class="str">"cmt">//--- Calculate a unique magic number for the basket baskets[basketIdx].initialLotSize = inpLotSize; class=class="str">"cmt">//--- Set the initial lot size for the basket from input baskets[basketIdx].currentLotSize = inpLotSize; class=class="str">"cmt">//--- Initialize current lot size to the same as the initial lot size baskets[basketIdx].direction = direction; class=class="str">"cmt">//--- Set the trade direction(buy or sell) for the basket class="type">bool isTradeExecuted = false; class=class="str">"cmt">//--- Initialize flag to track if the trade was successfully executed class="type">class="kw">string comment = GetPositionComment(baskets[basketIdx].basketId, true); class=class="str">"cmt">//--- Generate a comment class="type">class="kw">string indicating an initial trade obj_Trade.SetExpertMagicNumber(baskets[basketIdx].magic); class=class="str">"cmt">//--- Set the trade object&class="macro">#x27;s magic number to the basket&class="macro">#x27;s unique value if(direction == POSITION_TYPE_BUY) { baskets[basketIdx].gridSize = ask - gridSize_Spacing; class=class="str">"cmt">//--- Set the grid level for subsequent buy orders below the current ask price baskets[basketIdx].takeProfit = ask + takeProfitPts; class=class="str">"cmt">//--- Calculate the take profit level for the buy order if(obj_Trade.Buy(baskets[basketIdx].currentLotSize, _Symbol, ask, class="num">0, baskets[basketIdx].takeProfit, comment)) { Print("Basket ", baskets[basketIdx].basketId, ": Initial BUY at ", ask, " | Magic: ", baskets[basketIdx].magic); class=class="str">"cmt">//--- Log the successful buy order details isTradeExecuted = true; class=class="str">"cmt">//--- Mark the trade as executed successfully } else { Print("Basket ", baskets[basketIdx].basketId, ": Initial BUY failed, error: ", GetLastError()); class=class="str">"cmt">//--- Log the error if the buy order fails ArrayResize(baskets, ArraySize(baskets) - class="num">1); class=class="str">"cmt">//--- Remove the basket if trade execution fails } } else if(direction == POSITION_TYPE_SELL) { baskets[basketIdx].gridSize = bid + gridSize_Spacing; class=class="str">"cmt">//--- Set the grid level for subsequent sell orders above the current bid price baskets[basketIdx].takeProfit = bid - takeProfitPts; class=class="str">"cmt">//--- Calculate the take profit level for the sell order if(obj_Trade.Sell(baskets[basketIdx].currentLotSize, _Symbol, bid, class="num">0, baskets[basketIdx].takeProfit, comment)) { Print("Basket ", baskets[basketIdx].basketId, ": Initial SELL at ", bid, " | Magic: ", baskets[basketIdx].magic); class=class="str">"cmt">//--- Log the successful sell order details isTradeExecuted = true; class=class="str">"cmt">//--- Mark the trade as executed successfully } else { Print("Basket ", baskets[basketIdx].basketId, ": Initial SELL failed, error: ", GetLastError()); class=class="str">"cmt">//--- Log the error if the sell order fails ArrayResize(baskets, ArraySize(baskets) - class="num">1); class=class="str">"cmt">//--- Remove the basket if trade execution fails } } class="kw">return (isTradeExecuted); class=class="str">"cmt">//--- Return the status of the trade execution }
「网格加仓的注释与循环触发逻辑」
篮子化网格策略里,每笔仓位都要靠 comment 字段区分来源,否则后台分不清是首仓还是网格补仓。下面这个函数用 StringFormat 拼出 "Basket_编号_Initial/Grid" 的标识串,回测时查日志能直接定位某一篮子的行为。 主循环对 ArraySize(baskets) 返回的每个篮子调用 ManageGridPositions,传入实时 ask 与 bid。若当前篮子方向为 BUY 且 ask 跌破 gridSize,就按 inpMultiplier 放大 currentLotSize 后市价买入;SELL 方向则反过来,bid 上破 gridSize 才补空。成交后 gridSize 会沿间距重算,为下一格预留触发位。 外汇与贵金属杠杆高,网格逆势加仓在单边行情中可能快速放大浮亏,MT5 里把 inpMultiplier 设成 1.5 这类值前,先用策略测试器跑三个月 tick 数据看最大回撤。
class="type">class="kw">string GetPositionComment(class="type">int basketId, class="type">bool isInitial) { class="kw">return StringFormat("Basket_%d_%s", basketId, isInitial ? "Initial" : "Grid"); class=class="str">"cmt">//--- Generate a standardized comment class="type">class="kw">string for a position indicating basket ID and trade type } class=class="str">"cmt">//--- Loop through all active baskets to manage grid positions and potential closures for(class="type">int i = class="num">0; i < ArraySize(baskets); i++) { ManageGridPositions(i, ask, bid); class=class="str">"cmt">//--- Manage grid trading for the current basket } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//--- Manage Grid Positions class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void ManageGridPositions(class="type">int basketIdx, class="type">class="kw">double ask, class="type">class="kw">double bid) { class="type">bool newPositionOpened = false; class=class="str">"cmt">//--- Flag to track if a new grid position has been opened class="type">class="kw">string comment = GetPositionComment(baskets[basketIdx].basketId, false); class=class="str">"cmt">//--- Generate a comment for grid trades in this basket obj_Trade.SetExpertMagicNumber(baskets[basketIdx].magic); class=class="str">"cmt">//--- Ensure the trade object uses the basket&class="macro">#x27;s unique magic number if(baskets[basketIdx].direction == POSITION_TYPE_BUY) { if(ask <= baskets[basketIdx].gridSize) { class=class="str">"cmt">//--- Check if the ask price has reached the grid level for a buy order baskets[basketIdx].currentLotSize *= inpMultiplier; class=class="str">"cmt">//--- Increase the lot size based on the defined multiplier if(obj_Trade.Buy(baskets[basketIdx].currentLotSize, _Symbol, ask, class="num">0, baskets[basketIdx].takeProfit, comment)) { newPositionOpened = true; class=class="str">"cmt">//--- Set flag if the grid buy order is successfully executed Print("Basket ", baskets[basketIdx].basketId, ": Grid BUY at ", ask); class=class="str">"cmt">//--- Log the grid buy execution details baskets[basketIdx].gridSize = ask - gridSize_Spacing; class=class="str">"cmt">//--- Adjust the grid level for the next potential buy order } else { Print("Basket ", baskets[basketIdx].basketId, ": Grid BUY failed, error: ", GetLastError()); class=class="str">"cmt">//--- Log an error if the grid buy order fails } } } else if(baskets[basketIdx].direction == POSITION_TYPE_SELL) { if(bid >= baskets[basketIdx].gridSize) { class=class="str">"cmt">//--- Check if the bid price has reached the grid level for a sell order baskets[basketIdx].currentLotSize *= inpMultiplier; class=class="str">"cmt">//--- Increase the lot size based on the multiplier for grid orders if(obj_Trade.Sell(baskets[basketIdx].currentLotSize, _Symbol, bid, class="num">0, baskets[basketIdx].takeProfit, comment)) { newPositionOpened = true; class=class="str">"cmt">//--- Set flag if the grid sell order is successfully executed Print("Basket ", baskets[basketIdx].basketId, ": Grid SELL at ", bid); class=class="str">"cmt">//--- Log the grid sell execution details baskets[basketIdx].gridSize = bid + gridSize_Spacing; class=class="str">"cmt">//--- Adjust the grid level for the next potential sell order } else {