Python Tech

MT5をPythonで操作してレート取得・注文する方法まとめ

FXなどの金融商品の取引プラットフォームMetaTrader5(MT5)では、システムトレードをMQL5という開発言語で実装(EA)するのですが、最近はPythonのAPIが正式に提供されています。

これでPythonからMT5を操作し、取引を自動化したり、時系列データを効率よく収集できるようになりました。

今回は MT5のPython APIを使ってレートやチャート(OHLC)を取得し、注文・クローズまでの一連の流れを、コードをみながら確認していきましょう。

検証環境
Windows 10
MetaTrader5 5.00
Python 3.9
MetaTrader5(Pythonモジュール) 5.0.34
numpy 1.20.2

公式ドキュメント

 

事前準備

Python API モジュール

MT5(MetaTrader5)をPythonから操作するためにモジュールをインストールします。
pip なら以下のコマンドを実行します。

pip install MetaTrader5

基本的にMetaTrader5はWindows向けソフトなので、PythonモジュールもWindows環境でしかパッケージが提供されていません。

コードではMetaTrader5 をimportします。

import MetaTrader5 as mt5 

 

検証用デモ口座

動作確認やテスト用に、デモ口座の情報も用意しておきましょう。

記事ではMetaQuotes-Demo口座を使っていますが、MT5に対応してさえいれば、どのブローカーのデモ口座でもOKです。

 

MT5のオプション設定

ツール → オプション からEAの動作許可をしておかないと、Pythonからも操作できません。

 

 

 

「アルゴリズム取引を許可」にチェックを入れておきます。

また「外部Python APIを介したアルゴリズム取引を無効にする」のチェックが外れているのを確認しましょう。

 

初期化と接続・認証

まずはMT5と接続し、MT5をPythonから操作できる状態にしてあげます。

 

# MT5と接続
if not mt5.initialize():
    print(f"initialize() failed, error code = {mt5.last_error()}")
    return

# 接続ができたらMT5のバージョンを表示する
print(f"MetaTrader5 package version {mt5.__version__}")

# MT5でブローカーにログイン
authorized = mt5.login(
    12345678,
    password="password",
    server="Demo",
)
if not authorized:
    print(f"User Authorization Failed")
    return

 

MT5は、事前に起動させていてもいなくても、
どちらでも大丈夫です。

 

initialize

MT5と接続するのは mt5.initialize() 関数。

取引やレート参照など、いずれかの操作をする前にコードのどこかで呼べばOKです。

 

login

口座残高を参照したりはもちろん、取引を実行する場合にもログインが必要です。

口座の認証を行うのは mt5.login() 関数です。

渡すパラメータはMT5のログイン画面とまったく同じです。

mt5.login(
    12345678,
    password="password",
    server="Demo",
)

 

  • 第一引数に「Login」(数字のID)
    ※int型じゃないと通らない
  • password引数に取引Password
  • server引数にServer名

を渡します。

 

口座情報の参照

残高を取得するときなどに使います。

# 口座情報を取得する
account_info = mt5.account_info()
if account_info is None:
    print(f"Retreiving account information failed")
    return
print(f"Balance: {account_info.balance}")

mt5.account_info()で口座情報をごっそり取れます。

戻り値のAccountInfoオブジェクトにいろいろなデータが格納されています。

例えば、残高は account_info().balance メンバです。

その他のメンバはDocを参照してください。

 

時系列(OHLC)の取得

# シンボルの情報を取得する
symbol_info = mt5.symbol_info(SYMBOL)
if symbol_info is None:
    print("Symbol not found")
    return

# 時系列データを取得する
df_rates = get_rates(SYMBOL, frame=mt5.TIMEFRAME_H1, count=100)
last_close_price = df_rates['Close'][-1]
print(f"Close: {last_close_price}")


def get_rates(symbol, frame, count):
    """ 一定の期間の時系列データを取得
       """
    rates = mt5.copy_rates_from_pos(symbol, frame, 0, count)
    df_rates = pd.DataFrame(rates)
    df_rates['time'] = pd.to_datetime(df_rates['time'], unit='s')
    df_rates = df_rates.set_index('time')
    df_rates.columns = [
        'Open', 'High', 'Low', 'Close', 'tick_volume', 'spread', 'real_volume',
    ]
    return df_rates

 

時系列データを取る方法は複数あります。

上記コードは copy_rates_from_pos() で最新時点(直近のバー)から過去100バー分のOHLCデータを取得するものです。

 

copy_rates_from_pos

おそらく一番使い勝手が良さそうなのが copy_rates_from_pos() 関数。

mt5.copy_rates_from_pos(
    symbol='USDJPY', # 銘柄 
    frame=mt5.TIMEFRAME_H1, # 時間軸
    start=0, # 開始バーの位置。0は現在を表す
    count=100, # 取得するバーの数
)

 

frameはmt5モジュールで定義されている定数で指定します。

mt5.TIMEFRAME_M11分足
mt5.TIMEFRAME_M55分足
mt5.TIMEFRAME_M1515分足
mt5.TIMEFRAME_H11時間足

 

注意すべきなのは、startは新しい時点から始まって、過去へさかのぼってカウントしていくというところ。

1時間足なら、

start=0 は現在の足
start=1 は1時間前の足
start=2 は2時間前の足

を指します。

戻り値はnumpy形式なので、そのままでも使えますが、適当にDataFrameなど扱いやすい形式に変換してもいいでしょう。

上のコードでは取得したデータ(rates変数)をDataFrameに変換し、インデックスを付けた上で、カラム名を変更しています。

def get_rates(symbol, frame, count):
    """ 一定の期間の時系列データを取得
    """
    rates = mt5.copy_rates_from_pos(symbol, frame, 0, count)
    df_rates = pd.DataFrame(rates)
    df_rates['time'] = pd.to_datetime(df_rates['time'], unit='s')
    df_rates = df_rates.set_index('time')
    df_rates.columns = [
        'Open', 'High', 'Low', 'Close', 'tick_volume', 'spread', 'real_volume',
    ]

timeはUNIXTIME(秒単位)なので、pd.to_datetime()により、DataFrameのDateTime型に変換しています。

また、列名はそのままでも何も問題ないのですが、後でTA-Libで分析するとき楽にするため少し変更を加えています。

他に、時系列データを取得する関数にはcopy_rates_fromcopy_rates_rangeがあります。
これらは過去の一定期間の範囲のOHLCデータを取得できる関数です。

詳しくはそれぞれのDocを読んでみてください。

copy_rates_from

copy_rates_range

 

長期間の時系列データ取得

Python APIを経由してMT5から過去の時系列データ(ヒストリカルデータ)を取得できることを解説しました。

しかし1年~数年以上前のヒストリカルは取得できません(ブローカーによります)。

ブローカーによっては、特典として過去のTickデータを無料で配布している場合があります。OANDAでは5年くらい前からのTickデータを無料でダウンロードできます。ありがたいです…。

もし深層学習などで学習データを用意するなら、さらに前(5年~30年)のヒストリカルデータが必要かもしれません。

そんなときはForex Testerでデータ取得するのもオススメです。本来はシステムトレードのバックテスターですが、各種通貨ペア・株価指数・個別ティッカーの過去数十年の分足データなど、豊富なデータセットが用意されています。ちょっとした出費になりますが、リアルタイムでシミュレーションできるソフトの中では定番の商品となっています。

 

注文

# 注文を出す
point = symbol_info.point
result = post_market_order(
    SYMBOL, 
    type=mt5.ORDER_TYPE_BUY, 
    vol=0.1, 
    price=mt5.symbol_info_tick(SYMBOL).ask, 
    dev=20, 
    sl=mt5.symbol_info_tick(SYMBOL).ask - point * 100,
    tp=mt5.symbol_info_tick(SYMBOL).ask + point * 100,
)

# 決済する
position = result.order
result = post_market_order(
    SYMBOL, 
    type=mt5.ORDER_TYPE_SELL, 
    vol=0.1, 
    price=mt5.symbol_info_tick(SYMBOL).bid, 
    dev=20, 
    position=position,
)

def post_market_order(symbol, type, vol, price, dev, sl=None, tp=None, position=None):
    """ 注文を送信
        """
    request = {
        'action': mt5.TRADE_ACTION_DEAL,
        'symbol': symbol,
        'volume': vol,
        'price': price,
        'deviation': dev,   # float型じゃだめ
        'magic': 234000,
        'comment': "python script open",    # 何でもOK
        'type_time': mt5.ORDER_TIME_GTC,
        'type': type,
        'type_filling': mt5.ORDER_FILLING_IOC, # ブローカーにより異なる
    }
    if sl is not None:
        request.update({"sl": sl,})
    if tp is not None:
        request.update({"tp": tp,})
    if position is not None:
        request.update({"position": position})

    result = mt5.order_send(request)
    return result

 

order_send

新規注文を送信するにはmt5.order_send()関数を使います。

引数requestには、注文に必要な情報を入力した辞書(dict)を渡してあげます。基本的にドキュメントと同じように書けば正常に注文が完了します。

ただしorder_send()は、少しパラメータが間違っているだけでNoneが返ってくるなど、優しくない仕様なのでハマるときにはハマります。

特に上のサンプルではエラーハンドリングをまったくしていませんが、実践的な場面ではよーく検証して例外処理したほうがよいでしょう。

以下では、少し分かりにくかったフィールドを詳しく見ていきます。

 

action

