「Market closed」のエラー

環境

  • OANDA MetaTrader 5
  • Version: 5.00 build 3211

日足でバックテストするとエラーになる

MT5で足の種類を「Daily」、モデルを「始値のみ」でバックテストすると

failed market buy 0.1 USDJPY [Market closed]

のようなエラーが出てくることがある。つまり「市場が閉じている」ので取引ができないのだ。

原因は取引時間の設定

原因はいろいろあるかもしれないが、私の環境では取引時間の設定が原因だ。

設定を確認してみよう。

①ストラテジーテスターの「設定」タブを選択する。

②「銘柄」で「USDJPY」を選択する。

③「銘柄」の行の右端にある「S」みたいなボタンをクリックする。

「テスト済みの銘柄」というウィンドウが開く。

④「テスト済みの銘柄」で一番下までスクロールする。

すると、私の環境では以下のようになっていた。

セッション  相場情報      取引
日曜日
月曜日      00:00-24:00  00:03-23:55
火曜日      00:00-24:00  00:10-23:55
水曜日      00:00-24:00  00:10-23:55
木曜日      00:00-24:00  00:10-23:55
金曜日      00:00-24:00  00:10-23:55
土曜日

足の種類を「Daily」、モデルを「始値のみ」でバックテストした場合、取引のタイミングは「00:00」になる。

ところが、設定では取引が始まるのは月曜日では「00:03」から、火曜日〜金曜日では「00:10」からとなっている。

つまり、「00:00」の時点ではまだ取引時間が始まっておらず、「市場が閉じている」状態なのだ。この取引できない時間はメンテナンス時間だろう。

その時間に取引しようとしたためにエラーとなったのだ。

設定を修正するのがよさそう

エラーを回避する最も簡単な方法は「モデル」を「1分足 OHLC」に変更することだ。そうすれば取引が成立するまでエラーは出続けるが、月曜日なら「00:03」に、火曜日〜金曜日なら「00:10」に取引が成立して問題はない。

エラーが出るのが嫌なら市場が開いている時間でしか取引しないようなコードをEAに書き込めばよい。実際、私もこれまでそのようにしてきた。

ただ、問題はバックテストに時間がかかることだ。日足なら一瞬で終わるようなバックテストが1分足の四本値を参照しているために10秒以上かかったりする。最適化で何十通り、何百通りもバックテストをする場合、さすがにこの時間のかかり方は無視できない。

となると、24時間休みなしで取引できるように設定を変えるのが一番だと思う。

上の④の続きからその手順を示す。

⑤「月曜日」の「取引」の「00:03-23:55」の部分をダブルクリックする。

「セッション USDJPY: 月曜日」というウィンドウが開く。

⑥「複数のサービス稼働時間を設定」のチェックを外す。

⑦「YES」ボタンをクリックする。

「00:03-23:55」が「00:00-24:00」に変わる。

⑧同様にして火曜日〜金曜日をすべて「00:00-24:00」に変える。

⑨「テスト済みの銘柄」で「YES」ボタンをクリックする。

これで設定変更終了。設定は以下のようになっているはずだ。

セッション  相場情報      取引
日曜日
月曜日      00:00-24:00  00:00-24:00
火曜日      00:00-24:00  00:00-24:00
水曜日      00:00-24:00  00:00-24:00
木曜日      00:00-24:00  00:00-24:00
金曜日      00:00-24:00  00:00-24:00
土曜日

このやり方の唯一面倒な点は銘柄ごとに設定を変更しなければならないこと。ただ、一回設定を変更すれば、その変更は保存されるので毎回バックテストに時間をかけるよりはましだろう。

設定を元に戻したい場合は「テスト済みの銘柄」で「初期値」ボタンをクリックする。設定が元に戻っていることを確認したら「YES」ボタンをクリックする。

メンテナンス時間に取引しているので、メンテナンス終了直後に取引する場合と比べてバックテストの結果に若干の相違はあるが、無視していいレベルだと思う。

レジサポのバッファの目安

レジスタンスの「少し」上、サポートの「少し」下の「少し」ってどのくらい?

レジスタンスライン、サポートラインのバッファはどのくらいがよいのかということを考えてみる。

相場の格言に「鬼より怖い一文新値」というのがある。一文は最小単位の貨幣。FXなら1銭とか1pipsといったところだろうか。

ブレイクアウトを狙ってエントリーするときやストップを置くとき、ダマシに遭わないためにもレジスタンスライン、サポートラインを「少し」超えたところをエントリーポイント、ストップポイントとするのがよい、とよく言われる。

だが、その「少し」というのが具体的にどのくらいなのか、説明してくれる人はあまりいない。例え説明してくれる人がいても、大抵はその人の個人的な経験に基づくものであって、取引する銘柄や見ている足の種類によっても違ってくるのであるから、あまり信用はできない。

ヒゲの長さをバッファの目安とする

ぶっちゃけると「正解はない、終了」ということになるのだが、それでは身も蓋もないので、簡単に目安を見つける方法を考えてみた。

バッファは値幅だが、「終値で超えたら」という考え方もある。高値、安値でレジスタンスライン、サポートラインを一時的に超えても終値では超えなかった、ダマシだった、ということがよくある。それで「終値で超えたら」というのをダマシを避ける方法の一つとするわけだ。

すると、高値、安値と終値の差、つまり上ヒゲ、下ヒゲがどのくらいの値幅であるか分かれば、その値幅をも超えるなら終値でも超えるだろうという見当はつく。

ただ、上ヒゲ、下ヒゲの値幅というのはやはり取引する銘柄や見ている足の種類によって違ってくるので、もっと一般的な方法を考えたい。そこで思いついたのがランダムウォークで動くデータを大量に作って上ヒゲ、下ヒゲが高値マイナス安値のレンジのどのくらいの割合になるか調べてみようということだ。

レンジは例えばテクニカル指標のATRを使えばいい。割合さえ分かっていればすぐ計算できる。

前置きが長くなったが、Pythonを使って、その割合を調べてみる。一応、コードも参考に書くが、大事なのは結果だけなのでコードを理解する必要はない。

サンプルコード

①1分足のランダムウォークのデータを1年分作る。

import numpy as np
import pandas as pd
# 再現性を得るため、ここでは乱数のシードを固定する。
np.random.seed(seed=0)
# 平均0.0、標準偏差1.0のランダムデータを1秒に1個として1年分作成する。
rnd = np.random.normal(0.0, 1.0, 60*60*24*365)
# データの累積和を求めてランダムウォークデータとする。
data = np.cumsum(rnd)
# ランダムウォークデータを2021年1月1日から1秒単位の時系列データに変換する。
index = pd.date_range('1/1/2021', periods=60*60*24*365, freq='S')
s1 = pd.Series(data, index=index)
# 1分足のランダムウォークデータを作成する。
m1 = s1.resample('1min').ohlc()

四本値始値終値の大きい方、小さい方を求める。

o = np.array(m1.open)  # 始値
h = np.array(m1.high)  # 高値
l = np.array(m1.low)  # 安値
c = np.array(m1.close)  # 終値
max_oc = np.fmax(o, c)  # 始値、終値の大きい方
min_oc = np.fmin(o, c)  # 始値、終値の小さい方

③レンジ、実体、上ヒゲ、下ヒゲの値幅を求める。

high_low_range = h - l  # レンジ
real_body = max_oc - min_oc  # 実体
upper_shadow = h - max_oc  # 上ヒゲ
lower_shodow = min_oc - l  # 下ヒゲ

④レンジ、実体、上ヒゲ、下ヒゲの値幅の平均を出力する。

print("レンジの平均 =", high_low_range.mean())
print("実体の平均 =", real_body.mean())
print("上ヒゲの平均 =", upper_shadow.mean())
print("下ヒゲの平均 =", lower_shodow.mean())
レンジの平均 = 11.146347709890597
実体の平均 = 6.1274280719479375
上ヒゲの平均 = 2.507315791275367
下ヒゲの平均 = 2.5116038466672874

⑤実体、上ヒゲ、下ヒゲのレンジに対する割合を出力する

