ATcl - Intérprete Tcl para MT4

 

Las vacaciones fueron fructíferas, y me complace presentar ATcl - el intérprete Tcl incorporado para MT4.

La idea del proyecto era (y sigue siendo) hacer la interfaz más conveniente para el intérprete sin una portación directa de las funciones Tcl C Api.

Esto es lo que obtuve (ejemplo de trabajo - un script que guarda una cotización en una base de datos 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
}

En esta etapa es una alfa inactiva o API Preview - evaluación de la usabilidad de la API. Pero como puedes ver las interfaces con el DBMS funcionan, con cierta precisión también funciona EventLoop, es decir, puedes usar comandos asíncronos, I/O y temporizadores.


He adjuntado el archivo con la versión actual, puedes probarla...

La documentación, que estoy actualizando todo lo que puedo y las últimas versiones se pueden encontrar en la página del proyecto http://luxtrade.tk/atcl.

Este hilo está preparado para responder a las preguntas sobre el propio Tcl/Tk y la mencionada biblioteca ATcl. Se agradecerán ideas, comentarios, sugerencias y críticas.

ACTUALIZACIÓN: adjunto el código fuente del script en este tema
ACTUALIZACIÓN: atcl.zip actualizado

Archivos adjuntos:
atcl.zip  9 kb
 

Es interesante.

Estoy, como siempre, comiendo la idea de R. Me parece que usted es la persona adecuada para al menos discutir la idea.


La idea es la siguiente.

Hoy en día es posible arrastrar un archivo exesh, que fue generado por el compilador mcl4/5, a la carta del terminal.

¿Es posible hacer un desarrollo para poder encadenar un script de R al gráfico?

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

Es interesante.

Estoy, como siempre, comiendo la idea de R. Me parece que usted es la persona adecuada para al menos discutir la idea.


La idea es la siguiente.

Hoy en día es posible arrastrar un archivo exesh, que fue generado por el compilador mcl4/5, a la carta del terminal.

¿Y es posible hacer un desarrollo para poder encadenar un script de R al gráfico?

Por cierto, sobre R :-) ¿Has prestado atención al kit de distribución R?

Bueno, hay un directorio library/tcltk, que contiene R tcl incorporado. Cuando un lenguaje/plataforma carece de facilidades, le añaden tcl, también conocido como "Tool Common Language". R carecía de GUI y puso tcl/tk en él (al igual que en Python y Ruby).

Me faltaban muchas funciones en MQL, excepto los gráficos, así que me hice con ATcl.

 

ATcl también puede hacer estadísticas :-) Otro pequeño caso de prueba: calcular los valores estadísticos básicos de un conjunto de datos. El script toma Close, cuántos se darán en el parámetro de entrada

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


Se adjunta el guión. Sólo hay una línea de cálculo :-)

Archivos adjuntos:
 

Y un ejemplo más :-)

Tomemos un zip y calculemos las sumas de comprobación de los archivos almacenados en él. Calculamos MD5 y 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();   
}


Archivos adjuntos:
 

Para empezar, una captura de pantalla, por supuesto :-) Este es un servidor tcp EA en 60 líneas de mql + 60 líneas de tcl


Y MQ4 del propio Asesor Experto:

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

Hasta ahora todo (librería e intérprete) funciona en EAs o scripts. Para los indicadores aún no está listo :-( Hay algún tipo de rareza con las secuencias OnInit/OnDeinit, la carga de DLL y los temporizadores.
Archivos adjuntos:
atcl.zip  9 kb
 

Todo es cada vez más estable, EventLoop funciona con confianza en EAs y scripts (y todavía no en indicadores)

Nueva y útil demostración - Cliente y analizador HTTP asíncrono. El código fuente (mq4 y tcl) es de unas 60 líneas.
La consulta se ejecuta de forma paralela al Asesor Experto y el análisis de HTML se realiza correctamente, a través del analizador 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();
}
Estoy casi satisfecho. No tardaré mucho en poder eliminar de mis falsos MQL todo lo que no esté directamente relacionado con el comercio y añadir las comunicaciones normales con el mundo exterior (bases de datos, correos electrónicos, informes e incluso GUI).

Si no fuera por el problema actual con los indicadores y los temporizadores :-(.
Archivos adjuntos:
 

¿No se puede adjuntar más de un archivo zip aquí?
o hay algún problema con los archivos del mismo nombre...

La versión actual de atcl todavía no está conectada :-) El sitio tiene un glitchy o algo así... por ahora puedes conseguirlo desde la página del proyecto.
Cuando se pasen los glitches, lo adjuntaré aquí

 
Maxim Kuznetsov:
En un poco más de tiempo, podría eliminar todo lo que no está directamente relacionado con el comercio de sus creaciones MQL y añadir las comunicaciones normales con el mundo exterior (bases de datos, correos electrónicos, informes e incluso una interfaz gráfica de usuario).

¿Y qué le impide salir al mundo exterior a través de las tuberías?
El resultado es el mismo: prácticamente puedes olvidarte de mql - escribir un servidor una vez y ya está.

 
Alexey Oreshkin:

¿Qué le impide salir al exterior a través de tuberías?
el resultado es el mismo: prácticamente puedes olvidarte de µl: escribe un servidor una vez y ya está.

¿pero quién controlará los pips, implementará el protocolo y analizará los resultados? es largo, lento y sólo por sondeo.

y hay un nivel de integración completamente diferente - los datos pueden ser fácilmente transferidos entre tcl y mql - están sentados en una sola memoria, en un solo proceso. En teoría (no he comprobado el trabajo, pero debería funcionar) y con cierta precisión se pueden enlazar variables.
Resulta que todas las características y bibliotecas que están en tcl pero que faltan en mql están fácilmente disponibles.
 

Los INDICADORES están derrotados (casi) :-) Es decir, ATcl ahora funciona en indicadores, incluyendo el malogrado EventLoop.

En la captura de pantalla - el mismo programa que la última vez (cliente http asíncrono), pero ahora en el indicador:


Por supuesto, el algoritmo de inicialización de la DLL no es el mismo :-)

Podemos decir que la API-Preview está casi terminada, sólo hay que limpiar el código fuente y la documentación, y entonces podremos anunciar la versión Beta

Adjunta el pavo y la biblioteca. Y lo voy a celebrar con cerveza :-)

Archivos adjuntos: