値幅、価格比、ボラティリティ

利食い幅、損切り幅、取引サイズを決めるとき、値幅、価格比、ボラティリティのうち、どれを基準として使うべきかを考えてみる。

値幅

先ず、値幅について考えてみる。値幅とは1円や100pipsといったものだ。

例えば、ドル円を1円で利食い、1万通貨で売買する戦略によって買ったとする。

勝てば1ドルが100円のときでも200円のときでも1万円の利益となる。

だが、利食うためには100円では1%、200円では0.5%の変動が必要で、2倍違う。100円のときのほうが利食うまでに時間がかかり、トレード機会も減るだろう。

価格比

次に、価格比について考えてみる。価格比とは価格の1%といったものだ。

例えば、価格の1%で利食い、1万通貨で売買する戦略によって買ったとする。

勝てば100円のときは1万円、200円のときは2万円の利益となる。

今度は200円のときのほうが利益が大きくなってしまう。「この戦略は1ドル200円のときにパフォーマンスがいい」と結論付けてはおかしなことになる。

そこで、価格の1%で利食い、100万円÷価格の取引サイズで売買する戦略によって買う、というように修正してみる。

100円のときは1円で利食い、1万通貨だから1万円の利益となる。

200円のときは2円で利食い、5000通貨だから1万円の利益となる。

これで、勝てば1ドルが100円のときでも200円のときでも1万円の利益となる。

ところで、ドル円が100円のときに、ある時期では1日に1%変動し、ある時期では2%変動するような場合も考えてみる。

再び価格の1%で利食い、100万円÷価格の取引サイズで売買する戦略によって買うとしよう。

価格がランダムウォークであると仮定すると、1日で1%変動するときは2%変動するのに約1.4日(√2日)かかる計算になる。

だが、1日で2%変動するときは2%変動するのにもちろん1日しかかからない。

すると1日で2%変動するときは1%変動するときより約1.4倍多くトレードできることになり、利益も増えることになる。

これはドル円が200円のときでも同じだ。

値幅のときと同様、やはりトレード機会の違いという問題が残る。

ボラティリティ

それでは最後に、ボラティリティについて考えてみる。ボラティリティとは1ATRといったものだ。

例えば、ATRを日足で計算して1ATRで利食い、100万円÷1ATR÷100の取引サイズで売買する戦略によって買うとしよう。

先ず、1ATRが1円だったとする。

すると取引サイズは1万通貨となるから、1万円の利益である。

1日で1ATR=1円変動するのにかかる時間は1日である。

次に、1ATRが2円だったとする。

すると取引サイズは5000通貨となるから、やはり1万円の利益である。

1日で1ATR=2円変動するのにかかる時間もやはり1日である。

こうなると、1日のボラティリティがどれくらいであろうと利益は変わらず、トレード機会も変わらない。

そして、これは価格が100円でも 200円でも同じである。

まとめ

利食い損切りをpips単位で、つまり値幅で決める人は多い。

だが、1ドルが100円のときでも200円のときでも1円、または100pipsで利食ったり、損切ったりするのには違和感を感じる。

そこで価格比はどうかと考える。

取引サイズも価格比に反比例させて調整するとよさそうだ。

だが、価格が同じ100円でも1日で1%変動する銘柄もあれば、2%変動する銘柄もあり、同じ銘柄でもあるときは1%変動し、あるときは2%変動するといったようにボラティリティに違いがあり、トレード機会にも影響する。

そこで、利食い幅、損切り幅をボラティリティに比例させ、取引サイズをボラティリティに反比例させると、利益、トレード機会ともに一定にできるのでよさそうだ。

だが、問題点もある。

同じ銘柄でもボラティリティの高い時期もあれば、低い時期もあるが、もしボラティリティATRで計測するなら、ATRの計算期間をどれくらいにするのがよいのか。

よく使われれる14がいいのか、足の種類も日足がいいのか、それとも最適化するのがいいのか。

だが、最適化はかえってシステムを不安定にしかねない。

また、利食い損切りを値幅で決めるというのは、ある意味、原始的なやり方だが、バックテストしてみると、意外とそのほうがパフォーマンスがいいということもある。

ドル円、クロス円を20銭で利食う習慣の人は価格が100円でも200円でも20銭で利食うものであり、また、ラウンドナンバーなどのように切りのいい数値で売買し、利食い、損切る人も多い。

相場も人間の習性に基づいて動いていく。

人間にそういう習性がある以上、原始的なやり方だからといってバカにもできない。

私個人はボラティリティ利食い幅、損切り幅、取引サイズを決めるのが好みだが、他の人もそうすべきだとまでは断言できない。

Ubuntuメモ

Ubuntuについて記事にするほどではないが、メモとして残しておきたいものをここでまとめておく。随時更新。

パッケージのインストール

sudo apt upgrade -y

を実行したとき、

以下のパッケージは保留されます

と出て、インストールが保留になるパッケージがある場合、とりあえず

sudo apt install -y [パッケージ名]

としてインストールしておけばよいようだ。

MQL5メモ

MQL5について記事にするほどではないが、メモとして残しておきたいものをここでまとめておく。ただし、プログラミング全般に関するメモも含む。随時更新。

外出先からEAを操作

実は私はVPSを使ったことがない。

安いノートパソコンをEA専用にして稼働している。

WindowsもHome Editionなのでリモートデスクトップにはできない。何かソフトを入れてやろうとも思わない。

もし外出先からスマホでEAを操作したいなら、EAにそういう機能を持たせるやり方もある。

例えば、

  • ドル円で1円の指値買い注文が入ったら自動売買をオフにする。
  • 2円だったら、エントリーは買いのみで売りはやらない。
  • 3円だったらエントリーは売りのみで買いはやらない。
  • 注文が削除されたら元に戻す。

といった機能をEAに持たせるのだ。

そうすればスマホから注文を入れたり、それを削除したりすることでEAを操作することができる。

指値注文を使わない利食い

指値注文の利食いを入れて最適化すると、パフォーマンスはイン期間では向上してもアウト期間ではかえって悪化することが多い。つまり再現性がない。

最適化した利食いにはイン期間で大勝したトレードの利益を取り切ろうとしてそれに引きずられる傾向があって、システムが不安定になる。その利食いはどうしても別の期間では「帯に短し襷に長し」となる。

それとは別に、利益を限定してしまうデメリットも大きい。

利食わないでいると相場が反転して負けトレードになってしまった、ということはよくある。また、「利食い千人力」などという格言が「早く利食え」と誘惑してくる。

確かに利食えば勝率は上がるが、長い目で見れば損益を減らしてしまうことのほうが多そうだ。利益を限定してしまうデメリットは少しばかり勝率が上がるくらいではカバーできないのだ。

指値注文の利食いと似たことをやりながら、なるべく利益を限定しない方法もある。

例えば、ある銘柄を買ったとする。

そのときの価格をentry_priceとする。説明の単純化のためにスプレッドは無視しよう。

また1本前の足の終値をclose1、2本前の足の終値をclose2とし、利食い幅は10(pips)としよう。

そこでエグジットの条件に

if(close2-entry_price<10 && close1-entry_price>10)
  決済注文;

を加えるのだ。

こうすると10pips以上の利益が確保されるが、場合によっては15pipsになったり、20pipsになったり、それ以上になったりすることもあり、利益は限定されない。

もし足が5分足なら、利益が10pipsを超えてもすぐにはエグジットせず、足が完成されるまで最大5分間は待つ。

その間に利益を伸ばしてもらうのだ。

もちろん、反転して損失となることもあるが、長い目で見れば、若干勝率は下げても利益を伸ばしたほうがいい。

このやり方は単に指値利食いするよりもアウト期間でのパフォーマンスが向上し、再現性もそれなりにあると思う。

それでも、10pipsの利益が100pipsになったり、200pipsになったりすることはまれで、利益が全く限定されないわけではない。

指値利食いするよりはましといったところだ。

スタイラー

MetaEditorのツールバーに「スタイラー」というのがある。

インデントを調整してくれる機能らしい。

MQL5ではインデントをどうするのが公式なのか、という疑問があった。

MQL5リファレンスにあるサンプルコードやMT5にあるサンプルEAのコードを見ると、必ずしも統一されていない。

「スタイラー」で調整されたものが公式なのだろうか。

とりあえず気が付いた2つの例を挙げる。

// コメント

のようにコメントアウトから始まる行の場合、行頭にインデントを置いてもなくなり、左詰めになる。

void func()
  {
   int a=0;
  }

のような場合、インデントは「{」と「}」の行は2つで、「int a=0;」の行は3つとなる。

もちろん、このようにしなければならないわけではない。

ただ、これが公式ならば、MQL5リファレンスにあるサンプルコードやMT5にあるサンプルEAのコードもこれで統一してほしい。

更に言えば、MQL5もコーディング規約を公式に示してほしい。

そうすれば、コードを書く側もそれに合わせて書くし、他人の書いたコードも読みやすくなるだろう。

ストップリミット注文

指値注文ではネガティブスリッページが生じる。

バックテストではネガティブスリッページの反映が不十分で、パフォーマンスは過大評価されやすい。

そこで、ストップリミット注文を使ってみるのはどうか。

トリガー価格に到達したらトリガー価格のすぐ下に指値注文を置く。

こうすれば順張りでエントリーしながらスリッページがポジティブになるのも可能だ。

だが、価格が指値注文を置いた価格まで戻らない場合はエントリーされず、トレード機会を失うことにもなる。

また、価格が戻らないのは順張りではよいトレードであり、それを逃すことになる。

逆に戻る場合は順張りではよくないトレードである可能性があり、それを掴むことになる。

つまり、単にトレード機会を失うということではなく、収益が得られる可能性の高いトレード機会を失うことになるのだ。

それはさて、ある戦略で逆指値注文とストップリミット注文とでどのようなパフォーマンスの違いが生じるかバックテストしてみた。

指値注文でエントリーした場合の平均獲得pipsは7.06pips。

これをストップリミット注文に変えると3.71pipsとなった。

3.35pipsも悪化した。

実際のトレードにおいては逆指値注文ではネガティブスリッページが、ストップリミット注文ではポジティブスリッページが発生する可能性があり、そこまで差はないにしても、ストップリミット注文での平均獲得pipsが逆指値注文でのそれを上回るとは考えにくい。

また、トレード数は逆指値注文が1185回で、ストップリミット注文が1082回だった。

失ったトレード機会は103回で1割弱。

それくらいなら、とは思うが、実は失ったトレードの内訳は88勝15敗の勝率85%で、これは大きい。元々、勝率が40%程度の戦略なのだ。

しかも逆指値注文のペイオフレシオは2.5倍で、ストップリミット注文は2.4倍。

失われたトレードはというと3.1倍にもなる。

ペイオフレシオが3.1倍で勝率85%のトレードを103回も逃すというのはさすがにきつい。

ネガティブスリッページを避け、ポジティブスリッページを受けるメリットがあるとはいえ、少なくともこの戦略ではストップリミット注文は使いにくい。

補足

指値注文でもストップリミット注文でもエントリーしたトレードは注文方法の違いにより若干のパフォーマンスの違いがあると思う。

だが、ここでは同じと考えて失われたトレードのパフォーマンスを計算している。

もし同じでないとするとストップリミット注文でのパフォーマンスは必ずしも逆指値注文でのパフォーマンスに含まれないことになり、

失われたトレードのパフォーマンス=逆指値注文でのパフォーマンス-ストップリミット注文でのパフォーマンス

という計算が成り立たないことになる。

例えこの計算が成り立たないとしても、逆指値注文でのパフォーマンスがストップリミット注文でのそれよりトレード機会が多く、勝率が高く、ペイオフレシオも大きかったということは言える。

必要証拠金の計算

円口座でユーロドルを取引する場合の1ロット当たりの必要証拠金を調べてみた。

double bid=SymbolInfoDouble(Symbol(),SYMBOL_BID);
double margin;
OrderCalcMargin(ORDER_TYPE_SELL,Symbol(),1.0,bid,margin);
Print(margin);

を実行すると、2023/2/23 23:59の時点だと

570688.0

と出力された。

同時点のユーロ円は142.672円で、1ロットが10万通貨、レバレッジが25倍だとすると、

142.672*100000/25=570688

となるのでぴったり合う。

ユーロドルを取引しているのでユーロドル×ドル円を使うのかと思っていたが、それだと少しずれるので、ユーロ円を使っているようである。

今度はポンドドルで調べてみる。

すると、

809030.0

と出力された。

同時点のポンド円は161.806円なので、同様に計算すると

161.806*100000/25=647224

となり、合わない。

実は

161.806*100000/20=809030

と計算するとぴったり合う。

つまりレバレッジが20倍なのだ。

だが、ポンド円でもポンドドルでも確認してみるとレバレッジは25倍となっている。

ちなみにオージードルの場合は

366632.0

と出力され、同時点でのオージー円は91.658円だから

91.658*100000/25=366632

で25倍で計算されていることが分かる。

円口座でドルストレートを取引すると、クロス円で必要証拠金が計算されるということは分かったが、ポンドドルでレバレッジが20倍になる理由が分からない。

ともあれ、求めたmarginに基づいてロット数を計算すると、通貨ペアによっては過小になることがある点に注意が必要だ。

今までmarginを使ってロット数を計算していたのだが、少し小さい場合があることに気付いた。

そこで調べてみたところ、なぜかこういうことが起きているということが分かった次第である。

とりあえずmarginを使わない計算方法にしたほうがよさそうだ。

ついでにもう少し調べると、円口座ではmarginは

オージー円、オージードルの場合→オージー円(25倍)
ユーロオージー、ユーロポンド、ユーロ円、ユーロドルの場合→ユーロ円(25倍)
ポンドオージー→?(ほとんどポンド円だが、少しずれている。ポンドドル×ドル円でもない。20倍)
ポンド円、ポンドドルの場合→ポンド円(20倍)
ドル円の場合→ドル円(25倍)

で計算されている。

基本的に取引通貨ペアのベース通貨+円が使われると考えてよさそうだ。

ユーロポンドはユーロ円が使われているからか、20倍ではなく25倍となっている。

細かいところでは謎が残るが、とりあえずそういう仕様だということにしておこう。

ポートフォリオの最適化

MT5を使ったポートフォリオの最適化

  1. 複数の戦略を1つのEAにまとめる
  2. 全資金を100として、各戦略に配分する資金をinput変数として指定する
  3. 各戦略のロット数は配分された資金に応じて増減させる
  4. OnInit関数を使い、各戦略に配分された資金の合計が100とならない組み合わせはパスする

以下のA~Eの5つの戦略でポートフォリオの最適化をしてみた。

最適化はリカバリーファクターを基準とした。

左の数字はリカバリーファクター、右の数字は最適化した資金配分(合計100)。

最適化は各戦略の配分を0から90まで、10刻みで行った。

A: 27.43/10
B: 17.98/0
C: 17.04/20
D: 11.86/50
E: 9.03/20

リカバリーファクターを基準に最適化したにもかかわらず、配分は各戦略のリカバリーファクターに比例しないようだ。

リカバリーファクターで4番目の戦略に資金の半分を投入し、2番目の戦略を0にするというのはなかなかやりにくい。

なお、最適化したポートフォリオ全体のリカバリーファクターは51.15で、資金を20ずつに5等分したポートフォリオでは42.41だった。

ポートフォリオの最適化に再現性はあるのかという疑問はある。

ただ、パフォーマンスのいい戦略に重点配分すればよいというものでもなさそうだ。

芸がないが、等分配分が最も無難なのかもしれない。

ならば最適化は不要か。

BuyメソッドとSellメソッド

MQL5リファレンスを見ると、CTradeクラスのBuyメソッドでpriceを指定しない、または0にすると買値が使用されるとある。

だが、実際には価格をいくらに入力しても無視され、エラーにもならない。

恐らく買値で買われているのだろう。

同様にSellメソッドでpriceを指定しない、または0にすると売値が使用されるとある。

だが、やはり実際には価格をいくらに入力しても無視され、エラーにもならない。

恐らく売値で売られているのだろう。

これまで、成行で売買するときはpriceを0にしなければならないものだと思っていた。

だが、実際にはいくらに入力しても影響はないようだ。

とはいえ、わざわざでたらめな価格を入力する必要はない。

StringToTime

もし、2024年1月17日に

Print(StringToTime("12:00"));

を実行すると、

2024.01.17 12:00

と出力され、日付を補ってくれていることが分かる。

問題は補ってくれる日付がNYC基準ではなくて、日本時間基準だということだ。

冬時間の場合、NYC基準の17時に日本時間では日付が変わる。

このため、17時以降を指定した場合に問題が起きる。

例えば、やはり2024年1月17日に

Print(StringToTime("20:00"));

を実行すると、

2024.01.17 20:00

と出力される。

これは日本時間では2024年1月18日4時0分となる。

もし、これを翌18日の1時に実行したとする。

NYC基準ではまだ17日だが、

2024.01.18 20:00

と出力され、日付が変わってしまう。

もし

if(TimeCurrent()==StringToTime("20:00"))
  処理

のように毎日20時0分に何らかの処理を行うとする。

一見、何の問題もなさそうだが、この時間が来る前に日付が翌日になってしまうので、永遠にこの時間は来ない。

そして何の処理も行われない。

今日の日付を省略して補ってもらおうとすると、予想外のことが起きるから、省略せずにちゃんと書いておいたほうがよい。

例えば

string sdate=TimeToString(TimeCurrent(),TIME_DATE);
if(TimeCurrent()==StringToTime(sdate+" 20:00"))
  処理

のようにするのが無難だ。

パフォーマンス指標の定数名

総損益やプロフィットファクターのようなパフォーマンス指標の定数名を調べたい場合、

を見ればいい。

あるいは

から「標準的な定数、列挙と構造体」→「環境状態」→「テスト統計」の順にクリックしても見ることができる。

例えば、OnTester関数内でプロフィットファクターを出力させたいときは

double pf=TesterStatistics(STAT_PROFIT_FACTOR);
Print(pf);

のようにする。

論理否定演算子

aがbool型の変数であるとする。

aが真のときに処理をしたい場合、

if(a==true)
  処理;

と書ける。

だが、

if(a)
  処理;

とシンプルに書くこともできる。

aが偽のときに処理をしたい場合は

if(a==false)
  処理;

と書ける。

だが、論理否定演算子の「!」を使って

if(!a)
  処理;

とシンプルに書くこともできる。

論理否定演算子は便利だが、使わないほうがいい場合もある。

例えば、aが真のときは処理1を、偽のときは処理2を実行したい場合、

if(a)
  処理1;
else
  処理2;

と書ける。

論理否定演算子を使って

if(!a)
  処理2;
else
  処理1;

と書くこともできる。

だが、この場合はわざわざ論理否定演算子を使わなくても書けるし、そのほうがよりシンプルであるから使わないほうがいい。

ENUM_TIMEFRAMES型

その1

MQL5のENUM_TIMEFRAMES型の変数も最適化できる。

例えば、

input ENUM_TIMEFRAMES timeframe=PERIOD_M5;

のようにしておけば、変数timeframeを最適化できる。

「current」から「1 Month」まで22種類を選択できるので、スタートとストップを選択する。

ただし、ステップは選択できない。

選択できる種類は

current
1 Minute
2 Minutes
(中略)
1 Week
1 Month

となっており、「corrent」より後は足の短い順に並んでいる。

その2

MQL5で例えば

Print(PERIOD_M5);

とすると何を出力するか。

MQL5リファレンスのどこかに書いてあったと思うが、ENUM_TIMEFRAMES型の各足で実際に出力させると、

M1:1
M5:5
M15:15
M30:30
H1:16385
H4:16388
D1:16408
W1:32769
MN1:49153

のようになっていた。

分足以外は不規則に見えるが、長い足ほど大きな数値のようだ。

とすると、例えば

Print(PERIOD_M5>=PERIOD_D1);

とすれば

false

が出力されることになる。

短期足と長期足でどの足を使用するか最適化する場合、上のような条件式を使えば

短期足>=長期足

となる無意味な組み合わせをスキップするという使い方もできる。

HistoryOrder~関数について

その1

MQL5で過去のオーダーを検索するとき、約定することなく消去されたオーダーは無視される。

約定はしていないが、まだ消去されていない、つまり現在生きているオーダーは過去のオーダーとして検索にかかる。

オーダーを短時間に繰り返しオープンしないようにするために過去のオーダー検索を利用する場合、注意が必要だ。

約定していなくてもまだ生きているオーダーは検索にかかるから、オーダーを短時間に繰り返し追加することは防げる。

だが、約定することなく消去されたオーダーは検索されないから、オーダーのオープンと消去を短時間に繰り返すことは防げない。

オーダーを短時間に繰り返しオープンすることを防ぐ簡単な方法はstatic変数を使ってオープン時間を記憶させることだ。

ただstatic変数はMT5を再起動したりすると記憶が失われるところが少し気に食わない。

もっとも、MT5が短時間に再起動を繰り返してその度にstatic変数が記憶を失うようなことは普通ないから、実務的にはそれで十分なのだろう。

その2

もし注文期限を超えてオーダーが消滅しても、同じ注文期限を持つオーダーを繰り返し出すことはできない。

なぜなら、その注文期限はすでに過去であり、オーダーを出してもエラーになるから。

それに注文を出す時間を設定しているなら、その時間は注文期限より前に設定しているはず。

注文期限より前の時間と注文期限以後の時間が同時に成立することはないから、そもそも注文期限を超えてオーダーが消滅した後にオーダーが出されるようなことはないわけだ。

仮に設定をミスして、注文を出す時間を注文期限より後に設定したとしても、そのオーダーはエラーになる。

というわけで、短時間にオーダーが出されることを防ぐのに過去のオーダーを検索することは、注文期限を超えて消滅したオーダーは検索できないにもかかわらず、問題はないということになる。

なお、「注文期限を超えて消滅した」オーダーは過去のオーダーに含まれないが、「キャンセルして消去した」オーダーは過去のオーダーに含まれる。ややこしい。

とすれば、過去のオーダーを検索したい場合、オーダーの消去に注文期限を使わず、キャンセルを使うのも一つの方法だ。

だが、注文期限を使うとMT5が起動していなくても消去できるので、なるべく注文期限を使いたいところではある。

ORDER_TIME_SETUPとORDER_TIME_DONEについて

MQL5で

ORDER_TIME_SETUP:約定するしないに関わらずオーダーをオープンした時間

ORDER_TIME_DONE:約定していないときはオーダーをオープンした時間、約定したときは約定した時間

だと思うが、確信はない。

iSpread関数について

その1

iSpread関数が返すスプレッドは始値時点なのか、終値時点なのか。

調べてみるとどちらでもない。

どうやら足の開始時間から終了時間までの間の最小スプレッドのようだ。

だから月足だとスプレッド0などということもある。

例えば、スプレッド(ポイント単位)が1つの足の中で

10
5
3
5
10

のように推移した場合、iSpread関数が返すスプレッドは

10
5
3
3
3

のように推移し、足が完成したときのスプレッドは3となる。

したがって、iSpread関数が返すスプレッドは小さめになる。

許容する最大スプレッドでエントリーを抑制したい場合はiSpread関数を使うのではなく、BIDとASKをリアルタイムで取得し、ASK-BIDを使ったほうがよさそうだ。

例えば

double ask=SymbolInfoDouble(Symbol(),SYMBOL_ASK); // ASK
double bid=SymbolInfoDouble(Symbol(),SYMBOL_BID); // BID
int spread=int(MathRound((ask-bid)/Point())); // スプレッド(ポイント単位)

として、このspreadを使えばいい。

その2

MT5でドル円各足のスプレッドの20本平均を見てみると、2024年2月19日22時0分で

M1:0.6
M5:0.4
M15:0.3
M30:0.3
H1:0.3
H4:0.3
D1:0.3
W1:0.3
MN1:0.2

となった(単位はpips)。

時間の長い足ほど小さくなる傾向があることが分かる。

これは足のスプレッドとして記録されている数値が、足の形成中に変化したスプレッドの中で最も小さいものが選ばれているからだ。

1か月の間には瞬間的にスプレッドが0になることもあるようだ。

だから月足だと0になったりすることもある。

全ティックでバックテストしても1分足のスプレッドが適用されるだけなので、リアルティックに比べると甘めの想定になる。

Zというテクニカル指標

MQL4/5でiBands関数を使う人は多いと思うが、私はiZという関数を作って代わりにすることが多い。

計算式は

z=(終値-移動平均)/標準偏差

だ。

ボリンジャーバンドの-2σで買い、+2σで売りたい場合、終値、上バンド、下バンドの3つが必要になるが、zなら1つで済む。

例えば

if(z<-2)
  買い注文;

のように書ける。

HistoryDeal~の諸関数の戻り値について

HistoryDealsTotal()は取引数×2+1の数字を返す。

リスト内番号を0から検索すると、DEAL_TIMEでは

バックテストの開始時間
1番目の取引のin時間
1番目の取引のout時間
2番目の取引のin時間
2番目の取引のout時間
…

の順となり、DEAL_PROFITでは

初期証拠金
0
1番目の取引の損益
0
2番目の取引の損益
…

の順となる。

なお、HistoryDeal〜の諸関数は決済したポジションだけが対象ではない。現在保有しているポジションでもエントリー自体は完結した取引として対象となる。

DoubleToString関数について

MQL5のDoubleToString関数はDouble型の数値を四捨五入した上でString型に変換してくれる。切り捨てではない。

例えば、1.23456を小数点第3位に指定して変換してみる。

Print(DoubleToString(1.23456,3));

を実行すると

1.235

と出力される。切り捨てられて1.234にはならない。

ところで気を付けたいのは、例えば1.2345を小数点第3位に指定して変換してみた場合だ。

Print(DoubleToString(1.2345,3));

を実行すると

1.234

と出力される。

今度は切り捨てられてしまったように見える。四捨五入の仕方が違うのだろうか。

私は1.2345と入力しても、それがコンピューター側では1.2344999…9となったり、1.2345000…1となったりすることがあるのではないかと思う。そして前者の場合、四捨五入すれば1.234となるわけだ。

したがって、四捨五入の結果が予想していた数値と違うことがあるということは頭に入れておいたほうがいい。

ウォークフォワードテストのコードの書き方

MQL5でウォークフォワードテストのコードをどう書けばいいか。

簡単なのは手動で各インサンプル期間の最適パラメータを調べて、それをアウトオブサンプル期間に適用することだ。

例えば、平均期間、ストップロスを2016-2020年で最適化した結果、平均期間=20、ストップロス=100が最適だったとしたら、これを2021年に適用する。

次に、平均期間、ストップロスを2017-2021年で最適化した結果、平均期間=15、ストップロス=200が最適だったとしたら、これを2022年に適用する。

ma_period:平均期間
sl:ストップロス(ポイント)

であるとして、コードは

MqlDateTime mdt;
TimeCurrent(mdt);
if(mdt.year==2021)
 {
  ma_period=20;
  sl=100;
 }
if(mdt.year==2022)
 {
  ma_period=15;
  sl=200;
 }

のように書く。

MQL5の公式サイトにはウォークフォワードテストを実行するための有料、無料のコードが提供されているが、かなりの数のファイルをインクルードする必要があるようだ。

他人の作った、よく分からないコードは使いたくない、コードの内容をチェックするのも大変だという人には上のやり方を試すのがいいかもしれない。

int型とdouble型について

その1

MQL5でdouble型の変数をint型に変換すると、小数点以下は四捨五入ではなくて切り捨てとなる。他の言語でもそうだと思うが、よくうっかりする。

例えばdouble型同士の変数を足したり引いたりして答えが10だとする。

だが実際には9.999…9や10.000…1になることがあり、int型に変換して切り捨てると、9になったり、10になったりしてしまうので、注意が必要だ。

また、

Print(1/2*100);

とすると

0

と出力される。

これは1と2がint型なので1/2は0.5ではなく、切り捨てられて0になるからだ。

Print(1/2*100.0);

としても後の祭りだ。

0.0

と出力されるだけで、大して変わらない。

Print(1.0/2*100);

または

Print(1/2.0*100);

なら

50.0

となる。常識だが、ついうっかりする。

その2

プログラミング言語では、int型の数とdouble型の数で四則計算をすると答えはdouble型になるのが普通だと思う。

MQL5でもそうか確認してみた。

int a=10;
Print(a+0.2);
Print(a-0.2);
Print(a*0.2);
Print(a/0.2);

を実行すると

10.2
9.8
2.0
50.0

と出力された。

MQL5でも、すべてdouble型に変換されているようだ。

ついでにint型の変数をdouble型の変数に代入するとどうなるか確認してみた。

int a=10;
double b=a;
Print(b);

を実行すると

10.0

と出力され、double型に変換されていることが分かる。

逆に

double a=10;
int b=a;

とすると、コンパイルはできるが

possible loss of data due to type conversion from 'double' to 'int'

という警告が出る。

MqlDateTimeについて

MQL5で

MqlDateTime mdt;

とした後に

TimeCurrent(mdt);

としても

TimeToStruct(TimeCurrent(),mdt);

としても同じだ。

だが後者の方が出来ることが増える。

例えば

TimeToStruct(TimeCurrent()+45*60,mdt);
Print(mdt.hour==10 && mdt.min==30);

とすると、

true

と出力するのは10時30分ではなくて、45分早い9時45分になる。

こういう使い方も考えられる。

例えばFOMCの日にtrueを返す関数を作ったとしよう。

ついでにFOMC前日、または翌日にもtrueを返すようにしたい場合、新たに関数を作ってもいいが、引数に246060を与えれば前日、-246060を与えれば翌日になるようにすれば便利だろう。

演算子について

MQL5で

Print(1.5 && true);
Print(-1.5 && true);
Print(0.0 && true);
Print(-0.0 && true);

true
true
false
false

と出力される。

double a;
Print(a=1.5 && true);
Print(a=0.0 && true);

true
false

と出力される。

a==bのつもりがa=bと間違えても条件式は成立するので注意。

オーダー、ポジションのリスト内番号について

オーダー、ポジションのリスト内番号が時系列的にどうなっているか調べてみると、0が最も古く、大きくなるにしたがって新しくなった。

例えば0時、1時、2時、3時、4時にポジションを作ったとする。

リスト内番号は0、1、2、3、4の順となった。

ここで2時のポジションを決済する。

すると、0時、1時のポジションのリスト内番号は0、1のままだが、3時、4時は2、3に変わった。

さらに5時に新たにポジションを作ったが、リスト内番号は4になった。

整理するとエントリー時間とリスト内番号は

0時:0
1時:1
2時:2→決済
3時:3→2
4時:4→3
5時:4

となった。

iBarShift関数について

その1

MQL5のiBarShift関数は指定した時間から足が何本経過しているかを返す。

1時間足の場合、1時0分に指定すると、2時0分では「1」を返す。もし1時5分に指定すると、2時0分では「0」。2時5分でもやはり「0」。3時0分でようやく「1」。

1時間足なら1時間に1回、0分に更新で端数は切り捨てとなる。

その2

MQL5のiBarShiftは指定した時間がバックテストの開始日より1年以上前だと-1を返すようだ。

CopyBufferも指定した開始日がバックテストの開始日より1年以上前だと配列は空になってしまう。

バックテストの開始日より1年以上前のデータは持っていないという仕様なのだろうか。

指定した期間範囲内で最大のTRを返す関数を作りたかったのだが、MT5の仕様?でうまくいかない。

テクニカル指標関数のshiftも日足だと500本くらいでおかしな数値を出す。

時間足だともう少し多くても大丈夫なようだが。

バックテストで500本以上前のデータを使うことはあまりないとはいえ、知らないと間違える。

実際のトレードでもおかしな数値が出ないとも限らない。

200日移動平均線は大丈夫だろうが、500日とか1000日などは全然違う数値になっているかもしれないから使うのはやめておいたよさそう。

マジックナンバーの注意点

MQL5でコンパイルするとき、マジックナンバーが11桁以上だと

truncation of constant value

という警告が出て別の数値に変換される。

21桁以上だと

integer constant is too big, so it was converted into double

という警告が出てやはり別の数値に変換される。

マジックナンバーが想定していない数値に変換されているのは危険なので、10桁以下に収めるべきだ。

今は分からないが、昔、MQL4でマジックナンバーの桁数が大きすぎるとすべて0に変換された。別の数値に変換されるだけなら、重複する可能性は低いからまだいい。だが、すべて0に変換されるとなると重複するリスクが高くなる。

複数のEAを稼働させ、しかもマジックナンバーがすべて0になっていると、EA同士で互いのポジションに干渉して、最悪、エントリーとエグジットを高速で繰り返し、あっという間に破産するだろう。実際、私はデモ口座でだが、破産しかけたことがある。

バックテストはEA単体の動きしか分からず、複数のEAを稼働させたときに初めて起こる問題には気づきにくいので要注意だ。複数EAを稼働させる場合、先ずはデモ口座で稼働予定のすべてのEAを同時に稼働させて問題がないか確認する必要がある。

EAの暴走を防ぐ工夫

エントリーとエグジットの条件を排他的にする

EAで売買ルールを書くとき、私は

bool buy_entry=…;
bool sell_entry=…;
bool buy_exit=…;
bool sell_exit=…;

のようにしている。

ところで、エントリーとエグジットを高速で繰り返して破産するのを避けるには、エグジットが偽のときのみ、エントリーが真となるように注意してルールを書く必要がある。

そうでないと、エントリーした瞬間にエグジットし、エグジットした瞬間にエントリーし、ということが高速で繰り返され、あっという間に破産するということが起こりかねない。しかし、細心の注意を払ってルールを書いても、うっかりということがあるだろう。

それを防ぐ方法の一つとして、売買ルールを書いた後に

buy_entry&=buy_exit==false;
sell_entry&=sell_exit==false;

の2行を追加しておくのがよいと思う。こうすればエグジットが偽のときのみ、エントリーが真となることが担保される。

エグジット後、エントリーを一定時間抑制する

例えば1時間足で前の足の終値移動平均線を上抜いたら買うとする。

このシグナルは次の足まで、つまり1時間は続くので、その間に利食い損切りでエグジットすると、また買ってしまう。エントリーとエグジットの条件を排他的にしたとしても、利食い損切りでエグジットするとこういうことが起こりうる。

このようなことを避ける方法として、次のエントリーをするにはエグジットから一定時間経過していることを要件とすべきだろう。少なくともエントリーの条件が継続する時間以上の時間を経過することを要件とするべきだ。エントリーしたその足で利食い損切りが生じるような狭い値幅に設定しなければ、普通このようなことは起きない。とはいえ、極端な値動きというのは時折起こりうるので、やはりそれにも備えるべきだ。

他銘柄データを利用するときの注意点

OANDAのMT5でEURUSDをバックテストするときに

Print(iClose("US30",0,1));

としてダウ平均の終値を出力させてみると、問題なく出力する。

だが、USDJPYでバックテストすると「0」が出力される。エラーはない。

バックテストの開始時期を2016年以降に変更すると、なぜか出力するようになる。

US30のデータは2016年10月以前は存在しないようで、そのあたりが関係しているかもしれない。データが存在する前からバックテストしてデータ取得に失敗すると、それが後まで引きずるのだろうか。ではなぜEURUSDでは引きずらないのか?

ともあれ、バックテストで他銘柄のデータを使うときは注意が必要だ。

1つのEAで複数戦略

私はトレード戦略を関数にして1つのEAにしている。例えば、

void OnTick(void)
 {
  Strategy1(0.4);
  Strategy2(0.3);
  Strategy3(0.2);
  Strategy4(0.1);
 }

みたいにして資金を割り当てる。

こうすれば、複数戦略全体のバックテストをしたい場合に便利だ。資金割当を最適化すれば、ポートフォリオの最適化もできる。

もし個々の戦略単体のバックテストがしたい場合は対象戦略以外の資金割当を0にすればいい。例えば、

void Strategy1(double allocation)
 {
  if(allocation==0)
    return;
//--- 中略
 }

みたいにしておけば、対象戦略以外の処理をスキップできる。

ダイバージェンシーのコードの書き方

例えばMACDのダイバージェンシーを調べるとする。

今、高値が直近高値を更新し、直近高値がn本前の高値だったとすると、

H[0]>H[n]

と書けるだろう。

このとき、もし

MACD[0]<MACD[n]

だとしたら、これはダイバージェンシーと言えるのではないだろうか。

MT5メモ

MT5について記事にするほどではないが、メモとして残しておきたいものをここでまとめておく。ただし、トレード全般に関するメモも含む。随時更新。

アノマリー

その1

特定の時間帯に買いだけ、あるいは売りだけ、のようなアノマリーを発見しようとすると過剰最適化になりやすい。

バックテスト上は勝てるルールを見つけやすいので、レベルの低い商材屋が手を出すところでもある。

イン期間が上昇トレンドだと買いの時間帯が拡大し、売りの時間帯が縮小する、逆もまた然り、いうようなことも起こる。

アウト期間でのチェックが必須だ。

その2

学者は直近20年でこの時間帯は上昇する傾向があるなどと論文に書いたりする。

確かに20年全体ではそういう結果かもしれないが、その時間帯に買う戦略を作って資産曲線を見ると、初めの15年は右肩上がりだが、直近5年は右肩下がりということもある。

直近で勝てなければ意味はない。

学者は統計的に有意な傾向を発見できればそれでいいかもしれないが、トレーダーは勝たなければならないから、トレーダーのやり方で研究する必要がある。

だから、X軸を時間帯、Y軸を変化率にして棒グラフを作るようなのはトレーダー的にはだめで、X軸を時間軸にして、Y軸は累積変化率にして時間帯別に曲線を何本も描くのがよい。

学者的なやり方が間違っているというわけではないが、関心のある場所が違うのだ。

バックテスト期間の長さ

その1

昔、ウォークフォワードテストを10年くらいやって、イン期間とアウト期間の最適化をしてみたことがある。

するとイン期間は3年、アウト期間は短いほどいい、という結果だった。

あくまでもテストに使った戦略では、の話で、一般化はできない。

ただ、こういうこともあって、私は長期間のバックテストとパラメータをそのままで使い続けることには懐疑的だ。

私が実際に使っているEAは直近5年で最適化し、1か月ごとに最適化してパラメータを更新している。

私が使っているEAに限った話だが、今のところはうまくいっているようだ。

もしバックテスト期間が5年しかない、毎月パラメータを更新する必要があるようなEAを販売するとしたら売れないだろう。

売れるEAはバックテスト期間が10年以上あって、パラメータは放置でいいもののはずだ。

だが販売者にとって売れるEAと再現性があって購入者に利益をもたらすEAとは別物だと思う。

話は変わるが、ケビン・J・ダービー氏によると、アウト期間はイン期間の10-50%がよいそうだ。

イン期間が5年ならアウト期間は半年もあれば十分となる。

実際、私も毎月最適化はしているが、最適なパラメータ値が変わることはあまりないので、今後は半年にしようかとも思う。

その2

イン期間は長すぎないのがよいのだとしても、やはり問題はある。

イン期間が短いほどパフォーマンスはよくなる傾向があるので、リスクを過小評価しがちになるからだ。

例えばPFがイン期間5年で3.0、続くアウト期間5年で1.5だったとする。

1.5もあればまずまずだが、問題は3.0から50%も劣化していることだ。

もしイン期間5年の3.0に浮かれて過大なロット数でトレードすると、想定外のドローダウンに見舞われることにもなりかねない。

イン期間5年が再現性の高いパラメータ値を選んでくれるのだとしても、想定するリスクもそれでよいというわけではないのだ。

リスクはアウト期間でのパフォーマンスで想定する、あるいは少なくともイン期間のドローダウンの2倍は想定してロット数を抑えるのが無難だと思う。

イン期間でリスクを想定して何年で何億儲けるとか、十何年で何十億儲けるとか考えるのは取らぬ狸の皮算用にも程がある。

EAへの干渉

バックテスト原理主義とでもいうのか、指標があろうと何があろうとEAは止めない、干渉しないと考える人がいる。

バックテストでは止めたり、干渉したりしていないのだから、そんなことをしたらバックテスト通りの利益は期待できなくなると考えるのだ。

だが私が思うに、このバックテスト通りの利益というのがそもそも怪しい。

バックテストというのはかなり理想的な環境で行われている。

実際の相場では価格、スプレッド、スリッページなどがバックテストで使われるデータより激しく変動している。

そして、この激しさはトレーダーに不利な方向に動くことが多い。

以前、私は指標での激変で大敗したことがある。50pipsの損切りを入れていたが、一瞬で刈られたばかりか、盛大に滑って150pipsの負け。

ところが後日、その期間を含めてバックテストすると、その大敗したトレードが直近数年で最大の大勝となっていた。

実際には相場の激変で一瞬で刈られていたのに、バックテストでは生き残ったことになっており、その後、爆益となったのだ。全ティックでバックテストしてもだ。

バックテスト通りの利益を期待してEAを止めない、干渉しないのであれば、大勝どころか、かえって大敗にもなりかねない。

もちろん、やたらとEAを止めたり、干渉すればEA本来の力を発揮できないことにもなる。

ではどうするか。

実際とバックテストを激しく乖離させるような指標やイベントは限られている。例えば、FOMC、雇用統計、CPI、日銀などだ。

こういうときにはトレードしないというルールをEAに加えてバックテストすればよいと思う。

そうすれば指標時にEAを止めても、それはバックテスト通りのトレードをしていることになる。

EAを止めたくなるのは指標だけではない。

年末年始はフラッシュクラッシュが怖い。

週末はポジションを持ち越したくない。

そう思うなら、それもEAのルールに加えよう。

自分がEAを止めたくなる、干渉したくなるような状況を総点検し、EAにそのルールを加えるのがいい。

