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에 대한 인터페이스는 어느 정도 정확하게 작동하며 EventLoop도 작동합니다. 즉, 비동기 명령, 입력/출력 및 타이머를 사용할 수 있습니다.


현재 버전으로 아카이브를 첨부하여 시도할 수 있습니다..

최대한 보완한 문서와 최신 버전은 프로젝트 페이지 http://luxtrade.tk/atcl 에서 가져갈 수 있습니다.

이 스레드에서 저는 Tcl/Tk 자체와 언급된 ATcl 라이브러리에 대한 질문에 답할 준비가 되었습니다. 아이디어, 제안, 비판을 듣게 되어 기쁩니다.

업데이트: 주제에 제공된 스크립트 소스 첨부
업데이트: 업데이트된 atcl.zip

파일:
atcl.zip  9 kb
 

흥미로운 일입니다.

언제나처럼 나는 R 아이디어를 만지작거리고 있는데, 나는 당신이 적어도 그 아이디어에 대해 논의할 적임자라고 생각합니다.


아이디어는 다음과 같습니다.

오늘날 터미널 차트에서 µl4/5로 컴파일러를 생성한 실행 파일을 드래그할 수 있습니다.

R 스크립트를 일정에 연결할 수 있도록 개발할 수 있습니까?

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

흥미로운 일입니다.

언제나처럼, 나는 R 아이디어를 만지작거리고 있다 . 당신은 적어도 그 아이디어에 대해 논의할 수 있는 사람인 것 같습니다.


아이디어는 다음과 같습니다.

오늘날 터미널 차트에서 µl4/5로 컴파일러를 생성한 실행 파일을 드래그할 수 있습니다.

R 스크립트를 일정에 연결할 수 있도록 개발할 수 있습니까?

그건 그렇고, R에 대해 :-) R 분포의 구성에주의를 기울였습니까?

따라서 내장 R tcl을 포함하는 library/tcltk 디렉토리가 있습니다. 언어/플랫폼에 자금이 부족하면 "Tool Common Language"라고 하는 tcl이 추가됩니다. R에서는 GUI가 충분하지 않았고 tcl/tk가 그 안에 설치되었습니다(실제로 Python 및 Ruby에서와 같이).

차트를 제외하고 MQL의 많은 기능이 부족하여 ATcl을 얻었습니다.

 

ATcl은 통계도 할 수 있습니다 :-) 또 하나의 작은 테스트 예제는 기본 통계 계산입니다. 데이터 세트에 대한 값. 닫기는 스크립트에서 수행되며 입력 매개변수에 설정되는 개수

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


스크립트를 첨부합니다. 한 줄만 계산하면 됩니다 :-)

파일:
 

그리고 또 하나의 예 :-)

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


파일:
 

우선, 물론 스크린샷 :-) 이것은 60mql 라인 + 60tcl 라인의 tcp-server Advisor입니다.


그리고 이 EA의 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();
}

지금까지 모든 것(라이브러리와 인터프리터)은 Expert Advisors 또는 스크립트에서 작동합니다. 아직 표시기에 대한 준비가 되지 않았습니다.:-( OnInit/OnDeinit 대기열, DLL 로드 및 언로딩, 타이머가 있는 일종의 게임이 있습니다.
파일:
atcl.zip  9 kb
 

모든 것이 점점 더 안정되고 있습니다. EventLoop은 Expert Advisors 및 스크립트에서 자신 있게 작동합니다(그러나 표시기에서는 여전히 작동하지 않음)

새롭고 유용한 데모 - 비동기식 HTTP 클라이언트 및 파서. 소스 코드(mq4 및 tcl)의 길이가 약 60줄인 것은 이미 전통적입니다.
요청은 전문가와 병렬로 수행되며 html 페이지의 파싱은 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();
}
거의 만족합니다. 조금만 더 하면 MQL 기술에서 거래와 직접적으로 관련되지 않은 모든 것을 제거하고 외부 세계(데이터베이스, 편지, 보고서, GUI)와의 정상적인 통신을 추가할 수 있습니다.

표시기 및 타이머와 관련된 현재 문제가 아니라면 :-(
파일:
 

여기에 두 개 이상의 zip 파일을 첨부할 수 없습니까?
또는 같은 이름의 파일 문제..

나는 여전히 현재 버전의 atcl을 첨부하지 않습니다 :-) 사이트가 버그가 있거나 다른 것입니다. 지금은 프로젝트 페이지에서 가져갈 수 있습니다.
결함은 통과할 것입니다. 여기에 첨부하겠습니다.

 
Maxim Kuznetsov :
조금만 더 하면 MQL 기술에서 거래와 직접 관련되지 않은 모든 것을 제거하고 외부 세계(데이터베이스, 편지, 보고서 및 GUI)와의 정상적인 통신을 추가할 수 있습니다.

그리고 파이프를 통해 외부 세계로 나가는 것을 막는 것은 무엇입니까?
결과는 동일합니다. µl은 거의 잊어버릴 수 있습니다. 서버는 한 번만 작성하면 됩니다.

 
Alexey Oreshkin :

그리고 파이프를 통해 외부 세계로 나가는 것을 막는 것은 무엇입니까?
결과는 동일합니다. µl은 거의 잊어버릴 수 있습니다. 서버는 한 번만 작성하면 됩니다.

누가 파이프를 제어하고 프로토콜을 구현하며 결과를 구문 분석합니까? 길고 느리며 유일한 투표입니다.

여기에 완전히 다른 수준의 통합이 있습니다. tcl과 mql 간에 데이터를 쉽게 전송할 수 있습니다. 이들은 단일 프로세스에서 동일한 메모리에 바로 위치합니다. 이론상(나는 작업을 확인하지 않았지만 작동해야 함) 약간의 정확도로 변수를 연결할 수 있습니다.
tcl에는 있지만 mql에는 없는 모든 기능과 라이브러리에 쉽게 액세스할 수 있습니다.
 

INDICATORS (거의) :-) 즉, ATcl은 이제 불운한 EventLoop를 포함하여 표시기에서 작동합니다.

화면에서 - 지난 번과 동일한 프로그램(비동기 http 클라이언트), 하지만 이제 표시기에:


물론 DLL 초기화 알고리즘은 똑같았다 :-)

API-Preview가 거의 끝났다고 할 수 있습니다. 문서와 함께 소스 코드를 정리하면 베타 버전을 발표할 수 있을 것입니다.

칠면조와 도서관을 붙이고 있습니다. 그리고 맥주와 함께 축하해요 :-)

파일: