PythonからMetaTrader5を起動してEAの最適化を行い、結果をPythonに取り込む

# はじめに
MetaTrader5(MT5)は、売買システム(EA)のバックテストや最適化に便利です。MT5を単独で使う場合、最適化して終わりなのですが、今回、色々な最適化ができるように、PythonからMT5の最適化を実行して、その結果をPythonに取り込んでみます。

(注)本記事の方法はMT4では使えません。設定ファイルやレポートファイルの互換性がないのです。

# PythonからMT5を起動する
PythonからWindowsのアプリケーションを実行するには、subprocessモジュールを使います。MT5のプログラムが`C:\Program Files\Alpari MT5\terminal64.exe`だとすると、以下のコードでMT5が実行できます。

```python
import subprocess
subprocess.run(r'C:\Program Files\Alpari MT5\terminal64.exe')
```

# MT5を起動してEAの最適化を行う
このままだとMT5を起動するだけで、最適化は手動で操作しなくてはなりません。ところが、MT5にはコマンドラインオプションで起動時の動作を設定する機能があります。設定ファイル（ここでは、`C:\MT5\myconfig.ini`とします。内容は後述します。）を用意しておき、次のように`/config:`オプションで設定ファイルを指定することができます。

````python
import subprocess
subprocess.run(r'C:\Program Files\Alpari MT5\terminal64.exe /config:C:\MT5\myconfig.ini')
````

これを実行すると、MT5を起動した後に設定ファイルで指定した内容を自動的に実行します。

## 最適化のための設定ファイル
EAの最適化を行うための設定ファイルの記述例です。

````myconfig.ini
[Tester]
;--- テストするEAの名前(MQL5\Expertsの下)
Expert=Examples\MACD\MACD Sample

;--- テストするEAのパラメータファイル(MQL5\Profiles\Testerの下)
ExpertParameters=MACD Sample.set

;--- テストする通貨ペア
Symbol=EURUSD_i

;--- テストするタイムフレーム
Period=H1

;--- テスト用の口座番号
;Login=123456

;--- 初期資産
Deposit=100000

;--- テスト用のレバレッジ
Leverage=1:100

;--- ティック生成モデル(0:全ティック、1:1分足OHLC、2:始値のみ、3:数値計算、4:リアルティックに基づいたすべてのティック)
Model=1

;--- 約定モード(0:遅延なし、-1:ランダム遅延 >0:ミリ秒単位のカスタム遅延)
ExecutionMode=0

;--- 最適化の種類(0:無効、1:完全アルゴリズム、2:遺伝的アルゴリズム、3:気配値表示で選択されたすべての銘柄)
Optimization=1

;--- 最適化基準(0:バランスの最大値、1:バランスと利益の積の最大値、2:バランスと期待利益の積の最大値、3:バランスと(100% - ドローダウン%)の積の最大値、4:バランスとリカバリファクタの積の最大値、5:バランスとシャープレシオの積の最大値、6:OnTester()で定義されたカスタム基準)
OptimizationCriterion=0

;--- テストする期間
FromDate=2017.01.01
ToDate=2018.01.01

;--- フォワードテストモード(0:無効、1:テスト期間の1/2、2:テスト期間の1/3、3:テスト期間の1/4、4:カスタム)
ForwardMode=0

;--- フォワードテスト開始日(ForwardMode=4の場合のみ)
;ForwardDate=2011.03.01

;--- レポートファイル名(データフォルダの下、テスト:Report.htm、最適化:Report.xml)
Report=MACD Sample

;--- レポートファイルの上書き(0:無効、1:有効)
ReplaceReport=1

;--- テスト終了時のターミナルのシャットダウン(0:無効、1:有効)
ShutdownTerminal=1
````

MT5をご利用になったことのある方でしたら、コメントの説明だけでわかるかと思います。簡単に言うと、「MACD Sample」というEAをEURUSDの2017年の1時間足のチャートを使ってバックテストを行うということです。

ただし、EAのパラメータは、下図のように設定されているものとします。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/131169/5e3e2861-0a4a-d665-1056-03fbdc522fab.png)

変化させるパラメータは`MA trend period`のみで、それを10から30まで1刻みで変えるようになっています。要は、`MA trend period`の値を21通り変化させてバックテストを行って、それぞれの評価値を出力するわけです。

`MACD Sample.set`のパラメータファイルは、自動的に作成されます。EAと別の名前にしたければ、上の画面で右クリックして保存するか、別途ファイルを作成してください。

## 最適化の結果の出力
最適化した結果は、下図のように21回のパスに対する損益、プロフィットファクター、リカバリーファクター、最大ドローダウンなどの値が表形式で出力されます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/131169/f39d4cfa-c13a-5b92-787e-cedde09d2149.png)

このデータは設定ファイルで指定したファイル名（ここでは、`MACD Sample.xml`）にも出力されます。