放置できるようなEAを作るのだ。

そうしたら、事前に想定できなかった不定期、または突発的な状況を除き、EAは止めず、干渉しないと決める。

ここで注意したいのは、ルールを加えたことによってバックテストでのパフォーマンスが悪化した場合、このルールはないほうが利益が見込めるから加えるのはやめよう、などと考えないことだ。

先ほども書いたように、実際とバックテストには乖離がある。

ルールを加えることによってバックテストでは失われた利益も実際には損失だった可能性だってあるのだ。

そして、仮に実際にパフォーマンスが悪化するのであったとしても、EAを止めたり干渉したりする労力から解放され、何よりも安心を得られるメリットのほうが大きいと考えるべきだ。

例えば朝スキャ戦略で深夜から翌早朝までトレードする場合を考えてみる。

このルールだと、金曜深夜にエントリーして、週末にポジションを持ち越すことも起こり得る。

それが嫌なので金曜深夜はエントリーしないというルールを加えたところ、バックテストではパフォーマンスが悪化したとする。

これをどう考えるか。

もし週末にポジションを持ち越すことが気にならないのであれば、ルールを加えなくてもいいだろう。

だが持ち越したポジションが気になって仕方なく、週末を楽しめないのであれば加えたほうがいい。

「週末を楽しめない」というのはバックテストの損益には現れないが、これは大きな損失なのだ。

バックテストでの損益ばかりに振り回されず、ストレスフリーでトレードすることが大事だと思う。

始値のみの注意点

例えばMT5で始値のみ+M5でバックテストしたとする。

もしEAがM1のテクニカルを使用していた場合、これはエラーになる。

この場合、ストラテジーテスター側はM5のデータしか使わないので、EAがM1のデータを使うよう要求すると拒否するのだ。

もしEAがM15のテクニカルを使用していた場合はエラーにならない。

これはM5のデータを3本使ってM15を合成することが可能だからだ。

つまりEAが使おうとしている足が5の倍数であればエラーにはならない。

一覧にすると

〇始値のみ+M5のときにEA側で使える足の種類
M1:✕
M2:✕
M3:✕
M4:✕
M5:〇
M6:✕
M10:〇
M12:✕
M15:〇
M20:〇
M30:〇
H1:〇
H2:〇
H3:〇
H4:〇
H6:〇
H8:〇
H12:〇
D1:〇
W1:〇
MN1:〇

のようになる。

M5以上なら使えるのではないかと思いがちだが、M6、M12のように5より大きくても5の倍数ではない足は使えない。

SendNotification関数を使った場合のプッシュ通知設定

MQL5でSendNotification関数を使うとEAからプッシュ通知を送信することができるが、これは取引サーバからではなくてEAを稼働させているMT5から送信される。考えてみれば当たり前か。

だから、それができるように設定しないと通知は送信されない。

設定は以下の通り。

  1. MT5のメニューバーで「ツール」をクリックする。
  2. 「オプション」をクリックする。
  3. 「通知」タブをクリックする。
  4. 「プッシュ通知機能を有効にする」と「ローカルターミナルからの通知」にチェックを入れる。
  5. 「OK」ボタンをクリックする。

この場合、「取引サーバからの通知」だけにチェックを入れても通知は来ない。

取引サーバからの通知は注文を送信したり、約定したときだけのようだ。

オプティマイズ結果で使われる色分け

MT5で最適化して「オプティマイズ結果」を見ると、各指標の数値で色が違うことがある。

パフォーマンスのよいものから

緑
薄緑
黄緑
オレンジ
赤

の順だろうか。

パフォーマンスがよくても取引数が少ないと緑にはならないようだ。

色分けの基準は分からないが、もし重視する指標のパフォーマンスが最もよくても緑でない場合、そのパラメータ値は選択しないようにすると、各指標でバランスの取れたものになるかもしれない。

終値の位置関係と移動平均線の向き

終値>N本前の終値

N本移動平均線は上向き

は一見別物のようだが、実は同じだ。

知らないと、少し不思議に感じるかもしれない。

N本移動平均線が上向きとは

N本移動平均>1本前のN本移動平均

ということ。

例えば終値

3 1 4 1 5 9

と変化した場合、5本移動平均

- - - - 2.8 4.0

9>3、4.0>2.8となる。

最初の3を9に変えると

9 1 4 1 5 9
- - - - 4.0 4.0

9=9、4.0=4.0となる。

5本移動平均と1本前の5本移動平均は1、4、1、5を共通に持っているので、終値と5本前の終値のどちらが大きいかで向きが決まる。 だから

終値>N本前の終値、ならば、
N本移動平均線は上向き

となるのだ。

下向きの場合も同様。

バックテスト期間は何年がよいか

バックテスト期間は5年は必要だ、いや10年はほしい、といった意見を見かけるが、判断は難しい。

3つの戦略を2つの通貨ペアでバックテストし、バックテスト期間がどのくらいであれば再現性が高い、つまり信頼できるかを簡単に調べてみた。

イン期間を

  1. 2014-2018年(5年)
  2. 2009-2018年(10年)
  3. 2004-2018年(15年)

の3パターンで最適化し、2019-2023年をアウト期間としてPFを比較する。

左側の数値をイン期間の、右側の数値をアウト期間のPFとする。

通貨ペア1
◯戦略1
5年:3.35→1.57
10年:1.97→0.91
15年:1.81→1.11
◯戦略2
5年:1.76→1.81
10年:1.44→1.75
15年:1.08→1.68
◯戦略3
5年:1.43→1.65
10年:1.21→1.40
15年:1.01→1.40

通貨ペア2
◯戦略1
5年:3.60→1.68
10年:2.37→1.11
15年:1.77→1.84
◯戦略2
5年:1.69→1.71
10年:1.62→1.71
15年:1.24→1.54
◯戦略3
5年:1.31→1.66
10年:1.28→1.58
15年:1.02→1.58

イン期間のPFは5年が最も高く、15年が最も低い。

期間が長いほどサンプルが増えるので自然なことだ。

また、戦略には寿命があり、寿命を超える期間でバックテストするとパフォーマンスが落ちるということも考えられる。

アウト期間のPFが最も高いのは5年が5回、10年が1回、15年が1回。

最も低いのは5年が0回、10年が4回、15年が4回。

なお、同数首位、同数最下位を含む。

全体として5年が最もよく、10年、15年は同じくらいに悪い。通貨ペア1+戦略1の10年では損益がマイナスになった。

各戦略の傾向は通貨ペアが違っても似ており、各戦略の好不調は通貨ペアが違っても同時に起きそうだ。

戦略2、戦略3はアウト期間のパフォーマンスがイン期間よりよい。

普通はこういうことは起きないが、たまたまアウト期間の相場に合っていたのだろう。

もし戦略の採用基準がイン期間でPF1.5以上だとしたら、戦略3は採用されない。

アウト期間では勝っているが、これは仕方がない。どの戦略が今の相場に合っているかは後にならなければ分からないことだからだ。

戦略2の場合、5年では採用されるが、10年、15年では採用されないこともあり、惜しい気がする。もし戦略の寿命が5年くらいであることが多いのだとしたら、イン期間をいたずらに長くすると使える戦略をはじくことになる。

それでは改めてバックテスト期間は何年なら信頼できるのかを考えてみる。

それは戦略次第だというのが正しい答えなのだろう。

ただ、5年くらいがよいことが多そうだ。

あまり長いと直近の相場に合わないパラメータ値が選択される恐れがある。

また、必要以上にイン期間のパフォーマンスを下げ、直近で使える戦略を却下してしまうこともある。

長期間、高パフォーマンスを維持した戦略は使い始めたときには寿命がすでに尽きていることも考えられる。

これらを考え合わせると、長期間で最適化したパラメータ値を使うこと、それをアップデートせずに使い続けるのはあまりよくないことのように思われる。

指値注文のモデル別パフォーマンス

ある逆指値注文を使ったブレイクアウト戦略をMT5でモデル別にバックテストしてみた。

期間は直近1年。

モデルが始値のみのときの損益、期待利得、PF、RFをそれぞれ100とした場合、

全ティック:95 93 97 77
リアルティック:68 63 85 55

という結果だった。

延滞は「遅延ゼロ、理想的な実行」にしているからスリッページの影響はないはず。

価格変化とスプレッドの違いによるものだろう。

リアルティックでのパフォーマンスの劣化が著しいが、逆に言えば始値のみのパフォーマンスが過大評価されているのだ。

全ティックでも始値のみ寄りの結果で、かなり甘い。

トレード戦略にもよるだろうが、始値のみや全ティックでバックテストした場合、実際のパフォーマンスはかなり劣化すると想定しておくべきだ。

全ティックだから正確、などと考えてはいけない。

RFが最も劣化している点にも注意が必要だ。

始値のみ、全ティックでは含み損の想定がそれだけ甘くなるということになる。

市販のEA

市販のEAはパラメータをそのままにして使うものが多いと思う。

ユーザーが変更できるのはロット数など、主にリスク管理に関するものに限られる。

テクニカルなどのパラメータもあるはずだが、ストラテジーテスターのパラメータには表示されない。

このようなパラメータはロジックの核心に関わるもので、非公開、つまりブラックボックスとなっている。

隠す代わりにパラメータは開発者が販売時点で最適化しているはずだ。

だが、そのようなパラメータはその後どんどん劣化していく。

パフォーマンスはそのEAが販売開始したときがMAXだろう。

こう考えると、EAはやはり自作に限る。

破綻するEAでも使える?

破綻するEAでも使えるという考え方があるようだ。

破綻する前に利益をこまめに出金するということを繰り返し、破綻しても原資以上の出金ができればトータルではプラスだ、という考え方らしい。

例えば、口座に100万円を入金する。

10万円利益が出たら出金し、利益が出なければ損切りはせずに破綻するまで放置するとしよう。

そして、20回出金し、200万円利益が出たところで、破綻したとする。

原資の100万円は失われたが、それ以上の200万円を出金したのでトータルでは100万円の利益だ。

だが、もし原資以上の出金ができる前に破綻してしまったら、それでおしまいではないだろうか。

そこで、実は資金が1000万円あったとすることにしよう。

入金は100万円だけにし、破綻した場合はまた100万円入金して再チャレンジする。

そして、1000万円すべてが失われる可能性はほとんどなく、長期的にはプラスとなる可能性が高いとしよう。

こういうことであるなら、破綻するEAでも使えると言えそうだ。

だが、1つ疑問がある。

別に破綻させなくても同じことができるのではないか、ということだ。

例えば口座に1000万円入金する。

しかし、ロット数は100万円のときと同じとする。

10万円利益が出たら出金する。

入金額を除けばここまでは同じだが、100万円の含み損を抱えたら損切りして仕切り直すこととする。入金額が1000万円なので、100万円の含み損では破綻しない。

だが、資金が1000万円あり、入金額100万円で破綻して再入金するのも、初めから1000万円入金して100万円の含み損で損切りするのも実質的に結果は同じではないだろうか。

だとすると、このEAは100万円で損切りして破綻しないのだから、実は「破綻するEA」ではなくて「破綻しないEA」なのだ。

こう考えると、「破綻するEAでも使える」というのは正しくなく、「破綻しないEAを部分的に破綻させながら利益を上げるような運用方法もある」というのが正確ではないだろうか。

では、なぜ破綻しないEAをわざわざ破綻させるような運用の仕方をするのか。

どうやら「破綻するEA」というのはナンピンマーチンなどのハイリスクなEAを海外FXのゼロカットシステムを利用して運用することを前提としているようだ。

損切りだとすべることがある。

損切りの代わりに破綻させる場合、すべるということは追証があるということになる。

だが、海外FXのゼロカットシステムを利用すれば追証がないわけで、これはつまり損切りですべらないということになるだろう。

あくまでも海外FXを利用すればの話だが、損切りする代わりに破綻させるというやり方も一応の理屈はありそうだ。

また、国内、海外を問わず、FX会社というのは全面的に信頼するのもリスクがあるので、資金の一部しか入金しないというのも合理的ではある。

ただ、日本の規制を受けない海外FXで運用するのはよりリスクが高いと私は考えている。

また、破綻した場合、強制ロスカット手数料を徴収されることもあるのではないだろうか。それは余分な出費になる。

そして、やはり勘違いしてはならないのは「破綻しないEAを部分的に破綻させながら利益を上げるような運用方法もある」のであって、「破綻するEAでも使える」ということではないことだ。

勝てないEAでも海外FXを利用し、まめに出金すれば勝てるEAに生まれ変わるなどと考えては痛い目に逢うだろう。

バックテストにおけるスワップ

MT5でデフォルトの設定でバックテストすると、損益にスワップが加算されると思う。

だが、スワップヒストリカルデータがあって、それがトレード日に応じて加算されているわけではなく、ストラテジーテスターで設定されている数値が一律に加算されているだけなので、知らない人は注意が必要だ。

ストラテジーテスターで設定されている数値はバックテストした日のスワップに基づいている。数値が更新されるタイミングは分からないが、私が主に利用している取引会社では日本時間で日付が変わる前後のようである。

同じバックテストを別の日にしたら、結果が微妙に違うという経験をした人はいないだろうか。

それは恐らく加算されたスワップの違いによって生じたものだと思う。

微妙に違うだけでも気持ち悪いが、買いだけ、または売りだけの戦略の場合、問題が起こる。

スワップに違いがあっても、売買両方ある戦略ではプラスとマイナスが相殺されて、それほど影響しない。

だが、買いだけ、または売りだけの戦略だと、スワップのプラス、またはマイナスだけが蓄積されるので、大きな影響が出ることもある。

たまたまスワップが大きい日にバックテストすると、全体の損益のかなりの部分をスワップによる損益が占めてしまい、バックテスト結果が信用できなくなることもある。

例えば、スワップ通常の3倍、または4倍も付く日がある。

祝日の関係もあるので一概に言えないが、私が主に利用している取引会社の場合、木曜日が3、4倍になる。

もしスワップが3、4倍になった日に買いだけ、または売りだけの戦略をバックテストし、それがプラスのスワップであったなら、本来なら微妙なパフォーマンスだったはずの戦略が突然、聖杯になってしまうということも起こるだろう。

それで勘違いして使ったら勝てなかった、というようなことにもなりかねない。

私はそれが嫌なのでスワップは0に設定している。

スワップをあくまでもおまけと考え、売買での損益を重視するなら、スワップを0にしても大きな問題はないだろう。

バックテストする日によって結果が変わるよりはいいと思う。

0に設定するにはストラテジーテスターで、

  1. 銘柄の行の右端にあるアイコンをクリックする
  2. 「テスト済みの銘柄」ウィンドウで下にスクロールし、「買スワップ」と「売スワップ」の数値を0にする
  3. 「YES」ボタンをクリックする

とすればいい。

元に戻したい場合は

  1. 銘柄の行の右端にあるアイコンをクリックする
  2. 「テスト済みの銘柄」ウィンドウで「初期値」ボタンをクリックする
  3. 「YES」ボタンをクリックする

とする。

ただし、「買スワップ」、「売スワップ」以外にも設定を変更していた場合、それも初期値に戻ってしまうので注意。

スプレッドが狭くなると得をする?

スプレッドが広くなったときに売買して狭くなったときに決済すればもしかして得をする?とふと思ったので一応考えてみる。

以下の状況を想定する。

  • BIDとASKの中間値が1日を通して変化せず、100円だとする。
  • 早朝前のスプレッドは0円で、BIDもASKも100円だとする。
  • 早朝のスプレッドは0.1円で、BIDは99.95円、ASKは100.05円だとする。
  • 早朝後のスプレッドは0円で、BIDもASKも100円だとする。

早朝前に買って早朝前に決済すると、100円で買って100円で決済するので損益は0円。

早朝前に買って早朝に決済すると、100円で買って99.95円で決済するので損益は-0.05円。

早朝前に買って早朝後に決済すると、100円で買って100円で決済するので損益は0円。

早朝に買って早朝に決済すると、100.05円で買って99.95円で決済するので損益は-0.1円。

早朝に買って早朝後に決済すると、100.05円で買って100円で決済するので損益は-0.05円。

早朝後に買って早朝後に決済すると、100円で買って100円で決済するので損益は0円。

整理すると

  1. 買いと決済がいずれも早朝以外の場合、損益は0円
  2. 買いと決済のいずれかが早朝の場合、損益は-0.05円
  3. 買いと決済がいずれも早朝の場合、損益は-0.1円

となる。

早朝に買った(スプレッドは広くなる)場合、早朝後に決済(スプレッドは狭くなる)すれば、同じ早朝に決済するより損失は小さくなるが、やはり損失はある。

スプレッドがある限り、常に損失が生じる。スプレッドが狭くなることで損失が小さくなることはあっても、それが利益に転じることはない。ここではスプレッドがマイナスになることは考えない。

結論、得はしない。当たり前か(笑)

投資適正テスト

投資詐欺に遭ったり、馬鹿げたトレードをして大金を失う人が少なくない。

そういう人は初めから投資に手を出さないほうが幸せだと思う。

そこで、投資適性テストを独断と偏見で思いつくままに書いてみる。

エスかノーか。

もし下のリストでいくつもイエスだという人がいたら、投資適性を疑ったほうがいいと思う。

  • アナリストが上がると言えば買い、下がると言えば売る。
  • 買ったら下がってしまった。他の人の意見を見てみると、下がると言う人もいるが、上がると言う人もいる。このまま持ち続けよう。
  • こうすれば勝てると聞けば検証せずにすぐ実行する。
  • 優秀なトレーダーを信じ、交流することが勝つための早道だ。
  • 人を信じないのは遠回りであり、勝てるようになる機会を捨てるものだ。
  • 優秀なトレーダーを信じ、交流することで勝てるようになったと言う人がいる。私も信じ、交流しよう。
  • 将来、大きな利益を得られることを考えれば、商材に金を使うのは必要経費だ。
  • だめな商材もあるが、いい商材だってある。
  • 一人で努力するだけではなかなか勝てるようにはならない。
  • 投資で儲けている人を見ると羨ましい。
  • SNSで日々勝っていることを報告している人を見るとすごいと思う。
  • SNSで日々勝っていることを報告している人を見ると胡散臭いと思う。だが、自力ではなかなか勝てないし、もしこの人が本物でそれを疑うのは利益を得る機会を失うことになる。よし、信じてみよう。
  • SNSでフォロワーが多く、たくさんの人がこの人のおかげで勝てるようになったと言っているから、この人に付いて行こう。
  • SNSでEAやnoteなどの無料配布と聞くとすぐ応募する。
  • 友達から儲かる話を聞いた。SNSの知り合いではなく、リア友だから信じて大丈夫だ。
  • この人の言うことを聞いて何回かトレードしたら勝てたので、この人は信頼できる。
  • 聖杯はどこかにある。
  • これは聖杯EAだと言っている人がいる。胡散臭いが、もしこれが本物でそれを疑うのは利益を得る機会を失うことになる。よし、買ってみよう。
  • バックテストで資産曲線がきれいな右肩上がりのEAは今後も勝つと思う。
  • 優秀なEAの値段が高いのは当然だ。
  • このEAはバックテストで勝っているし、フォワードテストでも買っているから本物だ。
  • このEAで何回かトレードしたら勝てた。もっと儲けたいからロットを増やそう。
  • このEAで何回かトレードしたら負けた。使うのはやめよう。
  • 損切りすると負けが確定するが、含み損は負けではない。
  • 投資をやらないということは利益を得る機会を捨てるということだ。
  • 1日トレードしなければ1日利益を得る機会を捨てたことになる。
  • レバレッジ25倍は小さすぎる。
  • レバレッジをかけるほど大きな利益を得られる。
  • 資金が足りないので家族のために積み立てたお金を使おう。大儲けして家族を驚かせたい。

まだ他にもありそうだが、とりあえずこの辺で。

ペイオフレシオとトレーリングストップ

ペイオフレシオを3倍でエントリーしたとする。

もし100円で買って103円で利食うとしたら、99円で損切りすることになる。

価格が101円に上昇すると、利益幅は2円、損失幅も2円でペイオフレシオは1倍になる。

価格が102円に上昇すると、利益幅は1円、損失幅は3円でペイオフレシオは0.33倍になる。

利食い価格、損切り価格を固定にすると、利益が乗ったときにペイオフレシオが急速に悪化することが分かる。

次に、トレーリングストップを加えるとする。

価格が101円に上昇すると、損切り価格は100円になる。

利益幅は2円、損失幅は1円でペイオフレシオは2倍になる。

価格が102円に上昇すると、損切り価格は101円になる。

利益幅は1円、損失幅は1円でペイオフレシオは1倍になる。

利食い価格、損切り価格が固定である場合よりは遅いが、やはりペイオフレシオは悪化する。

最後にペイオフレシオを3倍に維持するとする。

価格が101円に上昇すると、損切り価格は100.33円になる。

利益幅は2円、損失幅は0.67円でペイオフレシオは3倍のままとなる。

価格が102円に上昇すると、損切り価格は101.67円になる。

利益幅は1円、損失幅は0.33円でペイオフレシオは3倍のままとなる。

このように損切り価格の上昇を価格の上昇より速くすることで、ペイオフレシオを維持することができる。

さて、利食い価格と損切り価格を固定にすると、利益が乗った場合にペイオフレシオが急速に悪化することが分かった。

ここで利食い直前の状況を考えてみよう。

100円で買って103円で利食い、99円で損切りする場合、もし価格が102.99円に上昇したとしたら、固定では利益幅が0.01円、損失幅が3.99円で、ペイオフレシオは0.0025倍となる。

このようなひどいペイオフレシオでエントリーする人は普通いないと思う。

ではなぜエントリーしてしまうと、ペイオフレシオの悪化に無頓着になるのだろうか。

もしペイオフレシオを維持するのがよいのだとすると、トレーリングストップは合理的な手段だ。

しかし、通常のトレーリングストップでは不十分で、買いならば価格の上昇より速く損切り価格を引き上げる必要がある。

逆に買った後に価格が下落した場合も考えることができる。

先ほどのエントリーを例にすると、価格が0.5円下落したら、損失額は0.5円なので、ペイオフレシオを維持するなら利益幅を1.5円にしなければならない。

エントリー時点の利益幅は3円だったので、価格の下落より速く利食い価格を引き下げる必要があるわけだ。

こういう場合はトレーリングリミットと呼ぶのだろうか。

私はこのようなトレーリングストップ、またはトレーリングリミットを採用してはいないが、一つの考え方ではあると思う。

スプレッドの急拡大

スプレッドの急拡大でストップがついてしまうということはあってもリミットがついてしまうということはない。

例えば買いポジションを保有していて現在価格の上にリミットを、下にストップを置いていたとする。

ここでスプレッドの急拡大が起きたとしよう。

買いポジションの決済はBIDで行われるからBIDだけを見る。

スプレッドが拡大したとき、BIDは下に動く。

するとリミットからは遠ざかり、ストップに近づく。

だから、「スプレッドの急拡大でストップがついてしまうということはあってもリミットがついてしまうということはない」というのだ。

MT5ではバックテストでスプレッドの変化を細かく反映させたいと思っても、使えるのは最も細かいもので1分足のスプレッドなので、せいぜい1分間隔だ。

しかもMT5の仕様では、1分足のスプレッドは1分間で最も小さいスプレッドのようなのだ。

つまり、スプレッドは実際より小さくなっており、しかも、1分間変わらない。「全ティック」でバックテストしてもだ。

したがって、スプレッドの急拡大はバックテストでは限定的にしか反映されず、実際にはストップが先についたのに、バックテストではストップはつかずにリミットがついた、ということが起こりうる。

逆に、実際にはリミットが先についたのに、バックテストではリミットはつかずにストップがついた、ということは起こらないだろう。

スプレッドの急拡大を十分に反映させるには「リアルティックに基づいたすべてのティック」でバックテストする必要がある。

それ以外のモデルでバックテストした場合、パフォーマンスは多かれ少なかれ、過大評価されることはあっても過小評価されることはないだろう。

ブレイクアウト戦略

ブレイクアウト戦略では直近高安のブレイクを使う人が多いかもしれないが、ボリンジャーバンドのブレイクもなかなかの優れ者だ。

直近高安を使ったブレイクアウト戦略を持っているなら、直近高安の部分をボリンジャーバンドに差し替えて試してみるのも悪くないと思う。

パフォーマンスや再現性が向上することもありうる。

直近高安とボリンジャーバンドを併用するというのも考えられるが、これはあまりお勧めしない。

別に相性が悪いというわけではなく、似たような指標をごちゃごちゃと組み合わせて複雑にするのはシステムを不安定化させると思うからだ。

複雑にすればバックテストではパフォーマンスは上がるが、再現性があるかは怪しい。

ストラテジーテスターのモデル

その1

スキャル戦略をモデルを「始値のみ」にしてバックテストするとパフォーマンスがいいことがある。

ところが、「全ティック」や「リアルティックに基づいたすべてのティック」でバックテストすると資産曲線が一直線の右肩下がりで負けたりする。

モデルが「始値のみ」の場合、利食い幅と損切り幅が狭いとどちらが先に到達したか明らかでなくなることがある。

だからと言って、利食いに有利に働くかどうかは分からないが、「始値のみ」のほうがパフォーマンスがいい印象がある。

また、「始値のみ」では1分足でも1分に1回しかエントリーできないが、「全ティック」や「リアルティックに基づいたすべてのティック」ではもっと高頻度でエントリーできるので、その分、コストがかさんだり、あまり有利でないトレードを繰り返したり、ということが起きるのかもしれない。

このようなことはスキャル戦略で著しいが、利食い幅と損切り幅を設定する限り、値幅の大きい戦略でも起こりうる。

すると、「始値のみ」、「全ティック」、「リアルティックに基づいたすべてのティック」でバックテストの結果に差が出てくる。

もちろん、実際のトレードでのパフォーマンスとも乖離が生じる。

このようなことを避けるためには、エントリーやエグジットはテクニカル指標や時間などで行い、指値や逆指値は使わない、というのが一つのやり方だ。

ただし、相場の急変に備えて損切りの逆指値だけは置く。

こうすると、バックテストでのパフォーマンスが落ちることもあるが、元々、パフォーマンスが過大評価されていたのであり、本来の実力に近づいただけとも考えられる。

私はバックテストでのパフォーマンスより本来の実力を知りたいので、指値や逆指値損切りの逆指値を除いて基本的に使わない。

こうすれば、モデルを「始値のみ」にしようと、「全ティック」、「リアルティックに基づいたすべてのティック」にしようとパフォーマンスに大差がないので、「始値のみ」を使って検証時間を短縮することもできる。

逆に言うと、モデルを変えただけでパフォーマンスが大きく変わるようなら、実際のトレードでのパフォーマンスも大きく変わるだろう。

その2

ポジションを翌週まで持ち越したとする。

そして、月曜朝に大きな窓が開き、ストップ、またはリミットがついたとする。

MT5のバックテスト上ではいくらで決済されるだろうか。

1分足で「始値のみ」で調べてみると、ストップ、またはリミットを超えてはいるが、金曜日終値に近い価格で決済されていた。

「全ティック」だとストップ、またはリミットを大きく超え、月曜日始値に近い価格で決済されていた。

だとすると、「始値のみ」は窓を反映していないようだ。

得することもあれば損することもあるだろうから、総損益は大差ないだろう。

だが、最大勝ちトレードが実際にはもっと大きい可能性がある一方で、最大負けトレードもやはり実際にはもっと大きい可能性があるというのはリスク管理の面で気にはなる。

もし最大負けトレードを参考にしてロットサイズを決めるのであれば、「全ティック」でも確認しておくのが無難だ。

その3

トレーリングストップを使った戦略を「全ティック」でバックテストすると、「始値のみ」と比べて損益、PFはあまり変わらないが、RFはかなり劣化する場合がある。

なぜそうなるかはよく分からない。

その4

ストップ幅が狭い戦略はパフォーマンスが過大になる傾向がある。

始値のみ」、「全ティック」でバックテストするとパフォーマンスはよくても「リアルティックに基づいたすべてのティック」でバックテストするとパフォーマンスががくっと落ちることがある。

実際にはスプレッドの急拡大でストップが付いてしまったのに、「始値のみ」、「全ティック」のバックテストでは反映されないケースがあるからだろう。

通貨ペアにもよるだろうが、ストップ幅が15pipsくらいだと、そういったケースが頻発して乖離が生じてきそう。

40pipsくらいあれば乖離はあまり生じない印象。

最適化は時間の節約のためにも「始値のみ」でいいと思うが、ストップ幅が狭い戦略では最適化したパラメータを使用する前に一度は「リアルティックに基づいたすべてのティック」で確認するほうがいい。

負ける要素を除去すれば勝てるのか

例えば、その日の始めに買って終わりに決済するとする。

そして、曜日別に損益を見ると火水金がプラスで月木がマイナスであったとする。

そこで、火水金のみ買う、というルールを加える。

まずまずのパフォーマンスになった。

よし、これで行こう!

と、安直に考えるのはやめたほうがいい。

別の検証期間では全く別の結果になるかもしれないからだ。

検証期間をイン期間とアウト期間に分け、イン期間でルールを作ってアウト期間で確認する。

この作業を省くと、バックテストで勝てる売買ルールは簡単に作れるが実際には使い物にならない、ということが起こり得る。

フィルターを加えて負けを除去していけば、残るのが勝ちなのは当たり前のことだ。

だが、再現性がなければバックテストの結果がどれほどよくても何の意味もない。

負ける要素を除去していけば勝てる戦略になる、というほど簡単なものではないのだ。

1分足OHLC

MT5で形成中の最新バーの終値

C0:iClose(NULL,0,0)

を1分足OHLCで見ると、

その足が陽線の場合は

00秒:C0=その足の始値
20秒:C0=その足の安値
40秒:C0=その足の高値
59秒:C0=その足の終値

その足が陰線の場合は

00秒:C0=その足の始値
20秒:C0=その足の高値
40秒:C0=その足の安値
59秒:C0=その足の終値

という順に変化していくようだ。

陽線だと安値が先で高値が後、陰線だと高値が先で安値が後、と機械的に決めているらしい。

値動きの流れを考えれば自然ではある。

実際には全てではないにしても多くはこの順序だろう。

それはさて、「1分足OHLC」は危険だと思う。

例えば「モデル」を「1分足OHLC」にして〜円以下になったら成行で買うとする。

すると、その条件を満たした1分足の安値で買うことになる。

だが予知能力でもない限り、実際に安値で買うなど不可能だ。

従って逆張りだとパフォーマンスが過大になってしまう。これでは聖杯を見つけたと勘違いしかねない。

逆に〜円以上になったら成行で買うとする。

するとその条件を満たした1分足の高値で買うことになる。

今度は順張りだとパフォーマンスが過小になるという結果になる。

これを避けるには成行ではなくて、指値、逆指値を使うべきだろう。

そうすれば高値、安値ではなく、指定した指値、逆指値で売買できる。

だが、それなら「1分足OHLC」ではなくても、タイムフレームを「M1」にした上でモデルを「始値のみ」としても大差はなく、しかも速い。

「1分足OHLC」では成行は使わない、いっそのこと、「1分足OHLC」自体を使わないとしたほうがよさそうだ。

買いのみ、または売りのみのトレード戦略

仲値前の買いと仲値後の売りは非対称なトレード戦略なので仕方がないが、特に理由がない限り、買いだけ、あるいは売りだけというトレード戦略はよくない。

また、買いのエントリー条件は緩く、売りのエントリー条件は厳しいといったように、売買両方をやっているが、条件が異なり、売買に偏りがあるのもよくない。

極端な例だが、ある銘柄に対してバイ・アンド・ホールドとセル・アンド・ホールドの戦略を採用したとする。

一方が勝っていれば、もう一方は負けているだろう。

だからといって、勝っているほうが負けているほうより優れているということにはならない。

たまたま検証期間において買いが、あるいは売りが優位だったというだけに過ぎないのだ。

バイ・アンド・ホールドやセル・アンド・ホールドのような戦略では勝っていても大した勝ちではないだろう。

だからバックテスト結果を見ても採用しようとは思わない。

だが、これにナンピンナンピンマーチンが加わるとどうなるか。

突如として魅力的な戦略に見えてくる。

ナンピンナンピンマーチンというのは鬼滅の刃で例えるなら、寿命の前借りというやつだ。

早死にと引き換えに驚異的なパフォーマンスを発揮するのだ。

これにナンピンナンピンマーチンを加えると、例えば売りだけの戦略は破綻するかもしれないが、そのとき、買いだけの戦略は大きな含み損を抱えることもほとんどなく、聖杯のように見えるだろう。

だが、それもたまたまであって、聖杯のように見える戦略も破綻した戦略も実質は同じなのだ。

買いだけ、あるいは売りだけのナンピンEA、ナンピンマーチンEAを見かけて、それがどれほど素晴らしいパフォーマンスだったとしても、安易に手を出さないほうがいい。

仲値トレード

仲値トレードをする人は多いと思う。

ただ、仲値トレードをゴトー日ではない金曜日にやる人、仲値前に買って負けたのに続けて仲値後に売る人も時折見かける。

私はこれに対し

  • ゴトー日でない金曜日にトレードするメリットは少ない。
  • 仲値買いがなければ仲値売りもない。

と考えている。

ゴトー日でない金曜日にトレードするメリットは少ない

仮に仲値トレードを毎日やるとする。

パフォーマンスを曜日別に見ると、金曜日のパフォーマンスが特によいことが分かるだろう。だからゴトー日であるかいなかを問わず、金曜日に仲値トレードをやろうとする人がいても不思議ではない。

だが、金曜日だからパフォーマンスがよいのだろうか。そういう要因が全くないとは言わないが、私は違うと考えている。

土日がゴトー日の場合は金曜日がゴトー日となるので金曜日は他の曜日と比べてゴトー日になる確率は3倍だ。

金曜日のパフォーマンスがいいのはゴトー日になる確率が高いからだと思う。

実際、ゴトー日でない金曜日のパフォーマンスはいまいちだ。

仲値買いがなければ仲値売りもない

仲値売りは仲値買いの反動だから、仲値に向けた買いがなかった場合、仲値通過後の売りもなくなる。

仲値前に買って負けた後、仲値で売るのは往復ビンタになりやすい。

仲値前に買いがあったか(上昇したか)いなかで分けてバックテストすると、仲値前に買いがあったときはパフォーマンスがよく、買いがなかったときはパフォーマンスがいまいちなのが分かるはずだ。

だから仲値前に買って負けたのに、続けて仲値で売るのはどうかと思う。

ところで、仲値前に買いがなかったというのと自分が買って負けたというのとは必ずしも一致しない。

例えば仲値の7時間前に買うとパフォーマンスがいいとする。

だが、仲値売りをするために仲値買いがあったかどうかを判定するとき、仲値1時間前の価格と比較したほうがパフォーマンスがいいとする。

もし、仲値7時間前に買って最初の6時間は下がり続け、最後の1時間で上がったものの負けたとする。

この場合、仲値前に買って負けたが、仲値後に売ってもいいのである。

それにしても7時間と1時間は大きな差だ。

なぜ同じにならないのか。

要因の一つとしてトレンドの存在があると思う。

例えば、検証期間が上昇トレンドにあったとする。

すると買いは保有時間が長いほど有利になりやすい。

このため、仲値前に買う時間は早いほうがパフォーマンスはよくなる。

一方、上昇トレンドは売りには不利に働く。

だからエントリーは抑制するほうが有利になりやすい。

仲値前から遡る時間が長いほど上昇しやすく、エントリーしやすくなるのだとしたら、遡る時間は短いほうがエントリーしにくくなり、パフォーマンスはよくなる。

そこで、もし仲値の7時間前に買うのがパフォーマンスがよく、仲値から1時間遡った時間を基準にして買いの有無を判定して売るのがパフォーマンスがよいのだとしたら、それは検証期間が上昇トレンドにあるからなのかもしれない、と考えるわけだ。

買いと売りの条件が対称の場合、買いが有利なときは売りが足を引っ張り、その逆もまたしかりなので、結局は買いと売りにバランスよく有利なパラメータ値を見つけることができるだろう。

だが、仲値前の買い、仲値後の売りは非対称なので、同じようにはできない。

仲値前の買い、仲値後の売りを1つの戦略にし、仲値前に買いを入れる時間と買いの有無を判定するための基準時間を同じにするという考え方もあるだろう。

だが、買いがなかった場合に売りをやらないのだとすると、単純に考えて仲値後の売りのトレード数は仲値前の買いの半分にしかならない。

このため、仲値前の買いのほうが大きな割合を占め、仲値前の買いに有利な時間に引きずられるので、必ずしも買いと売り双方にバランスの取れた時間にはならない。

したがって、仲値前の買いと仲値後の売りは別々の戦略と考えたほうがよさそうだ。

トレンドの影響は気になるが、その場合は検証期間の開始日と終了日の価格がほぼ同じとなるような検証期間を選択するというのも一案だ。

MT5の通知

MT5からの通知を設定したい場合、

  1. メニューバーで「ツール」をクリックする。
  2. 「オプション」を選択する。「オプション」ウィンドウが表示される。
  3. 「オプション」ウィンドウで「通知」タブをクリックする。
  4. 「通知」タブで「取引サーバからの通知」にチェックを入れる。
  5. 「MetaQuotes ID:」に通知先のMT5のMetaQuotes IDを入力する。
  6. 「OK」ボタンをクリックする。

とする。

通知先のMT5のMetaQuotes IDを調べたい場合、AndroidスマートフォンのMT5では

  1. 「メッセージ」をタップする。
  2. 「MQID」ボタンをタップする。

とすれば表示される。

取引サーバではなく、通知元のMT5から通知を出したい場合は

  • 「通知」タブで「プッシュ通知機能を有効にする」と「ローカルターミナルからの通知」にチェックを入れる。

以外は取引サーバからの通知と同じ。

「ローカルターミナルからの通知」を設定した場合、通知元のMT5を起動していないと通知が来ないので、「取引サーバからの通知」のほうがいいと思う。

両方を設定すると、同じ通知が2つ来てうるさい。

口座情報の削除

MT5で口座にログインするとき、以前にログインしたことのある口座のリストから選ぶことができる。だが、すでに存在していない口座がたくさんあると邪魔になることもある。

すでに存在していない口座をリストから削除したい場合は

  1. 「ナビゲータ」で「口座」を展開
  2. 削除したい口座を右クリック
  3. 「削除」をクリック

の順でできる。

これはあくまでも口座情報をMT5から削除しているだけで、口座そのものを取引会社から削除しているわけではない。心配なら存在していない口座情報のみ削除するばいい。

変動スプレッドの落とし穴

MT5は変動スプレッドでバックテストできるので、MT4のように固定スプレッドを設定する必要もなく、便利だ。

だが、落とし穴もある。

スプレッドの情報がない場合、スプレッドが0になっているのだ。

例えばOANDAのXAUUSD。

2021年4月以前のスプレッドは0になっている。

この期間でバックテストすると、簡単に聖杯ができてしまう。

一般的には固定スプレッドより変動スプレッドのほうが実際の環境に近い。

だが、変動スプレッドでバックテストしたからといって信用できるものとは限らないのだ。

ナンピンEA

私は損切りなしのナンピンEAは使わないが、一応作ってはいる。

いくらか深い含み損を抱えることもあるが必ず戻すので、使ってみたくもなる。

だが、戻す保証はないので使わない。

それはさて、作ったナンピンEAで損切り利食いの何倍にすれば損切りなしと同じ結果になるか簡単に調べてみた。

すると1000倍に…😱

単純に計算すれば勝率は99.9%以上になるから、むしろ勝つのが当然だ。

だが、負けたときは勝ったときの1000倍の損失となる。

実際には保有ポジション全体の合計損益で利食っているので、その意味では勝率は100%だが、個々のポジションでは勝率は85%と負けることもあり、平均損失は平均利益の3倍程度となっている。

これだと「ちゃんと損切りしてますよ」と嘘をつけなくもない。

勝率が100%でなければ損切りしているようにも見えるが、これはナンピンを利用したごまかしだ。

話は変わるが、昔から「損切りするから負けるのだ。損切りしなければ負けない」と豪語するトレーダーを何人も見かけた。

あのトレーダーたちは今でも生き残っているのだろうか。

最適化の指標について

EAを最適化する際、パフォーマンスを評価する指標には様々なものがある。

取引数で評価するとサンプルも多くなるので信頼性が高まる。だが、取引数自体は勝ち負けとは関係ないので、これは補助的な指標だ。

1000回は必要だという人がいる。

最適化するパラメータの数✕30〜100回は必要という人もいる。

私の場合、最適化するパラメータの数は1〜4個だが、パラメータが少なくても取引数が200回くらいはないと再現性もかなり落ちるような感じがする。

総損益で評価すると取引数が多くなる傾向がある。

取引数が多いこと自体はサンプルが多いということで、信頼性も高まる。

ただ、リスクを考慮していないため、薄利多売になりがちだ。

コストのわずかな変化がパフォーマンスに大きな影響を与えることがあり、再現性もいまいちという印象がある。

PFで評価するとリスクも考慮される。

PFが高いと再現性もそれなりにある。

ただ、取引数が極端に少ない場合があり、その場合では信頼性も低くなる。

RFで評価するとリスクも考慮され、取引数が極端に少なくこともほとんどなく、バランスが取れている。

ただ、総損益ほどではないが、若干、薄利多売の傾向がなくもない。総損益は取引数に比例して増加するが、ドローダウンの増加は取引数の増加に比べるとかなり小さい。このため、総損益の大きさがある程度影響し、薄利多売の傾向を生む。

RF/バックテスト年数を使う人もいる。

これも悪くないが、これが1以上であれば、ドローダウンを1年以内に回復できると考えるのは正しくないと思う。

バックテスト年数が増えればそれに比例して取引数が増えるだろうし、取引数が増えればそれに比例して総損益も増えるだろう。

だが、ドローダウンの増え方はそれよりずっと遅いので、RFはバックテスト年数の増加より速く大きくなるだろう。

したがって、RF/バックテスト年数はバックテスト年数が増えるに連れて少しずつ大きくなり、バックテスト年数が5年だと1未満だが、10年に伸ばすと1を超えるということが起こり得る。

必ずしも「ドローダウンを1年以内に回復できる」ということではないのだ。

個人的にはRFで評価し、PFが1.5未満のものは除外、というのがよさそうに思う。

取引数は200回はほしいが、必ずしもそこで線は引かない。

アウトオブサンプルテストでの再現性を見て判断する。

最近はモンテカルロ・シミュレーションでのリターン・ドローダウン・レシオが2未満、タープの期待値が0.1未満のものも除外という条件を加えて試している。悪くない印象だ。

以前はLR相関も試してみた。これが1に近いほど、資産曲線は直線に近くなり、安定して資産が増える。

だが、大きな損失があった場合だけではなく、大きな利益があった場合でもLR相関は悪化する。

少数の取引結果が影響を与えることがあるので、LR相関が高くても本当に安定しているのか、たまたま運よく損益の大きな取引を避けられただけなのかははっきりしない。

そういう訳で最近は使っていない。

どのような指標を使うのか、どのように使うのかについてはなかなか答えが出ない。今はこうだと思っていても明日には考えが変わるかもしれない。これからも試行錯誤が続くのだろう。

ただ忘れてはいけないのは、どの指標をどう使えば再現性が高くなるかを常に意識するということだ。

他人がやっていることをやる必要はない。実際には役に立たないものも少なくないだろう。

自分にとって使い勝手がよく、再現性もあるようなら、それで十分だ。

災害時のトレードについて

災害が起きたときにトレードをするというのは悪なのだろうか。不謹慎なことなのだろうか。

災害時にトレードをしていると、「こんなときにトレードをするのは不謹慎だ」とか、「災害を利用してトレードするのはとんでもない」と非難する人がいる。13年前の東日本大震災のときもそうだったし、今の能登半島地震でもそうだ。

私は不謹慎であるとも悪であるとも思わない。トレーダーは状況に応じて金融商品を売買しているだけで、そうしなければ生きていけない。被災者が必要とする物資を買い占めて転売するような売買をしているわけではないのだ。

ただ、自分の利益と他人の幸福が相反するようなとき、居心地の悪さは感じる。

私がFXを始めたばかりのころ、ニュージーランド地震東日本大震災が立て続けに起きた。

余震があったり、より深刻な被害状況が伝わると、NZドルは安くなり、円は高くなった。

だから、私はNZドルを売ったり、円を買ったりはしなかった。

余震が続いたり、被害状況がより深刻になることが利益になるのだとしたら、私にはそれを望む願望が生まれるかもしれない。

それが嫌だったからだ。

私は悪いことが起きるという予測はあまりしたくない。

なぜなら、予測をした以上は的中したいと思う。

的中することを望むということは、悪いことが起きるということを望むことになるからだ。

だが、これは心の弱い私の防衛策であって、他人に求めようとは思わない。

感情を排してトレードしたり、予測したりできる人もいるだろうし、それによって得た利益を困っている人に寄付する人もいるだろう。

人はそれぞれだ。

自分だけの正義感を振り回して他人のやることを一々非難するようなことは私の主義ではない。

ストラテジーテスターのスプレッド設定について

MT4でバックテストするときは固定スプレッドの値を設定する。

一方、EA側ではエントリーする際に許容する最大スプレッドを設定しているとする。

最大スプレッド>固定スプレッド

であるかぎり、エントリーは常に可能だ。

だが実際にはスプレッドは固定でないので、しばしば

最大スプレッド<実際のスプレッド

となり、エントリーが行われないということが起こり得る。

こうなると、実際のトレード数はバックテストでのトレード数よりずっと少なくなり、バックテスト結果が信用できないものになってしまう。

例えばポンドドルの朝スキャ戦略を作ったとする。

固定スプレッドは1.5pipsに設定する。利用するFX会社のスプレッドを調べると、ポンドドルは1日のうち、ほとんどの時間で1.5pips未満のようだ。

EA側では最大スプレッドを5pipsに設定しておく。

こうすれば、エントリーは常に可能となる。

バックテストすると、パフォーマンスも良好だ。これはいい!

だが、実際にトレードしてみると、トレード数はバックテストで想定される数と比べて激減し、トレードできた場合でもスプレッドが1.5pipsより大きくなっていて、想定していたより利益は小さく、損失は大きい。

なぜ、こういうことが起きるかといえば、早朝はスプレッドが通常の何倍、何十倍に広がることがあるからだ。

早朝にトレードするのでなければスプレッドから受ける影響は小さい。

スキャルでなければスプレッドから受ける影響は小さい。

だが、朝スキャは二重にスプレッドの影響を受ける。

したがって、MT4の固定スプレッドでバックテストした朝スキャ戦略のパフォーマンスはほとんど信用できないというわけだ。

もし朝スキャ戦略をバックテストするなら、実際のスプレッドに基づいた変動スプレッドを使う必要がある。

固定スプレッドは実際の平均スプレッドよりやや広めに設定することで負荷を与えることができる。

それはそれでメリットではあるが、MT4、MT5の仕様ではスプレッドをどう設定しようとBIDは変わらず、ASKだけが上下動する。

スプレッドを広く設定した場合、指値売りは影響を受けないが、指値買いはヒットしにくくなる。

指値売りは影響を受けないが、逆指値買いはヒットしやすくなる。

このように、スプレッドの設定は単に負荷を調整するだけではなくて、エントリーのタイミングにまで影響してしまうので、デメリットもある。

バックテストするときはそういう点も頭に入れておく必要がある。

ところで、MT5は変動スプレッドだ。

そこで、MT5を使って私の朝スキャEAを直近5年のポンドドルでバックテストしてみた。

エントリーは0時台のみ。

エントリーを許容する最大スプレッドによってトレード数がどう変化するかを調べた。結果は以下のようである。

100pips:175回
5pips:74回
3pips:44回
1.5pips:17回

スプレッドが100pipsを超えることは基本的にないので、100pipsはスプレッド無制限だと考えていい。

もしMT4を使って固定スプレッド1.5pipsでバックテストした場合、いずれの最大スプレッドでもトレード数は175回になる。

変動スプレッドの場合との乖離がいかに大きいかが分かるだろう。

最大スプレッド1.5pipsだとわずが17回で、10分の1に激減している。

しかも初めの2年で16回、後の3年で1回なのだ。直近ではトレードできる機会はないに等しい。

これはここ数年、早朝のスプレッド拡大がかなりひどい状況になっているのが原因だろう。

トレード機会をある程度生むにはエントリーする時間帯や許容する最大スプレッドを調整する必要がある。

ところがMT4の固定スプレッドでバックテストする人はそういうことにはお構いなしなのだ。実際にはトレード機会がないに等しいのに十分あると考え、パフォーマンスもいいと勘違いする。

人が公開するバックテスト結果は信用できないものが多いが、

MT4+固定スプレッド+朝スキャ

は特に信用できないと言っていいだろう。

そういえば、Xで今年(2023年)は朝スキャEAのパフォーマンスが悪かった、というポストを見かけた。

私個人は特に悪いとは感じていない。

そういう人はその朝スキャEAがどうやって作られたのかを確認したほうがいいかもしれない。

もしMT4+固定スプレッドで作られたEAなら作り直したほうがいいと思う。

スリッページについて

指値や逆指値を使うと、ネガティブスリッページは盛大に滑るのに、ポジティブスリッページはゼロか、ほぼゼロということがよくある。

FX会社がインチキをやっているのではないかと思ったりするが、そうとも言い切れない。

例えば、イベントで価格が上昇し、スプレッドも拡大したとする。

このとき、ASKは価格の上昇にスプレッドの拡大による上昇が加わるので、上昇は加速する。

一方、BIDは価格の上昇にスプレッドの拡大による下落が加わって上昇は減速する。

スリッページが上昇速度に比例するなら、買うときのネガティブスリッページが大きく、売るときのポジティブスリッページが小さくなるのは理由のあることだ。FX会社のインチキとばかりは言えない。

イベントで価格が下落し、スプレッドが拡大した場合では、売るときのネガティブスリッページが大きく、買うときのポジティブスリッページが小さくなるということが起こるだろう。

とはいえ、価格の変化は常にスプレッドの拡大を伴うわけではないから、ネガティブスリッページがポジティブスリッページより大きいことをいつでも正当化できるわけではない。

許容する最大スプレッドについて

普段、トレード戦略を開発するとき、許容する最大スプレッドは100pipsにしている。つまり、スプレッドがどんなに広がっても取引する。

そして最後に、バックテスト結果に影響しない範囲でスプレッドを絞る。

ドル円、ユーロ円だと3pipsくらいになることが多い。

3pips程度であれば、心理的にも受け入れていいかなと思う。

だが、もしそれが10pipsであったら、ちょっと受け入れにくいだろう。

そういう場合は最大スプレッドをどのくらいにするか、最適化するのもありかもしれない。

ただ、その場合も最適化が目的ではなく、心理的に受け入れられるレベルに収めるのが目的だ。最適化はバックテスト結果をよくするが、やりすぎると過剰最適化になって再現性を失う。やらないで済むならやらないほうがいい。

もし最大スプレッドを最適化するのであれば、取引数の減少がスプレッド無制限で取引する場合と比べて5%以内とすべきか。5%という数字に意味はないが、統計学有意水準でよく使われる数字であり、5%程度なら異常なスプレッドでの取引ということで除外してもいいか、ということだ。

最大スプレッドの最適化の影響を小さくするためには、やはり先ずはスプレッド無制限で最適化を行って最適なパラメータを決定し、最後の仕上げで最大スプレッドのみを最適化するのがいいだろうか。最大スプレッドの最適化を他のパラメータの選択に影響させないほうがいいと思う。

朝スキャ戦略の場合は最大スプレッドをどの程度にするかということが特に重要になるので工夫が必要となるかもしれない。朝スキャ戦略の場合でも上記のやり方で最適化したいが、場合によっては最大スプレッドの最適化を他のパラメータの選択に影響させることも考える。

ポジションの分散について

ポジションは一度に取るのがいいのか、分散して取るのがいいのか。

個人的には一度に取るのがいいと考えている。

ポジションを取った直後に相場が急変して痛い目に遭うと、分散しておけばよかったと思う。

だが、分散しても往々にして勝つときはすべて、あるいはほとんど勝ち、負けるときはすべて、あるいはほとんど負けるものだ。

また、ある価格で、あるいはある時間にポジションを取るのはそのタイミングがベストだと考えるからだろう。

それを一定のpips、あるいは一定の時間の間隔を空けて分散するのはベストでないタイミングでポジションを取るということになる。1個のトレードだけを見ると、分散すればよかったと思うこともあるが、日々ベストでないタイミングでポジションを取る損失の積み重ねのほうが大きいようにも思える。

こういうことは理屈で考えても埒が明かず、データに聞くのが一番だ。

間隔を空ける一定のpips、一定の時間で最適化してみればいい。

もし何pips、何分がよいという結果であれば分散効果はある。

だが、0pips、0分が最適という結果であれば、それは分散しないほうがいいということだ。

同じ銘柄、同じコンセプトのトレードであるなら、ポジションを取るタイミングを少しずらしたくらいで得られる分散効果は限定的だろう。もし分散効果を得たいのであれば、違う銘柄、違うコンセプトのトレードに分散するのが本筋だと思う。

ただ、分散したほうが安心だというのなら、バックテスト結果に関わらず分散したほうがいいとも思う。

快適にトレードする、ということも長くやるには大事なことだからだ。例えば、週末はポジションを気にせずに過ごしたいなら、持ち越したほうがバックテスト結果がよかっとしても閉じたほうがいい。不安を抱えたまま週末を過ごすということは、それはそれで損失なのだ。

MT5の時間について

MT5のツールボックスで「取引」や「口座履歴」の時間はサーバー時間、「エキスパート」や「操作ログ」の時間はPC時間だ。

EAはサーバー時間に基づいて動く(あえてサーバー時間以外を使うようにコードを書いていない限り)ので、仮にMT5を稼働しているPCの時間が遅れていたり進んでいたりしても影響はない(多分)。

フォワードテストの手順

  1. ストラテジーテスターの「フォワードテスト」で例えば「1/3」を選択する。すると、「日付」で指定された期間の初めの2/3がイン期間、残りの1/3がアウト期間となる。つまり「1/3」とはアウト期間の割合ということだ。「1/2」、「1/4」でも同様。「カスタム」を選択した場合、その右側の日付を指定すると、その日付の前がイン期間、後がアウト期間となる。
  2. オプティマイズ」で「完全アルゴリズム(遅い)」に設定する。時間をかけたくない場合は「遺伝的アルゴリズム(速い)」でもよい。
  3. 「スタート」をクリックする。

すると、イン期間、アウト期間それぞれで最適化するので結果を比較する。

なお、「オプティマイズ」で「無効化」に設定すると、イン期間、アウト期間ともに「値」で設定されたパラメータ値でバックテストされる。

MT5の謎仕様

MT5で最適化するとき、例えば

  • スタート:2
  • ステップ:4
  • ストップ:20

の設定だと、2、6、10、14、18の5回のバックテストになる。 ところが、

  • スタート:0.2
  • ステップ:0.4
  • ストップ:2.0

の設定だと、0.2、0.6、1.0、1.4、1.8、2.2の6回のバックテストになる。

ポジションのコメントについて

MT5でコメントをつけたポジションを部分決済すると、残りのポジションはコメントなしになる。

MT5のストラテジーテスターでスプレッドを設定する

MT5のストラテジーテスターでスプレッドを設定する。

環境

  • OANDA MetaTrader 5: 5.00 build 4040
  • MetaEditor: 5.00 build 4040

手順

ストラテジーテスターでスプレッドを設定するには

  1. ストラテジーテスターの「設定」タブをクリックする。
  2. 「銘柄」の行の右端にあるアイコンをクリックする。
  3. 「テスト済みの銘柄」で「スプレッド」をダブルクリックする。
  4. 設定したいスプレッドの数値を入力する(ポイント単位。1ピップス=10ポイント)。
  5. 「YES」ボタンをクリックする。

とする。

MT4とMT5の違い

MT5では限定的な固定スプレッドになる

MT4の場合、例えばスプレッドを10と設定すると、

ASK=BID+10

となると思う。

ところが、MT5だと

ASK≧BID+10

となる点に注意。

MT5の場合、スプレッドの初期値は「フローティング」であり、変動スプレッドが適用される。ここでスプレッドを10と設定すると、すべてのスプレッドが10になるわけではなく、10未満のもののみが10になる。

例えば、変動スプレッドが

8
9
10
11
12
11
10
9
8

のように変動していたとする。ここでスプレッドを10と設定すると

10
10
10
10
10
10
10
10
10

とはならず、

10
10
10
11
12
11
10
10
10

のように10未満の数値のみが10に置き換えられる。

MT5ではスプレッド設定がモデルによっては反映されない

MT5でスプレッド設定が反映されるのはモデルが①「全ティック」、②「1分足OHLC」、③「始値のみ」+「M1」のときだけのようである。

始値のみ」+「M1」で先ずバックテストし、その後で足の種類を「M1」以外に変更してからバックテストした場合も反映されているように見える。しかし、いかにも裏技的な使い方で、ちゃんと反映されているのか不安はある。

MQL5でモンテカルロ・シミュレーションを実行する関数

MQL5でモンテカルロ・シミュレーションを実行する関数を作った。

モンテカルロ・シミュレーションと一口に言ってもやり方はいろいろある。ここでは

  • バックテストした後に各トレードの損益を1年当たりのトレード数だけ重複ありで抽出し、リターン、ドローダウン、リターン・ドローダウン比を計算する。
  • そして、これを2500回繰り返し、リターン、ドローダウン、リターン・ドローダウン比それぞれの中央値を出力する。
  • また、2500回中で黒字だった回数、破産した回数を計算して黒字確率、破産確率も出力する。
  • 黒字とは最終損益がプラスであったこと、破産とは最初に入金として設定した初期証拠金が最小ロット数を注文するに必要な金額を下回ったことを指す。
  • 戻り値はリターン・ドローダウン比の中央値である。

といった内容である。これはケビン・J・ダービー著「システムトレード 検証と実践」で紹介していたやり方を参考にした。

環境

  • OANDA MetaTrader 5: 5.00 build 4040
  • MetaEditor: 5.00 build 4040

MQL5でモンテカルロ・シミュレーションを実行する関数

//+------------------------------------------------------------------+
//| モンテカルロ・シミュレーション(2023/11/26動作確認)                             |
//+------------------------------------------------------------------+
double MonteCarlo()
 {
  int trades=0; // 取引数
  datetime start_time=0; // 開始時間
  datetime end_time=0; // 終了時間
  double profit[100000]; // 損益
  HistorySelect(0,TimeCurrent());
  //--- リスト内番号0は初期証拠金なので除外
  for(int i=1;i<HistoryDealsTotal();i++)
    {
    ulong deal_ticket=HistoryDealGetTicket(i); // 約定のチケット
    double deal_profit=HistoryDealGetDouble(deal_ticket,DEAL_PROFIT); // 約定の損益
    datetime deal_time=(datetime)HistoryDealGetInteger(deal_ticket,DEAL_TIME); // 約定の時間
    // 損益を含まないデータを除外。損益0のケースはまれなので無視
    if(deal_profit!=0)
      {
      trades+=1;
      profit[trades-1]=deal_profit;
      }
    //--- 最初の取引の決済時間を開始時間と見なす
    if(i==1)
      start_time=deal_time;
    //--- 最後の取引の決済時間を終了時間と見なす
    if(i==HistoryDealsTotal()-1)
      end_time=deal_time;
    //---
    }
  //---
  double years=double(end_time-start_time)/60/60/24/365; // バックテスト年数
  double initial_deposit=TesterStatistics(STAT_INITIAL_DEPOSIT); // 初期証拠金
  //--- 最小証拠金を計算する(バックテスト終了日の終値+スプレッドが基準になる)
  double ask=SymbolInfoDouble(Symbol(),SYMBOL_ASK); // ASK(BIDを基準にするより最小証拠金がわずかに大きくなる、つまり破産確率がわずかに高くなる)
  double volume_min=SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN); // 最小取引数量  
  double contract_size=SymbolInfoDouble(Symbol(),SYMBOL_TRADE_CONTRACT_SIZE); // 契約サイズ(通常は10万通貨)
  double leverage=(double)AccountInfoInteger(ACCOUNT_LEVERAGE); // レバレッジ(アカウント上限のレバレッジ。通常は25倍)
  double min_equity=ask*volume_min*contract_size/leverage; // 最小証拠金
  //---
  double ret[2500]; // リターン(%)
  double dd[2500]; // ドローダウン(%)
  double ret_dd[2500]; // リターン・ドローダウン比(%)
  double surpluse[2500]; // 黒字(黒字なら1、黒字でなければ0)
  double ruin[2500]; // 破産(破産したら1、破産しなければ0)
  for(int i=0;i<2500;i++)
    {
    double cum_profit=0; // 累積損益
    double max_profit=0; // 最大損益
    double max_dd=0; // 最大ドローダウン
    double max_ddp=0; // 最大ドローダウン(%)
    //--- 黒字と破産は0で初期化
    surpluse[i]=0;
    ruin[i]=0;
    //--- 1年当たりの取引数の数だけ損益を重複ありでランダム抽出し、各パフォーマンスを計算する
    MathSrand(i);
    for(int j=0;j<MathRound(trades/years);j++)
      {
      //--- 乱数は0以上の整数なので、取引数で割った余りは0から取引数-1の間の乱数になる
      cum_profit+=profit[MathRand()%trades];
      //--- 最大損益を計算する
      if(max_profit<cum_profit)
        max_profit=cum_profit;
      //--- 最大ドローダウンを計算する
      if(max_dd<max_profit-cum_profit)
        max_dd=max_profit-cum_profit;
      //--- 最大ドローダウン(%)を計算する
      if(max_ddp<max_dd/(initial_deposit+max_profit)*100)
        max_ddp=max_dd/(initial_deposit+max_profit)*100;
      //--- 初期証拠金+累積損益が一時でも最小証拠金を下回ったら破産と見なして1を入力する
      if(initial_deposit+cum_profit<min_equity)
        ruin[i]=1;
      //---
      }
    //--- 最終的な累積損益が0より大きければ黒字と見なして1を入力する
    if(cum_profit>0)
      surpluse[i]=1;
    //---
    ret[i]=cum_profit/initial_deposit*100;
    dd[i]=max_ddp;
    if(dd[i]!=0)
      ret_dd[i]=ret[i]/dd[i];
    else
      ret_dd[i]=0;
    }
  //--- 昇順(小→大)に並べ替える
  ArraySort(ret);
  ArraySort(dd);
  ArraySort(ret_dd);
  //--- 破産した回数と黒字だった回数を計算する
  double ruins=0; // 破産した回数
  double surpluses=0; // 黒字だった回数
  for(int i=0;i<2500;i++)
    {
    ruins+=ruin[i];
    surpluses+=surpluse[i];
    }
  //--- 結果を出力する
  Print("★★★モンテカルロ・シミュレーション★★★");
  Print("初期証拠金=",DoubleToString(initial_deposit,0),"円");
  Print("リターンの中央値=",DoubleToString((ret[1249]+ret[1250])/2,2),"%");
  Print("ドローダウンの中央値=",DoubleToString((dd[1249]+dd[1250])/2,2),"%");
  Print("リターン・ドローダウン比の中央値=",DoubleToString((ret_dd[1249]+ret_dd[1250])/2,2));
  Print("黒字確率=",DoubleToString(surpluses/2500*100,2),"%");
  Print("破産確率=",DoubleToString(ruins/2500*100,2),"%");
  //--- リターン・ドローダウン比の中央値を戻り値とする
  double res=(ret_dd[1249]+ret_dd[1250])/2; // 戻り値
  //---
  return(res);
 }

使用例

OnTester()関数内でこの関数を利用する。

double OnTester()
 {
  return(MonteCarlo()*10000);
 }

なぜ戻り値を1万倍しているかというと、「カスタム最大」で最適化すると「結果」では小数が省略されるため、小数部分が分からなくなるからである。

あるトレード戦略でバックテストすると以下のように出力された。

★★★モンテカルロ・シミュレーション★★★
初期証拠金=1000000円
リターンの中央値=3.68%
ドローダウンの中央値=1.25%
リターン・ドローダウン比の中央値=2.87
黒字確率=94.72%
破産確率=0.00%

ケビン・J・ダービー氏によると、リターン・ドローダウン比の中央値が2未満のトレード戦略は使い物にならないとのことである。

なお、戻り値は28685.79426565952で、2.87(出力では四捨五入されている)の1万倍となっている。

MQL5でエグジット後の秒数を返す関数

MQL5で直近のトレードがエグジットしてから経過した秒数を返す関数を作った。

直近のトレードがエグジットしてから一定の時間を経過するまでエントリーしたくないときなどに使える。

この関数はEAが最後にエグジットした時間を起点としている。このため、トレーダーが手動でポジションを決済した場合、その時間は起点とはならず、あくまでもEAが最後にエグジットした時間が起点となったままである点に注意が必要だ。恐らく、EA自身がエグジットした場合しかエグジット時間が記録されないのだろう。

ポジションが存在するかいなかを返す関数を内部で使用しているので、この関数も併せて書く。

(補足)取引が存在するかいなかを返す関数も追加しておく。この関数は必要ないが、エグジット後の秒数を返す関数を直近のトレードがエグジットしてから一定の時間を経過するまでエントリーしたくないときに使う場合は併用したほうがいい。例えばエグジットしてから1時間以上経過しないとエントリーしないとする。これを売買ルールに入れると、1回もエントリーしないということが起きる。なぜなら最初のエントリーではその前のエグジットが存在しないため、エグジット後の秒数が0になるからだ。よって、少なくとも1回は取引が存在することは確認する必要がある。

なお、MQL5での取引とは入金、エントリー、エグジットのタイミングで発生する。例えば、バックテストをして10回トレードがあった場合、取引数は21回になる。最初の入金1回、エントリー10回、エグジット10回で計21回というわけだ。ここではトレードはエントリー1回、エグジット1回のセットという意味で使うことにする。したがって必ずしも「取引=トレード」ではない。

環境

  • OANDA MetaTrader 5: 5.00 build 4040
  • MetaEditor: 5.00 build 4040

エグジット後のバー数を返す関数

//+------------------------------------------------------------------+
//| 取引が存在するかいなか(2023/12/08動作確認)                               |
//+------------------------------------------------------------------+
bool isDealExist(int magic // マジックナンバー
                )
 {
  bool res=false; // 戻り値
  HistorySelect(0,TimeCurrent());
  for(int i=0;i<HistoryDealsTotal();i++)
    {
    ulong deal_ticket=HistoryDealGetTicket(i); // 取引のチケット
    string deal_symbol=HistoryDealGetString(deal_ticket,DEAL_SYMBOL); // 取引の銘柄
    ulong deal_magic=HistoryDealGetInteger(deal_ticket,DEAL_MAGIC); // 取引のマジックナンバー
    if(deal_symbol==Symbol() && deal_magic==magic)
      {
      res=true;
      break;
      }
    }
  return(res);
 }
//+------------------------------------------------------------------+
//| ポジションが存在するかいなか(2023/11/10動作確認)                            |
//+------------------------------------------------------------------+
bool isPositionExist(int magic // マジックナンバー
                    )
 {
  bool res=false; // 戻り値
  for(int i=0;i<PositionsTotal();i++)
    {
    string position_symbol=PositionGetSymbol(i); // ポジションの銘柄
    long position_magic=PositionGetInteger(POSITION_MAGIC); // ポジションのマジックナンバー
    if(position_symbol==Symbol() && position_magic==magic)
      {
      res=true;
      break;
      }
    }
  return(res);
 }
//+------------------------------------------------------------------+
//| エグジット後の秒数(2023/12/09動作確認)                                   |
//+------------------------------------------------------------------+
int SecondsSinceExit(int magic // マジックナンバー
                    )
 {
  HistorySelect(0,TimeCurrent());
  int res=0; // 戻り値
  //--- 新しい順に検索している点に注意。ここでは最新の取引のみ検索対称としている
  for(int i=HistoryDealsTotal()-1;i>=0;i--)
    {
    ulong deal_ticket=HistoryDealGetTicket(i); // 取引のチケット
    string deal_symbol=HistoryDealGetString(deal_ticket,DEAL_SYMBOL); // 取引の銘柄
    ulong deal_magic=HistoryDealGetInteger(deal_ticket,DEAL_MAGIC); // 取引のマジックナンバー
    ulong deal_entry=HistoryDealGetInteger(deal_ticket,DEAL_ENTRY); // 取引のエントリー
    if(deal_symbol==Symbol() && deal_magic==magic && deal_entry==DEAL_ENTRY_OUT)
      {
      datetime deal_time=(datetime)HistoryDealGetInteger(deal_ticket,DEAL_TIME); // 取引の時間
      res=int(TimeCurrent()-deal_time);
      break;
      }
    }
  //---
  return(res);
 }

使用例

マジックナンバーが1であるとして、直近のトレードがエグジットしてから1時間(3600秒)はエントリーしたくない場合、以下のようにする。

if((isDealExist(1)==false || SecondsSinceExit(1)>=3600) && 他のエントリー条件)
  新規注文;