スプレッドの影響
スプレッドの影響は周知の通り、トレーダーに対して不利益をもたらす。
不利益というより、トレードを行うためのコストであるから仕方ない。
短期トレードであればあるほど、収益に対するスプレッドの割合が上昇するため、影響を受けやすくなる。
多くの場合において、トレーダーが勝てない理由だとも考えられる。
たかが3ポイント、されど3ポイント。
ちりつもの影響力を数字で見てみる
1回のトレードで±20Pipsの損益を繰り返すトレーダーAさん(30)がいる。 彼のトレードの勝率はピッタリ50%である。 初期証拠金100万円を元に頑張ってもらう。
彼がスプレッドの無い世界で生きていたとして、還暦を迎えるまでの30年間トレードしてもらう。
1日1回のトレードを年間200日、30年で6,000回のトレードを続けてもらう。
ちなみにさっさと全損されても困るのでロットは0.1ロットで固定させる(要するに±2,000円)。
Aさんが60歳を迎えた時、どうなっているだろうか。
Aさんに10万回転生してもらい、負けた人生・勝った人生・引き分けの人生をカウントしてみた。
win:49,349 lose:49,637 draw:1,014
想像の通り勝敗は半々になる。
証拠金を完全にトントンで維持した場合はオマケとして勝ちと見なすなら、還暦を迎えた時点での生涯勝率は50%をほんのり超える。
では、彼がスプレッド3ポイント固定の世界で生きていたとして、同じことをやってもらうとどうなるだろうか。
3ポイントだけ不利であるから、+1,970円/-2,000円のトレードを勝率50%で6,000回繰り返す人生を送ってもらう。
win:28,123 lose:71,877 draw:0
7割強の人生で負けてしまった。
2,000円に対して30円、1.5%のスプレッドでこれだけの影響が出てくる。
Aさんが少し短気になって、±10Pipsの損益を繰り返すようになったら、いったいどうなってしまうのか。
win:4,788 lose:95,212 draw:0
ほぼ負けが約束された人生になる。
短気なAさんはそんなに沢山トレードしてはいけない。
短気なAさんが半分以上勝ち越すためには、2つの方法がある。
1つは勝率を上げることだ。
以下を式を解くと、1回1回のトレード勝率を約0.76%上げることができれば、生涯勝率は5割まで回復することが分かる。
970 * X - 1000 * (1 - X) = 0 //トントン(=0)になるために必要な勝率(X)を求める 970X - 1000 + 1000X = 0 1970X = 1000 X = 1000 / 1970 X = 0.5076142132
もう1つの方法は、トレードを30年間で1回だけ行うようにすること。
本末転倒だが、これで生涯勝率は50%になる。
EAで扱うスプレッド
システムトレードにおいても、スプレッドに関して多くのパターンを考慮することになる。
MQLの中では、売値(Bid)と買値(Ask)を使用する場面がいくつかある。
使い方を間違えると、意図したトレードが出来なくなるので注意が必要になる。
BidとAskを必ず使うのは、エントリー時だ。
ロングなら必ずAskで、ショートなら必ずBidでエントリーする必要がある。
SLとTPを固定Pipsで設定するような場合、例えば-10PipsにSLを置きたい時、ロングであればAsk-10Pipsの位置にSLを設定するだろう。
それ以外の箇所で、特に必要でないならAskの使用は極力避けなければならない。
チャートはBidで描画されていて、インジケータの値はもちろん、多くのデータは全てBidが元になっているからだ。
レートがMAを超えたら
といった条件を書く際に、原則、レートとしてAskを使ってはいけない。
※Askベースで描かれたチャートならOK
かなりの短期トレードに限った話だが、10ポイントのスプレッドがある通貨ペアで10ポイントの値動きを取っても利益は出ない。
スプレッド込みで利益が出るのかを確認しなければならない。
また、スプレッドは変動するので、不利なスプレッドではエントリーしない・より有利なスプレッドの時にエントリーするという方法が考えられる。
EAの入力パラメータとしてスプレッドを指定して、その値以上の時はエントリーさせないという処理は非常に簡単で効果が大きい。
有利なスプレッドの選択方法としては、例えばEA稼働中にスプレッドの最頻値を記録して、その値と同等以下になった場合にのみエントリーを許可するようにする。
下記のようなコード(MQL5)で、最も多く出現したスプレッドを選択することができる。
出現回数が同等のものが複数ある場合は、より低いスプレッドを選択する。
struct Spread { int spread; int count; }; Spread spreads[]; void OnTick(){ recordSpread(SymbolInfoInteger(Symbol(),SYMBOL_SPREAD)); //...(中略)... int mosIndex=getModeOfSpreadIndex(); if(mosIndex!=-1){ if(SymbolInfoInteger(Symbol(),SYMBOL_SPREAD)<=spreads[mosIndex].spread){ //エントリーして良い } } } void recordSpread(int spread){ bool exist=false; for(int i=0;i<ArraySize(spreads);i++){ if(spreads[i].spread==spread){ spreads[i].count++; exist=true; break; } } if(!exist){ int newSize=ArraySize(spreads)+1; int resizeResult=ArrayResize(spreads,newSize); if(resizeResult!=-1){ Spread newSpread; newSpread.spread=spread; newSpread.count=1; spreads[newSize-1]=newSpread; }else{ Print("lost"); } } } int getModeOfSpreadIndex(){ int maxCount=0; int resultIndex=-1; for(int i=0;i<ArraySize(spreads);i++){ maxCount=maxCount==0 ? spreads[i].count : spreads[i].count>maxCount ? spreads[i].count : maxCount; } for(int i=0;i<ArraySize(spreads);i++){ if(spreads[i].count==maxCount){ resultIndex=resultIndex==-1 ? i : spreads[resultIndex].spread>spreads[i].spread ? i : resultIndex; } } return resultIndex; }