До любого обращения к ATcl должна быть вызвана функций ATcl_OnInit() и в завершении скрипта/индикатора/советника ATcl_OnDeinit(const int reason). Между вызовами этих двух функций возможны обращения ко всем прочим функциям библиотеки. При разработке советников и индикторов также рекомендуется вызывать функции ATcl_OnEvent() ATcl_OnTimer() ATcl_OnTick() внутри соответсвующих функций MQL.

Все функции Tcl доступны через интерпретаторы, которые представлены классом ATcl. Вы можете создавать несколько интерпретаторов, не обязательно чтобы он был один, главное чтобы все вызовы были между ATcl_OnInit и ATcl_OnDeinit.

И после такой вводной можно уже и иллюстрировать на MQL:

#include <ATcl.mqh>
void OnStart() {
  ATcl_Init();			/// начинаем использовать ATcl
  ATcl *tcl= new ATcl;		/// создадим интерпретатор
  Print(tcl.StringEval("info tclversion"));	/// выведем в консоль версию tcl
  delete tcl;			/// удалим интерпретатор
  ATcl_Deinit();		/// завершаем использовать ATcl
}

Конечно перед тем как это использовать надо Установить всё :-) то есть поставить Tcl в систему и разместить ATcl.mqh , ATcl.dll по нужным путям.

Для того чтобы пользоваться было легко и просто, реализованы методы StringEval, DoubleEval, IntEval, - которые исполняют некий скрипт и возвращают требуемое по логике значение. Все эти (а далее возможно и другие) являются обёрткой над общим методом Eval(script,flags) который исполняет заданный скрипт и возвращает код результата ( TCL_OK или иной.

Если переписать пример с максимальным контролем, то будет :

#include <ATcl.mqh>
void OnStart() {
  /// начинаем использовать ATcl
  if (ATcl_Init()!=INIT_SUCCESUL) {
    // некая ошибка при интициализации библиотеки
    Alert("Error in ATcl_Init()");
    return;
  }
  /// создадим интерпретатор
  ATcl *tcl= new ATcl;
  if (tcl == NULL || !tcl.Ready()) {
    // ошибка при создании интерпретатора
    Alert("Error in 'new ATcl'");
    return
  }
  if (tcl.Eval("info tcl_version")!=TCL_OK) {
    // Ошибка при исполнении команды
    Alert(StringFormat("Error in script: %s",tcl.StringResult()));
  } else {
    // команда исполнена 
    Print("Tcl version=%s",tcl.StringResult());
  }
  /// звершаем работу
  delete tcl;			/// удалим интерпретатор
  ATcl_Deinit();		/// завершаем использовать ATcl
}

В общем работать с библиотекой довольно просто, методы XxxEval исполняют скрипты Tcl, методы Typename(obj) конвертируют объекты Tcl в значения Mql, методы Obj(x) создают объекты Tcl из значений. Дополнительно методы Set,Get позволяют задавать и менять значения переменных Tcl.

Для более серьёзной демонстрации будет скрипт который сохраняет котировки в базу данных. Возьмём встроенную базу sqlite3, хотя при наличии желания можно и другие (mysql,postgress,jdbc,monetdb активно поддерживаются, также можно подключать ODBC). У Oracle свой интерфейс представленный отдельным пакетом oratcl.

//+------------------------------------------------------------------+
//|                                                  atcl_sqlite.mq4 |
//|                                                Maxim A.Kuznetsov |
//|                                                      luxtrade.tk |
//+------------------------------------------------------------------+
#property copyright "Maxim A.Kuznetsov"
#property link      "luxtrade.tk"
#property version   "1.00"
#property strict
 
#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
}

Исполнение скриптов Tcl в интерпретаторе. Если в качестве первого параметра будет передан Tcl_Obj, то он будет предварительно скомпиллирован.

   int Eval(Tcl_Obj, int flags=0); /// исполнить скрипт Tcl и получить код успеха. Результат будет доступен через Result
   int Eval(string &,int flags=0);
   double DoubleEval(Tcl_Obj,int flags)=0; /// исполнить скрипт и сразу получить результат. При ошибке получится 0.0
   double DoubleEval(string &,int flags=0);
   string StringEval(Tcl_Obj,int flags=0); /// исполнить скрипт и сразу получить результат. При Ошибке будет полуен ""
   string StringEval(string &,int flags=0);
   int Call(Tcl_Obj command,...);	// исполнить команду с аргументами Tcl_Obj (до 10-ти шт)
   int Call(Tcl_Obj &objv[],int objc,int flags); // исполнить команду с аргументами, переданными через массив

Главным методом является Eval - исполнить скрипт заданный в объекте и вернуть код результата. Сам по себе результат будет доступен через Result. Могут быть возращены коды TCL_OK - если всё правильно, TCL_ERROR - в случае ошибки, TCL_BREAK,TCL_CONINUE в особых случаях

Типичный Use-case :

   ATcl_Init();	// включить системы
   ATcl *tcl=new ATcl;	// создать интерпретатор
   ....
   Tcl_Obj script=tcl.Obj("clock seconds"); // создать объект
   tcl.Ref(script);	// увеличить счётчик ссылок, чтобы он не был разрушен после первого использования
	// исполнить скрипт
   if (tcl.Eval(script)!=TCL_OK) {
   	// какая-то ошибка, дать Alert
        Alert(StringFormat("Error in Eval():%s",tcl.StringResult()) );
   } else {
        // ошибок при исполнении нет, можно пользоваться результатом
        // (теперь script скомпилирован и исполнен)
        PrintFormat("UNIXTIME: %d",tcl.LongResult());
   }
   ....
   tcl.Unref(script); // объекты к которым применён ранее Ref() надо Unref() чтобы они удалились
   ....
   delete tcl;	  // удаляем интерпретатор
   ATcl_Deinit(); // выключаем систему

Чтобы не писать таких длинных «простыней» добавлены простые методы для исполнения скриптов, например DoubleEval - исполнить скрипт и получить результат как double. И скрипт можно передавать как объектом, так и обычной строкой.

   PrintFormat("sqrt(2)=%f",tcl.DoubleEval("expr sqrt(2)"));

Source - исполнить файл (с учётом флага Common)

Методы Result используются для получения последнего результата.

   Tcl_Obj Result(int code=0); 		// получить результат в виде объекта
   double DoubleResult(int code=0);	// получить результат в виде double
   string StringResult(int code=0);     // получить результат как string

Аргумент code игнорируется и служит для возможности «вложений» внутрь вызова Eval:

   if (tcl.Eval("array get tcl_platform")!=TCL_OK) {
   	// какая-то ошибка, получим её описание
        Alert(StringFormat("Error : %s",tcl.StringResult());
   } else {
        // ошибок нет, печатаем инфорацию о платформе
        Print(tcl.StringResult());
   }
   // использование "вложенности" 
   PrintFormat("Version: %f",tcl.DoubleResult(tcl.Eval("set tcl_version")) );

Методы Obj() используются для преобразования данных Mql в объекты Tcl. Создают новый объект со 0-м счётчиком ссылок. То есть созданный объект будет автоматически удалён после первого использования. Для многократного использования используется метод Ref(obj), который этот счётчик увиличит. Для освобождения объекта соотв. Unref(obj).

   /// создание объектов
   Tcl_Obj Obj();	// создать пустой объект
   Tcl_Obj Obj(string);	// создать объект из строки
   Tcl_Obj Obj(double); // создать объект из double
   Tcl_Obj Obj(long);	// создать объект из long
   Tcl_Obj Obj(datetime);// из datetime
   /// управление ссылками
   Tcl_Obj Ref(Tcl_Obj obj); // увеличить счётчик и вернуть тот-же объект
   Tcl_Obj Unref(Tcl_Obj obj); // уменьшить счётчик ссылок и вернуть объект; 0 если счётчик обнулился и объект был удалён

Методы String,Double,Long используются для преобразования объектов Tcl в соответствующие значения Mql.

   string String(Tcl_Obj);  		// получить string из объекта
   string String(Tcl_Obj,int index); 	// получить string из элемента списка по индексу
   double Double(Tcl_Obj);  		// получить double из объекта
   double Double(Tcl_Obj,int index); 	// получить double из элемента списка по индексу
   long   Long(Tcl_Obj);                // аналогично - получение Long
   long   Long(Tcl_Obj,int index);
   datetime Datetime(Tcl_Obj);          // то-же но datetime
   datetime Datetime(Tcl_Obj,index);

При обращении к списку по индексу, элементы списка нумеруется от 0 (0-самый первый). Отрицательные индесы адреусуют элементы от конца списка (-1 - саый последний).

К примеру:

   Tcl_Obj list=tcl.Obj("alpha beta gamma theta");
   tcl.Ref(list);
   PrintFormat("List elements: first=%s last=%s",tcl.String(list,0),tcl.String(list,-1));
   tcl.Unref(list);

Небольшой интерфейс к спискам Tcl.

    int ListLength(Tcl_Obj); 			// получить кол-во элементов списка
    Tcl_Obj ListIndex(Tcl_Obj,int index);	// получить элемент списка по индексу
    int Count(Tcl_Obj);				// синоним ListLength, для любителей C#
    int ListAppend(Tcl_Obj list,Tcl_Obj element); // добавить элемент в конец списка
    int Append(Tcl_Obj list,Tcl_Obj element);   // синоним ListAppend

Элементы списков нумеруются от 0 (0-самый первый элемент). При указание отрицательного индекса, элементы выбираются от конца списка (-1 - самый последний)

Добавить вторичный индекс для обращений к спискам-списов и элементам матриц

Для создание объектов из массивов используется расширенный метод Obj. Для получение данных из объектов в массивы метод ToArray. Синтаксис ToArray масксимально близок к CopyArray

    Tcl_Obj Obj(double &array[],int pos=0,int count=WHOLE_ARRAY);// создание объекта из массива double
    Tcl_Obj Obj(string &array[],int pos=0,int count=WHOLE_ARRAY);// создание объекта из массива string
    Tcl_Obj Obj(long &array[],int pos=0,int count=WHOLE_ARRAY);// создание объекта из массива long
    Tcl_Obj Obj(datetime &array[],int pos=0,int count=WHOLE_ARRAY);// создание объекта из массива datetime
    int ToArray(double &dst[],Tcl_Obj src,int dst_pos=0,int src_pos=0,int count=WHOLE_ARRAY);
    int ToArray(string &dst[],Tcl_Obj src,int dst_pos=0,int src_pos=0,int count=WHOLE_ARRAY);
    int ToArray(long &dst[],Tcl_Obj src,int dst_pos=0,int src_pos=0,int count=WHOLE_ARRAY);

если будет востребовано, использовать tarray или vectcl (оба - для быстрых и компактных операций с массивами)

Хотя управление переменными возможно через Eval, для удобства и скорости добавленны методы Set,Get,Unset

   Tcl_Obj Set(Tcl_Obj name,Tcl_Obj value);	// задать значение переменной name
   Tcl_Obj Set(Tcl_Obj hash,Tcl_Obj key, Tcl_Obj value); // задать значение элемента массива
   Tcl_Obj Get(Tcl_Obj name);			// получить значение переменной
   Tcl_Obj Get(Tcl_Obj hash,Tcl_Obj key);	// получить значение элемента массива

Unset - удаление переменных

IsSet, IsArray - проверки на существование переменных/массивов

прозрачная поддержка словарей наравне с массивами

Рекомендации по практическому использованию ATcl см. на странице Practice

Чтобы всё работало, во первых надо поставить Tcl. Для работы в MT4 должны быть установлена 32-х битная версия. Дистрибутив включающий Tcl/Tk с кучей доп.библиотек и пакетов можно взять у ActiveState (www.ActiveState.com) или MagicSplat (www.MagicSplat.com)

И конечно нужны ATcl.mqh и ATcl.dll, они вот тут в одном архиве : atcl.zip