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

ساخت اولین ربات معامله‌گر در MT4

ساخت اولین ربات معامله‌گر در MT4

دنیای معاملات آنلاین، با وجود تمام نوسانات و پیچیدگی‌های ذاتی‌اش، همواره محلی برای نوآوری و پیشرفت بوده است. در میان ابزارهای متعددی که برای موفقیت در این بازار توسعه یافته‌اند، ربات معامله‌گر (Trading Bot) یا همان اکسپرت ادوایزر (Expert Advisor) جایگاهی ویژه دارد. توانایی خودکارسازی استراتژی‌ها، حذف احساسات انسانی از فرآیند تصمیم‌گیری و امکان نظارت شبانه‌روزی بر بازار، این ابزارها را به یکی از جذاب‌ترین مسیرهای پیش روی معامله‌گران تبدیل کرده است. با این حال، بسیاری از افراد تنها مصرف‌کننده این ابزارها باقی می‌مانند، در حالی که قدرت واقعی در توانایی ساخت و شخصی‌سازی آن‌ها نهفته است. این مقاله آموزشی جامع، شما را گام‌به‌گام به سمت درک عمیق و ساخت اولین ربات معامله‌گر خود در پلتفرم محبوب متاتریدر 4 (MetaTrader 4) با استفاده از زبان برنامه‌نویسی MQL4 (MetaQuotes Language 4) هدایت خواهد کرد. هدف این است که شما از یک مصرف‌کننده منفعل به یک خالق فعال در بازار تبدیل شوید، درکی عمیق از منطق پشت معاملات الگوریتمی به دست آورید و بتوانید با اطمینان بیشتری به مدیریت سرمایه و ریسک خود بپردازید.

اهمیت ساخت ربات معامله‌گر به‌صورت شخصی

تصمیم برای ساختن ربات معامله‌گر به جای خرید یا استفاده از راهکارهای آماده، یک تغییر پارادایم در رویکرد شما به معاملات است. زمانی که شما خودتان کد یک استراتژی را می‌نویسید، در واقع در حال مستندسازی دقیق و ریاضیاتی هر جنبه از استراتژی‌تان هستید. این فرآیند شما را مجبور می‌کند تا در مورد قوانین ورود، خروج، مدیریت ریسک (Risk Management) و مدیریت سرمایه (Money Management) به طور کاملاً شفاف و بدون ابهام فکر کنید. تفاوت عمده‌ای بین داشتن یک ایده کلی برای خرید “زمانی که قیمت از میانگین متحرک عبور کرد” و تبدیل آن ایده به کدی دقیق که تمام شرایط محیطی، مانند اندازه لات، توقف ضرر و حد سود را به طور خودکار محاسبه کند، وجود دارد. خرید یک ربات آماده، مانند خرید یک لباس سایز ثابت است؛ ممکن است خوب باشد، اما به ندرت کاملاً متناسب با نیازهای خاص شما، میزان سرمایه (Capital) شما، و میزان ریسک‌پذیری (Risk Tolerance) شماست. ساخت شخصی به شما این امکان را می‌دهد که هر متغیر و هر قانون را بر اساس ویژگی‌های منحصر به فرد بازار و حساب معاملاتی خود تنظیم کنید. علاوه بر این، زمانی که ربات شما دچار مشکل می‌شود یا عملکرد مورد انتظار را ندارد، دانش برنامه‌نویسی به شما این توانایی را می‌دهد که به سرعت اشکالات را شناسایی و رفع کنید. این استقلال فکری و فنی، ارزش بسیار بالاتری نسبت به صرف هزینه برای یک سیستم از پیش ساخته شده دارد. در نهایت، فرآیند یادگیری برنامه‌نویسی و تبدیل استراتژی‌های ذهنی به کدهای اجرایی، درک شما از روانشناسی بازار و چگونگی تعامل الگوها با یکدیگر را به شدت تعمیق می‌بخشد و شما را به معامله‌گری تحلیل‌گرتر تبدیل می‌کند.

آشنایی کامل با متاتریدر 4 و ساختار آن

متاتریدر 4 (MT4) یک پلتفرم معاملاتی الکترونیکی است که توسط شرکت MetaQuotes Software توسعه داده شده است. شهرت MT4 نه تنها به دلیل رابط کاربری ساده و عملکرد سریع آن است، بلکه به دلیل جامعه کاربری بزرگ و اکوسیستم قدرتمند توسعه نرم‌افزارهای جانبی آن، به ویژه اکسپرت ادوایزرها (Expert Advisors)، شناخته می‌شود. درک ساختار MT4 برای هر برنامه‌نویسی ضروری است. MT4 یک محیط سفارشی‌سازی شده را ارائه می‌دهد که به تریدرها اجازه می‌دهد تحلیل‌های فنی خود را به صورت خودکار انجام دهند. هسته اصلی این پلتفرم، سیستم مدیریت سفارشات و نمایش نمودارها است که توسط زبان MQL4 کنترل می‌شود. فایل‌های اجرایی برنامه در MT4 که به آن‌ها اکسپرت ادوایزر یا اندیکاتور گفته می‌شود، در پوشه‌های خاصی ذخیره می‌شوند و ساختار سلسله مراتبی خاصی دارند.

پوشه اصلی MT4 معمولاً در مسیر C:\Program Files\MetaTrader 4 یا مسیری مشابه قرار دارد. مهم‌ترین زیرپوشه‌ها که برای توسعه ربات معامله‌گر اهمیت دارند، عبارتند از:

  1. Experts: این پوشه محلی است که فایل‌های اجرایی اکسپرت ادوایزرها (با پسوند ex4 برای نسخه کامپایل شده و mq4 برای کد منبع) در آن قرار می‌گیرند. وقتی شما یک ربات معامله‌گر را در MetaEditor ایجاد و کامپایل می‌کنید، فایل ex4 نهایی در اینجا ذخیره می‌شود و از طریق پلتفرم قابل اجرا خواهد بود.
  2. Indicators: محل ذخیره اندیکاتورهای سفارشی.
  3. Libraries: برای ذخیره فایل‌های کتابخانه‌ای (.dll یا .ex4های کتابخانه‌ای) که توابع مشترک بین چند اکسپرت ادوایزر را فراهم می‌کنند.
  4. MQL4: این پوشه شامل فایل‌های Source Code (.mq4) است که شما مستقیماً در MetaEditor روی آن‌ها کار می‌کنید.

نحوه تعامل MT4 با ربات معامله‌گر بر پایه “رویدادها” (Events) استوار است. پلتفرم به طور مداوم در حال رصد تغییرات بازار است و هر بار که یک تغییر مهم رخ می‌دهد – مانند دریافت یک تیک جدید قیمت، یا تغییر تایم‌فریم نمودار – یک رویداد خاص فعال می‌شود. اکسپرت ادوایزر شما باید به این رویدادها واکنش نشان دهد. این واکنش‌ها توسط توابع خاصی تعریف می‌شوند که در ادامه بیشتر به آن‌ها خواهیم پرداخت، اما نکته کلیدی این است که MT4 یک محیط برنامه‌نویسی مبتنی بر رویداد (Event-Driven) است. درک این مدل برای ساخت منطق صحیح در کد بسیار حیاتی است؛ چرا که شما نمی‌توانید صرفاً یک دستور را اجرا کنید و انتظار داشته باشید که بازار به طور مداوم منتظر اجرای دستور شما بماند. بلکه باید کد شما در لحظات خاصی به طور خودکار فعال شود.

معرفی زبان MQL4 و تفاوت آن با سایر زبان‌ها

MQL4 (MetaQuotes Language 4) زبانی است که به طور خاص برای پلتفرم متاتریدر 4 طراحی شده است. این زبان یک زبان شیءگرا (Object-Oriented) نیست، بلکه بیشتر شبیه به زبان C یا C++ بوده و ساختاری رویه‌ای (Procedural) دارد، هرچند در نسخه‌های جدیدتر (MQL5) ویژگی‌های شیءگرایی بیشتری اضافه شده است. MQL4 برای انجام عملیات‌های مرتبط با معاملات مالی بهینه‌سازی شده است و دارای توابع داخلی بسیار قدرتمندی برای دسترسی به داده‌های بازار، مدیریت سفارشات و اجرای استراتژی‌های معاملاتی است.

اصلی‌ترین تفاوت MQL4 با زبان‌های عمومی‌تر مانند Python یا Java در تمرکز آن است. زبان‌های عمومی، ابزارهایی همه‌کاره برای توسعه وب، هوش مصنوعی، نرم‌افزارهای دسکتاپ و… هستند. اما MQL4 به طور مطلق برای یک هدف واحد طراحی شده: اجرای الگوریتم‌های معاملاتی در محیط MT4. این تمرکز باعث شده است که توابعی مانند OrderSend(), iMA(), یا HistoryDealSelect() به صورت بومی و بسیار کارآمد در دسترس باشند. شما نیازی به نصب کتابخانه‌های جداگانه برای اتصال به کارگزاری یا تحلیل داده‌های قیمتی ندارید؛ همه چیز از قبل در هسته زبان تعبیه شده است.

اگرچه سادگی ساختاری MQL4 برای مبتدیان جذاب است، اما محدودیت‌هایی نیز دارد. به عنوان مثال، کار کردن با ساختارهای داده پیچیده یا اجرای محاسبات سنگین آماری ممکن است به اندازه پایتون با کتابخانه‌های NumPy و Pandas کارآمد نباشد. همچنین، MQL4 یک زبان کامپایل شده به فایل‌های اجرایی (ex4) است و به صورت استاندارد امکان اجرای زنده در محیط‌های خارج از MT4 را ندارد. برای توسعه‌دهندگان حرفه‌ای‌تر که به دنبال کارایی بالاتر یا الگوریتم‌های پیچیده‌تر هستند، مهاجرت به MQL5 (که شیءگراتر است و امکانات پیشرفته‌تری دارد) یا استفاده از پل‌هایی (Bridges) مانند ZeroMQ برای اتصال پایتون به MT4 توصیه می‌شود. اما برای ساخت اولین ربات معامله‌گر، MQL4 بهترین نقطه شروع است زیرا مستقیماً با پلتفرمی که در حال حاضر استفاده می‌کنید، یکپارچه می‌شود.

پیش‌نیازهای فنی و ذهنی برای شروع برنامه‌نویسی ربات

برنامه‌نویسی ربات معامله‌گر نیازمند ترکیبی از دانش فنی و نگرش ذهنی صحیح است. از نظر فنی، آشنایی اولیه با منطق برنامه‌نویسی، متغیرها، ساختارهای کنترلی (مانند if, for, while) و مفاهیم پایه توابع برای موفقیت الزامی است. اگر هیچ پیش‌زمینه‌ای در برنامه‌نویسی ندارید، بهتر است ابتدا چند ساعت صرف یادگیری مبانی زبان C (یا هر زبانی با سینتکس مشابه) کنید تا با مفاهیم پایه‌ای مانند اعلان متغیرها، ارجاعات و سینتکس دستورات آشنا شوید.

در بعد ذهنی، آمادگی برای مواجهه با شکست‌های مکرر و نیاز به دقت وسواس‌گونه در جزئیات بسیار مهم است. بازار مالی محیطی پیچیده است و کدی که روی کاغذ یا در تست‌های ساده به نظر بی‌نقص می‌رسد، در مواجهه با نوسانات لحظه‌ای بازار، اسپرد متغیر یا خطاهای ارتباطی ممکن است دچار مشکل شود. ذهنیت یک برنامه‌نویس ربات باید مبتنی بر “شکاکیت سازنده” باشد؛ همیشه فرض کنید که کد شما در یک شرایط پیش‌بینی نشده شکست خواهد خورد و وظیفه شما این است که آن شرایط را پیش‌بینی کرده و برای آن‌ها راه حلی در کد لحاظ کنید.

پیش‌نیازهای کلیدی عبارتند از:

  1. فهم قوی استراتژی معاملاتی: قبل از نوشتن حتی یک خط کد، استراتژی شما باید به صورت یک الگوریتم کاملاً تعریف شده باشد. هر قانون باید به صورت یک شرط منطقی قابل بیان باشد (مثلاً: “اگر RSI زیر 30 است و قیمت بسته شدن کندل فعلی از میانگین متحرک 20 روزه بالاتر است، آنگاه سیگنال خرید صادر می‌شود”).
  2. آشنایی با محیط MetaEditor: شما باید بدانید که چگونه MetaEditor را باز کنید، یک فایل پروژه جدید ایجاد کنید، کد را کامپایل کنید و خروجی آن (فایل ex4) را در MT4 ببینید.
  3. صبر و پشتکار: برنامه‌نویسی، به ویژه دیباگ کردن (Debug)، زمان‌بر است. انتظار نداشته باشید اولین ربات شما در همان روز اول سودده شود. موفقیت در این حوزه نیازمند تکرار، تست و بهبود مستمر است.

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

اکسپرت ادوایزر (Expert Advisor یا EA) نام رسمی ربات معامله‌گر در پلتفرم متاتریدر 4 است. EA یک برنامه نرم‌افزاری است که بر روی یک نمودار خاص در MT4 اجرا می‌شود و وظیفه دارد بر اساس منطق برنامه‌نویسی شده، به طور خودکار و بدون دخالت دستی معامله کند. نقش اصلی اکسپرت ادوایزر اجرای استراتژی‌های معاملاتی است که به صورت قوانین سخت‌گیرانه تعریف شده‌اند.

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

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

ساختار فایل‌های ربات در MT4

هر اکسپرت ادوایزر در MQL4 به عنوان یک فایل منبع با پسوند .mq4 شروع می‌شود. این فایل حاوی تمام کدهای لازم برای اجرای ربات است. ساختار یک فایل .mq4 از چند بخش اساسی تشکیل شده است که باید به ترتیب خاصی در MetaEditor نوشته شوند:

  1. تعاریف اولیه (Pragma Directives): این بخش شامل دستورات خاصی است که به کامپایلر می‌گوید این فایل چه نوع برنامه‌ای است و چه پارامترهایی را باید به عنوان ورودی بپذیرد. مهم‌ترین دستور در این قسمت #property است که اطلاعاتی نظیر نسخه برنامه و مجوز استفاده را تعیین می‌کند. همچنین، با استفاده از #property indicator_separate_window یا عدم استفاده از آن، مشخص می‌شود که آیا این کد یک اندیکاتور است یا اکسپرت ادوایزر. برای EAها، معمولاً از دستور #property strict برای فعال‌سازی حالت کدنویسی سخت‌گیرانه‌تر استفاده می‌شود که به نوشتن کدهای تمیزتر کمک می‌کند.
  2. تعریف متغیرهای ورودی (Input Variables): این متغیرها پارامترهایی هستند که کاربر می‌تواند از طریق پنجره تنظیمات EA در MT4 آن‌ها را تغییر دهد، بدون نیاز به دستکاری مستقیم کد. این پارامترها باید با کلمه کلیدی input تعریف شوند. مثال: input int MagicNumber = 12345; یا input double LotSize = 0.1;. این بخش حیاتی است زیرا امکان بهینه‌سازی (Optimization) و تست کردن استراتژی با پارامترهای مختلف را فراهم می‌کند.
  3. متغیرهای سراسری (Global Variables): متغیرهایی که در خارج از توابع اصلی تعریف می‌شوند و در تمام طول عمر اکسپرت ادوایزر (از زمان اجرا تا زمان توقف) مقدار خود را حفظ می‌کنند. این متغیرها معمولاً برای نگهداری نتایج محاسبات سنگین یا وضعیت فعلی ربات استفاده می‌شوند.
  4. توابع اصلی (Core Functions): این بخش شامل سه تابع اصلی است که MT4 به طور خودکار در زمان‌های مشخص فراخوانی می‌کند: OnInit(), OnTick(), و OnDeinit(). این توابع ستون فقرات هر ربات معامله‌گر هستند.
  5. توابع کمکی سفارشی (Custom Helper Functions): هر تابع دیگری که شما برای ساده‌سازی کدنویسی و تفکیک منطق‌های مختلف می‌نویسید، در این قسمت قرار می‌گیرد.

پس از اتمام کدنویسی، فایل .mq4 باید توسط MetaEditor کامپایل شود. اگر هیچ خطای سینتکسی وجود نداشته باشد، یک فایل اجرایی با پسوند .ex4 در پوشه Experts ایجاد می‌شود که آماده اجرا بر روی نمودار است.

توضیح کامل توابع اصلی مانند OnInit، OnTick و OnDeinit

عملکرد یک اکسپرت ادوایزر کاملاً بر پایه تعامل با سه تابع اصلی تعریف شده در MQL4 استوار است. این توابع، رویدادهای کلیدی در چرخه حیات ربات معامله‌گر را مدیریت می‌کنند.

تابع OnInit()

تابع OnInit() (Initialization Function) دقیقاً یک بار و در لحظه راه‌اندازی اکسپرت ادوایزر روی نمودار فراخوانی می‌شود. این تابع برای انجام کارهای آماده‌سازی که فقط باید یک بار انجام شوند، ایده‌آل است. معمولاً کارهای زیر در این تابع انجام می‌شود:

  • اعتبارسنجی ورودی‌ها: بررسی اینکه آیا پارامترهای ورودی منطقی هستند (مثلاً حجم لات صفر یا منفی نباشد).
  • بررسی شرایط محیطی: اطمینان از اینکه پلتفرم MT4 به سرور متصل است و یا حداقل داده‌های مورد نیاز برای شروع محاسبات اولیه در دسترس هستند.
  • مقداردهی اولیه متغیرهای سراسری: تنظیم مقادیر اولیه برای متغیرهایی که وضعیت ربات را در طول عمرش حفظ می‌کنند.
  • تنظیم Magic Number: اختصاص یک عدد جادویی (Magic Number) به ربات برای شناسایی سفارشاتی که توسط این ربات معامله‌گر خاص صادر شده‌اند.

اگر تابع OnInit() با کد بازگشت INIT_FAILED خاتمه یابد، اکسپرت ادوایزر از کار باز می‌ایستد و اجرا نخواهد شد. بازگشت مقدار INIT_SUCCEEDED نشان‌دهنده شروع موفقیت‌آمیز عملیات است.

تابع OnTick()

این تابع قلب تپنده ربات معامله‌گر است و هر بار که یک “تیک” جدید قیمت دریافت می‌شود (یعنی قیمت جدیدی در بازار اعلام می‌شود)، توسط MT4 فراخوانی می‌شود. یک تیک می‌تواند شامل تغییر در قیمت پیشنهادی (Ask) یا قیمت درخواستی (Bid) باشد. به دلیل ماهیت رویدادمحور، تمام منطق معاملاتی اصلی، شامل بررسی سیگنال‌ها، مدیریت پوزیشن‌های باز و تصمیم‌گیری برای ورود یا خروج، در این تابع قرار می‌گیرد.

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

تابع OnDeinit()

تابع OnDeinit() زمانی فراخوانی می‌شود که اکسپرت ادوایزر از نمودار حذف می‌شود یا پلتفرم MT4 بسته می‌شود. این تابع برای “پاکسازی” یا “نظافت” منابع استفاده می‌شود. وظیفه اصلی آن اطمینان از این است که پس از خروج ربات، هیچ عملیات ناخواسته‌ای انجام نشود یا منابع اشغال شده آزاد گردند.

اقداماتی که معمولاً در OnDeinit() انجام می‌شود شامل:

  • بستن پوزیشن‌ها (اختیاری): بسته به استراتژی، ممکن است لازم باشد قبل از خروج کامل، تمامی پوزیشن‌های باز بسته شوند. این کار معمولاً با بررسی پوزیشن‌های باز و استفاده از تابع OrderClose() انجام می‌شود.
  • حذف اشیاء گرافیکی: اگر ربات خطوط، پیکان‌ها یا متن‌هایی را روی نمودار رسم کرده باشد (با استفاده از توابع گرافیکی)، باید آن‌ها را با استفاده از ObjectDelete() حذف کند تا نمودار تمیز بماند.

نحوه دریافت داده‌های بازار (قیمت، تایم‌فریم، اندیکاتورها)

ربات معامله‌گر بدون دسترسی دقیق به داده‌های بازار، بی‌معنی است. MQL4 توابع داخلی متعددی برای بازیابی این داده‌ها فراهم کرده است. مهم‌ترین داده‌ها شامل قیمت‌های لحظه‌ای، داده‌های تاریخچه‌ای و خروجی اندیکاتورها هستند.

دسترسی به قیمت‌ها (Bid/Ask)

قیمت‌های لحظه‌ای خرید و فروش با استفاده از توابع استاندارد دسترسی به قیمت در MT4 قابل دریافت هستند. قیمت فعلی Ask (قیمتی که می‌توان خرید) و Bid (قیمتی که می‌توان فروش) از طریق آرایه‌های داخلی در دسترس قرار می‌گیرند:

  • Ask: قیمت لحظه‌ای فروش (Ask Price).
  • Bid: قیمت لحظه‌ای خرید (Bid Price).

برای دسترسی به قیمت‌های کندل بسته شده قبلی، باید از تابع iClose()، iOpen(), iHigh(), iLow() یا توابع عمومی مانند Close[1] (در اسکریپت‌ها یا اندیکاتورها) استفاده کرد، اما در اکسپرت ادوایزر که در تابع OnTick() کار می‌کنیم، بهتر است از توابع دسترسی به قیمت‌های مربوط به تایم فریم فعلی استفاده کنیم. معمولاً قیمت بسته شدن کندل قبلی با اندیس [1] و کندل فعلی (که هنوز بسته نشده) با اندیس [0] مشخص می‌شود.

مدیریت تایم‌فریم‌ها (Timeframes)

ربات معامله‌گر باید بداند که بر روی کدام تایم فریم اجرا می‌شود یا داده‌های کدام تایم فریم را تحلیل کند. تایم فریم جاری نمودار با استفاده از تابع Period() به دست می‌آید. این تابع یک مقدار عددی (مانند PERIOD_M5 برای 5 دقیقه یا PERIOD_H1 برای یک ساعت) را برمی‌گرداند. برای تحلیل داده‌های تایم فریم‌های دیگر (مثلاً تحلیل M15 با استفاده از داده‌های H1)، باید صراحتاً درخواست داده‌های آن تایم فریم را انجام داد.

دریافت خروجی اندیکاتورها

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

برای مثال، برای دریافت مقدار میانگین متحرک (Moving Average یا MA) در یک کندل خاص، از تابع iMA() استفاده می‌شود:

[ \text{double MA_Value} = \text{iMA}(\text{Symbol}(), \text{Period}(), \text{MA_Period}, 0, \text{MODE_SMA}, \text{PRICE_CLOSE}, \text{Shift}) ]

در اینجا:

  • Symbol(): نماد فعلی (مثلاً EURUSD).
  • Period(): تایم فریم فعلی نمودار.
  • MA_Period: دوره (تعداد کندل‌ها) میانگین متحرک.
  • MODE_SMA: نوع میانگین (ساده، نمایی، و غیره).
  • PRICE_CLOSE: مبنای محاسبه (قیمت بسته شدن).
  • Shift: فاصله کندل مورد نظر (0 برای کندل فعلی، 1 برای کندل قبلی).

با استفاده از این توابع، ربات معامله‌گر می‌تواند تمام تحلیل‌های فنی مورد نیاز خود را به صورت خودکار محاسبه کند، که این اساس اتوماسیون معاملات است.

پیاده‌سازی اولین استراتژی ساده معاملاتی

حالا زمان آن رسیده که دانش تئوری را با یک مثال عملی ترکیب کنیم. استراتژی انتخابی ما یک استراتژی بسیار ساده مبتنی بر تقاطع دو میانگین متحرک (Moving Average Crossover) خواهد بود. این استراتژی یکی از رایج‌ترین الگوها برای شروع برنامه‌نویسی است.

قوانین استراتژی:

  1. سیگنال خرید (Long Entry): زمانی که میانگین متحرک سریع (مثلاً 10 دوره‌ای) از زیر به بالای میانگین متحرک کند (مثلاً 30 دوره‌ای) عبور کند.
  2. سیگنال فروش (Short Entry): زمانی که میانگین متحرک سریع (10 دوره‌ای) از بالای به زیر میانگین متحرک کند (30 دوره‌ای) عبور کند.
  3. خروج: در این مثال اولیه، فرض می‌کنیم پوزیشن باز فقط زمانی بسته می‌شود که سیگنال مخالف صادر شود (معامله به صورت متضاد بسته شده و پوزیشن جدید باز شود).

مراحل کدنویسی (خلاصه):

  1. تعریف متغیرهای ورودی: تعریف دوره دو MA و حجم لات.
    input int FastMAPeriod = 10;
    input int SlowMAPeriod = 30;
    input double LotSize = 0.1;
    
  2. محاسبه مقادیر MA در OnInit یا OnTick: باید مقادیر MA کندل قبلی (Shift=1) را محاسبه کنیم تا از ورود یا خروج در لحظه تیک جاری مطمئن شویم.
    double fastMA_prev, slowMA_prev;
    // در OnTick:
    fastMA_prev = iMA(Symbol(), 0, FastMAPeriod, 0, MODE_SMA, PRICE_CLOSE, 1);
    slowMA_prev = iMA(Symbol(), 0, SlowMAPeriod, 0, MODE_SMA, PRICE_CLOSE, 1);
    
    double fastMA_prev_prev, slowMA_prev_prev;
    fastMA_prev_prev = iMA(Symbol(), 0, FastMAPeriod, 0, MODE_SMA, PRICE_CLOSE, 2);
    slowMA_prev_prev = iMA(Symbol(), 0, SlowMAPeriod, 0, MODE_SMA, PRICE_CLOSE, 2);
    

    نکته مهم: ما از کندل‌های با شیفت 1 و 2 استفاده می‌کنیم تا مطمئن شویم تقاطع در کندل بسته شده قبلی رخ داده و محاسبات ما بر اساس داده‌های قطعی است، نه داده‌های در حال تغییر تیک جاری.

  3. بررسی شرایط ورود در OnTick():
    // بررسی سیگنال خرید (تقاطع صعودی در کندل قبلی)
    if (fastMA_prev > slowMA_prev && fastMA_prev_prev <= slowMA_prev_prev)
    {
        // چک کنید آیا پوزیشن بازی وجود دارد؟ اگر نیست، سفارش خرید ارسال کنید.
        if (PositionsTotal() == 0)
        {
            OrderSend(Symbol(), OP_BUY, LotSize, Ask, 3, 0, 0, "MA Crossover Buy", MagicNumber, 0, clrGreen);
        }
    }
    
    // بررسی سیگنال فروش (تقاطع نزولی در کندل قبلی)
    else if (fastMA_prev < slowMA_prev && fastMA_prev_prev >= slowMA_prev_prev)
    {
        // چک کنید آیا پوزیشن بازی وجود دارد؟ اگر نیست، سفارش فروش ارسال کنید.
        if (PositionsTotal() == 0)
        {
            OrderSend(Symbol(), OP_SELL, LotSize, Bid, 3, 0, 0, "MA Crossover Sell", MagicNumber, 0, clrRed);
        }
    }
    

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

مدیریت ریسک (Risk Management) در ربات معامله‌گر

هیچ ربات معامله‌گری بدون مدیریت ریسک مناسب نمی‌تواند در بلندمدت دوام بیاورد. مدیریت ریسک تضمین می‌کند که یک سری معاملات بد نتواند کل سرمایه شما را از بین ببرد. در سطح برنامه‌نویسی، این یعنی تبدیل پارامترهای ریسک به مقادیر محاسباتی در کد.

مهم‌ترین پارامتر ریسک که باید در کد تعریف شود، حداکثر ریسک درصدی برای هر معامله است. فرض کنید می‌خواهیم در هر معامله، حداکثر 1% از کل سرمایه را به خطر بیندازیم.

محاسبه حجم لات بر اساس ریسک (Risk-Based Lot Sizing):

برای محاسبه حجم لات (Lot Size) مناسب، ما نیاز به سه ورودی اصلی داریم:

  1. درصد ریسک مجاز (مثلاً 1%).
  2. فاصله حد ضرر (Stop Loss) به پیپ.
  3. مقدار پول هر پیپ (Pip Value) برای حجم پایه.

فرمول کلی برای محاسبه حجم لات به شرح زیر است:

[ \text{Lot Size} = \frac{\text{AccountBalance} \times \text{RiskPercentage}}{(\text{StopLossPips} \times \text{PointValue} \times \text{Multiplier})} ]

در MQL4:

  • AccountBalance: از تابع AccountBalance() به دست می‌آید.
  • RiskPercentage: متغیر ورودی (مثلاً 0.01 برای 1%).
  • StopLossPips: فاصله حد ضرر که شما باید آن را با توجه به استراتژی و فاصله قیمت فعلی تا حد ضرر محاسبه کنید.
  • PointValue: برای محاسبه ارزش هر پیپ، نیاز به در نظر گرفتن اندازه لات پایه (100,000 واحد برای لات استاندارد) و میزان اعشار در قیمت‌ها داریم. تابع MarketInfo(Symbol(), MODE_TICKVALUE) می‌تواند کمک کند، اما روش استاندارد محاسبه فاصله بر اساس سایز پوینت (Point) است.

نحوه پیاده‌سازی حد ضرر (Stop Loss) و حد سود (Take Profit):

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

اگر حد ضرر ما 50 پیپ است:

int StopLossPips = 50;
double PipsToPoints = (Point == 0.00001 || Point == 0.001) ? 10 : 1; // تعدیل برای 4/5 رقمی
double StopLossLevel = Ask - (StopLossPips * Point * PipsToPoints);
double TakeProfitLevel = Ask + (StopLossPips * 2 * Point * PipsToPoints); // ریسک به ریوارد 1:2

// برای اطمینان از گرد کردن به نقطه صحیح بروکر:
StopLossLevel = NormalizeDouble(StopLossLevel, Digits);
TakeProfitLevel = NormalizeDouble(TakeProfitLevel, Digits);

در کد ارسال سفارش، باید این سطوح را ارسال کنیم. این محاسبات دقیق، ضامن اجرای صحیح مدیریت ریسک توسط ربات معامله‌گر است.

مدیریت سرمایه (Money Management) در کدنویسی ربات

مدیریت سرمایه (MM) مفهومی گسترده‌تر از مدیریت ریسک است و به نحوه تخصیص سرمایه کلی حساب به معاملات مختلف می‌پردازد. در حالی که مدیریت ریسک محدود به اندازه هر معامله است، MM به طول عمر کلی استراتژی و ظرفیت حساب برای تحمل افت‌ها می‌پردازد.

در سطح برنامه‌نویسی MQL4، مدیریت سرمایه مستقیماً با محاسبه حجم لات (Lot Sizing) که در بخش قبل توضیح داده شد، پیوند می‌خورد. یک ربات معامله‌گر با MM قوی هرگز نباید اجازه دهد که حجم معاملات به گونه‌ای افزایش یابد که با یک ضرر متوالی، کل سرمایه از بین برود.

یکی از اصول مهم در MM، استفاده از حجم لات متناسب با موجودی حساب (Dynamic Lot Sizing) است. برای مثال، اگر سرمایه حساب افزایش یابد، ربات باید بتواند حجم لات را به صورت متناسب افزایش دهد (با حفظ درصد ریسک ثابت)، و بالعکس.

پیاده‌سازی پله‌ای در MM:

برخی معامله‌گران، برای جلوگیری از ریسک‌های ناگهانی در حساب‌های کوچک، محدودیت‌های سخت‌تری اعمال می‌کنند. مثلاً:

  1. حداکثر درصد ریسک در کل حساب: تعیین اینکه مجموع حد ضررهای باز نباید از X درصد کل سرمایه تجاوز کند.
  2. تعیین حداقل و حداکثر حجم لات: حتی اگر محاسبات Risk-Based Lot Sizing حجمی بسیار کوچک یا بسیار بزرگ را پیشنهاد داد، ربات باید بین مقادیر مجاز بروکر (حداقل 0.01 لات و حداکثر بر اساس مارجین موجود) محدود شود.

برای بررسی مجموع ریسک باز، باید تمام پوزیشن‌های باز را با استفاده از OrdersTotal() پیمایش کرده و مجموع مارجین استفاده شده یا مجموع حد ضرر بالقوه را محاسبه کنیم.

اگر از Magic Number اختصاصی استفاده کرده باشید، پیمایش پوزیشن‌های باز آسان‌تر می‌شود:

double TotalExposure = 0;
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
    if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
    {
        if(OrderMagicNumber() == MagicNumber && OrderSymbol() == Symbol())
        {
            // محاسبه مارجین مورد نیاز یا مجموع حد ضرر
            TotalExposure += OrderLots(); // فقط برای نمایش مجموع حجم معاملات
        }
    }
}

// اعمال محدودیت MM قبل از OrderSend
if (TotalExposure + CurrentLotSize > MaxAllowedLots) { return; }

نحوه باز و بسته کردن معاملات در MQL4

عملیات اصلی یک ربات معامله‌گر، ارسال سفارشات (Order Sending) و مدیریت آن‌هاست. این کار در MQL4 توسط مجموعه‌ای از توابع صورت می‌گیرد که محوریت آن‌ها توابع Order...() است.

باز کردن معاملات (OrderSend)

همانطور که در مثال استراتژی دیدیم، برای باز کردن یک پوزیشن جدید از تابع OrderSend() استفاده می‌شود:

[ \text{int OrderSend}(\text{string symbol}, \text{int type}, \text{double size}, \text{double price}, \text{int slippage}, \text{double stoploss}, \text{double takeprofit}, \text{string comment}, \text{int magic}, \text{datetime expiration}, \text{color arrow_color}) ]

  • type: نوع سفارش (OP_BUY, OP_SELL).
  • price: قیمتی که سفارش باید در آن اجرا شود (Ask برای خرید، Bid برای فروش).
  • slippage: میزان انحراف مجاز از قیمت مورد نظر بر حسب پیپ. این پارامتر برای جلوگیری از پر شدن سفارش در قیمت‌های بسیار نامطلوب در بازارهای پرنوسان حیاتی است.
  • magic: عدد جادویی برای شناسایی سفارش.

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