取引操作の種類。値はTRADE_REQUEST_ACTIONS列挙体のうちの1つです。

注文に応じて変えます。

成行注文

mt5. TRADE_ACTION_DEAL

指値/逆指値注文

mt5. TRADE_ACTION_PENDING

SL・TPの変更

mt5. TRADE_ACTION_SLTP

指値注文の取消

mt5. TRADE_ACTION_REMOVE

詳しくはDoc

 

type

注文の種類。値はORDER_TYPE列挙体のうちの1つです。

こちらも注文に応じて変えます。actionパラメータとセットで変更するイメージです。

mt5.ORDER_TYPE_BUY成行買い注文
mt5.ORDER_TYPE_SELL成行売り注文
mt5.ORDER_TYPE_BUY_LIMIT買い指値注文
mt5.ORDER_TYPE_SELL_LIMIT売り指値注文
mt5.ORDER_TYPE_BUY_STOP買い逆指値注文
mt5.ORDER_TYPE_SELL_STOP売り逆指値注文

詳しくはDoc

 

type_filling

注文の種類。値はORDER_TYPE_FILLING値のうちの1つです。

ブローカー、または銘柄(通貨)によって対応しているパラメータが異なります。

ドキュメントのサンプルではORDER_FILLING_RETURNが指定されていますが、使っているブローカーに合わせて変更してください。

OANDAのUSDJPYではORDER_FILLING_IOCしか受け付けられませんでした。

mt5.ORDER_FILLING_FOK

注文数量の全部が約定しない状況では、注文数量の全部をキャンセルする

mt5.ORDER_FILLING_IOC

注文数量の全部が約定しない状況では、約定できる分の数量だけ部分的に約定させ、残りはキャンセルする

ORDER_FILLING_RETURN

注文数量の全部が約定しない状況では、約定できる分の数量だけ部分的に約定させ、残りはリミット注文として残す

詳しくはDoc

 

position

ポジションチケット。明確に識別するために、ポジションを変更および決済するときに入力します。通常は、ポジションを開いた注文のチケットと同じです。

クローズ(決済)や注文変更で指定することになります。

注文時のorder_send()の戻り値から以下のようにポジションチケット(int)が取得できます。

result = order_send(requests)
position = result.order

取得したポジションチケットを、決済注文や注文変更時にpositionフィールドに指定します。

requests = {
    position: ポジションチケット
}
mt5.order_send(requests)

 

order_sendの結果・エラー処理

mt5.order_send()はOrderSendResultオブジェクトを返しますが、その中に注文の処理結果が格納されています。

例えば、注文がエラーとなった場合に、以下のようにエラーコードを確認できます。※例示のため一部コードのみです。

code = result.retcode
if code == 10009:
    print("注文完了")
elif code == 10013:
    print("無効なリクエスト")
elif code == 10018:
    print("マーケットが休止中")

 

リターンコードの一覧はDocを参照してください。

リターンコード以外のフィールドは以下のようなものがあります(一部)

retcodeリターンコード
deal約定チケット
order注文チケット
volume(約定したら)約定ボリューム
price(約定したら) 約定価格
bid現在のマーケットBID
ask現在のマーケットASK
comment注文結果のコメント

 

ポジションの確認

ticket = result.order
# ポジションを確認する
position = mt5.positions_get(ticket=ticket)
if position is None:
    print("ポジションが存在しない")
    return
pos = position[0]
print(f"Open: {pos.price_open}")
print(f"Current: {pos.price_current}")
print(f"Swap: {pos.swap}")
print(f"Profit: {pos.profit}")

mt5.positions_get() で現在のポジションの一覧とそれぞれの状態を取得できます。

positions_get()のパラメータにはシンボル(銘柄)かポジションチケットを指定し、見つかったポジションはタプルとして返ってきます。

返ってきたポジション情報の一覧は DataFrame などに変換すると扱いやすいでしょう。

DataFrameへの変換は公式APIドキュメントにサンプルがあります。

 

さいごに

記事ではMetaTrader5(MT5)をPythonで操作し、現在値取得から取引注文・決済まで一通りの動作を確認しました。

API自体はとてもシンプルで理解しやすいですが(所詮MQLのラッパーでしかないためか)、Pythonの世界では例外処理などが非常にやりにくいものになっています。

なのでシステムトレード(自動運用)の実運用では、素直にMQLで書いたほうがいいかもしれませんね…。

とはいえ、データ処理や分析面では、Pythonの既存資産を活用しやすいです。うまくすみ分けて活用していきたいですね。

今回の記事で使用したサンプルコードの全体を載せておきます。

 

import pandas as pd
import MetaTrader5 as mt5

