サインツールの書き方と勝率の表示
巷にあふれるサインツールの書き方を学習する。
サインツールとは
一般的にサインツールは、カスタムインジケータだ。
どこで売買を行うか上矢印 or 下矢印で表示する。その際アラートが鳴ることもある。
自動売買は行わず、トレードは使用者の裁量に委ねている。
サインの出し方は主に2通りあると考えられる。
1つは、インジケータバッファを用いて矢印を表示するもの。
もう1つは、矢印のオブジェクトを指定した位置に描画していくもの。
前者はインジケータバッファにサインのデータが入っているため、サインツールのソースコードが手元に無くても、他のカスタムインジケータやEAなどからデータの参照・使用が容易になる。
そのため、そのサインツールをEA化し、自由にバックテストで回すということも非常に簡単に行える。
また、矢印はインジケータバッファなので、インジケータを取り除けば矢印も一緒に消える。
後者のオブジェクトを描画するタイプの場合であっても、オブジェクトが新しく生成されたかどうかを監視すればEA化することはできるだろうが、EAにおける一般的なインジケータの利用とは異なる事から、かなり億劫に感じる。
今回は前者のタイプを書いてみる。
ロジックの定義と注意点
ここでは単純なサインツールの完成を目標にする。
ロジック検証ではないので、ルールは単純なものにして、レートがボリンジャーバンドを抜けたら逆張りサインを出すことにする。
以下のように定義。
- そのバーの始値がボリンジャーバンド内に収まっており、現在値(Close)がボリンジャーバンドを超えた時にサインを出す。
- サインを出した後に、そのバーが条件を満たさなくなった場合はサインを消す。
バーが確定した際に条件を満たしていればサインを残す。 - 過去チャートについては、全てのバーが確定しているため、始値/終値を確認してサインを置く。
- サインが置かれて確定した後は、以下の条件が満たされるまで新しいサインは出さない。
上方向サイン(レートは下降)を出した後は、陽線でもってボリンジャーバンド内に戻る。
下方向サイン(レートは上昇)を出した後は、陰線でもってボリンジャーバンド内に戻る。
※ボリンジャーバンドを抜けたバーの次バーの始値がボリンジャーバンド内に位置するケースがあるため。
※バンド内に戻るどころか突き抜けていった場合は無視。再度バンド内に値が落ち着くまで待機する。
サインツールを書く際の注意点として、基本的にバーを未来から過去へ遡ってはいけない。
逆走したサインツールを書くと、大体の場合において勝率が非常に高くなる。
未来から順に見るということは、本来分からないはずの未来のバーが、探している条件を満たしていないことを確認してしまうことになるためだ。
MAのようなカスタムインジケータであれば、対象のポジションから過去何本分かを計算すれば良いだけなので問題無いが、サインツールの場合は時系列を守らなければならない。
逆走してることを意識すれば書けなくは無いと思うが、多分面倒なのでオススメしない。
バッファを使ったサインツールを書く
まずは、確定したバーに対してサインを置いていく記述を行う。
分かりやすくするために、確定したバーと確定していないバーのそれぞれに対する処理は、if文で完全に分離させる。
//MT4用
#property copyright "nisai"
#property link "https://nisaifx.com"
#property version "1.00"
#property strict
#property indicator_chart_window
#property indicator_buffers 2
#property indicator_width1 2
#property indicator_color1 DeepSkyBlue
#property indicator_width2 2
#property indicator_color2 OrangeRed
input int bandsPeriod=20;
input double bandsDeviation=2.0;
input int bandsShift=0;
input ENUM_APPLIED_PRICE bandsAppliedPrice=PRICE_CLOSE;
double buySignBuffer[];
double sellSignBuffer[];
int state=0; //1:買いサイン後, -1:売りサイン後
int OnInit(){
SetIndexBuffer(0,buySignBuffer);
SetIndexStyle(0,DRAW_ARROW);
SetIndexArrow(0,233); //Up arrow
SetIndexBuffer(1,sellSignBuffer);
SetIndexStyle(1,DRAW_ARROW);
SetIndexArrow(1,234); //Down arrow
return(INIT_SUCCEEDED);
}
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[]){
int position=rates_total-1;
if(prev_calculated!=0){
position=rates_total-prev_calculated;
}
for(int i=position;i>=0&&!IsStopped();i--){
double upper=iBands(Symbol(),Period(),bandsPeriod,bandsDeviation,bandsShift,bandsAppliedPrice,MODE_UPPER,i);
double lower=iBands(Symbol(),Period(),bandsPeriod,bandsDeviation,bandsShift,bandsAppliedPrice,MODE_LOWER,i);
if(i==0){
//確定していないバーの処理
}else{
//確定したバーの処理
if(state==0){
if(open[i]<upper && open[i]>lower){
if(close[i]>upper){
sellSignBuffer[i]=high[i]+20*Point(); //高値より少し上に売りサインを置く
state=-1;
}else if(close[i]<lower){
buySignBuffer[i]=low[i]-20*Point(); //安値より少し下に買いサインを置く
state=1;
}
}
}else if(state==1){
//買いサイン後
if(open[i]<close[i] && close[i]>lower){
state=0;
}
}else if(state==-1){
//売りサイン後
if(open[i]>close[i] && close[i]<upper){
state=0;
}
}
}
}
return rates_total;
}
チャートに適用してみる。

意図した通りにサインが置かれている。
続いて、インジケーターを適用した後の値動き(確定していないバー)でのサイン表示を行う。
バーが確定していなくても、現在レートはcloseで参照できる(Bidでも良い)。
レートがボリンジャーバンドを抜けたり戻ったりする度にサインを出したり消したりするので、stateの更新(サインの確定)は、そのバーが確定した後に行う。
バーが確定した際は、rates_totalとprev_calcuratedの差が”1”になるため、上記「確定したバーの処理」に移り、現在バーから1本前のバーに対する処理が1度だけ行われる。
その際、もしサインが置かれたままであればstateを更新する。
下記、ハイライト箇所を追記。
//MT4用
#property copyright "nisai"
#property link "https://nisaifx.com"
#property version "1.00"
#property strict
#property indicator_chart_window
#property indicator_buffers 2
#property indicator_width1 2
#property indicator_color1 DeepSkyBlue
#property indicator_width2 2
#property indicator_color2 OrangeRed
input int bandsPeriod=20;
input double bandsDeviation=2.0;
input int bandsShift=0;
input ENUM_APPLIED_PRICE bandsAppliedPrice=PRICE_CLOSE;
double buySignBuffer[];
double sellSignBuffer[];
int state=0; //1:買いサイン後, -1:売りサイン後
int OnInit(){
SetIndexBuffer(0,buySignBuffer);
SetIndexStyle(0,DRAW_ARROW);
SetIndexArrow(0,233); //Up arrow
SetIndexBuffer(1,sellSignBuffer);
SetIndexStyle(1,DRAW_ARROW);
SetIndexArrow(1,234); //Down arrow
return(INIT_SUCCEEDED);
}
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[]){
int position=rates_total-1;
if(prev_calculated!=0){
position=rates_total-prev_calculated;
}
for(int i=position;i>=0&&!IsStopped();i--){
double upper=iBands(Symbol(),Period(),bandsPeriod,bandsDeviation,bandsShift,bandsAppliedPrice,MODE_UPPER,i);
double lower=iBands(Symbol(),Period(),bandsPeriod,bandsDeviation,bandsShift,bandsAppliedPrice,MODE_LOWER,i);
if(i==0){
//確定していないバーの処理
if(state==0){
if(open[i]<upper && open[i]>lower){
if(close[i]>upper){
sellSignBuffer[i]=high[i]+20*Point();
}else if(close[i]<lower){
buySignBuffer[i]=low[i]-20*Point();
}else{
sellSignBuffer[i]=0;
buySignBuffer[i]=0;
}
}
}
}else{
//参照しているポジション(i)に既にサインが置かれている場合はstateを更新
if(sellSignBuffer[i]==high[i]+20*Point()){
state=-1;
return rates_total;
}else if(buySignBuffer[i]==low[i]-20*Point()){
state=1;
return rates_total;
}
//確定したバーの処理
if(state==0){
if(open[i]<upper && open[i]>lower){
if(close[i]>upper){
sellSignBuffer[i]=high[i]+20*Point(); //高値より少し上に売りサインを置く
state=-1;
}else if(close[i]<lower){
buySignBuffer[i]=low[i]-20*Point(); //安値より少し下に買いサインを置く
state=1;
}
}
}else if(state==1){
//買いサイン後
if(open[i]<close[i] && close[i]>lower){
state=0;
}
}else if(state==-1){
//売りサイン後
if(open[i]>close[i] && close[i]<upper){
state=0;
}
}
}
}
return rates_total;
}
これで、リアルタイムチャートでもサインが表示されるようになった。
単純なサインツールであれば、以上で完了になる。
勝敗条件と注意点
続いて勝率を計算してテキストオブジェクトで表示させてみる。
勝率を計算するなら、決済条件を決めなければならない(そこまで決まっていればEAで良いのではという話もある)。
前述のソースコードに決済条件を加える。
決済条件があるという事は、仮想であってもポジション保有中かどうか判断しなければならない。
また、決済条件によっては複数のポジションを保有するケースも考えられるし、逆にポジションを1つに限定したいこともあるかもしれない。
この辺りはやりたいようにやってもらうとして、今回は前述のソースコードのままで置かれたサイン全ての勝敗をカウントする。
明確に勝敗が分かるよう、買いサインなら次の足が陽線かどうか、売りサインなら次の足が陰線かどうかで勝敗を決める。
厄介なのは、例えば±固定pipsで決済するとした場合、過去足に関しては固定pipsの値動きをどちらも満たしてしまうケースが存在する点だ。
SL/TPを置いていると考えた場合、先にどちらに到達したのかが不明なため、勝敗を正しく判定できない。
とりあえず今回は、陽線/陰線で判定するので明確だが、FXというよりバイナリーのツールっぽい感じになる。
なお、判定するバーが完全な十字線だった場合は負けとする。
勝敗判定と勝率表示の処理を書く
下記、ハイライト部分を追記。
//MT4用
#property copyright "nisai"
#property link "https://nisaifx.com"
#property version "1.00"
#property strict
#property indicator_chart_window
#property indicator_buffers 4
#property indicator_width1 2
#property indicator_color1 DeepSkyBlue
#property indicator_width2 2
#property indicator_color2 OrangeRed
input int bandsPeriod=20;
input double bandsDeviation=2.0;
input int bandsShift=0;
input ENUM_APPLIED_PRICE bandsAppliedPrice=PRICE_CLOSE;
double buySignBuffer[];
double sellSignBuffer[];
int state=0; //1:買いサイン後, -1:売りサイン後
double win=0;
double lose=0;
int OnInit(){
SetIndexBuffer(0,buySignBuffer);
SetIndexStyle(0,DRAW_ARROW);
SetIndexArrow(0,233); //Up arrow
SetIndexBuffer(1,sellSignBuffer);
SetIndexStyle(1,DRAW_ARROW);
SetIndexArrow(1,234); //Down arrow
return(INIT_SUCCEEDED);
}
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[]){
int position=rates_total-1;
if(prev_calculated!=0){
position=rates_total-prev_calculated;
}
for(int i=position;i>=0&&!IsStopped();i--){
double upper=iBands(Symbol(),Period(),bandsPeriod,bandsDeviation,bandsShift,bandsAppliedPrice,MODE_UPPER,i);
double lower=iBands(Symbol(),Period(),bandsPeriod,bandsDeviation,bandsShift,bandsAppliedPrice,MODE_LOWER,i);
if(i==0){
//確定していないバーの処理
if(state==0){
if(open[i]<upper && open[i]>lower){
if(close[i]>upper){
sellSignBuffer[i]=high[i]+20*Point();
}else if(close[i]<lower){
buySignBuffer[i]=low[i]-20*Point();
}else{
sellSignBuffer[i]=0;
buySignBuffer[i]=0;
}
}
}
}else{
//参照しているポジション(i)の1つ前のバーにサインが置かれている場合は勝敗判定
if(i<rates_total-1){
if(sellSignBuffer[i+1]==high[i+1]+20*Point()){
if(open[i]>close[i]){
win++;
}else{
lose++;
}
}else if(buySignBuffer[i+1]==low[i+1]-20*Point()){
if(open[i]<close[i]){
win++;
}else{
lose++;
}
}
}
//参照しているポジション(i)に既にサインが置かれている場合はstateを更新
if(sellSignBuffer[i]==high[i]+20*Point()){
state=-1;
return rates_total;
}else if(buySignBuffer[i]==low[i]-20*Point()){
state=1;
return rates_total;
}
//確定したバーの処理
if(state==0){
if(open[i]<upper && open[i]>lower){
if(close[i]>upper){
sellSignBuffer[i]=high[i]+20*Point(); //高値より少し上に売りサインを置く
state=-1;
}else if(close[i]<lower){
buySignBuffer[i]=low[i]-20*Point(); //安値より少し下に買いサインを置く
state=1;
}
}
}else if(state==1){
//買いサイン後
if(open[i]<close[i] && close[i]>lower){
state=0;
}
}else if(state==-1){
//売りサイン後
if(open[i]>close[i] && close[i]<upper){
state=0;
}
}
}
}
ObjectCreate("winRate",OBJ_LABEL,0,0,0);
ObjectSet("winRate",OBJPROP_CORNER,1);
ObjectSet("winRate",OBJPROP_XDISTANCE,10);
ObjectSet("winRate",OBJPROP_YDISTANCE,10);
ObjectSetText(
"winRate",
IntegerToString((int)win)
+"/"
+IntegerToString((int)(lose+win))
+"("
+IntegerToString((int)MathRound(win/(lose+win)*100))
+"%)",
14,
"MS ゴシック",
clrWhite
);
return rates_total;
}
ストラテジーテスターで動かしてみる。

右上に勝率が表示された。
こんにちは、MQLの勉強に大変有用な情報をありがとうございます。
今回のボリンジャーバンドの制御にプラスして、ストキャスティクスオシレーターによる制御を追加する場合、どのような記載になるのでしょうか?
flameさん
こんにちは。コメントありがとうございます。
ストキャスティクスを使う場合は、
例えば53行目の次の行に、
double sto = iStochastic(Symbol(), Period(), 5, 3, 3, MODE_SMA, STO_LOWHIGH, MODE_MAIN, i);
と追記して、ストキャスティクスの値を取得します。
続いて、ストキャスティクスの値をどのように判定するのかによりますが、
例えば、既存のボリンジャーバンドの判定条件に単純にプラスするなら、
if(close[i]>upper){
↓
if(close[i]>upper && sto>70){
のようにすれば、ストキャスティクスの条件を加えてサイン表示することが出来ます。
参考になれば幸いです。
nisaiさん、こんばんは。
教えていただき、ありがとうございます!(この時間に即レス、驚きました:D)
まさしく追加したかったストキャスMainのレベル越えでの制御で、大変助かりました。
教えていただいた通り書いてみたところ、思ってた通り動きました!感動です
MQLの勉強を始めたばかりなのですが、このボリバン+ストキャスに、さらに追加でいろいろな制御を追加したいと思い、頑張っています。
例えば、追加で今足が前足の50%以下であり、かつ直近高値・安値を超えていない場合にサインを出すといった制御を追加したいと考えております。
こんばんは。訳わからん生活リズムしてることがバレましたね笑
判定条件を決めたら、そのためのデータを取って比較、で色んなパターンを試せますね。
是非学習を続けて良いサインツールを作成してください。
どう書けば良いのか分からず困った時は、ChatGPTなどのLLMに聞くのも非常に有効です。
どうしても詰まってしまうようであれば、またいつでも質問してください。
nisaiさん、こんにちは。
ストキャスの条件に加えて、下記のように現在の足と前の足の大きさ判定を受験に加えたところ、なぜかサインと勝率が表示されなくなってしまいました。
for(int i=position;i>=0&&!IsStopped();i–){
double upper=iBands(Symbol(),Period(),bandsPeriod,bandsDeviation,bandsShift,bandsAppliedPrice,MODE_UPPER,i);
double lower=iBands(Symbol(),Period(),bandsPeriod,bandsDeviation,bandsShift,bandsAppliedPrice,MODE_LOWER,i);
double sto = iStochastic(Symbol(), Period(), Kperiod, Dperiod, Slowing, MODE_SMA, STO_LOWHIGH, MODE_MAIN, i);
double currentCandleRange = high[i] – low[i];
double previousCandleRange = high[i+1] – low[i+1];
bool isBigCandle = (currentCandleRange >= previousCandleRange * 1.1);
//判定
if(close[i]>upper && sto>70 && isBigCandle){
chatGPTも使ったりしていろいろやってみたのですが、どうやっても解消できず行き詰っています・・・
大変申し訳ないのですが、アドバイス頂けたら助かります。
よろしくお願いいたします。
差分のソースコードありがとうございます。
しかしこれだと、どの段階でエラーになっているのか判断が付かないですね。
Discordやられているようでしたら、「fx.nisai」へフレンド申請送っていただければサポートします。
もしくはメール (fx.nisai@gmail.com) でも大丈夫です。現在のソースファイルを添付してお送りいただければ。