بهینه سازی ازدحام ذرات | روش الگوریتم PSO

الگوریتم PSO

 همان­طور که می­دانید، MetaTrader 5 امکان بهینه­ سازی استراتژی­های معاملاتی را با استفاده از استراتژی تستر داخلی بر اساس دو الگوریتم فراهم می­کند: شمارش مستقیم پارامترهای ورودی و الگوریتم ژنتیک – GA. بهینه ­سازی ژنتیک نوعی الگوریتم تکاملی است که سرعت قابل توجه فرآیند را فراهم می­کند. با این حال، نتایج GA می­تواند به طور قابل توجهی به وظیفه و مشخصات یک اجرای خاص GA ، به ویژه مواردی که توسط تستر ارائه می­شود، بستگی داشته باشد. به همین دلیل بسیاری از معامله­ گرانی که مایل به افزایش عملکرد استاندارد هستند سعی می­کنند بهینه ­سازهای خود را برای MetaTrader ایجاد کنند. در این جا، روش های بهینه سازی سریع، محدود به الگوریتم ژنتیک نیستند. علاوه برGA ، روش­های معروف دیگری مانند الگوریتم تبرید شبیه سازی شده و بهینه سازی ازدحام ذرات نیز وجود دارد. در این مقاله، الگوریتم PSO (الگوریتم بهینه سازی ازدحام ذرات) را پیاده ­سازی خواهیم کرد و سعی خواهیم کرد آن را در تستر MetaTrader ادغام کنیم تا به صورت موازی بر روی عوامل محلی موجود اجرا شود. تابع بهینه­ سازی هدف، معیار عملکرد معامله EA است که توسط کاربر انتخاب شده است.

روش ازدحام ذرات در الگوریتم PSO

از نظر الگوریتمی ، روش الگوریتم PSO نسبتاً ساده است. ایده اصلی، تولید مجموعه­ ای از “ذرات” مجازی در فضای پارامترهای ورودی Expert Advisor است. سپس ذرات، بسته به معیارهای معاملات EA در نقاط مربوط به فضا حرکت کرده و سرعت خود را تغییر می­دهند. این روند چندین بار تکرار می­شود تا زمانی که عملکرد بهبود یابد. شبه کد الگوریتم در زیر نشان داده شده است:

کد های بهینه سازی در الگوریتم PSO
شبه کد بهینه سازی ازدحام ذرات

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

 class Particle
  {
    public:
      double position[];    // current point
      double best[];        // best point known to the particle
      double velocity[];    // current speed
      
      double positionValue; // EA performance in current point
      double bestValue;     // EA performance in the best point
      int    group;
      
      Particle(const int params)
      {
        ArrayResize(position, params);
        ArrayResize(best, params);
        ArrayResize(velocity, params);
        bestValue = -DBL_MAX;
        group = -1;
      }
  };

اندازه همه آرایه­ ها با بعد فضای بهینه ­سازی برابر است، بنابراین برابر است با تعداد پارامترهای Expert Advisor بهینه سازی شده (منتقل شده به سازنده). به طور پیش فرض، هرچه مقدار تابع هدف بزرگ­تر باشد، بهینه ­سازی بهتر است. بنابراین، bestValue را با حداقل تعداد -DBL_MAX ممکن مقدار دهی کنید. یکی از معیارهای معاملات مانند سود، ضریب سود، نسبت شارپ و غیره معمولاً به عنوان معیار ارزیابی EA استفاده می­شود. اگر بهینه ­سازی توسط پارامتری که مقادیر پایین­تر آن بهتر تلقی می­شود، انجام شود، مانند افت سرمایه، می­توان برای به حداکثر رساندن مقادیر مخالف، تغییرات مناسب را ایجاد کرد.

آرایه­ ها و متغیرها برای ساده ­سازی دسترسی و کد محاسبه مجدد آن­ها به صورت public در می­ آیند. رعایت دقیق اصول OOP مستلزم پنهان کردن آن­ ها با استفاده از اصلاح کننده “private” و توصیف روش­های خواندن و اصلاح است.

علاوه بر ذرات منفرد، الگوریتم با اصطلاحاً “توپولوژی” یا زیر مجموعه ذرات عمل می­کند. با توجه به اصول مختلف می ­توان آن­ها را ایجاد کرد. “توپولوژی گروه اجتماعی” در مورد ما استفاده خواهد شد. چنین گروهی اطلاعاتی را درباره بهترین موقعیت در بین تمام ذرات خود ذخیره می­کند.

class Group
  {
    private:
      double result;    // best EA performance in the group
    
    public:
      double optimum[]; // best known position in the group
      
      Group(const int params)
      {
        ArrayResize(optimum, params);
        ArrayInitialize(optimum, 0);
        result = -DBL_MAX;
      }
      
      void assign(const double x)
      {
        result = x;
      }
      
      double getResult() const
      {
        return result;
      }
      
      bool isAssigned()
      {
        return result != -DBL_MAX;
      }
  };

با تعیین یک نام گروه در قسمت ” group  از کلاس Particle، گروهی را که ذره به آن تعلق دارد نشان می­دهیم (به بالا نگاه کنید).

حال، اجازه دهید ما به سراغ برنامه­ نویسی خود الگوریتم PSO برویم. به عنوان یک کلاس جداگانه اجرا خواهد شد. بیایید با آرایه­ های particles و groups شروع کنیم.

 class Swarm
  {
    private:
      Particle *particles[];
      Group *groups[];
      int _size;             // number of particles
      int _globals;          // number of groups
      int _params;           // number of parameters to optimize

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

 double highs[];
 double lows[];
 double steps[];

علاوه بر این، مجموعه بهینه پارامترها باید در جایی ذخیره شود.

  double solution[];

از آن جا که کلاس، چندین سازنده مختلف خواهد داشت، بیایید متد مقداردهی اولیه یکنواخت را توصیف کنیم.

 void init(const int size, const int globals, const int params, const double &max[], const double &min[], const double &step[])
    {
      _size = size;
      _globals = globals;
      _params = params;
      
      ArrayCopy(highs, max);
      ArrayCopy(lows, min);
      ArrayCopy(steps, inc);
      
      ArrayResize(solution, _params);
      
      ArrayResize(particles, _size);
      for(int i = 0; i < _size; i++)          // loop through particles
      {
        particles[i] = new Particle(_params);
        
        ///do
        ///{
          for(int p = 0; p < _params; p++)    // loop through all dimensions
          {
            // random placement
            particles[i].position[p] = (MathRand() * 1.0 / 32767) * (highs[p] - lows[p]) + lows[p];
            // adjust it according to step granularity
            if(steps[p] != 0)
            {
              particles[i].position[p] = ((int)MathRound((particles[i].position[p] - lows[p]) / steps[p])) * steps[p] + lows[p];
            }
            // the only position is the best so far
            particles[i].best[p] = particles[i].position[p];
            // random speed
            particles[i].velocity[p] = (MathRand() * 1.0 / 32767) * 2 * (highs[p] - lows[p]) - (highs[p] - lows[p]);
          }
        ///}
        ///while(index.add(crc64(particles[i].position)) && !IsStopped());
      }
      
      ArrayResize(groups, _globals);
      for(int i = 0; i < _globals; i++)
      {
        groups[i] = new Group(_params);
      }
      
      for(int i = 0; i < _size; i++)
      {
        // random group membership
        particles[i].group = (_globals > 1) ? (int)MathMin(MathRand() * 1.0 / 32767 * _globals, _globals - 1) : 0;
      }
    }

 

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

توجه داشته باشید که نسخه کلاسیک الگوریتم PSO برای بهینه سازی توابع تعریف شده روی مختصات پیوسته در نظر گرفته شده است. با این حال، پارامترهای EA معمولاً با یک مرحله مشخص تست می­شوند. به عنوان مثال، یک میانگین متحرک استاندارد نمی­تواند 5.11 دوره داشته باشد. به همین دلیل است که علاوه بر طیف وسیعی از مقادیر قابل قبول برای همه ابعاد، مرحله استفاده شده برای گرد کردن موقعیت ذرات را تنظیم می­کنیم. این کار نه تنها در مرحله مقدماتی، بلکه در محاسبات هنگام بهینه سازی نیز انجام خواهد شد.

اکنون، ما می­توانیم چند سازنده را با استفاده از init پیاده ­سازی کنیم.

 #define AUTO_SIZE_FACTOR 5
  
  public:
    Swarm(const int params, const double &max[], const double &min[], const double &step[])
    {
      init(params * AUTO_SIZE_FACTOR, (int)MathSqrt(params * AUTO_SIZE_FACTOR), params, max, min, step);
    }
    
    Swarm(const int size, const int globals, const int params, const double &max[], const double &min[], const double &step[])
    {
      init(size, globals, params, max, min, step);
    }

مورد اول برای محاسبه اندازه جمعیت و تعداد گروه­ ها بر اساس تعداد پارامترها از یک قانون کلی شناخته شده استفاده می­کند. ثابت AUTO_SIZE_FACTOR را که به طور پیش فرض 5 است، می­توان به دلخواه تغییر داد. سازنده دوم به طور صریح مشخصات همه مقادیر را مجاز می­داند.

مخرب حافظه اختصاص داده شده را آزاد می­کند.

 ~Swarm()
    {
      for(int i = 0; i < _size; i++)
      {
        delete particles[i];
      }
      for(int i = 0; i < _globals; i++)
      {
        delete groups[i];
      }
    }

اکنون وقت آن است که روش اصلی کلاس را که مستقیماً بهینه­ سازی را انجام می­دهد، بنویسیم.

    double optimize(Functor &f, const int cycles, const double inertia = 0.8, const double selfBoost = 0.4, const double groupBoost = 0.4)

اولین پارامتر، Functor & f، مورد توجه خاص است. بدیهی است که Expert Advisor  در طی فرآیند بهینه­ سازی پارامترهای ورودی مختلف، فراخوانی می­شود که در پاسخ به آن­ها عدد تخمینی (سود، سودآوری یا ویژگی دیگر) برمی­گردد. این جمعیت در مورد Expert Advisor  چیزی نمی­داند (و نباید چیزی هم بداند). تنها وظیفه آن یافتن مقدار بهینه یک تابع هدف ناشناخته با مجموعه دلخواه آرگومان­ های عددی است. به همین دلیل ما از یک رابط انتزاعی یعنی کلاس Functor استفاده می­کنیم.

class Functor
  {
    public:
      virtual double calculate(const double &vector[]) = 0;
  };

تنها متدی که آرایه ­ای از پارامترها را دریافت می­کند و یک عدد را برمی­گرداند (همه انواع double  هستند). در آینده، EA باید به نوعی یک کلاس مشتق شده از Functor را پیاده سازی کند و متغیر مورد نیاز را در متد calculate محاسبه کند. بنابراین، اولین پارامتر متد”optimize ” یک شی با تابع بازگشتی ارائه شده توسط ربات معاملاتی را دریافت می­کند.

پارامتر دوم متد ” optimize” حداکثر تعداد حلقه ­ها برای اجرای الگوریتم است. 3 پارامتر بعدی ضرایب PSO را تنظیم می­کنند: ‘ inertia ‘ – حفظ سرعت ذره (سرعت معمولاً با مقادیر کم­تر از 1 کاهش می­یابد)،   ‘selfBoost’  و ‘ ’groupBoost تعیین می­کنند که ذره هنگام تنظیم جهت خود به بهترین موقعیت­های شناخته شده در تاریخچه ذره / گروه چقدر پاسخگو است.

اکنون که تمام پارامترها را بررسی کردیم، می­توانیم به سراغ الگوریتم PSO برویم. حلقه­های بهینه­سازی تقریباً به صورت کاملاً ساده شبه کد را به طور کامل دوباره تولید می­کنند.

 double optimize(Functor &f, const int cycles, const double inertia = 0.8, const double selfBoost = 0.4, const double groupBoost = 0.4)
    {
      double result = -DBL_MAX;
      ArrayInitialize(solution, 0);
      
      for(int c = 0; c < cycles && !IsStopped(); c++)   // predefined number of cycles
      {
        for(int i = 0; i < _size && !IsStopped(); i++)  // loop through all particles
        {
          for(int p = 0; p < _params; p++)              // update particle position and speed
          {
            double r1 = MathRand() * 1.0 / 32767;
            double rg = MathRand() * 1.0 / 32767;
            particles[i].velocity[p] = inertia * particles[i].velocity[p] + selfBoost * r1 * (particles[i].best[p] - particles[i].position[p]) + groupBoost * rg * (groups[particles[i].group].optimum[p] - particles[i].position[p]);
            particles[i].position[p] = particles[i].position[p] + particles[i].velocity[p];
            
            // make sure to keep the particle inside the boundaries of parameter space
            if(particles[i].position[p] < lows[p]) particles[i].position[p] = lows[p];
            else if(particles[i].position[p] > highs[p]) particles[i].position[p] = highs[p];
            
            // respect step size
            if(steps[p] != 0)
            {
              particles[i].position[p] = ((int)MathRound((particles[i].position[p] - lows[p]) / steps[p])) * steps[p] + lows[p];
            }
          }
          
          // get the function value for the particle i
          particles[i].positionValue = f.calculate(particles[i].position);
          
          // update the particle's best value and position (if improvement is found)          
          if(particles[i].positionValue > particles[i].bestValue)
          {
            particles[i].bestValue = particles[i].positionValue;
            ArrayCopy(particles[i].best, particles[i].position);
          }
          
          // update the group's best value and position (if improvement is found)          
          if(particles[i].positionValue > groups[particles[i].group].getResult())
          {
            groups[particles[i].group].assign(particles[i].positionValue);
            ArrayCopy(groups[particles[i].group].optimum, particles[i].position);
            
            // update the global maximum value and solution (if improvement is found)          
            if(particles[i].positionValue > result)
            {
              result = particles[i].positionValue;
              ArrayCopy(solution, particles[i].position);
            }
          }
        }
      }
      
      return result;
    }

این روش حداکثر مقدار به دست آمده از تابع هدف را برمی­گرداند. روش دیگر مختص خواندن مختصات است (مجموعه پارامترها).

  bool getSolution(double &result[])
    {
      ArrayCopy(result, solution);
      return !IsStopped();
    }

این تقریباً کل الگوریتم PSO است. من قبلاً اشاره کردم که چند ساده ­سازی وجود دارد. اول از همه، ویژگی خاص زیر را در نظر بگیرید.

جهان گسسته بدون تکرار در الگوریتم PSO

برای محاسبه مجدد پویای مجموعه پارامترها،functor  بارها و بارها فراخوانی می­شود، اما هیچ تضمینی وجود ندارد که الگوریتم PSO چندین بار به همان نقطه برخورد نخواهد کرد، خصوصاً با توجه به دقت در محورها. برای جلوگیری از چنین برخوردی، لازم است به نحوی امتیازات محاسبه شده را شناسایی کرده و از آن­ها صرف نظر کنید.

پارامترها فقط اعداد یا دنباله ­ای از بایت ­ها هستند. مشهور ترین تکنیک برای بررسی منحصر به فرد بودن داده­ها استفاده از هش است. محبوب­ترین راه برای به دست آوردن هش، CRC است. CRC یک عدد چک است (معمولاً یک عدد صحیح چند بیتی است) که بر اساس داده­ها تولید می­شود به گونه­ای که مطابقت دو عدد مشخص از مجموعه داده ­ها به معنی این است که به احتمال زیاد مجموعه­ ها یکسان است. هر چقدر بیت در CRC بیش­تر باشد، احتمال همسان سازی بیش­تر است (تقریباً تا 100٪). CRCی  64 بیتی احتمالاً برای کار ما کافی است. در صورت نیاز، می­توان آن را توسعه داد یا به تابع هش دیگری تغییر داد. پیاده­ سازی محاسبه CRC را می­توان به راحتی از C به MQL منتقل کرد. یکی از گزینه­ های ممکن در فایل crc64.mqh پیوست شده در زیر موجود است. تابع اصلی دارای نمونه اولیه است.

  ulong crc64(ulong crc, const uchar &s[], int l);

CRC را از بلوک داده قبلی (اگر بیش از یک باشد یا اگر یک بلوک وجود داشته باشد 0 را مشخص می­کند)، آرایه­ای از بایت ­ها و اطلاعات مربوط به تعداد عناصر پردازش شده در آن می پذیرد. این تابع CRC ی 64 بیتی را برمی­گرداند.

ما باید مجموعه ­ای از پارامترها را وارد این تابع کنیم. اما این کار مستقیماً قابل انجام نیست، زیرا هر یک double است. برای تبدیل آن به یک آرایه بایت، اجازه دهید از کتابخانه TypeToBytes.mqh استفاده کنیم (فایل به مقاله پیوست شده است ؛ اما بهتر است کد پایه برای نسخه به روز آن را بررسی کنید).

بعد از گنجاندن این کتابخانه، می توان یک تابع wrapper  برای محاسبه CRC64 از مجموعه­ ای از پارامترها ایجاد کرد:

  #include <TypeToBytes.mqh>
  #include <crc64.mqh>
  
  template<typename T>
  ulong crc64(const T &array[])
  {
    ulong crc = 0;
    int len = ArraySize(array);
    for(int i = 0; i < len; i++)
    {
      crc = crc64(crc, _R(array[i]).Bytes, sizeof(T));
    }
    return crc;
  }

اکنون سوالات زیر به وجود می ­آیند: کجا باید هش ­ها را ذخیره کرد و چگونه می­توان خاص بودن آن­ها را بررسی کرد. مناسب ­ترین راه ­حل یک درخت باینری است. این یک ساختار داده است که عملکردهای سریعی را برای افزودن مقادیر جدید و بررسی وجود موارد اضافه شده فراهم می­کند. سرعت زیاد توسط ویژگی خاص درختی به نام درخت متوازن تامین می­شود. به عبارت دیگر، درخت باید متوازن باشد (باید دائماً در حالت تعادل نگه داشته شود) تا از حداکثر سرعت عملیات اطمینان حاصل شود. واقعیت خوب این است که ما از درخت برای ذخیره هش استفاده می­کنیم. در اینجا تعریف هش آمده است.

تابع هش (الگوریتم تولید هش) برای هر داده ورودی مقدار خروجی توزیع شده یکنواخت ایجاد می­کند. در نتیجه، افزودن یک هش به یک درخت باینری از نظر آماری وضعیت آن را نزدیک به متوازن فراهم می­کند، و در نتیجه منجر به کارایی بالا می­شود.

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

یک جفت کلاس الگو برای پیاده سازی این منطق ایجاد شده است: TreeNode و BinaryTree. کدهای کامل آن­ها در فایل هدر پیوست ارائه شده است.

  template<typename T>
  class TreeNode
  {
    private:
      TreeNode *left;
      TreeNode *right;
      T value;
  
    public:
      TreeNode(T t): value(t) {}
      // adds new value into subtrees and returns false or
      // returns true if t exists as value of this node or in subtrees
      bool add(T t);
      ~TreeNode();
      TreeNode *getLeft(void) const;
      TreeNode *getRight(void) const;
      T getValue(void) const;
  };
    
  template<typename T>
  class BinaryTree
  {
    private:
      TreeNode<T> *root;
      
    public:
      bool add(T t);
      ~BinaryTree();
  };

اگر مقدار از قبل در درخت وجود داشته باشد، متد ‘add’، true  را برمی­گرداند. اگر قبلاً وجود نداشته باشد اما به تازگی اضافه شده باشد، false  را برمی­گرداند. حذف یک ریشه در مخرب درخت به طور خودکار منجر به حذف تمام نودهای فرزند می­شود.

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

اجازه دهید BinaryTree را به کلاس Swarm اضافه کنیم.

 class Swarm
  {
    private:
      BinaryTree<ulong> index;

قسمت ­هایی از متد ” optimize ” که در آن ذرات را به موقعیت ­های جدید منتقل می­کنیم باید گسترش یابد.

double optimize(Functor &f, const int cycles, const double inertia = 0.8, const double selfBoost = 0.4, const double groupBoost = 0.4)
      {
        // ...
        
        double next[];
        ArrayResize(next, _params);
  
        for(int c = 0; c < cycles && !IsStopped(); c++)
        {
          int skipped = 0;
          for(int i = 0; i < _size && !IsStopped(); i++)
          {
            // new placement of particles using temporary array next
            for(int p = 0; p < _params; p++)
            {
              double r1 = MathRand() * 1.0 / 32767;
              double rg = MathRand() * 1.0 / 32767;
              particles[i].velocity[p] = inertia * particles[i].velocity[p] + selfBoost * r1 * (particles[i].best[p] - particles[i].position[p]) + groupBoost * rg * (groups[particles[i].group].optimum[p] - particles[i].position[p]);
              next[p] = particles[i].position[p] + particles[i].velocity[p];
              if(next[p] < lows[p]) next[p] = lows[p];
              else if(next[p] > highs[p]) next[p] = highs[p];
              if(steps[p] != 0)
              {
                next[p] = ((int)MathRound(next[p] / steps[p])) * steps[p];
              }
            }
  
            // check if the tree contains this parameter set and add it if not
            if(index.Add(crc64(next)))
            {
              skipped++;
              continue;
            }
  
            // apply new position to the particle
            ArrayCopy(particles[i].position, next);
            
            particles[i].positionValue = f.calculate(particles[i].position);
            
            // ...
          }
          Print("Cycle ", c, " done, skipped ", skipped, " of ", _size, " / ", result);
          if(skipped == _size) break; // full coverage
        }

ما آرایه کمکی  ” next “را اضافه کردیم، جایی که ابتدا مختصات تازه ایجاد شده اضافه می­شوند. CRC برای آن­ها محاسبه شده و منحصر به فرد بودن مقدار بررسی می­شود. اگر موقعیت جدید هنوز پیدا نشده باشد، آن را به درخت اضافه می­کنیم، در ذره مربوطه کپی می­کنیم و تمام محاسبات لازم برای این موقعیت انجام می­شود. اگر موقعیت از قبل در درخت وجود داشته باشد (یعنی، functor  برای آن قبلاً محاسبه شده است)، از این تکرار صرف نظر می­شود.

تست عملکرد اولیه در الگوریتم PSO

هر آن چه در بالا مورد بحث قرار گرفت حداقل مبنای لازم برای اجرای اولین آزمون­ ها است. برای اطمینان از این که بهینه ­سازی واقعاً کار می­کند، اجازه دهید از اسکریپت testpso.mq5 استفاده کنیم. فایل هدر ParticleSwarmParallel.mqh فایل هدر مورد استفاده در این اسکریپت است که نه تنها شامل کلاس­ هایی است که از قبل آشنا بودند، بلکه شامل پیشرفت­های دیگری است که در زیر بررسی خواهیم کرد.

تست ­ها به سبک OOP طراحی شده­اند که به شما اجازه می­دهد توابع هدف مورد علاقه خود را تنظیم کنید. کلاس پایه برای آزمون­ ها BaseFunctor است.

 class BaseFunctor: public Functor
    {
      protected:
        const int params;
        double max[], min[], steps[];
        
      public:
        BaseFunctor(const int p): params(p) // number of parameters
        {
          ArrayResize(max, params);
          ArrayResize(min, params);
          ArrayResize(steps, params);
          ArrayInitialize(steps, 0);
          
          PSOTests::register(&this);
        }
        
        virtual void test(const int loop)   // worker method
        {
          Swarm swarm(params, max, min, steps);
          swarm.optimize(this, loop);
          double result[];
          swarm.getSolution(result);
          for(int i = 0; i < params; i++)
          {
            Print(i, " ", result[i]);
          }
        }
    };

همه اشیای کلاس های مشتق شده با استفاده از روش ‘register’ در کلاس PSOTests به طور خودکار در زمان ایجاد ثبت می شوند.

 class PSOTests
  {
      static BaseFunctor *testCases[];
    
    public:  
      static void register(BaseFunctor *f)
      {
        int n = ArraySize(testCases);
        ArrayResize(testCases, n + 1);
        testCases[n] = f;
      }
      
      static void run(const int loop = 100)
      {
        for(int i = 0; i < ArraySize(testCases); i++)
        {
          testCases[i].test(loop);
        }
      }
  };

تست­­ ها (بهینه ­سازی) با استفاده از متد ” run ” اجرا می­شود، که “test” را بر روی تمام اشیای ثبت شده فراخوانی می­کند.

بسیاری از توابع محک محبوب، از جمله “” rosenbrock ، “griewank” ، ” sphere ” وجود دارد که در اسکریپت اجرا می­شوند. به عنوان مثال، دامنه جستجو و متد ” calculate ” برای یک sphere  می­تواند به شرح زیر تعریف شود.

  class Sphere: public BaseFunctor
      {
        public:
          Sphere(): BaseFunctor(3) // expected global minimum (0, 0, 0)
          {
            for(int i = 0; i < params; i++)
            {
              max[i] = 100;
              min[i] = -100;
            }
          }
          
          virtual void test(const int loop)
          {
            Print("Optimizing " + typename(this));
            BaseFunctor::test(loop);
          }
          
          virtual double calculate(const double &vec[])
          {
            int dim = ArraySize(vec);
            double sum = 0;
            for(int i = 0; i < dim; i++) sum += pow(vec[i], 2);
            return -sum; // negative for maximization
          }
      };

توجه داشته باشید که توابع استاندارد محک از مینیمم ­سازی استفاده می­کنند، در حالی که ما یک الگوریتم PSO مبتنی بر ماکزیمم ­سازی را پیاده سازی کردیم (زیرا هدف ما جستجوی ماکزیمم عملکرد EA است). به همین دلیل، از نتیجه محاسبه با علامت منفی استفاده خواهد شد. هم­چنین، ما در این جا از مرحله گسسته استفاده نمی­کنیم، بنابراین توابع پیوسته هستند.

 void OnStart()
  {
    PSOTests::Sphere sphere;
    PSOTests::Griewank griewank;
    PSOTests::Rosenbrock rosenbrock;
    PSOTests::run();
  }

با اجرای اسکریپت، می­بینید که مقادیر مختصات نزدیک به راه­حل دقیق (extremeum) را ثبت می­کند. از آن جا که ذرات به صورت تصادفی مقداردهی می­شوند، هر اجرا، مقادیر کمی متفاوت تولید می­کند. دقت راه­حل به پارامترهای ورودی الگوریتم بستگی دارد.

Optimizing PSOTests::Sphere
  PSO[3] created: 15/3
  PSO Processing...
  Cycle 0 done, skipped 0 of 15 / -1279.167775306995
  Cycle 10 done, skipped 0 of 15 / -231.4807406906516
  Cycle 20 done, skipped 0 of 15 / -4.269510657558273
  Cycle 30 done, skipped 0 of 15 / -1.931949742316357
  Cycle 40 done, skipped 0 of 15 / -0.06018744740061506
  Cycle 50 done, skipped 0 of 15 / -0.009498109984732127
  Cycle 60 done, skipped 0 of 15 / -0.002058433538555499
  Cycle 70 done, skipped 0 of 15 / -0.0001494176502579518
  Cycle 80 done, skipped 0 of 15 / -4.141817579039349e-05
  Cycle 90 done, skipped 0 of 15 / -1.90930142126799e-05
  Cycle 99 done, skipped 0 of 15 / -8.161728746514931e-07
  PSO Finished 1500 of 1500 planned calculations: true
  0 -0.000594423827318461
  1 -0.000484001094843528
  2 0.000478096358862763
  Optimizing PSOTests::Griewank
  PSO[2] created: 10/3
  PSO Processing...
  Cycle 0 done, skipped 0 of 10 / -26.96927938978973
  Cycle 10 done, skipped 0 of 10 / -0.939220906325796
  Cycle 20 done, skipped 0 of 10 / -0.3074442362962919
  Cycle 30 done, skipped 0 of 10 / -0.121905607345751
  Cycle 40 done, skipped 0 of 10 / -0.03294107382891465
  Cycle 50 done, skipped 0 of 10 / -0.02138355984774098
  Cycle 60 done, skipped 0 of 10 / -0.01060479828529859
  Cycle 70 done, skipped 0 of 10 / -0.009728742850384609
  Cycle 80 done, skipped 0 of 10 / -0.008640623678293768
  Cycle 90 done, skipped 0 of 10 / -0.008578769833161193
  Cycle 99 done, skipped 0 of 10 / -0.008578769833161193
  PSO Finished 996 of 1000 planned calculations: true
  0 3.188612982502877
  1 -4.435728146291838
  Optimizing PSOTests::Rosenbrock
  PSO[2] created: 10/3
  PSO Processing...
  Cycle 0 done, skipped 0 of 10 / -19.05855349617553
  Cycle 10 done, skipped 1 of 10 / -0.4255148824156119
  Cycle 20 done, skipped 0 of 10 / -0.1935391314277153
  Cycle 30 done, skipped 0 of 10 / -0.006468452482022688
  Cycle 40 done, skipped 0 of 10 / -0.001031992354315317
  Cycle 50 done, skipped 0 of 10 / -0.00101322411502283
  Cycle 60 done, skipped 0 of 10 / -0.0008800704421316765
  Cycle 70 done, skipped 0 of 10 / -0.0005593151578155307
  Cycle 80 done, skipped 0 of 10 / -0.0005516786893301249
  Cycle 90 done, skipped 0 of 10 / -0.0005473814163781119
  Cycle 99 done, skipped 0 of 10 / -7.255520122486163e-06
  PSO Finished 982 of 1000 planned calculations: true
  0 1.001858172119364
  1 1.003524791491219

توجه داشته باشید که اندازه ازدحام و تعداد گروه­ها (در log نوشته شده است در خطوطی مانند PSO[N]  ایجاد شده: X / G، جایی كه N بعد فضایی است، X تعداد ذرات است، G تعداد گروه­ها است) به طور خودکار مطابق با قوانین برنامه­ریزی شده بر اساس داده­های ورودی انتخاب می­شود.

حرکت به دنیای موازی در الگوریتم PSO

اولین آزمون خوب است. با این حال، این یک تفاوت ظریف دارد – چرخه شمارش ذرات در یک نخ واحد انجام می شود، در حالی که ترمینال امکان استفاده از تمام هسته­های پردازنده را فراهم می­کند. هدف نهایی ما نوشتن یک موتور بهینه ­سازی PSO است که می­تواند در EA برای بهینه­ سازی چند رشته ­ای در تستر MetaTrader تعبیه شود و بنابراین یک جایگزین برای الگوریتم ژنتیک استاندارد ارائه دهد.

با انتقال مکانیکی الگوریتم درون EA به جای اسکریپت، نمی­توان محاسبات را موازی کرد. این مستلزم اصلاح الگوریتم PSO است.

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

برای جلوگیری از اصلاح هسته کلاس “” Swarm، بیایید از یک راه ­حل ساده استفاده کنیم: به جای چندین گروه در داخل کلاس، چندین نمونه کلاس ایجاد خواهیم کرد که در هر کدام، تعداد گروه­ها از بین می­رود، یعنی برابر با یک. علاوه بر این، ما باید کدی را ارائه دهیم که به نمونه ­ها امکان تبادل اطلاعات را بدهد، زیرا هر نمونه روی عامل آزمایش خود اجرا می­شود.

ابتدا اجازه دهید روش جدیدی را برای مقداردهی اولیه اضافه کنیم.

    Swarm(const int size, const int globals, const int params, const double &max[], const double &min[], const double &step[])
    {
      if(MQLInfoInteger(MQL_OPTIMIZATION))
      {
        init(size == 0 ? params * AUTO_SIZE_FACTOR : size, 1, params, max, min, step);
      }
      ...

با توجه به عملکرد برنامه در حالت بهینه سازی، تعداد گروه ­ها را برابر با 1 تنظیم کنید. اندازه ازدحام پیش فرض با یک قانون کلی تعیین می­شود (مگر این­که پارامتر “size” صریحاً روی مقداری غیر از 0 تنظیم شود).

در کنترل کننده رویداد OnTester ، Expert Advisor می­تواند نتیجه یک mini-swarm  (متشکل از تنها یک نود) را با استفاده از تابع getSolution به دست آورد و آن را در یک فریم به ترمینال ارسال کند. ترمینال می­تواند passها را آنالیز کرده و بهترین را انتخاب کند. از نظر منطقی، تعداد ازدحام / گروه­ های موازی باید برابر حداقل تعداد هسته­ ها باشد. با این حال، می­تواند بالاتر باشد (اگرچه باید سعی کنید چندین برابر آن باشد). هرچه ابعاد فضا بزرگ­تر باشد، ممکن است به گروه­های بیش­تری نیز نیاز باشد. اما تعداد هسته­ ها باید برای آزمایش­های ساده کافی باشد.

برای محاسبه فضای بدون امتیاز تکراری، تبادل داده بین نمونه­ها مورد نیاز است. همان­طور که به یاد دارید، لیست نقاط پردازش شده در هر شی در درخت باینری ” ” index ذخیره می­شود. همانند نتایج، می­تواند در یک فریم به ترمینال ارسال شود، اما مشکل این است که رجیستری ترکیب فرضی این لیست­ ها نمی­تواند به عامل ­های آزمایش برگردانده شود. متأسفانه، معماری تستر از انتقال کنترل شده اطلاعات فقط از طریق عامل­ها به ترمینال پشتیبانی می­کند اما نه به عقب. وظایف حاصل از ترمینال در قالب بسته به عوامل توزیع می­شود.

بنابراین، من تصمیم گرفتم فقط از عامل ­­های محلی استفاده کنم و شاخص­های هر گروه را در فایل­های یک فولدر مشترک (FILE_COMMON) ذخیره کنم. هر عامل، شاخص خود را می­نویسد و می­تواند شاخص­های سایر پاس­ ها را در هر زمان بخواند، و هم­چنین آن­ها را به شاخص خود اضافه کند. این می­تواند در هنگام راه ­اندازی اولیه مورد نیاز باشد.

درMQL ، فقط در صورت بسته بودن فایل، تغییرات در فایل نوشته شده توسط سایر فرایندها قابل خواندن است. فلگ­ های FILE_SHARE_READ، FILE_SHARE_WRITE و توابع FileFlush در اینجا کمکی نمی­کنند.

پشتیبانی از نوشتن شاخص­ها با استفاده از الگوی معروف “visitor” اجرا می­شود.

  template<typename T>
  class Visitor
  {
    public:
      virtual void visit(TreeNode<T> *node) = 0;
  };

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

 template<typename T>
  class Exporter: public Visitor<T>
  {
    private:
      int file;
      uint level;
      
    public:
      Exporter(const string name): level(0)
      {
        file = FileOpen(name, FILE_READ | FILE_WRITE | FILE_CSV | FILE_ANSI | FILE_SHARE_READ| FILE_SHARE_WRITE | FILE_COMMON, ',');
      }
      
      ~Exporter()
      {
        FileClose(file);
      }
      
      virtual void visit(TreeNode<T> *node) override
      {
        #ifdef PSO_DEBUG_BINTREE
        if(node.getLeft()) visit(node.getLeft());
        FileWrite(file, node.getValue());
        if(node.getRight()) visit(node.getRight());
        #else
        const T v = node.getValue();
        FileWrite(file, v);
        level++;
        if((level | (uint)v) % 2 == 0)
        {
          if(node.getLeft()) visit(node.getLeft());
          if(node.getRight()) visit(node.getRight());
        }
        else
        {
          if(node.getRight()) visit(node.getRight());
          if(node.getLeft()) visit(node.getLeft());
        }
        level--;
        #endif
      }
  };

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

روش ویژه ذخیره درخت به visitor عبور داده شده را می­توان با استفاده از کلاس Explorer به راحتی به کلاس BinaryTree اضافه کرد.

 template<typename T>
  class BinaryTree
  {
      ...
      void visit(Visitor<T> *visitor)
      {
        visitor.visit(root);
      }
  };

برای اجرای عملیات، روش جدیدی در کلاس Swarm نیز لازم است.

void exportIndex(const int id)
    {
      const string name = sharedName(id);
      Exporter<ulong> exporter(name);
      index.visit(&exporter);
    }

پارامتر ‘id’ به معنی یک عدد pass منحصر به فرد (برابر با شماره گروه) است. از این پارامتر برای پیکربندی بهینه ­سازی در تستر استفاده خواهد شد. روش exportIndex را باید بلافاصله پس از اجرای دو روش ازدحام فراخوانی کنید: بهینه سازی و getSolution. این کار توسط یک کد فراخوانی انجام می­شود ، زیرا ممکن است همیشه لازم نباشد: اولین مثال “موازی” ما (به ادامه مطلب مراجعه کنید) نیازی به آن ندارد. اگر تعداد گروه­ها با تعداد هسته­ ها برابر باشد، آن­ها قادر به تبادل اطلاعات نخواهند بود، زیرا به طور موازی راه اندازی می­شوند و خواندن فایل درون حلقه کارآمد نیست.

تابع کمکی sharedName، که در داخل exportIndex ذکر شده است، ایجاد یک نام منحصر به فرد را بر اساس شماره گروه، نام EA و پوشه ترمینال امکان پذیر می­کند.

 #define PPSO_FILE_PREFIX "PPSO-"
  
  string sharedName(const int id, const string prefix = PPSO_FILE_PREFIX, const string ext = ".csv")
  {
    ushort array[];
    StringToShortArray(TerminalInfoString(TERMINAL_PATH), array);
    const string program = MQLInfoString(MQL_PROGRAM_NAME) + "-";
    if(id != -1)
    {
      return prefix + program + StringFormat("%08I64X-%04d", crc64(array), id) + ext;
    }
    return prefix + program + StringFormat("%08I64X-*", crc64(array)) + ext;
  }

اگر یک شناسه برابر با -1 به تابع منتقل شود، تابع یک mask ایجاد می­کند تا تمام فایل­های این نمونه ترمینال را پیدا کند. این ویژگی هنگام حذف فایل­های موقت قدیمی (از بهینه سازی قبلی این Expert Advisorrt) و هم­چنین هنگام خواندن فهرست­های جریان­های موازی استفاده می­شود. در این­جا نحوه انجام آن به صورت کلی آمده است.

 

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

همان­طور که حدس می­زنید ، رابط Feed باید مستقیماً در ازدحام اجرا شود، زیرا ما این را از داخل FileReader عبور دادیم.

class Swarm: public Feed
  {
    private:
      ...
      int _read;
      int _unique;
      int _restored;
      BinaryTree<ulong> merge;
      
    public:
      ...
      virtual bool feed(const int dump) override
      {
        const ulong value = (ulong)FileReadString(dump);
        _read++;
        if(!index.add(value)) _restored++;    // just added into the tree
        else if(!merge.add(value)) _unique++; // was already indexed, hitting _unique in total
        return true;
      }
      ...

 

با استفاده از متغیرهای _read ، _unique و _restored ، روش تعداد کل عناصر خوانده شده (از تمام فایل­ها)، تعداد عناصر اضافه شده به فهرست و تعداد عناصر اضافه نشده (که قبلاً در فهرست هستند) محاسبه می­کند. از آن­جا که گروه­ها به طور مستقل کار می­کنند، شاخص­های گروه­های مختلف می­توانند تکراری باشند.

این آمار در تعیین لحظه ای که فضای جستجو به طور کامل کاوش شده یا نزدیک به کاوش کامل است مهم است. در این حالت ، تعداد _unique به تعداد ترکیبات ممکن پارامتر نزدیک می­شود.

با افزایش تعداد passهای تکمیل شده، امتیازات بی نظیر بیش­تری از تاریخ مشترک در فهرست­های محلی بارگیری می­شوند. پس از اجرای بعدی “محاسبه” ، ایندکس نقاط جدیدی را بررسی می­کند و اندازه فایل­های ذخیره شده دائماً رشد می­کند. به تدریج ، همپوشانی عناصر در فایل­ها شروع به غلبه می­کند. این امر به برخی هزینه­های اضافی نیاز دارد که با این وجود کم­تر از محاسبه مجدد فعالیت تجاری EA است. با نزدیک شدن پردازش به پوشش کامل فضای بهینه سازی ، این امر منجر به تسریع چرخه های PSO با پردازش هر یک از گروه­های بعدی (وظایف تستر) خواهد شد.

نمودار کلاس بهینه سازی ازدحام ذرات

نمودار کلاس بهینه سازی ازدحام ذرات
نمودار کلاس بهینه سازی ازدحام ذرات

تست محاسبات موازی در الگوریتم PSO

برای آزمایش عملکرد الگوریتم در چندین نخ، اجازه دهید اسکریپت قدیمی را به PspO.mq5 Expert Advisor تبدیل کنیم. این در حالت محاسبه ریاضی اجرا خواهد شد، زیرا هنوز فضای معامله لازم نیست.

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

  enum TEST
  {
    Sphere,
    Griewank,
    Rosenbrock
  };
  
  sinput int Cycles = 100;
  sinput TEST TestCase = Sphere;
  sinput int SwarmSize = 0;
  input int GroupCount = 0;

 

در این­جا می­توانیم تعداد چرخه­ها، اندازه ازدحام و تعداد گروه­ها را نیز مشخص کنیم. همه این­ها در پیاده­سازی functor ، به ویژه در سازنده Swarm استفاده می­شود. مقادیر صفر پیش فرض به معنای انتخاب خودکار بر اساس بُعد کار است.

  class BaseFunctor: public Functor
  {
    protected:
      const int params;
      double max[], min[], steps[];
      
      double optimum;
      double result[];
  
    public:
      ...
      virtual void test()
      {
        Swarm swarm(SwarmSize, GroupCount, params, max, min, steps);
        optimum = swarm.optimize(this, Cycles);
        swarm.getSolution(result);
      }
      
      double getSolution(double &output[]) const
      {
        ArrayCopy(output, result);
        return optimum;
      }
  };

تمام محاسبات از OnTester handler شروع می­شود. از پارامتر  GroupCount (که به وسیله آن تکرار تسترها سازماندهی می­شوند) به عنوان یک تصادفی ساز استفاده می­شود تا اطمینان حاصل شود که نخ­ها در رشته­های مختلف حاوی ذرات مختلف هستند. بسته به پارامتر TestCase یک test functor ایجاد می­شود. بعد، روش ()functor.test فراخوانی می­شود، پس از آن می­توان نتایج را با استفاده از () functor.getSolution خوانده و می­توان آن را در یک فریم به ترمینال ارسال کرد.

  double OnTester()
  {
    MathSrand(GroupCount); // reproducible randomization
    
    BaseFunctor *functor = NULL;
    
    switch(TestCase)
    {
      case Sphere:
        functor = new PSOTests::Sphere();
        break;
      case Griewank:
        functor = new PSOTests::Griewank();
        break;
      case Rosenbrock:
        functor = new PSOTests::Rosenbrock();
        break;
    }
    
    functor.test();
    
    double output[];
    double result = functor.getSolution(output);
    if(MQLInfoInteger(MQL_OPTIMIZATION))
    {
      FrameAdd("PSO", 0xC0DE, result, output);
    }
    else
    {
      Print("Solution: ", result);
      for(int i = 0; i < ArraySize(output); i++)
      {
        Print(i, " ", output[i]);
      }
    }
    
    delete functor;
    return result;
  }

 

یک دسته از توابع OnTesterInit ، OnTesterPass ، OnTesterDeinit در ترمینال کار می­کنند. این فریم­ها را جمع آوری می­کند و بهترین راه­حل را از راه­حل­های ارسال شده تعیین می­کند.

  int passcount = 0;
  double best = -DBL_MAX;
  double location[];
  
  void OnTesterPass()
  {
    ulong pass;
    string name;
    long id;
    double r;
    double data[];
    
    while(FrameNext(pass, name, id, r, data))
    {
      // compare r with all other passes results
      if(r > best)
      {
        best = r;
        ArrayCopy(location, data);
      }
    
      Print(passcount, " ", id);
      
      const int n = ArraySize(data);
      ArrayResize(data, n + 1);
      data[n] = r;
      ArrayPrint(data, 12);
    
      passcount++;
    }
  }
  
  void OnTesterDeinit()
  {
    Print("Solution: ", best);
    ArrayPrint(location);
  }

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

هم­چنین اجازه دهید Expert Advisory نه تنها در تستر بلکه در یک نمودار منظم نیز فعال شود. در این حالت، الگوریتم PSO در حالت تک نخی منظم اجرا می­شود.

  int OnInit()
  {
    if(!MQLInfoInteger(MQL_TESTER))
    {
      EventSetTimer(1);
    }
    return INIT_SUCCEEDED;
  }
  
  void OnTimer()
  {
    EventKillTimer();
    OnTester();
  }

بگذارید ببینیم چگونه کار می­کند مقادیر زیر از پارامترهای ورودی استفاده خواهد شد:

  • Cycles — 100 ؛
  • TestCase – Griewank ؛
  • SwarmSize – 100 ؛
  • GroupCount — 10 ؛

هنگام راه اندازی یک Expert Advisor در نمودار، گزارش زیر نوشته خواهد شد.

Successive PSO of Griewank
  PSO[2] created: 100/10
  PSO Processing...
  Cycle 0 done, skipped 0 of 100 / -1.000317162069485
  Cycle 10 done, skipped 0 of 100 / -0.2784790501384311
  Cycle 20 done, skipped 0 of 100 / -0.1879188508394087
  Cycle 30 done, skipped 0 of 100 / -0.06938172138150922
  Cycle 40 done, skipped 0 of 100 / -0.04958694402304631
  Cycle 50 done, skipped 0 of 100 / -0.0045818974357138
  Cycle 60 done, skipped 0 of 100 / -0.0045818974357138
  Cycle 70 done, skipped 0 of 100 / -0.002161613760466419
  Cycle 80 done, skipped 0 of 100 / -0.0008991629607246754
  Cycle 90 done, skipped 0 of 100 / -1.620636881582982e-05
  Cycle 99 done, skipped 0 of 100 / -1.342285474092986e-05
  PSO Finished 9948 of 10000 planned calculations: true
  Solution: -1.342285474092986e-05
  0 0.004966759354110293
  1 0.002079707592422949

از آن­جا که موارد آزمایشی به سرعت محاسبه می­شوند (ظرف یک یا دو ثانیه)، اندازه گیری زمان، منطقی نیست. بعداً برای کارهای واقعی معامله اضافه خواهد شد.

اکنون، EA را در تستر انتخاب کنید، “محاسبات ریاضی” را در لیست “”Modeling تنظیم کنید، از پارامترهای فوق به جز  GroupCount برای EA استفاده کنید. این پارامتر برای بهینه سازی استفاده خواهد شد. بنابراین، مقادیر اولیه و نهایی را برای آن تنظیم کنید، بگذارید بگوییم 0 و 3 ، با یک مرحله 1 ، برای تولید 4 گروه لازم است (برابر با تعداد هسته). اندازه همه گروه­ها 100 خواهد بود (SwarmSize ، کل گروه). با وجود تعداد هسته­های پردازنده کافی است (اگر همه گروه­ها به طور موازی بر روی عوامل کار کنند)، این نباید بر کارایی تأثیر بگذارد، بلکه از طریق بررسی­های اضافی فضای بهینه سازی، دقت راه­حل را افزایش می­دهد. گزارش زیر را می­توان دریافت کرد:

  Parallel PSO of Griewank
  -12.550070232909  -0.002332638407  -0.039510275469
  -3.139749741924  4.438437934965 -0.007396077598
   3.139620588383  4.438298282495 -0.007396126543
   0.000374731767 -0.000072178955 -0.000000071551
  Solution: -7.1550806279852e-08 (after 4 passes)
   0.00037 -0.00007

 

بنابراین، اطمینان حاصل کردیم که اصلاح موازی الگوریتم PSO در تستر در حالت بهینه سازی در دسترس قرار گرفته است. اما تاکنون فقط یک آزمون با استفاده از محاسبات ریاضی بوده است. در مرحله بعد، اجازه دهید PSO را برای بهینه سازی Expert Advisors در یک فضای معامله­ای وفق دهیم.

برای مطالعه ادامه این مقاله روی لینک زیر کلیک کنید:

مجازی سازی و بهینه سازی  Expert Advisors

این مقاله ترجمه شده توسط تیم آکادمی ایران ام کیو ال می باشد. 

صفحه اصلی مقاله

 

سایر مقالات مرتبط

متا تریدر چیست؟
متاتریدر

متا تریدر چیست؟

متا تریدر چیست؟ اولین سوالی که هر فرد وقتی می خواهد آموزش های متاتریدر مانند آموزش صفر تا صد mql5،

کامل ترین و بهترین آموزش متاتریدر 4
mql4

کامل ترین آموزش متاتریدر 4

بهترین آموزش متاتریدر 4 متاتریدر4 یک پلتفرم معاملاتی محسوب می‌ شود که دارای رابط کاربری ساده است و همین یادگیری

پاسخ‌ها

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