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

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

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

برنامه‌نویسی در متاتریدر 4 (MetaTrader 4) و زبان کد نویسی MQL4 (MQL4 Coding) به معامله‌گران این امکان را می‌دهد که استراتژی‌های معاملاتی خود را به‌صورت خودکار از طریق اکسپرت ادوایزر (Expert Advisor – EA) پیاده‌سازی کنند. یکی از اساسی‌ترین و حیاتی‌ترین عملکردهایی که یک اکسپرت ادوایزر باید قادر به انجام آن باشد، ارسال سفارش خرید (Buy Order) و سفارش فروش (Sell Order) به بروکر (Broker) است. این فرآیند نیازمند درک عمیقی از ساختار تابع OrderSend (OrderSend Function) و نحوه تعامل آن با سرور معاملاتی است. هدف این آموزش، ارائه یک راهنمای جامع، دقیق و عملی برای تسلط بر ارسال سفارشات در محیط MQL4 است.

درک بنیادین ارسال سفارش در MQL4

قبل از ورود به جزئیات کد نویسی MQL4 و استفاده از تابع OrderSend، لازم است مفاهیم بنیادی بازار فارکس و نحوه اجرای سفارشات را مرور کنیم. بازار ارزهای خارجی همواره بر پایه قیمت دوطرفه قیمت Bid و Ask (Bid and Ask Price) عمل می‌کند. قیمت Bid قیمتی است که در آن می‌توانید سفارش فروش (Sell Order) خود را اجرا کنید (یعنی بفروشید)، و قیمت Ask قیمتی است که در آن می‌توانید سفارش خرید (Buy Order) خود را اجرا کنید (یعنی بخرید). تفاوت این دو قیمت، اسپرد (Spread) نامیده می‌شود که منبع درآمد بروکر است.

وقتی شما تصمیمی برای ورود به معامله می‌گیرید، چه به‌صورت دستی و چه از طریق اکسپرت ادوایزر، باید مشخص کنید که آیا قصد دارید در جهت افزایش قیمت سود کسب کنید (خرید) یا در جهت کاهش قیمت (فروش). این تصمیم مستقیماً نحوه استفاده از تابع OrderSend را تعیین می‌کند.

پارامترهای حیاتی سفارشات: لات (Lot Size)، حد ضرر (Stop Loss)، و حد سود (Take Profit)

هر سفارشی که ارسال می‌کنید، باید دارای پارامترهای مشخصی باشد تا سیستم معاملاتی بتواند آن را مدیریت کند. این پارامترها شامل موارد زیر هستند:

  1. حجم معامله یا لات (Lot Size): تعیین‌کننده میزان ریسک و پتانسیل سود و زیان شماست. این مقدار باید با سیستم مدیریت سرمایه (Money Management) شما همخوانی داشته باشد. حجم می‌تواند به‌صورت استاندارد (1.0)، مینی (0.1) یا میکرو (0.01) تعریف شود.
  2. سطح ورود (Entry Price): قیمتی که سفارش شما در آن اجرا می‌شود. برای سفارش خرید، از قیمت Ask و برای سفارش فروش، از قیمت Bid استفاده می‌شود.
  3. سطح حد ضرر (Stop Loss – SL): قیمتی که در صورت حرکت بازار در خلاف جهت دلخواه، معامله به‌طور خودکار بسته می‌شود تا از زیان بیشتر جلوگیری شود. این یک جزء حیاتی مدیریت ریسک (Risk Management) است.
  4. سطح حد سود (Take Profit – TP): قیمتی که در صورت رسیدن بازار به آن سطح، معامله به‌طور خودکار بسته شده و سود محقق می‌شود.
  5. انحراف مجاز (Slippage): حداکثر انحرافی که حاضر هستید قیمت اجرای سفارش شما از قیمت درخواستی شما داشته باشد. این پارامتر در زمان نوسانات شدید بازار اهمیت پیدا می‌کند و از اجرای سفارش با قیمت‌های بسیار نامطلوب جلوگیری می‌کند.

آشنایی عمیق با تابع OrderSend (OrderSend Function)

قلب تپنده ارسال هر سفارشی در MQL4، تابع OrderSend است. این تابع به‌طور مداوم برای ارسال سیگنال‌های معاملاتی به سرور بروکر استفاده می‌شود. ساختار کلی این تابع بسیار مهم است و باید تمام پارامترهای آن را به‌دقت درک کرد:

int OrderSend(
   string symbol,             // نماد معاملاتی (مثلاً "EURUSD")
   int cmd,                   // نوع معامله (OP_BUY یا OP_SELL)
   double volume,             // حجم لات (Lot Size)
   double price,              // قیمت مورد نظر برای اجرا
   int slippage,              // حداکثر اسلیپیج مجاز (بر حسب پیپ)
   double stoploss,           // سطح حد ضرر (Stop Loss)
   double takeprofit,         // سطح حد سود (Take Profit)
   string comment=NULL,       // توضیحات اختیاری برای سفارش
   int magic=0,               // عدد جادویی برای شناسایی سفارشات این اکسپرت
   datetime expiration=0,     // زمان انقضا برای سفارشات معلق (Pending Orders)
   color arrow_color=clrNONE  // رنگ پیکان روی چارت برای نمایش سفارش
);

تحلیل پارامترهای تابع OrderSend

  1. symbol: یک رشته متنی است که نشان‌دهنده نماد معاملاتی است که می‌خواهید روی آن معامله کنید. معمولاً از Symbol() برای استفاده از نماد چارتی که اکسپرت ادوایزر روی آن اجرا می‌شود، استفاده می‌شود.
  2. cmd (Command Type): این پارامتر تعیین‌کننده نوع سفارش خرید یا سفارش فروش است. مقادیر مجاز برای سفارشات بازار (Market Orders) عبارتند از:
    • OP_BUY: برای ارسال سفارش خرید (Market Buy).
    • OP_SELL: برای ارسال سفارش فروش (Market Sell).
    • (همچنین مقادیری برای سفارشات معلق مانند OP_BUYLIMIT, OP_SELLLIMIT, OP_BUYSTOP, OP_SELLSTOP وجود دارند که در این آموزش فعلاً تمرکز بر سفارشات مارکت است.)
  3. volume: حجم معامله به لات (Lot Size) است. این مقدار باید یک عدد مثبت باشد و با حداقل و حداکثر حجم مجاز بروکر شما مطابقت داشته باشد. مثلاً 0.1 برای یک مینی لات.
  4. price: این پارامتر بسیار حساس است و بستگی به نوع سفارش (Buy یا Sell) دارد:
    • اگر cmd برابر با OP_BUY باشد، price باید قیمت Ask جاری باشد.
    • اگر cmd برابر با OP_SELL باشد، price باید قیمت Bid جاری باشد. عدم رعایت این اصل یکی از رایج‌ترین خطاهای معاملاتی (Trade Errors) است.
  5. slippage: حداکثر انحراف مجاز از قیمت درخواستی، بر حسب نقطه (Point) یا پیپ (Pip). اگر این مقدار صفر باشد، اجرای سفارش فقط در صورتی مجاز است که قیمت دقیقاً همان قیمتی باشد که در پارامتر price ارسال شده است. در بازارهای پرنوسان، بهتر است مقدار کمی (مثلاً 1 تا 3 نقطه) برای اسلیپیج (Slippage) در نظر بگیرید. توجه داشته باشید که در MQL4، یک پیپ معمولاً 10 برابر یک نقطه است (برای کارگزاری‌هایی که تا 5 رقم اعشار قیمت می‌دهند). در کد نویسی MQL4 برای محاسبه تعداد نقاط، از تابع _Point استفاده می‌شود.
  6. stoploss: قیمت سطح حد ضرر (Stop Loss). اگر 0 یا نامعتبر باشد، سفارشی بدون SL ارسال می‌شود (که شدیداً در مدیریت ریسک نامطلوب است). برای تبدیل پیپ به قیمت، باید محاسبات دقیقی انجام شود.
  7. takeprofit: قیمت سطح حد سود (Take Profit). مشابه SL، اگر 0 یا نامعتبر باشد، سفارشی بدون TP ارسال می‌شود.
  8. comment و magic: این دو پارامتر برای سازماندهی و شناسایی سفارشات بسیار مهم هستند. magic یک عدد صحیح منحصربه‌فرد است که اکسپرت ادوایزر شما از آن برای تشخیص سفارشاتی که خود ایجاد کرده است، استفاده می‌کند.
  9. expiration: برای سفارشات مارکت معمولاً صفر باقی می‌ماند، زیرا سفارشات مارکت باید بلافاصله اجرا شوند. این پارامتر برای سفارشات معلق (Pending) کاربرد دارد.

تفاوت حیاتی اجرای سفارش خرید و سفارش فروش

نحوه انتخاب قیمت در تابع OrderSend تفاوت اصلی بین سفارش خرید و سفارش فروش است.

برای سفارش خرید (OP_BUY): شما می‌خواهید دارایی را با قیمت پایین بخرید و بعداً با قیمت بالاتر بفروشید. بنابراین، شما باید سفارش خود را با قیمت Ask ارسال کنید.

[ \text{قیمت ورود برای Buy} = \text{Ask Price} ]

