ATcl & Excel

ATcl & Excel

29 января 2023, 14:10
Maxim Kuznetsov
0
185

В предыдущих статьях затрагивалась работа с Word, а сейчас сделаем минималку для работы с Excel. Будем просто читать/писать данные. Без фанатизма - только самое необходимое. Всё прочее (оформление, формулы) можете дополнить потом по образу и подобию.



Сделаем простой скрипт - бросаем его на график и он открывает эксель и записывает в таблицу последние 5 котировок. И для проверки читает их оттуда и выводит в журнал.

Основную работу с Excel будем проводить в скрипте tcl, сделаем в нём класс и предоставим для MQL методы SetRange - передать значения из двумерного массива в эксель, GetRange - прочесть из эксель в массив. Так как MQL строг с типами, то элементы массивов могут быть или double или long или string;

Даже такого просто функционала хватит для большинства задач.

Как и в предыдущей статье, будем использовать интерфейс COM то есть пакет cawt tcl https://www.tcl3d.org/cawt/download/CawtReference-Excel.html. Он обладает громадными возможностями, но нам потребуется всего несколько основных функций: SetRangeByIndex - выделать область ячеек, SetRangeValues - задать значения для области и GetRangeValues - получить значения оттуда.

скрипт tcl получился почти невесомый:

package require cawt
 
oo::class create SimpleExcel {
variable FileName       ; # имя файла 
variable App            ; # дескриптор приложения (excel)
variable Doc            ; # дескриптор документа
variable Sheet          ; # дескриптор листа
constructor { {fileName {}} } {
        set FileName [ string trim $fileName ]
        set Doc {}
        set Cheet {}
        if { $FileName == "" } {
                # имя файла не задано - просто создадим новый документ
                set App [ Excel::OpenNew 1 ]
                set Doc [ Excel::AddWorkbook $App ]
        } else {
                # а если задано - то надо привести его в порядок
                # файл в каталоге files, путь абсолютный, нормализованый
                set FileName [ file normalize $FileName ]
                set path [ file nativename [ file join [ pwd ] "mql5" "files" $FileName ] ]
                if { ! [ file readable $path ] } {
                        error "File $FileName not readable"
                }
                set App [ Excel::OpenNew 1 ]
                set Doc [ Excel::OpenWorkbook $App $FileName ]
        }
        # в обоих случаях пока работает тлько с первым листом
        set Sheet [ Excel::GetWorksheetIdByIndex $Doc 1 ]
}
 
destructor {
        # по завершению - освобождаем дескрипторы
        # ошибки и исключения перехватываем, потому-что деструктор
        catch { Cawt Destroy $Sheet }
        catch { Cawt Destroy $Book }
        catch { Cawt Destroy $App }
}
# передать данные из массива в заданные координаты (строки и столбцы - числа от 1)
method SetRange { R C Matrix } {
        set rows [ llength $Matrix ]
        set cols [ llength [ lindex $Matrix 0 ] ]
        set R2 [ expr $R + $rows - 1 ]
        set C2 [ expr $C + $cols - 1 ]
        # выделяем диапазон от R:C до R2:C2 включительно
        # получаем его дескритор
        set range [ Excel::SelectRangeByIndex $Sheet $R $C $R2 $C2 ]
        # передаём туда данные
        Excel::SetRangeValues $range $Matrix 
        # освобождает дескриптор диапазона
        Cawt Destroy $range
}
# прочесть(вернуть) данные из заданных координат. rows,cols - кол-во строк и столбцов
method GetRange { R C rows cols } {
        set R2 [ expr $R + $rows - 1 ]
        set C2 [ expr $C + $cols - 1 ]
        set range [ Excel::SelectRangeByIndex $Sheet $R $C $R2 $C2 ]
        set data [ Excel::GetRangeValues $range ]
        Cawt Destroy $range
        return $data
}
export SetRange GetRange
};#/class Excel

Соответсвующий ему класс и скрипт MQL (сразу вместе с демо в одном флаконе) несколько побольше будет:

#resource "SimpleExcel.tcl" as string SimpleExcel_tcl;
#include <ATcl/ATcl.mqh>
 
