ATcl - Tcl-Interpreter für MT4

 

Die Ferien waren fruchtbar, und ich freue mich, ATcl - einen eingebauten Tcl-Interpreter für MT4 - vorstellen zu können.

Die Idee des Projekts war (und ist immer noch), die bequemste Schnittstelle zum Interpreter zu schaffen, ohne eine direkte Portierung von Tcl C Api Funktionen.

So sieht es aus (Arbeitsbeispiel - ein Skript, das ein Zitat in einer SQLite-Datenbank speichert):

//+------------------------------------------------------------------+
//|                                                  atcl_sqlite.mq4 |
//|                                                Maxim A.Kuznetsov |
//|                                                      luxtrade.tk |
//+------------------------------------------------------------------+
#property copyright "Maxim A.Kuznetsov"
#property link      "luxtrade.tk"
#property version   "1.00"
#property strict
#property script_show_inputs
#include "ATcl.mqh"

const string Shema = 
"CREATE TABLE IF NOT EXISTS Bar ("
"   symbol TEXT,"
"   period TEXT,"
"   time INTEGER NOT NULL,"
"   open REAL NOT NULL, "
"   high REAL NOT NULL, "
"   low REAL NOT NULL,  "
"   close REAL NOT NULL,"
"   volume INTEGER NOT NULL, "
"   PRIMARY KEY(symbol,period,time)"
" )";
void OnStart()
{
   ATcl_OnInit(); // включить ATcl
   
   ATcl *tcl=new ATcl; // создать интерпретатор
   if (tcl==NULL || !tcl.Ready()) {
      Alert("Ошибка при создании интерпретатора");
      ATcl_OnDeinit();
      return;
   }
   int ok=false;
   do { 
      Print("Берём встроенный SQLite");
      if (tcl.Eval("package require tdbc::sqlite3")!=TCL_OK) break;
      Print("Считаем путь к базе");
      tcl.Set("dbname",tcl.Obj("bar.db3"));
      tcl.Set("datadir",tcl.Obj(TerminalInfoString(TERMINAL_DATA_PATH)));
      if (tcl.Eval("set fullPath [ file join $datadir MQL4 Files $dbname ]")!=TCL_OK) break;
      PrintFormat("Открываем базу %s",tcl.String(tcl.Get("fullPath")));
      if (tcl.Eval("tdbc::sqlite3::connection create db $fullPath")!=TCL_OK) break;
      Print("Задаём схему");
      if (tcl.Eval(StringFormat("db allrows {%s}",Shema))!=TCL_OK) break;
      Print("Готовим стейтмент");
      if (tcl.Eval("set stmt [ db prepare {REPLACE INTO Bar (symbol,period,time,open,high,low,close,volume) VALUES (:symbol,:period,:t,:o,:h,:l,:c,:v)} ]")!=TCL_OK) break;
      Print("Делаем переменные");
      tcl.Set("symbol",tcl.Obj(Symbol()));
      tcl.Set("period",tcl.Obj(EnumToString((ENUM_TIMEFRAMES)Period())) );
      tcl.Set("time",tcl.Obj(Time));
      tcl.Set("open",tcl.Obj(Open));
      tcl.Set("high",tcl.Obj(High));
      tcl.Set("low",tcl.Obj(Low));
      tcl.Set("close",tcl.Obj(Close));
      tcl.Set("volume",tcl.Obj(Volume));
      tcl.Set("n",tcl.Obj((long)0));
      Print("Запускаем стейтмент по массивам");
      // скрипт как объект, чтобы скомпилялся
      Tcl_Obj f=tcl.Obj("foreach t $time o $open h $high l $low c $close v $volume { $stmt execute ; incr n ; if {$n==100} break }");
      tcl.Ref(f);
      if (tcl.Eval(f)!=TCL_OK) break;
      tcl.Unref(f);
      Print("Удаляем стейтмент");
      tcl.Eval("$stmt close");
      Print("Закрываем базу");
      tcl.Eval("$db close");
      ok=true;
   }while(false);
   if (!ok) {
      PrintFormat("Что-то пошло не так: %s",tcl.StringResult());
   }
   delete tcl;          // удалить интерпретатор
   ATcl_OnDeinit();  // выключить ATcl
}

In diesem Stadium handelt es sich um eine ruhende Alpha- oder API-Vorschau - Bewertung der Benutzerfreundlichkeit der API. Aber wie Sie sehen können, funktionieren die Schnittstellen zum DBMS, mit einiger Genauigkeit auch EventLoop, d.h. Sie können asynchrone Befehle, I/O und Timer verwenden.


Ich habe das Archiv mit der aktuellen Version angehängt, Sie können es ausprobieren...

Die Dokumentation, die ich so oft wie möglich aktualisiere, und die neuesten Versionen sind auf der Projektseite http://luxtrade.tk/atcl zu finden .

Dieser Thread ist dazu da, Fragen zu Tcl/Tk selbst und der erwähnten ATcl-Bibliothek zu beantworten. Ideen, Kommentare, Vorschläge und Kritik sind erwünscht.

UPDATE: Quellcode des Skripts in diesem Thema angehängt
UPDATE: atcl.zip aktualisiert

Dateien:
atcl.zip  9 kb
 

Interessantes Zeug.

Ich nasche wie immer an der Idee von R. Ich denke, dass Sie die richtige Person sind, um diese Idee zumindest zu diskutieren.


Die Idee ist die folgende.

Heute ist es möglich, eine exesh-Datei, die vom mcl4/5-Compiler erzeugt wurde, in die Terminalkarte zu ziehen.

Ist es möglich, eine Entwicklung zu machen, um ein R-Skript mit dem Diagramm zu verbinden?

 
СанСаныч Фоменко:

Interessantes Zeug.

Ich nasche wie immer an der Idee von R. Ich denke, dass Sie die richtige Person sind, um die Idee zumindest zu diskutieren.


Die Idee ist die folgende.

Heute ist es möglich, eine exesh-Datei, die vom mcl4/5-Compiler erzeugt wurde, in die Terminalkarte zu ziehen.

Und ist es möglich, eine Entwicklung zu machen, um ein R-Skript mit dem Diagramm zu verbinden?

Übrigens, über R :-) Haben Sie dem R-Verteiler-Bausatz Ihre Aufmerksamkeit geschenkt?

Nun, es gibt ein Verzeichnis library/tcltk, das eingebautes R tcl enthält. Wenn es einer Sprache/Plattform an Möglichkeiten mangelt, fügen sie ihr tcl, auch bekannt als "Tool Common Language", hinzu. R fehlte eine GUI und tcl/tk wurde in sie integriert (genau wie in Python und Ruby).

In MQL fehlten mir viele Funktionen, außer den Graphen, also habe ich ATcl gekauft.

 

ATcl kann auch Statistiken erstellen :-) Ein weiterer kleiner Testfall - die Berechnung grundlegender statistischer Werte für einen Datensatz. Das Skript nimmt Close, wie viele wird im Eingabeparameter angegeben

#property copyright "Maxim A.Kuznetsov"
#property link      "luxtrade.tk"
#property version   "1.00"
#property strict
#property script_show_inputs
//--- input parameters
input int      DEPTH=200;

#include "ATcl.mqh"
const string NAMES[]={
   "mean", "minimum", "maximum", "number of data", "sample standard deviation", "sample variance", "population standard deviation","population variance"
};
void OnStart()
{
   ATcl_OnInit();
   ATcl *tcl=new ATcl;
   if (tcl==NULL || !tcl.Ready()) {
      ATcl_OnDeinit();
      Alert("Ошибка при создании интерпретатора");
   }
   bool ok=false;
   do {
      if (tcl.Eval("package require math::statistics")!=TCL_OK) break;
      tcl.Set("data",tcl.Obj(Close,0,DEPTH));
      if (tcl.Eval("math::statistics::basic-stats $data")!=TCL_OK) break;
      Tcl_Obj stats=tcl.Result();
      tcl.Ref(stats);
      int total=tcl.Count(stats);
      for(int i=0;i<total;i++) {
         PrintFormat("stats %d \"%s\" = %s",i,(i<ArraySize(NAMES)?NAMES[i]:"??"),tcl.String(stats,i));
      }
      tcl.Unref(stats);
      ok=true;
   } while(false);
   if (!ok) {
      PrintFormat("Что-то пошло не так : %s",tcl.StringResult());
   }
   delete tcl;
   ATcl_OnDeinit();
}


