CSV ファイルを介した MetaTrader 4 と Matlab 間の連携
はじめに
Matlab 環境の計算能力は、MQL4 を含めたあらゆるプログラム言語の計算能力より各段に優れていることで知られています。Matlab が提供する数学関数の幅広さにより、行われる処理の理論的根拠を完全に度外視して複雑な計算を行うことができます。
トレーディングターミナルと Matlab の間のリアルタイムの連携は簡単ではありません。。本稿では、CSV ファイルを介した MetaTrader 4 と Matlab 間のデータ交換を編成する方法を提供します。
1. 相互連携
新規バーの入信時、MetaTrader 4 が直近のバー100本についてのデータを Matlab に対して送信し、処理結果を受信すると仮定します。
この課題を解くためには、データをテキストファイルに書き込み、処理結果を Matlab によって作成される別のテキストファイルから読み取る MetaTrader 4 にインディケータを作成する必要があります。
MetaTrader 4 は新規バー受信ごとにそのデータファイルを作成しなければなりません。また、ティックごとに結果を読み取るようにする必要もあります。Matlab が更新する前に結果を読まないようにするため、出力ファイル作成前に結果を持つファイルを削除してしまうのです。この場合、読み取りの試みは Matlab が計算を終了し、新規ファイルを作成したあとに初めて成功するのです。
Matlab は、MetaTrader 4 によって作成されるファイル属性を毎秒分析し、作成時刻が変わるとき処理を開始する必要があります。処理が終わると、データのレコード前に MetaTrader 4 によって削除されたファイルが再作成されます。MetaTrader 4 はそれを正常に削除し、新規データをロードしてへ返事を待ちます。
2. 出力データファイル作成
データをファイルとして保存することを専門に扱った記事は数多くあるので、その点についてここで手間をかけることはしません。われわれはデータを7列に書き込む点だけはっきりさせておきます。それは、“DATE”, “TIME”, “HI”, “LOW”, “CLOSE”, “OPEN”, “VOLUME”です。分離する文字は“;”です。バーの優先順位は早いものから後のものへ、すなわちゼロバー特性を持つ行は最後にレコードされるのです。ファイルには列名を持つ行が当てられます。ファイル名はシンボル名とタイムフレームで構成されます。
#property indicator_chart_window extern int length = 100; // The amount of bars sent to be processed double ExtMap[]; // Chart buffer string nameData; int init() { nameData = Symbol()+".txt"; // name of the data file to be sent return(0); } int start() { static int old_bars = 0; // remember the amount of bars already known if (old_bars != Bars) // if a new bar is received { write_data(); // write the data file } old_bars = Bars; // remember how many bars are known return(0); } //+------------------------------------------------------------------+ void write_data() { int handle; handle = FileOpen(nameData, FILE_CSV|FILE_WRITE,';'); if(handle < 1) { Comment("Creation of "+nameData+" failed. Error #", GetLastError()); return(0); } FileWrite(handle, ServerAddress(), Symbol(), Period()); // heading FileWrite(handle, "DATE","TIME","HIGH","LOW","CLOSE","OPEN","VOLUME"); // heading int i; for (i=length-1; i>=0; i--) { FileWrite(handle, TimeToStr(Time[i], TIME_DATE), TimeToStr(Time[i], TIME_SECONDS), High[i], Low[i], Close[i], Open[i], Volume[i]); } FileClose(handle); Comment("File "+nameData+" has been created. "+TimeToStr(TimeCurrent(), TIME_SECONDS) ); return(0); }
これらデータをすべて必要とはしませんが、未知の数字の入ったただの列セットよりも、常に意味のあるファイルを取得する方がよいものです。
3. グラフィカルユーザーインターフェース(GUI)の作成
というわけで、ファイルは準備できました。Matlab を起動します。
ファイルからテキストデータを読み出すアプリケーションを作成し、データを処理し、結果を別のファイルに記録します。GUI を作成し、ファイル名を指定、チャートを閲覧し、処理を開始します。始めましょう。
GUI を作成するには、コンソールに“guide”と打ち込むか、Matlabのメインパネルで押して『GUIDE クイックスタート』を起動します。表示されるダイアログボックスで『新規 GUI を作成する』→『空の GUI(デフォルト)』を選択します。 ここで空のフォームで GUI 作成用インターフェースが表示されます。このフォームに以下のオブジェクトを設定します。それは『テキストを編集する』、『ボタンを押す』、『静的テキスト』、『軸』、『ボタンを押す』です。結果、このようなものが表示されます。
次にそれぞれのオブジェクトに対してビジュアルプロパティビルダーを呼び出し、以下のようにプロパティを設定します。
静的テキスト:水平位置 -左、タグ- textInfo、文字列- Info
テキストを編集する:水平位置 -左、タグ- editPath、文字列- パス選択
ボタンを押す:タグ- pushBrowse、文字列-Browse
軸:ボックス- on、フォント名-MS Sans Serif、フォントサイズ- 8、タグ- axesChart.
ボタンを押す:タグ- pushSrart、文字列- Start
「タグ」のプロパティを変更することでオブジェクトごとにユニークな名前を選択します。その他項目を変更し、表示を変えます。
すべて準備が整ったら、『実行する」を押してインターフェースを起動し、インターフェース ファイルの保存とM ファイルの保存を確認し、名前をつけ(たとえば“FromTo”)、『保存する』を押します。その後、GUI が起動し動作中の表示どおりに表示されます。Matlab はのちのプログラムの基となるように M ファイルを作成し、埋め込まれたエディタにそれを開きます。
なんらかの理由で、表示が適切でなければ、動作中の GUI を閉じ、エディタでオブジェクトの装飾を修正します。たとえば、わたしのディストリビューションは MS Sans Serif を正確に表示しませんでした。そのため私はフォント名を“Sans Serif”に変更しなければなりませんでした。
4. ユーザーインターフェースの構築
インターフェースの動作は Matlab 言語で 「M ファイル エディタ」でプログラムします。 Matlab によって作成されるスケルトン プログラムは、インターフェースオブジェクトで作業するときユーザーによって呼び出される関数リストを表示します。関数は空なので、GUI はまだ何も行いません。必要な内容で関数に書き込むのはわれわれの仕事です。
4.1ブラウズ ボタンのプログラミング
まず、MetaTrader 4 が作成したファイルにアクセスする必要があります。そうして『ブラウズ』ボタンを押すことで呼び出される関数を起動するのです。
ボタンを押すことで呼び出される関数名は、ボタン名(『タグ』プロパティで設定されます)と接尾辞 "_Callback" で構成されます。ファイルテキストで関数“pushBrowse_Callback”を見つけるか、ツールバーの『関数を表示する』を押してリストで“pushBrowse_Callback”を選択します。
Matlab プログラム言語の構文は C 言語や C 類似言語でコーディングする従来のルールとは異なります。特に、関数本文を中括弧でマークしたり、関数に渡されるデータタイプや1から始まる配列(ベクトル)インデックスを指定する必要はなく、コメント文字は“%”です。よって、上のグリーンのテキストは全体にプログラムではなく、われわれがこのケースを把握できるよう Matlab の開発者によってつけられたコメントなのです。
われわれはファイルのフルネームを入力するためのダイアログを作成する必要があります。これには、関数“uigetfile”を使用します。
% --- Executes on button press in pushBrowse. function pushBrowse_Callback(hObject, eventdata, handles) [fileName, filePath] = uigetfile('*.txt'); % receive and the path from the user if fileName==0 % if it is canceled fileName=''; % create an empty name filePath=''; % create an empty path end fullname = [filePath fileName] % form a new name set(handles.editPath,'String', fullname); % write in the editPath
ここでの“handles”は、オブジェクトすべてのディスクリプタを GUI に格納するストラクチャです。 それにはディスクリプタを設定する形式も含まれます。ストラクチャは関数から別の関数に渡され、オブジェクトにアクセスできるようにします。
“hObject”は関数を呼んだオブジェクトのディスクリプタです。
“set”は特定の値にオブジェクト値を設定するのに役立ち、以下の構文になっています:set(object_descriptor, object_property_name, property_value)。
オブジェクト プロパティの値を検索するには次の関数を使用します:property_value = get(object_descriptor, object_descriptor_name)。
ただし、名前は文字列タイプの値であることを忘れないようにします。よってそれは単一クオートの必要があります。
オブジェクトについて知っておくべき最後の事柄と、そのプロパティです。GUI エレメントを設定した形式自体が“root” オブジェクトに設定されたオブジェクトなのです(its はその派生です)。それはまた変更可能なプロパティ セットを持ちます。プロパティは『オブジェクト エディタ』という名前のツールをインターフェース エディタのメイン ツールバーから呼び出すことによって閲覧可能です。名前からわかるようにオブジェクト“root”はグラフィカルオブジェクト階層の根本で、上位階層は持ちません。
それではここで、結果取得したものを確認します。M ファイルエディタのメインツールバーで「実行する」を押して GUI を起動します。「ブラウズ」をクリックしてみて、ファイルを選択します。オンになっていますか?それから動作中の GUI を閉じて、先に進みます。
4.2「開始」ボタン、チャート描画のプログラミング
ファイルからデータを読み出しそれをチャートに表示する関数を呼び出して「開始する」ボタンを割り当てます。
まず、関数そのものを作成します。インプットとして 'handles' オブジェクト ディスクリプタのストラクチャが必要です。オブジェクトにアクセスしたら、それを読みプロパティを設定することができます。
% data reading, chart drawing, processing, storage function process(handles) fullname = get(handles.editPath, 'String'); % read the name from editPath data = dlmread(fullname, ';', 2, 2); % read the matrix from file info = ['Last update: ' datestr(now)]; % form an informative message set(handles.textInfo, 'String',info); % write info into the status bar high = data(:,1); % it is now high where the first column of the data matrix is low = data(:,2); % d low -- the second close = data(:,3); % --/-- open = data(:,4); % len = length(open); % the amount of elements in open axes(handles.axesChart); % make the axes active hold off; % clean axes off before adding a new chart candle(high, low, close, open); % draw candlesticks (in the current axes) set(handles.axesChart,'XLim',[1 len]); % set limits for charting
以下、説明です。
“dlmread” は分離子を伴ってテキストファイルからデータを読み出します。それは次の構文になっています。:dlmread(full_file_name, separator, skip_strings, skip_columns)
“length(qqq)” -行列 qqq の大きいサイズ
”now” -現在日時
“datestr(now)” -テキストへ変換した日時
Matlab は理論と例でひじょうに大きなヘルプ情報を提供することも知っておく必要があります。
プログラムの末尾にわれわれの関数を入れ(そこにある方が探しやすいでしょう)、“pushStart_Callback”にその呼び出しを追加します。
% --- Executes on button press in pushStart. function pushStart_Callback(hObject, eventdata, handles) process(handles);
『実行する』ボタンで起動し、ファイルを選択したら、『開始する』を押して、結果を楽しんでください。
4.3ファイルへのパス保存
これですべて上首尾ですが、『ブラウズ』を押した後、マウスをクリックしてファイルの選択を永久に行うのはやや面倒です。一度選択したら、パスを保存します。
読み出しから始めます。パスを格納するファイル名は GUI 名と接尾辞“_saveparam”で構成され、拡張子“.mat”を持ちます。
関数“FromTo_OpeningFcn”は GUI フォームが作成されたら直ちに実行されます。そこにファイルからのパス読み出しをするよう追加します。その試みが失敗すれば、デフォルト値が使用されます。
% --- Executes just before FromTo is made visible. function FromTo_OpeningFcn(hObject, eventdata, handles, varargin) guiName = get(handles.figure1, 'Name'); % get the name of our GUI name = [guiName '_saveparam.mat'] % define the file name h=fopen(name); % try to open the file if h==-1 % if the file does not open path='D:\'; % the default value else load(name); % read the file fclose(h); % close the file end set(handles.editPath,'String', path); % write the name into object "editPath"
関数“FromTo_OpeningFcn”のその他文字列は変更されません。
関数“pushBrowse_Callback”を以下のように変更します。
% --- Executes on button press in pushBrowse. function pushBrowse_Callback(hObject, eventdata, handles) path = get(handles.editPath,'String'); % read the path from object editPath [partPath, partName, partExt] = fileparts(path); % divide the path into parts template = [partPath '\*.txt']; % create a template of parts [userName, userPath] = uigetfile(template); % get the user name and the path from the user if userName~=0 % if "Cancel" is not pressed path = [userPath userName]; % reassemble the path end set(handles.editPath,'String', path); % write the path into object "editPath" guiName = get(handles.figure1, 'Name'); % get to know the name of our GUI save([guiName '_saveparam.mat'], 'path'); % save the path
4.4データ処理
通常の手順として、四次多項式関数によって列“OPEN”を補間します。
関数“process”の末尾に次のコードを追加します。
fitPoly2 = fit((1:len)',open,'poly4'); % get the polynomial formula fresult = fitPoly2(1:len); % calculate Y values for X=(from 1 to len) hold on; % a new chart has been added to the old one stairs(fresult,'r'); % plot stepwise by color - 'r'- red
起動し、『開始する』を押します。
上記とだいたい同様の結果が得られれば、ファイルとしてデータを保存するのに最適なタイミングです。
4.5データのファイルとしての保存
データ保存は読み出しほど複雑ではありません。ただ一つ『慎重に扱うべきこと』は、ベクトル“fresult”を逆に数えることです。すなわち、最後から始めに向かって数えるのです。これは MetaTrader 4 において、ゼロバーから始めファイルの終わりまで、ファイルの読み出しを簡単にするためです。
次に、以下のコードで関数“process”を補います。
[pathstr,name,ext,versn] = fileparts(fullname); % divide the full name % of the file into parts newName = [pathstr '\' name '_result' ext]; % re-compose the new file name fresult = flipud(fresult); % turn vector fresult dlmwrite(newName, fresult); % write in the file
ここで、結果を持つファイルが作成され、このファイルは最初のファイルがあるのと同じ場所に入っていて、接尾辞“_result”が追加された同名であることに留意ください。
4.6タイマー制御
これはこの作業でもっともむつかしい部分です。MetaTrader 4 形式のファイル作成時間を毎秒確認するタイマーを作成する必要があります。時間が変われば、関数“process”が起動しなければなりません。タイマーの開始と停止は“Start”によって行われます。GUI が開くとき、以前に作成されたタイマーはすべて削除します。
関数“FromTo_OpeningFcn”内に以下のコードを入れてタイマーを作成します。
timers = timerfind; % find timers if ~isempty(timers) % if timers are available delete(timers); % delete all timers end handles.t = timer('TimerFcn',{@checktime, handles},'ExecutionMode','fixedRate','Period',1.0,'UserData', 'NONE');
上記のコードはこの関数への以前の挿入後すぐに挿入する必要があります。すなわち文字列“handles.output = hObject;” と “guidata(hObject, handles);”の前です。
このコードを実行することで、GUI の作成直後に Matlab がタイマーが使用可能であるかチェックし、既存のタイマーを削除して、新規にタイマーを作成します。タイマーは毎秒、関数“checktime”を呼び出し、そこにディスクリプタ“handles”のリストを渡します。“handles”とは別に、タイマーは関数に、呼出し時刻と理由を持つストラクチャと共に、自分のディスクリプタを渡します。われわれはこれに関与することはできませんが、タイマーに呼び出される関数をコーディングする際、このことに配慮が必要です。
関数自体は好きな箇所に入れることができます。その関数に 呼び出された時刻を自分でMatlab ステータスバーに書き込ませます。
% function called by the timer function checktime(obj, event, handles) set(handles.textInfo,'String',datestr(now));
それが作成されるとき、タイマーは停止します。これからそれを取り入れます。関数“pushStart_Callback”を検索します。そこにある'process(handles)' 呼び出しをコメントし、タイマー管理をそこに書き込みます。
% --- Executes on button press in pushStart. function pushStart_Callback(hObject, eventdata, handles) % process(handles); statusT = get(handles.t, 'Running'); % Get to know the timer status if strcmp(statusT,'on') % If it is enabled - stop(handles.t); % disable it set(hObject,'ForegroundColor','b'); % change color of pushStart marking set(hObject,'String',['Start' datestr(now)]); % change the button top marking end if strcmp(statusT,'off') % If it is disabled - start(handles.t); % enable it set(hObject,'ForegroundColor','r');% change color of pushStart marking set(hObject,'String',['Stop' datestr(now)]); % change the button top marking end
それぞれがどのように動作するか確認します。『開始する』でタイマーを有効に、また無効にしてみます。タイマーが有効になったら、パス入力フィールドの上にある時計が機能する必要があります。
GUI を閉じる際、"X" ボタンでタイマーを削除する方がよいでしょう。それを行いたい場合は、
stop(handles.t) ; % stop the timer delete(handles.t); % delete the timer
を関数“figure1_CloseRequestFcn”の冒頭に追加します。この関数は GUI を閉じるときに呼び出されます。それには GUI エディタからアクセス可能です。
ただし、開いている GUI を閉じずにエディタの『実行する』を押すと、新規にタイマーが作成されるときに古いタイマーが削除されないことに配慮してください。そして、次のとき、もう一つ作成される、などとなります。『未処理』タイマーは Matlab コンソールのコマンド“delete(timerfind)”で処理することができます。
すべてうまく動作すれば、MetaTrader 4 からの直近のファイル変更時刻を確認する関数を作成します。
% function to be called by the timer function checktime(obj, event, handles) filename = get(handles.editPath, 'String'); % get to know the file name fileInfo = dir(filename); % get info about the file oldTime = get(obj, 'UserData'); % recall the time if ~strcmp(fileInfo.date, oldTime) % if the time has changed process(handles); end set(obj,'UserData',fileInfo.date); % save the time set(handles.pushStart,'String',['Stop ' datestr(now)]); % override the time
関数 "dir(full_file_name)" はファイル情報(名前、日付、バイト数、isdir)を持つストラクチャを返します。前回のファイル作成に関する情報はタイマーオブジェクトの『ユーザーデータ』プロパティに格納されます。そのディスクリプタは obj という名前で関数 "checktime" に渡されます。
ここで、MetaTrader 4 によって作成されたファイルを変更すると、われわれのプログラムは結果を上書きします。これはファイルをマニュアルで変更し(たとえば、最後の文字列を削除する)導かれたチャートやファイルで変更を追跡することで確認ができます。もちろんそこで『開始する』ボタンを押す必要があります。
プログラム操作中にチャートのコピーがある別のウィンドウが作成されれば、関数"process"の始めに以下の文字列を追加します。
set(handles.figure1,'HandleVisibility','on');
5. MetaTrader 4 での結果描画
では MetaTrader 4 に戻ります。ファイルから結果を読み、それをチャートに描画する関数でわれわれのインディケータを補う必要があります。プログラムの動作は以下に説明しています。
1. 新規バーが受け取られたら、古い結果ファイルを削除し、チャートを消去し、データファイルを保存します。
2. 結果ファイルが読み取り可能であれば、ファイルを読み、チャートを描画し、結果ファイルを削除します。
以下のコードがどのように動作するかはここで述べません。それは、ファイルからデータを読むことと、インディケータを描くことは別の記事で参照できるからです。ここでの結果ファイルはチャートに入れられた直後に削除される、ということだけお知らせしておきます。よって複数の読み取りエラーメッセージを見ても心配しないでください。
読み取りエラーは次の2つのケースで発生します。
1. 新規バーが受信された直後。これは結果ファイルがまだ作成されていないためです。
2. 結果が読まれ、チャートが描かれた直後。同じデータが再度読まれないようファイルが削除されたためです。
このため、プログラムは実質上常に『読み込みエラー』状態を維持するのです。:)
#property indicator_chart_window #property indicator_buffers 1 #property indicator_width1 2 #property indicator_color1 Tomato extern int length = 100; // The amount of bars to be sent for processing double ExtMap[]; // Chart buffer string nameData; string nameResult; int init() { nameData = Symbol()+".txt"; // the name of the data file to be sent nameResult = Symbol()+"_result.txt";// the name of the received file containing results SetIndexStyle(0, DRAW_LINE); SetIndexBuffer(0, ExtMap); return(0); } int deinit() { Comment(""); return(0); } int start() { static int attempt = 0; // the amount of attempts to read the result static int old_bars = 0; // remember the amount of the known bars if (old_bars != Bars) // if a new bar has income { FileDelete(nameResult); // delete the result file ArrayInitialize( ExtMap, EMPTY_VALUE); // empty the chart write_data(); // save the data file old_bars = Bars; return(0); // nothing should be done this time } // int handle_read = FileOpen(nameResult, FILE_CSV|FILE_READ, ';'); // try to open the result file attempt++; // count the attempt to open if(handle_read >= 0) // if the file has opened for reading { Comment(nameResult+". Opened with attempt #"+ attempt); // opening report read_n_draw(handle_read); // read the result and draw a chart FileClose(handle_read); // close the file FileDelete(nameResult); // delete the result file attempt=0; // zeroize the amount of attempts to read } else // if we cannot open the result file { Comment( "Failed reading "+nameResult+ ". Amount of attempts: "+attempt+ ". Error #"+GetLastError()); //Report about failed reading } old_bars = Bars; // remember how many bars are known return(0); } //+------------------------------------------------------------------+ void read_n_draw(int handle_read) { int i=0; while ( !FileIsEnding(handle_read)) { ExtMap[i] = FileReadNumber(handle_read); i++; } ExtMap[i-1] = EMPTY_VALUE; } void write_data() { int handle; handle = FileOpen(nameData, FILE_CSV|FILE_WRITE,';'); if(handle < 1) { Comment("Failed creating "+nameData+". Error #", GetLastError()); return(0); } FileWrite(handle, ServerAddress(), Symbol(), Period()); // header FileWrite(handle, "DATE","TIME","HIGH","LOW","CLOSE","OPEN","VOLUME"); // header int i; for (i=length-1; i>=0; i--) { FileWrite(handle, TimeToStr(Time[i], TIME_DATE), TimeToStr(Time[i], TIME_SECONDS), High[i], Low[i], Close[i], Open[i], Volume[i]); } FileClose(handle); Comment("File "+nameData+" has been created. "+TimeToStr(TimeCurrent(), TIME_SECONDS) ); return(0); }
以下が私の最終結果です。誤りなくみなさんがそれを再現できることを願っています。
おわりに
本稿では、CSV ファイルを介した MetaTrader 4 と Matlab 間の連携を構築する方法を説明しました。この方法は特異なものでも最適なものでもありません。この方法の価値は、MetaTrader 4 とMatlab 以外のプログラムツールを使わず操作の特別な技能がなくてもデータ配列を交換できるのに役立つ、というものです。
MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/1489
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索