В предыдущих статьях затрагивалась работа с 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 архив с приведёнными исходниками никак не прикладывается