print("実体の割合 =", real_body.mean()/high_low_range.mean())
print("上ヒゲの割合 =", upper_shadow.mean()/high_low_range.mean())
print("下ヒゲの割合 =", lower_shodow.mean()/high_low_range.mean())
実体の割合 = 0.5497251863505772
上ヒゲの割合 = 0.22494505433833958
下ヒゲの割合 = 0.22532975931108282

バッファの目安は「ATR × 0.225」

乱数で生成したデータなので多少の誤差はあるが、ヒゲの割合はレンジの0.225くらいと見ていいだろう。先程も書いたが、レンジはテクニカル指標のATRを使えばいい。そのATRに0.225を掛ければ、それがバッファの目安である。

例えば下のサイトでドル円日足のATR(14)を見ると(2022/03/11終値時点)

0.7182

となっている。

https://jp.investing.com/currencies/usd-jpy-technical

これに0.225を掛けると

0.7182 × 0.225 = 0.161595

となる。15銭(15pips)くらいで、大体そんな感じかなあといったところ。

UbuntuでPython

UbuntuにAnacondaをインストールしてPythonを使う。

UbuntuではデフォルトでPythonが使える。

だが、Anaconda版のPythonはデータサイエンスや機械学習用のライブラリが多く用意されており、依存問題も解消されていて便利だ。そこで、ここではAnacondaをインストールしてPythonを使うこととする。

環境

Anacondaのダウンロード

①端末で以下のコマンドをコピー&ペーストして「Enter」キーを押す。

cd ~/ダウンロード

Anacondaは「~/ダウンロード」に保存することとする。

そこで先ずは作業ディレクトリを「~/ダウンロード」にする。

②端末で以下のコマンドをコピー&ペーストして「Enter」キーを押す。

wget https://repo.anaconda.com/archive/Anaconda3-2021.11-Linux-x86_64.sh

現時点で最新のLinux用Anacondaをダウンロードする。

Anacondaのインストール

上に続けて実行する。

①端末で以下のコマンドをコピー&ペーストして「Enter」キーを押す。

bash Anaconda3-2021.11-Linux-x86_64.sh

②端末の

Please, press ENTER to continue
>>>

で「Enter」キーを押し続ける

③端末に

Please answer 'yes' or 'no':'
>>>

と表示されたら離す。

④「yes」と入力して「Enter」キーを押す。

⑤端末の

[/home/****/anaconda3] >>>

で「Enter」キーを押す。インストールが始まるので、しばらく待つ。

⑥端末の

Do you wish the installer to initialize Anaconda3
by running conda init? [yes|no]
[no] >>>

で「yes」と入力して「Enter」キーを押す。

Anacondaのアップデート

○端末で以下のコマンドをコピー&ペーストして「Enter」キーを押す。

conda update --all --yes

Jupyter Notebookの起動

Jupyter Notebookを起動したいときは以下のようにする。

○端末で以下のコマンドをコピー&ペーストして「Enter」キーを押す。

jupyter notebook

※普通はこれでブラウザで開けるはずなのだが、なぜか、私の環境ではFirefoxが起動しても「ファイルへのアクセスが拒否されました」と出て開けない。端末に

Or copy and paste one of these URLs:
    http://localhost:8888/?token=************************************************
 or http://127.0.0.1:8888/?token=************************************************

とあったので、上の

http://localhost:8888/?token=************************************************

または

http://127.0.0.1:8888/?token=************************************************

の部分を右クリックし、「リンクを開く」を選択すると開くことができた。一時的な不具合なのかよく分からないが、とりあえず、これで開けるので、しばらく様子を見る。

ACオシレーターの検証

今回はACオシレーターを調べてみる。

環境

  • OANDA MetaTrader 5
  • Version: 5.00 build 3211

ストラテジーテスターの設定

  • 銘柄:USDJPY
  • 足の種類:Daily
  • 日付:期間限定
  • 開始日:2012.01.01
  • 終了日:2022.01.01
  • フォワードテスト:キャンセル
  • 延滞:遅延ゼロ、理想的な実行
  • モデル:1分足 OHLC
  • より高速計算のためのピップ単位利益:チェックあり
  • 入金:100000
  • レバレッジ:1:25
  • オプティマイズ:無効化
  • チャート、指標、取引を表示するビジュアルモード:チェックなし

順張り戦略

エントリー

エグジット

検証結果

f:id:fxst24:20220219214911p:plain

結果は全然だめ。

逆張り戦略

エントリー

エグジット

検証結果

f:id:fxst24:20220219214940p:plain

結果はいまいち。

サンプルコード

(警告)バックテスト用のサンプルコードなので、リアルトレードに使用しないこと。

//2022年2月19日に更新
//ライブラリー
#include <Trade\Trade.mqh>
//Input変数
input int TradingStrategy=1;//トレード戦略のナンバー
input double Lot=0.1;//ロット数
input double MaxAcceptableLoss=1.0;//許容可能な最大損失(総資産を1とした場合の割合)
//オンティック関数
void OnTick(void)
{
  //トレードに使う変数
   string id="AC_"+Symbol();//注文のID
   bool buy_entry=false;//買いエントリー
   bool sell_entry=false;//売りエントリー
   bool buy_exit=false;//買いエグジット
   bool sell_exit=false;//売りエグジット
   double sl=0.0;//損切り価格。使用しないときは0.0
   double tp=0.0;//利食い価格。使用しないときは0.0
   //順張り戦略
   if(TradingStrategy==1)
   {
      int handle=iAC(NULL,0);//テクニカル指標のハンドル
      double ac[10];//テクニカル指標を格納する配列
      CopyBuffer(handle,0,0,10,ac);//テクニカル指標を配列に格納
      ArrayReverse(ac,0,10);//テクニカル指標を逆に並べ替え
      //売買ルール
      buy_entry=ac[1]>ac[2] && ac[2]>ac[3] && ac[2]>0.0;
      sell_entry=ac[1]<ac[2] && ac[2]<ac[3] && ac[2]<0.0;
      buy_exit=ac[1]<ac[2];
      sell_exit=ac[1]>ac[2];
   }
   //逆張り戦略
   if(TradingStrategy==2)
   {
      int handle=iAC(NULL,0);//テクニカル指標のハンドル
      double ac[10];//テクニカル指標を格納する配列
      CopyBuffer(handle,0,0,10,ac);//テクニカル指標を配列に格納
      ArrayReverse(ac,0,10);//テクニカル指標を逆に並べ替え
      //売買ルール
      buy_entry=ac[1]>ac[2] && ac[2]>ac[3] && ac[3]>ac[4] && ac[1]<0.0;
      sell_entry=ac[1]<ac[2] && ac[2]<ac[3] && ac[3]<ac[4] && ac[1]>0.0;
      buy_exit=ac[1]<ac[2];
      sell_exit=ac[1]>ac[2];
   }
   //市場が開いていればエントリー、エグジットを行う
   if(IsMarketOpen())
   {
      CTrade trade;//取引関数にアクセスするためのクラス
      //買いエグジット成立、または許容最大損失を超えたら買いポジションを決済
      if(buy_exit || MaxIntradayLoss(MaxAcceptableLoss))
      {
         StrategyExit(trade,POSITION_TYPE_BUY,id);
      }
      //売りエグジット成立、または許容最大損失を超えたら売りポジションを決済
      if(sell_exit || MaxIntradayLoss(MaxAcceptableLoss))
      {
         StrategyExit(trade,POSITION_TYPE_SELL,id);
      }
      //買いエントリー成立、かつ許容最大損失を超えていなければ買いポジションを保有
      if(buy_entry && MaxIntradayLoss(MaxAcceptableLoss)==false)
      {
         StrategyEntry(trade,ORDER_TYPE_BUY,Lot,sl,tp,id);
      }
      //売りエントリー成立、かつ許容最大損失を超えていなければ売りポジションを保有
      if(sell_entry && MaxIntradayLoss(MaxAcceptableLoss)==false)
      {
         StrategyEntry(trade,ORDER_TYPE_SELL,Lot,sl,tp,id);
      }
   }
}
//ここから下はおまじない

//許容可能な最大日中損失(含み損益込み)を超えていないかチェック
bool MaxIntradayLoss(double max_acceptable_loss)
{
   static double equity_at_open=0.0;//1日の開始時の資産
   double equity_now;//現在の資産
   double pl;//損益
   MqlDateTime mdt;//日付時刻の構造体
   TimeCurrent(mdt);//現在の日付時刻を格納
   //1日の開始時の資産を格納
   if((mdt.hour==0 && mdt.min==0) || equity_at_open==0.0)
   {
      equity_at_open=AccountInfoDouble(ACCOUNT_EQUITY);
   }
   equity_now=AccountInfoDouble(ACCOUNT_EQUITY);//現在の資産を格納
   //損益を計算
   if(MathAbs(equity_at_open)>0.0)
   {
      pl=(equity_now-equity_at_open)/equity_at_open;
   }
   else
   {
      pl=0.0;
   }
   //損失が許容可能な最大日中損失を超えていたら真を返す
   if(pl<-max_acceptable_loss)
   {
      return true;
   }
   //超えていなければ偽を返す
   else
   {
      return false;
   }
}
//市場が開いているかをチェック
//OANDAサーバー用の設定であり、他のサーバーでは不要または要修正
bool IsMarketOpen(void)
{
   MqlDateTime mdt;//日付時刻の構造体
   TimeCurrent(mdt);//現在の日付時刻を格納
   //日曜日は開いていない
   if(mdt.day_of_week==0)
   {
      return false;
   }
   //月曜日は0:03より前、23:55以降は開いていない
   else if(mdt.day_of_week==1 && ((mdt.hour==0 && mdt.min<3) || (mdt.hour==23 && mdt.min>=55)))
   {
      return false;
   }
   //火曜日から金曜日は0:10より前、23:55以降は開いていない
   else if(mdt.day_of_week>=2 && mdt.day_of_week<6 && ((mdt.hour==0 && mdt.min<10) || (mdt.hour==23 && mdt.min>=55)))
   {
      return false;
   }
   //土曜日は開いていない
   else if(mdt.day_of_week==6)
   {
      return false;
   }
   //それ以外は開いている
   else
   {
      return true;
   }
}
//新規注文を送信
void StrategyEntry(CTrade &trade,
                   ENUM_ORDER_TYPE type,
                   double lot,
                   double sl,
                   double tp,
                   string id)
{
   trade.SetTypeFillingBySymbol(Symbol());//なくても問題なさそうだが一応
   bool is_position_open=false;//ポジションを持っているかどうかチェック
   uint total=PositionsTotal();//口座のポジション数を求める。
   //銘柄とIDが一致するポジションを探す
   for(uint i=0;i<total;i++)
   {
      string position_symbol=PositionGetSymbol(i);
      //見つかればポジションがあると判断
      if(Symbol()==position_symbol && id==PositionGetString(POSITION_COMMENT))
      {
         is_position_open=true;
         break;
      }
   }
   //ポジションがなければ新規注文を送信
   if(is_position_open==false){
      double ask=SymbolInfoDouble(Symbol(),SYMBOL_ASK);//買値
      double bid=SymbolInfoDouble(Symbol(),SYMBOL_BID);//売値
      double price=0.0;
      //買うときは買値で注文
      if(type==ORDER_TYPE_BUY)
      {
         price=ask;
      }
      //売るときは売値で注文
      if(type==ORDER_TYPE_SELL)
      {
         price=bid;
      }
      trade.PositionOpen(Symbol(),type,lot,price,sl,tp,id);}
}
//決済注文を送信
void StrategyExit(CTrade &trade,
                  long type,
                  string id)
{
   trade.SetTypeFillingBySymbol(Symbol());//なくても問題なさそうだが一応
   uint total=PositionsTotal();//口座のポジション数を求める
   //銘柄とIDが一致するポジションを探す
   for(uint i=0;i<total;i++)
   {
      string position_symbol=PositionGetSymbol(i);
      if(Symbol()==position_symbol && id==PositionGetString(POSITION_COMMENT))
      {
         long ticket=PositionGetInteger(POSITION_TICKET);//ポジションのチケット
         //ポジションのタイプが一致したら決済する
         if(PositionGetInteger(POSITION_TYPE)==type)
         {
            trade.PositionClose(ticket);
            break;
         }
      }
   }
}

移動平均乖離率の応用

移動平均乖離率を使ったトレード戦略を少し改良してみた。

環境

  • OANDA MetaTrader 5
  • Version: 5.00 build 3137

ストラテジーテスターの設定

  • 銘柄:USDJPY
  • 足の種類:M5
  • 日付:期間限定
  • 開始日:2012.01.01
  • 終了日:2022.01.01
  • フォワードテスト:キャンセル
  • 延滞:遅延ゼロ、理想的な実行
  • モデル:1分足 OHLC
  • より高速計算のためのピップ単位利益:チェックあり
  • 入金:100000
  • レバレッジ:1:25
  • オプティマイズ:無効化
  • チャート、指標、取引を表示するビジュアルモード:チェックなし

移動平均乖離率を使ったトレード戦略の改良版

検証結果

f:id:fxst24:20220210175757p:plain

結果はまずまず。

ただ、かなりの薄利多売なので、スプレッドやスリッページの影響を受けやすく、実際にこの通りのパフォーマンスになるかは微妙だと思う。

スプレッドを調べると、直近で概ね0.3pipsだった。

MT5のヒストリカルデータがどこから来ているのかは分かっていないのだが、このスプレッドはOANDAの東京サーバーと同水準。

「延滞」の設定を「1000ミリ秒」や「ランダム遅延」にしてもやってみたが、結果は変わらず。

パラメータはそのままでユーロドル、ユーロ円でもバックテストをしてみたが、若干落ちるものの同様のパフォーマンスを得られた。

まだ改良の余地はあるだろうが、多分使わないかな。

DMIの検証

今回はDMIを調べてみる。

環境

  • OANDA MetaTrader 5
  • Version: 5.00 build 3137

ストラテジーテスターの設定

  • 銘柄:USDJPY
  • 足の種類:Daily
  • 日付:期間限定
  • 開始日:2012.01.01
  • 終了日:2022.01.01
  • フォワードテスト:キャンセル
  • 延滞:遅延ゼロ、理想的な実行
  • モデル:1分足 OHLC
  • より高速計算のためのピップ単位利益:チェックあり
  • 入金:10000
  • レバレッジ:1:50
  • オプティマイズ:無効化
  • チャート、指標、取引を表示するビジュアルモード:チェックなし

+DIと-DIの交差

パラメータ

  • 平均期間:14

エントリー

  • +DIが-DIを上抜き、かつ、ADXが-DIより上なら買い
  • +DIが-DIを下抜き、かつ、ADXが+DIより上なら売り

エグジット

  • 買いポジションを保有して売りシグナルが点灯したら決済
  • 売りポジションを保有して買いシグナルが点灯したら決済

検証結果

f:id:fxst24:20220201165508p:plain

結果はいまいち。

パラボリックの検証

今回はパラボリックを調べてみる。

環境

  • OANDA MetaTrader 5
  • Version: 5.00 build 3137

ストラテジーテスターの設定

  • 銘柄:USDJPY
  • 足の種類:Daily
  • 日付:期間限定
  • 開始日:2012.01.01
  • 終了日:2022.01.01
  • フォワードテスト:キャンセル
  • 延滞:遅延ゼロ、理想的な実行
  • モデル:1分足 OHLC
  • より高速計算のためのピップ単位利益:チェックあり
  • 入金:10000
  • レバレッジ:1:50
  • オプティマイズ:無効化
  • チャート、指標、取引を表示するビジュアルモード:チェックなし

終値とSARの交差

パラメータ

  • ステップ:0.02
  • 最大:0.2

エントリー

  • 終値がSARを上抜けたら買い
  • 終値がSARを下抜けたら売り

エグジット

  • 買いポジションを保有して売りシグナルが点灯したら決済
  • 売りポジションを保有して買いシグナルが点灯したら決済

検証結果

f:id:fxst24:20220201151405p:plain

結果はいまいち。