بستن معاملات (OrderClose و OrderCloseBy)

بستن یک پوزیشن باز نیز باید با دقت انجام شود. دو روش اصلی وجود دارد:

  1. بستن مستقیم (OrderClose): برای بستن یک پوزیشن مشخص با حد ضرر/حد سود یا با قیمت بازار.
    [ \text{bool OrderClose}(\text{int ticket}, \text{double lots}, \text{double price}, \text{int slippage}, \text{color clr}) ] برای بستن یک پوزیشن خرید، قیمت باید قیمت Bid فعلی باشد و برای فروش، قیمت Ask فعلی. باید اطمینان حاصل کنید که “Ticket” (شناسه منحصر به فرد سفارش) صحیح را به تابع می‌دهید.
  2. بستن معکوس (OrderCloseBy): این تابع برای بستن یک پوزیشن با یک پوزیشن دیگر در جهت مخالف استفاده می‌شود (مثلاً بستن یک خرید با یک فروش). این روش در برخی مواقع می‌تواند کارمزد کمتری داشته باشد اما پیچیدگی‌های محاسباتی خاص خود را دارد.

فرایند استاندارد بستن پوزیشن در EA:

در هر تیک، ربات معامله‌گر باید ابتدا بررسی کند که آیا پوزیشن بازی دارد یا خیر (با استفاده از OrdersTotal() و پیمایش). اگر پوزیشن باز است و شرایط خروج (بر اساس استراتژی یا رسیدن SL/TP) برقرار است، باید پوزیشن بسته شود. اگر استراتژی شما از سیستم مارتینگل یا اوردینگ (Hedging) استفاده نمی‌کند و هدف این است که فقط یک پوزیشن در هر زمان باز باشد، بستن پوزیشن مخالف در زمان صدور سیگنال جدید، بخش مهمی از اجرای استراتژی است.

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

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

  1. خطای زمان‌بندی (Timing Errors): شایع‌ترین خطا در EAها. این اتفاق زمانی می‌افتد که ربات بر اساس داده‌هایی که هنوز نهایی نشده‌اند، تصمیم می‌گیرد. به عنوان مثال، استفاده از قیمت کندل جاری (Shift=0) برای تصمیم‌گیری ورود، در حالی که هنوز کندل جاری بسته نشده و قیمت‌های آن ممکن است تغییر کنند. راه‌حل: همیشه برای تصمیم‌گیری ورود، از کندل بسته شده قبلی (Shift=1 یا حتی Shift=2) استفاده کنید.
  2. عدم بررسی موفقیت سفارش: ارسال OrderSend() و فرض اینکه حتماً اجرا شده است. اگر بروکر به دلیل کمبود مارجین یا تنظیمات نادرست SL/TP خطا دهد، ربات بدون هیچ پوزیشن بازی، اجرا را ادامه می‌دهد و ممکن است منتظر سیگنال بازگشت بماند. راه‌حل: همیشه بررسی کنید OrderSend() با موفقیت انجام شده است و در صورت عدم موفقیت، کد خطا را ثبت کنید.
  3. مشکلات مربوط به جابجایی قیمت (Slippage): اگر Slippage در OrderSend() خیلی کوچک تعریف شود (مثلاً 0)، و بازار با اسپرد بالا یا نوسان شدید مواجه شود، سفارش رد می‌شود. راه‌حل: استفاده از یک مقدار منطقی برای Slippage (مثلاً 3 تا 5 پیپ) در شرایط عادی، یا پیاده‌سازی منطقی برای افزایش Slippage در صورت نوسانات شدید بازار.
  4. عدم استفاده از Magic Number: اگر چندین ربات معامله‌گر یا معامله دستی در یک نمودار اجرا شود و همه از یک نماد استفاده کنند، ربات نمی‌تواند تشخیص دهد کدام پوزیشن متعلق به خودش است. راه‌حل: اختصاص یک عدد جادویی منحصر به فرد برای هر اکسپرت ادوایزر و فیلتر کردن سفارشات بر اساس آن در توابع بررسی پوزیشن.
  5. تغییرات در ساختار داده قیمت (Point Value): برخی کارگزاران از قیمت‌های 5 رقمی (مثلاً EURUSD 1.12345) و برخی 4 رقمی (1.1234) استفاده می‌کنند. اگر ربات شما مقدار Point را به درستی برای تبدیل پیپ به قیمت مطلق (به خصوص در محاسبه SL/TP) تنظیم نکند، محاسبات حجم لات و حد ضرر اشتباه خواهد بود. استفاده از تابع NormalizeDouble() برای اطمینان از اینکه سطوح قیمت به تعداد ارقام صحیح بروکر گرد می‌شوند، الزامی است.

اصول تست اولیه قبل از بک‌تست

قبل از وارد کردن ربات معامله‌گر به ابزار بک‌تست (Backtest) MT4، باید مطمئن شوید که کد شما از نظر عملکردی و منطقی در محیط زنده (Demo/Live) کار می‌کند. این مرحله تست‌های اولیه یا “تست‌های سفت و سخت” نامیده می‌شود.

تست‌های عملکردی (Functional Testing):

  1. تست OnInit: مطمئن شوید که ربات بدون خطا روی نمودارهای مختلف (با تایم فریم‌ها و نمادهای متفاوت) بارگذاری می‌شود و همه پارامترهای ورودی به درستی مقداردهی می‌شوند.
  2. تست تیک و اجرا: ربات را روی یک حساب دمو باز کنید و با یک حجم بسیار کوچک (مثلاً 0.01 لات) اجرا کنید. تغییرات قیمت را رصد کنید. آیا سیگنال‌ها در لحظه درست صادر می‌شوند؟ آیا SL/TP به درستی تنظیم می‌شود؟
  3. تست خروج: اطمینان حاصل کنید که وقتی استراتژی سیگنال خروج می‌دهد، ربات به درستی پوزیشن را می‌بندد و برای سیگنال بعدی آماده می‌شود. اینجاست که عدم وجود MagicNumber یا خطای پیمایش پوزیشن‌ها مشخص می‌شود.
  4. تست خطاها: عمداً شرایطی ایجاد کنید که باعث شکست سفارش شود (مثلاً تلاش برای باز کردن پوزیشن با حجم غیرمجاز) و مطمئن شوید که ربات به جای کرش کردن، خطای GetLastError() را ثبت کرده و به کار خود ادامه می‌دهد.

این تست‌های اولیه بر روی داده‌های زنده (حتی دمو) از نظر زمانی و نحوه اجرای دستورات در محیط واقعی MT4 بسیار مهم‌تر از بک‌تست صرف هستند، زیرا بک‌تست همیشه نمی‌تواند تمام مشکلات اجرایی لحظه‌ای (مانند تأخیر در اجرای سفارش) را شبیه‌سازی کند.

معرفی مفهوم بک‌تست بدون ورود عمیق

بک‌تست (Backtest) فرآیند اجرای ربات معامله‌گر بر روی داده‌های تاریخی قیمت است تا عملکرد آن در گذشته ارزیابی شود. این ابزار موجود در MT4 (Strategy Tester) اجازه می‌دهد تا ببینیم اگر ربات شما در گذشته با این پارامترها فعال بود، چه نتایجی به دست می‌آورد.

چرا بک‌تست مهم است؟
بک‌تست به شما کمک می‌کند تا:

  1. قابلیت اطمینان استراتژی: تعیین کنید که آیا استراتژی از نظر آماری پتانسیل سوددهی دارد یا خیر.
  2. بهینه‌سازی پارامترها: بهترین تنظیمات برای پارامترهای ورودی (مانند دوره‌های MA) را پیدا کنید.
  3. بررسی مدیریت ریسک: مشاهده کنید که در بدترین سناریوهای تاریخی، حساب شما چقدر تحت فشار قرار می‌گرفته است (Drawdown).

نکات اساسی در بک‌تست MQL4:

  • کیفیت داده‌ها: بک‌تست تنها به اندازه داده‌های تاریخی که استفاده می‌کنید، دقیق است. در MT4، اگر داده‌های تاریخچه‌ای کافی نباشد (معمولاً داده‌های یک ماه گذشته به صورت پیش‌فرض موجود است)، کیفیت بک‌تست به شدت کاهش می‌یابد. توصیه می‌شود از ابزارهای خارجی برای وارد کردن داده‌های تیک سطح بالا (Tick Data) استفاده کنید تا شبیه‌سازی دقیق‌تر باشد.
  • مدل‌سازی: در Strategy Tester، سه حالت مدل‌سازی وجود دارد: “Every Tick”، “Control Points”، و “Open Prices Only”. برای اکسپرت ادوایزرها که نیازمند تحلیل دقیق در طول کندل هستند، حالت Every Tick بهترین دقت را ارائه می‌دهد اما زمان‌برترین است.
  • تفسیر نتایج: نتایج بک‌تست باید با احتیاط تفسیر شوند. یک استراتژی که در بک‌تست عالی عمل کرده، به دلیل پدیده‌ای به نام “Curve Fitting” یا “Overfitting” ممکن است در آینده شکست بخورد؛ یعنی ربات شما بیش از حد برای شرایط گذشته بهینه شده و فاقد انعطاف‌پذیری لازم برای شرایط بازار فعلی است.

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

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

  1. پیاده‌سازی پیشرفته مدیریت ریسک: فراتر از ریسک ثابت 1%، الگوریتم‌هایی برای مدیریت سرمایه پویا بر اساس نوسانات بازار (Volatility-Based Sizing) یا با در نظر گرفتن ضریب زیان متوالی (Drawdown Tolerance) اعمال کنید.
  2. استفاده از چند استراتژی (Portfolio Approach): سعی کنید به جای تکیه بر یک سیگنال ساده، چندین سیگنال مستقل را ترکیب کنید. به عنوان مثال، اجرای یک استراتژی میانگین متحرک در کنار یک استراتژی حجمی (Volume Profile) می‌تواند قدرت ربات را افزایش دهد.
  3. بهبود مدیریت خطا و لاگ‌برداری (Logging): یک ربات معامله‌گر حرفه‌ای باید بتواند تمام اقدامات مهم (ورود موفق، رد سفارش، خطاها، تغییر پارامترها) را در یک فایل لاگ ثبت کند. این کار برای دیباگ کردن پس از وقوع مشکلات در محیط زنده حیاتی است. استفاده از تابع Print() یا توابع سفارشی لاگ‌نویسی در MQL4 ضروری است.
  4. انعطاف‌پذیری در تایم فریم: ربات خود را طوری طراحی کنید که بتواند به راحتی بین تایم فریم‌های مختلف سوئیچ کند و داده‌های مورد نیاز را به درستی بازیابی کند، به جای آنکه صرفاً به تایم فریم اجرای خود متکی باشد.

ساختن ربات معامله‌گر در متاتریدر 4 با MQL4 یک سفر طولانی و یادگیری مداوم است. با تسلط بر مفاهیم پایه‌ای مدیریت ریسک و ساختار توابع اصلی، شما ابزارهای لازم برای خودکارسازی استراتژی‌های خود را در اختیار خواهید داشت و از دنیای معاملات دستی به سمت معاملات الگوریتمی پیشرفته گام برداشته‌اید. این فرآیند نیازمند دقت، صبر و میل به یادگیری مداوم است تا رباتی که می‌سازید، نه تنها بر روی داده‌های تاریخی، بلکه در گذر زمان و در مواجهه با دینامیک‌های متغیر بازار، کارآمد باقی بماند.

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

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

*
*