ATcl - MT4用Tclインタプリタ

 

この連休は実り多く、MT4用の組み込みTclインタプリタであるATclを発表できることを嬉しく思います。

このプロジェクトのアイデアは、Tcl C API関数を直接移植することなく、インタープリタへの最も便利なインタフェースを作ることでした(現在もそうです)。

これは私が得たものです(作業例 - 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
}

この段階では、休眠状態のアルファ版、あるいはAPIプレビュー(APIの使い勝手を評価するもの)である。しかし、あなたが見ることができるように、DBMSへのインタフェースは、いくつかの精度で、イベントループも動作します、つまり、あなたは非同期コマンド、I / O、およびタイマーを使用することができます。


現在のバージョンのアーカイブを添付しますので、試してみてください...。

ドキュメントは、できる限り更新していますし、最新版はプロジェクトページhttp://luxtrade.tk/atcl で見ることができます。

このスレッドでは、Tcl/Tkそのものや、言及されているATclライブラリに関する質問に答える用意があります。アイデア、コメント、提案、批評をお待ちしています。

UPDATE: このトピックにスクリプトのソースコードを添付しました。
UPDATE: atcl.zipをアップデートしました。

ファイル:
atcl.zip  9 kb
 

面白いものですね。

私はいつものように、Rのアイデアをノセています。少なくとも相談相手としては適任だと思われます。


考え方は次のとおりです。

現在では、mcl4/5コンパイラで生成されたexeshファイルをターミナルチャートにドラッグすることが可能です。

チャートにRスクリプトを連鎖させることができるような開発は可能でしょうか?

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

面白いものですね。

私はいつものように、Rのアイデアをノセて います。少なくとも相談相手としては適任だと思われます。


考え方は次のとおりです。

現在では、mcl4/5コンパイラで生成されたexeshファイルをターミナルチャートにドラッグすることが可能です。

また、チャートにRスクリプトを連鎖させられるような展開は可能でしょうか?

ところで、Rについてですが :-)Rディストリビューション・キットに注目しましたか?

さて、Rのビルトインtclを含むディレクトリlibrary/tcltkがあります。ある言語・プラットフォームに設備が足りない場合、tcl、別名「ツール共通言語」を追加するのです。RのGUIを欠き、そこにtcl/tkを入れる(PythonやRubyと同じ)。

MQLではグラフ以外の多くの機能が足りなかったので、ATclを手に入れました。

 

ATclは統計もできる :-)もう一つの小さなテストケース - データセットの基本的な統計値を計算する。スクリプトはCloseを取るが、その数は入力パラメータに与えられる

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


スクリプトを添付します。計算の線は1本だけです :-)

ファイル:
 

そして、もうひとつの例 :-)

ZIPを取り出し、その中に格納されているファイルのチェックサムを計算してみましょう。MD5と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();   
}


ファイル:
 

手始めに、もちろんスクリーンショットを :-)60行のmql + 60行のtclで構成されるEA tcp-serverです。


そしてExpert Advisor自体のMQ4。

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

今のところ、EAやスクリプトですべて(ライブラリやインタプリタ)が動作しています。OnInit/OnDeinitシーケンス、DLLロード、タイマーに何らかの不都合があるようです。
ファイル:
atcl.zip  9 kb
 

EAやスクリプトでEventLoopが確実に動作するようになり、すべてが安定してきた(インジケーターではまだだが)

新しい、便利なデモ - 非同期HTTP-クライアントとパーサー。ソースコード(mq4とtcl)は約60行です。
クエリーはExpert Advisorと並行して実行され、DOMパーサーによってhtmlの解析が正しく行われます。


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();
}
ほぼ満足しています。私のMQLの偽物から、取引に直接関係のないものをすべて取り除き、外部との通常のコミュニケーション(データベース、メール、レポート、さらにはGUI)を追加できるようになるのに、そう時間はかからないでしょう。

現在のインジケーターとタイマーの問題さえなければ:-(笑)。
ファイル:
 

ここに複数のzipファイルを添付することはできません ?
それとも、同じ名前のファイルに問題があるのでしょうか...。

現在のatclのバージョンはまだ添付されていません :-)サイトが不具合なのか何なのか...今のところプロジェクト ページから取得できます。
不具合が過ぎたらここに添付します。

 
Maxim Kuznetsov:
もう少ししたら、MQLクリエーションから取引に直接関係しないものをすべて取り除き、外部との通常のコミュニケーション(データベース、メール、レポート、さらにはGUI)を追加することも可能でしょう。

また、パイピングで外界に出られないのはなぜですか?
その結果、mqlのことは実質的に忘れることができ、サーバーを一度書けばそれで終わりです。

 
Alexey Oreshkin:

パイプラインで外部に出ることを阻むものは何ですか?
その結果、µlのことは実質的に忘れることができ、サーバーを一度書けばそれで終わりです。

しかし、誰がpipsをコントロールし、プロトコルを実装し、結果を解析するのでしょうか?それは長く、遅く、ポーリングによってのみ行われます。

tclとmqlの間では、データを簡単に転送することができ、1つのメモリ、1つのプロセスで動作します。理論的には(動作確認はしていませんが、動作するはずです)、ある程度の精度で、変数をリンクさせることができます。
tclにはあってmqlにない機能やライブラリが簡単に手に入ることがわかったのです。
 

INDICATORSは敗北した(ほぼ) :-)つまり、ATclは、不遇のEventLoopを含むインジケータで動作するようになったのだ。

スクリーンショット - 前回と同じプログラム(非同期httpクライアント)が、インジケータで表示されるようになりました。


もちろん、DLLの初期化アルゴリズムは同じではありません :-)

APIプレビューはほぼ完了し、あとはソースコードとドキュメントのクリーンアップを行い、ベータ版を発表することができます。

ターキーとライブラリーを装着する。そして、ビールで祝杯をあげるつもりです :-)

ファイル: