🚀 بهترین برنامه نویس و طراح ربات معامله گر فارکس و سفارش ربات و اکسپرت معامله گر متاتریدر به زبان MQL4 و MQL5 | متااکسپرت

آموزش ارسال سفارش Buy و Sell در MQL5

آموزش ارسال سفارش Buy و Sell در MQL5

ارسال سفارش‌های معاملاتی، قلب تپنده هر اکسپرت، اسکریپت یا اندیکاتور معاملاتی در متاتریدر ۵ است. درک عمیق این فرآیند نه تنها برای ساخت ربات‌های معامله‌گر ضروری است، بلکه برای هر برنامه‌نویسی که قصد دارد کنترل کامل بر روی استراتژی خود داشته باشد، یک الزام محسوب می‌شود. تفاوت‌های بنیادین بین معماری MQL5 و نسخه قدیمی‌تر آن، MQL4، باعث شده که رویکرد به معاملات کاملاً متحول شود. در MQL4، توابعی مانند OrderSend همه کار را به صورت یکجا انجام می‌دادند، اما در MQL5، این فرآیند به‌صورت شیءگرا، ماژولار و با تفکیک دقیق مسئولیت‌ها بازطراحی شده است. این معماری جدید امکان مدیریت دقیق‌تر، کنترل خطاهای بهتر و انعطاف‌پذیری بیشتر را فراهم می‌کند، اما در عین حال نیازمند درک جامع‌تری از ساختار سیستم معاملاتی متاتریدر ۵ است.

ساختار سیستم معاملاتی در متاتریدر ۵

برای درک چگونگی ارسال سفارش خرید (Buy Order) و ارسال سفارش فروش (Sell Order)، ابتدا باید با معماری چندلایه و شیءگرای پلتفرم متاتریدر ۵ آشنا شد. در هسته این سیستم، مفهوم نماد معاملاتی (Symbol) به عنوان یک کلاس کامل قرار دارد که تمامی اطلاعات مربوط به یک دارایی (مانند EURUSD یا XAUUSD) از جمله قیمت Bid و Ask (Bid & Ask Price)، حداقل و حداکثر حجم، اندازه لات (Lot Size)، اسپرد (Spread)، اطلاعات حاشیه (Margin) و جزئیات قرارداد را در خود نگهداری می‌کند. هر عملیات معاملاتی باید از فیلتر این کلاس عبور کند تا از اعتبار پارامترهای ورودی اطمینان حاصل شود.

لایه بعدی، سیستم حساب معاملاتی یا اکانت (Account) است. وضعیت مالی حساب، شامل موجودی حساب (Account Balance)، سود و زیان شناور (Floating Profit/Loss)، ارزش خالص حساب (Account Equity)، مارجین استفاده شده (Margin) و مارجین آزاد (Free Margin) در این لایه مدیریت می‌شود. قبل از هر سفارش، ربات شما باید این پارامترها را بررسی کند تا مطمئن شود موجودی کافی برای باز کردن معامله جدید، با در نظر گرفتن مدیریت ریسک (Risk Management)، وجود دارد. عدم توجه به این لایه می‌تواند منجر به خطاهای بحرانی مانند ERR_NOT_ENOUGH_MONEY شود.

سطح سوم، موتور معاملاتی است که خود به دو بخش اصلی تقسیم می‌شود: سفارشات بازار (Market Orders) و سفارشات معلق (Pending Orders). سفارش بازار (Market Order) دستوری برای خرید یا فروش فوری یک نماد معاملاتی (Symbol) در بهترین قیمت موجود در بازار است. در مقابل، سفارش معلق (Pending Order) دستوری است که در سیستم قرار می‌گیرد تا در آینده و زمانی که شرایط از پیش تعیین‌شده (مثل رسیدن قیمت به یک سطح خاص) محقق شد، به طور خودکار به یک سفارش بازار (Market Order) تبدیل و اجرا شود. انواع سفارش‌ها (Order Types) معلق شامل Buy Limit، Sell Limit، Buy Stop و Sell Stop می‌شوند. این تفکیک در MQL5 بسیار شفاف‌تر از MQL4 است.

برای تعامل با این سیستم ساختاریافته، MQL5 دو مسیر اصلی را پیش روی برنامه‌نویس قرار می‌دهد: استفاده از توابع تجاری مستقیم (Trade Functions) یا استفاده از کلاس CTrade (CTrade Class) که بخشی از کتابخانه استاندارد (Standard Library) و به طور خاص کتابخانه Trade (Trade Library) است. کلاس CTrade یک آبجکت از پیش ساخته‌شده و بهینه است که پیچیدگی‌های تعامل با موتور معاملاتی را پنهان می‌کند و استفاده از آن را بسیار ساده، ایمن و خوانا می‌سازد.

معرفی کامل کلاس CTrade و متدهای Buy و Sell

