English
preview
Implementierung des Truncated-Newton-Conjugate-Gradient-Algorithmus in MQL5

Implementierung des Truncated-Newton-Conjugate-Gradient-Algorithmus in MQL5

MetaTrader 5Tester |
27 0
Francis Dube
Francis Dube

Einführung

Bei der Minimierung reellwertiger Funktionen greifen MQL5-Entwickler häufig auf die von der ALGLIB-Bibliothek bereitgestellten Hilfsfunktionen zurück. Das ALGLIB-Modul minLBFGS bietet eine robuste Implementierung der LBFGS-Methode (Limited-memory Broyden-Fletcher-Goldfarb-Shanno). Leider fehlt die beschränkte Version dieser Methode in der MQL5-Portierung von ALGLIB. Diese Lücke zwingt die Nutzer oft dazu, auf andere nichtlineare Optimierer innerhalb der Bibliothek zurückzugreifen, was für einfache Aufgaben mit Box-Beschränkungen mitunter überdimensioniert sein kann.  Wir benötigen einen praktischen Solver für Probleme mit Box-Beschränkungen, der sich ebenso einfach wie ALGLIBs minLBFGS in Projekte integrieren lässt und sowohl analytische Gradienten als auch stabile numerische Approximationen nahe den Grenzen unterstützt sowie Box-Beschränkungen berücksichtigt.

In diesem Artikel stellen wir eine Implementierung des Truncated-Newton-Conjugate-Gradient-Algorithmus (TNC) vor. Neben der Erläuterung der zugrunde liegenden Theorie werden wir die Fähigkeit des Verfahrens testen, das globale Minimum der Rosenbrock-Funktion zu finden, und seine praktische Anwendung demonstrieren, indem wir TNC zum Trainieren einer logistischen Regression als Alternative zu LBFGS einsetzen.



Was ist TNC und worin unterscheidet es sich von LBFGS?

Die Truncated-Newton-Konjugierte-Gradienten-Methode (TNC) ist ein Optimierungsalgorithmus zweiter Ordnung, der das Verhalten der Newton-Methode approximiert. Im Zusammenhang mit der Optimierung ist die Newton-Methode ein Verfahren, mit dem das Minimum einer Funktion ermittelt wird. Das allgemeine Verfahren besteht aus einer Anfangsschätzung, gefolgt von der Berechnung des Gradienten und der Hessematrix der Funktion, bevor die Variablen bzw. die Lösungsschätzung aktualisiert werden. Dieser Vorgang wird so lange wiederholt, bis Konvergenz erreicht ist.

Der Gradient einer Funktion (die erste Ableitung) gibt Auskunft über die Steigung und zeigt die Richtung des steilsten Anstiegs an. Die Hessematrix (die zweite Ableitung in Matrixform) liefert Informationen über die Form, insbesondere über die Krümmung der Funktion an einem bestimmten Punkt. Wenn die Hessematrix in allen Richtungen eine positive Krümmung aufweist, deutet dies auf ein lokales Minimum (einen Tiefpunkt) hin. Ist die Krümmung in allen Richtungen negativ, handelt es sich bei dem Punkt um ein lokales Maximum (einen Scheitelpunkt). Wenn die Hessematrix Bewegungen erst in die eine und dann in die andere Richtung anzeigt, deutet dies auf einen Sattelpunkt hin.

Gradienten- und Hessematrix-Optimierung 

Bei der reinen Newton-Methode muss der Optimierer eine Hessematrix berechnen, die die Ableitungen zweiter Ordnung enthält, welche die Krümmung der Funktion beschreiben. Bei Problemen mit einer großen Anzahl von Variablen sind die Berechnung und Speicherung dieser Matrix rechenintensiv. TNC löst dieses Problem, indem es den Newton-Prozess verkürzt. Anstatt die Newton-Gleichung exakt zu lösen, wird eine innere Schleife des konjugierten Gradientenalgorithmus (CG-Algorithmus) verwendet, um eine ungefähre Suchrichtung zu ermitteln. Diese innere Schleife erfordert lediglich Hessematrix-Vektor-Produkte, die mithilfe von Finite-Differenzen des Gradienten effizient geschätzt werden können. Dadurch kann der Algorithmus Krümmungsinformationen erfassen, ohne jemals explizit eine umfangreiche Hessematrix zu bilden.

TNC-Optimierung



LBFGS ist ebenfalls eine Quasi-Newton-Methode, basiert jedoch auf einem grundlegend anderen Ansatz. LBFGS speichert eine fortlaufende Historie der Gradienten und Positionen aus den letzten Iterationen, um eine implizite Approximation der inversen Hessematrix zu erstellen. Da LBFGS Informationen aus vorherigen Schritten wiederverwendet, ist es oft extrem schnell und pro Iteration rechnerisch kostengünstig, was es zum Industriestandard für glatte, numerisch gutmütige Funktionen im maschinellen Lernen macht.

LBFGS-Optimierung



Die TNC-API im Überblick

Der Code, der den TNC-Optimierer implementiert, kann durch Einbinden der Header-Datei tnc.mqh in jedes beliebige MQL5-Programm integriert werden. Die Anwendung der TNC-API erfolgt in vier Schritten:

  • Zunächst definieren wir die Zielfunktion, ihren Gradienten bzw. ihre Jacobi-Matrix und etwaige Box-Beschränkungen. 
  • Zweitens konfigurieren wir den Optimierer selbst.
  • Im dritten Schritt führen wir den Optimierer aus. 
  • Und der letzte Schritt ist das Abrufen der Ergebnisse. 

Die API-Schnittstelle besteht aus zwei Klassen. Die Klasse CFunctor übernimmt alle Aspekte der Zielfunktion, und CTruncNewtonCG steht für den TNC-Optimierer.

Die Spezifikation der Zielfunktion erfolgt durch die Definition einer Klasse, die von der Basisklasse CFunctor abgeleitet ist. Die Zielfunktion soll durch Überschreiben der Methode orig_fun von CFunctor definiert werden. Wenn Gradient bzw. Jacobi-Matrix bekannt sind, können sie durch Überschreiben von grad_fun explizit bereitgestellt werden.

class CScalarFunc:public CFunctor
  {
   public:
                     CScalarFunc(void)
     {
     }
                    ~CScalarFunc(void)
     {
     }
   virtual double    orig_fun(vector& x)
     {
      return np::rosen(x);
     }
   virtual vector    grad_fun(vector& x)
     {
      return np::rosen_gradient(x);
     }
  };

Die von CFunctor abgeleitete Instanz stellt dann die Schnittstelle bereit, über die Benutzer verschiedene Aspekte der Zielfunktion und ihrer Ableitung konfigurieren können. Dazu gehört die Festlegung von Box-Beschränkungen, die über die Methode setBounds erfolgt. Diese erwartet eine Matrix, in der jede Zeile eine Dimension darstellt und die Spalten in dieser Reihenfolge die Unter- und Obergrenzen angeben. Wenn die Methode grad_fun überschrieben wurde, sollte sie durch Aufruf von setGradOption mit der Option GRAD_POINT_CALLABLE aktiviert werden. Andernfalls wird der Gradient mithilfe der Finite-Differenzen-Methode berechnet. Die Optionen der Enumeration ENUM_DIFF_POINTS ermöglichen es dem Benutzer, die Methode der finiten Differenzierung auszuwählen, die angewendet wird, wenn die Gradientenfunktion entweder nicht festgelegt oder nicht aktiviert ist. Weitere Einzelheiten zu diesen Optionen finden Sie in den folgenden Abschnitten dieses Artikels. 

CScalarFunc sf;
matrix bd = {{-2, 2}, {-1, 3}};
sf.setBounds(bd);
sf.setGradOption(GRAD_POINT_CALLABLE);
if(!sf.initialize(init_params))
   return;

Die Einrichtung der Zielfunktion wird durch den Aufruf der Methode initialize abgeschlossen, die einen Vektor mit dem anfänglichen Lösungsvorschlag entgegennimmt und zur Laufzeit den Wert true zurückgeben sollte. Wenn die Methode false zurückgibt, sollte der Optimierungsprozess nicht fortgesetzt werden. Das deutet darauf hin, dass etwas nicht richtig eingerichtet wurde. 

tnc::CTruncNewtonCG tnc_minim;
tnc_minim.SetLoglevel(tnc::TNC_MSG_ALL);
tnc_minim.SetMaxCGit(in_maxCGit);
tnc_minim.SetMaxFunCalls(in_maxnfeval);
tnc_minim.SetEta(in_eta);
tnc_minim.SetStepMax(in_stepmx);
tnc_minim.SetAccuracy(in_accuracy);
tnc_minim.SetFmin(in_fmin);
tnc_minim.SetFtol(in_ftol);
tnc_minim.SetXtol(in_xtol);
tnc_minim.SetPGtol(in_pgtol);
tnc_minim.SetRescaleFactor(in_rescale);

Anschließend kann der TNC-Optimierer konfiguriert werden. Hier können Anwender verschiedene Parameter des TNC-Minimierers einstellen. Im Folgenden werden die Methoden zur Feinabstimmung der Parameter des TNC-Optimierers zusammengefasst.

  • Die Methode SetLoglevel akzeptiert eine ENUM_TNC_MESSAGE-Enumeration zur Steuerung der Terminalausgabe, die von einer einzigen Zeile pro Iteration über detaillierte Skalierungsinformationen bis hin zum endgültigen Beendigungsgrund reicht.
  • Die Methode SetMaxCGit legt die Obergrenze für die Anzahl der internen Iterationen des konjugierten Gradientenverfahrens fest, um sicherzustellen, dass die innere Schleife effizient bleibt. 
  • Die Methode SetMaxFunCalls legt eine feste Obergrenze für die Gesamtzahl der Auswertungen der Zielfunktion fest.
  • Die Methoden SetFtol und SetXtol legen die Konvergenzschwellenwerte für relative Änderungen des Funktionswerts bzw. des Positionsvektors fest. Der Parameter ftol (Funktionstoleranz) überwacht die relative Änderung des Zielwerts zwischen aufeinanderfolgenden Iterationen. Wenn die Abnahme des Funktionswerts so gering wird, dass sie unter diesen Schwellenwert fällt, geht der Optimierer davon aus, dass er ein Plateau erreicht hat, auf dem weitere Anstrengungen nur noch zu vernachlässigbaren Verbesserungen führen würden. Wird dieser Wert zu hoch gewählt, kommt es zu einer vorzeitigen Konvergenz, bei der der Algorithmus stoppt, bevor er den tatsächlichen Tiefpunkt erreicht, während eine zu niedrige Einstellung zu unnötigen Berechnungen führen kann, die lediglich dem numerischen Rauschen entgegenwirken. Der Parameter xtol (Schritttoleranz) konzentriert sich eher auf die Bewegung im Suchraum als auf den Funktionswert. Er misst den euklidischen Abstand zwischen der aktuellen und der vorherigen Position; wenn der Optimierer nur mikroskopisch kleine Anpassungen an den Variablen vornimmt, löst er einen Abbruch aus. Dies ist besonders nützlich in flachen Bereichen einer Funktion, in denen sich die Höhe kaum ändert, der Algorithmus aber andernfalls möglicherweise viele Iterationen lang ziellos umherirren würde, ohne die Lösung tatsächlich zu verbessern.
  • Die Methode SetPGtol legt den Schwellenwert für die Norm des projizierten Gradienten fest. Der Algorithmus wird beendet, wenn der Gradient unter diesen Wert fällt. Die pgtol (Toleranz des projizierten Gradienten) ist wohl die mathematisch strengste Abbruchbedingung. Er misst die Steilheit des lokalen Gefälles unter Berücksichtigung von Nebenbedingungen; ein Gradient von Null weist auf einen stationären Punkt hin, was die theoretische Voraussetzung für ein lokales Minimum ist. Ein sehr kleiner pgtol-Wert zwingt den Algorithmus dazu, den exakten Tiefpunkt eines Tals zu ermitteln, während ein größerer Wert es dem Solver ermöglicht, den Prozess zu beenden, sobald er sich nahe genug am optimalen Bereich befindet.
  • Die Methode SetAccuracy legt die Maschinenpräzision oder den geschätzten Funktionsfehler fest, um die Gradientenschritte zu steuern.
  • Die SetEta-Methode akzeptiert für die Liniensuche einen Wert zwischen 0 und 1, wodurch ein Ausgleich zwischen der Abnahme der Funktion und der Schritt-Effizienz hergestellt wird.

Der Minimierer wird durch Aufruf der Methode Minimize von CTruncNewtonCG ausgeführt.

int return_code = tnc_minim.Minimize(sf);

Diese benötigt als einziges Argument eine Instanz eines von CFunctor abgeleiteten Objekts. Die Ergebnisse des Optimierungsprozesses können dann über die folgenden CTruncNewtonCG-Methoden abgerufen werden:

  • Die Methode Solution gibt die Variablen des globalen Minimums zurück.
  • Die Methode ObjectiveResult gibt den Wert der Zielfunktion am globalen Minimum zurück.
  • Die Methode ObjectiveGradient gibt den Gradienten am globalen Minimum zurück.
  • NumFeval gibt einen ganzzahligen Wert zurück, der angibt, wie oft die Zielfunktion ausgewertet wurde.
  • Die Methode NumIters gibt einen ganzzahligen Wert zurück, der die Anzahl der Iterationen des Optimierungsprozesses angibt.

Im nächsten Abschnitt stellen wir unsere Implementierung auf die Probe.



Test zur Optimierung unter Nebenbedingungen

In der Standardliteratur zur Optimierung gilt die Rosenbrock-Funktion als gängiger Maßstab zur Bewertung der Leistungsfähigkeit von Optimierungsalgorithmen. Sie gilt als anspruchsvolles Minimierungsproblem, da es ein flaches, enges Tal aufweist, in dem sich das globale Minimum befindet, was es vielen Algorithmen erschweren kann, effizient zu konvergieren. Funktionen mit ähnlichen Geometrien sind in Bereichen wie der Wirtschaftswissenschaft und dem maschinellen Lernen weitverbreitet.

Wir können diese Funktion im MetaTrader 5-Strategietester mithilfe der Funktion zur Auswertung mathematischer Funktionen visualisieren. Der Expert Advisor RosenBrock.mq5 setzt die Logik für diese Auswertung um.

//+------------------------------------------------------------------+
//|                                                   RosenBrock.mq5 |
//|                                  Copyright 2025, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
//--- input parameters
input double   x=-1.2;// start=-2, step=0.01, stop=2
input double   y=1.0;// start=-1, step=0.01, stop=3
//+------------------------------------------------------------------+
//| Tester function                                                  |
//+------------------------------------------------------------------+
double OnTester()
  {
//---
   double ret= pow(1.-x,2.0)+100.*pow(y-pow(x,2.),2.0);
//---
   return(ret);
  }
//+------------------------------------------------------------------+

Beim Ausführen des EAs im Tester zeigt sich, dass das globale Minimum bei [1,1] liegt.

Ergebnisse der Funktionsauswertung

Eine dreidimensionale Visualisierung verdeutlicht das charakteristische enge Tal, in dem sich das Minimum verbirgt – eine Eigenschaft, die häufig dazu führt, dass Optimierer oszillieren oder ins Stocken geraten.

Rosenbrock-Funktion in 3D

Um zu sehen, wie nah der TNC-Minimierer an das tatsächliche globale Minimum herankommt, verwenden wir das Skript TestTNC.mq5. 

//+------------------------------------------------------------------+
//|                                                      TestTNC.mq5 |
//|                                  Copyright 2025, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property script_show_inputs
#include<tnc/tnc.mqh>
#include<np.mqh>
//---
input double in_eta = -1.0;
input double in_stepmx = 0.0;
input double in_accuracy = 0.0;
input double in_fmin = 0.0;
input double in_ftol = -1.0;
input double in_xtol = -1.0;
input double in_pgtol = -1.0;
input double in_rescale = -1.0;
input int in_maxCGit = -1;
input int in_maxnfeval = 100;
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   vector init_params = {-1.2, 1.0};//vector::Zeros(2);
   CScalarFunc sf;
   matrix bd = {{-2, 2}, {-1, 3}};
   sf.setBounds(bd);
   sf.setGradOption(GRAD_POINT_CALLABLE);
   if(!sf.initialize(init_params))
      return;
   tnc::CTruncNewtonCG tnc_minim;
   tnc_minim.SetLoglevel(tnc::TNC_MSG_ALL);
   tnc_minim.SetMaxCGit(in_maxCGit);
   tnc_minim.SetMaxFunCalls(in_maxnfeval);
   tnc_minim.SetEta(in_eta);
   tnc_minim.SetStepMax(in_stepmx);
   tnc_minim.SetAccuracy(in_accuracy);
   tnc_minim.SetFmin(in_fmin);
   tnc_minim.SetFtol(in_ftol);
   tnc_minim.SetXtol(in_xtol);
   tnc_minim.SetPGtol(in_pgtol);
   tnc_minim.SetRescaleFactor(in_rescale);
   int return_code = tnc_minim.Minimize(sf);
   Print(" Optimization return code ", tnc::tnc_rc_string[return_code]);
   Print(" optimization solution ", tnc_minim.Solution());
  }
//+------------------------------------------------------------------+
class CScalarFunc:public CFunctor
  {
public:
                     CScalarFunc(void)
     {
     }
                    ~CScalarFunc(void)
     {
     }
   virtual double    orig_fun(vector& x)
     {
      return np::rosen(x);
     }
   virtual vector    grad_fun(vector& x)
     {
      return np::rosen_gradient(x);
     }
  };
//+------------------------------------------------------------------+

Beim Ausführen des Skripts erhalten wir die folgende Ausgabe.

QO      0       19:06:52.209    TestTNC (GBPUSD,D1)       NIT   NF   F                       GTG
GJ      0       19:06:52.209    TestTNC (GBPUSD,D1)         0    1  2.420000000000000E+01   5.42273600E+04
MQ      0       19:06:52.209    TestTNC (GBPUSD,D1)     tnc: fscale = 0.00107357
DE      0       19:06:52.209    TestTNC (GBPUSD,D1)         1    3  4.567781791360708E+00   9.57591179E+02
RH      0       19:06:52.209    TestTNC (GBPUSD,D1)         2    5  4.127793942693432E+00   3.78238779E+00
LO      0       19:06:52.209    TestTNC (GBPUSD,D1)     tnc: fscale = 0.128546
GK      0       19:06:52.209    TestTNC (GBPUSD,D1)         3    7  4.116988513105550E+00   1.81606916E+01
MO      0       19:06:52.209    TestTNC (GBPUSD,D1)         4   16  3.317053801700717E+00   2.94434047E+02
MS      0       19:06:52.209    TestTNC (GBPUSD,D1)         5   20  3.172067249923803E+00   5.34629683E+02
HD      0       19:06:52.209    TestTNC (GBPUSD,D1)         6   26  1.770939721007689E+00   6.64565256E+01
LI      0       19:06:52.209    TestTNC (GBPUSD,D1)         7   28  1.651272391609239E+00   4.99232721E+00
GR      0       19:06:52.210    TestTNC (GBPUSD,D1)         8   38  1.269069338899967E+00   4.60590202E+01
JF      0       19:06:52.210    TestTNC (GBPUSD,D1)         9   42  1.110240253054296E+00   7.40799184E+01
MJ      0       19:06:52.210    TestTNC (GBPUSD,D1)        10   44  7.993902318805092E-01   3.13860496E+00
LM      0       19:06:52.210    TestTNC (GBPUSD,D1)        11   48  6.601238685636209E-01   3.15085702E+01
EP      0       19:06:52.210    TestTNC (GBPUSD,D1)        12   53  3.391820074593478E-01   5.23994236E+01
DD      0       19:06:52.210    TestTNC (GBPUSD,D1)        13   55  2.644309665366499E-01   7.39869479E-01
NO      0       19:06:52.210    TestTNC (GBPUSD,D1)        14   61  1.675328075835125E-01   1.16689434E+01
ER      0       19:06:52.210    TestTNC (GBPUSD,D1)        15   65  1.224744553458436E-01   3.21610219E+01
LE      0       19:06:52.210    TestTNC (GBPUSD,D1)        16   67  6.925326451315299E-02   6.87199782E-01
JI      0       19:06:52.210    TestTNC (GBPUSD,D1)        17   73  2.930633981879897E-02   1.06326643E+01
GL      0       19:06:52.210    TestTNC (GBPUSD,D1)        18   77  1.341804114201537E-03   2.64590552E+00
DG      0       19:06:52.210    TestTNC (GBPUSD,D1)        19   79  2.447262820598820E-04   6.40221664E-04
DE      0       19:06:52.210    TestTNC (GBPUSD,D1)     tnc: fscale = 9.88041
FR      0       19:06:52.210    TestTNC (GBPUSD,D1)        20   81  2.442825206410282E-04   2.70915012E-04
EE      0       19:06:52.210    TestTNC (GBPUSD,D1)        21   83  5.876599419779381E-06   1.17530118E-02
FI      0       19:06:52.210    TestTNC (GBPUSD,D1)        22   85  3.385708330669465E-11   1.17585414E-10
QS      0       19:06:52.210    TestTNC (GBPUSD,D1)     tnc: fscale = 23054.9
NG      0       19:06:52.210    TestTNC (GBPUSD,D1)     tnc: |fn-fn-1] = 7.61917e-14 -> convergence
FN      0       19:06:52.210    TestTNC (GBPUSD,D1)        23   87  3.378089160219990E-11   3.50223471E-11
DN      0       19:06:52.210    TestTNC (GBPUSD,D1)      tnc: Converged (|f_n-f_(n-1)| ~= 0)
MK      0       19:06:52.210    TestTNC (GBPUSD,D1)      Optimization return code 1
LS      0       19:06:52.210    TestTNC (GBPUSD,D1)      optimization solution [0.9999941953936243,0.9999883612512912]

Die Ergebnisse zeigen, dass sich der Optimierer dem globalen Minimum stark annähert, auch wenn die Endwerte aufgrund der festgelegten Toleranzen möglicherweise nicht genau 1,0 entsprechen. Um einen praxisnaheren Anwendungsfall zu veranschaulichen, werden wir nun den LBFGS-Minimierer in einer Implementierung der logistischen Regression durch unseren TNC-Löser ersetzen.



Implementierung einer TNC-basierten logistischen Regression

Die CLogit-Klasse für die logistische Regression stützte sich bisher ausschließlich auf die LBFGS-Implementierung von ALGLIB als Optimierungsverfahren. In diesem Abschnitt erweitern wir die Klasse um den TNC-Optimierer, um eine robuste Alternative für die Erstellung logistischer Modelle bereitzustellen. Dies wird erreicht, indem die bestehende Fit-Methode so angepasst wird, dass eine Auswahl des Optimierers möglich ist.

//+------------------------------------------------------------------+
//|  fit a model                                                     |
//+------------------------------------------------------------------+
bool Clogit::Fit(matrix &predictors, vector &targets,
                 ENUM_MINIM_METHOD minimizer=MINIM_LBFGS,
                 tnc::ENUM_TNC_MESSAGE in_display = tnc::TNC_MSG_NONE,
                 double in_eta = -1.0,
                 double in_stepmx = 0.0,
                 double in_accuracy = 0.0,
                 double in_fmin = 0.0,
                 double in_ftol = -1.0,
                 double in_xtol = -1.0,
                 double in_pgtol = -1.0,
                 double in_rescale = -1.0,
                 int in_maxCGit = -1,
                 int in_maxnfeval = 100)
  {
   switch(minimizer)
     {
      case MINIM_LBFGS:
         return fit_lbfgs(predictors,targets);
      case MINIM_TNC:
         return fit_tnc(predictors,targets,in_display,in_eta,in_stepmx,in_accuracy,in_fmin,in_ftol,in_xtol,in_pgtol,in_rescale,in_maxCGit,in_maxnfeval);
     }
   return false;
  }

Um dies zu ermöglichen, wird der Code in logistic.mqh aktualisiert, indem die Enumeration ENUM_MINIM_METHOD definiert wird, die die verfügbaren Optimierungsalgorithmen zusammenfasst.

//+------------------------------------------------------------------+
//|minimizer used                                                    |
//+------------------------------------------------------------------+
enum ENUM_MINIM_METHOD
  {
   MINIM_LBFGS=0,//LBGS
   MINIM_TNC//TNC
  };

Zudem wurde die von CFunctor abgeleitete Klasse CLogitFunctor in die Header-Datei aufgenommen. Zwei private Member-Funktionen, fit_lbfgs und fit_tnc, übernehmen die Ausführungslogik für ihre jeweiligen Optimierer.

bool              fit_lbfgs(matrix &predictors, vector &targets);
bool              fit_tnc(matrix &predictors, vector &targets,
                             tnc::ENUM_TNC_MESSAGE in_display = tnc::TNC_MSG_NONE,
                             double in_eta = -1.0,
                             double in_stepmx = 0.0,
                             double in_accuracy = 0.0,
                             double in_fmin = 0.0,
                             double in_ftol = -1.0,
                             double in_xtol = -1.0,
                             double in_pgtol = -1.0,
                             double in_rescale = -1.0,
                             int in_maxCGit = -1,
                             int in_maxnfeval = 100);

Das Skript LogisticRegression.mq5 testet die Klasse CLogit anhand des klassischen Iris-Datensatzes. Das Programm trainiert das Modell unter Verwendung beider Optimierer auf einem Teil der Daten an, und die daraus resultierenden Modellparameter werden in das Protokoll des Terminals ausgegeben.