double Unbox(ATcl *tcl,Tcl_Obj x,double fake) {
   return tcl.Double(x);
}
string Unbox(ATcl *tcl,Tcl_Obj x,string fake) {
   return tcl.String(x);
}
long Unbox(ATcl *tcl,Tcl_Obj x,long fake) {
   return tcl.Long(x);
}
/// минимальные функции для работы с Excel
class SimpleExcel {
public:
   ATcl *tcl;
   string fileName;
   Tcl_Obj instance;
public:
   // задаём таблицу, если файл не задан - то пустую
   SimpleExcel(string xlsFileName="") {
      tcl=new ATcl();
      fileName=xlsFileName;
      instance=0;
   }
   ~SimpleExcel() {
      if (tcl!=NULL) {
         if (instance!=0) {
            tcl.Call(instance,tcl.Obj("destroy"));
            tcl.Eval("catch { Cawt Destroy }");
            tcl.Unref(instance);
         }
         delete tcl;
      }
   }
   // стандартный протокол MQL
   // инициализируем объект к работе
   int OnInit() {
      if (tcl==NULL || tcl.OnInit()!=INIT_SUCCEEDED) {
         return INIT_FAILED;
      }
      if (tcl.Eval(SimpleExcel_tcl)!=TCL_OK) {
         return INIT_FAILED;
      }
      // делаем себе иснтанс класса SimpleExcel
      instance=tcl.ObjCall(tcl.Obj("SimpleExcel"),tcl.Obj("new"),tcl.Obj(fileName));
      if (instance==0) {
         return INIT_FAILED;
      }
      tcl.Ref(instance);
      return INIT_SUCCEEDED;
   }
   void OnDeinit(const int reason) {
   }
   // передать значения из массива в таблицу   
   template<typename T>
   bool SetRange(int R,int C,T &arr[][]) {
      int rows=ArrayRange(arr,0);
      int cols=ArrayRange(arr,1);
      bool res=false;
      // из массива скаляров делаем матрицу(список списков) объектов
      Tcl_Obj matr=tcl.Obj();
      tcl.Ref(matr);
      for(int y=0;y<rows;y++) {
         Tcl_Obj vect=tcl.Obj();
         tcl.Ref(vect);
         for(int x=0;x<cols;x++) {
            tcl.ListAppend(vect,tcl.Obj(arr[y][x]));
         }
         tcl.ListAppend(matr,vect);
         tcl.Unref(vect);
      }
      // вызываем метод SetRange
      if (tcl.Call(instance,tcl.Obj("SetRange"),tcl.Obj(R),tcl.Obj(C),matr)!=TCL_OK) {
         res=false;
      } else {
         res=true;
      }
      tcl.Unref(matr);
      return res;
   }
   // прочесть значения из таблицы в массив
   // размерности массива должны быть заранее определены (ArrayResize)
   template <typename T>
   bool GetRange(int R,int C,T &arr[][]) {
      int rows=ArrayRange(arr,0);
      int cols=ArrayRange(arr,1);
      bool res=false;
      // получаем матрицу матрицу(список списков) объектов
      if (tcl.Call(instance,tcl.Obj("GetRange"),tcl.Obj(R),tcl.Obj(C),tcl.Obj(rows),tcl.Obj(cols))!=TCL_OK) {
         return false;
      }
      // вытаскиваем из неё данные в массив скаляров
      Tcl_Obj matr=tcl.Result();
      tcl.Ref(matr);
      for(int y=0;y<rows;y++) {
         for(int x=0;x<cols;x++) {
            arr[y][x]=Unbox(tcl,tcl.Index(matr,y,x),arr[0][0]);
         }
      }
      return true;
   }
};
 
void OnStart()
{
   // конструруем объект
   SimpleExcel SE("");
   if (SE.OnInit()!=INIT_SUCCEEDED) {
      return;
   }
   // делаем заголовок таблицы
   string header[1][5]={
      {"time","open","high","low","close"}
   };
   SE.SetRange(1,1,header);
   MqlRates rates[];
   if (CopyRates(_Symbol,PERIOD_CURRENT,0,5,rates)==5) {
      // готовим 2 (разных типов) массива
      string times[5][1];
      double prices[5][4];
      for(int y=0;y<5;y++) {
         times[y][0]=TimeToString(rates[y].time,TIME_DATE|TIME_MINUTES);
         prices[y][0]=rates[y].open;
         prices[y][1]=rates[y].high;
         prices[y][2]=rates[y].low;
         prices[y][3]=rates[y].close;
      }
      // перекидываем их в Эксель
      SE.SetRange(2,1,times);
      SE.SetRange(2,2,prices);
   }
   // просто ради демонстрации - прочитаем и напечатаем в журнал
   string outs[6][5]; // 6 строк (включая заголовки) по 5 столцов
   if (SE.GetRange(1,1,outs)) {
      for(int y=0;y<6;y++) {
         string s="";
         for(int x=0;x<5;x++) {
            if (x) s=s+" ";
            s=s+outs[y][x];
         }
         Print(s);   
      }
   }
   SE.OnDeinit(REASON_REMOVE);
}

Конечно для MQL такой размер это мизер, там всё большое 

Накидываем скрипт на график, видим результат:

Всё готово. Теперь можно довольно просто перекибывать данные между MetaTrader и Excel.

По вкусу можете добавить функций/методов.

Скачать библиотеку ATcl отсюда https://uploadfiles.in/6q9 или https://dfiles.eu/files/3sigiedi1 или https://www.filefactory.com/file/nmqp3op9f9q/atclsetup_1.01.exe

а также со страницы загрузки эскизного сайта http://atcl.unaux.com/download/

или следуя инструкциям, из репозитария проекта https://chiselapp.com/user/nektomk/repository/atcl-lib/home

Читайте также предыдущие статьи:

интерфейс сайта немного глючит, и zip архив с приведёнными исходниками никак не прикладывается