English Русский 中文 Español 日本語 Português
preview
Royal-Flush-Optimierung (RFO)

Royal-Flush-Optimierung (RFO)

MetaTrader 5Handelssysteme |
20 2
Andrey Dik
Andrey Dik

Inhalt

  1. Einführung
  2. Implementierung des Algorithmus
  3. Testergebnisse


Einführung

Es gibt viele Ansätze zur Lösung von Optimierungsproblemen, unter denen genetische Algorithmen einen besonderen Platz einnehmen, da sie in der Lage sind, den Suchraum effektiv zu erkunden, indem sie die Prozesse der natürlichen Evolution simulieren. Der herkömmliche genetische Algorithmus verwendet eine binäre Kodierung der Lösungen, was eine Umwandlung von reellen Zahlen in ein binäres Format erfordert. Diese Umwandlung führt nicht nur zu zusätzlicher Komplexität, sondern belastet auch den Algorithmus erheblich. In der modernen Welt spielt die Minimierung des Rechenaufwands eine entscheidende Rolle, und die Produktivität einer Methode ist in der Regel direkt proportional zu ihrer Geschwindigkeit. Bei der Lösung dieses Problems kam mir die Idee, wie man die genetischen Operatoren beibehalten und gleichzeitig die schwierigsten Berechnungen bei der Umwandlung von reellen Zahlen durch einfachere und weniger energieaufwändige Operationen ersetzen kann.

Mein Algorithmus, die Royal Flush Optimierung (RFO), ist ein neuer Ansatz zur Lösung von Optimierungsproblemen, der die wichtigsten Vorteile genetischer Algorithmen beibehält, aber eine direktere Art der Darstellung von Lösungen verwendet. Der Grundgedanke besteht darin, jede Koordinate des Suchraums in Sektoren zu unterteilen, ähnlich wie ein Pokerblatt aus einzelnen Spielkarten eines bestimmten Ranges besteht. Anstatt mit Bitstrings zu arbeiten, verwaltet der Algorithmus Spielkartenränge (Sektornummern), wodurch die Topologie des Suchraums auf natürliche Weise erhalten werden kann.

Meiner Meinung nach liegen die Hauptvorteile des vorgeschlagenen Ansatzes in der Einfachheit der Implementierung und der intuitiven Klarheit (die Arbeit mit „Spielkarten“ ist visueller als mit Bit-Strings) sowie in der Tatsache, dass keine reellen Zahlen kodiert und dekodiert werden müssen, während die kombinatorischen Eigenschaften des genetischen Algorithmus erhalten bleiben. In diesem Artikel werden wir die Implementierung des Algorithmus und die Merkmale der Entscheidungsänderungsoperatoren im Detail betrachten.

Die Poker-Metapher gibt dem Algorithmus nicht nur seinen Namen, sondern beschreibt auch sein Wesen gut: So wie beim Poker ein Spieler danach strebt, die beste Kombination von Spielkarten zu sammeln, kombiniert der Algorithmus Sektoren verschiedener Lösungen und bildet so nach und nach optimale „Hände“. So wie beim Poker jede Spielkarte ihren eigenen Rang und ihre eigene Farbe hat, so hat beim Algorithmus jeder Sektor seinen eigenen Wert und seine eigene Position im Suchraum. In diesem Fall ist, wie in einem echten Spiel, nicht nur der Wert der einzelnen Spielkarten wichtig, sondern auch ihr Zusammenspiel in der Gesamtkombination.

Es ist erwähnenswert, dass dieser Ansatz als eine Verallgemeinerung der Ideen der diskreten Optimierung auf den Fall der kontinuierlichen Räume betrachtet werden kann, was interessante Perspektiven für die theoretische Analyse und praktische Anwendung eröffnet. So wie Poker Elemente des Zufalls und der Strategie kombiniert, verbindet RFO die zufällige Suche mit gerichteter Optimierung, was es für komplexe multivariate Probleme effektiv macht.


Implementierung des Algorithmus

Der Algorithmus der Royal Flush Optimierung (RFO) basiert auf der Idee, den Suchraum als diskrete Sektoren darzustellen, ähnlich wie die Spielkarten beim Poker bestimmte Ränge haben. Bei einem herkömmlichen genetischen Algorithmus werden die Werte (optimierte Parameter) für alle Koordinaten in Binärcode umgewandelt und in eine chromosomenähnliche Sequenz gefaltet, was zusätzliche Rechenkosten verursacht. In der RFO wird dieser Ansatz zugunsten einer einfacheren und intuitiveren Darstellung aufgegeben.

Anstelle einer binären Kodierung unterteilen wir jede Koordinate des Suchraums in Sektoren und weisen ihnen Werte zu, die denen von Pokerkarten entsprechen – von Bube bis Ass (J, Q, K, A). Die Anzahl der Ränge (Sektoren) wird in den externen Parametern des Algorithmus angegeben und kann ein beliebiger ganzzahliger Wert sein. So kann jeder Punkt im Suchraum als eine Kombination von Spielkarten dargestellt werden, wobei jede Spielkarte entsprechend ihrer Koordinate einem bestimmten Sektor entspricht. Dieser Ansatz vereinfacht nicht nur die Berechnungen, sondern bewahrt auch die kombinatorischen Eigenschaften des genetischen Algorithmus.

Während der Optimierung arbeitet der Algorithmus mit „Blättern“, d. h. mit Spielkartensätzen, die verschiedene Lösungen darstellen. Crossover- und Mutations-Operatoren werden direkt auf „Hände“ (Spielkartensätze) angewandt, wobei jede Hand einem Chromosom entspricht, was die Erkundung des Suchraums ohne binäre Umwandlung ermöglicht.

Die folgende Abbildung (Abbildung 1) verdeutlicht dieses Prinzip. Sie zeigt einen dreidimensionalen Suchraum mit X-, Y- und Z-Koordinaten, wobei jede Koordinate in Sektoren unterteilt ist, die den Rängen der Spielkarten entsprechen. Auf der rechten Seite sind Beispiele für „Hände“ zu sehen – verschiedene Kombinationen von Spielkarten, die der Algorithmus bei der Suche nach der optimalen Lösung bildet.

RFO

Abbildung 1. Der RFO-Algorithmus und die Aufteilung der Koordinaten in Spielkartenspielränge

Gehen wir nun dazu über, den Pseudocode für den Algorithmus der Royal Flush Optimierung (RFO) zu schreiben:

  • Initialisierung:
    • Lege die Anzahl der Spieler am „Pokertisch“ fest (Populationsgröße).
    • Bestimme die Größe des „Decks“ (die Anzahl der Sektoren für jede Dimension).
    • Lege die Wahrscheinlichkeit des „Bluffens“ (Mutation) fest.
    • Erstelle eine erste „Hand“ – zufällige Generierung von Spielkartenrängen für jede Koordinate.
    • Wandle die Ränge in reale Werte mit einem zufälligen Offset innerhalb des Sektors.
  • Die Hauptspielschleife:
    • Für jede Position am Tisch:
      • Wähle den „Gegner“ durch quadratische Auswahl (stärkere „Hände“ haben eine höhere Chance, ausgewählt zu werden).
      • Kopiere die aktuelle „Hand“ (Lösung).
      • Führe einen Drei-Punkte-Spielkartentausch durch:
        • Wähle nach dem Zufallsprinzip drei „Schnittpunkte“ aus.
        • Sortiere die Schnittpunkte.
        • Wähle zufällig einen Startpunkt (0 oder 1).
        • Tausche die Spielkarten zwischen der aktuellen Hand und der Hand des Gegners.
        • Ein „Bluff“ (Mutationswahrscheinlichkeit) ist möglich – eine zufällige Änderung des Ranges einer Spielkarte mit einer bestimmten Wahrscheinlichkeit.
      • Wandle die erhaltenen Spielkartenränge in reale Koordinatenwerte.
  • Bewertung und Aktualisierung:
    • Berechne den Wert jeder „Hand“ (den Wert der Zielfunktion).
    • Erinnere Dich an die beste gefundene Kombination (global beste Lösung).
    • Kombiniere die aktuellen Hände mit dem vorherigen Deck.
    • Sortiere alle „Hände“ nach ihrem Wert.
    • Gib die besten „Hände“ an die nächste Generation weiter.
  • Beendigung des Algorithmus bei Erreichen des Abbruchkriteriums.
  • Fahren wir mit dem Schreiben des Algorithmus-Codes fort. Wir schreiben die Struktur S_RFO_Agent, die ein Objekt darstellt, das Informationen über die „Hand“ im Kontext des Spiels enthält.

    Die Felder der Struktur:

    • card [] – Array zum Speichern des tatsächlichen Wertes der Spielkarte.
    • f – Handwert (Wert der Fitnessfunktion).
    • cardRanks [] – Array von Ganzzahlen, die die „Spielkartenränge“ (Sektornummern) darstellen.

    Die Methode Init () initialisiert die Struktur und nimmt einen einzelnen Parameter „coords“, der die Anzahl der Spielkarten in der „Hand“ angibt.

    //——————————————————————————————————————————————————————————————————————————————
    // Structure for representing a single "hand"
    struct S_RFO_Agent
    {
        double card [];       // cards
        double f;             // value of the fitness function ("hand value")
        int    cardRanks [];  // sector numbers ("map ranks") 
    
        void Init (int coords)
        {
          ArrayResize (cardRanks, coords);
          ArrayResize (card,      coords);
          f = -DBL_MAX;       // initialization with minimum value
        }
    };
    //——————————————————————————————————————————————————————————————————————————————
    

    Die Klasse C_AO_RFO implementiert den Algorithmus und erbt Eigenschaften und Methoden von der Basisklasse C_AO. Schauen wir uns das genauer an.

    Der Konstruktor C_AO_RFO () setzt Werte für Klassenvariablen und initialisiert Parameter:
    • popSize – Die Populationsgröße (Pokertisch) wird auf 50 gesetzt.
    • deckSize – Anzahl der Spielkarten im Deck (oder Sektoren) – 1000.
    • dealerBluff – Die Wahrscheinlichkeit eines Bluffs (Mutation) wird auf 3% (0,03) gesetzt.
    • Das Array „params“ dient zum Speichern von Parametern, wird auf 3 verkleinert und mit den Werten für popSize, deckSize und dealerBluff gefüllt.

    Die Methode SetParams () – die Methode extrahiert Werte aus dem Array „params“ und weist sie den entsprechenden Klassenvariablen zu.

    Die Methode Init () dient dazu, die Klasse mit den übergebenen Parametern zu initialisieren, z. B. mit den Minimal- und Maximalwerten der zu optimierenden Parameter und deren Schritt.

    Die Methoden Moving() und Revision() werden verwendet, um Operationen auszuführen, die mit dem Mischen von Spielkarten in Ihrer Hand und der Überprüfung ihrer besten Kombination zusammenhängen.

    Felder der Klasse:
    • deckSize – Anzahl der Sektoren in der Dimension.
    • dealerBluff – Mutationswahrscheinlichkeit.
    • deck [], tempDeck [], hand [] – Arrays vom Typ S_RFO_Agent, die das Hauptdeck, das temporäre Deck zum Sortieren bzw. die aktuelle Hand (Nachkommen) repräsentieren.
    Private Klassenmitglieder:
    • cutPoints – Anzahl der „Schnittpunkte“ des vorliegenden Spielkartensatzes, die zum Kombinieren von Spielkartensatzvarianten verwendet werden.
    • tempCuts [] und finalCuts [] – Arrays zum Speichern von temporären und endgültigen „Schnittpunktindizes“.
    • Die verwendeten Methoden sind Evolution() – verantwortlich für die Durchführung der grundlegenden Entwicklung von Spielkarten-Permutationen, und DealCard() – verantwortlich für die Umwandlung eines Sektors in seinen realen Wert. Die Methode ShuffleRanks () ist für die Mutation der Ränge zuständig (zufällige Auswahl unter den verfügbaren Rängen).
      //——————————————————————————————————————————————————————————————————————————————
      class C_AO_RFO : public C_AO
      {
        public:
        C_AO_RFO ()
        {
          ao_name = "RFO";
          ao_desc = "Royal Flush Optimization";
          ao_link = "https://www.mql5.com/en/articles/17063";
      
          popSize      = 50;      // "poker table" (population) size
          deckSize     = 1000;    // number of "cards" in the deck (sectors)
          dealerBluff  = 0.03;    // "bluff" (mutation) probability
      
          ArrayResize (params, 3);
      
          params [0].name = "popSize";      params [0].val = popSize;
          params [1].name = "deckSize";     params [1].val = deckSize;
          params [2].name = "dealerBluff";  params [2].val = dealerBluff;
        }
      
        void SetParams ()
        {
          popSize     = (int)params [0].val;
          deckSize    = (int)params [1].val;
          dealerBluff = params      [2].val;
        }
      
        bool Init (const double &rangeMinP  [],  // minimum values
                   const double &rangeMaxP  [],  // maximum values
                   const double &rangeStepP [],  // step change
                   const int     epochsP = 0);   // number of epochs
      
        void Moving   ();
        void Revision ();
      
        //----------------------------------------------------------------------------
        int    deckSize;         // number of sectors in the dimension
        double dealerBluff;      // mutation probability
      
        S_RFO_Agent deck [];     // main deck (population)
        S_RFO_Agent tempDeck []; // temporary deck for sorting
        S_RFO_Agent hand [];     // current hand (descendants)
      
        private: //-------------------------------------------------------------------
        int cutPoints;           // number of cutting points
        int tempCuts  [];        // temporary indices of cutting points
        int finalCuts [];        // final indices taking the beginning and end into account
      
        void   Evolution ();     // main process of evolution
        double DealCard (int rank, int suit);  // convert sector to real value
        void   ShuffleRanks (int &ranks []);   // rank mutation
      };
      //——————————————————————————————————————————————————————————————————————————————
      

      Die Methode Init dient der Initialisierung eines Objekts der Klasse C_AO_RFO.

      Die Methode beginnt mit dem Aufruf einer Funktion, die die Standardinitialisierung der gegebenen Parameter, wie z. B. Mindest- und Höchstwerte, sowie Parameteränderungsschritte durchführt. Schlägt diese Initialisierung fehl, bricht die Methode ab und gibt „false“ zurück. Nach erfolgreicher Initialisierung der Parameter bereitet die Methode Datenstrukturen für die Speicherung von Informationen über „Hände“ und „Decks“ vor. Dazu müssen die Arrays für die Speicherung von „Händen“ und „Decks“ an die Größe der Population angepasst werden.

      Die Methode initialisiert dann jedes Element des Arrays „hands“ mit einer speziellen Methode, die sie auf der Grundlage der angegebenen Koordinaten konfiguriert. In ähnlicher Weise werden auch die Arrays „deck“ und „temp deck“ vorbereitet und initialisiert. Die Methode legt die Anzahl der Schnittpunkte fest, die für den Crossover-Algorithmus erforderlich sind. In diesem Fall werden drei Schnittpunkte festgelegt (dies ist der beste Wert, der bei Versuchen ermittelt wurde). Dann werden Arrays eingerichtet, um temporäre und endgültige Schnittpunkte zu speichern. Am Ende gibt die Methode den Wert „true“ zurück, der bestätigt, dass die Initialisierung erfolgreich abgeschlossen wurde.

      //——————————————————————————————————————————————————————————————————————————————
      bool C_AO_RFO::Init (const double &rangeMinP  [],
                           const double &rangeMaxP  [],
                           const double &rangeStepP [],
                           const int     epochsP = 0)
      {
        if (!StandardInit (rangeMinP, rangeMaxP, rangeStepP)) return false;
      
        //----------------------------------------------------------------------------
        // Initialize structures for storing "hands" and "decks"
        ArrayResize (hand, popSize);
        for (int i = 0; i < popSize; i++) hand [i].Init (coords);
      
        ArrayResize (deck,     popSize * 2);
        ArrayResize (tempDeck, popSize * 2);
        for (int i = 0; i < popSize * 2; i++)
        {
          deck     [i].Init (coords);
          tempDeck [i].Init (coords);
        }
      
        // Initialize arrays for cutting points
        cutPoints = 3;  // three cutting points for a "three-card" crossover
        ArrayResize (tempCuts,  cutPoints);
        ArrayResize (finalCuts, cutPoints + 2);
      
        return true;
      }
      //——————————————————————————————————————————————————————————————————————————————
      

      Die Methode „Moving“ ist für den Prozess des „Verschiebens“ oder der Aktualisierung des Zustands der Population innerhalb des RFO-Algorithmus verantwortlich.

      Überprüfung des Status – Die Methode beginnt die Ausführung mit der Überprüfung der Bedingung, die bestimmt, ob die anfängliche Initialisierung „Austeilen“ der Spielkarten abgeschlossen ist. Wenn dies nicht der Fall ist (Revision == false), führt die Methode eine Initialisierung durch.

      Initialisierung der anfänglichen Verteilung – die Methode iteriert durch alle Elemente der Grundgesamtheit, für jedes Element der Grundgesamtheit (jede „Hand“) wird ein Satz von Spielkarten erstellt. Die innere Schleife durchläuft jede erforderliche Anzahl von Spielkarten und führt die folgenden Aktionen aus:

      • Ein Spielkartenrang wird zufällig aus dem Stapel ausgewählt.
      • Die Methode wird dann aufgerufen, um die Spielkarte auf der Grundlage des erzeugten Ranges zu verteilen.
      • Die resultierende Spielkarte wird mit Hilfe einer Funktion angepasst, die prüft, ob sie innerhalb eines bestimmten Bereichs liegt, und je nach den angegebenen Parametern die erforderlichen Änderungen vornimmt.
      • Schließlich wird der Wert der empfangenen Spielkarte auf das Array „a“ gesetzt.

      Aktualisieren des Status – nach Abschluss der Initialisierung wird „revision“ auf „true“ gesetzt, was bedeutet, dass die ursprüngliche Verteilung abgeschlossen ist und keine weitere Neuinitialisierung erforderlich ist.

      Aufruf der Methode Evolution () – wenn die erste Ausgabe bereits erfolgt ist, führt die Methode den evolutionären Prozess des Mischens und Verteilens der Spielkarten auf die Hände durch.

      //——————————————————————————————————————————————————————————————————————————————
      void C_AO_RFO::Moving ()
      {
        //----------------------------------------------------------------------------
        if (!revision)
        {
          // Initialize the initial "distribution"
          for (int i = 0; i < popSize; i++)
          {
            for (int c = 0; c < coords; c++)
            {
              hand [i].cardRanks [c] = u.RNDminusOne (deckSize);
              hand [i].card      [c] = DealCard (hand [i].cardRanks [c], c);
              hand [i].card      [c] = u.SeInDiSp (hand [i].card [c], rangeMin [c], rangeMax [c], rangeStep [c]);
      
              a [i].c [c] = hand [i].card [c];
            }
          }
      
          revision = true;
          return;
        }
      
        //----------------------------------------------------------------------------
        Evolution ();
      }
      //——————————————————————————————————————————————————————————————————————————————
      

      Die Revisionsmethode ist dafür verantwortlich, die beste „Kombination“ von „Händen“ zu finden, ihre Eignung zu bewerten und das Gesamtdeck zu aktualisieren.

      Suche nach der besten Kombination:

      • Die Methode beginnt mit der Initialisierung der Variablen bestHand, die den Index des besten Blatts aller Mitglieder der Population speichert.
      • Dann wird eine Schleife ausgeführt, die alle Elemente der Grundgesamtheit durchläuft (von 0 bis popSize). Innerhalb der Schleife vergleicht die Methode den Fitnesswert jeder Hand „a“ mit dem aktuell besten Wert von fB.
      • Wenn der Fitnesswert der aktuellen Hand größer als fB ist, wird eine Aktualisierung mit dem neuen besten Wert vorgenommen und der Index dieser „Hand“ wird der Variablen bestHand zugewiesen.

      Wenn die beste Hand gefunden wird, werden die Spielkarten in das cB-Array kopiert, wodurch der Zustand der besten Kombination (die beste globale Lösung) gespeichert werden kann. Die Methode aktualisiert dann die Fitnesswerte für jede Hand im Array „hand“ so, dass sie gleich den entsprechenden Werten im Array „a“ sind. Dies ist notwendig, um sicherzustellen, dass die Eignungsdaten für jede Hand auf dem neuesten Stand sind. Nach der Aktualisierung der Fitnesswerte werden die aktuellen Hände aus dem Array „hand“ dem allgemeinen Array „deck“ hinzugefügt, beginnend an der Position popSize (d.h. am Ende der Population, ihrer zweiten Hälfte).

      Schließlich sortiert die Methode das „Deck“-Array mit Hilfe eines separaten temporären tempDeck-Arrays, um das Deck nach Kombinationswert zu ordnen. So können wir die erhöhte Wahrscheinlichkeit der Auswahl wertvoller Spielkartenkombinationen bei der Auswahl mit ihrer nachfolgenden Kombination nutzen.

      //——————————————————————————————————————————————————————————————————————————————
      void C_AO_RFO::Revision ()
      {
        // Search for the best "combination"
        int bestHand = -1;
      
        for (int i = 0; i < popSize; i++)
        {
          if (a [i].f > fB)
          {
            fB = a [i].f;
            bestHand = i;
          }
        }
      
        if (bestHand != -1) ArrayCopy (cB, a [bestHand].c, 0, 0, WHOLE_ARRAY);
      
        //----------------------------------------------------------------------------
        // Update fitness values
        for (int i = 0; i < popSize; i++)
        {
          hand [i].f = a [i].f;
        }
      
        // Add current hands to the general deck
        for (int i = 0; i < popSize; i++)
        {
          deck [popSize + i] = hand [i];
        }
      
        // Sort the deck by combination value
        u.Sorting (deck, tempDeck, popSize * 2);
      }
      //——————————————————————————————————————————————————————————————————————————————
      

      Die Evolutionsmethode ist für die Hauptlogik des Algorithmus verantwortlich, bei der Spielkarten zwischen den „Händen“ der Spieler am Tisch ausgetauscht werden, „Bluffing“ stattfindet und die tatsächlichen Werte der Spielkarten aktualisiert werden.

      Die Methode beginnt mit einer Schleife, die alle Elemente der Grundgesamtheit durchläuft. Die folgenden Aktionen werden durchgeführt:

      Auswählen eines Gegners:

      • Um einen Gegner auszuwählen, wird eine Zufallszahl generiert, die dann quadriert wird, um die Auswahl zu erhöhen (die Wahrscheinlichkeit, einen Gegner auszuwählen, wird mit seiner Bewertung gekreuzt). Dadurch wird es wahrscheinlicher, dass Sie die beste Kombination von Händen wählen.
      • Die Zufallszahl wird mit der Funktion u.Scale skaliert, um den Index des Gegners zu erhalten.

      Das aktuelle Blatt (aus dem Array „deck“) wird in das Array „hand“ kopiert. Die Methode erzeugt zufällige „Schnittpunkte“ für das Spielkartenblatt. Diese Punkte bestimmen, welche Spielkarten zwischen den beiden Händen ausgetauscht werden. Die Schnittpunkte werden sortiert und mit Begrenzungen versehen; die erste Begrenzung wird auf „0“ und die letzte Begrenzung auf „coords – 1“ gesetzt. Die Methode wählt einen zufälligen Startpunkt, um mit dem Spielkartentausch zu beginnen, indem sie u.RNDbool () verwendet. Nachdem der Spielkartentausch abgeschlossen ist, wird eine Chance zum „Bluffen“ gegeben.

      Umrechnung von Rängen in reale Werte:
      • In der letzten Schleife werden die Spielkartenränge mit der Methode DealCard in ihre Werte umgerechnet und auf die Einhaltung der festgelegten Grenzen überprüft.
      • Danach werden die Werte im Array „a“, das die letzten Spielkarten enthält, aktualisiert.

      //——————————————————————————————————————————————————————————————————————————————
      void C_AO_RFO::Evolution ()
      {
        // For each position at the table
        for (int i = 0; i < popSize; i++)
        {
          // Select an opponent based on their rating (probability squared to enhance selection)
          double rnd = u.RNDprobab ();
          rnd *= rnd;
          int opponent = (int)u.Scale (rnd, 0.0, 1.0, 0, popSize - 1);
      
          // Copy the current hand
          hand [i] = deck [i];
      
          // Define cutting points for card exchange
          for (int j = 0; j < cutPoints; j++)
          {
            tempCuts [j] = u.RNDminusOne (coords);
          }
      
          // Sort cutting points and add borders
          ArraySort (tempCuts);
          ArrayCopy (finalCuts, tempCuts, 1, 0, WHOLE_ARRAY);
          finalCuts [0] = 0;
          finalCuts [cutPoints + 1] = coords - 1;
      
          // Random selection of a starting point for exchange
          int startPoint = u.RNDbool ();
      
          // Exchange cards between hands
          for (int j = startPoint; j < cutPoints + 2; j += 2)
          {
            if (j < cutPoints + 1)
            {
              for (int len = finalCuts [j]; len < finalCuts [j + 1]; len++) hand [i].cardRanks [len] = deck [opponent].cardRanks [len];
            }
          }
      
          // Possibility of "bluffing" (mutation)
          ShuffleRanks (hand [i].cardRanks);
      
          // Convert ranks to real values
          for (int c = 0; c < coords; c++)
          {
            hand [i].card [c] = DealCard (hand [i].cardRanks [c], c);
            hand [i].card [c] = u.SeInDiSp (hand [i].card [c], rangeMin [c], rangeMax [c], rangeStep [c]);
      
            a [i].c [c] = hand [i].card [c];
          }
        }
      }
      //——————————————————————————————————————————————————————————————————————————————
      

      Die DealCard-Methode ist ein Schlüsselelement des Royal-Flush-Optimierungsalgorithmus, der diskrete Sektoren des Suchraums in kontinuierliche Koordinatenwerte umwandelt. Die Methode benötigt zwei Parameter als Eingabe: „Rang“ – den Rang der Spielkarte und „Farbe“ – den Koordinatenindex (Farbe).

      Die Umwandlung besteht aus zwei Stufen. Zunächst wird die Größe eines Sektors (suitRange) berechnet, indem der gesamte Suchbereich durch die Anzahl der Sektoren geteilt wird. Dann wird ein bestimmter Wert innerhalb des ausgewählten Sektors erzeugt. Der Zufallsabstand u.RNDprobab() gewährleistet eine gleichmäßige Erkundung des Raums innerhalb jedes Sektors, und „rank“ definiert die Basisposition im Suchraum.

      Dieser Ansatz ermöglicht es, eine diskrete Darstellung von Lösungen durch Sektoren mit einem kontinuierlichen Suchraum zu kombinieren und so ein Gleichgewicht zwischen globaler und lokaler Suche herzustellen.

      //——————————————————————————————————————————————————————————————————————————————
      double C_AO_RFO::DealCard (int rank, int suit)
      {
        // Convert the map rank to a real value with a random offset within the sector
        double suitRange = (rangeMax [suit] - rangeMin [suit]) / deckSize;
        return rangeMin [suit] + (u.RNDprobab () + rank) * suitRange;
      }
      //——————————————————————————————————————————————————————————————————————————————
      

      Die Methode ShuffleRanks implementiert den Mutationsmechanismus im Royal-Flush-Optimierungsalgorithmus und fungiert als „Bluff“ bei der Arbeit mit Spielkartenrängen. Bei einem Array von Rängen als Referenz durchläuft die Methode jede Koordinate und ersetzt mit der Wahrscheinlichkeit dealerBluff den aktuellen Rang durch einen Zufallswert aus dem Bereich der gültigen Ränge im Deck. Dieser Prozess kann mit einer Situation beim Poker verglichen werden, wenn ein Spieler unerwartet die Spielkarte in seiner Hand ändert und damit ein Element der Unvorhersehbarkeit in das Spiel einbringt. Dieser Mutationsmechanismus soll dem Algorithmus helfen, nicht in lokalen Optima stecken zu bleiben und eine Vielfalt möglicher Lösungen während der Optimierung zu erhalten.

      //——————————————————————————————————————————————————————————————————————————————
      void C_AO_RFO::ShuffleRanks (int &ranks [])
      {
        // Rank shuffle (mutation)
        for (int i = 0; i < coords; i++)
        {
          if (u.RNDprobab () < dealerBluff) ranks [i] = (int)MathRand () % deckSize;
        }
      }
      //——————————————————————————————————————————————————————————————————————————————
      


      Testergebnisse

      RFO algorithm test results:

      RFO|Royal Flush Optimization|50.0|1000.0|0.03|
      =============================
      5 Hilly's; Func runs: 10000; result: 0.8336125672709654
      25 Hilly's; Func runs: 10000; result: 0.7374210861383783
      500 Hilly's; Func runs: 10000; result: 0.34629436610445113
      =============================
      5 Forest's; Func runs: 10000; result: 0.8942431024645086
      25 Forest's; Func runs: 10000; result: 0.7382367793268382
      500 Forest's; Func runs: 10000; result: 0.24097956383750824
      =============================
      5 Megacity's; Func runs: 10000; result: 0.6315384615384616
      25 Megacity's; Func runs: 10000; result: 0.5029230769230771
      500 Megacity's; Func runs: 10000; result: 0.16420769230769366
      =============================
      All score: 5.08946 (56.55%)

      Das Endergebnis von 56,55 % ist ein sehr respektables Ergebnis. In der Visualisierung zeigt der Algorithmus kein spezifisches Verhalten; es sieht aus wie ein chaotisches Wandern einzelner Punkte.

      Hilly

        RFO mit die Testfunktion Hilly

      Forest

      RFO mit die Testfunktion Forest

      Megacity

      RFO mit die Testfunktion Megacity

      Auf der Grundlage der Testergebnisse belegt der RFO-Optimierungsalgorithmus Platz 15 und reiht sich damit in die Liste der stärksten bekannten Algorithmen ein.

      # AO Beschreibung Hilly Hilly final Forest Forest final Megacity (discrete) Megacity final Final result % of MAX
      10 p (5 F) 50 p (25 F) 1000 p (500 F) 10 p (5 F) 50 p (25 F) 1000 p (500 F) 10 p (5 F) 50 p (25 F) 1000 p (500 F)
      1 ANS Suche über die gesamte Nachbarschaft 0.94948 0.84776 0.43857 2.23581 1.00000 0.92334 0.39988 2.32323 0.70923 0.63477 0.23091 1.57491 6.134 68.15
      2 CLA Code-Sperr-Algorithmus (joo) 0.95345 0.87107 0.37590 2.20042 0.98942 0.91709 0.31642 2.22294 0.79692 0.69385 0.19303 1.68380 6.107 67.86
      3 AMOm Optimierung der Tiermigration M 0,90358 0,84317 0,46284 2,20959 0,99001 0,92436 0,46598 2,38034 0,56769 0,59132 0,23773 1,39675 5.987 66,52
      4 (P+O)ES (P+O) Entwicklungsstrategien 0.92256 0.88101 0.40021 2.20379 0.97750 0.87490 0.31945 2.17185 0.67385 0.62985 0.18634 1.49003 5.866 65.17
      5 CTA Kometenschweif-Algorithmus (joo) 0.95346 0.86319 0.27770 2.09435 0.99794 0.85740 0.33949 2.19484 0.88769 0.56431 0.10512 1.55712 5.846 64.96
      6 TETA Zeit-Evolutions-Reise-Algorithmus (Joo) 0.91362 0.82349 0.31990 2.05701 0.97096 0.89532 0.29324 2.15952 0.73462 0.68569 0.16021 1.58052 5.797 64.41
      7 SDSm stochastische Diffusionssuche M 0.93066 0.85445 0.39476 2.17988 0.99983 0.89244 0.19619 2.08846 0.72333 0.61100 0.10670 1.44103 5.709 63.44
      8 AAm Algorithmus für das Bogenschießen M 0.91744 0.70876 0.42160 2.04780 0.92527 0.75802 0.35328 2.03657 0.67385 0.55200 0.23738 1.46323 5.548 61.64
      9 ESG Entwicklung sozialer Gruppen (joo) 0.99906 0.79654 0.35056 2.14616 1.00000 0.82863 0.13102 1.95965 0.82333 0.55300 0.04725 1.42358 5.529 61.44
      10 SIA Simuliertes isotropes Glühen (Joo) 0.95784 0.84264 0.41465 2.21513 0.98239 0.79586 0.20507 1.98332 0.68667 0.49300 0.09053 1.27020 5.469 60.76
      11 ACS künstliche, kooperative Suche 0.75547 0.74744 0.30407 1.80698 1.00000 0.88861 0.22413 2.11274 0.69077 0.48185 0.13322 1.30583 5.226 58.06
      12 DA dialektischer Algorithmus 0.86183 0.70033 0.33724 1.89940 0.98163 0.72772 0.28718 1.99653 0.70308 0.45292 0.16367 1.31967 5.216 57.95
      13 BHAm Algorithmus für schwarze Löcher M 0.75236 0.76675 0.34583 1.86493 0.93593 0.80152 0.27177 2.00923 0.65077 0.51646 0.15472 1.32195 5.196 57.73
      14 ASO Anarchische Gesellschaftsoptimierung 0,84872 0,74646 0,31465 1,90983 0,96148 0,79150 0,23803 1,99101 0,57077 0,54062 0,16614 1,27752 5.178 57,54
      15 RFO Optimierung des Royal Flush (joo) 0.83361 0.73742 0.34629 1.91733 0.89424 0.73824 0.24098 1.87346 0.63154 0.50292 0.16421 1.29867 5.089 56.55
      16 AOSm Suche nach atomaren Orbitalen M 0.80232 0.70449 0.31021 1.81702 0.85660 0.69451 0.21996 1.77107 0.74615 0.52862 0.14358 1.41835 5.006 55.63
      17 TSEA Schildkrötenpanzer-Evolutionsalgorithmus (joo) 0.96798 0.64480 0.29672 1.90949 0.99449 0.61981 0.22708 1.84139 0.69077 0.42646 0.13598 1.25322 5.004 55.60
      18 DE differentielle Evolution 0.95044 0.61674 0.30308 1.87026 0.95317 0.78896 0.16652 1.90865 0.78667 0.36033 0.02953 1.17653 4.955 55.06
      19 CRO Optimierung chemischer Reaktionen 0.94629 0.66112 0.29853 1.90593 0.87906 0.58422 0.21146 1.67473 0.75846 0.42646 0.12686 1.31178 4.892 54.36
      20 BSA Vogelschwarm-Algorithmus 0.89306 0.64900 0.26250 1.80455 0.92420 0.71121 0.24939 1.88479 0.69385 0.32615 0.10012 1.12012 4.809 53.44
      21 HS Harmoniesuche 0.86509 0.68782 0.32527 1.87818 0.99999 0.68002 0.09590 1.77592 0.62000 0.42267 0.05458 1.09725 4.751 52.79
      22 SSG Setzen, Säen und Wachsen 0.77839 0.64925 0.39543 1.82308 0.85973 0.62467 0.17429 1.65869 0.64667 0.44133 0.10598 1.19398 4.676 51.95
      23 BCOm Optimierung mit der bakteriellen Chemotaxis M 0.75953 0.62268 0.31483 1.69704 0.89378 0.61339 0.22542 1.73259 0.65385 0.42092 0.14435 1.21912 4.649 51.65
      24 ABO Optimierung des afrikanischen Büffels 0.83337 0.62247 0.29964 1.75548 0.92170 0.58618 0.19723 1.70511 0.61000 0.43154 0.13225 1.17378 4.634 51.49
      25 (PO)ES (PO) Entwicklungsstrategien 0.79025 0.62647 0.42935 1.84606 0.87616 0.60943 0.19591 1.68151 0.59000 0.37933 0.11322 1.08255 4.610 51.22
      26 TSm Tabu-Suche M 0.87795 0.61431 0.29104 1.78330 0.92885 0.51844 0.19054 1.63783 0.61077 0.38215 0.12157 1.11449 4.536 50.40
      27 BSO Brainstorming-Optimierung 0.93736 0.57616 0.29688 1.81041 0.93131 0.55866 0.23537 1.72534 0.55231 0.29077 0.11914 0.96222 4.498 49.98
      28 WOAm Wal-Optimierungsalgorithmus M 0.84521 0.56298 0.26263 1.67081 0.93100 0.52278 0.16365 1.61743 0.66308 0.41138 0.11357 1.18803 4.476 49.74
      29 AEFA Algorithmus für künstliche elektrische Felder 0.87700 0.61753 0.25235 1.74688 0.92729 0.72698 0.18064 1.83490 0.66615 0.11631 0.09508 0.87754 4.459 49.55
      30 AEO Algorithmus zur Optimierung auf der Grundlage künstlicher Ökosysteme 0.91380 0.46713 0.26470 1.64563 0.90223 0.43705 0.21400 1.55327 0.66154 0.30800 0.28563 1.25517 4.454 49.49
      31 ACOm Ameisen-Kolonie-Optimierung M 0.88190 0.66127 0.30377 1.84693 0.85873 0.58680 0.15051 1.59604 0.59667 0.37333 0.02472 0.99472 4.438 49.31
      32 BFO-GA Optimierung der bakteriellen Futtersuche — ga 0.89150 0.55111 0.31529 1.75790 0.96982 0.39612 0.06305 1.42899 0.72667 0.27500 0.03525 1.03692 4.224 46.93
      33 SOA einfacher Optimierungsalgorithmus 0.91520 0.46976 0.27089 1.65585 0.89675 0.37401 0.16984 1.44060 0.69538 0.28031 0.10852 1.08422 4.181 46.45
      34 ABHA Algorithmus für künstliche Bienenstöcke 0.84131 0.54227 0.26304 1.64663 0.87858 0.47779 0.17181 1.52818 0.50923 0.33877 0.10397 0.95197 4.127 45.85
      35 ACMO Optimierung atmosphärischer Wolkenmodelle 0.90321 0.48546 0.30403 1.69270 0.80268 0.37857 0.19178 1.37303 0.62308 0.24400 0.10795 0.97503 4.041 44.90
      36 ADAMm adaptive Momentabschätzung M 0.88635 0.44766 0.26613 1.60014 0.84497 0.38493 0.16889 1.39880 0.66154 0.27046 0.10594 1.03794 4.037 44.85
      37 ATAm Algorithmus für künstliche Stämme M 0.71771 0.55304 0.25235 1.52310 0.82491 0.55904 0.20473 1.58867 0.44000 0.18615 0.09411 0.72026 3.832 42.58
      38 ASHA Algorithmus für künstliches Duschen 0.89686 0.40433 0.25617 1.55737 0.80360 0.35526 0.19160 1.35046 0.47692 0.18123 0.09774 0.75589 3.664 40.71
      39 ASBO Optimierung des adaptiven Sozialverhaltens 0.76331 0.49253 0.32619 1.58202 0.79546 0.40035 0.26097 1.45677 0.26462 0.17169 0.18200 0.61831 3.657 40.63
      40 MEC Evolutionäre Berechnung des Geistes 0.69533 0.53376 0.32661 1.55569 0.72464 0.33036 0.07198 1.12698 0.52500 0.22000 0.04198 0.78698 3.470 38.55
      41 IWO Optimierung mit invasiven Unkräutern 0.72679 0.52256 0.33123 1.58058 0.70756 0.33955 0.07484 1.12196 0.42333 0.23067 0.04617 0.70017 3.403 37.81
      42 Micro-AIS Künstliches Mikro-Immunsystem 0.79547 0.51922 0.30861 1.62330 0.72956 0.36879 0.09398 1.19233 0.37667 0.15867 0.02802 0.56335 3.379 37.54
      43 COAm Kuckuck-Optimierungsalgorithmus M 0.75820 0.48652 0.31369 1.55841 0.74054 0.28051 0.05599 1.07704 0.50500 0.17467 0.03380 0.71347 3.349 37.21
      44 SDOm Optimierung der Spiraldynamik M 0.74601 0.44623 0.29687 1.48912 0.70204 0.34678 0.10944 1.15826 0.42833 0.16767 0.03663 0.63263 3.280 36.44
      45 NMm Nelder-Mead-Verfahren M 0.73807 0.50598 0.31342 1.55747 0.63674 0.28302 0.08221 1.00197 0.44667 0.18667 0.04028 0.67362 3.233 35.92
      RW Random Walk 0.48754 0.32159 0.25781 1.06694 0.37554 0.21944 0.15877 0.75375 0.27969 0.14917 0.09847 0.52734 2.348 26.09


      Zusammenfassung

      Bei der Erforschung und Entwicklung neuer Optimierungsmethoden stehen wir oft vor der Notwendigkeit, ein Gleichgewicht zwischen Effizienz und Implementierungskomplexität zu finden. Die Arbeit am Algorithmus der Royal Flush Optimierung (RFO) hat interessante Ergebnisse erbracht, die Fragen über die Natur der Optimierung und Möglichkeiten zu ihrer Verbesserung aufwerfen.

      Wenn wir die Leistung des Algorithmus beobachten, wenn er 57 % seines theoretischen Maximums erreicht, sehen wir ein interessantes Phänomen: Manchmal kann Vereinfachung wertvoller sein als Komplikation. RFO zeigt, dass der Verzicht auf eine komplexe binäre Kodierung zugunsten eines einfacheren sektorbasierten Ansatzes zu einer erheblichen Beschleunigung der Algorithmusleistung führen kann, während gleichzeitig eine ausreichend hohe Qualität der Lösungen beibehalten wird. Dies erinnert an die Situation beim Pokern, wo manchmal eine einfachere, aber schnellere Strategie effizienter sein kann als eine komplexere, die langwierige Berechnungen erfordert.

      Wenn man über den Platz von RFO in der Familie der Optimierungsalgorithmen nachdenkt, kann man eine Analogie mit der Evolution von Fahrzeugen ziehen. So wie es einen Bedarf an spritsparenden Stadtautos neben leistungsstarken Sportwagen gibt, so gibt es auch in der Welt der Optimierungsalgorithmen Raum für Methoden, die sich auf unterschiedliche Prioritäten konzentrieren. RFO kann als eine „kostengünstige“ Variante des genetischen Algorithmus betrachtet werden, die einen vernünftigen Kompromiss zwischen Leistung und Ressourceneffizienz bietet.

      Abschließend ist festzustellen, dass die Entwicklung der RFO interessante Perspektiven für die weitere Forschung eröffnet. Dies könnte nur der erste Schritt zur Entwicklung einer ganzen Familie von Algorithmen sein, die auf einem sektorbasierten Optimierungsansatz basieren. Die Einfachheit und Eleganz der Methode, kombiniert mit ihrer praktischen Wirksamkeit, kann als Inspirationsquelle für die Entwicklung neuer Algorithmen dienen, die ein Gleichgewicht zwischen Leistung und Recheneffizienz herstellen.

      Es ist erwähnenswert, dass die Aufteilung in Sektoren virtuell erfolgt, ohne dass Speicher in Form von Arrays zugewiesen wird. Dieser RFO-Rahmen ist ein hervorragender Ausgangspunkt für die weitere Entwicklung verbesserter Versionen des Pokeralgorithmus.

      Tabelle

      Abbildung 2. Farbabstufung der Algorithmen nach den entsprechenden Tests

      Histogramm

      Abbildung 3. Histogramm der Algorithmus-Testergebnisse (Skala von 0 bis 100, je höher, desto besser, wobei 100 das maximal mögliche theoretische Ergebnis ist; im Archiv befindet sich ein Skript zur Berechnung der Bewertungstabelle)


      RFO Pro und Kontra:

      Vorteile:

      1. Es gibt nur wenige externe Parameter, nur zwei, wenn man die Populationsgröße nicht mitzählt.
      2. Einfache Umsetzung.
      3. Schnell.
      4. Ausgewogene, gute Leistungen bei Aufgaben verschiedener Dimensionen.

      Nachteile:

      1. Durchschnittliche Konvergenzgenauigkeit.

      Der Artikel wird von einem Archiv mit den aktuellen Versionen der Codes der Algorithmen begleitet. Der Autor des Artikels übernimmt keine Verantwortung für die absolute Richtigkeit der Beschreibung der kanonischen Algorithmen. An vielen von ihnen wurden Änderungen vorgenommen, um die Suchmöglichkeiten zu verbessern. Die in den Artikeln dargelegten Schlussfolgerungen und Urteile beruhen auf den Ergebnissen der Versuche.

      Programme, die im diesem Artikel verwendet werden

      # Name Typ Beschreibung
      1 #C_AO.mqh
      Include
      Übergeordnete Klasse von Populationsoptimierungsalgorithmen
      2 #C_AO_enum.mqh
      Include
      Enumeration der Algorithmen zur Populationsoptimierung
      3 TestFunctions.mqh
      Include
      Bibliothek mit Testfunktionen
      4
      TestStandFunctions.mqh
      Include
      Bibliothek mit Funktionen für den Prüfstand
      5
      Utilities.mqh
      Include
      Bibliothek mit Hilfsfunktionen
      6
      CalculationTestResults.mqh
      Include
      Skript zur Berechnung der Ergebnisse in der Vergleichstabelle
      7
      Testing AOs.mq5
      Skript Der einheitliche Prüfstand für alle Algorithmen zur Populationsoptimierung
      8
      Simple use of population optimization algorithms.mq5
      Skript
      Ein einfaches Beispiel für die Verwendung von Algorithmen zur Populationsoptimierung ohne Visualisierung
      9
      Test_AO_RFO.mq5
      Skript RFO-Teststand

      Übersetzt aus dem Russischen von MetaQuotes Ltd.
      Originalartikel: https://www.mql5.com/ru/articles/17063

      Beigefügte Dateien |
      RFO.zip (161.58 KB)
      Letzte Kommentare | Zur Diskussion im Händlerforum (2)
      Alex-4
      Alex-4 | 12 Feb. 2025 in 03:57

      Und wie genau (in Bezug auf die Ideen)
      können wir die Berechnungen
      in EA speziell für BP anpassen? Ich habe mir
      einen kurzen Blick geworfen und auf der mql-Website habe ich keine Besonderheiten gefunden.
      Andrey Dik
      Andrey Dik | 12 Feb. 2025 in 05:08
      blef #:

      Und wie genau (in Bezug auf die Ideen)
      Sie die Berechnungen anpassen können
      in EA speziell für BP anpassen? Angeschaut
      und auf der mql-Website konnte ich keine spezifischen Angaben finden.

      In allen Fällen, in denen es darum geht, die beste Lösung unter vielen möglichen zu finden. Zum Beispiel, Berater mit Selbst-Optimierung.

      Marktsimulation (Teil 05): Erstellen der Klasse C_Orders (II) Marktsimulation (Teil 05): Erstellen der Klasse C_Orders (II)
      In diesem Artikel erkläre ich, wie Chart Trade zusammen mit dem Expert Advisor eine Anfrage zur Schließung aller offenen Positionen des Nutzers bearbeitet. Das mag einfach klingen, aber es gibt einige Komplikationen, mit denen Sie umgehen müssen.
      Neuronale Netze im Handel: Hierarchical Dual-Tower Transforme (letzter Teil) Neuronale Netze im Handel: Hierarchical Dual-Tower Transforme (letzter Teil)
      Wir setzen die Entwicklung des Modells von „Hidformer Hierarchical Dual-Tower Transformer“ fort, das für die Analyse und Vorhersage komplexer multivariater Zeitreihen entwickelt wurde. In diesem Artikel werden wir die Arbeit, die wir zuvor begonnen haben, zu einem logischen Abschluss bringen - wir werden das Modell an realen historischen Daten testen.
      Biologisches Neuron zur Vorhersage von Finanzzeitreihen Biologisches Neuron zur Vorhersage von Finanzzeitreihen
      Wir werden ein biologisch korrektes System von Neuronen für die Vorhersage von Zeitreihen aufbauen. Die Einführung einer plasmaähnlichen Umgebung in die Architektur des neuronalen Netzes schafft eine Art „kollektive Intelligenz“, bei der jedes Neuron den Betrieb des Systems nicht nur durch direkte Verbindungen, sondern auch durch weitreichende elektromagnetische Wechselwirkungen beeinflusst. Mal sehen, wie sich das neuronale Gehirnmodellierungssystem auf dem Markt schlagen wird.
      Von der Grundstufe bis zur Mittelstufe: Struct (I) Von der Grundstufe bis zur Mittelstufe: Struct (I)
      Heute werden wir damit beginnen, Strukturen auf eine einfachere, praktischere und bequemere Weise zu studieren. Strukturen gehören zu den Grundlagen der Programmierung, ob sie nun strukturiert sind oder nicht. Ich weiß, dass viele Menschen bei Strukturen nur an Datensammlungen denken, aber ich versichere Ihnen, dass sie viel mehr sind als nur Strukturen. Und hier werden wir beginnen, dieses neue Universum auf die didaktischste Weise zu erkunden.