def main():
    # シンボル(銘柄)
    SYMBOL = 'USDJPY'

    # MT5と接続
    if not mt5.initialize():
        print(f"initialize() failed, error code = {mt5.last_error()}")
        return

    # 接続ができたらMT5のバージョンを表示する
    print(f"MetaTrader5 package version {mt5.__version__}")

    # MT5でブローカーにログイン
    authorized = mt5.login(
        12345678,  # int 型
        password="password",
        server="Demo",
    )
    if not authorized:
        print(f"User Authorization Failed")
        return

    # 口座情報を取得する     
    account_info = mt5.account_info()
    if account_info is None:
        print(f"Retreiving account information failed")
        return
    print(f"Balance: {account_info.balance}")

    # シンボルの情報を取得する
    symbol_info = mt5.symbol_info(SYMBOL)
    if symbol_info is None:
        print("Symbol not found")
        return

    # 時系列データを取得する
    df_rates = get_rates(SYMBOL, frame=mt5.TIMEFRAME_H1, count=100)
    last_close_price = df_rates['Close'][-1]
    print(f"Close: {last_close_price}")

    # 注文を出す
    point = symbol_info.point
    result = post_market_order(
        SYMBOL, 
        type=mt5.ORDER_TYPE_BUY, 
        vol=0.1, 
        price=mt5.symbol_info_tick(SYMBOL).ask, 
        dev=20, 
        sl=mt5.symbol_info_tick(SYMBOL).ask - point * 100,
        tp=mt5.symbol_info_tick(SYMBOL).ask + point * 100,
    )

    ticket = result.order
    # ポジションを確認する
    position = mt5.positions_get(ticket=ticket)
    if position is None:
        print("ポジションが存在しない")
        return
    pos = position[0]
    print(f"Open: {pos.price_open}")
    print(f"Current: {pos.price_current}")
    print(f"Swap: {pos.swap}")
    print(f"Profit: {pos.profit}")
    
    # 決済する
    ticket = result.order
    result = post_market_order(
        SYMBOL, 
        type=mt5.ORDER_TYPE_SELL, 
        vol=0.1, 
        price=mt5.symbol_info_tick(SYMBOL).bid, 
        dev=20, 
        position=ticket,
    )

    code = result.retcode
    if code == 10009:
        print("注文完了")
    elif code == 10013:
        print("無効なリクエスト")
    elif code == 10018:
        print("マーケットが休止中")
    

def get_rates(symbol, frame, count):
    """ 一定の期間の時系列データを取得
    """
    rates = mt5.copy_rates_from_pos(symbol, frame, 0, count)
    df_rates = pd.DataFrame(rates)
    df_rates['time'] = pd.to_datetime(df_rates['time'], unit='s')
    df_rates = df_rates.set_index('time')
    df_rates.columns = [
        'Open', 'High', 'Low', 'Close', 'tick_volume', 'spread', 'real_volume',
    ]
    return df_rates

def post_market_order(symbol, type, vol, price, dev, sl=None, tp=None, position=None):
    """ 注文を送信
    """
    request = {
        'action': mt5.TRADE_ACTION_DEAL,
        'symbol': symbol,
        'volume': vol,
        'price': price,
        'deviation': dev,   # float型じゃだめ
        'magic': 234000,
        'comment': "python script open",    # 何でもOK
        'type_time': mt5.ORDER_TIME_GTC,
        'type': type,
        'type_filling': mt5.ORDER_FILLING_IOC, # ブローカーにより異なる
    }
    if sl is not None:
        request.update({"sl": sl,})
    if tp is not None:
        request.update({"tp": tp,})
    if position is not None:
        request.update({"position": position})

    result = mt5.order_send(request)
    return result

if __name__ == '__main__':
    main()
  • この記事を書いた人

次世代ペンギン

長いのでペンギンとお呼びください。システム開発・プログラミングのお仕事をしています。甘味とコーヒーは生命線。多くの人に役立つ情報のシェアが目標です。

人気の記事

1

会社員でプログラマーとして働いている人、インフラやネットワークのエンジニアとして働いている人の中には、フリーランスのプログラマーとして独立、もしくは転向したい人もいるので ...

2

キャリアアップのため、または高収入を目指して、しっかりプログラミングを学びたいという人が増えてきましたね。 この記事では現役のエンジニアである私が、実際に仕事で稼げるよう ...

3

フリーランスのプログラマーにとって収入の向上に最も直結するのはスキルです。 必要なスキル、スキルの獲得方法が気になる人も多いでしょう。 また、これからフリーランスを目指す ...

4

Vuetifyの v-progress-circular コンポーネントは、数値データや処理状況を環状(円状)のデザインで教えてくれるUIデザインです。 ローディングのス ...

5

Vuexのstore(ストア)を使うと、各コンポーネント間で個別にデータのやり取りすることなく、データを一元的に管理できます。Vueでは欠かせない機能といえるでしょう。 ...

-Python, Tech
-, , ,