برای سفارش فروش (OP_SELL): شما می‌خواهید دارایی را با قیمت بالا بفروشید (Short Sell) و بعداً با قیمت پایین‌تر بخرید تا سود کسب کنید. بنابراین، شما باید سفارش خود را با قیمت Bid ارسال کنید.

[ \text{قیمت ورود برای Sell} = \text{Bid Price} ]

برای دسترسی به قیمت‌های جاری، توابع Ask و Bid در MQL4 استفاده می‌شوند.

محاسبات لازم برای SL و TP: تبدیل پیپ به قیمت

یکی از چالش‌های رایج در کد نویسی MQL4، تبدیل سطوح حد ضرر و حد سود که معمولاً به‌صورت فاصله (بر حسب پیپ) در ذهن برنامه‌نویس تعریف می‌شوند، به قیمت‌های عددی لازم برای پارامترهای stoploss و takeprofit در تابع OrderSend است.

ابتدا باید بدانیم که تابع Point() در MQL4 اندازه یک نقطه را برمی‌گرداند. برای حساب‌هایی که 4 رقم اعشار دارند، Point() برابر با 0.0001 است. برای حساب‌هایی که 5 رقم اعشار دارند، Point() برابر با 0.00001 است.

اگر فاصله مورد نظر شما بر حسب پیپ (Pip) باشد (مثلاً 50 پیپ)، باید آن را به تعداد نقاط ترجمه کنید. در حساب‌های 5 رقمی، 1 پیپ برابر با 10 نقطه است.

[ \text{تعداد نقاط SL/TP} = \text{تعداد پیپ‌ها} \times 10 \times \text{Point()} ]

اگر از تابع Digits() برای تعیین تعداد ارقام اعشار استفاده کنیم، رابطه کلی پیچیده‌تر می‌شود اما ساده‌ترین راه این است که فاصله بر حسب نقطه را محاسبه کنیم:

double pipsToPoints(int pips)
{
    // اگر Digits برابر 3 یا 5 باشد (مضاعف 10)
    if (Digits == 3 || Digits == 5)
    {
        return pips * 10 * _Point;
    }
    // اگر Digits برابر 2 یا 4 باشد
    else
    {
        return pips * _Point;
    }
}

با داشتن فاصله بر حسب قیمت (مثلاً slDistance بر حسب قیمت)، نحوه محاسبه سطح قیمت به این صورت است:

برای سفارش خرید (Buy):
[ \text{Stop Loss Price} = \text{Ask Price} – \text{Distance in Price} ] [ \text{Take Profit Price} = \text{Ask Price} + \text{Distance in Price} ]

برای سفارش فروش (Sell):
[ \text{Stop Loss Price} = \text{Bid Price} + \text{Distance in Price} ] [ \text{Take Profit Price} = \text{Bid Price} – \text{Distance in Price} ]

توجه داشته باشید که مقادیر SL و TP باید با سطح دقت قیمتی بروکر سازگار باشند. برای اطمینان، بهتر است قیمت‌های محاسبه‌شده را با تابع NormalizeDouble به تعداد ارقام اعشار صحیح گرد کنیم:

double normalizedSL = NormalizeDouble(calculated_sl_price, Digits);

ارسال سفارش خرید (Buy Order) ساده

فرض کنید می‌خواهیم یک سفارش خرید با حجم 0.1 لات روی نماد فعلی (EURUSD مثلاً) باز کنیم. در این مثال ساده، ما حد ضرر و حد سود را تعریف نمی‌کنیم (که در عمل توصیه نمی‌شود، اما برای نمایش پایه تابع OrderSend مفید است).

// --- متغیرها
string symbolName = Symbol();
double lotSize = 0.1;
int magicNumber = 12345;

// --- اجرای سفارش خرید
void SendSimpleBuy()
{
    // دریافت قیمت Ask برای ورود به خرید
    double entryPrice = Ask; 
    
    // ارسال سفارش با OrderSend
    int ticket = OrderSend(
        symbolName,        // نماد
        OP_BUY,            // نوع: خرید
        lotSize,           // حجم
        entryPrice,        // قیمت ورود (Ask)
        3,                 // اسلیپیج مجاز (3 نقطه)
        0,                 // بدون حد ضرر
        0,                 // بدون حد سود
        "Simple Buy Order", // کامنت
        magicNumber,       // عدد جادویی
        0,                 // عدم انقضا
        clrBlue            // رنگ پیکان آبی
    );

    // بررسی نتیجه
    if (ticket > 0)
    {
        Print("سفارش خرید با موفقیت ارسال شد. تیکت: ", ticket);
    }
    else
    {
        Print("خطا در ارسال سفارش خرید. کد خطا: ", GetLastError());
    }
}

ارسال سفارش فروش (Sell Order) ساده

مشابه مثال بالا، اما برای سفارش فروش، باید از قیمت Bid استفاده کنیم و نوع دستور را OP_SELL قرار دهیم.

// --- متغیرها
string symbolName = Symbol();
double lotSize = 0.1;
int magicNumber = 12345;

// --- اجرای سفارش فروش
void SendSimpleSell()
{
    // دریافت قیمت Bid برای ورود به فروش
    double entryPrice = Bid; 
    
    // ارسال سفارش با OrderSend
    int ticket = OrderSend(
        symbolName,        // نماد
        OP_SELL,           // نوع: فروش
        lotSize,           // حجم
        entryPrice,        // قیمت ورود (Bid)
        3,                 // اسلیپیج مجاز (3 نقطه)
        0,                 // بدون حد ضرر
        0,                 // بدون حد سود
        "Simple Sell Order", // کامنت
        magicNumber,       // عدد جادویی
        0,                 // عدم انقضا
        clrRed             // رنگ پیکان قرمز
    );

    // بررسی نتیجه
    if (ticket > 0)
    {
        Print("سفارش فروش با موفقیت ارسال شد. تیکت: ", ticket);
    }
    else
    {
        Print("خطا در ارسال سفارش فروش. کد خطا: ", GetLastError());
    }
}

ارسال سفارش همراه با حد ضرر و حد سود

استفاده صحیح از حد ضرر و حد سود سنگ بنای مدیریت ریسک در اکسپرت ادوایزر است. فرض کنید می‌خواهیم یک سفارش خرید 0.2 لات باز کنیم، با 40 پیپ حد ضرر و 80 پیپ حد سود.

ابتدا باید محاسبه کنیم که 40 پیپ و 80 پیپ در قیمت فعلی بازار چقدر فاصله قیمتی دارند. فرض می‌کنیم حساب ما 4 رقم اعشار دارد و از تابع کمکی برای تبدیل پیپ به قیمت استفاده می‌کنیم.

// --- پارامترهای ریسک
int riskPipsSL = 40;
int riskPipsTP = 80;
double volume = 0.2;

// تابع کمکی برای تبدیل پیپ به قیمت با در نظر گرفتن Point()
double ConvertPipsToPrice(int pips)
{
    // فرض می‌کنیم 10 نقطه در 1 پیپ برای حساب‌های 5 رقمی
    int multiplier = (Digits == 3 || Digits == 5) ? 10 : 1;
    return pips * multiplier * _Point;
}

void SendBuyWithSLTP()
{
    string symbolN = Symbol();
    double entryAsk = Ask;
    int magicN = 12345;

    // محاسبه فاصله قیمتی
    double slDistance = ConvertPipsToPrice(riskPipsSL);
    double tpDistance = ConvertPipsToPrice(riskPipsTP);
    
    // محاسبه سطوح قیمت SL و TP
    double stopLossPrice = entryAsk - slDistance; // برای خرید، SL پایین‌تر از قیمت ورود است
    double takeProfitPrice = entryAsk + tpDistance; // برای خرید، TP بالاتر از قیمت ورود است
    
    // اطمینان از نرمال‌سازی قیمت‌ها
    stopLossPrice = NormalizeDouble(stopLossPrice, Digits);
    takeProfitPrice = NormalizeDouble(takeProfitPrice, Digits);

    // ارسال سفارش
    int ticket = OrderSend(
        symbolN, 
        OP_BUY, 
        volume, 
        entryAsk, 
        5, // اسلیپیج 5 نقطه
        stopLossPrice, 
        takeProfitPrice, 
        "Buy w/ SL/TP", 
        magicN, 
        0, 
        clrAqua
    );
    
    if (ticket > 0)
    {
        Print("خرید با SL/TP موفق. SL: ", stopLossPrice, ", TP: ", takeProfitPrice);
    }
    else
    {
        Print("خطا در ارسال خرید با SL/TP. کد خطا: ", GetLastError());
    }
}

بررسی و مدیریت خطاهای معاملاتی (Trade Errors)

پس از فراخوانی تابع OrderSend، اگر مقدار بازگشتی منفی باشد، عملیات ناموفق بوده است. برای تشخیص دقیق علت شکست، باید از تابع GetLastError() بلافاصله پس از شکست استفاده کرد. درک کدهای خطا برای دیباگ کردن اکسپرت ادوایزر ضروری است.

برخی از رایج‌ترین خطاهای معاملاتی عبارتند از:

  1. Error 129: Invalid Stops: این خطا معمولاً زمانی رخ می‌دهد که سطح حد ضرر یا حد سود ارسالی خارج از محدوده مجاز تعیین‌شده توسط بروکر باشد، یا فاصله SL/TP نسبت به قیمت جاری کمتر از حداقل مجاز (Minimum Distance) باشد.
  2. Error 130: Invalid Volume: حجم ارسالی (Lot Size) خارج از محدوده‌های مجاز (حداقل، حداکثر، یا گام‌های حجمی) است.
  3. Error 131: Invalid Price: قیمت ارسالی (یا قیمت SL/TP) با سطح دقت مورد نیاز بروکر مطابقت ندارد، یا قیمت ورودی برای سفارش خرید، قیمت Bid است نه Ask، یا بالعکس.
  4. Error 132: Invalid Lots: این خطا اغلب زمانی رخ می‌دهد که حجم معامله مورد نظر شما (مثلاً 0.001 لات) توسط بروکر پشتیبانی نشود.
  5. Error 135: Market Closed: تلاش برای ارسال سفارش در زمانی که بازار بسته است (مثلاً آخر هفته).
  6. Error 148: Requote: این خطا بیشتر مربوط به سفارشات معلق است، اما می‌تواند در شرایط نوسان شدید برای سفارشات مارکت نیز رخ دهد، به این معنی که قیمت مورد نظر شما دیگر در دسترس نیست و سرور نیاز به ارسال قیمت جدید دارد.

در کد نویسی MQL4، باید یک ساختار شرطی برای بررسی این خطاها پس از فراخوانی تابع OrderSend تعبیه شود:

// ... پس از OrderSend
if (ticket <= 0)
{
    int err = GetLastError();
    string errMsg = "";
    
    switch(err)
    {
        case 129: errMsg = "Invalid Stops - SL/TP خارج از محدوده مجاز است."; break;
        case 130: errMsg = "Invalid Volume - حجم لات نامعتبر است."; break;
        case 131: errMsg = "Invalid Price - قیمت ارسالی نامعتبر است."; break;
        case 135: errMsg = "Market Closed - بازار بسته است."; break;
        default: errMsg = "خطای نامشخص: " + IntegerToString(err); break;
    }
    Print("فرایند ارسال سفارش شکست خورد. خطا: ", errMsg);
}

اهمیت مدیریت سرمایه (Money Management) و محاسبه حجم لات

هیچ اکسپرت ادوایزر حرفه‌ای بدون یک سیستم مدیریت سرمایه قوی معنی ندارد. ارسال سفارش با حجم ثابت (Fixed Lot) در طول زمان، به‌ویژه در صورت توالی باخت‌ها، منجر به از بین رفتن کل حساب می‌شود. مدیریت ریسک نیازمند محاسبه دینامیک حجم لات بر اساس میزان ریسک‌پذیری و وضعیت فعلی حساب است.

هدف اصلی مدیریت سرمایه این است که در هر معامله، مقدار دلاری مشخصی از سرمایه را به خطر نیندازیم، بلکه درصد مشخصی از کل سرمایه را به خطر بیندازیم.

فرمول محاسبه ریسک در هر معامله:

[ \text{ریسک دلاری در هر معامله} = \text{Equity} \times \text{درصد ریسک مجاز} ]

[ \text{حجم لات قابل قبول} = \frac{\text{ریسک دلاری}}{\text{ارزش یک نقطه برای یک لات استاندارد}} ]

بیایید این محاسبات را برای یک سفارش خرید انجام دهیم. فرض کنید می‌خواهیم در هر معامله حداکثر 1% از اکوئیتی (Equity) را ریسک کنیم.

  1. محاسبه ریسک دلاری:
    double riskAmount = AccountEquity() * (RiskPercentage / 100.0);
  2. محاسبه فاصله SL بر حسب قیمت:
    فرض کنید SL را 50 پیپ در نظر گرفته‌ایم. این مقدار باید به قیمت تبدیل شود (با استفاده از تابع ConvertPipsToPrice قبلی).
  3. محاسبه ارزش یک پیپ برای یک لات استاندارد (1.0):
    این مقدار به نماد بستگی دارد. برای جفت‌های EURUSD، USDJPY و غیره، می‌توان از تابع MarketInfo استفاده کرد: double pointValueForOneLot = MarketInfo(Symbol(), MODE_TICKVALUE); توجه: MODE_TICKVALUE معمولاً ارزش حرکت یک تیک (یک نقطه) برای 1 لات را برمی‌گرداند. برای پیپ (اگر 4 رقمی است)، باید آن را در 10 ضرب کنید. یک روش مطمئن‌تر این است که ارزش یک پیپ کامل (10 نقطه در حساب 5 رقمی) را محاسبه کنیم.

    برای سادگی و دقت بیشتر (به‌ویژه در حساب‌های 5 رقمی)، می‌توانیم از این فرمول برای یافتن ارزش پیپ برای 1 لات استفاده کنیم:

    double pipValueForOneLot(string sym)
    {
        // محاسبه ارزش یک پیپ برای یک لات استاندارد (1.0)
        double tickSize = MarketInfo(sym, MODE_TICKSIZE); // اندازه کوچکترین تغییر قیمت (نقطه)
        double tickValue = MarketInfo(sym, MODE_TICKVALUE); // ارزش دلاری آن تیک
        
        int pipMultiplier = (Digits == 3 || Digits == 5) ? 10 : 1; 
        
        // ارزش یک پیپ = ارزش یک تیک * تعداد تیک‌ها در یک پیپ
        return tickValue * pipMultiplier;
    }
    
  4. محاسبه حجم لات قابل قبول:

    [ \text{حجم لات} = \frac{\text{ریسک دلاری}}{\text{فاصله SL بر حسب قیمت} \times \text{ارزش یک لات در SL}} ]

    اگر فاصله SL بر حسب قیمت (مثلاً 0.0050) باشد، باید آن را به تعداد پیپ تبدیل کنیم و سپس از ارزش پیپ استفاده کنیم.

    روش استاندارد و عملی:

    double CalculateLotSize(double equityRiskPercent, double slInPips)
    {
        double currentEquity = AccountEquity();
        double riskAmount = currentEquity * (equityRiskPercent / 100.0);
        
        // محاسبه فاصله SL بر حسب قیمت
        double slDistancePrice = ConvertPipsToPrice(slInPips);
        
        // محاسبه ارزش یک پیپ برای 1 لات (مثلاً 100,000 واحد پایه)
        double pipValuePerLot = pipValueForOneLot(Symbol()); 
        
        // مقدار دلاری ریسک شده به ازای 1 لات
        double riskPerLot = slDistancePrice / _Point * pipValuePerLot; 
        // توجه: تقسیم بر _Point برای تبدیل فاصله قیمتی به تعداد پیپ‌های واقعی است، سپس ضرب در ارزش دلاری هر پیپ.
        
        // حجم لات مورد نیاز
        double requiredLots = riskAmount / riskPerLot;
        
        // نرمال‌سازی حجم لات به گام‌های مجاز بروکر
        double lotStep = MarketInfo(Symbol(), MODE_LOTSTEP);
        double minLot = MarketInfo(Symbol(), MODE_MINLOT);
        double maxLot = MarketInfo(Symbol(), MODE_MAXLOT);
        
        // تنظیم حجم لات بر اساس حداقل، حداکثر و گام
        requiredLots = MathFloor(requiredLots / lotStep) * lotStep;
        
        if (requiredLots < minLot) requiredLots = minLot;
        if (requiredLots > maxLot) requiredLots = maxLot;
        
        return MathMax(minLot, requiredLots); // اطمینان از اینکه حداقل 1 مینی لات باشد
    }
    

این تابع تضمین می‌کند که اکسپرت ادوایزر شما هرگز بیش از حد مجاز تعیین‌شده (مثلاً 1%) را در یک معامله به خطر نیندازد، که این امر در مدیریت ریسک حیاتی است.

ارسال سفارش بر اساس شرط (استفاده از اندیکاتور)

در دنیای واقعی، سفارشات بر اساس سیگنال‌های تولیدشده توسط اندیکاتورها یا الگوریتم‌های تحلیلی ارسال می‌شوند. فرض کنید یک سیگنال خرید زمانی صادر می‌شود که میانگین متحرک نمایی (EMA) کوتاه‌مدت، میانگین متحرک بلندمدت را به سمت بالا قطع کند (Golden Cross).

برای این کار، ابتدا باید قیمت‌های اندیکاتور را محاسبه کنیم.

// تعریف متغیرهای اندیکاتور
int fastMAPeriod = 10;
int slowMAPeriod = 50;

// --- در تابع OnInit یا در شروع محاسبه
// تعریف هندل‌ها
int fastMAHandle;
int slowMAHandle;

int OnInit()
{
    // 0 = نرخ فعلی، fastMAPeriod = دوره، PRICE_CLOSE = قیمت بسته شدن
    fastMAHandle = iMA(Symbol(), Period(), fastMAPeriod, 0, MODE_EMA, PRICE_CLOSE);
    slowMAHandle = iMA(Symbol(), Period(), slowMAPeriod, 0, MODE_EMA, PRICE_CLOSE);
    
    if(fastMAHandle == INVALID_HANDLE || slowMAHandle == INVALID_HANDLE)
    {
        Print("خطا در ایجاد هندل اندیکاتورها.");
        return(INIT_FAILED);
    }
    return(INIT_SUCCEEDED);
}

// --- در تابع OnTick
void CheckForGoldenCross()
{
    // آرایه‌هایی برای دریافت مقادیر EMA
    double fastMAValues[2];
    double slowMAValues[2];
    
    // کپی کردن آخرین دو مقدار (اندیس 0: کندل فعلی، اندیس 1: کندل بسته شده قبلی)
    if(CopyBuffer(fastMAHandle, 0, 0, 2, fastMAValues) <= 0 || 
       CopyBuffer(slowMAHandle, 0, 0, 2, slowMAValues) <= 0)
    {
        Print("خطا در کپی کردن مقادیر اندیکاتور.");
        return;
    }
    
    // اگر کندل فعلی هنوز کامل نشده است، فقط کندل قبلی را بررسی می‌کنیم (اندیس 1)
    double prevFastMA = fastMAValues[1];
    double prevSlowMA = slowMAValues[1];
    
    // بررسی سیگنال: آیا در کندل قبلی، EMA سریع، EMA کند را به بالا قطع کرده است؟
    bool signalBuy = (prevFastMA > prevSlowMA) && (fastMAValues[0] < slowMAValues[0]);
    
    // اطمینان از عدم وجود پوزیشن باز برای جلوگیری از ارسال مکرر سفارش
    if (OrdersTotal() == 0 && signalBuy)
    {
        // اگر سیگنال Buy داشتیم، حجم لات را بر اساس مدیریت ریسک محاسبه می‌کنیم
        double lots = CalculateLotSize(1.0, 50); // 1% ریسک، 50 پیپ SL
        
        int ticket = OrderSend(
            Symbol(), 
            OP_BUY, 
            lots, 
            Ask, 
            5, 
            Ask - ConvertPipsToPrice(50), // محاسبه SL
            Ask + ConvertPipsToPrice(100), // محاسبه TP (100 پیپ)
            "EMA Cross Buy", 
            12345, 
            0, 
            clrGreen
        );
        
        if (ticket > 0)
        {
            Print("سیگنال خرید تایید شد و سفارش ارسال گردید.");
        }
    }
}

ارسال سفارش در حساب دمو و حساب ریل

یکی از نکات مهم در کد نویسی MQL4 مربوط به محیط اجرای اکسپرت ادوایزر است. تفاوت اصلی بین اجرای کد در حساب دمو (Demo Account) و حساب واقعی (Real Account) معمولاً در سطح دسترسی و قوانین خاص بروکر است، اما خود تابع OrderSend در هر دو محیط به یک شکل فراخوانی می‌شود.

با این حال، حساب‌های دمو ممکن است گاهی اوقات از اجرای دستورات جلوگیری کنند (مثلاً در شبیه‌سازی‌های خاص بک‌تست)، اما در حالت اجرای زنده (Live Trading) روی دمو، عملکرد دقیقاً مشابه حساب ریل است.

نکته عملی برای تشخیص محیط:
می‌توانید از تابع IsTradeAllowed() استفاده کنید تا مطمئن شوید که اکسپرت ادوایزر اجازه معامله دارد، یا از AccountInfoInteger(ACCOUNT_TRADE_MODE) برای بررسی نوع حساب (دمو یا ریل) استفاده کنید.

void CheckEnvironmentAndSend()
{
    if (!IsTradeAllowed())
    {
        Print("اجرای معامله در این حساب مجاز نیست.");
        return;
    }
    
    int tradeMode = AccountInfoInteger(ACCOUNT_TRADE_MODE);
    if (tradeMode == ACCOUNT_TRADE_MODE_DEMO)
    {
        Print("در حال اجرای تست روی حساب دمو.");
    }
    else if (tradeMode == ACCOUNT_TRADE_MODE_REAL)
    {
        Print("در حال اجرای زنده روی حساب ریل. احتیاط لازم است!");
    }
    
    // ادامه فراخوانی OrderSend
    SendSimpleBuy();
}

در حساب‌های ریل، اجرای سریع و بدون خطا اهمیت مضاعف پیدا می‌کند، زیرا اسلیپیج و Requote می‌توانند تأثیر مالی مستقیم داشته باشند.

مشکلات رایج و راهکارهای پیشرفته

همانطور که اشاره شد، ارسال سفارش موفق تنها نیمی از ماجراست؛ مدیریت موفقیت یا شکست ارسال سفارش بسیار مهم است.

Requote و Slippage

اسلیپیج (Slippage) عدم تطابق بین قیمت درخواستی و قیمت اجرایی است. اگرچه ما در تابع OrderSend پارامتری برای کنترل آن (Slippage) تعریف می‌کنیم، اما در شرایط نوسان بالا، بروکر ممکن است با خطای 148 (Requote) پاسخ دهد. این یعنی قیمت مورد نظر شما در لحظه رسیدن سفارش به سرور تغییر کرده است.

راهکار: اگر با خطای Requote مواجه شدید، بهترین عمل این است که قیمت‌های جاری (Ask یا Bid) را مجدداً دریافت کرده و سفارش را با قیمت جدید و پارامتر اسلیپیج کمی بزرگ‌تر تکرار کنید. این کار معمولاً در یک حلقه تکرار (Loop) مدیریت می‌شود.

Invalid Stops و محدودیت‌های بروکر

بزرگترین مانع در اجرای حد ضرر و حد سود، محدودیت‌های تعیین‌شده توسط بروکر است. هر بروکر حداقل فاصله‌ای (Minimum Distance) را بین قیمت ورود و سطح SL/TP تعیین می‌کند (مثلاً 15 پیپ برای EURUSD). اگر قیمت SL/TP محاسبه شده شما کمتر از این فاصله باشد، با خطای 129 (Invalid Stops) مواجه خواهید شد.

راهکار: قبل از ارسال سفارش، باید مطمئن شویم که SL/TP محاسبه شده با حداقل فاصله مجاز مطابقت دارد. این فاصله را می‌توان از طریق MarketInfo(Symbol(), MODE_STOPLEVEL) دریافت کرد.

int minimumStopLevel = (int)MarketInfo(Symbol(), MODE_STOPLEVEL);

// محاسبه SL برای خرید
double requiredSL = Ask - ConvertPipsToPrice(50); 

// بررسی انطباق با حداقل فاصله قانونی
if (MathAbs(Ask - requiredSL) / _Point < minimumStopLevel)
{
    // اگر کمتر از حداقل فاصله بود، SL را روی حداقل فاصله تنظیم می‌کنیم
    requiredSL = Ask - (minimumStopLevel * _Point);
    Print("SL تنظیم مجدد شد به دلیل کم بودن فاصله.");
}

این رویکرد تضمین می‌کند که اکسپرت ادوایزر همواره سفارشاتی با پارامترهای معتبر ارسال کند.

مدیریت سفارشات باز و بستن آن‌ها

ارسال سفارش تنها یک بخش از چرخه است. اکسپرت ادوایزر باید قادر به مدیریت سفارشات باز خود باشد. برای بستن یک سفارش، از تابع OrderClose() استفاده می‌شود و نیاز به تیکت (Ticket Number) سفارش دارد.

void CloseOrderByTicket(int ticketToClose)
{
    // دریافت اطلاعات سفارش با استفاده از تیکت
    if (OrderSelect(ticketToClose, SELECT_BY_TICKET, MODE_TRADES))
    {
        int cmd = OrderType();
        double currentPrice;
        
        // بستن خرید با Bid، بستن فروش با Ask
        if (cmd == OP_BUY)
        {
            currentPrice = Bid;
        }
        else if (cmd == OP_SELL)
        {
            currentPrice = Ask;
        }
        else 
        {
            Print("سفارش مورد نظر یک سفارش مارکت باز نیست.");
            return;
        }
        
        // اجرای OrderClose
        bool success = OrderClose(ticketToClose, OrderLots(), currentPrice, 5, clrNONE);
        
        if (success)
        {
            Print("سفارش تیکت ", ticketToClose, " با موفقیت بسته شد.");
        }
        else
        {
            Print("خطا در بستن سفارش تیکت ", ticketToClose, ". کد خطا: ", GetLastError());
        }
    }
}

این ساختار تضمین می‌کند که هنگام بستن سفارش خرید، از قیمت Bid و هنگام بستن سفارش فروش، از قیمت Ask استفاده می‌شود، که این امر اجرای دقیق و بدون زیان اضافی ناشی از اسپرد را ممکن می‌سازد.

در نهایت، تسلط بر تابع OrderSend، درک عمیق قیمت Bid و Ask، و ترکیب آن با اصول سختگیرانه مدیریت سرمایه و مدیریت ریسک، سنگ بنای ساخت یک اکسپرت ادوایزر پایدار و سودآور در متاتریدر 4 است. تمام جنبه‌های کد نویسی MQL4 باید با احتیاط و توجه به جزئیات فنی بروکر پیاده‌سازی شوند تا از خطاهای معاملاتی رایج جلوگیری شود.

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

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

*
*