VQ

ここでは、インジケーター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内部で算出するには、次のようにします。

Dr.EADr.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;
   }
}

【VQsimple_2020.mq4】をダウンロード

にゃんたにゃんた

わからんにゃ・・・

※テンプレート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_CloseBarfalseの場合は値が0になるので、Direction[1]Direction[0]が反転した場合、言い換えると1本前と現在変動中のローソク足での上昇下降方向が異なればエントリーシグナル発生となるようにしています。

Entry_CloseBartrueの場合は値が1になるので、2本前と1本前のローソク足での上昇下降方向が異なればエントリーシグナル発生となるようにしています。

UseVQ_Exitによる決済シグナル判定もExit_ClosedBarで現在/1本前を切り替えるようにしています。

CalcVQ()関数

CalcVQ()関数は、軽量化したVQインジケーター【VQ_mod01.mq4】のOnCalculate()関数内のコードをEA内の関数用にカスタマイズしたものです。

軽量化したVQ【VQ_mod01.mq4】をダウンロード

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のパラメーターRealTimefalseの場合は、始値時点で新規ローソク足に矢印サインが表示されることがあるので、要注意です。

さらにさかのぼってVQラインの値を取得するようにするためには、VQ用配列の要素数を増やして、スライドさせる処理のところでfor文を使ってスライドさせるとよさそうです。

いろいろと改良してみてくださいね!
では、今回はこのへんでm(..)m