ここでは、インジケーターVQをEA内で算出する方法を解説します。
インジケーターVQについて知らない方は、過去記事【VQとVQ_EA】をご覧ください。
過去記事【VQとVQ_EA】へ
iCustom()関数を利用する方法
通常のやり方でiCustom()関数を利用する方法は次のようにします。
extern bool Crash = false; extern int TimeFrame = 0; extern int Length = 5; extern int Method = 3; extern int Smoothing = 1; extern int Filter = 5; extern int SignShift = 1;
VQのパラメーターとSignShiftというパラメータを設置して、OnTick()関数内で次のようにインジケーターVQのシグナルになりそうなインジケーターインデックスの値を取得します。
int = iCustom(NULL, TimeFrame, "VQ", Crash, 0, Length, Method, Smoothing, Filter, 6, SignShift);
しかし、VQ自体にいろいろな機能が付いているため、EA内で計算したほうが動作がスムーズになります。
VQをEA内部で算出する方法
VQをEA内部で算出するには、次のようにします。
(カタカタ、カタカタ・・・)ほい!
#property strict // 外部パラメーター extern double Lots = 0.01; extern double TakeProfit = 0.0; extern double StopLoss = 0.0; extern double Slippage = 1.0; extern string EA_Comment = "Sample EA"; extern int MagicNumber = 20191119; extern int MaxOrder_Buy = 1; extern int MaxOrder_Sell = 1; extern bool HedgeMode = false; extern string Indicator_Setting = " --- Indicator Setting"; extern int TimeFrame = 0; extern int Length = 5; extern int Method = 3; extern int Smoothing = 1; extern int Filter = 5; extern bool RealTime = true; extern bool Steady = false; extern bool Entry_ClosedBar = true; extern bool UseVQ_Exit = true; extern bool Exit_ClosedBar = false; // グローバル変数 double _point; int entry_bar_buy; int entry_bar_sell; // VQ用の配列 double SumVQ[3]; // VQラインの値 int Direction[3]; // VQの向き±1を格納 //+------------------------------------------------------------------+ //| EA稼働開始時に実行される関数 | //+------------------------------------------------------------------+ int OnInit() { // パラメーターをPips入力にする為の処理 _point = Point; if (Digits % 2 == 1) { _point *= 10; Slippage *= 10; } ArrayInitialize(Direction, 0); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| EA稼働終了時に実行される関数 | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { } //+------------------------------------------------------------------+ //| ティック毎に実行される関数 | //+------------------------------------------------------------------+ void OnTick() { int i; // 保有ポジションを確認 ------------------------------------------ int pos_cnt_buy = 0; int pos_cnt_sell = 0; for (i=0; i<OrdersTotal(); i++) { if (OrderSelect(i, SELECT_BY_POS) == false) return; if (OrderSymbol() != Symbol() || OrderMagicNumber() != MagicNumber) continue; if (OrderType() == OP_BUY) pos_cnt_buy++; if (OrderType() == OP_SELL) pos_cnt_sell++; } // シグナル判定 ------------------------------------------------- CalcVQ(); // エントリー用シグナル int entry_sign = 0; if (Direction[Entry_ClosedBar+1] == -1 && Direction[Entry_ClosedBar] == 1) entry_sign = 1; if (Direction[Entry_ClosedBar+1] == 1 && Direction[Entry_ClosedBar] == -1) entry_sign = -1; // ポジション保有時の処理 -------------------------------------- // 決済判定 bool exit_ok_buy = false; if (pos_cnt_buy > 0) if (UseVQ_Exit == true && Direction[Exit_ClosedBar] == -1) exit_ok_buy = true; bool exit_ok_sell = false; if (pos_cnt_sell > 0) if (UseVQ_Exit == true && Direction[Exit_ClosedBar] == 1) exit_ok_sell = true; if (exit_ok_buy == true || exit_ok_sell == true) { for (i=OrdersTotal()-1; i>=0; i--) { if (OrderSelect(i, SELECT_BY_POS) == false) continue; if (OrderSymbol() != Symbol() || OrderMagicNumber() != MagicNumber) continue; if (exit_ok_buy == true && OrderType() == OP_BUY) if (OrderClose(OrderTicket(), OrderLots(), OrderClosePrice(), (int)Slippage, clrYellow) == true) pos_cnt_buy--; if (exit_ok_sell == true && OrderType() == OP_SELL) if (OrderClose(OrderTicket(), OrderLots(), OrderClosePrice(), (int)Slippage, clrYellow) == true) pos_cnt_sell--; } } // エントリー注文処理 ----------------------------------- int ticket; double sl, tp; if (HedgeMode == false) { if (pos_cnt_buy > 0 || pos_cnt_sell == MaxOrder_Sell) entry_bar_sell = Bars; if (pos_cnt_sell > 0 || pos_cnt_buy == MaxOrder_Buy) entry_bar_buy = Bars; } // 買いエントリー判定 bool entry_ok_buy = false; if (pos_cnt_buy < MaxOrder_Buy) if (entry_bar_buy != Bars) if (entry_sign == 1) entry_ok_buy = true; // 買い注文処理 if (entry_ok_buy == true) { sl = 0; tp = 0; if (StopLoss > 0) sl = Ask - StopLoss * _point; if (TakeProfit > 0) tp = Ask + TakeProfit * _point; ticket = OrderSend(Symbol(), OP_BUY, Lots, Ask, (int)Slippage, sl, tp, EA_Comment, MagicNumber, 0, clrBlue); if (ticket > 0) entry_bar_buy = Bars; else Print("OrderSend(Buy) error."); } // 売りエントリー判定 bool entry_ok_sell = false; if (pos_cnt_sell < MaxOrder_Sell) if (entry_bar_sell != Bars) if (entry_sign == -1) entry_ok_sell = true; // 売り注文処理 if (entry_ok_sell == true) { sl = 0; tp = 0; if (StopLoss > 0) sl = Bid + StopLoss * _point; if (TakeProfit > 0) tp = Bid - TakeProfit * _point; ticket = OrderSend(Symbol(), OP_SELL, Lots, Bid, (int)Slippage, sl, tp, EA_Comment, MagicNumber, 0, clrRed); if (ticket > 0) entry_bar_sell = Bars; else Print("OrderSend(Sell) error."); } } void CalcVQ() { int i, limit; int now_bars = iBars(NULL, TimeFrame); // RealTimeがfalseの場合、バー1本につき1回のみ計算 if (RealTime == false) { static int last_calc_bar = 0; if (last_calc_bar == now_bars) return; last_calc_bar = now_bars; } if (Direction[1] == 0) // EA稼働開始直後など { limit = iBars(NULL, TimeFrame) - Length - 1; if (limit < 100) return; SumVQ[2] = iClose(NULL, TimeFrame, limit + 1); SumVQ[1] = SumVQ[2]; } else { static int last_check_bar = 0; if (last_check_bar == now_bars) limit = 0; else limit = 1; last_check_bar = now_bars; } int ma_method; if (Steady == true) ma_method = PRICE_MEDIAN; else ma_method = PRICE_CLOSE; for (i = limit; i >= 0; i--) { // base line double ma_high = iMA(NULL, TimeFrame, Length, 0, Method, PRICE_HIGH, i); double ma_low = iMA(NULL, TimeFrame, Length, 0, Method, PRICE_LOW, i); double ma_open = iMA(NULL, TimeFrame, Length, 0, Method, PRICE_OPEN, i); double ma = iMA(NULL, TimeFrame, Length, 0, Method, ma_method, i); double ma_smooth = iMA(NULL, TimeFrame, Length, 0, Method, ma_method, i + Smoothing); double max_deviation = MathMax(ma_high - ma_smooth, ma_smooth - ma_low); // VQ用配列のインデックス int index = 0; if (i > 0) { SumVQ[2] = SumVQ[1]; Direction[2] = Direction[1]; index = 1; } // 分母0の割り算を回避するための処理 if (MathMax(ma_high - ma_low, max_deviation) == 0) { SumVQ[index] = SumVQ[index + 1]; Direction[index] = Direction[index + 1]; continue; } double VQ = (ma - ma_smooth) / MathMax(ma_high - ma_low, max_deviation); VQ += (ma - ma_open) / (ma_high - ma_low); VQ *= 0.5; VQ = MathAbs(VQ) * (ma * 2 - ma_smooth - ma_open) * 0.5; SumVQ[index] = SumVQ[index + 1] + VQ; if (Filter > 0 && MathAbs(SumVQ[index] - SumVQ[index + 1]) < Filter * Point) SumVQ[index] -= VQ; // direction Direction[index] = Direction[index + 1]; if (SumVQ[index] - SumVQ[index + 1] > 0) Direction[index] = 1; if (SumVQ[index + 1] - SumVQ[index] > 0) Direction[index] = -1; } }
わからんにゃ・・・
※テンプレートEAをカスタマイズしたコードです。
今日は、まだ一度もExpert Advisorを作ったことがない人のためにMetatrader4用のテンプレートEAを作りました! 移動平均と終値を使った単純ルールです。エントリーのフィルタも移動平均を使った単純なもので …
EAで使用するVQ用の配列を宣言
VQの矢印サインは、VQラインが下降中の上昇で↑矢印、上昇中の下降で↓矢印が描画されます。EAでVQの状態を知るには、「VQラインの値」と「前回のサイン方向」が必要です。そのため、次のように2つのVQ用の配列をグローバル領域で宣言します。
double SumVQ[3]; int Direction[3];
配列SumVQ[]にはVQのラインの値を、配列Direction[]には上昇中か下降中かを判別できるように±1を格納するようにします。
現在変動中のローソク足・1本前のローソク足・2本前のローソク足でのそれぞれのVQの値と方向を格納するため、配列の要素数を3つにしています。
シグナル判定
OnTick()関数内のエントリーシグナル判定部分を次のようにしています。
CalcVQ(); int entry_sign = 0; if (Direction[Entry_ClosedBar+1] == -1 && Direction[Entry_ClosedBar] == 1) entry_sign = 1; if (Direction[Entry_ClosedBar+1] == 1 && Direction[Entry_ClosedBar] == -1) entry_sign = -1;
CalcVQ()関数を実行すれば、先ほど宣言した2つの配列に現在のVQの状態が反映されるようにすることにして、CalcVQ()関数を実行します。
Entry_CloseBarがfalseの場合は値が0になるので、Direction[1]とDirection[0]が反転した場合、言い換えると1本前と現在変動中のローソク足での上昇下降方向が異なればエントリーシグナル発生となるようにしています。
Entry_CloseBarがtrueの場合は値が1になるので、2本前と1本前のローソク足での上昇下降方向が異なればエントリーシグナル発生となるようにしています。
UseVQ_Exitによる決済シグナル判定もExit_ClosedBarで現在/1本前を切り替えるようにしています。
CalcVQ()関数
CalcVQ()関数は、軽量化したVQインジケーター【VQ_mod01.mq4】のOnCalculate()関数内のコードをEA内の関数用にカスタマイズしたものです。
VQのパラメーターRealTimeの機能を再現するために、次のように1度計算したローソク足では計算しないようにしています。
if (RealTime == false) { static int last_calc_bar = 0; if (last_calc_bar == now_bars) return; last_calc_bar = now_bars; }
次に、どこまでさかのぼって計算するかを変数limitに代入しています。
if (Direction[1] == 0) // EA稼働開始直後など { limit = iBars(NULL, TimeFrame) - Length - 1; if (limit < 100) return; SumVQ[2] = iClose(NULL, TimeFrame, limit + 1); SumVQ[1] = SumVQ[2]; } else { static int last_check_bar = 0; if (last_check_bar == now_bars) limit = 0; else limit = 1; last_check_bar = now_bars; }
for文内では、次のようにVQ用配列の参照・格納インデックスを決定します。シフト数0ではない場合、すなわち現在変動中のローソク足の計算ではない場合、VQ用配列のインデックスを1にして、もともと格納されていた値をインデックス2にスライドさせるようにしています。
int index = 0; if (i > 0) { SumVQ[2] = SumVQ[1]; Direction[2] = Direction[1]; index = 1; }
あとがき
VQのパラメーターRealTimeがfalseの場合は、始値時点で新規ローソク足に矢印サインが表示されることがあるので、要注意です。
さらにさかのぼってVQラインの値を取得するようにするためには、VQ用配列の要素数を増やして、スライドさせる処理のところでfor文を使ってスライドさせるとよさそうです。
いろいろと改良してみてくださいね!
では、今回はこのへんでm(..)m
慶次さん、こんにちは。
早速ダウンロードさせてもらいました(^0^)/
今回のファイルは、私にとってとても有益なEAです!
貴重なコードの公開ありがとうございます!