Im Anhang finden Sie das Skript. Es gibt nur eine Rechenzeile :-)

Dateien:
 

Und noch ein Beispiel :-)

Nehmen wir ein Zip-Archiv und berechnen wir die Prüfsummen der darin gespeicherten Dateien. Wir berechnen MD5 und SHA256

#property copyright "Maxim A.Kuznetsov"
#property link      "luxtrade.tk"
#property version   "1.00"
#property strict
#property script_show_inputs
//--- input parameters
input string   ZIP="MQL4\\Scripts\\atcl.zip";
#include "ATcl.mqh"
void OnStart()
{
   ATcl_OnInit();
   ATcl *tcl=new ATcl;
   if (tcl==NULL || !tcl.Ready()) {
      ATcl_OnDeinit();
      Alert("Ошибка при создании интерпретатора");
   }
   bool ok=false;
   do {
      if (tcl.Eval("package require vfs::zip")!=TCL_OK) break;
      if (tcl.Eval("package require md5")!=TCL_OK) break;
      if (tcl.Eval("package require sha256")!=TCL_OK) break;
      tcl.Set("archive",tcl.Obj(ZIP));
      if (tcl.Eval("set z [ vfs::zip::Mount $archive $archive ]")!=TCL_OK) break;
      if (tcl.Eval("set list [ glob -directory $archive -nocomplain *.* ]")!=TCL_OK) break;
      Tcl_Obj list=tcl.Result();
      tcl.Ref(list);
      int total=tcl.Count(list);
      // создадим новую процедуру 
      if (tcl.Eval("proc content { name } { set f [ open $name rb ] ; set ret [ read $f ] ; close $f ; set ret }")!=TCL_OK) break;
      for(int i=0;i<total;i++) {
         string fileName=tcl.String(list,i);
         tcl.Set("fileName",tcl.Obj(fileName));    
         PrintFormat("%s",fileName);
         if (tcl.Eval("set content [ content $fileName ]")!=TCL_OK) {
            PrintFormat("Не удалось прочесть файл %s:%s",fileName,tcl.StringResult());
            continue;
         }
         string sha256=tcl.StringEval("sha2::sha256 -hex -- $content");
         PrintFormat("  sha256: %s",sha256);
         string md5=tcl.StringEval("md5::md5 -hex -- $content");
         PrintFormat("  md5: %s",md5);
      }
      tcl.Unref(list);
      tcl.Eval("vfs::zip::Unmount $z $archive");
      ok=true;
   } while(false);
   if (!ok) {
      PrintFormat("Что-то пошло не так : %s",tcl.StringResult());
   }
   delete tcl;
   ATcl_OnDeinit();   
}


Dateien:
 

Für den Anfang natürlich ein Bildschirmfoto :-) Dies ist ein EA tcp-server in 60 Zeilen mql + 60 Zeilen tcl


Und MQ4 des Expert Advisors selbst:

#property copyright "Maxim A.Kuznetsov"
#property link      "luxtrade.tk"
#property version   "1.00"
#property strict

//--- input parameters
input ushort   PORT=8000;

#include "ATcl.mqh"
ATcl *tcl=NULL;   // интерпретатор
Tcl_Obj StartServer=0,StopServer=0,SendMsg=0,SymName=0,PortNum=0; // объекты Tcl - имена команд и имя символа
int OnInit()
{
   ATcl_OnInit(); // включить ATcl
   tcl=new ATcl;  // создать интерпретатор
   if (tcl==NULL || !tcl.Ready()) {
      return INIT_FAILED;
   }
   // прочтём исходник c командами
   if (tcl.Eval("source ./MQL4/Experts/atcl_tcpserv.tcl")!=TCL_OK) {
      PrintFormat("Error in Source:%s",tcl.StringResult());
      return INIT_FAILED;
   }
   StartServer=tcl.Ref(tcl.Obj("StartServer")); // команда запуска сервера
   StopServer=tcl.Ref(tcl.Obj("StopServer"));   // остановка сервера
   SendMsg=tcl.Ref(tcl.Obj("SendMsg"));         // рассылка сообщения всем клиентам
   SymName=tcl.Ref(tcl.Obj(Symbol()));          // запомним имя текущего символа
   PortNum=tcl.Ref(tcl.Obj((long)PORT));        // и номер порта
   /// запускаем сервер
   if (tcl.Call(StartServer,PortNum)!=TCL_OK) {
      PrintFormat("Error on StartServer:%s",tcl.StringResult());
      return INIT_FAILED;
   }
   EventSetTimer(2);
   Print("Server started");
   return(INIT_SUCCEEDED);
}
void OnDeinit(const int reason) 
{
   if (tcl!=NULL) {
      if (tcl.Ready()) {
         // остановить сервер
         tcl.Call(StopServer);
      }
      // освободить все объекты
      tcl.Unref(StartServer);
      tcl.Unref(StopServer);
      tcl.Unref(SendMsg);
      tcl.Unref(SymName);
      tcl.Unref(PortNum);
      // удалить интерпретатор
      delete tcl;
   }
   ATcl_OnDeinit(reason);
}
void OnTick()
{
   MqlTick tick;
   tcl.Update();
   if (SymbolInfoTick(Symbol(),tick)!=0) {
      Tcl_Obj message=tcl.Obj();
      tcl.Ref(message);
      tcl.Append(message,SymName);
      tcl.Append(message,tcl.Obj((long)tick.time));
      tcl.Append(message,tcl.Obj((long)tick.time_msc));
      tcl.Append(message,tcl.Obj((double)tick.bid));
      tcl.Append(message,tcl.Obj((double)tick.ask));
      tcl.Append(message,tcl.Obj((long)tick.volume));
      if (tcl.Call(SendMsg,message)!=TCL_OK) {
         PrintFormat("Error in SendMsg:%s",tcl.StringResult());
      }
      tcl.Unref(message);
   }
}
void OnTimer() {
   tcl.Update();
}
void OnChartEvent(const int id,         // идентификатор события   
                  const long& lparam,   // параметр события типа long 
                  const double& dparam, // параметр события типа double 
                  const string& sparam  // параметр события типа string
   )
{
   tcl.Update();
}

Bislang funktioniert alles (Bibliothek und Interpreter) in EAs oder Skripten. Für Indikatoren ist es noch nicht fertig :-( Es gibt noch einige Ungereimtheiten mit OnInit/OnDeinit Sequenzen, DLL Laden und Timern.
Dateien:
atcl.zip  9 kb
 

Alles wird immer stabiler, EventLoop funktioniert zuverlässig in EAs und Skripten (und noch nicht in Indikatoren)

Neue, nützliche Demo - Asynchroner HTTP-Client und Parser. Der Quellcode (mq4 und tcl) umfasst etwa 60 Zeilen.
Die Abfrage wird parallel zum Expert Advisor ausgeführt, und das HTML-Parsing wird über den DOM-Parser korrekt durchgeführt.


input int      EVERY_MINUTS=15;

#include "ATcl.mqh"
ATcl *tcl=NULL;
Tcl_Obj StartClient=0,StopClient=0,report=0,minutes=0;
int OnInit()
{
   ATcl_OnInit(); // включить ATcl
   tcl=new ATcl;  // создать интерпретатор
   if (tcl==NULL || !tcl.Ready()) {
      return INIT_FAILED;
   }
   // прочтём исходник c командами
   if (tcl.Eval("source ./MQL4/Experts/atcl_httpclient.tcl")!=TCL_OK) {
      PrintFormat("Error in Source:%s",tcl.StringResult());
      return INIT_FAILED;
   }
   StartClient=tcl.Ref(tcl.Obj("StartClient"));
   StopClient=tcl.Ref(tcl.Obj("StopClient"));
   report=tcl.Ref(tcl.Obj("report"));
   minutes=tcl.Ref(tcl.Obj((long)EVERY_MINUTS));
   if (tcl.Call(StartClient,minutes)!=TCL_OK) {
      Print("call failed");
      return INIT_FAILED;
   }
   EventSetTimer(1);
   return(INIT_SUCCEEDED);
}
void OnDeinit(const int reason) 
{
   if (tcl!=NULL) {
      if (tcl.Ready()) {
         tcl.Call(StopClient);
      }
      tcl.Unref(StartClient);
      tcl.Unref(StopClient);
      tcl.Unref(report);
      tcl.Unref(minutes);
      delete tcl;
   }
   ATcl_OnDeinit(reason);
}
void OnTimer()
{
   tcl.Update();
   if (tcl.IsSet(report)) {
      string comment=tcl.String(tcl.Get(report));
      tcl.Unset(report);
      Comment(comment);
   }   
}

void OnTick() {
   tcl.Update();
}
Ich bin fast zufrieden. Es wird nicht lange dauern, bis ich in der Lage sein werde, aus meinen MQL-Fälschungen alles zu entfernen, was nicht direkt mit dem Handel zusammenhängt, und die normale Kommunikation mit der Außenwelt hinzuzufügen (Datenbanken, E-Mails, Berichte und sogar GUI).

Wenn da nur nicht das aktuelle Problem mit den Indikatoren und Timern wäre :-(.
Dateien:
 

Sie können hier nicht mehr als eine Zip-Datei anhängen?
oder gibt es ein Problem mit den gleichnamigen Dateien...

Die aktuelle atcl-Version ist immer noch NICHT angeschlossen :-) Die Website ist fehlerhaft oder etwas anderes... für jetzt können Sie es von der Projektseite bekommen.
Wenn die Probleme vorbei sind, werde ich es hier anhängen

 
Maxim Kuznetsov:
In kurzer Zeit könnten Sie alles aus Ihren MQL-Kreationen entfernen, was nicht direkt mit dem Handel zu tun hat, und die normale Kommunikation mit der Außenwelt (Datenbanken, E-Mails, Berichte und sogar eine grafische Benutzeroberfläche) hinzufügen.

Und was hindert Sie daran, über Rohrleitungen in die Außenwelt zu gehen?
Das Ergebnis ist das gleiche: Sie können mql praktisch vergessen - schreiben Sie einmal einen Server und das war's.

 
Alexey Oreshkin:

Was hindert Sie daran, über Pipelines in die Außenwelt zu gehen?
das Ergebnis ist dasselbe: Sie können µl praktisch vergessen - schreiben Sie einmal einen Server und das war's.

Aber wer wird die Pips kontrollieren, das Protokoll umsetzen und die Ergebnisse auswerten? Das ist langwierig, langsam und erfolgt nur durch Umfragen.

und es gibt eine ganz andere Ebene der Integration - Daten können leicht zwischen tcl und mql übertragen werden - sie sitzen direkt in einem Speicher, in einem einzigen Prozess. Theoretisch (ich habe die Arbeit nicht überprüft, aber es sollte funktionieren) und mit einiger Genauigkeit können Sie Variablen verknüpfen.
Es stellt sich heraus, dass alle Funktionen und Bibliotheken, die in tcl vorhanden sind, aber in mql fehlen, leicht verfügbar sind.
 

INDICATORS sind (fast) besiegt :-) Das heißt, ATcl funktioniert jetzt in Indikatoren, einschließlich des unseligen EventLoop.

Auf dem Screenshot - das gleiche Programm wie beim letzten Mal (asynchroner http-Client), aber jetzt als Indikator:


Natürlich ist der Algorithmus zur Initialisierung der DLL nicht derselbe :-)

Wir können sagen, dass die API-Vorschau fast fertig ist, wir müssen nur noch den Quellcode und die Dokumentation aufräumen, und dann können wir die Beta-Version ankündigen

Truthahn und Bibliothek anhängen. Und das werde ich mit Bier feiern :-)

Dateien:
Grund der Beschwerde: