ATcl - Interprète Tcl pour MT4

 

Les vacances ont été fructueuses, et j'ai le plaisir de vous présenter ATcl - l'interprète Tcl intégré pour MT4.

L'idée du projet était (et est toujours) de créer l'interface la plus pratique pour l'interpréteur sans un portage direct des fonctions de l'Api C de Tcl.

Voici ce que j'ai obtenu (exemple de travail - un script qui enregistre une citation dans une base de données SQLite) :

//+------------------------------------------------------------------+
//|                                                  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
}

À ce stade, il s'agit d'une alpha ou "API Preview" - évaluation de la convivialité de l'API. Mais comme vous pouvez le constater, les interfaces avec le SGBD fonctionnent, avec une certaine précision, même EventLoop fonctionne, c'est-à-dire que vous pouvez utiliser des commandes asynchrones, des E/S et des temporisateurs.


J'ai joint l'archive avec la version actuelle, vous pouvez l'essayer...

La documentation, que je mets à jour autant que je peux, et les dernières versions peuvent être trouvées sur la page du projet http://luxtrade.tk/atcl.

Dans ce fil de discussion, je suis prêt à répondre aux questions concernant Tcl/Tk lui-même et la bibliothèque ATcl mentionnée. Les idées, commentaires, suggestions et critiques seront appréciés.

UPDATE : joint le code source du script dans ce sujet
UPDATE : mise à jour de atcl.zip

Dossiers :
atcl.zip  9 kb
 

C'est intéressant.

Comme toujours, je me régale de l'idée de R. Il me semble que vous êtes la bonne personne avec qui discuter au moins de cette idée.


L'idée est la suivante.

Aujourd'hui, il est possible de faire glisser un fichier exesh, qui a été généré par le compilateur mcl4/5, vers le graphique du terminal.

Est-il possible de faire un développement pour pouvoir enchaîner un script R sur le graphique ?

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

C'est intéressant.

Comme toujours, je me régale à l'idée de R. Il me semble que vous êtes la bonne personne avec qui discuter au moins de cette idée.


L'idée est la suivante.

Aujourd'hui, il est possible de faire glisser un fichier exesh, qui a été généré par le compilateur mcl4/5, vers le graphique du terminal.

Et est-il possible de faire un développement pour pouvoir enchaîner un script R sur le graphique ?

Au fait, à propos de R :-) Avez-vous porté votre attention sur le kit de distribution R ?

Eh bien, il y a un répertoire library/tcltk, qui contient le R tcl intégré. Lorsqu'un langage/une plateforme manque de facilités, on lui ajoute le tcl, alias "Tool Common Language". R n'a pas d'interface graphique et met tcl/tk dedans (tout comme en Python et Ruby).

Il me manquait de nombreuses fonctionnalités dans MQL, à l'exception des graphiques, alors je me suis procuré ATcl.

 

ATcl peut aussi faire des statistiques :-) Un autre petit cas de test - le calcul des valeurs statistiques de base pour un ensemble de données. Le script prend Close, dont le nombre sera donné dans le paramètre d'entrée.

#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();
}


Vous trouverez ci-joint le script. Il n'y a qu'une seule ligne de calcul :-)

Dossiers :
 

Et un autre exemple :-)

Prenons un zip et calculons les sommes de contrôle des fichiers qu'il contient. Nous calculons MD5 et 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();   
}


Dossiers :
 

En guise d'avant-goût, bien sûr, une capture d'écran :-) Il s'agit d'un serveur tcp EA en 60 lignes de mql + 60 lignes de tcl.


Et MQ4 du conseiller expert lui-même :

#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();
}

Jusqu'à présent, tout (bibliothèque et interpréteur) fonctionne dans les EA ou les scripts. Pour les indicateurs, ce n'est pas encore prêt :-( Il y a une sorte de bizarrerie avec les séquences OnInit/OnDeinit, le chargement des DLL et les timers.
Dossiers :
atcl.zip  9 kb
 

Tout devient de plus en plus stable, EventLoop fonctionne en toute confiance dans les EA et les scripts (mais pas encore dans les indicateurs).

Nouvelle démo utile - Client HTTP asynchrone et analyseur syntaxique. Le code source (mq4 et tcl) est d'environ 60 lignes.
La requête est exécutée parallèlement à l'Expert Advisor et l'analyse html est effectuée correctement, via l'analyseur DOM.


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();
}
Je suis presque satisfait. Il ne faudra pas longtemps avant que je puisse retirer de mes faux MQL tout ce qui n'est pas directement lié au trading et ajouter les communications normales avec le monde extérieur (bases de données, emails, rapports et même GUI).

Si ce n'est pour le problème actuel des indicateurs et des minuteurs :-(.
Dossiers :
 

Vous ne pouvez pas joindre plus d'un fichier zip ici ?
ou y a-t-il un problème avec les fichiers de même nom...

La version actuelle d'atcl n'est toujours pas jointe :-) Le site est en panne ou quelque chose d'autre... pour l'instant, vous pouvez l'obtenir à partir de la page du projet.
Lorsque les problèmes seront résolus, je l'attacherai ici.

 
Maxim Kuznetsov:
Dans un peu plus de temps, vous pourriez retirer de vos créations MQL tout ce qui n'est pas directement lié au trading et ajouter des communications normales avec le monde extérieur (bases de données, e-mails, rapports et même une interface graphique).

Et qu'est-ce qui vous empêche d'aller vers le monde extérieur par la tuyauterie ?
Le résultat est le même : vous pouvez pratiquement oublier mql - écrire un serveur une fois et c'est tout.

 
Alexey Oreshkin:

Qu'est-ce qui vous empêche d'aller vers le monde extérieur via les pipelines ?
le résultat est le même : vous pouvez pratiquement oublier le µl - écrire un serveur une fois et c'est tout.

mais qui va contrôler les pips, mettre en œuvre le protocole et analyser les résultats ? c'est long, lent et uniquement par sondage.

et il y a un niveau d'intégration complètement différent - les données peuvent facilement être transférées entre tcl et mql - elles se trouvent dans une seule mémoire, dans un seul processus. En théorie (je n'ai pas vérifié le travail, mais cela devrait fonctionner) et avec une certaine précision, vous pouvez relier des variables.
Il s'avère que toutes les fonctionnalités et les bibliothèques qui sont dans tcl mais qui manquent dans mql sont facilement disponibles.
 

Les INDICATEURS sont vaincus (presque) :-) En d'autres termes, ATcl fonctionne maintenant dans les indicateurs, y compris l'infortuné EventLoop.

Sur la capture d'écran - le même programme que la dernière fois (client http asynchrone), mais maintenant en indicateur :


Bien sûr, l'algorithme d'initialisation de la DLL n'est pas le même :-)

Nous pouvons dire que la révision de l'API est presque terminée, il ne reste plus qu'à nettoyer le code source et la documentation, et nous pourrons alors annoncer la version bêta.

Attachez la dinde et la bibliothèque. Et je vais fêter ça avec de la bière :-)

Dossiers :