کلاس CTrade (CTrade Class) یک کپسول قدرتمند از تمامی توابع و منطق مورد نیاز برای انجام معاملات است. این کلاس در فایل Trade.mqh تعریف شده و برای استفاده از آن باید این فایل را در برنامه خود include کنید. (#include <Trade/Trade.mqh>). ایجاد یک نمونه از این کلاس، یک شیء معامله‌گر در اختیار شما قرار می‌دهد که می‌تواند عملیات خرید، فروش، تعدیل و بستن معاملات را انجام دهد.

مزیت اصلی استفاده از CTrade، مدیریت خودکار بسیاری از جزئیات فنی است. برای مثال، این کلاس به طور خودکار آخرین قیمت Bid و Ask (Bid & Ask Price) را برای نماد معاملاتی (Symbol) مورد نظر استعلام می‌کند، بررسی می‌کند که حجم معامله (Lot Size) در محدوده مجاز حساب و نماد قرار دارد، حد ضرر (Stop Loss) و حد سود (Take Profit) را به درستی تنظیم می‌کند (با در نظر گرفتن فاصله ضروری از قیمت فعلی یا Stop Level)، و مهم‌تر از همه، پاسخ سرور را پردازش کرده و خطاهای معاملاتی (Trade Errors) را در قالب یک کد عددی و یک شرح متنی در اختیار شما قرار می‌دهد.

دو متد اصلی این کلاس برای باز کردن معاملات جدید، Buy و Sell هستند. امضای این متدها به شکل زیر است:

bool CTrade::Buy(
   double volume,            // حجم معامله
   string symbol=NULL,       // نماد معاملاتی (اگر NULL باشد، نماد فعلی chart است)
   double price=0.0,         // قیمت (برای Market Order معمولاً 0)
   double sl=0.0,            // حد ضرر
   double tp=0.0,            // حد سود
   string comment=NULL       // کامنت معامله
);

bool CTrade::Sell(
   double volume,
   string symbol=NULL,
   double price=0.0,
   double sl=0.0,
   double tp=0.0,
   string comment=NULL
);

مقدار بازگشتی این متدها یک بولین است. اگر true باشد، به معنای آن است که درخواست معامله برای سرور ارسال شده است (نه لزوماً اجرا شده). اگر false باشد، یعنی یک خطا در مرحله آماده‌سازی درخواست رخ داده و سفارش حتی برای سرور ارسال هم نشده است. برای اطلاع از نتیجه واقعی اجرای سفارش در بازار، باید نتیجه معامله (Trade Result) را از طریق متد ResultRetcode() یا ResultComment() بررسی کرد که بعداً به طور مفصل توضیح داده خواهد شد.

تفاوت‌های کلیدی MQL5 با MQL4 در ارسال سفارش‌ها

درک این تفاوت‌ها برای برنامه‌نویسانی که از MQL4 به MQL5 مهاجرت می‌کنند حیاتی است و از بروز اشتباهات رایج جلوگیری می‌کند.

۱. معماری شیءگرا در مقابل رویه‌ای: در MQL4، تمام کار با یک تابع OrderSend انجام می‌شد که لیست بلندبالایی از پارامترها داشت. در MQL5، شما با اشیایی مانند CTrade، CPositionInfo، COrderInfo و CHistoryOrderInfo سروکار دارید که هر کدام داده‌های مخصوص به خود را کپسوله کرده‌اند. این باعث سازماندهی بهتر کد و قابلیت استفاده مجدد می‌شود.

۲. تفکیک سفارش (Order) و معامله (Trade/Position): این یکی از بزرگترین تغییرات است. در MQL4، یک مفهوم واحد به نام “Order” وجود داشت که هم شامل سفارشات معلق می‌شد و هم معاملات باز. در MQL5، این دو مفهوم کاملاً از هم جدا شده‌اند. سفارش (Order) یک درخواست در حال انتظار است (مانند یک Pending Order). وقتی این درخواست اجرا می‌شود و یک معامله واقعی در بازار ایجاد می‌کند، نتیجه آن یک پوزیشن (Position) باز است. بنابراین، در MQL5 شما Positions باز دارید و Orders معلق. این تفکیک در بررسی وضعیت حساب بسیار منطقی‌تر است.

۳. مدیریت خطاها و نتایج: در MQL4، OrderSend() هم کد خطا و هم تیکت معامله را مستقیماً بازمی‌گرداند. در MQL5، وقتی از CTrade.Buy() استفاده می‌کنید، این متد فقط true/false برمی‌گرداند. برای دریافت جزئیات دقیق نتیجه معامله (Trade Result) شامل کد بازگشتی سرور (مثلاً TRADE_RETCODE_DONE به معنای موفقیت) یا کد خطا (Error Codes) (مثلاً 10027 برای Requote)، باید از متدهای جداگانه شیء CTrade استفاده کنید. این طراحی، پردازش نتایج را منعطف‌تر کرده است.

۴. سیستم قیمت‌گذاری: در MQL4، شما یک قیمت را به OrderSend می‌دادید. در MQL5، با توجه به تفاوت قیمت Bid و Ask (Bid & Ask Price) و اینکه معاملات خرید در قیمت Ask و معاملات فروش در قیمت Bid انجام می‌شوند، کلاس CTrade به طور خودکار قیمت مناسب را محاسبه و اعمال می‌کند. اگر قیمت را خودتان مشخص کنید (غیر از صفر)، باید مطمئن شوید که قیمت صحیحی را برای نوع معامله وارد کرده‌اید.

۵. ساختار داده‌های تاریخی: تاریخچه معاملات در MQL5 بسیار غنی‌تر و ساختاریافته‌تر است. دسترسی به تاریخچه سفارشات (Order History)، معاملات انجام‌شده (Deals History) و پوزیشن‌های بسته‌شده (Closed Positions) هرکدام از طریق کلاس‌های مجزایی انجام می‌شود که امکان تحلیل دقیق‌تری از عملکرد ربات را فراهم می‌آورد.

بررسی پارامترهای مهم در ارسال سفارش‌ها

هر فراخوانی متد Buy یا Sell نیازمند تنظیم دقیق پارامترهاست. درک نقش هر پارامتر برای موفقیت معامله ضروری است.

حجم معامله (Lot Size): این پارامتر تعیین‌کننده اندازه پوزیشن است. محاسبه آن قلب مدیریت ریسک (Risk Management) است. باید مطمئن شد که حجم وارد شده، هم کوچک‌تر یا مساوی حداکثر حجم مجاز نماد (SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX)) است، هم بزرگ‌تر یا مساوی حداقل حجم (SYMBOL_VOLUME_MIN) و هم مضربی از گام حجم (SYMBOL_VOLUME_STEP). یک روش حرفه‌ای، محاسبه حجم بر اساس درصد ثابتی از سرمایه حساب (Account Balance) یا بر اساس فاصله حد ضرر (Stop Loss) و مقدار ریسک دلاری قابل پذیرش برای هر معامله است. فرمول رایج برای محاسبه حجم بر اساس ریسک به این صورت است:

[
\text{Lot Size} = \frac{\text{Account Balance} \times \text{Risk Percentage}}{\text{Stop Loss in Points} \times \text{Point Value per Lot}} ]

نماد معاملاتی (Symbol): اگر این پارامتر NULL باشد یا به صراحت Symbol() داده شود، از نماد chart جاری استفاده می‌کند. برای ارسال سفارش روی نمادهای دیگر، باید نام دقیق نماد (مثلاً "GBPJPY") را وارد کرد. قبل از ارسال سفارش، بررسی وضعیت نماد با SymbolInfoInteger(symbol, SYMBOL_TRADE_MODE) برای اطمینان از امکان معامله (مثلاً SYMBOL_TRADE_MODE_FULL) ضروری است.

قیمت (Price): برای سفارشات بازار (Market Orders)، این مقدار باید 0.0 باشد تا کلاس CTrade به طور خودکار بهترین قیمت جاری بازار (Ask برای Buy، Bid برای Sell) را درخواست کند. وارد کردن یک قیمت خاص برای Market Order می‌تواند منجر به خطا شود، مگر اینکه قصد استفاده از سفارشات محدود (Limit Orders) را داشته باشید. برای سفارشات معلق (Pending Orders)، این پارامتر تعیین‌کننده قیمت فعال‌سازی است.

حد ضرر (Stop Loss) و حد سود (Take Profit): این سطوح حیاتی مدیریت معامله هستند. می‌توانند به صورت قیمت مطلق (مثلاً 1.08500) یا به صورت نسبی با اختلاف از قیمت ورود (مثلاً price - 100 * _Point) تنظیم شوند. نکته بسیار مهم این است که این سطوح باید فاصله کافی از قیمت فعلی (مطابق با SYMBOL_TRADE_STOPS_LEVEL) داشته باشند، در غیر این صورت سفارش توسط سرور ریجکت (Reject) خواهد شد. کلاس CTrade به طور خودکار این بررسی را انجام می‌دهد و سطوح را در صورت لزوم اصلاح می‌کند، اما آگاهی برنامه‌نویس از این محدودیت‌ها ضروری است. تنظیم sl و tp به عنوان 0.0 به معنای عدم تعیین حد ضرر یا حد سود است.

کامنت (Comment): یک رشته متنی اختیاری که به پوزیشن الصاق می‌شود. می‌توان از آن برای شناسایی استراتژی خاص، نسخه ربات یا هر اطلاعات دیگری استفاده کرد. این کامنت در پنجره Trade و در گزارش‌ها قابل مشاهده است.

دریافت قیمت‌های Bid و Ask قبل از ارسال سفارش

اگرچه کلاس CTrade این کار را به طور خودکار انجام می‌دهد، اما یک برنامه‌نویس حرفه‌ای اغلب نیاز دارد قبل از ارسال سفارش، قیمت‌های بازار را مستقیماً بررسی کند. این کار برای محاسبات دقیق حد ضرر (Stop Loss)، حد سود (Take Profit)، بررسی شرایط ورود یا محاسبه اسپرد (Spread) ضروری است. توابع SymbolInfoDouble() برای این منظور طراحی شده‌اند.

// دریافت قیمت‌های جاری Bid و Ask برای نماد فعلی
double currentBid = SymbolInfoDouble(Symbol(), SYMBOL_BID);
double currentAsk = SymbolInfoDouble(Symbol(), SYMBOL_ASK);
double currentSpread = (currentAsk - currentBid) / _Point; // اسپرد به پیپ

// دریافت قیمت برای یک نماد خاص
double goldBid = SymbolInfoDouble("XAUUSD", SYMBOL_BID);

// بررسی اینکه آیا قیمت‌ها معتبر هستند (برابر نبودن با صفر یا EMPTY_VALUE)
if(currentBid == 0 || currentAsk == 0) {
   Print("خطا در دریافت قیمت‌های بازار.");
   return;
}

همچنین، در لحظه ارسال سفارش بازار (Market Order)، به دلیل تأخیر احتمالی شبکه (اسلیپیج (Slippage))، قیمت نهایی اجرا ممکن است با قیمت لحظه‌ای که شما دریافت کرده‌ید متفاوت باشد. پارامتر deviation در برخی از متدهای سطح پایین‌تر MQL5 (و به صورت داخلی در CTrade) برای تعیین حداکثر انحراف مجاز قیمت اجرا از قیمت درخواستی استفاده می‌شود. اسلیپیج (Slippage) در بازارهای پرنوسان یا با نقدشوندگی کم می‌تواند قابل توجه باشد و باید در مدیریت ریسک (Risk Management) مد نظر قرار گیرد.

ارسال سفارش Buy با مثال کد واقعی و توضیح خط به خط

در این بخش، یک نمونه کامل از ارسال یک سفارش خرید (Buy Order) با استفاده از کلاس CTrade، همراه با بررسی‌های اولیه و مدیریت خطا ارائه می‌شود.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
#include <Trade/Trade.mqh> // شامل کردن کتابخانه معاملاتی

CTrade trade; // ایجاد یک شیء از کلاس CTrade به صورت سراسری

//+------------------------------------------------------------------+
//| تابع اجرای اسکریپت                                             |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- 1. تعریف و محاسبه پارامترهای معامله
   string symbol = Symbol(); // استفاده از نماد chart جاری
   double volume = 0.1; // حجم ثابت 0.1 لات (در عمل باید مدیریت ریسک شود)
   double stopLoss = 0; // ابتدا صفر تعریف می‌شود
   double takeProfit = 0;
   double price = 0; // قیمت صفر برای Market Order

//--- 2. دریافت قیمت‌های جاری برای محاسبه SL و TP
   double askPrice = SymbolInfoDouble(symbol, SYMBOL_ASK);
   double bidPrice = SymbolInfoDouble(symbol, SYMBOL_BID);
   double point = SymbolInfoDouble(symbol, SYMBOL_POINT);

//--- 3. محاسبه حد ضرر و حد سود بر اساس یک منطق نمونه (مثلاً 100 پیپ)
   int stopPoints = 100;
   int takeProfitPoints = 200;
   stopLoss = askPrice - (stopPoints * point);
   takeProfit = askPrice + (takeProfitPoints * point);

//--- 4. بررسی‌های اولیه امنیتی و مدیریت سرمایه
// بررسی امکان معامله روی نماد
   long tradeMode = SymbolInfoInteger(symbol, SYMBOL_TRADE_MODE);
   if(tradeMode != SYMBOL_TRADE_MODE_FULL)
     {
      Print("معامله برای نماد ", symbol, " در حال حاضر مجاز نیست. حالت: ", tradeMode);
      return;
     }

// بررسی مارجین آزاد کافی (به صورت ساده)
   double requiredMargin = 0;
   if(!OrderCalcMargin(ORDER_TYPE_BUY, symbol, volume, askPrice, requiredMargin))
     {
      Print("خطا در محاسبه مارجین مورد نیاز.");
      return;
     }
   if(requiredMargin > AccountInfoDouble(ACCOUNT_FREEMARGIN))
     {
      Print("مارجین آزاد کافی نیست. مورد نیاز: ", requiredMargin, " , آزاد: ", AccountInfoDouble(ACCOUNT_FREEMARGIN));
      return;
     }

//--- 5. ارسال سفارش خرید با استفاده از متد Buy
   bool success = trade.Buy(volume, symbol, price, stopLoss, takeProfit, "My EA Buy Order");

//--- 6. بررسی نتیجه اولیه ارسال
   if(success)
     {
      Print("درخواست خرید برای سرور ارسال شد.");
      // اینجا موفقیت فقط به معنای ارسال درخواست است، نه اجرای آن.
     }
   else
     {
      Print("خطا در ارسال درخواست خرید. کد خطا (Retcode): ", trade.ResultRetcode(), " , شرح: ", trade.ResultComment());
      // ممکن است خطا در سمت کلاینت باشد (مثلاً پارامترهای نامعتبر).
      return;
     }

//--- 7. بررسی نهایی نتیجه اجرای سفارش توسط سرور (اختیاری اما توصیه‌شده)
// معمولاً نیاز به مکانیزمی مانند رویداد OnTrade است.
// اما می‌توان با تاخیر کوتاه بررسی کرد:
   Sleep(100); // تاخیر 100 میلی‌ثانیه برای پردازش توسط سرور
   uint retcode = trade.ResultRetcode();
   if(retcode == TRADE_RETCODE_DONE || retcode == TRADE_RETCODE_PLACED)
     {
      Print("سفارش خرید با موفقیت انجام شد. تیکت پوزیشن: ", trade.ResultOrder());
     }
   else
     {
      Print("سفارش خرید توسط سرور رد شد. کد خطا: ", retcode, " , شرح: ", trade.ResultComment());
      // پردازش خطای خاص (مثلاً Requote، حجم نامعتبر و...)
     }
  }
//+------------------------------------------------------------------+

توضیح خط به خط:

  1. خطوط 1-6: فایل کتابخانه معاملاتی شامل شده و یک شیء جهانی از کلاس CTrade ایجاد می‌شود.
  2. خطوط 11-16: پارامترهای اولیه معامله تعریف می‌شوند. symbol نماد جاری، volume حجم ثابت (در عمل باید پویا باشد)، price=0 برای سفارش بازار.
  3. خطوط 19-21: قیمت Ask و Bid جاری و مقدار Point نماد دریافت می‌شوند. قیمت Ask برای خرید مهم است.
  4. خطوط 24-26: حد ضرر (Stop Loss) و حد سود (Take Profit) به صورت نمونه‌ای، با فاصله‌ای ثابت (به پیپ) از قیمت ورود محاسبه می‌شوند.
  5. خطوط 29-36: بررسی‌های اولیه: اطمینان از اینکه نماد برای معامله کامل باز است (SYMBOL_TRADE_MODE_FULL).
  6. خطوط 39-48: یک بررسی مدیریت سرمایه (Risk Management) ساده انجام می‌شود. مارجین مورد نیاز برای این معامله محاسبه و با مارجین آزاد (Free Margin) حساب مقایسه می‌شود. تابع OrderCalcMargin این محاسبه را به طور دقیق انجام می‌دهد.
  7. خط 51: متد Buy فراخوانی می‌شود. تمام پارامترهای محاسبه‌شده به آن داده می‌شود.
  8. خطوط 54-65: نتیجه اولیه فراخوانی متد بررسی می‌شود. اگر false باشد، خطایی در سمت کلاینت رخ داده (مثلاً پارامتر حجم نامعتبر) و پیام خطا چاپ می‌شود.
  9. خطوط 68-79: پس از یک تاخیر کوتاه (شبیه‌سازی انتظار برای پاسخ سرور)، نتیجه نهایی از سرور بررسی می‌شود. کدهای بازگشتی TRADE_RETCODE_DONE (برای معاملات فوری) یا TRADE_RETCODE_PLACED (برای سفارشات معلق) نشان‌دهنده موفقیت‌آمیز بودن ثبت سفارش در سمت سرور هستند. هر کد دیگری نشان‌دهنده یک خطای معاملاتی (Trade Error) است که باید منطق برنامه برای آن پیش‌بینی شود.

ارسال سفارش Sell با مثال کد واقعی و توضیح خط به خط

فرآیند ارسال سفارش فروش (Sell Order) تقریباً آینه‌ای فرآیند خرید است، با این تفاوت که منطق قیمت‌گذاری و محاسبه سطوح حفاظتی معکوس می‌شود.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
#include <Trade/Trade.mqh>
#include <Trade/AccountInfo.mqh> // برای دسترسی به اطلاعات حساب

CTrade trade;
CAccountInfo accountInfo; // یک شیء برای دسترسی راحت‌تر به اطلاعات حساب

//+------------------------------------------------------------------+
//| تابع اجرای اسکریپت                                             |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- 1. تعریف پارامترها
   string symbol = Symbol();
   double riskPercent = 1.0; // ریسک 1% از سرمایه
   double stopLossPoints = 150;
   double riskMoney;
   double volume;

//--- 2. دریافت قیمت‌های جاری
   double bidPrice = SymbolInfoDouble(symbol, SYMBOL_BID);
   double askPrice = SymbolInfoDouble(symbol, SYMBOL_ASK);
   double point = SymbolInfoDouble(symbol, SYMBOL_POINT);
   double tickValue = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE); // ارزش هر tick به ارز حساب

   if(bidPrice == 0 || askPrice == 0)
     {
      Print("قیمت‌های بازار نامعتبر هستند.");
      return;
     }

//--- 3. محاسبه حجم بر اساس مدیریت ریسک پیشرفته
// فرض: حد ضرر 150 پیپ پایین‌تر از قیمت فروش (Bid) قرار می‌گیرد.
   riskMoney = accountInfo.Balance() * (riskPercent / 100.0);
// ارزش هر پیپ برای یک لات استاندارد = (Tick Value) * (Point / Tick Size) * حجم
// برای ساده‌سازی، از تابع OrderCalcProfit استفاده می‌کنیم.
   double profit;
   if(!OrderCalcProfit(ORDER_TYPE_SELL, symbol, 1.0, bidPrice, bidPrice - (stopLossPoints * point), profit))
     {
      Print("خطا در محاسبه ارزش پیپ.");
      return;
     }
// profit در اینجا مقدار منفی خواهد بود (چون یک ضرر است). قدر مطلق آن را می‌گیریم.
   double lossPerLot = MathAbs(profit);
   if(lossPerLot == 0)
     {
      Print("خطا: lossPerLot صفر است.");
      return;
     }
// محاسبه حجم نهایی
   volume = NormalizeDouble(riskMoney / lossPerLot, 2);
// اطمینان از قرار گرفتن حجم در محدوده مجاز نماد
   double minVol = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);
   double maxVol = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);
   double stepVol = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
   volume = MathMax(minVol, volume);
   volume = MathMin(maxVol, volume);
   volume = MathRound(volume / stepVol) * stepVol; // گرد کردن به نزدیک‌ترین گام

   Print("حجم محاسبه‌شده: ", volume, " لات. سرمایه در خطر: ", riskMoney, " ", accountInfo.Currency());

//--- 4. محاسبه حد ضرر و حد سود
   double stopLoss = bidPrice + (stopLossPoints * point); // برای Sell، SL بالای قیمت ورود است
   double takeProfitPoints = 300; // نسبت ریسک به پاداش 1:2
   double takeProfit = bidPrice - (takeProfitPoints * point);

//--- 5. ارسال سفارش فروش
   bool success = trade.Sell(volume, symbol, 0, stopLoss, takeProfit, "My EA Sell - Risk 1%");

//--- 6. بررسی نتایج
   if(success)
     {
      Print("درخواست فروش ارسال شد. منتظر پاسخ سرور...");
      Sleep(200);
      uint retcode = trade.ResultRetcode();
      string comment = trade.ResultComment();
      if(retcode == TRADE_RETCODE_DONE)
        {
         ulong ticket = trade.ResultOrder();
         Print("سفارش فروش اجرا شد. تیکت: ", ticket, " قیمت اجرا: ", trade.ResultPrice());
        }
      else
        {
         Print("خطا از سمت سرور. کد: ", retcode, " , شرح: ", comment);
         // تحلیل خطا: مثلاً خطای 10027 (Requote) ممکن است نیاز به اقدام خاصی داشته باشد.
         if(retcode == 10027) // TRADE_RETCODE_REQUOTE
           {
            Print("Requote رخ داد. قیمت جدید پیشنهادی: ", trade.ResultPrice());
            // در اینجا می‌توان منطق تکرار سفارش با قیمت جدید را پیاده‌سازی کرد.
           }
        }
     }
   else
     {
      Print("خطا در آماده‌سازی و ارسال سفارش فروش. کد: ", trade.ResultRetcode(), " , شرح: ", trade.ResultComment());
     }
  }
//+------------------------------------------------------------------+

توضیح خط به خط (تمرکز روی تفاوت‌ها با Buy):

  1. خطوط 1-8: علاوه بر کتابخانه Trade، کتابخانه AccountInfo نیز برای دسترسی آسان‌تر به Balance() و Currency() حساب اضافه شده است.
  2. خطوط 15-25: در محاسبات، قیمت مرجع برای فروش، قیمت Bid (Bid Price) است. tickValue نیز دریافت می‌شود که برای محاسبه ارزش پیپ ضروری است.
  3. خطوط 28-54: حجم معامله (Lot Size) به روشی پیچیده‌تر و مبتنی بر مدیریت ریسک (Risk Management) محاسبه می‌شود. هدف این است که حداکثر ضرر معامله (stopLossPoints پیپ) معادل riskPercent درصد از موجودی حساب (Account Balance) باشد. تابع OrderCalcProfit برای محاسبه سود/ضرر یک معامله فرضی با حجم ۱ لات استفاده می‌شود تا ارزش هر پیپ مشخص شود. سپس حجم نهایی با تقسیم riskMoney بر lossPerLot به دست می‌آید. در نهایت، حجم با استفاده از NormalizeDouble و منطق گرد کردن به گام حجم، در محدوده مجاز نماد قرار می‌گیرد. این یک روش حرفه‌ای برای محاسبه حجم است.
  4. خطوط 57-60: محاسبه حد ضرر (Stop Loss) و حد سود (Take Profit) برای Sell برعکس Buy است. حد ضرر (Stop Loss) در بالای قیمت ورود (bidPrice + stopLossPoints*point) و حد سود (Take Profit) در پایین قیمت ورود (bidPrice - takeProfitPoints*point) قرار می‌گیرد.
  5. خط 63: متد Sell فراخوانی می‌شود.
  6. خطوط 66-88: پردازش نتایج. در این مثال، یک خطای خاص (10027 یا TRADE_RETCODE_REQUOTE) نیز بررسی شده است. Requote زمانی اتفاق می‌افتد که قیمت درخواستی شما منقضی شده و سرور یک قیمت جدید پیشنهاد می‌دهد. یک ربات حرفه‌ای باید بتواند این حالت را تشخیص دهد و تصمیم بگیرد که آیا با قیمت جدید معامله کند یا صبر نماید.

تنظیم Stop Loss و Take Profit در زمان ارسال سفارش

تنظیم حد ضرر (Stop Loss) و حد سود (Take Profit) شاید مهم‌ترین بخش مدیریت ریسک (Risk Management) در یک معامله خودکار باشد. روش‌های مختلفی برای تعیین این سطوح وجود دارد:

۱. تنظیم ثابت بر اساس پیپ: همانند مثال‌های بالا، که یک فاصله ثابت بر حسب پیپ از قیمت ورود در نظر گرفته می‌شود. ساده اما ممکن است با شرایط بازار متناسب نباشد.

۲. تنظیم بر اساس سطوح تکنیکال: استفاده از اندیکاتورها یا محاسبات برای یافتن سطوح حمایت و مقاومت، کف و سقف کندل‌ها و غیره. برای مثال، قرار دادن حد ضرر (Stop Loss) زیر Low کندل ورود یا زیر یک میانگین متحرک.

// مثال: قرار دادن حد ضرر زیر Low 10 کندل اخیر
int barsForSL = 10;
double lowestLow = iLow(symbol, PERIOD_CURRENT, iLowest(symbol, PERIOD_CURRENT, MODE_LOW, barsForSL, 1));
stopLoss = lowestLow - (10 * point); // کمی فاصله ایمنی اضافه می‌کنیم

۳. تنظیم بر اساس نوسان (ATR): استفاده از اندیکاتور Average True Range برای تنظیم حد ضرر (Stop Loss) و حد سود (Take Profit) به صورت پویا و متناسب با نوسان بازار. این روش در بازارهای پرنوسان بسیار موثر است.

// محاسبه حد ضرر بر اساس 1.5 برابر ATR دوره 14
double atrValue = iATR(symbol, PERIOD_CURRENT, 14, 1);
stopLoss = askPrice - (1.5 * atrValue); // برای Buy
takeProfit = askPrice + (2.5 * atrValue); // نسبت ریوارد به ریسک ~ 1.66

۴. تنظیم بر اساس درصد از حساب: محاسبه فاصله SL به گونه‌ای که درصد مشخصی از حساب را به خطر بیندازد، که قبلاً در مثال Sell به آن پرداخته شد.

نکات حیاتی:

  • همیشه پس از محاسبه سطوح، باید از اعتبار آنها مطمئن شوید. فاصله SL و TP از قیمت جاری باید بیشتر از سطح توقف (Stop Level) تعریف‌شده توسط کارگزار باشد (SYMBOL_TRADE_STOPS_LEVEL).
  • برخی کارگزاران برای برخی نمادها، حد ضرر (Stop Loss) و حد سود (Take Profit) را به صورت ثابت (Fixed) نمی‌پذیرند و باید حتماً تنظیم شوند.
  • در سفارشات معلق مانند Buy Stop یا Sell Limit، قیمت فعال‌سازی سفارش (price پارامتر) خود می‌تواند به عنوان یک سطح حفاظتی عمل کند، اما همچنان تنظیم SL و TP جداگانه توصیه می‌شود.

مدیریت خطاها و بررسی نتیجه سفارش‌ها

هر ربات معامله‌گر واقعی باید یک سیستم مدیریت خطای قوی داشته باشد. خطاهای معاملاتی (Trade Errors) می‌توانند به دلایل متعددی رخ دهند: مشکلات شبکه، تغییر قیمت (اسلیپیج (Slippage) بیش از حد)، محدودیت‌های حساب، قوانین کارگزار و بسیاری دلایل دیگر.

کدهای بازگشتی (Return Codes): سرور متاتریدر برای هر درخواست معاملاتی یک کد بازگشتی ارسال می‌کند. این کدها در فایل stderror.mqh به صورت ثابت تعریف شده‌اند. برخی از مهم‌ترین آنها عبارتند از:

  • TRADE_RETCODE_DONE (10009): سفارش با موفقیت اجرا شد.
  • TRADE_RETCODE_PLACED (10008): سفارش معلق با موفقیت ثبت شد.
  • TRADE_RETCODE_REJECT (10004): سفارش توسط سرور رد شد (دلیل کلی).
  • TRADE_RETCODE_REQUOTE (10027): قیمت تغییر کرده، سرور قیمت جدیدی پیشنهاد می‌دهد.
  • TRADE_RETCODE_NOT_ENOUGH_MONEY (10014): سرمایه حساب (Account Balance) یا مارجین آزاد (Free Margin) کافی نیست.
  • TRADE_RETCODE_INVALID_STOPS (10015): سطوح حد ضرر (Stop Loss) یا حد سود (Take Profit) نامعتبر هستند (خیلی نزدیک به قیمت).
  • TRADE_RETCODE_INVALID_VOLUME (10015): حجم معامله (Lot Size) خارج از محدوده مجاز است.

استراتژی مدیریت خطا: پس از هر فراخوانی Buy() یا Sell()، باید مراحل زیر را انجام داد:

  1. بررسی مقدار بولین بازگشتی. اگر false بود، خطا مربوط به اعتبارسنجی محلی است.
  2. اگر true بود، با استفاده از trade.ResultRetcode() کد نهایی سرور را دریافت کنید.
  3. بر اساس کد خطا، عمل مناسب را انجام دهید. برای برخی خطاها (مثل Requote) ممکن است بخواهید درخواست را با قیمت جدید تکرار کنید. برای برخی دیگر (مثل حجم نامعتبر) باید محاسبات خود را اصلاح کنید. برای خطاهای بحرانی (مثل عدم وجود ارتباط) باید تمام فعالیت‌های معاملاتی را متوقف کرد.
// تابع کمکی برای تفسیر خطا و تصمیم‌گیری
void HandleTradeError(uint retcode)
  {
   switch(retcode)
     {
      case TRADE_RETCODE_REQUOTE:
         Print("Requote دریافت شد. قیمت جدید: ", trade.ResultPrice());
         // می‌توان منطق تکرار سفارش را اینجا فراخوانی کرد (با احتیاط).
         break;
      case TRADE_RETCODE_NOT_ENOUGH_MONEY:
         Print("خطا: موجودی یا مارجین کافی نیست.");
         // ممکن است لازم باشد ربات تا شارژ مجدد حساب یا بسته شدن معاملات سودده، متوقف شود.
         ExpertRemove(); // توقف کامل اکسپرت
         break;
      case TRADE_RETCODE_INVALID_STOPS:
         Print("خطا: سطوح Stop Loss یا Take Profit نامعتبرند.");
         // می‌توان SL/TP را با اضافه کردن یک offset ایمنی محاسبه و دوباره تلاش کرد.
         break;
      case TRADE_RETCODE_TIMEOUT:
         Print("خطا: زمان درخواست به پایان رسید. احتمال مشکل شبکه.");
         // لاگ کردن خطا و عدم تکرار فوری (برای جلوگیری از ارسال دوبله).
         break;
      default:
         Print("خطای معاملاتی ناشناخته یا عمومی. کد: ", retcode, " شرح: ", trade.ResultComment());
         break;
     }
  }

نکات حرفه‌ای برای جلوگیری از ریجکت شدن سفارش

برای به حداقل رساندن احتمال ریجکت (Reject) شدن سفارش‌ها توسط سرور، رعایت این نکات کلیدی ضروری است:

۱. نرمالیزه کردن (Normalization): تمامی اعداد اعشاری، به خصوص حجم معامله (Lot Size)، حد ضرر (Stop Loss)، حد سود (Take Profit) و قیمت‌ها باید با استفاده از NormalizeDouble() به تعداد ارقام اعشاری مناسب (معمولاً _Digits) گرد شوند. حجم نیز باید به گام حجم (SYMBOL_VOLUME_STEP) گرد شود.

volume = NormalizeDouble(volume, 2); // معمولاً دو رقم اعشار برای حجم
stopLoss = NormalizeDouble(stopLoss, _Digits);
takeProfit = NormalizeDouble(takeProfit, _Digits);

۲. رعایت فاصله حداقل (Stop Level): همیشه فاصله سطوح SL، TP و قیمت ورود از قیمت جاری باید بیشتر از SYMBOL_TRADE_STOPS_LEVEL (بر حسب پیپ) باشد. می‌توان یک تابع کمکی برای تصحیح سطوح نوشت.

double AdjustAboveStopLevel(double price, double referencePrice, string symbol)
{
   double point = SymbolInfoDouble(symbol, SYMBOL_POINT);
   long stopLevel = SymbolInfoInteger(symbol, SYMBOL_TRADE_STOPS_LEVEL);
   double minDistance = stopLevel * point;
   if(MathAbs(price - referencePrice) < minDistance)
   {
      if(price > referencePrice)
         price = referencePrice + minDistance;
      else
         price = referencePrice - minDistance;
      price = NormalizeDouble(price, _Digits);
   }
   return price;
}

۳. بررسی زمان بازار (Trading Hours): قبل از ارسال سفارش، از باز بودن بازار برای آن نماد اطمینان حاصل کنید. برخی نمادها (مثل شاخص‌ها) فقط در ساعات خاصی معامله می‌شوند.

۴. مدیریت منطق در شرایط اختیاری (Trading Context): آیا اکسپرت مجاز به معامله است؟ آیا حداکثر تعداد مجاز پوزیشن باز یا سفارش معلق از پیش تعیین‌شده رعایت شده است؟ این بررسی‌ها باید قبل از هر درخواست انجام شود.

۵. احتیاط در مورد قیمت‌های تاریخی: هنگام کار با سفارشات معلق، اطمینان حاصل کنید که قیمت فعال‌سازی (مثلاً برای Buy Stop) بالاتر از قیمت Ask فعلی است و برای Sell Stop پایین‌تر از Bid فعلی است. در غیر این صورت ممکن است بلافاصله به عنوان یک Market Order اجرا شود که ممکن است خواسته شما نباشد.

۶. استفاده از Refresh Rates: قبل از ارسال سفارش، با فراخوانی SymbolInfoTick(symbol, last_tick) یا SymbolSelect(symbol, true)، اطلاعات قیمت را برای آن نماد رفرش کنید تا از دریافت آخرین قیمت‌ها مطمئن شوید.

بررسی تاثیر اسپرد، اسلیپیج و حجم معامله

این سه فاکتور هزینه‌های پنهان معامله هستند که مستقیماً بر سودآوری ربات تأثیر می‌گذارند.

اسپرد (Spread): تفاوت بین قیمت Ask و Bid (Bid & Ask Price). برای یک سفارش خرید (Buy Order)، شما در Ask می‌خرید و بلافاصله معامله در ضرر به اندازه اسپرد قرار می‌گیرد. برای سودده شدن، قیمت باید به اندازه至少 اسپرد به نفع شما حرکت کند. ربات‌هایی که معاملات کوتاه‌مدت (اسکالپ) انجام می‌دهند، به ویژه به اسپرد حساس هستند. بهتر است در زمان‌هایی که اسپرد گسترده می‌شود (مثل زمان انتشار اخبار مهم یا نقدشوندگی پایین)، از معامله خودداری کرد.

double currentSpread = (askPrice - bidPrice) / _Point;
double maxAllowedSpread = 20; // حداکثر اسپرد مجاز به پیپ
if(currentSpread > maxAllowedSpread)
{
   Print("اسپرد بسیار بالاست. معامله متوقف می‌شود. اسپرد فعلی: ", currentSpread);
   return;
}

اسلیپیج (Slippage): تفاوت بین قیمت درخواستی شما و قیمت واقعی اجرای سفارش. در بازارهای سریع یا در زمان نوسان بالا، ممکن است قیمت بین لحظه ارسال درخواست و لحظه اجرا توسط سرور تغییر کند. اسلیپیج (Slippage) می‌تواند به نفع یا ضرر شما باشد، اما معمولاً به عنوان یک ریسک در نظر گرفته می‌شود. کلاس CTrade به صورت داخلی یک پارامتر deviation (انحراف مجاز) دارد. شما می‌توانید در متدهای سطح پایین‌تر مانند OrderSend این مقدار را تنظیم کنید، اما در CTrade مستقیماً در دسترس نیست. یک راه‌حل، استفاده از کلاس CTrade شخصی‌سازی‌شده یا توابع OrderSend مستقیم است.

حجم معامله (Lot Size): حجم بزرگ‌تر، سود و ضرر بالقوه بزرگ‌تری ایجاد می‌کند، اما همزمان مارجین (Margin) بیشتری قفل می‌کند و ممکن است بر مارجین آزاد (Free Margin) برای معاملات بعدی تأثیر بگذارد. همچنین، در نمادهای با نقدشوندگی کم، حجم‌های بسیار بزرگ می‌توانند خود بر قیمت بازار تأثیر بگذارند (Slipage بیشتر) یا حتی به طور کامل اجرا نشوند. محاسبه حجم بر اساس مدیریت ریسک (Risk Management)، بهترین روش برای تعادل‌بخشی بین پتانسیل سود و حفظ سرمایه است.

بهترین روش‌های طراحی منطق ارسال سفارش در ربات معامله‌گر

۱. جداسازی لایه‌ها (Separation of Concerns): منطق تحلیل بازار (سیگنال‌یابی)، منطق مدیریت ریسک (محاسبه حجم و سطوح) و منطق اجرای معامله (فراخوانی Buy/Sell) را در توابع یا کلاس‌های جداگانه قرار دهید. این کار تست، دیباگ و نگهداری کد را آسان‌تر می‌کند.

۲. استفاده از State Machine (ماشین حالت): ربات شما نباید بلافاصله پس از دریافت یک سیگنال، اقدام به معامله کند. بهتر است حالت‌هایی مانند CHECK_SIGNAL, CHECK_RISK, SEND_ORDER, WAIT_CONFIRMATION, MANAGE_POSITION داشته باشد. این کار از ارسال سفارش‌های تکراری یا نابهنگام جلوگیری می‌کند.

۳. پیاده‌سازی سیستم رویدادمحور: به جای چک کردن مداوم شرایط در OnTick، از رویدادهای متاتریدر مانند OnTrade، OnTradeTransaction، OnTick به صورت ترکیبی استفاده کنید. رویداد OnTradeTransaction به ویژه قدرتمند است زیرا برای هر تغییر در وضعیت معاملاتی (باز شدن پوزیشن، بسته شدن، تغییر SL/TP، ثبت سفارش معلق و …) فراخوانی می‌شود و جزئیات کامل تراکنش را در اختیار شما قرار می‌دهد.

۴. افزونگی و بازیابی از خطا: ربات باید بتواند پس از خطاهای متعدد شبکه یا ریاست، خود را بازیابی کند. ذخیره وضعیت مهم (مثلاً تیکت پوزیشن‌های باز) در متغیرهای سراسری و بررسی تطابق آنها با واقعیت حساب در زمان راه‌اندازی مجدد اکسپرت (OnInit) یک روش خوب است.

۵. تست逆向 (Inverse Testing): قبل از استفاده واقعی، ربات را در شرایط خطا (مثلاً قطعی اینترنت، Requote مکرر، عدم وجود مارجین) تست کنید تا پاسخ آن را بررسی نمایید.

نکات امنیتی و مدیریت سرمایه در ربات‌های خودکار

۱. محدود کردن ریسک در هر معامله: هرگز بیش از یک درصد کوچک (معمولاً ۱٪ تا ۲٪) از سرمایه حساب (Account Balance) را در یک معامله به خطر نیندازید. این قانون طلایی مدیریت ریسک (Risk Management) است که در بلندمدت بقای حساب شما را تضمین می‌کند.

۲. استفاده از حداکثر ضرر روزانه/هفتگی: منطقی در ربات قرار دهید که اگر کل ضرر شناور یا بسته‌شده در یک روز یا هفته به حد خاصی رسید، تمام پوزیشن‌ها را ببندد و تا دوره بعدی (مثلاً روز بعد) از معامله کردن دست بکشد. این از “انتقام‌جویی” ربات پس از یک سری ضرر جلوگیری می‌کند.

۳. محافظت در برابر Martingale و Averaging-down خطرناک: اگر استراتژی شما شامل میانگین کم کردن (آوردن میانگین) است، حتماً برای آن یک حداکثر تعداد لایه و یک حداکثر ریسک کلی تعریف کنید. افزایش بی‌رویه حجم پس از ضررها (مارتینگل) می‌تواند به سرعت حساب را نابود کند.

۴. تنظیمات امنیتی متاتریدر: از قابلیت “حداکثر ضرر مجاز” و “حداکثر سود مجاز” در تب Inputs اکسپرت استفاده کنید. همچنین، گزینه “اجازه معاملات خودکار” را در متاتریدر فعال کرده و از قابلیت رمزگذاری (Enable DLL) و محدود کردن معامله به نماد/حساب خاص استفاده نمایید.

۵. مانیتورینگ و لاگ‌گیری: تمام فعالیت‌های ربات، سیگنال‌ها، تصمیمات برای ارسال سفارش، پارامترهای محاسبه‌شده و به ویژه تمام خطاهای معاملاتی (Trade Errors) و نتیجه معامله (Trade Result) را در فایل لاگ (Print یا Logging.mqh) یا حتی در فایل CSV ذخیره کنید. این لاگ‌ها برای عیب‌یابی و بهینه‌سازی استراتژی حیاتی هستند.

۶. back-test و forward-test گسترده: هرگز رباتی را که به طور گسترده در تاریخچه بازار تست نشده و در حساب دمو (حتی بهتر: در حساب دمو با شرایط واقعی سرور) به مدت کافی (چند هفته یا ماه) بررسی نشده، روی حساب واقعی اجرا نکنید. تست در گذشته موفقیت آینده را تضمین نمی‌کند، اما بسیاری از باگ‌های فاحش منطق معاملاتی را آشکار می‌سازد.

با درک عمیق این مفاهیم و رعایت اصول ذکر شده، شما نه تنها قادر به ارسال سفارش خرید (Buy Order) و ارسال سفارش فروش (Sell Order) به صورت فنی خواهید بود، بلکه می‌توانید ربات‌های معامله‌گری بسازید که پایدار، ایمن و در بلندمدت قادر به حفظ و رشد سرمایه باشند. یادگیری MQL5 یک سفر مستمر است و تسلط بر بخش معاملاتی آن، سنگ بنای این سفر محسوب می‌شود.

دیدگاه‌ها (0)

  • نظرات نامربوط به محتوا تأیید نخواهند شد.
  • لطفاً از افزودن نظرات تکراری خودداری کنید.
  • نظرات مربوط به دوره‌ها فقط برای خریداران محصول است.

*
*