# 最適化の結果をPythonに取り込む
`MACD Sample.xml`はMT5のデータフォルダの下に出力されるので、そのままPythonに取り込むには、そのパスを書く必要があります。パス名は長いので、ここでは、`C:\MT5\MACD Sample.xml`にコピーしたものを使います。

xmlファイルからデータを取り出す方法は色々とあるようなのですが、今回はBeautifulSoup使ってみます。

まずは、次のようなコードでxmlファイルをオープンしてBeautifulSoupのオブジェクトを作成します。

````python
from bs4 import BeautifulSoup
with open(r'C:\MT5\MACD Sample.xml') as fp:
    soup = BeautifulSoup(fp, 'xml')
````

このxmlファイルは、各行のデータが`<Row>`タグで囲まれており、それぞれの要素が`<Data>`タグで囲まれているので、次のようなコードでpandasのDatFrameオブジェクトに取り込んでみます。

````python
import pandas as pd
df = pd.DataFrame()
for r,row in enumerate(soup.find_all('Row')):
    for c,cell in enumerate(row.find_all('Data')):
        df.loc[r, c] = cell.string
````

dfを表示させると

<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>0</th>
      <th>1</th>
      <th>2</th>
      <th>3</th>
      <th>4</th>
      <th>5</th>
      <th>6</th>
      <th>7</th>
      <th>8</th>
      <th>9</th>
      <th>10</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>Pass</td>
      <td>Result</td>
      <td>Profit</td>
      <td>Expected Payoff</td>
      <td>Profit Factor</td>
      <td>Recovery Factor</td>
      <td>Sharpe Ratio</td>
      <td>Custom</td>
      <td>Equity DD %</td>
      <td>Trades</td>
      <td>InpMATrendPeriod</td>
    </tr>
    <tr>
      <th>1</th>
      <td>5</td>
      <td>100355.2900</td>
      <td>355.2900</td>
      <td>3.6254</td>
      <td>1.2061</td>
      <td>0.8486</td>
      <td>0.0704</td>
      <td>0</td>
      <td>0.4167</td>
      <td>98</td>
      <td>15</td>
    </tr>
    <tr>
      <th>2</th>
      <td>3</td>
      <td>100280.7100</td>
      <td>280.7100</td>
      <td>2.6734</td>
      <td>1.1523</td>
      <td>0.6660</td>
      <td>0.0532</td>
      <td>0</td>
      <td>0.4195</td>
      <td>105</td>
      <td>13</td>
    </tr>
    <tr>
      <th>3</th>
      <td>1</td>
      <td>100251.2700</td>
      <td>251.2700</td>
      <td>2.1850</td>
      <td>1.1252</td>
      <td>0.6364</td>
      <td>0.0440</td>
      <td>0</td>
      <td>0.3930</td>
      <td>115</td>
      <td>11</td>
    </tr>
    <tr>
      <th>4</th>
      <td>6</td>
      <td>100249.4400</td>
      <td>249.4400</td>
      <td>2.6822</td>
      <td>1.1509</td>
      <td>0.5178</td>
      <td>0.0524</td>
      <td>0</td>
      <td>0.4794</td>
      <td>93</td>
      <td>16</td>
    </tr>
    <tr>
      <th>5</th>
      <td>2</td>
      <td>100243.0300</td>
      <td>243.0300</td>
      <td>2.2094</td>
      <td>1.1258</td>
      <td>0.5938</td>
      <td>0.0442</td>
      <td>0</td>
      <td>0.4075</td>
      <td>110</td>
      <td>12</td>
    </tr>
    <tr>
      <th>6</th>
      <td>4</td>
      <td>100227.0700</td>
      <td>227.0700</td>
      <td>2.2262</td>
      <td>1.1224</td>
      <td>0.5283</td>
      <td>0.0437</td>
      <td>0</td>
      <td>0.4278</td>
      <td>102</td>
      <td>14</td>
    </tr>
    <tr>
      <th>7</th>
      <td>7</td>
      <td>100217.4700</td>
      <td>217.4700</td>
      <td>2.3898</td>
      <td>1.1337</td>
      <td>0.4474</td>
      <td>0.0467</td>
      <td>0</td>
      <td>0.4838</td>
      <td>91</td>
      <td>17</td>
    </tr>
    <tr>
      <th>8</th>
      <td>14</td>
      <td>100177.5200</td>
      <td>177.5200</td>
      <td>2.3669</td>
      <td>1.1532</td>
      <td>0.4044</td>
      <td>0.0481</td>
      <td>0</td>
      <td>0.4377</td>
      <td>75</td>
      <td>24</td>
    </tr>
    <tr>
      <th>9</th>
      <td>8</td>
      <td>100177.1400</td>
      <td>177.1400</td>
      <td>2.0130</td>
      <td>1.1166</td>
      <td>0.3634</td>
      <td>0.0399</td>
      <td>0</td>
      <td>0.4851</td>
      <td>88</td>
      <td>18</td>
    </tr>
    <tr>
      <th>10</th>
      <td>17</td>
      <td>100115.3300</td>
      <td>115.3300</td>
      <td>1.6960</td>
      <td>1.1112</td>
      <td>0.3117</td>
      <td>0.0342</td>
      <td>0</td>
      <td>0.3689</td>
      <td>68</td>
      <td>27</td>
    </tr>
    <tr>
      <th>11</th>
      <td>9</td>
      <td>100114.6000</td>
      <td>114.6000</td>
      <td>1.3172</td>
      <td>1.0748</td>
      <td>0.2312</td>
      <td>0.0260</td>
      <td>0</td>
      <td>0.4935</td>
      <td>87</td>
      <td>19</td>
    </tr>
    <tr>
      <th>12</th>
      <td>10</td>
      <td>100103.2100</td>
      <td>103.2100</td>
      <td>1.2287</td>
      <td>1.0694</td>
      <td>0.2024</td>
      <td>0.0241</td>
      <td>0</td>
      <td>0.5078</td>
      <td>84</td>
      <td>20</td>
    </tr>
    <tr>
      <th>13</th>
      <td>18</td>
      <td>100102.6900</td>
      <td>102.6900</td>
      <td>1.5559</td>
      <td>1.0985</td>
      <td>0.2566</td>
      <td>0.0309</td>
      <td>0</td>
      <td>0.3990</td>
      <td>66</td>
      <td>28</td>
    </tr>
    <tr>
      <th>14</th>
      <td>13</td>
      <td>100088.8100</td>
      <td>88.8100</td>
      <td>1.1841</td>
      <td>1.0768</td>
      <td>0.1900</td>
      <td>0.0245</td>
      <td>0</td>
      <td>0.4660</td>
      <td>75</td>
      <td>23</td>
    </tr>
    <tr>
      <th>15</th>
      <td>0</td>
      <td>100059.7200</td>
      <td>59.7200</td>
      <td>0.4855</td>
      <td>1.0259</td>
      <td>0.1034</td>
      <td>0.0097</td>
      <td>0</td>
      <td>0.5751</td>
      <td>123</td>
      <td>10</td>
    </tr>
    <tr>
      <th>16</th>
      <td>12</td>
      <td>100057.9400</td>
      <td>57.9400</td>
      <td>0.7525</td>
      <td>1.0471</td>
      <td>0.1089</td>
      <td>0.0156</td>
      <td>0</td>
      <td>0.5303</td>
      <td>77</td>
      <td>22</td>
    </tr>
    <tr>
      <th>17</th>
      <td>19</td>
      <td>100050.7000</td>
      <td>50.7000</td>
      <td>0.7800</td>
      <td>1.0486</td>
      <td>0.1267</td>
      <td>0.0156</td>
      <td>0</td>
      <td>0.3990</td>
      <td>65</td>
      <td>29</td>
    </tr>
    <tr>
      <th>18</th>
      <td>16</td>
      <td>100033.3000</td>
      <td>33.3000</td>
      <td>0.4757</td>
      <td>1.0292</td>
      <td>0.0704</td>
      <td>0.0097</td>
      <td>0</td>
      <td>0.4717</td>
      <td>70</td>
      <td>26</td>
    </tr>
    <tr>
      <th>19</th>
      <td>11</td>
      <td>100026.9300</td>
      <td>26.9300</td>
      <td>0.3325</td>
      <td>1.0186</td>
      <td>0.0526</td>
      <td>0.0067</td>
      <td>0</td>
      <td>0.5102</td>
      <td>81</td>
      <td>21</td>
    </tr>
    <tr>
      <th>20</th>
      <td>15</td>
      <td>100025.2700</td>
      <td>25.2700</td>
      <td>0.3510</td>
      <td>1.0218</td>
      <td>0.0517</td>
      <td>0.0073</td>
      <td>0</td>
      <td>0.4872</td>
      <td>72</td>
      <td>25</td>
    </tr>
    <tr>
      <th>21</th>
      <td>20</td>
      <td>100016.1100</td>
      <td>16.1100</td>
      <td>0.2517</td>
      <td>1.0153</td>
      <td>0.0372</td>
      <td>0.0052</td>
      <td>0</td>
      <td>0.4323</td>
      <td>64</td>
      <td>30</td>
    </tr>
  </tbody>
</table>

となり、各データが読み込まれていることがわかります。

この結果を使って、さらに条件を変えて繰り返しMT5で最適化させることで、MT5だけではできないようなEAの最適化ができるのではないかと思います。具体的な方法についてはまた次回。

# 参考記事
Pythonのコード書くの久しぶりだったので、色々な記事を参考にさせていただきました。特にpandas.read_excel()でうまくいかなかったときには、以下の記事を参考にさせていただきました。

* http://nekoyukimmm.hatenablog.com/entry/2016/11/07/171822