چگونه معیارهای بهینه‌سازی خود را پیاده‌سازی کنیم؟

چگونه معیارهای بهینه‌سازی خود را پ

چگونه معیارهای بهینه‌سازی خود را پیاده‌سازی کنیم؟

گهگداری از این طرف و آن طرف می‌شنویم که ضروریست معیارهای بهینه‌سازی در تستر متاتریدر ۴ گسترش یابند. با این حال، مهم‌ نیست توسعه‌دهندگان چه معیارهایی را اضافه می‌کنند زیرا همیشه شرایطی هست و کاربرانی هستند که به معیارهای دیگری نیاز داشته‌ باشند. آیا می‌توانیم این مشکل را درون خود MQL4 و متاتریدر ۴ حل کنیم؟ بله، می‌توانیم. این مقاله به شما نحوه‌ی پیاده‌سازی و به‌کارگیری یک معیار سفارشی برای بهینه‌سازی را همراه با مثالی از یک اکسپرت مووینگ اَورج استاندارد، نشان می‌دهد. معیار ما در اینجا رابطه‌ی سود/ضرر است.

اکسپرت

با معیار بهینه‌سازی شروع می‌کنیم. برای محاسبه‌ی آن نیاز داریم حین تست گرفتن، حداکثر بالانس و Drawdown را ردیابی و رصد کنیم. برای اینکه به منطقِ عملکرد اکسپرت وابسته نباشیم، این رشته کد‌های موجود را به ابتدای تابع ()start اضافه می‌کنیم.

   if (AccountEquity() > MaxEqu) MaxEqu = AccountEquity();
   if (MaxEqu-AccountEquity() > MaxDD) MaxDD = MaxEqu-AccountEquity();

جهت پردازش آخرین تیک، همان کدها را باید در تابع ()deinit تکرار کرد. بعد از آن می‌توانیم مقدارِ معیارِ بهینه‌سازی را محاسبه کنیم.

    Criterion = (AccountBalance()-StartBalance)/MaxDD;

حالا می‌توانیم بخش اصلی را آغاز کنیم – [یعنی] نگهداری فرآیند بهینه‌سازی. یک مشکل داریم—در خودِ MQL4 ابزاری برای تعیینِ پایانِ بهینه‌سازی، نداریم. تنها یک راه نجات می‌شناسم که اصطلاحاً به آن “بهینه‌سازی با شمارنده” می‌گویند. و این یعنی: تنها پارامتر قابل تغییر اکسپرت، متغیرِ اکسترنالِ خاص، یعنی counter (شمارنده)، است. هنوز در اینجا عواقب خطرناکی در انتظار ماست – [با این کار]، احتمال تغییر پارامترهای واقعی اکسپرت به‌شکل استاندارد را از دست می‌دهیم و باید خودمان جورش را بکشیم! ضرر دیگر این است که کَش بهینه‌سازی (optimization cache) از دوست، به دشمن ما تبدیل خواهد شد. اما مقصد، روش رسیدن را توجیه می‌کند، پس بیایید با پررویی هرچه تمام ادامه دهیم!

متغیرهای اکسترنال را اضافه می‌کنیم:

extern int Counter                    = 1;    // Counter of the tester's running
extern int TestsNumber                = 200;  // he check digit - total number of runs 
extern int MovingPeriodStepsNumber    = 20;   // Number of optimization steps for MovingPeriod 
extern int MovingShiftStepsNumber     = 10;   // Number of optimization steps for MovingShift
extern double MovingPeriodLow         = 150;  // Lower limit of the optimization range for MovingPeriod
extern double MovingShiftLow          = 1;    // Lower limit of the optimization range for MovingShift
extern double MovingPeriodStep        = 1;    // Optimization step for MovingPeriod 
extern double MovingShiftStep         = 1;    // Optimization step for MovingShift

Counter (شمارنده)، در ابتدا می‌آید. بعدی، متغیر بررسی (و اطلاعات) است. سپس تعداد مراحل (steps) را تعیین می‌کنیم؛ حد پایینی و مرحله‌ی بهینه‌سازی برای دو متغیر توکارِ مووینگ اَورج، که برای بهینه‌سازی مورد نظر ما هستند. در اینجا بیش-از-حد-بودن‌هایی را مشاهده می‌کنید: اگر بخواهیم یک بررسی کامل انجام دهیم و یک تست تمام‌عیار بگیریم، (که قصد چنین کاری را داریم)، حاصلِ MovingPeriodStepsNumber و MovingShiftStepsNumber باید برابر با TestsNumber باشد.

پس از هر بار تست گرفتن، اکسپرت، کاملاً به کار خود پایان می‌دهد و با تست گرفتن بعدی، اکسپرت، [گویی] در یک جسم تازه متولد می‌شود. ما دو ابزار برای سازماندهی “ذخیره‌سازی تکوینی” داریم: متغیرهای جهانی و یک فایل مجزا. از هر دوی آنها استفاده خواهیم کرد.

بیایید تابع ()init را اصلاح کنیم:

int init() {
  if (IsTesting() && TestsNumber > 0) {
    if (GlobalVariableCheck("FilePtr")==false || Counter == 1) {
      FilePtr = 0; 
      GlobalVariableSet("FilePtr",0); 
    } else {
      FilePtr = GlobalVariableGet("FilePtr"); 
    }
    MovingPeriod = MovingPeriodLow+((Counter-1)/MovingShiftStepsNumber)*MovingPeriodStep;
    MovingShift = MovingShiftLow+((Counter-1)%MovingShiftStepsNumber)*MovingShiftStep;    StartBalance = AccountBalance();
    MaxEqu = 0;
    MaxDD = 0;
  }   
  return(0);
}

آنچه ما اضافه کردیم، در شرایط عملیاتی، و فقط در تستر و در TestsNumber غیر-صفر، واقع شده‌است. بنابراین، وظیفه‌ی TestsNumber=0 این است که اکسپرت را به یک مووینگ اَورج استاندارد، تبدیل کند. حال که در حال بحث در مورد فرآیند بهینه‌سازی هستیم، می‌بایستی از هر احتمالی برای بالا بردن سرعت فرآیند، استفاده کنیم. به همین دلیل است که این کد، با فرآهم ساختن [شرایط] مدیریت نشانگر مستقیم فایل (بواسطه‌ی اجرای تستر)، و با استفاده از یک متغیر جهانی، شروع می‌شود. آنگاه مقادیر پارامترهای قابل تغییر را محاسبه ‌کرده، و متغیرهایی که برای محاسبه‌ی معیار بهینه‌سازی، استفاده شده‌اند را، مقداردهی اولیه می‌کنیم.

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

    if (Counter == 1) {
// First run, create/initialize a datafile.
      h=FileOpen("test.txt",FILE_CSV|FILE_WRITE,';');
      FileWrite(h,Criterion,MovingPeriod,MovingShift,Counter);
// Remember the position of the file pointer after writing in the global variable
      FilePtr = FileTell(h); 
      GlobalVariableSet("FilePtr",FilePtr);
      FileClose(h);

ویژگی پردازش دیگر شروع‌ها این است که داده‌های جدید، درون فایل اضافه می‌شوند:

    } else {
//  After the first start is processed, the data are added into the file
      h=FileOpen("test.txt",FILE_CSV|FILE_READ|FILE_WRITE,';');
//  It is time to use the file pointer written in the global variable
      FilePtr = GlobalVariableGet("FilePtr");
      FileSeek(h,FilePtr, SEEK_SET);
      FileWrite(h,Criterion,MovingPeriod,MovingShift,Counter);
//  Remember the file pointer position once again
      FilePtr = FileTell(h); 
      GlobalVariableSet("FilePtr",FilePtr);

اکنون بیایید اولین شروع را پردازش کنیم:

if (Counter == TestsNumber) {
        ArrayResize(Data,TestsNumber); 
// Returns the file pointer to the beginning       
        FileSeek(h,0,SEEK_SET);
// Read from the file the results of all testings
        int i = 0;
        while (i<TestsNumber && FileIsEnding(h)== false) {
          for (int j=0;j<4;j++) {
            Data[i][j]=FileReadNumber(h); 
          }
          i++;
        } 
// Sort the array according to our optimization criterion
        ArraySort(Data,WHOLE_ARRAY,0,MODE_DESCEND);
// Now let us arrange the results. Reopen the file        
        FileClose(h); 
        h=FileOpen("test.txt",FILE_CSV|FILE_WRITE,' ');
        FileWrite(h,"  Criterion","     MovingPeriod"," MovingShift"," Counter");
        for (i=0;i<TestsNumber;i++) {
          FileWrite(h,DoubleToStr(Data[i][0],10),"        ",Data[i][1],"        ",Data[i][2],"        ",Data[i][3]);
        }

این آرایه به‌طور پیش‌فرض به‌عنوان [double Data[][4 مقداردهی اولیه شد. همین. بیایید پاکسازی کنیم:

        GlobalVariableDel("FilePtr");
      }
      FileClose(h); 
    }
  }

کامپایل کنید، تستر را باز کرده و این اکسپرت را انتخاب کنید. آنگاه property sheet را باز کرده و ۴ پوزیشن را چک کنید:

  • حاصلِ MovingPeriodStepsNumber بواسطه‌ی MovingShiftStepsNumber باید برابر با TestsNumber باشد.
  • بهینه‌سازی فقط باید برای Counter انجام شده باشد.
  • محدوده‌ی بهینه‌سازی باید از ۱ تا TestsNumber، با مرحله‌ی ۱ (step 1)، باشد.
  • الگوریتم‌ ژنتیکی باید غیرفعال شده باشد.

بهینه‌سازی را آغاز کنید و در پایان به پوشه‌ی [Meta Trader]\tester\files رفته و نتیجه را در فایل test.txt ببینید. نویسنده این کار را برای EURUSD_H1 از اواسط ۲۰۰۴، روی قیمت‌های بازشدن، انجام داده و نتایج زیر را بدست آورده است:

چگونه معیارهای بهینه‌سازی خود را پ
چگونه معیارهای بهینه‌سازی خود را پیاده‌سازی کنیم؟

اکنون بیایید به همان جمله برگردیم که می‌گفت کَش دشمن ماست. واقعیت این است که وقتی ما نتایج تست را از کَش می‌گیریم، توابع ()deinit و ()init فعال نشده‌اند. در نتیجه، هنگام شروع مجدد بهینه‌سازی، همه یا بخشی از حالت‌ها[ی موجود]، بی‌اعتبار هستند. علاوه بر این، درحالیکه تعداد واقعی اجراها، کمتر از TestsNumber است، آرایه Data شامل تعدادی صفر می‌شود. نویسنده دو راه برای حذف تاثیر کَش پیشنهاد می‌دهد: کامپایل کردن مجدد اکسپرت، یا، بستن/متوقف ساختن/باز کردن پنجره‌ی تستر.

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

// Code of the independent counter
    if (GlobalVariableCheck("TestsCnt")==false || Counter == 1) {
      TestsCnt = 0; 
      GlobalVariableSet("TestsCnt",0); 
    } else {
      TestsCnt = GlobalVariableGet("TestsCnt"); 
    }
// Code of the independent counter
    TestsCnt++;
    GlobalVariableSet("TestsCnt",TestsCnt);
// Code of the independent counter
        GlobalVariableDel("TestsCnt");

و در نهایت اینکه، خواننده‌ی پیگیر، حتماً متوجه این موضوع شده‌است که می‌توانیم بدون متغیر FilePtr، (و متغیر جهانی همراه آن)، کار را انجام دهیم – داده‌ها در پایان فایل نوشته شده‌اند و از ابتدا خوانده می‌شوند. پس این کار برای چیست؟ جواب این است که: این اکسپرت برای نشان دادن روش نگه‌داری بهینه‌سازی طراحی شده‌است. این روش اجازه می‌دهد که عملکرد را حین کار و با نتایج تست قبلی، مدیریت کنیم. و در اینجا، نشانگر مستقیم فایل می‌تواند مفید باشد، همانطور که شمارنده‌ی مستقل مفید است. به‌عنوان مثالی که نیازمند کار کردن با نتایج قبلی، آن‌هم حین کار، هستیم، می‌توانیم به مدیریت تست خارج از نمونه و پیاده‌سازی الگوریتم ژنتیکی [که توسط خود فرد طراحی شده‌است]، اشاره کنیم.

نتیجهگیری

دلایل علاقه به چنین مشکلی، موضوعات مورد بحث در این انجمن بوده‌اند:  http://forum.mql4.com

 این مقاله دارای فایل پیوست است.

فایل پیوست را میتوانید از اینجا دانلود کنید .

 

مقالات پیشنهادی :

M23admin

→ خواندن مطلب قبلی

تِستِر در نرم‌افزار متاتریدر ۴ : چیزی که به آن نیاز دارید…

خواندن مطلب بعدی ←

عبور از محدودیت‌های استراتژی‌تستر در تست اکسپرت هج

۳۸ مورد نظر

نوشتن نظر شما

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *