MT5でボリンジャーバンドの幅を調べるスクリプト
ボラティリティが無いとトレードしづらいので、トレード前にボリンジャーバンド(以下「BB」)の様子を見ています。しかし、手作業で主要通貨ペアのチャートを見るのは手間です。
そこで、BBの幅をエキスパートに出力するスクリプトを書いてみました。
なお、幅は(ポイント単位ではなく)pips単位で出力されます。
この記事に載っているコードは、次のようなセッティングになっています。
・計算するのは現在足時点 ・計算期間は20 ・BBの偏差は2 ・対象は1時間足 ・BBの幅が40pips以上の通貨ペアを出力
// BBの幅を調べるスクリプト // 1時間足、期間 20、2シグマ、対象は現在足、pips数で出力 // BBの幅が40pips以上の通貨ペアを出力 #property copyright "Copyright 2024, PHILOJUAN." #property version "1.00" #property strict #property script_show_inputs #define NUM_SYMBOL 28 // 通貨ペアの個数 input int InpTargetBar = 0; // 対象とするbar (0は現在足, 1は1本前) input int InpPeriod = 20; // BBの計算期間 input double InpDeviation = 2.0; // BBの偏差 input ENUM_TIMEFRAMES InpTimeFrame = PERIOD_H1; // 時間軸 input int InpTargetWidth = 40; // 基準値(BB幅 N pips以上を抽出) // 通貨ペア名配列 string symbols[NUM_SYMBOL] = { "AUDCAD", "AUDCHF", "AUDJPY", "AUDNZD", "AUDUSD", "CADCHF", "CADJPY", "CHFJPY", "EURAUD", "EURCAD", "EURCHF", "EURGBP", "EURJPY", "EURNZD", "EURUSD", "GBPAUD", "GBPCAD", "GBPCHF", "GBPJPY", "GBPNZD", "GBPUSD", "NZDCAD", "NZDCHF", "NZDJPY", "NZDUSD", "USDCAD", "USDCHF", "USDJPY" }; void OnStart() { int count = 0; for(int i = 0; i < NUM_SYMBOL; i++) { double width = ReturnPipsOfBBWidth(symbols[i], InpTargetBar, InpPeriod, InpDeviation, InpTimeFrame); if(width >= InpTargetWidth) { count++; printf("[%s][%s] BBの幅(pips) = %s", symbols[i], TimeFrameToString(InpTimeFrame), DoubleToString(width, 1)); } } if(count == 0) printf("BB幅 %d pips以上の通貨ペアはありませんでした", InpTargetWidth); } //----------------------------------------------------------------------------------- // ボリンジャーバンドの幅をPips数で返す関数(範囲は1シグマから3シグマまで) //----------------------------------------------------------------------------------- double ReturnPipsOfBBWidth(string symbol, // 通貨ペア名 int startPosition, // 対象(チャートの右端からN本前) int calcPeriod, // 計算期間 double sigma, // シグマ (2.0や3.0等) ENUM_TIMEFRAMES tf // 時間軸 (PERIOD_M30等) ) { //---------------------- // パラメーターチェック //---------------------- int barMax = Bars(symbol, tf) - 1; // 最古足 int requiredBar = startPosition + calcPeriod; // 計算に使用するbarのうち一番古いもの if(startPosition < 0 || startPosition > barMax || barMax < requiredBar || calcPeriod < 2 || sigma < 1.0 || sigma > 3.0) { Print("[ReturnPipsOfBBWidth Function] parameter is wrong."); return -1.0; } //---------------------- // BBの計算 //---------------------- double sum = 0.0; int endPosition = startPosition + calcPeriod - 1; int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS); // [memo] SYMBOL_DIGITSはintを返すのでint型にキャスト for(int i = startPosition; i <= endPosition; i++) { sum += iClose(symbol, tf, i); } double avg = sum / calcPeriod; sum = 0.0; // 変数をリセット for(int i = startPosition; i <= endPosition; i++) { sum += MathPow((iClose(symbol, tf, i) - avg), 2); } double variance = sum / calcPeriod; // 分散 double deviation = MathSqrt(variance); // 偏差 double upper = avg + sigma * deviation; // Upperバンド double lower = avg - sigma * deviation; // Lowerバンド double diff = upper - lower; // バンド幅 double multiplier = MathPow(10, digits); // 乗数 diff = diff * multiplier / 10; // バンド幅(Pips単位) return diff; } //----------------------------------------------------------------------------------- // 時間足を「H1」等の短縮表現に変換する関数 //----------------------------------------------------------------------------------- // 例 PERIOD_H4 → H4 string TimeFrameToString(ENUM_TIMEFRAMES intTF) { if(intTF == PERIOD_CURRENT) intTF = Period(); return StringSubstr(EnumToString(intTF), 7); }
MT5のインディケーターが出力する値に関する疑問
この誤差は何?
EAの中で平均足を計算するコードを作成し、動作チェックを行いました。具体的には、EAで計算した平均足の値と、MT5のHeiken_Ashiインディケーターがデータウィンドウに表示した値を比較しました。
(備考)MT5においては、Heikin_Ashiではなく、Heiken_Ashiとなっています。
2023年のヒストリカルデータから生成したGBPUSDの15分足チャートを使って比較したところ、場合によって1ポイントの誤差が生じていることに気づきました。
// EAから出力した値 2023.10.31 22:45 close = 1.21531 2023.10.31 23:00 close = 1.21511 // Heiken_Ashiインディケーターがデータウィンドウに表示した値 2023.10.31 22:45 close = 1.21531 2023.10.31 23:00 close = 1.21512
23:00の行を見ると、EAから出力した値とHeiken_Ashiインディケーターがデータウィンドウに表示した値が異なっています。
切上げも四捨五入もされていない?
平均足の終値は、その足の4値(始値、高値、安値、終値)の平均値です。そこで、23:00の4値を調べて、電卓で計算してみました。計算結果は「1.215115」でした。
この結果を見た時は「MT5のHeiken_Ashiインディケーターでは、小数点第6位を見て切上げまたは四捨五入を行っている」と考えました。
しかし、そうではありませんでした。
22:45のローソク足の4値を元に平均値を電卓で計算したら、結果は「1.215315」でした。もしMT5のHeiken_Ashiインディケーターが切上げまたは四捨五入を行っているのなら、データウィンドウに表示される値は「1.21532」となるはずです。しかし、データウィンドウに表示されている値は以下のとおりでした。
// Heiken_Ashiインディケーターがデータウィンドウに表示した値 2023.10.31 22:45 close = 1.21531
これを見ると、切上げも四捨五入もされていません。
以下は、電卓で計算した値です。
// 4値を元に電卓で計算した値 2023.10.31 22:45 close = 1.215315 2023.10.31 23:00 close = 1.215115
4値の中身に問題は無し
念のため4値の中身も調べました。私がEAで計算する際に用いた4値と、MT5のHeiken_Ashiインディケーターが計算するのに用いた4値との間に誤差があるのだろうか、と。
しかし、Print文を用いて出力したログを見ると、EAの計算でもHeiken_Ashiインディケーターの計算でも同じ4値が使用されていました。
ついでに書いておきますと、データを出力する際にはDoubleToString(値, _Digits)を使用していました。このためGBPUSDの場合、小数点第5位までしか表示されません。
もし4値データに小数点第6位や第7位が存在する場合、それらが計算結果に影響を与えた可能性があります。
そこで、この実験に使用したヒストリカルデータをチェックしました。しかし、始値、高値、安値、終値のデータはいずれも小数点第5位までで、第6位以下は存在しませんでした。
実験したこと
Heiken_Ashiインディケーターのコードを見たところ、「切上げ、切捨て、四捨五入」といった処理を行っているようには見えなかったので、バッファに入れる数値によって出力される結果が変わっているのかなと想像しました。
Heiken_Ashiインディケーターのコードには以下の部分があります。
double ha_open = (ExtOBuffer[i - 1] + ExtCBuffer[i - 1]) / 2; double ha_close = (open[i] + high[i] + low[i] + close[i]) / 4; double ha_high = MathMax(high[i], MathMax(ha_open, ha_close)); double ha_low = MathMin(low[i], MathMin(ha_open, ha_close)); ExtLBuffer[i] = ha_low; ExtHBuffer[i] = ha_high; ExtOBuffer[i] = ha_open; ExtCBuffer[i] = ha_close;
このうち、最後の4行の右辺を(変数ではなく)具体的な数値に置き換えて、データウィンドウに表示される値を見てみました。
// パターンA 小数点第6位は全て5で、第5位を1~4とした場合 ExtOBuffer[i] = 1.215115; → 1.21512 切上げ or 四捨五入? ExtHBuffer[i] = 1.215125; → 1.21513 切上げ or 四捨五入? ExtLBuffer[i] = 1.215135; → 1.21514 切上げ or 四捨五入? ExtCBuffer[i] = 1.215145; → 1.21514 切捨て? // パターンB 小数点第5位・6位は全て15で、第4位を1~8とした場合 ExtOBuffer[i] = 1.215115; → 1.21512 切上げ or 四捨五入? ExtHBuffer[i] = 1.215215; → 1.21522 切上げ or 四捨五入? ExtLBuffer[i] = 1.215315; → 1.21531 切捨て? ExtCBuffer[i] = 1.215415; → 1.21541 切捨て? ExtOBuffer[i] = 1.215515; → 1.21551 切捨て? ExtHBuffer[i] = 1.215615; → 1.21561 切捨て? ExtLBuffer[i] = 1.215715; → 1.21572 切上げ or 四捨五入? ExtCBuffer[i] = 1.215815; → 1.21582 切上げ or 四捨五入?
矢印より右側の数値がデータウィンドウに表示された値です。
いずれも小数点第5位がどのように変化するのかをチェックしました。
パターンAを見ると、小数点第6位が全て同じ値にも関わらず、第5位に生じる影響が一貫していません。
パターンBでは、第6位を全て5、第5位を全て1にして、第4位を変化させてみました。しかし、こちらにも一貫性が無いように見えます。
念のため、代入先のバッファを入れ替えてみました(たとえば、ExtOBufferに代入する代わりに、ExtCBufferに代入)。しかし、代入先バッファが異なることによる影響は無さそうでした。
よって、どんな数値を代入するかによって結果が変わることは間違いなさそうですが、そこにどんな規則性があるのかは不明です。
もしかしたら、10進法で考えるのではなく、ビット演算など他の次元で考えたら答えが得られるかもしれませんが、自分にはその技量がありません(汗)。
MacでVimのcolorschemeが反映しない
初歩的なミスで少し時間を失いました。
Windows上で使っていたcolorschemeファイルをMac上に移動して
.vim/colors
の中に配置しましたが、ファイル内容が反映されませんでした。
しばらく考えた後、Windows用の改行コードのままになっていることに気づきました。そこで、以下のようにお決まりの手順で改行コードを修正しました。
1. Mac上でcolorschemeファイルを開く。 2. 不要な改行コードを可視化するために次のコマンドを実行 :e ++ff=unix 3. sコマンドで ^M を除去 :%s/^M//g なお、^Mは特殊な文字なので「CTRL + V CTRL + M」で入力します。
MT5でspreadを取得する際の注意点
MT5でspreadの値を取得する方法はいくつかありますが、以下の記事によると、取得する方法によって値が異なる場合があるようです。
https://fmdsm.blog.fc2.com/blog-entry-94.html
上記記事の内容から「MqlRates.spread」「iSpread()」「spread[]」を使って取得したspreadの値は余り信用できないので、以下の2つを使って調べた方が無難だと感じました。
SymbolInfoInteger SymbolInfoTick
spreadの値に疑問を感じたのは、OnCalculate関数の中にあるspread[]を使ってみたからです。そこで、ちょっと実験をしてみました。
実験したこと
以下のコードを実行してみました。
int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { ArraySetAsSeries(spread, true); double iclose = iClose(NULL, PERIOD_M5, 0); double sp = spread[0] * _Point; double symbolinfo_ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); double symbolinfo_bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); static int count = 0; if(count == 0) { printf("iclose = %f", iclose); printf("symbolinfo_bid = %f", symbolinfo_bid); printf("symbolinfo_ask = %f", symbolinfo_ask); printf("spread = %f", sp); printf("ask - bid = %f", symbolinfo_ask - symbolinfo_bid); count++; } return(rates_total); }
次の結果がエキスパートに表示されました。
(各行の先頭にある〇行目は説明の便宜上、後付けしました)
1行目 iclose = 0.855450 2行目 symbolinfo_bid = 0.855450 3行目 symbolinfo_ask = 0.855480 4行目 spread = 0.000000 5行目 ask - bid = 0.000030
1行目と2行目はBid価格です。
3行目はAsk価格です。
spreadはAsk価格とBid価格の差です。3行目までを見るとAsk価格とBid価格に差があるのですが・・・、4行目を見るとspreadは0です。
このことから、spread[0]の値は正しくないと思われます(ただし、上記コードで取得したAsk価格やBid価格が正しければ、の話ですが)。
そこで、spread[0]の代わりにSymbolInfoIntegerでspreadの値を取得してみました。
それが以下のコードです。
int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { ArraySetAsSeries(spread, true); double iclose = iClose(NULL, PERIOD_M5, 0); double sp = SymbolInfoInteger(_Symbol, SYMBOL_SPREAD) * _Point; double symbolinfo_ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); double symbolinfo_bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); static int count = 0; if(count == 0) { printf("iclose = %f", iclose); printf("symbolinfo_bid = %f", symbolinfo_bid); printf("symbolinfo_ask = %f", symbolinfo_ask); printf("spread = %f", sp); printf("ask - bid = %f", symbolinfo_ask - symbolinfo_bid); count++; } return(rates_total); }
結果はこうなりました。
1行目 iclose = 0.855440 2行目 symbolinfo_bid = 0.855440 3行目 symbolinfo_ask = 0.855490 4行目 spread = 0.000050 5行目 ask - bid = 0.000050
今度はspreadの値が、Ask価格とBid価格の差になっています。
よって、このspreadの値は正しいと思われます。
MacのVimでフォント設定
Debianで使っているvimrcファイルの一部をMac上のvimrcにコピペしたら、フォントに関するエラーが表示されました。
エラーが表示された時の設定
set guifont=HackGen\ 13 guifontwide=HackGen\ 13
調べたところ、WindowsやMacでは次のようにする必要があると分かりました。
set guifont=HackGen:h13 guifontwide=HackGen:h13
ざっくり言うと、Linuxではフォント名とフォントサイズの間を「\ 」(バックスラッシュとスペース)にして、WindowsやMacではフォント名とフォントサイズの間を「:h」にします。
まとめると、次のとおりです。
Linuxの場合 set guifont=フォント名\ フォントサイズ (半角文字) set guifontwide=フォント名\ フォントサイズ (全角文字) WindowsやMacの場合 set guifont=フォント名:hフォントサイズ (半角文字) set guifontwide=フォント名:hフォントサイズ (全角文字)
Macのターミナルでコマンドプロンプトを少し変更
Macのターミナルは、デフォルトだとコマンドプロンプトがちょっと寂しい状況です(「デフォルトだと」と書きつつzshからbashに変更してありますが)。 白地の背景に黒い文字のみで、カレントディレクトリ名は表示されているものの、パスが表示されていません。
そこで、
hoge@Neko:~/Documents/buta$
のように現在位置をパスで表示して、ちょっと色を付けることにしました。
下記のように、コマンドプロンプトの色や書式を変更するにはPS1という変数に内容をセットします。
export PS1='\e[32m\u@\h\e[0m:\e[38;5;56m\w\e[0m$ '
パーツの説明を書いておきます。
\u はユーザー名 \h はホスト名(Macの名前) \w はカレントディレクトリのパス
次に色の説明です。
\e[32m とすることで、それ以降の文字色がグリーンになります。 この結果、\u@\hの部分がグリーンになります。 \e[0m とすることで、それ以降の文字色が黒になります。 この結果、 : が黒になります。 \e[38;5;56m とすることで、それ以降の文字色がパープルになります。 この結果、\wの部分がパープルになります。 \e[0m とすることで、それ以降の文字色が黒になります。 この結果、 $ が黒になります。
\e[38;5;56m だけ少し分かりにくいので補足します。
\e[38はこの後に2つの引数を取ります。ex. \e[38;N;Nm
1つ目の引数を5にすると、2つ目の引数で0〜255を使って色を指定できます。
\e[38;5;56m
この場合、0〜255のうち、56番目の色を指定しています。
しかし、それぞれがどんな色か分からないので以下のスクリプトを使って色を出力してみました。
for ((i = 0; i <= 255; i++)); do printf '\e[38;5;%dm%d ' $i $i done printf '\e[0m' echo
出力結果は次のとおりです。
これを参考にして56番目の色を選びました。