void OnStart()
  {
//---
   CHighQualityRandStateShell rngstate;
   CHighQualityRand::HQRndSeed(Random_Seed,Random_Seed+Random_Seed,rngstate.GetInnerObj());
//---
   Print(iris_data);
   string lines[],cells[];
   int ncells,nlines;
//---
   nlines = StringSplit(iris_data,StringGetCharacter("\n",0),lines);
   matrix data = matrix::Zeros(0,0);
   for(int i = 1; i<nlines-2; ++i)
     {
      ncells = StringSplit(lines[i],StringGetCharacter(",",0),cells);
      if(!data.Rows())
         data.Resize(nlines-2,ncells-1);
      for(int k = 1; k<(ncells); ++k)
         data[i-1,k-1] = StringToDouble(cells[k]);
     }
//---
   COneHotEncoder enc;
   ulong colum[1] = {4};
   if(!enc.fit(data,colum))
     {
      Print(" failed to encode data ");
      return;
     }
//---
   data = enc.transform(data);
//---
   long rindices[],trainset[],testset[];
   np::arange(rindices,int(data.Rows()));
//---
   np::shuffleArray(rindices,GetPointer(rngstate));
   ArrayCopy(trainset,rindices,0,0,int(ceil(Tra_Test_Split*rindices.Size())));
   ArraySort(trainset);
//---
   CSortedSet<long> test_set(rindices);
//---
   test_set.ExceptWith(trainset);
//---
   test_set.CopyTo(testset);
//---
   matrix testdata = np::selectMatrixRows(data,testset);
   matrix test_predictors = np::sliceMatrixCols(testdata,0,4);
   vector test_targets = testdata.Col(4);
   matrix traindata = np::selectMatrixRows(data,trainset);
   matrix tra_preditors = np::sliceMatrixCols(traindata,0,4);
   vector tra_targets = traindata.Col(4);
//---
   logistic::Clogit logit;
   if(!logit.Fit(tra_preditors,tra_targets))
     {
      Print(" failed to fit data with lbfgs");
      return;
     }
//---
   Print(" LBFGS logit results ");
   Print(" coefs ", logit.Get_Coefs());
   Print(" bias ", logit.Get_Bias());
//---
   if(!logit.Fit(tra_preditors,tra_targets,logistic::MINIM_TNC,0,_eta_,_stepmx_,_accuracy_,_fmin_,_ftol_,_xtol_,_pgtol_,_rescale_,_maxCGit_,_maxnfeval_))
     {
      Print(" failed to fit data with tnc ");
      return;
     }
//---
   Print(" TNC logit results ");
   Print(" coefs ", logit.Get_Coefs());
   Print(" bias ", logit.Get_Bias());

 } 

Die Ergebnisse zeigen, dass beide Optimierer zu ähnlichen Parametern konvergieren, was bestätigt, dass TNC als alternativer Optimierer für die logistische Regression eingesetzt werden könnte. 

CG      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  index,sl,sw,pl,pw,target
GS      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  0,5.1,3.5,1.4,0.2,0
HK      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  1,4.9,3.0,1.4,0.2,0
LR      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  2,4.7,3.2,1.3,0.2,0
CM      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  3,4.6,3.1,1.5,0.2,0
ED      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  4,5.0,3.6,1.4,0.2,0
DL      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  5,5.4,3.9,1.7,0.4,0
OG      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  6,4.6,3.4,1.4,0.3,0
IN      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  7,5.0,3.4,1.5,0.2,0
HI      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  8,4.4,2.9,1.4,0.2,0
QQ      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  9,4.9,3.1,1.5,0.1,0
NK      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  10,5.4,3.7,1.5,0.2,0
HP      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  11,4.8,3.4,1.6,0.2,0
PI      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  12,4.8,3.0,1.4,0.1,0
CF      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  13,4.3,3.0,1.1,0.1,0
OO      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  14,5.8,4.0,1.2,0.2,0
HD      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  15,5.7,4.4,1.5,0.4,0
JL      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  16,5.4,3.9,1.3,0.4,0
PE      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  17,5.1,3.5,1.4,0.3,0
OR      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  18,5.7,3.8,1.7,0.3,0
NK      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  19,5.1,3.8,1.5,0.3,0
HP      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  20,5.4,3.4,1.7,0.2,0
GI      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  21,5.1,3.7,1.5,0.4,0
HF      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  22,4.6,3.6,1.0,0.2,0
HO      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  23,5.1,3.3,1.7,0.5,0
MG      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  24,4.8,3.4,1.9,0.2,0
NL      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  25,5.0,3.0,1.6,0.2,0
OE      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  26,5.0,3.4,1.6,0.4,0
LR      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  27,5.2,3.5,1.5,0.2,0
KK      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  28,5.2,3.4,1.4,0.2,0
NP      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  29,4.7,3.2,1.6,0.2,0
LI      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  30,4.8,3.1,1.6,0.2,0
JF      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  31,5.4,3.4,1.5,0.4,0
NN      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  32,5.2,4.1,1.5,0.1,0
QG      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  33,5.5,4.2,1.4,0.2,0
JL      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  34,4.9,3.1,1.5,0.2,0
KE      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  35,5.0,3.2,1.2,0.2,0
IR      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  36,5.5,3.5,1.3,0.2,0
RK      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  37,4.9,3.6,1.4,0.1,0
LP      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  38,4.4,3.0,1.3,0.2,0
OI      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  39,5.1,3.4,1.5,0.2,0
NQ      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  40,5.0,3.5,1.3,0.3,0
NN      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  41,4.5,2.3,1.3,0.3,0
QG      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  42,4.4,3.2,1.3,0.2,0
QL      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  43,5.0,3.5,1.6,0.6,0
IE      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  44,5.1,3.8,1.9,0.4,0
RR      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  45,4.8,3.0,1.4,0.3,0
LK      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  46,5.1,3.8,1.6,0.2,0
GP      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  47,4.6,3.2,1.4,0.2,0
HH      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  48,5.3,3.7,1.5,0.2,0
CQ      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  49,5.0,3.3,1.4,0.2,0
PN      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  50,7.0,3.2,4.7,1.4,1
KG      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  51,6.4,3.2,4.5,1.5,1
JL      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  52,6.9,3.1,4.9,1.5,1
JE      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  53,5.5,2.3,4.0,1.3,1
QR      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  54,6.5,2.8,4.6,1.5,1
LK      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  55,5.7,2.8,4.5,1.3,1
QS      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  56,6.3,3.3,4.7,1.6,1
IH      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  57,4.9,2.4,3.3,1.0,1
QQ      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  58,6.6,2.9,4.6,1.3,1
NN      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  59,5.2,2.7,3.9,1.4,1
EG      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  60,5.0,2.0,3.5,1.0,1
OL      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  61,5.9,3.0,4.2,1.5,1
JE      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  62,6.0,2.2,4.0,1.0,1
PR      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  63,6.1,2.9,4.7,1.4,1
DJ      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  64,5.6,2.9,3.6,1.3,1
NS      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  65,6.7,3.1,4.4,1.4,1
PH      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  66,5.6,3.0,4.5,1.5,1
JQ      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  67,5.8,2.7,4.1,1.0,1
NN      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  68,6.2,2.2,4.5,1.5,1
JG      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  69,5.6,2.5,3.9,1.1,1
RL      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  70,5.9,3.2,4.8,1.8,1
RE      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  71,6.1,2.8,4.0,1.3,1
EM      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  72,6.3,2.5,4.9,1.5,1
JJ      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  73,6.1,2.8,4.7,1.2,1
JS      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  74,6.4,2.9,4.3,1.3,1
QH      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  75,6.6,3.0,4.4,1.4,1
KQ      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  76,6.8,2.8,4.8,1.4,1
JN      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  77,6.7,3.0,5.0,1.7,1
JG      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  78,6.0,2.9,4.5,1.5,1
PL      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  79,5.7,2.6,3.5,1.0,1
LD      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  80,5.5,2.4,3.8,1.1,1
GM      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  81,5.5,2.4,3.7,1.0,1
FJ      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  82,5.8,2.7,3.9,1.2,1
DS      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  83,6.0,2.7,5.1,1.6,1
RH      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  84,5.4,3.0,4.5,1.5,1
OQ      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  85,6.0,3.4,4.5,1.6,1
MN      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  86,6.7,3.1,4.7,1.5,1
NG      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  87,6.3,2.3,4.4,1.3,1
NO      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  88,5.6,3.0,4.1,1.3,1
QD      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  89,5.5,2.5,4.0,1.3,1
IM      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  90,5.5,2.6,4.4,1.2,1
DJ      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  91,6.1,3.0,4.6,1.4,1
DS      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  92,5.8,2.6,4.0,1.2,1
LH      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  93,5.0,2.3,3.3,1.0,1
RQ      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  94,5.6,2.7,4.2,1.3,1
MN      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  95,5.7,3.0,4.2,1.2,1
QF      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  96,5.7,2.9,4.2,1.3,1
MO      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  97,6.2,2.9,4.3,1.3,1
LD      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  98,5.1,2.5,3.0,1.1,1
HM      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  99,5.7,2.8,4.1,1.3,1
EI      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  100,6.3,3.3,6.0,2.5,2
FP      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  101,5.8,2.7,5.1,1.9,2
QK      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  102,7.1,3.0,5.9,2.1,2
DR      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  103,6.3,2.9,5.6,1.8,2
RM      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  104,6.5,3.0,5.8,2.2,2
ED      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  105,7.6,3.0,6.6,2.1,2
PO      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  106,4.9,2.5,4.5,1.7,2
EF      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  107,7.3,2.9,6.3,1.8,2
OQ      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  108,6.7,2.5,5.8,1.8,2
RH      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  109,7.2,3.6,6.1,2.5,2
FS      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  110,6.5,3.2,5.1,2.0,2
RJ      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  111,6.4,2.7,5.3,1.9,2
NE      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  112,6.8,3.0,5.5,2.1,2
IL      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  113,5.7,2.5,5.0,2.0,2
IG      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  114,5.8,2.8,5.1,2.4,2
IN      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  115,6.4,3.2,5.3,2.3,2
KI      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  116,6.5,3.0,5.5,1.8,2
MP      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  117,7.7,3.8,6.7,2.2,2
HK      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  118,7.7,2.6,6.9,2.3,2
LR      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  119,6.0,2.2,5.0,1.5,2
LM      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  120,6.9,3.2,5.7,2.3,2
FD      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  121,5.6,2.8,4.9,2.0,2
RO      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  122,7.7,2.8,6.7,2.0,2
FF      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  123,6.3,2.7,4.9,1.8,2
CQ      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  124,6.7,3.3,5.7,2.1,2
MH      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  125,7.2,3.2,6.0,1.8,2
LS      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  126,6.2,2.8,4.8,1.8,2
FJ      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  127,6.1,3.0,4.9,1.8,2
GE      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  128,6.4,2.8,5.6,2.1,2
DL      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  129,7.2,3.0,5.8,1.6,2
FG      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  130,7.4,2.8,6.1,1.9,2
DN      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  131,7.9,3.8,6.4,2.0,2
OI      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  132,6.4,2.8,5.6,2.2,2
LP      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  133,6.3,2.8,5.1,1.5,2
GK      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  134,6.1,2.6,5.6,1.4,2
HR      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  135,7.7,3.0,6.1,2.3,2
GM      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  136,6.3,3.4,5.6,2.4,2
ND      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  137,6.4,3.1,5.5,1.8,2
PO      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  138,6.0,3.0,4.8,1.8,2
FF      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  139,6.9,3.1,5.4,2.1,2
GQ      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  140,6.7,3.1,5.6,2.4,2
NH      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  141,6.9,3.1,5.1,2.3,2
GS      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  142,5.8,2.7,5.1,1.9,2
FJ      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  143,6.8,3.2,5.9,2.3,2
EE      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  144,6.7,3.3,5.7,2.5,2
FL      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  145,6.7,3.0,5.2,2.3,2
NG      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  146,6.3,2.5,5.0,1.9,2
CN      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  147,6.5,3.0,5.2,2.0,2
LI      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  148,6.2,3.4,5.4,2.3,2
LP      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  149,5.9,3.0,5.1,1.8,2
LI      0       19:11:15.136    LogisticRegression (GBPUSD,D1)  
EN      0       19:11:15.138    LogisticRegression (GBPUSD,D1)   LBFGS logit results 
QF      0       19:11:15.138    LogisticRegression (GBPUSD,D1)   coefs [[-0.3544914154418004,0.7640895553785291,-2.029702076841132,-0.8297514969607693]]
EK      0       19:11:15.138    LogisticRegression (GBPUSD,D1)   bias [5.587065638186691]
RJ      0       19:11:15.141    LogisticRegression (GBPUSD,D1)   TNC logit results 
FR      0       19:11:15.141    LogisticRegression (GBPUSD,D1)   coefs [[-0.3543682771949164,0.7643878803868314,-2.029620459724486,-0.8298337657974659]]
IN      0       19:11:15.141    LogisticRegression (GBPUSD,D1)   bias [5.585350403781237]

In den folgenden Abschnitten des Artikels wird die Implementierung des TNC-Optimierers genauer beschrieben, einschließlich der Funktionsweise des Codes und der Definition ausgewählter Variablen. 



Kernimplementierung von TNC

Die Implementierung der TNC-Optimierung besteht aus zwei Teilen: der in tnc.mqh enthaltenen Kernlogik und einer generischen sekundären Komponente, die Wrapper für die Zielfunktion, deren Ableitung und die Hessematrix definiert. Wir werden uns zunächst auf die Datei tnc.mqh konzentrieren, da diese den Kernalgorithmus enthält. Der Code in tnc.mqh beginnt mit der Definition mehrerer Enumerationen, die als strukturelles Gerüst für die interne Kommunikation dienen.

enum ENUM_TNC_MESSAGE
  {
   TNC_MSG_NONE = 0, /* No messages */
   TNC_MSG_ITER = 1, /* One line per iteration */
   TNC_MSG_INFO = 2, /* Informational messages */
   TNC_MSG_EXIT = 8, /* Exit reasons */
   TNC_MSG_ALL = TNC_MSG_ITER | TNC_MSG_INFO | TNC_MSG_EXIT /* All messages */
  };

enum ENUM_TNC_RC
  {
   TNC_MINRC        = -3, /* Constant to add to get the rc_string */
   TNC_ENOMEM       = -3, /* Memory allocation failed */
   TNC_EINVAL       = -2, /* Invalid parameters (n<0) */
   TNC_INFEASIBLE   = -1, /* Infeasible (low bound > up bound) */
   TNC_LOCALMINIMUM =  0, /* Local minima reach (|pg| ~= 0) */
   TNC_FCONVERGED   =  1, /* Converged (|f_n-f_(n-1)| ~= 0) */
   TNC_XCONVERGED   =  2, /* Converged (|x_n-x_(n-1)| ~= 0) */
   TNC_MAXFUN       =  3, /* Max. number of function evaluations reach */
   TNC_LSFAIL       =  4, /* Linear search failed */
   TNC_CONSTANT     =  5, /* All lower bounds are equal to the upper bounds */
   TNC_NOPROGRESS   =  6, /* Unable to progress */
   TNC_USERABORT    =  7  /* User requested end of minization */
  };
  • Die Enumeration ENUM_TNC_MESSAGE definiert Bitmasken-Flags zur Steuerung des Ausgabedetailgrads.
  • Die Enumeration ENUM_TNC_RC enthält eine Reihe von Rückgabecodes, die den Grund für die Beendigung angeben. 
  • Die Variable tnc_rc_string ist ein Zeichenfolgenarray, das diese Rückgabecodes mit für Menschen lesbaren Meldungen verknüpft.
const string tnc_rc_string[11] =
  {
   "Memory allocation failed",
   "Invalid parameters (n<0)",
   "Infeasible (low bound > up bound)",
   "Local minima reach (|pg| ~= 0)",
   "Converged (|f_n-f_(n-1)| ~= 0)",
   "Converged (|x_n-x_(n-1)| ~= 0)",
   "Maximum number of function evaluations reached",
   "Linear search failed",
   "All lower bounds are equal to the upper bounds",
   "Unable to progress",
   "User requested end of minimization"
  };

Darüber hinaus definieren spezifische Enumerationen die Rückgabestatus für die Subroutinen zur linearen Suche und zur Punktfindung, wodurch sichergestellt wird, dass der Algorithmus seinen Fortschritt durch jede verschachtelte Schleife präzise verfolgen kann.

/*
 * getptc return codes
 */
enum ENUM_GETPTC_RC
  {
   GETPTC_OK     = 0,          /* Suitable point found */
   GETPTC_EVAL   = 1,          /* Function evaluation required */
   GETPTC_EINVAL = 2,          /* Bad input values */
   GETPTC_FAIL   = 3           /* No suitable point found */
  };

/*
 * linearSearch return codes
 */
enum ENUM_LS_RC
  {
   LS_OK        = 0,           /* Suitable point found */
   LS_MAXFUN    = 1,           /* Max. number of function evaluations reach */
   LS_FAIL      = 2,           /* No suitable point found */
   LS_USERABORT = 3,           /* User requested end of minimization */
   LS_ENOMEM    = 4            /* Memory allocation failed */
  };

Die Klasse CObjective dient als Zustandsmanager für die zu optimierende Funktion. Sie enthält Metadaten wie beispielsweise die Dimensionalität des Problems, die Angabe, ob ein Fehler aufgetreten ist, sowie Zeiger auf die eigentliche Logik der Zielfunktion.

//+------------------------------------------------------------------+
//|objective function state                                          |
//+------------------------------------------------------------------+
class CObjective
  {

protected:
   bool              m_failed,m_fupdated,m_gupdated,m_hupdated;
   double            m_lowestx,m_lowest_f;
   ulong             m_size;
   IObjective        *m_objective;

public:
                     CObjective(void):m_failed(false),
                     m_size(0)
     {
     }
                    ~CObjective(void)
     {
     }
   void              set_objective(IObjective* &fun_obj)
     {
      m_objective = fun_obj;
     }
   void              has_failed(bool yes)
     {
      m_failed = yes;
     }
   void              set_dim(ulong size)
     {
      m_size = size;
     }
   bool              failed(void)
     {
      return m_failed;
     }
   bool              abort(void)
     {
      return IsStopped();
     }
   ulong             size(void)
     {
      return m_size;
     }
   ObjReturn         objective(vector& x)
     {
      return m_objective.fun_and_grad(x);
     }
   virtual int       callback(vector& x)
     {
      return 0;
     }
  }

Durch ihre Funktion als Wrapper ermöglicht diese Klasse dem TNC-Algorithmus, Funktionsauswertungen und Gradienten abzufragen und gleichzeitig interne Flags zum Optimierungsfortschritt zu verwalten. Diese Abstraktionsschicht stellt sicher, dass der Kern-Löser von den spezifischen mathematischen Details der zu minimierenden Funktion entkoppelt bleibt. Im Anschluss an die Klassendefinition werden im Code typedef-Funktionszeiger definiert, um die Signaturen der Ziel- und Callback-Funktionen zu vereinheitlichen.

//+------------------------------------------------------------------+
//| function pointers                                                |
//+------------------------------------------------------------------+
typedef int(*tnc_function)(vector&, double&, vector&, CObjective&);
typedef void(*tnc_callback)(vector&, CObjective&);
//+------------------------------------------------------------------+
//| fpointer                                                         |
//+------------------------------------------------------------------+
int func(vector& x, double& f, vector& g, CObjective& state)
  {
   ulong n = state.size();
   vector x_data,g_data;

   if(state.abort())
      return 2;

   if(state.failed())
      return 1;

   x_data = x;

   ObjReturn fg = state.objective(x_data);
   f = fg.f;

   if(fg.g.Size()!=n)
     {
      printf(" tnc: gradient must have length ", n);
      return 2;
     }
   g = fg.g;

   return 0;
  }
//+------------------------------------------------------------------+
//| callback                                                         |
//+------------------------------------------------------------------+
void call_back(vector& x, CObjective& state)
  {
   ulong n  = state.size();

   vector x_data = x;
   if(state.callback(x_data))
      state.has_failed(true);
   else
      state.has_failed(false);
   return;  

Die Funktionen func und call_back dienen als Brückenlogik zwischen dem Low-Level-Löser und dem CObjective-Zustand. Sie übernehmen die Extraktion von Daten aus Vektoren, die Überprüfung der Gradientendimensionen sowie die Überprüfung auf vom Benutzer ausgelöste Abbrüche, bevor die Ergebnisse an den Solver zurückgegeben werden. Dadurch wird sichergestellt, dass der Kernalgorithmus von den spezifischen Datenstrukturen, die vom Aufrufer verwendet werden, entkoppelt bleibt.

//+------------------------------------------------------------------+
//|Optimization results                                              |
//+------------------------------------------------------------------+
struct OptimizeResult
  {
   int               return_code;
   int               nfeval;
   int               niter;
   vector            solution;
   vector            objective_result;
   vector            objective_gradient;

                     OptimizeResult(void)
     {
      return_code = WRONG_VALUE;
      nfeval = niter = 0;
      solution = objective_result = objective_gradient = vector::Zeros(0);
     }
                     OptimizeResult(int rc,int feval,int iter,vector &x, vector& f, vector& g)
     {
      return_code = rc;
      nfeval = feval;
      niter = iter;
      solution = x;
      objective_result = f;
      objective_gradient = g;
     }
                     OptimizeResult(OptimizeResult& other)
     {
      return_code = other.return_code;
      nfeval = other.nfeval;
      niter = other.niter;
      solution = other.solution;
      objective_result = other.objective_result;
      objective_gradient = other.objective_gradient;
     }
   void              operator=(OptimizeResult& other)
     {
      return_code = other.return_code;
      nfeval = other.nfeval;
      niter = other.niter;
      solution = other.solution;
      objective_result = other.objective_result;
      objective_gradient = other.objective_gradient;
     }
  }

Die Struktur OptimizeResult ist eine kompakte Datenstruktur, die dazu dient, das Endergebnis des Optimierungsprozesses zu verpacken. Sie speichert wichtige Metadaten, darunter den Rückgabewert, die Anzahl der Funktionsauswertungen und die Gesamtzahl der durchgeführten Iterationen. Außerdem enthält es die Endvektoren für die Lösung, Zielfunktionswert und den Gradienten. Um nach Abschluss des Solvers einen nahtlosen Datenaustausch zwischen verschiedenen Teilen der Anwendung zu ermöglichen, enthält die Struktur mehrere Konstruktoren und einen Zuweisungsoperator.

//+------------------------------------------------------------------+
//|class encapsulating TNC minimizer                                 |
//+------------------------------------------------------------------+
class CTruncNewtonCG: public CObject
  {

private:


   int               tnc(int n, vector& x, double &f, vector& g, tnc_function& function,
                         CObjective &state, vector& low, vector& up, vector& scale,
                         vector& offset, int messages, int maxCGit, int maxnfeval,
                         double eta, double stepmx, double accuracy, double _fmin,
                         double ftol, double xtol, double pgtol, double rescale,
                         int &nfeval, int &niter, tnc_callback &callback);


   void              coercex(int n, vector& x, const vector& low, const vector& up);


   void              unscalex(int n, vector& x, const vector& xscale,
                              const vector& xoffset);


   void              scalex(int n, vector& x, const vector& xscale,
                            const vector& xoffset);



   void              scaleg(int n, vector& g, const vector& xscale, double& fscale);


   void              setConstraints(int n, vector& x, int& pivot[], vector& xscale,
                                    vector& xoffset, vector& low, vector& up);



   ENUM_TNC_RC       minize_tnc(int n, vector& x,
                                double &f, vector& gfull,
                                tnc_function function, CObjective &state,
                                vector& xscale, vector& xoffset,
                                double &fscale, vector& low, vector& up,
                                ENUM_TNC_MESSAGE messages, int maxCGit,
                                int maxnfeval, int &nfeval, int &niter,
                                double eta, double stepmx, double accuracy,
                                double _fmin, double ftol, double xtol,
                                double pgtol, double rescale,
                                tnc_callback& callback);


   void              printCurrentIteration(int n, double f, vector& g, int niter,
                                           int nfeval, int &pivot[]);


   void              project(int n, vector& x, const int &pivot[]);



   void              projectConstants(int n, vector& x, const vector& xscale);



   double            stepMax(double step, int n, vector& x, vector& dir,
                             int &pivot[], vector& low, vector& up,
                             vector& xscale, vector& xoffset);



   bool              addConstraint(int n, vector& x, vector& p, int &pivot[],
                                   vector& low, vector& up, vector& xscale,
                                   vector& xoffset);



   bool              removeConstraint(double gtpnew, double gnorm,
                                      double pgtolfs, double f,
                                      double fLastConstraint, vector& g,
                                      int &pivot[], int n);


   */
   int               tnc_direction(vector &zsol, vector &diagb,
                                   vector &x, vector& g, int n,
                                   int maxCGit, int maxnfeval, int &nfeval,
                                   bool upd1, double yksk, double yrsr,
                                   vector &sk, vector &yk, vector &sr, vector &yr,
                                   bool lreset, tnc_function & function,
                                   CObjective &state, vector& xscale, vector& xoffset,
                                   double fscale, int &pivot[], double accuracy,
                                   double gnorm, double xnorm, vector& low,
                                   vector& up);

   void              diagonalScaling(int n, vector& e, vector& v, vector& gv,
                                     vector& r);

   double            initialStep(double fnew, double _fmin, double gtp,
                                 double smax);

   int               hessianTimesVector(vector& v, vector& gv, int n,
                                        vector& x, vector& g,
                                        tnc_function & function, CObjective &state,
                                        vector& xscale, vector& xoffset,
                                        double fscale, double accuracy, double xnorm,
                                        vector& low, vector& up);

   int               msolve(vector& g, vector& y, int n,
                            vector& sk, vector& yk, vector& diagb, vector& sr,
                            vector& yr, bool upd1, double yksk, double yrsr,
                            bool lreset);

   void              ssbfgs(int n, double gamma, vector& sj, vector& hjv,
                            vector& hjyj, double yjsj,
                            double yjhyj, double vsj, double vhyj, vector& hjp1v);

   int               initPreconditioner(vector& diagb, vector& emat, int n,
                                        bool lreset, double yksk, double yrsr,
                                        vector& sk, vector& yk, vector& sr,
                                        vector& yr, bool upd1);

   ENUM_LS_RC        linearSearch(int n, tnc_function & function, CObjective &state,
                                  vector& low, vector& up,
                                  vector& xscale, vector& xoffset, double fscale,
                                  int &pivot[], double eta, double ftol,
                                  double xbnd, vector& p, vector& x, double &f,
                                  double &alpha, vector& gfull, int maxnfeval,
                                  int &nfeval);

   ENUM_GETPTC_RC    getptcInit(double &reltol, double &abstol, double tnytol,
                                double eta, double rmu, double xbnd,
                                double &u, double &fu, double &gu,
                                double &xmin, double &_fmin, double &gmin,
                                double &xw, double &fw, double &gw, double &a,
                                double &b, double &oldf, double &b1,
                                double &scxbnd, double &e, double &step,
                                double &factor, bool & braktd,
                                double &gtest1, double &gtest2, double &tol);
   
   ENUM_GETPTC_RC    getptcIter(double big, double
                                rtsmll, double &reltol, double &abstol,
                                double tnytol, double fpresn, double xbnd,
                                double &u, double &fu, double &gu,
                                double &xmin, double &_fmin, double &gmin,
                                double &xw, double &fw, double &gw, double &a,
                                double &b, double &oldf, double &b1,
                                double &scxbnd, double &e, double &step,
                                double &factor, bool & braktd,
                                double &gtest1, double &gtest2, double &tol);

   void              dxpy1(int n, const vector& dx, vector& dy);

   void              daxpy1(int n, double da, const vector& dx, vector& dy);

   void              dcopy1(int n, const vector& dx, vector& dy);
   void              dneg1(int n, vector& v);
   double            ddot1(int n, const vector& dx, const vector& dy);

   double            dnrm21(int n, const vector& dx);

   OptimizeResult    tnc_minimize(CFunctor &fungrad,vector& scale, vector& offset, int messages, int maxCGit, int maxfun, double eta, double stepmax, double accuracy, double fmin_, double ftol, double xtol,double pgtol, double rescale);


   vector            m_scale,m_offset;
   int               m_messages,m_maxCGit, m_maxfun;
   double            m_eta, m_stepmax, m_accuracy, m_fmin,m_ftol,m_xtol,m_pgtol, m_rescale;
   OptimizeResult    m_result;
public:
                     CTruncNewtonCG(void);

                    ~CTruncNewtonCG(void);

   void              SetScale(vector& scale);

   void              SetOffset(vector& offset);

   void              SetLoglevel(ENUM_TNC_MESSAGE messages);

   void              SetMaxCGit(int maxCGit);

   void              SetMaxFunCalls(int maxfun);

   void              SetEta(double eta);

   void              SetStepMax(double stepmax);

   void              SetAccuracy(double accuracy);

   void              SetFmin(double f_min);

   void              SetFtol(double ftol);

   void              SetXtol(double xtol);

   void              SetPGtol(double pgtol);

   void              SetRescaleFactor(double rescale);

   int               Minimize(CFunctor &fungrad);

   vector            Solution(void);

   double            ObjectiveResult(void);

   vector            ObjectiveGradient(void);

   int               NumFevals(void);

   int               NumIters(void);

  };

Die Klasse CTruncNewtonCG enthält die Kernlogik des Optimierungsalgorithmus. Sie umfasst die TNC-Einstiegsmethode, die eine erste Parameterprüfung durchführt, die Skalierung von Variablen übernimmt und Standardwerte für Toleranzen und Schrittweiten festlegt.

int               tnc(int n, vector& x, double &f, vector& g, tnc_function& function,
                         CObjective &state, vector& low, vector& up, vector& scale,
                         vector& offset, int messages, int maxCGit, int maxnfeval,
                         double eta, double stepmx, double accuracy, double _fmin,
                         double ftol, double xtol, double pgtol, double rescale,
                         int &nfeval, int &niter, tnc_callback &callback)
     {
      int rc, frc, i, nc, nfeval_local, free_low = TNC_FALSE,free_up = TNC_FALSE, free_g = TNC_FALSE;
      double fscale, rteps;
      vector xscale,xoffset;
      nfeval = nfeval_local = 0;

      /* Check for errors in the input parameters */
      if(n == 0)
        {
         rc = TNC_CONSTANT;
         nfeval = (nfeval==0)?nfeval_local:nfeval;
         if(bool(messages & TNC_MSG_EXIT))
            printf(" tnc: %s", tnc_rc_string[rc - TNC_MINRC]);
         return rc;
        }

      if(n < 0)
        {
         rc = TNC_EINVAL;
         nfeval = (nfeval==0)?nfeval_local:nfeval;
         if(bool(messages & TNC_MSG_EXIT))
            printf(" tnc: %s", tnc_rc_string[rc - TNC_MINRC]);
         return rc;
        }

      /* Check bounds arrays */
      if(!low.Size())
        {
         low = vector::Zeros(n);
         free_low = TNC_TRUE;
         for(i = 0; i < n; i++)
           {
            low[i] = -HUGE_VAL;
           }
        }

      if(!up.Size())
        {
         up = vector::Zeros(n);
         if(up.Size()==0)
           {
            rc = TNC_ENOMEM;
            nfeval = (nfeval==0)?nfeval_local:nfeval;
            if(bool(messages & TNC_MSG_EXIT))
               printf(" tnc: %s", tnc_rc_string[rc - TNC_MINRC]);
            return rc;
           }
         free_up = TNC_TRUE;
         for(i = 0; i < n; i++)
           {
            up[i] = HUGE_VAL;
           }
        }

      /* Coherency check */
      for(i = 0; i < n; i++)
        {
         if(low[i] > up[i])
           {
            rc = TNC_INFEASIBLE;
            nfeval = (nfeval==0)?nfeval_local:nfeval;
            if(bool(messages & TNC_MSG_EXIT))
               printf(" tnc: %s", tnc_rc_string[rc - TNC_MINRC]);
            return rc;
           }
        }

      /* Coerce x into bounds */
      coercex(n, x, low, up);

      if(maxnfeval < 1)
        {
         rc = TNC_MAXFUN;
         nfeval = (nfeval==0)?nfeval_local:nfeval;
         if(bool(messages & TNC_MSG_EXIT))
            printf(" tnc: %s", tnc_rc_string[rc - TNC_MINRC]);
         return rc;
        }

      /* Allocate g if necessary */
      if(g.Size()==0)
        {
         g = vector::Zeros(n);
         if(g.Size()==0)
           {
            rc = TNC_ENOMEM;
            nfeval = (nfeval==0)?nfeval_local:nfeval;
            if(bool(messages & TNC_MSG_EXIT))
               printf(" tnc: %s", tnc_rc_string[rc - TNC_MINRC]);
            return rc;
           }
         free_g = TNC_TRUE;
        }

      /* Initial function evaluation */
      //Print(__FUNCTION__," - ", x);
      frc = function(x, f, g, state);
      (nfeval)++;
      if(frc)
        {
         rc = TNC_USERABORT;
         nfeval = (nfeval==0)?nfeval_local:nfeval;
         if(bool(messages & TNC_MSG_EXIT))
            printf(" tnc: %s", tnc_rc_string[rc - TNC_MINRC]);
         return rc;
        }

      /* Constant problem ? */
      for(nc = 0, i = 0; i < n; i++)
        {
         if((low[i] == up[i]) || (scale.Size() != 0 && scale[i] == 0.0))
           {
            nc++;
           }
        }

      if(nc == n)
        {
         rc = TNC_CONSTANT;
         nfeval = (nfeval==0)?nfeval_local:nfeval;
         if(bool(messages & TNC_MSG_EXIT))
            printf(" tnc: %s", tnc_rc_string[rc - TNC_MINRC]);
         return rc;
        }

      /* Scaling parameters */
      xscale = vector::Zeros(n);
      if(xscale.Size()==0)
        {
         rc = TNC_ENOMEM;
         nfeval = (nfeval==0)?nfeval_local:nfeval;
         if(bool(messages & TNC_MSG_EXIT))
            printf(" tnc: %s", tnc_rc_string[rc - TNC_MINRC]);
         return rc;;
        }
      xoffset = vector::Zeros(n);
      if(xoffset.Size()==0)
        {
         rc = TNC_ENOMEM;
         nfeval = (nfeval==0)?nfeval_local:nfeval;
         if(bool(messages & TNC_MSG_EXIT))
            printf(" tnc: %s", tnc_rc_string[rc - TNC_MINRC]);
         return rc;
        }
      fscale = 1.0;

      for(i = 0; i < n; i++)
        {
         if(scale.Size() != NULL)
           {
            xscale[i] = fabs(scale[i]);
            if(xscale[i] == 0.0)
              {
               xoffset[i] = low[i] = up[i] = x[i];
              }
           }
         else
            if(low[i] != -HUGE_VAL && up[i] != HUGE_VAL)
              {
               xscale[i] = up[i] - low[i];
               xoffset[i] = (up[i] + low[i]) * 0.5;
              }
            else
              {
               xscale[i] = 1.0 + fabs(x[i]);
               xoffset[i] = x[i];
              }
         if(offset.Size() != NULL)
           {
            xoffset[i] = offset[i];
           }
        }

      /* Default values for parameters */
      rteps = sqrt(DBL_EPSILON);

      if(stepmx < rteps * 10.0)
        {
         stepmx = 1.0e1;
        }
      if(eta < 0.0 || eta >= 1.0)
        {
         eta = 0.25;
        }
      if(rescale < 0)
        {
         rescale = 1.3;
        }
      if(maxCGit < 0)             /* maxCGit == 0 is valid */
        {
         maxCGit = n / 2;
         if(maxCGit < 1)
           {
            maxCGit = 1;
           }
         else
            if(maxCGit > 50)
              {
               maxCGit = 50;
              }
        }
      if(maxCGit > n)
        {
         maxCGit = n;
        }
      if(accuracy <= DBL_EPSILON)
        {
         accuracy = rteps;
        }
      if(ftol < 0.0)
        {
         ftol = accuracy;
        }
      if(pgtol < 0.0)
        {
         pgtol = 1e-2 * sqrt(accuracy);
        }
      if(xtol < 0.0)
        {
         xtol = rteps;
        }

      /* Optimisation */
      rc = minize_tnc(n, x, f, g, function, state,
                      xscale, xoffset, fscale, low, up,(ENUM_TNC_MESSAGE)messages,
                      maxCGit, maxnfeval, nfeval, niter, eta, stepmx,
                      accuracy, _fmin, ftol, xtol, pgtol, rescale,
                      callback);

      if(bool(messages & TNC_MSG_EXIT))
         printf(" tnc: %s", tnc_rc_string[rc - TNC_MINRC]);
      return rc;
     }

Dieser Abschnitt ist dafür zuständig, Variablen in ihre definierten Grenzen zu zwingen und die Skalierungsparameter zu initialisieren. Diese Parameter gewährleisten, dass der Algorithmus numerisch stabil bleibt, selbst wenn Variablen mit stark unterschiedlichen Größenordnungen verarbeitet werden.

ENUM_TNC_RC       minize_tnc(int n, vector& x,
                                double &f, vector& gfull,
                                tnc_function function, CObjective &state,
                                vector& xscale, vector& xoffset,
                                double &fscale, vector& low, vector& up,
                                ENUM_TNC_MESSAGE messages, int maxCGit,
                                int maxnfeval, int &nfeval, int &niter,
                                double eta, double stepmx, double accuracy,
                                double _fmin, double ftol, double xtol,
                                double pgtol, double rescale,
                                tnc_callback& callback)
     {
      double fLastReset, difnew, epsred, oldgtp, difold, oldf, xnorm, newscale,
             gnorm, ustpmax, fLastConstraint, spe, yrsr, yksk;
      vector temp,sk,yk,diagb,sr,yr,oldg,pk,g;
      double alpha = 0.0;         /* Default unused value */
      int i, icycle, oldnfeval, frc;
      bool lreset, newcon, upd1, remcon;
      ENUM_TNC_RC rc = TNC_ENOMEM;     /* Default error */
      niter = 0;
      int pivot[];
      /* Allocate temporary vectors */
      oldg = vector::Zeros(n);
      if(oldg.Size()==0)
        {
         return rc;
        }
      g = vector::Zeros(n);
      if(g.Size()==0)
        {
         return rc;
        }
      temp = vector::Zeros(n);
      if(temp.Size()==0)
        {
         return rc;
        }
      diagb = vector::Zeros(n);
      if(diagb.Size()==0)
        {
         return rc;
        }
      pk = vector::Zeros(n);
      if(pk.Size()==0)
        {
         return rc;
        }

      sk = vector::Zeros(n);
      if(sk.Size()==0)
        {
         return rc;
        }
      yk = vector::Zeros(n);
      if(yk.Size()==0)
        {
         return rc;
        }
      sr = vector::Zeros(n);
      if(sr.Size()==0)
        {
         return rc;
        }
      yr = vector::Zeros(n);
      if(yr.Size()==0)
        {
         return rc;
        }

      ArrayResize(pivot,n);
      if(pivot.Size()==0)
        {
         return rc;
        }

      /* Initialize variables */
      difnew = 0.0;
      epsred = 0.05;
      upd1 = TNC_TRUE;
      icycle = n - 1;
      newcon = TNC_TRUE;

      /* Unneeded initialisations */
      lreset = TNC_FALSE;
      yrsr = 0.0;
      yksk = 0.0;

      /* Initial scaling */
      scalex(n, x, xscale, xoffset);
      f *=fscale;

      /* initial pivot calculation */
      setConstraints(n, x, pivot, xscale, xoffset, low, up);

      dcopy1(n, gfull, g);
      scaleg(n, g, xscale, fscale);

      /* Test the lagrange multipliers to see if they are non-negative. */
      for(i = 0; i < n; i++)
        {
         if(-pivot[i] * g[i] < 0.0)
           {
            pivot[i] = 0;
           }
        }

      project(n, g, pivot);

      /* Set initial values to other parameters */
      gnorm = dnrm21(n, g);

      fLastConstraint = f;       /* Value at last constraint */
      fLastReset = f;            /* Value at last reset */

      if(bool(messages & TNC_MSG_ITER))
        {
         printf("  NIT   NF   F                       GTG");
        }
      if(bool(messages & TNC_MSG_ITER))
        {
         printCurrentIteration(n, f / fscale, gfull,
                               niter, nfeval, pivot);
        }

      /* Set the diagonal of the approximate hessian to unity. */
      diagb = vector::Ones(diagb.Size());
      /*for(i = 0; i < n; i++)
        {
         diagb[i] = 1.0;
        }
       */

      /* Start of main iterative loop */
      while(TNC_TRUE && !IsStopped())
        {
         /* Local minimum test */
         if(dnrm21(n, g) <= pgtol * (fscale))
           {
            /* |PG| == 0.0 => local minimum */
            dcopy1(n, gfull, g);
            project(n, g, pivot);
            if(bool(messages & TNC_MSG_INFO))
              {
               printf("tnc: |pg| = %g -> local minimum", dnrm21(n,g)/(fscale));
              }
            rc = TNC_LOCALMINIMUM;
            break;
           }

         /* Terminate if more than maxnfeval evaluations have been made */
         if(nfeval >= maxnfeval)
           {
            rc = TNC_MAXFUN;
            break;
           }

         /* Rescale function if necessary */
         newscale = dnrm21(n, g);
         if((newscale > DBL_EPSILON) && (fabs(log10(newscale)) > rescale))
           {
            newscale = 1.0 / newscale;

            f *= newscale;
            fscale *= newscale;
            gnorm *= newscale;
            fLastConstraint *= newscale;
            fLastReset *= newscale;
            difnew *= newscale;

            g*=newscale;
            diagb = vector::Ones(diagb.Size());

            upd1 = TNC_TRUE;
            icycle = n - 1;
            newcon = TNC_TRUE;

            if(bool(messages & TNC_MSG_INFO))
              {
               printf("tnc: fscale = %g", fscale);
              }
           }

         dcopy1(n, x, temp);
         project(n, temp, pivot);
         xnorm = dnrm21(n, temp);
         oldnfeval = nfeval;

         /* Compute the new search direction */
         frc = tnc_direction(pk, diagb, x, g, n, maxCGit, maxnfeval, nfeval,
                             upd1, yksk, yrsr, sk, yk, sr, yr,
                             lreset, function, state, xscale, xoffset,
                             fscale, pivot, accuracy, gnorm, xnorm, low,
                             up);

         if(frc == -1)
           {
            rc = TNC_ENOMEM;
            break;
           }

         if(frc)
           {
            rc = TNC_USERABORT;
            break;
           }

         if(!newcon)
           {
            if(!lreset)
              {
               /* Compute the accumulated step and its corresponding gradient
                  difference. */
               dxpy1(n, sk, sr);
               dxpy1(n, yk, yr);
               icycle++;
              }
            else
              {
               /* Initialize the sum of all the changes */
               dcopy1(n, sk, sr);
               dcopy1(n, yk, yr);
               fLastReset = f;
               icycle = 1;
              }
           }

         dcopy1(n, g, oldg);
         oldf = f;
         oldgtp = ddot1(n, pk, g);

         /* Maximum unconstrained step length */
         ustpmax = stepmx / (dnrm21(n, pk) + DBL_EPSILON);

         /* Maximum constrained step length */
         spe = stepMax(ustpmax, n, x, pk, pivot, low, up, xscale, xoffset);

         if(spe > 0.0)
           {
            ENUM_LS_RC lsrc;
            /* Set the initial step length */
            alpha = initialStep(f, _fmin / (fscale), oldgtp, spe);

            /* Perform the linear search */
            lsrc = linearSearch(n, function, state, low, up,
                                xscale, xoffset, fscale, pivot,
                                eta, ftol, spe, pk, x, f, alpha, gfull,
                                maxnfeval, nfeval);

            if(lsrc == LS_ENOMEM)
              {
               rc = TNC_ENOMEM;
               break;
              }

            if(lsrc == LS_USERABORT)
              {
               rc = TNC_USERABORT;
               break;
              }

            if(lsrc == LS_FAIL)
              {
               rc = TNC_LSFAIL;
               break;
              }

            /* If we went up to the maximum unconstrained step, increase it */
            if(alpha >= 0.9 * ustpmax)
              {
               stepmx *= 1e2;
               if(bool(messages & TNC_MSG_INFO))
                 {
                  printf("tnc: stepmx = %g", stepmx);
                 }
              }

            /* If we went up to the maximum constrained step,
               a new constraint was encountered */
            if(alpha - spe >= -DBL_EPSILON * 10.0)
              {
               newcon = TNC_TRUE;
              }
            else
              {
               /* Break if the linear search has failed to find a lower point */
               if(lsrc != LS_OK)
                 {
                  if(lsrc == LS_MAXFUN)
                    {
                     rc = TNC_MAXFUN;
                    }
                  else
                    {
                     rc = TNC_LSFAIL;
                    }
                  break;
                 }
               newcon = TNC_FALSE;
              }
           }
         else
           {
            /* Maximum constrained step == 0.0 => new constraint */
            newcon = TNC_TRUE;
           }

         if(newcon)
           {
            if(!addConstraint(n, x, pk, pivot, low, up, xscale, xoffset))
              {
               if(nfeval == oldnfeval)
                 {
                  rc = TNC_NOPROGRESS;
                  break;
                 }
              }
            fLastConstraint = f;
           }

         (niter)++;

         /* Invoke the callback function */
         if(callback)
           {
            dcopy1(n, x, temp);
            unscalex(n, temp, xscale, xoffset);
            callback(temp, state);
           }

         /* Set up parameters used in convergence and resetting tests */
         difold = difnew;
         difnew = oldf - f;

         /* If this is the first iteration of a new cycle, compute the
            percentage reduction factor for the resetting test */
         if(icycle == 1)
           {
            if(difnew > difold * 2.0)
              {
               epsred += epsred;
              }
            if(difnew < difold * 0.5)
              {
               epsred *= 0.5;
              }
           }

         dcopy1(n, gfull, g);
         scaleg(n, g, xscale, fscale);

         dcopy1(n, g, temp);
         project(n, temp, pivot);
         gnorm = dnrm21(n, temp);

         /* Reset pivot */
         remcon = removeConstraint(oldgtp, gnorm, pgtol * (fscale), f,
                                   fLastConstraint, g, pivot, n);

         /* If a constraint is removed */
         if(remcon)
           {
            /* Recalculate gnorm and reset fLastConstraint */
            dcopy1(n, g, temp);
            project(n, temp, pivot);
            gnorm = dnrm21(n, temp);
            fLastConstraint = f;
           }

         if(!remcon && !newcon)
           {
            /* No constraint removed & no new constraint : tests for convergence */
            if(fabs(difnew) <= ftol * (fscale))
              {
               if(bool(messages & TNC_MSG_INFO))
                 {
                  printf(
                     "tnc: |fn-fn-1] = %g -> convergence",
                     fabs(difnew) / (fscale));
                 }
               rc = TNC_FCONVERGED;
               break;
              }
            if(alpha * dnrm21(n, pk) <= xtol)
              {
               if(bool(messages & TNC_MSG_INFO))
                 {
                  printf(
                     "tnc: |xn-xn-1] = %g -> convergence",
                     alpha * dnrm21(n, pk));
                 }
               rc = TNC_XCONVERGED;
               break;
              }
           }

         project(n, g, pivot);

         if(bool(messages & TNC_MSG_ITER))
           {
            printCurrentIteration(n, f / fscale, gfull,
                                  niter, nfeval, pivot);
           }

         /* Compute the change in the iterates and the corresponding change in the
            gradients */
         if(!newcon)
           {
            yk = g - oldg;
            sk = alpha*pk;


            /* Set up parameters used in updating the preconditioning strategy */
            yksk = ddot1(n, yk, sk);

            if(icycle == (n - 1) || difnew < epsred * (fLastReset - f))
              {
               lreset = TNC_TRUE;
              }
            else
              {
               yrsr = ddot1(n, yr, sr);
               if(yrsr <= 0.0)
                 {
                  lreset = TNC_TRUE;
                 }
               else
                 {
                  lreset = TNC_FALSE;
                 }
              }
            upd1 = TNC_FALSE;
           }
        }

      if(bool(messages & TNC_MSG_ITER))
        {
         printCurrentIteration(n, f / fscale, gfull,
                               niter, nfeval, pivot);
        }

      /* Unscaling */
      unscalex(n, x, xscale, xoffset);
      coercex(n, x, low, up);
      (f) /= fscale;

      return rc;
     

Die Methode minimize_tnc implementiert die iterative Hauptschleife des Algorithmus. Sie berechnet die Suchrichtung, führt eine Linien-Suche durch, um die optimale Schrittlänge zu ermitteln, und verwaltet die Logik zur Verfolgung von Variablen, die ihre Ober- oder Untergrenze erreichen. Das Verfahren umfasst zudem Funktionen zur Projektion von Gradienten unter Berücksichtigung von Nebenbedingungen sowie zur Aktualisierung der Vorkonditionierungsstrategie, um die Konvergenz zu beschleunigen. Diese Schleife wird so lange fortgesetzt, bis ein Konvergenzkriterium erfüllt ist – beispielsweise wenn die Gradienten-Norm unter einen bestimmten Schwellenwert fällt – oder bis die maximale Anzahl an Funktionsauswertungen erreicht ist.

Der letzte Abschnitt des Codes besteht aus verschiedenen Hilfsmethoden, die zur Vektorbearbeitung und zur Verwaltung von Einschränkungen dienen. Methoden wie scalex, unscalex und scaleg steuern die Transformation von Daten zwischen dem Koordinatenraum des Benutzers und dem internen skalierten Raum.

/* Unscale x */
   void              unscalex(int n, vector& x, const vector& xscale,
                              const vector& xoffset)
     {
      x = x*xscale+xoffset;

     }

   /* Scale x */
   void              scalex(int n, vector& x, const vector& xscale,
                            const vector& xoffset)
     {
      x = (x-xoffset)/xscale;
     }

   /* Scale g */
   void              scaleg(int n, vector& g, const vector& xscale, double& fscale)
     {
      g*=xscale*fscale;
     }

Die Methoden project und addConstraint befassen sich mit den Rahmenbedingungen des Problems. Sie sind dafür verantwortlich, Gradientenkomponenten manuell auf Null zu setzen oder Variablen anzupassen, die versuchen, den zulässigen Bereich zu verlassen, um sicherzustellen, dass die Lösung während des gesamten Optimierungsprozesses zulässig bleibt.

void              project(int n, vector& x, const int &pivot[])
     {
      int i;
      for(i = 0; i < n; i++)
        {
         if(pivot[i] != 0)
           {
            x[i] = 0.0;
           }
        }
     }

   /*
    * Set x[i] = 0.0 if direction i is constant
    */
   void              projectConstants(int n, vector& x, const vector& xscale)
     {
      int i;
      for(i = 0; i < n; i++)
        {
         if(xscale[i] == 0.0)
           {
            x[i] = 0.0;
           }
        }
     }



Der Zielfunktions-Wrapper

Die zweite Komponente der Implementierung befasst sich mit der Zielfunktion und deren Ableitungen erster und zweiter Ordnung, die alle in der separaten Header-Datei num_diff.mqh definiert sind. Diese Header-Datei beginnt mit mehreren Enumerationen, die als Konfigurationseinstellungen für den numerischen Differenzierungsprozess dienen. Diese Enums – wie beispielsweise ENUM_SCHEME_DIRECTION, ENUM_DIFF_POINTS und ENUM_HESS_DIFF_POINTS – ermöglichen es dem Benutzer, zwischen einseitigen und zweiseitigen Suchrichtungsschemata zu wählen und die Anzahl der Punkte festzulegen, die zur Schätzung von Gradienten und Hessematrizen verwendet werden. Diese Einstellungen bestimmen letztendlich das Gleichgewicht zwischen Rechengeschwindigkeit und der mathematischen Genauigkeit der Ableitungsnäherungen.

//+------------------------------------------------------------------+
//| directional scheme options                                       |
//+------------------------------------------------------------------+
enum ENUM_SCHEME_DIRECTION
  {
   SCHEME_1=0,//1 sided
   SCHEME_2//2 sided
  };
//+------------------------------------------------------------------+
//| num points of evaluation                                         |
//+------------------------------------------------------------------+
enum ENUM_DIFF_POINTS
  {
   GRAD_POINT_2=0,//2-point
   GRAD_POINT_3,//3-point
   GRAD_POINT_CS,//complex
   GRAD_POINT_CALLABLE//callable
  };
//+------------------------------------------------------------------+
//| num points of evaluation                                         |
//+------------------------------------------------------------------+
enum ENUM_HESS_DIFF_POINTS
  {
   HESS_POINT_2=0,//2-point
   HESS_POINT_3,//3-point
   HESS_POINT_CS,//complex
   HESS_POINT_HESS_STRATEGY,//hessian update strategy
   HESS_POINT_CALLABLE//callable
  };

Anschließend werden im Code die Struktur ObjReturn und die Schnittstelle IObjective eingeführt, die einen standardisierten Rahmen für das Optimierungsproblem bilden. Die Struktur ObjReturn ist ein einfacher Container, der dazu dient, sowohl den Funktionswert als auch den Gradientenvektor gleichzeitig zu speichern, wodurch separate, redundante Funktionsaufrufe überflüssig werden. Die IObjective-Schnittstelle stellt sicher, dass jede dem Solver übergebene Zielfunktion einer einheitlichen Struktur folgt, wobei Implementierungen sowohl für die Berechnung des reinen Zielwerts als auch für die Rückgabe des kombinierten Funktionsgradienten erforderlich sind.

//+------------------------------------------------------------------+
//|struct objective function return                                  |
//+------------------------------------------------------------------+
struct ObjReturn
  {
   double            f;
   vector            g;

                     ObjReturn(void)
     {
      f = double(0);
      g = vector::Zeros(0);
     }
                     ObjReturn(ObjReturn& other)
     {
      f = other.f;
      g = other.g;
     }
   void              operator=(ObjReturn& other)
     {
      f = other.f;
      g = other.g;
     }
  };
//+------------------------------------------------------------------+
//|IObjective provides the base interface for an objective function  |
//|that will be provided to a minimizer routine                      |
//+------------------------------------------------------------------+
interface IObjective
  {
//---the objective function
   vector objective_function(vector& x);
   ObjReturn fun_and_grad(vector& x);
  };

Die Strukturen GradDiffOptions und HessDiffOptions dienen als Konfigurationspakete, in denen die für Finite-Differenzen-Berechnungen erforderlichen Parameter gespeichert sind. Diese Strukturen enthalten die gewählte Schätzmethode, die relativen und absoluten Schrittweiten sowie die Randbedingungen. Durch die Gruppierung dieser Variablen übergibt der Code die Differenzierungseinstellungen durch die Ebenen des Solvers, ohne die Funktionssignaturen zu überladen.

//+------------------------------------------------------------------+
//|differentiation options                                           |
//+------------------------------------------------------------------+
struct GradDiffOptions
  {
   ENUM_DIFF_POINTS  method;
   vector         rel_step;
   vector         abs_step;
   matrix         bounds;

                     GradDiffOptions(void)
     {
      method = WRONG_VALUE;
      rel_step = abs_step = vector::Zeros(0);
      bounds = matrix::Zeros(0,0);
     }
                     GradDiffOptions(ENUM_DIFF_POINTS m, vector& relstep, vector& absstep, matrix& bnds)
     {
      method = m;
      rel_step = relstep;
      abs_step = absstep;
      bounds = bnds;
     }
                     GradDiffOptions(GradDiffOptions& other)
     {
      method = other.method;
      rel_step = other.rel_step;
      abs_step = other.abs_step;
      bounds = other.bounds;
     }
   void              operator=(GradDiffOptions& other)
     {
      method = other.method;
      rel_step = other.rel_step;
      abs_step = other.abs_step;
      bounds = other.bounds;
     }
  };
//+------------------------------------------------------------------+
//|differentiation options                                           |
//+------------------------------------------------------------------+
struct HessDiffOptions
  {
   bool              as_linear_operator;
   ENUM_HESS_DIFF_POINTS method;
   vector         rel_step;
   vector         abs_step;

                     HessDiffOptions(void)
     {
      method = WRONG_VALUE;
      as_linear_operator = false;
      rel_step = abs_step = vector::Zeros(0);
     }
                     HessDiffOptions(ENUM_HESS_DIFF_POINTS m, vector& relstep, vector& absstep, bool aslinearoperator)
     {
      method = m;
      rel_step = relstep;
      abs_step = absstep;
      as_linear_operator = aslinearoperator;
     }
                     HessDiffOptions(HessDiffOptions& other)
     {
      method = other.method;
      rel_step = other.rel_step;
      abs_step = other.abs_step;
      as_linear_operator = other.as_linear_operator;
     }
   void              operator=(HessDiffOptions& other)
     {
      method = other.method;
      rel_step = other.rel_step;
      abs_step = other.abs_step;
      as_linear_operator = other.as_linear_operator;
     }
  }

Die Klasse CFunctor ist der zentrale Verwalter des Optimierungszustands und implementiert die Logik zur Berechnung und Zwischenspeicherung von Funktionswerten, Gradienten und Hessematrizen. Sie enthält interne Methoden, um zu prüfen, ob für die aktuelle Position bereits ein Wert berechnet wurde, wodurch aufwendige, redundante Berechnungen vermieden werden. Diese Klasse verfolgt zudem den bisher besten Funktionswert und die beste Position und dient als Wrapper, der zwischen vom Benutzer bereitgestellten Ableitungen und automatisierten Finite-Differenzen-Schätzungen umschalten kann.

//+---------------------------------------------------------------------------+
//|function objective representing the objective function and its derivatives.|
//+---------------------------------------------------------------------------+
class CFunctor:public IObjective
  {
protected:
   vector         m_xp;
   vector         m_x;
   ulong          m_n;
   matrix         m_H;
   int            m_nfev,m_ngev,m_nhev;
   bool           m_fupdated,m_gupdated,m_hupdated;
   double         m_lowest_f,m_f;
   vector         m_lowest_x,m_g;
   GradDiffOptions m_grad_options;
   HessDiffOptions m_hess_options;
   void              update_fun(void)
     {
      if(!m_fupdated)
        {
         double fx = wrapped_fun(m_x);
         if(fx<m_lowest_f)
           {
            m_lowest_f = fx;
            m_lowest_x = m_x;
           }
         m_f  = fx;
         m_fupdated = true;
        }
     }
   void              update_grad(void)
     {
      if(!m_gupdated)
        {
         if(m_grad_options.method!=GRAD_POINT_CALLABLE)
            update_fun();
         vector ff(1);
         ff[0] = m_f;
         m_g = wrapped_grad(m_x,ff);
         m_gupdated = true;
        }
     }
   void              update_hess(void)
     {
      if(!m_hupdated)
       {
        if(m_hess_options.method != HESS_POINT_CALLABLE)
         {
          update_grad();
          m_H = wrapped_hess(m_x,m_g);
         }
        else
         {
          vector a = vector::Zeros(0);
          m_H = wrapped_hess(m_x,a);
         }
        m_hupdated = true;
       }
     }
   void              update_x(vector& x)
     {
      m_x = x;
      m_fupdated = m_hupdated = m_gupdated = false;
     }
   
public:
                     CFunctor(void)
     {
      m_fupdated = m_hupdated = m_gupdated = false;
      m_lowest_f = DBL_MAX;
      m_lowest_x = m_g = vector::Zeros(0);
      m_H = matrix::Zeros(0,0);
      m_nfev = m_ngev = m_nhev = 0;
      m_grad_options.method = GRAD_POINT_2;
      m_hess_options.method = HESS_POINT_CALLABLE;
      m_hess_options.as_linear_operator = true;
     }
                    ~CFunctor(void)
     {
     }
  void                   setGradOption(ENUM_DIFF_POINTS grad)
     {
      m_grad_options.method = grad;
     }
  void                    setAbsoluteStep(vector& epsilon)
     {
      m_grad_options.abs_step = epsilon;
      m_hess_options.abs_step = epsilon;
     }
   void                   setBounds(matrix& finite_bounds)
     {
      m_grad_options.bounds = finite_bounds;
     }
   void                   setRelativeStep(vector& finite_diff_rel_step)
     {
      m_grad_options.rel_step = finite_diff_rel_step;
      m_hess_options.rel_step = finite_diff_rel_step;
     }
   void                   setHessOption(ENUM_HESS_DIFF_POINTS hess)
     {
      m_hess_options.method = hess;
     }
   bool                   initialize(vector& x)
     {
      m_x = x;
      m_xp = m_x;
      m_n = x.Size();
      if(m_grad_options.method != GRAD_POINT_CALLABLE && m_hess_options.method != HESS_POINT_CALLABLE)
       {
        Print(__FUNCTION__, "Whenever the gradient is estimated via "
                             "finite-differences, it is required that"
                             " the Hessian "
                             "be estimated using one of the "
                             "quasi-Newton strategies.");
        return false;
       }
       
      double check = orig_fun(m_x);
      if(MathClassify(check)!=FP_NORMAL)
       {
        Print(__FUNCTION__," check the implementation of the objective function, currently evaluates to an invalid number ");
        return false;
       }
      
      if(m_grad_options.method == GRAD_POINT_CALLABLE)
       {
        vector a  = grad_fun(m_x);
        if(!a.Size())
         {
          Print(__FUNCTION__, " check the implementation of the overriden gradient function, currently evaluates to an empty vector ");
          return false;
         }
       }
      
      update_fun();
      
      update_grad();
      
      if(m_hess_options.method == HESS_POINT_CALLABLE)
       {
        vector a = vector::Zeros(0);
        m_H = wrapped_hess(x,a);
        m_hupdated = true;
       } 
       
      return true;
     }
   
   double                 wrapped_fun(vector& x)
     {
      m_nfev += 1;
      vector copy = x;
      return orig_fun(copy);
     }
   vector         objective_function(vector& x)
     {
      vector r(1);
      r[0] = wrapped_fun(x);
      return r;
     }
   vector         wrapped_grad(vector& x,vector& f0)
     {
      m_ngev += 1;
      vector copy = x;
      if(m_grad_options.method == GRAD_POINT_CALLABLE)
         return grad_fun(copy);
      IObjective* objective = GetPointer(this);
      matrix ad = approx_derivative(objective,copy,f0,m_grad_options.method,m_grad_options.rel_step,m_grad_options.abs_step,m_grad_options.bounds);
      //Print(__FUNCTION__, " - ", x, " - ", f0, " -> ", ad.Row(0), " | ", m_ngev);
      return ad.Row(0);
     }
   matrix         wrapped_hess(vector& x, vector& f0)
     {
      m_nhev += 1;
      vector copy = x;
      if(m_hess_options.method == HESS_POINT_CALLABLE)
         return hess_fun(copy);
      IObjective* objective = GetPointer(this);
      return approx_derivative(objective,x,f0,m_grad_options.method,m_grad_options.rel_step,m_grad_options.abs_step,m_grad_options.bounds);
     }
   vector  lower_bounds(void)
     {
      return m_grad_options.bounds.Col(0);
     }
   vector  upper_bounds(void)
     {
      return m_grad_options.bounds.Col(1);
     }
   vector  initial_params(void)
     {
      return m_xp;
     }
   virtual double orig_fun(vector& x)
     {
      return double("nan");
     }
   virtual vector grad_fun(vector& x)
     {
      return vector::Zeros(0);
     }
   virtual matrix hess_fun(vector& x)
     {
      return matrix::Zeros(0,0);
     }
   ObjReturn fun_and_grad(vector& x)
     {
      vector dif  = MathAbs(m_x - x);
      if(dif.Sum() >= DBL_EPSILON || dif.HasNan())
       {
         update_x(x);
       }
      update_fun();
      update_grad();
      ObjReturn out;
      out.f = m_f;
      out.g = m_g;
      return out;
     }
  
  }

Der nächste große Abschnitt in num_diff.mqh besteht aus den Hilfsfunktionen eps_for_method und compute_absolute_step, die die optimale Schrittweite für die numerische Differenzierung berechnen. Da Computer nur über eine endliche Genauigkeit verfügen, führt ein zu kleiner Schritt zu Rundungsfehlern, während ein zu großer Schritt zu Abschneidefehlern führt. Diese Funktionen nutzen das Maschinen-Epsilon und die jeweilige Differenzierungsmethode, um einen mathematisch fundierten Abstand zu ermitteln, um den die Variablen verschoben werden müssen, wenn die Steigung der Funktion ermittelt wird.

//+----------------------------------------------------------------------------------+
//|Calculates relative EPS step to use for a given data type and numdiff step method.|
//+----------------------------------------------------------------------------------+
double eps_for_method(ENUM_DIFF_POINTS method)
  {
   switch(method)
     {
      case GRAD_POINT_2:
      case GRAD_POINT_CS:
         return pow(2.220446049250313e-16,0.5);
      case GRAD_POINT_3:
         return pow(2.220446049250313e-16,(1./3.));
     };
   return DBL_EPSILON;
  }
//+---------------------------------------------------------------------------------+
//|Computes an absolute step from a relative step for finite difference calculation.|
//+---------------------------------------------------------------------------------+
vector compute_absolute_step(vector& rel_step,vector& x0, vector& f0, ENUM_DIFF_POINTS method)
  {
   vector signx0 = x0;
   vector abs_step = signx0;
   for(ulong i = 0; i<signx0.Size(); ++i)
     {
      if(x0[i] >= 0.)
         signx0[i]=1.*2-1;
      else
         signx0[i] = 0.0*2-1;
     }
   double rstep = eps_for_method(method);

   if(rel_step.Size()==0)
      for(ulong i = 0; i<abs_step.Size(); ++i)
         abs_step[i] = rstep*signx0[i]*MathMax(1.,fabs(x0[i]));
   else
     {
      abs_step = rstep*signx0*MathAbs(x0);
      vector dx = ((x0+abs_step) - x0);
      for(ulong i = 0; i<abs_step.Size(); ++i)
         if(dx[i] == 0.0)
            abs_step[i] = rstep*signx0[i]*MathMax(1.,fabs(x0[i]));
     }
   return abs_step;
  }

Die Funktion adjust_scheme_to_bounds verhindert, dass numerische Abtastungen die zulässigen Variablengrenzen überschreiten. Befindet sich der Algorithmus in der Nähe einer Grenze, kehrt diese Funktion automatisch die Richtung des Finite-Differenzen-Schritts um oder wechselt von einem zentrierten Schema zu einem einseitigen Schema. Dadurch wird sichergestellt, dass die Zielfunktion niemals an einem ungültigen Punkt ausgewertet wird, was für die Stabilität bei der Optimierung unter Nebenbedingungen von entscheidender Bedeutung ist.

//+------------------------------------------------------------------+
//|Adjust final difference scheme to the presence of bounds.         |
//+------------------------------------------------------------------+
vector adjust_scheme_to_bounds(vector& x0, vector& h, int num_steps,ENUM_SCHEME_DIRECTION scheme, vector& lb, vector& ub, vector &one_sided)
  {
   switch(scheme)
     {
      case SCHEME_1:
         one_sided = vector::Ones(h.Size());
         break;
      case SCHEME_2:
         one_sided = vector::Ones(h.Size());
         h = MathAbs(h);
         break;
     }

   bool all_true = true;
   for(ulong i = 0; i<x0.Size(); ++i)
      if(lb[i] != -double("inf") || ub[i] != double("inf"))
        {
         all_true = false;
         break;
        }
        
   if(all_true)
      return h;

   vector h_total  = h * double(num_steps);
   vector h_adjusted = h;
   vector lower_dist = x0 - lb;
   vector upper_dist = ub - x0;

   int forward,backward,fitting,violated,central, adjusted_central;
   forward = backward = violated = fitting = central  = false;
   double x = 0.;
   double min_dist = 0.;
   switch(scheme)
     {
      case SCHEME_1:
        {
         for(ulong i = 0; i<h.Size(); ++i)
           {
            x = x0[i] + h_total[i];
            violated = int(x<lb[i]|x>ub[i]);
            fitting  = int(fabs(h_total[i])<=MathMax(lower_dist[i],upper_dist[i]));
            if(bool(violated & fitting))
               h_adjusted[i]*=-1.;
            forward = int((upper_dist[i] >= lower_dist[i]) & ~fitting);
            if(forward)
               h_adjusted[i] = upper_dist[i]/double(num_steps);
            backward = int((upper_dist[i]<lower_dist[i]) & ~fitting);
            if(backward)
               h_adjusted[i] = -lower_dist[i]/double(num_steps);
           }
        }
      break;
      case SCHEME_2:
        {
         for(ulong i = 0; i<h.Size(); ++i)
           {
            central = int(((lower_dist[i]>=h_total[i]) & (upper_dist[i] >= h_total[i])));
            forward = int(((upper_dist[i]>=lower_dist[i]) & ~central));
            if(forward)
              {
               h_adjusted[i] = MathMin(h[i],0.5*upper_dist[i]/double(num_steps));
               one_sided[i] = 1.;
              }
            backward = int(((upper_dist[i]<lower_dist[i]) & ~central));
            if(backward)
              {
               h_adjusted[i] = -1.* MathMin(h[i],0.5*lower_dist[i]/double(num_steps));
               one_sided[i] = 1.0;
              }
            min_dist = MathMin(upper_dist[i],lower_dist[i])/double(num_steps);
            adjusted_central = int((~central & (fabs(h_adjusted[i])<=min_dist)));
            if(adjusted_central)
              {
               h_adjusted[i] = min_dist;
               one_sided[i] = 0.;
              }
           }
        }
      break;
     }
   return h_adjusted;
  }

Der letzte Abschnitt enthält die Funktionen dense_difference und approx_derivative, die die numerischen Berechnungen ausführen, die zur Erstellung der Jacobi-Matrix bzw. den Gradientenvektor. erforderlich sind. Die Funktion dense_difference durchläuft jede Dimension des Problems, verändert dabei den Eingabevektor geringfügig und misst die daraus resultierende Änderung der Ausgabe. Die Methode approx_derivative fungiert als übergeordneter Koordinator – sie validiert Eingaben, verwaltet Schrittanpassungen und gibt die endgültige Ableitungsmatrix an den Optimierer zurück.

//+------------------------------------------------------------------+
//|dense difference                                                  |
//+------------------------------------------------------------------+
matrix dense_difference(IObjective* fun, vector& x0, vector& f0, vector& h, vector& use_one_sided, ENUM_DIFF_POINTS method)
  {
   ulong m = f0.Size();
   ulong n = x0.Size();

   matrix j_transposed = matrix::Zeros(n,m);

   vector x1 = x0;
   vector x2 = x0;
   vector df = vector::Zeros(x0.Size());

   for(ulong i = 0; i<h.Size(); ++i)
     {
      double dx = 1.e-12;
      if(method == GRAD_POINT_2)
        {
         x1[i] += h[i];
         dx = x1[i] - x0[i];
         df = fun.objective_function(x1) - f0;
        }
      else
         if(method == GRAD_POINT_3 && use_one_sided[i]!=0.0)
           {
            x1[i] += h[i];
            x2[i] += 2. * h[i];
            dx = x2[i] - x0[i];
            df = -3.0 * f0 + 4 * fun.objective_function(x1) - fun.objective_function(x2);
           }
         else
            if(method == GRAD_POINT_3 && use_one_sided[i]==0.0)
              {
               x1[i] -= h[i];
               x2[i] += h[i];
               dx = x2[i] - x1[i];
               df = fun.objective_function(x2) - fun.objective_function(x1);
              }

      j_transposed.Row(df/dx,i);
      x1[i] = x2[i] = x0[i];
     }
   return j_transposed.Transpose();
  }
//+---------------------------------------------------------------------------------------+
//|Compute finite difference approximation of the derivatives of a vector-valued function.|
//+---------------------------------------------------------------------------------------+
matrix approx_derivative(IObjective* fun,vector& x0,vector &f0,ENUM_DIFF_POINTS method, vector &rel_step,vector &abs_step, matrix& bounds/*sparsity,as linear_operator*/)
  {
  
   if(CheckPointer(fun)==POINTER_INVALID)
    {
     Print(__FUNCTION__, " fun variable is an invalid pointer ");
     return matrix::Zeros(0,0);
    }
    
   vector lb,ub;
   lb = bounds.Col(0);
   ub = bounds.Col(1);

   if(lb.Size()!=x0.Size() ||ub.Size()!=x0.Size())
     {
      Print(__FUNCTION__, " inconsistent shaptes between bounds and x0 ");
      return matrix::Zeros(0,0);
     }

   if(!f0.Size())
      f0 = fun.objective_function(x0);
   for(ulong i = 0; i<x0.Size(); ++i)
      if(x0[i]<lb[i] || x0[i]>ub[i])
        {
         Print(__FUNCTION__, " x0 violates bound constraints ");
         return matrix::Zeros(0,0);
        }

   vector h;
   if(!abs_step.Size())
      h = compute_absolute_step(rel_step,x0,f0,method);
   else
     {
      h = abs_step;
      vector signx0 = vector::Zeros(x0.Size());
      for(ulong i = 0; i<x0.Size(); ++i)
        {
         if(x0[i]>=0.0)
            signx0[i] = 1.0*2.-1.;
         else
            signx0[i] = 0.0*2.-1.;
         if(((x0[i]+h[i]) - x0[i]) == 0.0)
            h[i] = eps_for_method(method)*signx0[i]*MathMax(1.,fabs(x0[i]));
        }
     }

   vector use_one_sided;
   switch(method)
     {
      case GRAD_POINT_2:
         h = adjust_scheme_to_bounds(x0,h,1,SCHEME_1,lb,ub,use_one_sided);
         break;
      case GRAD_POINT_3:
         h = adjust_scheme_to_bounds(x0,h,1,SCHEME_2,lb,ub,use_one_sided);
         break;
      case GRAD_POINT_CS:
         use_one_sided = vector::Zeros(x0.Size());
         break;
     }
   return dense_difference(fun,x0,f0,h,use_one_sided,method);
  } 



Schlussfolgerung

In diesem Artikel wurde die Implementierung des Optimierungsalgorithmus Truncated Newton Conjugate-Gradient in MQL5 vorgestellt. Die bereitgestellte Implementierung unterstützt die Minimierung von Optimierungsproblemen sowohl mit als auch ohne Box-Beschränkungen und bietet damit ein vielseitiges Werkzeug für MQL5-Entwickler. Die Komponente, die die Berechnung der Zielfunktion übernimmt, ist flexibel und unterstützt sowohl explizit definierte Gradientenfunktionen als auch die automatisierte numerische Differenzierung. Wir haben die Implementierung validiert, indem wir sie auf die anspruchsvolle Rosenbrock-Funktion angewandt haben, und damit ihre Fähigkeit unter Beweis gestellt, sich auf komplexen mathematischen Oberflächen zu bewegen. Abschließend haben wir eine praktische Anwendung vorgestellt, bei der der TNC-Optimierer als Alternative zu LBFGS in ein logistisches Regressionsmodell integriert wurde. Der gesamte im Artikel erwähnte Code ist im Anhang beigefügt und unten aufgeführt. Leser erhalten einen sofort einsatzbereiten Optimierer, indem sie einfach die zentralen Header-Dateien tnc.mqh und num_diff.mqh einbinden. 

Datei Beschreibung 
MQL5/experts/RosenBrock.mq5Dies ist der EA, der zur Auswertung der Rosenbrock-Funktion im Strategietester verwendet wird.
MQL5/files/iris.csvDer im Skript LogisticRegression verwendete Iris-Datensatz.
MQL5/include/tncDies ist der Ordner, der die Header-Datei tnc.mqh enthält.
MQL5/include/RegressionDies ist ebenfalls ein Ordner, der die Header-Datei logistic.mqh enthält.
MQL5/include/np.mqhDieser Header enthält verschiedene Hilfsfunktionen zur Bearbeitung von Vektoren und Matrizen. 
MQL5/include/num_diff.mqhDiese Header-Datei enthält Hilfsfunktionen zur Implementierung der Differenzierung. 
MQL5/scripts/LogisticRegression.mq5Das Skript veranschaulicht eine Implementierung der logistischen Regression unter Verwendung des TNC-Solvers. 
MQL5/scripts/TestTNC.mq5Dieses Skript dient zur Bewertung des TNC-Solvers anhand der Rosenbrock-Funktion. 

Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/21589

Beigefügte Dateien |
TNC.zip (63.7 KB)
MQL5-Handelswerkzeuge (Teil 23): Kameragesteuerte 3D-Diagramme mit DirectX-Unterstützung für Einblicke in die Verteilungen MQL5-Handelswerkzeuge (Teil 23): Kameragesteuerte 3D-Diagramme mit DirectX-Unterstützung für Einblicke in die Verteilungen
In diesem Artikel haben wir DirectX-3D in den MQL5-Viewer für die Binomialverteilung integriert. Damit ermöglichen wir umschaltbare 2D-/3D-Modi mit kameragesteuerter Drehung, Zoom und automatischer Anpassung für eine anschaulichere Analyse. Wir stellen 3D-Histogrammbalken, Bodenebenen und Achsen neben der Kurve der theoretischen Wahrscheinlichkeitsmassfunktion dar und behalten dabei 2D-Elemente wie Statistikpanel, Legenden sowie anpassbare Designs, Farbverläufe und Beschriftungen bei.
Trading-Disziplin im Code verankern (Teil 3): Durchsetzung symbolbezogener Handelsgrenzen mit einer Whitelist in MQL5 Trading-Disziplin im Code verankern (Teil 3): Durchsetzung symbolbezogener Handelsgrenzen mit einer Whitelist in MQL5
In diesem Artikel wird ein MQL5-Framework beschrieben, das den Handel auf eine genehmigte Auswahl von Symbolen beschränkt. Die Lösung kombiniert eine gemeinsam genutzte Bibliothek, ein Konfigurations-Dashboard und einen Expert Advisor zur Durchsetzung, der jeden Handel anhand einer Whitelist überprüft und blockierte Versuche protokolliert. Sie enthält voll funktionsfähige Code-Beispiele, eine klare Erläuterung der strukturellen Entwurfsentscheidungen sowie Validierungstests, die eine zuverlässige Symbolfilterung, eine kontrollierte Marktpräsenz und eine transparente Überwachung der Regelumsetzung bestätigen.
Entwicklung eines Toolkits zur Price-Action-Analyse (Teil 63): Automatisierung der Erkennung von steigenden und fallenden Keilen in MQL5 Entwicklung eines Toolkits zur Price-Action-Analyse (Teil 63): Automatisierung der Erkennung von steigenden und fallenden Keilen in MQL5
In diesem Teil der Reihe Entwicklung des Price-Action-Analyse-Toolkits entwickeln wir einen MQL5-Indikator, der steigende und fallende Keilformationen automatisch in Echtzeit erkennt. Das System überprüft Pivot-Strukturen, validiert die Konvergenz der Begrenzungslinien mathematisch, verhindert überlappende Formationen und überwacht die Bedingungen für Ausbruch und Scheitern mit präziser visueller Rückmeldung. Diese auf einer klaren, objektorientierten Architektur basierende Implementierung wandelt die subjektive Erkennung von Keilformationen in eine strukturierte, zustandsabhängige Analysekomponente um, die darauf ausgelegt ist, eine disziplinierte Price-Action-Analyse zu unterstützen.
MQL5-Handelswerkzeuge (Teil 22): Grafische Darstellung des Histogramms und der Wahrscheinlichkeitsfunktion (PMF) von der Binomialverteilung MQL5-Handelswerkzeuge (Teil 22): Grafische Darstellung des Histogramms und der Wahrscheinlichkeitsfunktion (PMF) von der Binomialverteilung
In diesem Artikel wird ein interaktives MQL5-Diagramm zur Binomialverteilung erstellt, das ein Histogramm der simulierten Ergebnisse mit der theoretischen Wahrscheinlichkeitsfunktion (PMF) kombiniert. Es bietet Funktionen für Mittelwert, Standardabweichung, Schiefe, Kurtosis, Perzentile und Konfidenzintervalle, sowie konfigurierbare Farbschemata und Beschriftungen und unterstützt das Ziehen, die Größenänderung und Live-Parameteränderungen. Nutzen Sie es, um bei der Validierung von Handelsstrategien erwartete Gewinn-Trades, wahrscheinliche Drawdown-Phasen und Konfidenzbereiche zu bewerten.