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

دریافت اطلاعات نماد معاملاتی در MQL

دریافت اطلاعات نماد معاملاتی در MQL

هسته هر استراتژی معاملاتی خودکار در پلتفرم متاتریدر (MetaTrader)، چه در نسخه چهارم و چه در نسخه پنجم، حول مفهوم نماد معاملاتی (Trading Symbol) می‌چرخد. یک نماد معاملاتی، در ساده‌ترین تعریف، نشانگر یک دارایی مالی است که قابلیت خرید و فروش دارد؛ مانند EURUSD در بازار فارکس، طلا (XAUUSD) در بازار کالا، یا شاخص داوجونز (US30) در بازار سهام. این نمادها حاوی مجموعه‌ای پیچیده و حیاتی از داده‌های دینامیک و استاتیک هستند که ربات معامله‌گر (Trading Bot) برای تصمیم‌گیری منطقی و اجرای دقیق معاملات، به شدت به آن‌ها وابسته است. بدون دسترسی دقیق و صحیح به این اطلاعات، هر اکسپرت یا اسکریپتی محکوم به شکست است، چرا که در محاسبات بنیادین خود مانند حجم معامله، سطوح حد ضرر و سود، و مدیریت ریسک، دچار خطاهای فاجعه‌باری خواهد شد.

در دنیای برنامه‌نویسی MQL (MQL Programming)، دریافت و پردازش این اطلاعات نماد (Symbol Information) یک مهارت پایه‌ای و در عین حال حیاتی محسوب می‌شود. این مقاله به صورت عمیق و جامع، به بررسی تمامی جنبه‌های مربوط به دریافت اطلاعات نماد در MQL4 و MQL5 می‌پردازد. ما از تعاریف اولیه تا پیاده‌سازی‌های پیشرفته را پوشش داده و با ارائه مثال‌های متعدد خط‌به‌خط، درک این مبحث را برای توسعه‌دهندگان در هر سطحی تسهیل می‌کنیم.

نماد معاملاتی و نقش آن در اکوسیستم متاتریدر

در پلتفرم متاتریدر، یک نماد معاملاتی تنها یک نام یا برچسب نیست، بلکه یک موجودیت کامل است که تمامی ویژگی‌های مالی و معاملاتی یک دارایی را در خود کپسوله کرده است. هر نماد به یک «مشخصه» (Specification) متصل است که توسط بروکر تعریف می‌شود و پارامترهایی ثابت مانند نام، نوع دارایی، قرارداد大小، و همچنین پارامترهای دینامیکی مانند قیمت جاری، اسپرد و حجم را شامل می‌شود. زمانی که شما در متاتریدر نمودار EURUSD را باز می‌کنید، در حقیقت در حال مشاهده و تعامل با یک نمونه (Instance) از این موجودیت هستید. ربات‌های معامله‌گر نیز دقیقاً با همین موجودیت‌ها سروکار دارند. آن‌ها باید بتوانند به این داده‌ها دسترسی پیدا کنند، آن‌ها را تفسیر کنند و بر اساس آن‌ها منطق معاملاتی خود را اجرا نمایند. درک ساختار این موجودیت اولین گام در طراحی هر سیستم خودکار است.

اهمیت حیاتی اطلاعات نماد در طراحی ربات معامله‌گر

اهمیت اطلاعات نماد را می‌توان در چند محور کلیدی خلاصه کرد:

  • دقت محاسباتی: تمامی محاسبات مالی یک اکسپرت، از محاسبه حجم معامله (Position Sizing) گرفته تا محاسبه حد ضرر و حد سود (Stop Loss / Take Profit)، وابسته به مقادیری مانند ارزش هر پوینت، اندازه لات استاندارد، و تعداد ارقام اعشار قیمت است. یک خطای کوچک در دریافت این اطلاعات می‌تواند منجر به محاسبه حجم معامله‌ای بسیار بزرگتر یا کوچک‌تر از حد انتظار، یا قرار دادن استاپ‌لاس در موقعیتی کاملاً اشتباه شود.
  • انطباق با قوانین بروکر: هر بروکر محدودیت‌های خاص خود را بر روی نمادها اعمال می‌کند، مانند حداقل حجم معامله (Minimum Lot)، حداکثر حجم معامله (Maximum Lot) و گام حجم (Lot Step). یک ربات معامله‌گر حرفه‌ای باید این محدودیت‌ها را استخراج کرده و دستورات معاملاتی خود را مطابق با آن‌ها تنظیم کند تا از رد شدن (Rejection) دستور معامله توسط سرور بروکر جلوگیری شود.
  • مدیریت ریسک (Risk Management) هوشمند: پارامترهایی مانند اسپرد (Spread) (تفاوت بین قیمت خرید و فروش) به صورت مستقیم بر سودآوری و ریسک معامله تأثیر می‌گذارند. یک ربات ممکن است در زمان‌هایی که اسپرد به طور غیرعادی گسترده می‌شود (مثلاً هنگام انتشار خبر مهم)، از ورود به معامله خودداری کند. همچنین، اطلاع از ارزش هر پوینت (Point Value) برای محاسبه دقیق ریسک به دلاری (یا واحد حساب) ضروری است.
  • انعطاف‌پذیری و تطبیق‌پذیری: یک اکسپرت قدرتمند باید بتواند بر روی نمادهای مختلف (فارکس، CFD، کریپتو) و بروکرهای مختلف (با مشخصات متفاوت) اجرا شود. این تنها با خواندن پویای اطلاعات نماد در زمان اجرا (Runtime) ممکن است، نه با سخت‌کد کردن (Hard-coding) مقادیر برای یک نماد خاص.

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

یک چالش بزرگ در برنامه‌نویسی MQL، تنوع بسیار زیاد در مشخصات نمادها میان بروکرهای گوناگون است. این تفاوت‌ها می‌تواند در موارد زیر ظاهر شود:

  • نام نماد: ممکن است یک بروکر از EURUSD استفاده کند و دیگری از EURUSDm یا EURUSD.
  • تعداد ارقام اعشار (Digits): برخی بروکرها قیمت‌ها را با ۴ رقم اعشار (قدیمی) و برخی با ۵ رقم اعشار (استاندارد جدید) ارائه می‌دهند. این بر مقدار پوینت (Point) تأثیر مستقیم دارد.
  • مقادیر لات: حداقل حجم معامله، حداکثر حجم معامله و گام حجم می‌توانند به طور چشمگیری متفاوت باشند. برخی بروکرها حداقل لات ۰.۰۱ ارائه می‌دهند، در حالی که برخی دیگر ممکن است ۰.۱ یا ۱ لات باشند.
  • ساعت معاملات و تعطیلات: زمان باز و بسته شدن بازار برای یک نماد خاص ممکن است بین بروکرها متفاوت باشد.
  • نوع نماد (Symbol Type): ممکن است نمادی در یک بروکر به صورت CFD و در بروکری دیگر به صورت مستقیم (Futures یا Spot) ارائه شود.

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

بررسی کامل توابع دریافت اطلاعات نماد در MQL4 و MQL5

دریافت اطلاعات نماد در دو نسخه MQL تفاوت‌های ساختاری مهمی دارد که ناشی از معماری متفاوت این دو زبان است.

در MQL4: تابع اصلی و همه‌کاره MarketInfo() است. این تابع با دریافت نام نماد و یک شناسه (Enum) از نوع int، مقدار متناظر با آن شناسه را به صورت double برمی‌گرداند. البته MQL4 دارای توابع مستقیم دیگری مانند Bid و Ask نیز هست.

// مثال MQL4: دریافت چند اطلاعات پایه با MarketInfo
double currentBid = MarketInfo("EURUSD", MODE_BID);
double currentAsk = MarketInfo("EURUSD", MODE_ASK);
double currentSpread = MarketInfo("EURUSD", MODE_SPREAD);
double pointSize = MarketInfo("EURUSD", MODE_POINT);
int    digits    = (int)MarketInfo("EURUSD", MODE_DIGITS);
double minLot    = MarketInfo("EURUSD", MODE_MINLOT);
double maxLot    = MarketInfo("EURUSD", MODE_MAXLOT);
double lotStep   = MarketInfo("EURUSD", MODE_LOTSTEP);

در MQL5: رویکردی مدرن‌تر و نوع‌محور (Type-oriented) اتخاذ شده است. به جای یک تابع کلی، سه تابع تخصصی وجود دارند:

  1. SymbolInfoInteger(): برای دریافت اطلاعات از نوع عدد صحیح (int, long, enum) مانند رقم اعشار، نوع نماد، وضعیت معامله.
  2. SymbolInfoDouble(): برای دریافت اطلاعات از نوع اعشاری (double) مانند قیمت‌ها، اسپرد بر حسب پوینت، مقادیر لات.
  3. SymbolInfoString(): برای دریافت اطلاعات متنی (string) مانند نام نماد، شرح نماد، مسیر دیتای نماد.

این تفکیک، کد را خوانا‌تر و مدیریت خطا را دقیق‌تر می‌کند.

// مثال MQL5: دریافت همان اطلاعات با توابع جدید
double currentBid = SymbolInfoDouble("EURUSD", SYMBOL_BID);
double currentAsk = SymbolInfoDouble("EURUSD", SYMBOL_ASK);
double pointSize  = SymbolInfoDouble("EURUSD", SYMBOL_POINT);
int    digits     = (int)SymbolInfoInteger("EURUSD", SYMBOL_DIGITS);
double minLot     = SymbolInfoDouble("EURUSD", SYMBOL_VOLUME_MIN);
double maxLot     = SymbolInfoDouble("EURUSD", SYMBOL_VOLUME_MAX);
double lotStep    = SymbolInfoDouble("EURUSD", SYMBOL_VOLUME_STEP);

توضیح عمیق توابع کلیدی

تابع MarketInfo در MQL4

این تابع سنگ بنای کار با نمادها در MQL4 است. ساختار آن به صورت زیر است:

double MarketInfo(string symbol, int request);

پارامتر symbol نام نماد مورد نظر است. اگر رشته خالی ("") ارسال شود، نماد جاری چارت در نظر گرفته می‌شود. پارامتر request یک شناسه از پیش تعریف شده است که نوع اطلاعات درخواستی را مشخص می‌کند (مانند MODE_BID, MODE_ASK, MODE_SPREAD و ده‌ها مورد دیگر). مقدار بازگشتی از نوع double است، حتی برای اطلاعاتی که ماهیت صحیح دارند (مانند MODE_DIGITS). بنابراین، اغلب نیاز به تبدیل نوع (Casting) به int وجود دارد. نقطه ضعف این تابع، وابستگی به شناسه‌های عددی و یکسان بودن نوع بازگشتی برای همه حالات است.

توابع SymbolInfoInteger، SymbolInfoDouble و SymbolInfoString در MQL5

این توابع نماینده یک طراحی مدرن هستند.

  • bool SymbolInfoInteger(string name, ENUM_SYMBOL_INFO_INTEGER propid, long& value): این تابع مقدار عددی صحیح مربوط به ویژگی propid را در متغیر value قرار می‌دهد و در صورت موفقیت true برمی‌گرداند. ENUM_SYMBOL_INFO_INTEGER شامل مواردی مانند SYMBOL_DIGITS, SYMBOL_SPREAD, SYMBOL_TRADE_MODE است.
  • bool SymbolInfoDouble(string name, ENUM_SYMBOL_INFO_DOUBLE propid, double& value): مشابه تابع قبلی، اما برای مقادیر اعشاری مانند SYMBOL_BID, SYMBOL_ASK, SYMBOL_POINT, SYMBOL_VOLUME_MIN.
  • bool SymbolInfoString(string name, ENUM_SYMBOL_INFO_STRING propid, string& value): برای دریافت مقادیر رشته‌ای مانند SYMBOL_DESCRIPTION یا SYMBOL_PATH.

مزیت بزرگ این توابع، داشتن مقدار بازگشتی bool است که امکان مدیریت خطا را به راحتی فراهم می‌آورد. همچنین، نوع‌دهی قوی (Strong Typing) با استفاده از Enumها، خطاهای برنامه‌نویس را به حداقل می‌رساند.

دریافت قیمت Bid و Ask

قیمت‌های Bid (قیمت فروش) و Ask (قیمت خرید) پایه‌ای‌ترین و پویاترین اطلاعات یک نماد معاملاتی هستند.

در MQL4:

// روش ۱: استفاده از توابع مستقیم (فقط برای نماد جاری چارت)
double bidCurrentChart = Bid;
double askCurrentChart = Ask;

// روش ۲: استفاده از MarketInfo (برای هر نمادی)
double bidEurUsd = MarketInfo("EURUSD", MODE_BID);
double askGold = MarketInfo("XAUUSD", MODE_ASK);

// محاسبه قیمت میانگین (می‌پای) - معمولاً در برخی محاسبات تئوریک استفاده می‌شود
double midPrice = (bidEurUsd + askGold) / 2.0;

در MQL5:

// دریافت قیمت برای هر نماد
double bidValue, askValue;

if(SymbolInfoDouble("EURUSD", SYMBOL_BID, bidValue) &&
   SymbolInfoDouble("EURUSD", SYMBOL_ASK, askValue))
{
   Print("EURUSD Bid: ", bidValue, ", Ask: ", askValue);
   // اسپرد را می‌توان بر حسب پیپ محاسبه کرد
   double spreadInPoints = (askValue - bidValue) / SymbolInfoDouble("EURUSD", SYMBOL_POINT);
   Print("Spread in points: ", spreadInPoints);
}
else
{
   Print("Failed to get price for EURUSD!");
}

توجه داشته باشید که در MQL5، برای نماد جاری چارت می‌توان از توابع SymbolInfoTick() نیز برای دریافت یک ساختار حاوی Bid، Ask و آخرین قیمت (Last) به صورت همزمان استفاده کرد که از نظر بهینه‌سازی مناسب‌تر است.

دریافت اسپرد

اسپرد (Spread) هزینه اولیه ورود به یک معامله است و در مدیریت ریسک و فیلتر کردن سیگنال‌ها در شرایط ناسالم بازار نقش دارد.

در MQL4: اسپرد معمولاً به دو صورت برگردانده می‌شود: بر حسب پوینت (MODE_SPREAD) و بر حسب مقدار خام (MODE_ASKMODE_BID).

double spreadInPoints = MarketInfo("EURUSD", MODE_SPREAD); // مثلاً ۲۰
double spreadInRaw = MarketInfo("EURUSD", MODE_ASK) - MarketInfo("EURUSD", MODE_BID); // مثلاً ۰.۰۰۰۲۰
int digits = (int)MarketInfo("EURUSD", MODE_DIGITS);
Print("Spread in points: ", spreadInPoints, " | In raw value: ", DoubleToString(spreadInRaw, digits));

مقدار MODE_SPREAD معمولاً در نمادهای ۵ رقمی، اسپرد را بر حسب «پوینت» (آخرین رقم اعشار) نشان می‌دهد. در نماد قدیمی ۴ رقمی، هر پوینت رقم چهارم اعشار است.

در MQL5: مشابه MQL4 است، اما با تفکیک بهتر.

long spreadInPoints = SymbolInfoInteger("EURUSD", SYMBOL_SPREAD); // اسپرد بر حسب پوینت
double bid = SymbolInfoDouble("EURUSD", SYMBOL_BID);
double ask = SymbolInfoDouble("EURUSD", SYMBOL_ASK);
double point = SymbolInfoDouble("EURUSD", SYMBOL_POINT);
double spreadInRaw = ask - bid;
double spreadInPointsCalculated = spreadInRaw / point; // باید برابر با مقدار قبلی باشد

PrintFormat("Spread: %d pts, Raw: %.5f, Calculated: %.1f pts",
             spreadInPoints, spreadInRaw, spreadInPointsCalculated);

توجه: SYMBOL_SPREAD همیشه یک عدد صحیح (long) است که اسپرد را بر حسب SYMBOL_POINT بیان می‌کند.

دریافت حداقل، حداکثر و گام حجم معامله

این سه پارامتر، ستون‌های محاسبه حجم معامله هستند. عدم رعایت آن‌ها منجر به خطای “Invalid volume” از سمت سرور می‌شود.

  • حداقل حجم معامله (Minimum Lot): کوچک‌ترین مقداری که می‌توان برای یک سفارش وارد کرد.
  • حداکثر حجم معامله (Maximum Lot): بزرگ‌ترین مجاز.
  • گام حجم (Lot Step): اندازه افزایش یا کاهش حجم. اگر گام حجم ۰.۰۱ باشد، حجم‌های معتبر ۰.۰۱، ۰.۰۲، ۰.۰۳ و … هستند. حجم‌هایی مانند ۰.۰۱۵ غیرمجاز خواهند بود.

در MQL4:

double minLot  = MarketInfo(Symbol(), MODE_MINLOT);
double maxLot  = MarketInfo(Symbol(), MODE_MAXLOT);
double lotStep = MarketInfo(Symbol(), MODE_LOTSTEP);

// تابع کمکی برای نرمال‌سازی حجم مطابق با قوانین بروکر
double NormalizeLotSize(double desiredVolume)
{
   double normalized = desiredVolume;
   if(normalized < minLot) normalized = minLot;
   if(normalized > maxLot) normalized = maxLot;
   normalized = MathRound(normalized / lotStep) * lotStep; // گرد کردن به نزدیک‌ترین گام
   normalized = NormalizeDouble(normalized, 2); // معمولاً حجم تا دو رقم اعشار نرمال می‌شود
   return normalized;
}

// مثال استفاده
double myCalculatedLot = 0.123; // حجم محاسبه شده از فرمول ریسک
double finalLot = NormalizeLotSize(myCalculatedLot); // به ۰.۱۲ تبدیل می‌شود اگر گام ۰.۰۱ باشد

در MQL5: مفهوم مشابه است اما با شناسه‌های متفاوت.

double minLot  = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MIN);
double maxLot  = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MAX);
double lotStep = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_STEP);

// تابع نرمال‌سازی حجم در MQL5 (مشابه MQL4)
double NormalizeLot(double volume)
{
   double normalized = MathMax(minLot, MathMin(volume, maxLot));
   normalized = MathRound(normalized / lotStep) * lotStep;
   return NormalizeDouble(normalized, 2);
}

// MQL5 همچنین دارای SYMBOL_VOLUME_LIMIT است که حداکثر حجم کل باز در یک سمت (خرید/فروش) را نشان می‌دهد.
double volumeLimit = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_LIMIT);

دریافت ارزش هر پوینت

ارزش هر پوینت (Point Value) یا ارزش هر تغییر کوچک‌ترین واحد قیمت، برای تبدیل سود/ضرر بر حسب پیپ به واحد پول حساب، حیاتی است. این مقدار به حجم معامله، نماد و نوع حساب بستگی دارد.

در MQL4: محاسبه آن نیاز به چند مرحله دارد و اغلب از MODE_TICKVALUE استفاده می‌شود. MODE_TICKVALUE ارزش تغییر یک MODE_TICKSIZE (معمولاً برابر با MODE_POINT) را برای یک لات استاندارد نشان می‌دهد.

double tickValue = MarketInfo("EURUSD", MODE_TICKVALUE); // ارزش یک تیک (پوینت) برای ۱ لات
double point     = MarketInfo("EURUSD", MODE_POINT);
double tickSize  = MarketInfo("EURUSD", MODE_TICKSIZE); // معمولاً برابر point است

// محاسبه ارزش هر پوینت برای حجم معین (مثلاً ۰.۱ لات)
double myVolume = 0.1;
double pointValueForMyVolume = tickValue * (myVolume / 1.0) * (point / tickSize);
// اگر tickSize و point برابر باشند، ساده می‌شود به: pointValueForMyVolume = tickValue * myVolume;

Print("برای حجم ", myVolume, " لات، هر پوینت تغییر قیمت ≈ ", pointValueForMyVolume, " دلار (واحد حساب) ارزش دارد.");

در MQL5: منطق مشابه است، اما توابع واضح‌تری وجود دارد. SYMBOL_TRADE_TICK_VALUE معادل MODE_TICKVALUE است.

double tickValue   = SymbolInfoDouble("EURUSD", SYMBOL_TRADE_TICK_VALUE);
double tickSize    = SymbolInfoDouble("EURUSD", SYMBOL_TRADE_TICK_SIZE);
double volume      = 0.1;
double pointValue  = tickValue * volume * (Point() / tickSize); // Point() تابعی است که نقطه نماد جاری را برمی‌گرداند

// یک روش مطمئن‌تر: استفاده از تابع اختصاصی MQL5 برای محاسبه سود
double profitCalc = 0.0;
MqlTradeRequest request = {};
MqlTradeResult  result = {};
request.symbol = "EURUSD";
request.volume = volume;
request.price  = SymbolInfoDouble("EURUSD", SYMBOL_ASK);
request.type   = ORDER_TYPE_BUY;

if(OrderCalcProfit(ORDER_TYPE_BUY, "EURUSD", volume, SymbolInfoDouble("EURUSD", SYMBOL_BID)+10*Point(), profitCalc))
{
    Print("سود ۱۰ پیپ حرکت برای حجم ", volume, " لات ≈ ", profitCalc, " واحد حساب است.");
    // بنابراین ارزش هر پیپ ≈ profitCalc / 10
}

توجه: OrderCalcProfit() یکی از دقیق‌ترین روش‌ها برای این محاسبات است.

دریافت تعداد ارقام اعشار قیمت

این مقدار (Digits) برای نرمال‌سازی (NormalizeDouble) قیمت‌ها قبل از ارسال به سرور ضروری است.

در MQL4:

int digits = (int)MarketInfo("EURUSD", MODE_DIGITS); // برای نماد ۵ رقمی، ۵ برمی‌گرداند
double price = 1.12345;
double normalizedPrice = NormalizeDouble(price, digits);

در MQL5:

int digits = (int)SymbolInfoInteger("EURUSD", SYMBOL_DIGITS);
// یا از تابع کمکی:
int digitsCurrent = _Digits; // برای نماد جاری چارت

توجه: در نمادهای ۵ رقمی، Digits برابر ۵ است، اما Point برابر ۰.۰۰۰۰۱ است. در نمادهای ۴ رقمی قدیمی، Digits برابر ۴ و Point برابر ۰.۰۰۰۱ است.

دریافت نوع نماد معاملاتی

نوع نماد (Symbol Type) به شما می‌گوید که با چه گونه‌ای از دارایی سر و کار دارید: فارکس، CFD روی سهام، کریپتو، شاخص و غیره. این موضوع می‌تواند بر روی استراتژی (مثلاً در نظر گرفتن هزینه سوآپ) تأثیر بگذارد.

در MQL4: به طور مستقیم از طریق MarketInfo قابل دریافت نیست و معمولاً از نام نماد استنباط می‌شود، که روشی ناقص است.

در MQL5: به راحتی و با دقت بالا قابل دریافت است.

ENUM_SYMBOL_TYPE symbolType = (ENUM_SYMBOL_TYPE)SymbolInfoInteger("EURUSD", SYMBOL_TYPE);

switch(symbolType)
{
    case SYMBOL_CURRENCY:
        Print("این یک نماد فارکس (جفت ارز) است.");
        break;
    case SYMBOL_INDEX:
        Print("این یک نماد شاخص (Index) است.");
        break;
    case SYMBOL_STOCKS:
        Print("این یک نماد سهام است.");
        break;
    case SYMBOL_COMMODITY:
        Print("این یک نماد کالا است.");
        break;
    case SYMBOL_CRYPTO:
        Print("این یک نماد ارز دیجیتال (Crypto) است.");
        break;
    default:
        Print("نوع نماد: ", EnumToString(symbolType));
}

بررسی نمادهای CFD، Forex، Crypto و Indices

  • فارکس (FOREX): معمولاً SYMBOL_CURRENCY. نرخ سوآپ بر اساس تفاوت نرخ بهره دو ارز محاسبه می‌شود. قیمت‌گذاری مستقیم و بسیار نقدشونده.
  • CFD (قرارداد مابه‌التفاوت): می‌تواند روی سهام، شاخص‌ها، کالاها و … باشد. معمولاً SYMBOL_CFD یا SYMBOL_STOCKS. ممکن است کارمزدهای متفاوت و محدودیت‌های معاملاتی خاصی داشته باشند. قیمت آن‌ها از دارایی پایه مشتق می‌شود.
  • کریپتو (Crypto): SYMBOL_CRYPTO. دارای نوسانات بالا، ممکن است اسپرد گسترده‌تری داشته باشند و معاملات آن‌ها در ۷ روز هفته و ۲۴ ساعته انجام شود (برخلاف فارکس که تعطیلات آخر هفته دارد).
  • شاخص‌ها (Indices): SYMBOL_INDEX. مانند US30، SPX500. معمولاً به صورت CFD ارائه می‌شوند. ساعت معاملات آن‌ها محدود به ساعات بازار سهام مربوطه است.

تفاوت نمادهای 4 رقمی و 5 رقمی

این تفاوت یکی از رایج‌ترین دلایل خطا در اکسپرت‌های قدیمی است.

  • نماد ۴ رقمی (قدیمی): کوچک‌ترین تغییر قیمت (پوینت) رقم چهارم اعشار است. Point = 0.0001. Digits = 4. یک پیپ معمولاً برابر با ۱۰ پوینت در نظر گرفته می‌شد (یعنی ۰.۰۰۰۱).
  • نماد ۵ رقمی (استاندارد فعلی): کوچک‌ترین تغییر قیمت (پوینت) رقم پنجم اعشار است. Point = 0.00001. Digits = 5. یک پیپ (مفهوم قدیمی) برابر با ۱۰ پوینت است (یعنی ۰.۰۰۰۱۰).

نتیجه‌گیری مهم: در کدنویسی مدرن، باید از مفهوم پوینت (کوچک‌ترین واحد تغییر) استفاده کنید، نه پیپ. مقادیری مانند اسپرد و فاصله حد ضرر را بر حسب Point محاسبه کنید.

// روش درست و مستقل از ۴ یا ۵ رقمی بودن
double stopLossDistance = 50; // می‌خواهیم استاپ‌لاس ۵۰ "پوینت" فاصله داشته باشد.
double stopLossPrice = entryPrice - stopLossDistance * _Point; // برای پوزیشن فروش

// روش اشتباه (وابسته به ۴ رقمی)
double stopLossPriceWrong = entryPrice - 50 * 0.0001;

مدیریت خطا هنگام دریافت اطلاعات نماد

همیشه امکان دارد درخواست اطلاعات برای نمادی ناموجود یا غیرفعال باشد. مدیریت خطا در اینجا ضروری است.

در MQL4: MarketInfo در صورت شکست، صفر (0) یا مقدار نامعتبر برمی‌گرداند. باید با مقایسه با مقادیر منطقی بررسی کرد.

double bid = MarketInfo("INVALID_SYMBOL", MODE_BID);
if(bid == 0)
{
    Print("خطا: احتمالاً نماد نامعتبر است یا اطلاعات قیمتی در دسترس نیست.");
    // می‌توان بررسی کرد آیا نماد در Market Watch وجود دارد
    if(!MarketInfo("INVALID_SYMBOL", MODE_TRADEALLOWED))
        Print("نماد برای معامله مجاز نیست.");
}

در MQL5: با استفاده از مقدار بازگشتی bool توابع، مدیریت خطا ساده‌تر است.

double bidValue;
if(!SymbolInfoDouble("INVALID_SYMBOL", SYMBOL_BID, bidValue))
{
    int errorCode = GetLastError();
    Print("دریافت اطلاعات نماد با خطا مواجه شد. کد خطا: ", errorCode, " - ", ErrorDescription(errorCode));
    // ممکن است نیاز به رفرش کردن لیست نمادها باشد: SymbolSelect("INVALID_SYMBOL", true);
}

// یک تابع کاربردی برای بررسی وجود و فعال بودن نماد
bool IsSymbolValidAndActive(string symbolName)
{
    // بررسی وجود نماد در Market Watch
    if(!SymbolInfoInteger(symbolName, SYMBOL_SELECT))
    {
        Print(symbolName, " در Market Watch نیست. در حال افزودن...");
        if(!SymbolSelect(symbolName, true))
        {
            Print("افزودن نماد به Market Watch ناموفق بود.");
            return false;
        }
    }
    // بررسی اجازه معامله
    if(SymbolInfoInteger(symbolName, SYMBOL_TRADE_MODE) != SYMBOL_TRADE_MODE_FULL)
    {
        Print(symbolName, " برای معامله کامل (Full) فعال نیست.");
        return false;
    }
    // بررسی وجود قیمت Bid/Ask
    double bid = 0;
    if(!SymbolInfoDouble(symbolName, SYMBOL_BID, bid) || bid == 0)
    {
        Print("قیمت Bid برای ", symbolName, " در دسترس نیست.");
        return false;
    }
    return true;
}

تأثیر اطلاعات نماد بر مدیریت ریسک

مدیریت ریسک (Risk Management) مؤثر بدون اطلاعات نماد غیرممکن است. فرض کنید می‌خواهید حداکثر ۲٪ از حساب را در یک معامله ریسک کنید.
۱. تعریف فاصله ریسک: فاصله از نقطه ورود تا حد ضرر (بر حسب پوینت) را محاسبه می‌کنید. ۲. تبدیل پوینت به ارزش پولی: اینجا ارزش هر پوینت (Point Value) برای حجم مشخص وارد می‌شود. حجم معامله مجهول است. ۳. حل معادله: شما می‌دانید: (ریسک دلاری) = (فاصله پوینت) * (ارزش هر پوینت برای حجم X). ارزش هر پوینت برای حجم X خود تابعی از X و tickValue است. با حل این معادله، حجم مجاز X محاسبه می‌شود. ۴. اعمال محدودیت‌های بروکر: حجم محاسبه شده X باید با حداقل/حداکثر حجم و گام حجم نرمال‌سازی شود.

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

نقش اطلاعات نماد در محاسبه حجم معامله

بیایید یک مثال کامل از محاسبه حجم معامله با در نظر گرفتن ریسک و اطلاعات نماد را ببینیم.

در MQL4:

// تابعی برای محاسبه حجم با ریسک ثابت (Risk Percent)
double CalculateLotSize(double riskPercent, double stopLossPoints)
{
    // ۱. محاسبه ریسک به دلار
    double accountBalance = AccountBalance();
    double riskAmount = accountBalance * riskPercent / 100.0;

    // ۲. دریافت اطلاعات مورد نیاز نماد
    string sym = Symbol();
    double tickValue = MarketInfo(sym, MODE_TICKVALUE); // برای ۱ لات
    double point     = MarketInfo(sym, MODE_POINT);
    double tickSize  = MarketInfo(sym, MODE_TICKSIZE);

    // ۳. محاسبه حجم (فرمول: حجم = ریسک دلاری / (فاصله استاپ به پوینت * ارزش هر پوینت برای ۱ لات))
    // نکته: ارزش هر پوینت برای ۱ لات = tickValue * (point / tickSize)
    if(stopLossPoints == 0 || tickValue == 0) return(0);

    double lotSize = riskAmount / (stopLossPoints * tickValue * (point / tickSize));

    // ۴. نرمال‌سازی حجم مطابق با قوانین بروکر
    double minLot  = MarketInfo(sym, MODE_MINLOT);
    double maxLot  = MarketInfo(sym, MODE_MAXLOT);
    double lotStep = MarketInfo(sym, MODE_LOTSTEP);

    lotSize = MathMax(minLot, MathMin(lotSize, maxLot));
    lotSize = MathRound(lotSize / lotStep) * lotStep;
    lotSize = NormalizeDouble(lotSize, 2);

    return(lotSize);
}

// نمونه استفاده
double slPoints = 100; // 100 پوینت فاصله استاپ‌لاس
double myLot = CalculateLotSize(2.0, slPoints); // ریسک ۲٪
Print("حجم محاسبه شده: ", myLot);

در MQL5: منطق مشابه است، اما با توابع جدید.

double CalculateLotSizeMQL5(double riskPercent, double stopLossPoints)
{
    string sym = Symbol();
    double accountEquity = AccountInfoDouble(ACCOUNT_EQUITY);
    double riskAmount = accountEquity * riskPercent / 100.0;

    double tickValue   = SymbolInfoDouble(sym, SYMBOL_TRADE_TICK_VALUE);
    double tickSize    = SymbolInfoDouble(sym, SYMBOL_TRADE_TICK_SIZE);
    double point       = SymbolInfoDouble(sym, SYMBOL_POINT);

    if(stopLossPoints == 0 || tickValue == 0) return 0.0;

    double lotSize = riskAmount / (stopLossPoints * tickValue * (point / tickSize));

    double minLot  = SymbolInfoDouble(sym, SYMBOL_VOLUME_MIN);
    double maxLot  = SymbolInfoDouble(sym, SYMBOL_VOLUME_MAX);
    double lotStep = SymbolInfoDouble(sym, SYMBOL_VOLUME_STEP);

    lotSize = MathMax(minLot, MathMin(lotSize, maxLot));
    lotSize = MathRound(lotSize / lotStep) * lotStep;
    lotSize = NormalizeDouble(lotSize, 2);

    return lotSize;
}

نقش اطلاعات نماد در محاسبه حد ضرر و حد سود

محاسبه قیمت‌های حد ضرر و حد سود (Stop Loss / Take Profit) نیز مستقیماً به Point وابسته است. علاوه بر این، باید از حداقل فاصله مجاز (Stop Level) نیز آگاه بود.

در MQL5:

// محاسبه SL و TP بر اساس فاصله پوینتی
double CalculateStopLossPrice(ENUM_ORDER_TYPE orderType, double entryPrice, double stopLossPoints)
{
    double stopLevel = SymbolInfoInteger(Symbol(), SYMBOL_TRADE_STOPS_LEVEL) * _Point;
    double minDist   = MathMax(stopLevel, SymbolInfoDouble(Symbol(), SYMBOL_TRADE_STOPS_LEVEL) * _Point); // حداقل فاصله مجاز

    double slPrice = 0;
    if(orderType == ORDER_TYPE_BUY)
    {
        slPrice = entryPrice - stopLossPoints * _Point;
        // بررسی فاصله حداقل
        if((entryPrice - slPrice) < minDist)
            slPrice = entryPrice - minDist;
    }
    else if(orderType == ORDER_TYPE_SELL)
    {
        slPrice = entryPrice + stopLossPoints * _Point;
        if((slPrice - entryPrice) < minDist)
            slPrice = entryPrice + minDist;
    }
    // نرمال‌سازی قیمت
    slPrice = NormalizeDouble(slPrice, _Digits);
    return slPrice;
}

// مثال استفاده
double entry = SymbolInfoDouble(Symbol(), SYMBOL_ASK); // فرض ورود با قیمت Ask
double slPrice = CalculateStopLossPrice(ORDER_TYPE_BUY, entry, 100);
double tpPrice = NormalizeDouble(entry + 150 * _Point, _Digits); // تیک پروفیت ۱۵۰ پوینتی
Print("ورود: ", entry, ", SL: ", slPrice, ", TP: ", tpPrice);

مثال جامع کدنویسی: کلاس مدیریت نماد در MQL5

در پایان، یک نمونه کلاس کاربردی که بسیاری از مفاهیم فوق را یکجا جمع‌آوری می‌کند ارائه می‌شود.

//+------------------------------------------------------------------+
//|                                                  SymbolHelper.mqh |
//|                                        آموزش جامع MQL - نمادها   |
//+------------------------------------------------------------------+
class CSymbolHelper
{
private:
    string            m_symbol;
    double            m_point;
    int               m_digits;
    double            m_minLot;
    double            m_maxLot;
    double            m_lotStep;
    long              m_spread;
    ENUM_SYMBOL_TYPE  m_type;
    bool              m_isInitialized;

    bool              LoadSymbolData();

public:
    // سازنده
    CSymbolHelper(string symbolName = NULL)
    {
        if(symbolName == NULL || symbolName == "")
            m_symbol = _Symbol;
        else
            m_symbol = symbolName;

        m_isInitialized = LoadSymbolData();
    }

    // دریافت وضعیت اولیه‌سازی
    bool IsReady() const { return m_isInitialized; }

    // دریافت نام نماد
    string GetName() const { return m_symbol; }

    // دریافت نوع نماد
    ENUM_SYMBOL_TYPE GetType() const { return m_type; }

    // دریافت اطلاعات پایه
    double GetPoint()   const { return m_point; }
    int    GetDigits()  const { return m_digits; }
    double GetMinLot()  const { return m_minLot; }
    double GetMaxLot()  const { return m_maxLot; }
    double GetLotStep() const { return m_lotStep; }
    long   GetSpread()  const { return m_spread; }

    // دریافت قیمت‌ها
    double GetBid() { double val=0; SymbolInfoDouble(m_symbol, SYMBOL_BID, val); return val; }
    double GetAsk() { double val=0; SymbolInfoDouble(m_symbol, SYMBOL_ASK, val); return val; }

    // نرمال‌سازی حجم
    double NormalizeVolume(double volume)
    {
        if(!m_isInitialized) return 0.0;
        double normalized = MathMax(m_minLot, MathMin(volume, m_maxLot));
        normalized = MathRound(normalized / m_lotStep) * m_lotStep;
        return NormalizeDouble(normalized, 2);
    }

    // محاسبه ارزش پیپ برای حجم داده شده
    double CalculatePipValue(double volume)
    {
        if(!m_isInitialized) return 0.0;
        double tickValue = SymbolInfoDouble(m_symbol, SYMBOL_TRADE_TICK_VALUE);
        double tickSize  = SymbolInfoDouble(m_symbol, SYMBOL_TRADE_TICK_SIZE);
        // یک پیپ = 10 پوینت (برای تطابق با مفهوم قدیمی)
        return tickValue * volume * (10 * m_point / tickSize);
    }

    // چاپ خلاصه اطلاعات
    void PrintSummary()
    {
        if(!m_isInitialized)
        {
            Print(m_symbol, " - اطلاعات نماد بارگذاری نشد.");
            return;
        }
        Print("=== خلاصه اطلاعات نماد [", m_symbol, "] ===");
        Print("   نوع: ", EnumToString(m_type));
        Print("   رقم اعشار (Digits): ", m_digits, " | Point: ", DoubleToString(m_point, m_digits+1));
        Print("   حجم: Min=", m_minLot, ", Max=", m_maxLot, ", Step=", m_lotStep);
        Print("   اسپرد فعلی: ", m_spread, " points");
        Print("   قیمت جاری: Bid=", GetBid(), ", Ask=", GetAsk());
        Print("   ارزش هر پیپ (برای ۱ لات): ", CalculatePipValue(1.0), " ", AccountInfoString(ACCOUNT_CURRENCY));
        Print("=====================================");
    }
};

//+------------------------------------------------------------------+
//| بارگذاری داده‌های نماد                                           |
//+------------------------------------------------------------------+
bool CSymbolHelper::LoadSymbolData()
{
    // بررسی وجود نماد در Market Watch
    if(!SymbolInfoInteger(m_symbol, SYMBOL_SELECT))
    {
        if(!SymbolSelect(m_symbol, true))
        {
            Print("خطا: نماد ", m_symbol, " یافت نشد.");
            return false;
        }
    }

    long tmpLong;
    double tmpDouble;

    // دریافت مقادیر صحیح
    if(!SymbolInfoInteger(m_symbol, SYMBOL_DIGITS, tmpLong)) return false;
    m_digits = (int)tmpLong;

    if(!SymbolInfoInteger(m_symbol, SYMBOL_SPREAD, tmpLong)) return false;
    m_spread = tmpLong;

    tmpLong = SymbolInfoInteger(m_symbol, SYMBOL_TYPE);
    m_type = (ENUM_SYMBOL_TYPE)tmpLong;

    // دریافت مقادیر اعشاری
    if(!SymbolInfoDouble(m_symbol, SYMBOL_POINT, tmpDouble)) return false;
    m_point = tmpDouble;

    if(!SymbolInfoDouble(m_symbol, SYMBOL_VOLUME_MIN, tmpDouble)) return false;
    m_minLot = tmpDouble;

    if(!SymbolInfoDouble(m_symbol, SYMBOL_VOLUME_MAX, tmpDouble)) return false;
    m_maxLot = tmpDouble;

    if(!SymbolInfoDouble(m_symbol, SYMBOL_VOLUME_STEP, tmpDouble)) return false;
    m_lotStep = tmpDouble;

    return true;
}

//+------------------------------------------------------------------+
//| نمونه استفاده از کلاس در یک اکسپرت                               |
//+------------------------------------------------------------------+
// void OnStart()
// {
//     CSymbolHelper symbolHelper("EURUSD");
//     if(symbolHelper.IsReady())
//     {
//         symbolHelper.PrintSummary();
//
//         double desiredVolume = 0.15;
//         double normalizedVol = symbolHelper.NormalizeVolume(desiredVolume);
//         Print("حجم دلخواه: ", desiredVolume, " -> نرمال‌شده: ", normalizedVol);
//
//         double riskAmount = 100; // 100 دلار ریسک
//         double slPoints = 50;
//         double pipValue = symbolHelper.CalculatePipValue(normalizedVol);
//         // حجم بر اساس ریسک: حجم = ریسک دلاری / (پیپ ریسک * ارزش هر پیپ)
//         // توجه: اینجا pipValue برای normalizedVol است، ابتدا باید حجم را محاسبه کرد.
//     }
// }

این مقاله سعی کرد تا با پوشش عمیق و جزئی تمامی جنبه‌های مربوط به دریافت اطلاعات نماد معاملاتی در MQL، منبعی جامع برای توسعه‌دهندگان ربات معامله‌گر فراهم کند. به خاطر داشته باشید که تسلط بر این مفاهیم نه‌تنها از بروز خطاهای فنی جلوگیری می‌کند، بلکه پایه و اساس ساخت سیستم‌های معاملاتی خودکار قوی، قابل اطمینان و حرفه‌ای را تشکیل می‌دهد.

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

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

*
*