PythonからMT5を利用してみた(Ubuntuからは不可)

学習記録

 PythonからMT5を利用できるように環境構築しました。PythonからMT5の機能を直接利用できるというより、PythonからMT5を操作できるようになるというようなイメージに近いかもしれません。

 例えば、為替データはPythonから直接利用できるのではなく、あくまでMT5に格納されているものをコピーしてPythonから操作できるといったような具合です。

 MT5モジュールでは、主にデータ取得と取引執行ができます。今回は、データ取得周りについて簡単にまとめておきます。合わせて、Ubutuでの失敗についても残しておきます。

MT5モジュールの導入手順

 MT5モジュールを利用するには、MT5がインストールされていることが大前提です。

 Python環境の構築についての説明は省略します。MT5のモジュールを追加するのも以下のpipコマンドを実行するだけで基本的には完了です。

pip install MetaTrader5

 合わせてタイムゾーンを適切に扱うために、”pytz”も以下のコマンドでインストールしておきます。(Anaconda環境ならconda installも可能)

pip install pytz

 基本的には以上で完了です。ただ、私の環境ではMT5モジュールのインストールでエラーが発生してしました。詳細については次項で説明します。

Ubuntuからは利用不可?

 私のPython開発環境は通常はWSL2(Ubuntu)で行っております。ですので、今回もUbuntuからMT5モジュールをインストールしようとしたところ以下のエラーが出ました。

(py38) <User>:~/Desktop$ pip install MetaTrader5
ERROR: Could not find a version that satisfies the requirement MetaTrader5 (from versions: none)
ERROR: No matching distribution found for MetaTrader5

 原因について調べてみたら、stackoverflowにWindowsのみ対応しているとのコメントを発見しました。

 公式ドキュメントの導入手順を見ても、pythonのインストーラーがWindowsになっているので、MT5モジュールはWindowを前提としているように思われます。

 そもそもMT5はWindows用のソフトウェアなので当然であることに後になって気づきました。WSL2でUbuntuを使っているものの、MT5自体はWindowsにインストールしているので、おとなしくWindowsにPythonをインストールしてMT5モジュールを使うことにします。

 将来的にMT5と連携させることを考えるとWindows環境のPythonを利用したほうが無難そう(苦労しない)です。

MT5モジュールを使ってみた

 MT5モジュールに内蔵されている関数はこちらをご参照ください。今回は、これらの関数のうちいくつかを利用してEUR/JPYのマーケットデータをPythonから取得をしてみたいと思います。

MT5の起動

 まずは、MT5ターミナルに接続します。以下を実行すると、MT5が起動します。

import MetaTrader5 as mt5
# connect to MetaTrader 5
if not mt5.initialize():
    print("initialize() failed")
    mt5.shutdown()

取得可能な通貨

 取得可能な通貨情報は、symbols_get関数を利用することで調べることができます。以下を実行すると、131通貨のデータが取得できることがわかりました。下の例では5通貨のみ表示してます。

symbols=mt5.symbols_get()
print('Symbols: ', len(symbols)) # len(symbols)とmt5.symbols_total()は同じ
count=0
# display the first five ones
for s in symbols:
    count+=1
    print("{}. {}".format(count,s.name))
    if count==5: break

 特定の通貨情報のみ取得したい場合は、引数に”*<通貨名>*を設定することで可能です。以下は日本円に関するシンボルを取得した場合です。日本円は11個の通貨情報があるようです。

jpy_symbols=mt5.symbols_get("*JPY*")
print('len(*JPY*): ', len(jpy_symbols))
for s in jpy_symbols:
    print(s.name)

 ”*<通貨名1>*, *<通貨名2>*”のように複数の通貨を指定することも可能です。また、特定の通貨を除外いた情報を取得したい場合は”*, !*<通貨名>*”としてあげることで可能です。

 通貨数のみ調べたい場合は、symbols_totalを利用することも可能です。

市場データの取得

 事前にMT5でデータを取得していないとデータを取得することができません。PythonからリクエストをしてもNoneが返ってくるだけです。あくまで、MT5モジュールは、MT5にあるデータをそのままコピーしてくるだけのようです。

時間足データ

 copy_rates_from, copy_rates_from_pos, copyrates_rangeを利用することで価格情報を取得できます。

 copy_rates_fromは指定した日付の期間のデータを取得できます。copy_rates_from_posは、指定した本数のデータを取得できます。copyrates_rangeは指定した期間(開始時点~終了時点)のデータを取得することができます。

 返り値は、(日付, 始値, 高値, 安値, ティック数, スプレッド, ボリューム)となります。日付は、世界標準時(UTC)で1970年1月1日0時0分からの経過時間を表示するUNIX時間となっております。

 copy_rates_fromの引数は以下のようになっております。

copy_rates_from(
   symbol,       // symbol name
   timeframe,    // timeframe
   date_from,    // initial bar open date
   count         // number of bars
   )

 symbolは通貨シンボル、timeframeは時間足、cuntは取得したいデータ数を入力します。date_fromはdatetimeオブジェクトもしくは数値を入力します。datetimeオブジェクトの場合は、その時間を起点として直近のデータを取得できます。数値の場合は1970年1月1日を起点とした経過秒数を指定してあげるそうです。これはあまり利用することはないかもしれません。timeframeには、1分 ~ 1weekまで指定可能です。詳細は、TIMEFRAME に記載があります。

 以下、例として、2021年5月15日0:00の直近10データ(1時間足)を取得してみたサンプルコードになります。タイムゾーンについて注意する必要があります。

UTC(Universal Time Coordinated:協定世界時)の場合

 timezoneをUTCとして設定してあげます。また、UNIX時間は秒(s)なのでdfにする際に表示を変換してます。

# set time zone to UTC
timezone = pytz.utc #pytz.timezone("UTC")

# create 'datetime' object in UTC time zone to avoid the implementation of a local time zone offset
utc_from = datetime(2021, 5, 15, tzinfo=timezone)

# get 10 EURUSD H4 bars starting from 01.10.2020 in UTC time zone
rates = mt5.copy_rates_from("EURJPY", mt5.TIMEFRAME_H1, utc_from, 10)

# create DataFrame out of the obtained data
rates_frame = pd.DataFrame(rates)
# convert time in seconds into the datetime format
rates_frame['time']=pd.to_datetime(rates_frame['time'], unit='s')
日本時間の場合

 UTC以外のタイムゾーンを扱う際、pytzは注意が必要のようです。詳細はこちらのサイトに記載があります。日本時間の場合は、15:00までのようです。

 UTCのデータと比較してみると時差の9時間分データがずれるのかと思いきや、同じ時間のデータはUTCと日本で同じであることがわかります。つまり、タイムゾーンは各国のマーケットの閉じる時間を起点としているようです。

 なので基本的にはUTCにしておけば良さそうです。

tokyo = pytz.timezone('Asia/Tokyo')
japan_dt = tokyo.localize(datetime(2021, 5, 15)) 

# get 10 EURUSD H4 bars starting from 01.10.2020 in UTC time zone
rates = mt5.copy_rates_from("EURJPY", mt5.TIMEFRAME_H1, japan_dt, 10)

# create DataFrame out of the obtained data
rates_frame = pd.DataFrame(rates)
# convert time in seconds into the datetime format
rates_frame['time']=pd.to_datetime(rates_frame['time'], unit='s')
                           
rates_frame 

 一方で、copy_rates_from_posの引数は以下のようになっております。

copy_rates_from_pos(
   symbol,       // symbol name
   timeframe,    // timeframe
   start_pos,    // initial bar index
   count         // number of bars
   )

 先ほどのcopy_rates_fromとの違いは、”start_pos”だけです。これは直近を起点として何本目のバーを基準として過去のデータを取得するかを指定してあげる引数になります。尚、0にすると直近を起点とすることができます。

 copyrates_rangeの引数は以下のようになっております。

copy_rates_range(
   symbol,       // symbol name
   timeframe,    // timeframe
   date_from,    // date the bars are requested from
   date_to       // date, up to which the bars are requested
   )

 以下、例としてEURJPYの2021年5月1日~5月15日のデータを取得するコードになります。

# set time zone to UTC
timezone = pytz.utc
# create 'datetime' objects in UTC time zone to avoid the implementation of a local time zone offset
utc_from = datetime(2021, 5, 1, tzinfo=timezone)
utc_to = datetime(2021, 5, 15, tzinfo=timezone)

rates = mt5.copy_rates_range("EURJPY", mt5.TIMEFRAME_D1, utc_from, utc_to)
rates

ティックデータ

 直近のティックは、symbol_info_tickを利用することで取得できます。以下のコードでEURJPYのティックデータを取得できます。

mt5.symbol_info_tick("EURJPY")

 長期のティックデータを取得するには、copy_ticks_fromcopy_ticks_rangeを利用します。

 copy_ticks_fromの引数は以下の通り。

copy_ticks_from(
   symbol,       // symbol name
   date_from,    // date the ticks are requested from
   count,        // number of requested ticks
   flags         // combination of flags defining the type of requested ticks
   )

 copy_ticks_rangeの引数は以下の通り。

copy_ticks_range(
   symbol,       // symbol name
   date_from,    // date the ticks are requested from
   date_to,      // date, up to which the ticks are requested
   flags         // combination of flags defining the type of requested ticks
   )

 引数はは、上で説明したcopy_rates_from, copyrates_rangeとほぼ同じです。大きな違いは、タイムフレームを選ぶ必要がなくなったのと”flags”の設定です。また、copy_ticks_fromで取得したデータは、date_fromで指定した日付以降のデータになる点も先ほどの市場データと異なります。

 flagで呼び出せる引数は以下の3つ。

IDDescription
COPY_TICKS_ALLall ticks
COPY_TICKS_INFOticks containing Bid and/or Ask price changes
COPY_TICKS_TRADEticks containing Last and/or Volume price changes

 以下、5月14日のEUR/JPYのティックデータを取得するコード例になります。

copy_ticks_fromを使用した場合

# set time zone to UTC
timezone = pytz.timezone("Etc/UTC")
# create 'datetime' object in UTC time zone to avoid the implementation of a local time zone offset
utc_from = datetime(2021, 5, 14, tzinfo=timezone)

ticks = mt5.copy_ticks_from("EURJPY", utc_from, 100000, mt5.COPY_TICKS_ALL)

# create DataFrame out of the obtained data
ticks_frame = pd.DataFrame(ticks)
# convert time in seconds into the datetime format
ticks_frame['time']=pd.to_datetime(ticks_frame['time'], unit='s')
 
# display data
ticks_frame.head(10)

 上で説明した通り、設定した日付以降のティックデータが取得されていることがわかります。

copy_ticks_rangeを使用した場合

# set time zone to UTC
timezone = pytz.utc

# create 'datetime' objects in UTC time zone to avoid the implementation of a local time zone offset
utc_from = datetime(2021, 5, 14, tzinfo=timezone)
utc_to = datetime(2021, 5, 15, 0, 0, tzinfo=timezone)

ticks = mt5.copy_ticks_range("EURJPY", utc_from, utc_to, mt5.COPY_TICKS_ALL)
ticks_frame = pd.DataFrame(ticks)

# convert time in seconds into the datetime format
ticks_frame['time']=pd.to_datetime(ticks_frame['time'], unit='s')
 
# display data
ticks_frame.head(10)

 結果は、copy_rates_fromとほぼ同じため、掲載しません。

”flags”について

 引数で設定しているflagとは何ぞやと思ったので軽く調べてみました。

フラグの種類は以下の6種類のようです。

IDDescription
TICK_FLAG_BIDBid price changed
TICK_FLAG_ASKAsk price changed
TICK_FLAG_LASTLast price changed
TICK_FLAG_VOLUMEVolume changed
TICK_FLAG_BUYlast Buy price changed
TICK_FLAG_SELLlast Sell price changed

 各フラグの変数について表示してみました。結果は、コメントアウトで記載した値です。

print(mt5.TICK_FLAG_BID)    # =>  2
print(mt5.TICK_FLAG_ASK)    # =>  4
print(mt5.TICK_FLAG_LAST)   # =>  8
print(mt5.TICK_FLAG_VOLUME) # => 16
print(mt5.TICK_FLAG_BUY)    # => 32
print(mt5.TICK_FLAG_SELL)   # => 64

 ビット演算でこれらの値を組み合わせた値が返り値となっているようです。詳細については完全に理解できませんでした。

 こちらのサイトが参考にし、以下の関数を定義し、返り値のflagsを書き換えてみました。

def convert_flags(flags):
    fl = ''
    
    if (flags &amp; mt5.TICK_FLAG_BID) == mt5.TICK_FLAG_BID:
        fl += "b"
    if (flags &amp; mt5.TICK_FLAG_ASK) == mt5.TICK_FLAG_ASK:
        fl += "s"
    if (flags &amp; mt5.TICK_FLAG_LAST) == mt5.TICK_FLAG_LAST:
        fl += "l"
    if (flags &amp; mt5.TICK_FLAG_VOLUME) == mt5.TICK_FLAG_VOLUME:
        fl += "v"
    if (flags &amp; mt5.TICK_FLAG_BUY) == mt5.TICK_FLAG_BUY:
        fl += "Ob";
    if (flags &amp; mt5.TICK_FLAG_SELL) == mt5.TICK_FLAG_SELL:
        fl += "Os";

    return fl

ticks_frame['flags_converted'] = ticks_frame['flags'].apply(convert_flags)
display(ticks_frame.head())
display(ticks_frame.tail())

ビット演算をうまく変換してあげることで、フラグの種類の組み合わせに変換できている様子がわかりました。5月14日のティックデータには、{‘bs’, ‘b’, ‘s’}の3種類しかありませんでした。

 とりあえずティックフラグに関しては、そこまで気にしないでおきます。

まとめ

 今回はMT5のデータをPythonで取り扱う方法についてまとめてみました。MT5モジュールを利用することで簡単にMT5のデータをPython環境に取り込むことができることがわかりました。

 取り込んだデータを機械学習などに利用するのも容易そうです。そして取り込んだデータからシグナルを出し、シグナルに基づいた取引判断をMT5側にリクエストすれば簡単にシステムトレードシステムが作れそうな印象です。もちろん、MT5のデータ以外もPython側で取り込んであげることで、MT5だけではできないトレーディングシステムを構築できそうです。

 ティックデータも利用できるのでこの辺についてもいろいろ試してみたいと思います。

 今回の調査を通して、シストレ開発・運用に一歩近づいた気がします。

以上

参考サイト

・第九回 MT5とPythonを連携してみよう!: https://metaquotes.co.jp/2019/07/01/blog09/

・stackoverflow: https://stackoverflow.com/questions/60724857/could-not-find-a-version-that-satisfies-the-requirement-metatrader5

・MetaTrader module for integration with Python: https://www.mql5.com/en/docs/integration/python_metatrader5

タイトルとURLをコピーしました