この記事の内容をコピペしていけば、最もシンプルなナンピンマーチン式のFX自動売買ツール(ここではナンピンマーチンbotと呼びます)をMQLではなくPythonで作成することが可能です。
初心者向けになるべくコードの意味まで丁寧に解説するように心がけていますので、初心者の方も是非、PythonでのFX自動売買botの作成にチャレンジしてみてください。
また、最後にこの記事で紹介したコードをまとめた.ipynbファイルをダウンロード可能にしております。順番にコピペしていっても上手くいかない場合や、一つずつコピペしていくのが大変な方はご活用ください。
自動売買ツール(bot)のロジック
基本的なナンピンマーチンbotの設計概要は以下のように非常にシンプルです。
- Inputの設定
- ループ処理
- ポジションの確認
- 新規エントリー注文
- 追加エントリー(ナンピン)注文
- ポジションクローズ注文
ロジック概要
複雑な条件は可能な限り省いて簡略化しています。
初期lot | 0.01lot |
エントリー | 稼働時間内であればすぐに新規ポジションを取る |
ナンピン幅 | 最低価格変動0.01に対して、価格幅2.00 |
マーチン倍率 | マーチンゲール法(倍率1.5倍) |
利確目標 | 1ポジションあたり143円 |
稼働時間 | 取引時間帯はフル稼働 |
強制クローズ | なし(翌日にポジションを持ち越す) |
Pythonの環境設定
この記事では、基本的なPythonの環境設定は整っていて、Jupyter Notebookを使用する前提で話を進めます。
Python用MetaTrader5のインストール
まず、MetaTrader5をインストールします。Jupyter Notebook上で以下のコマンドを入力すればインストールされます。
pip install MetaTrader5
この処理は一度だけ実行すれば問題なく、その後繰り返し実行する必要はありません。
Python用MetaTrader5のインポート
ここからは、Jupyter Notebook上に順番にコードを入力していきます。一通り入力して保存しておけば、それがFX自動売買ツール(bot)としての実行ファイルになります。
まずは、インストール済みのPython用MetaTrader5を以下のコマンドでインポートします。
import MetaTrader5 as mt5
この処理により、今後は、「mt5.関数名」という形で、Python用MT5で定義されている関数を呼び出すことが可能になります。
ログイン情報を入力
Pythonの場合、APIを介してMT5にログインするような形になるので、ログイン情報を入力する必要があります。そこで、通常MT5にログインするのと同じ以下の情報を用意します。
login_ID = 12345678 # ご自身のログインIDを入力
login_server = 'XMTrading-MT5' # ご自身のログインサーバーを入力
login_password = 'XXXXXXXXX' # ご自身のログインパスワードを入力
上記はサンプルですので、ご自身のログイン情報に書き換えてください。
MT5に接続
上記のログイン情報で指定した取引口座でMT5に接続します。
# ログイン情報で指定した取引口座でMetaTrader5に接続
if not mt5.initialize(login=login_ID, server=login_server,password=login_password):
print("initialize() failed, error code =",mt5.last_error())
quit()
なお、実行する際は事前にMT5を起動しておくことをおすすめします。MT5を起動していない場合は、この処理で自動的にMT5が起動するのですが、完全に起動する前に次の処理が進んでしまってエラーになったりするからです。
MT5のインストール
上記の処理は、MT5がインストールされていることが前提です。MT5をインストールした状態で進めてください。
インストールされていない方は、以下のリンクから「PC対応MT5」をダウンロードしてインストールしておきます。
アルゴリズム取引を許可
MT5を開いたら、ツールのオプションで以下の通り、
「アルゴリズム取引を許可」にチェックを入れておきます。
「外部Python APIを介したアルゴリズム取引を無効にする」にチェックが入っていないことも確認しておいてください。
そうでないと、PythonからMT5にアクセスすることが出来ません。
Inputの設定
初期設定として、いくつかインプットを定めておきます。
symbol = 'GOLD' # 取引対象
first_lot = 0.01 # 初期ロット
nanpin_range = 200 # ナンピン幅
profit_target = 143 # 利益目標
magic_number = 10001 # マジックナンバー
slippage = 10 # スリッページ
取引対象(symbol)
取引対象(通貨ペア)を選びます。ここでは、XMTradingのGOLDを取引対象としますので、’GOLD’と入力します。
初期ロット(first_lot)
1番目のポジションを取得する時のロット数の設定です。最小は0.01ですので、ひとまず0.01に設定しておきます。
ナンピン幅(nanpin_range)
1番目のポジションの後、どれくらい価格が動いたら次のポジションを取りに行くかという価格幅の設定です。200と設定すると、GOLDの場合、2.00の価格幅を意味します。(厳密には、この後に *Pointという形で単位を決めにいきます。)
利益目標(profit_target)
利益目標を金額で設定します。ここでは単位は円で、143円という設定です。1ポジションあたり143円の含み益が発生したら、クローズ注文を行うようなロジックです。
マジックナンバー(magic_number)
どのようなEAでも設定する識別番号のようなものです。他のEAと同時に動かすようなことがなければ、どんな数値でも構いません。ロジックには関係ありません。ここでは適当に10001と設定しています。
スリッページ(slippage)
MT4では、注文時にスリッページの許容範囲が設定可能です。ただし、業者によっては設定しても無効になるようです。XMTradingもその一つで、注文時にスリッページの許容範囲を設定しても無効になります。ただし、以下の公式の説明の通り、スリッページはほとんど発生しません。
スリッページは発生しますか?
当社で取引される場合、スリッページはほとんど発生しません。しかし、特に重要な経済ニュースの発表時などは、市場価格の急騰または急落により、お客様の注文はリクエストされたものと異なるレートで執行される場合があります。
ここでは適当に10と設定しておきます。
各種情報取得
必要な情報をMT5から取得します。ここではひとまず、pointのみです。
point=mt5.symbol_info(symbol).point # 価格の最小単位
pointは、通貨ペア(取引対象)毎に定まる最小単位です。XMTradingのGOLDの場合は、point=0.01を取得します。
ループ処理
初期設定が終わり、ここからループ処理に入ります。
無限ループ(while 1:)
while 1:
これは、Pythonにおいて無限ループを実現させる方法の一つです。MQL4のvoid OnTick()のように、tickを受信する度というループではなく、tickによらず無限に処理をループします。
ナンピンマーチンbotでは、少しでも価格が変動したら素早くエントリー注文やクローズ注文を行う必要がありますので、主要なロジックはほとんどこの中に記載していきます。
これ以降は、「while 1:」の中に記載していく内容になります。
最新価格(tick)情報を取得
まず、最新の価格(tick)情報を取得します。
symbol_tick=mt5.symbol_info_tick(symbol) # symbolのtick情報を取得
これで、symbol_tick.ask
や、symbol_tick.bid
によって、Ask値やBid値を取得することが出来るようになります。
ポジションの確認
次に、ポジションを確認する処理を行います。
具体的には、buyポジションとsellポジションの数、保有ポジションの含み損益、最新のポジションのlot数と価格を取得しておき、次の注文の際に使用します。
#### ポジションの確認###########################################
buy_position = 0 # buyポジション数の初期化
sell_position = 0 # sellポジション数の初期化
buy_profit = 0 # buy_profitの初期化
sell_profit = 0 # sell_profitの初期化
current_buy_lot = 0 # 最新のbuyポジションのlot数の初期化
current_sell_lot = 0 # 最新のsellポジションのlot数の初期化
positions=mt5.positions_get(group='*'+symbol+'*') # ポジション情報を取得
for i in range(len(positions)): # 全てのポジションを確認
order_type = positions[i][5] # buyかsellか取得
profit = positions[i][15] # ポジションの含み損益を取得
if order_type == 0: # buyポジションの場合
buy_position += 1 # buyポジションのカウント
buy_profit += profit # buyポジションの含み損益に加算
current_buy_lot = positions[i][9] # 最新のbuyポジションのlot数を取得
current_buy_price = positions[i][10] # 最新のbuyポジションの取得価格を取得
if order_type == 1: # sellポジションの場合
sell_position += 1 # sellポジションのカウント
sell_profit += profit # sellポジションの含み損益に加算
current_sell_lot = positions[i][9] # 最新のsellポジションのlot数を取得
current_sell_price = positions[i][10] # 最新のsellポジションの取得価格を取得
################################################################
buy_position、sell_position
buy_positionは、buyポジションの数を意味します。buyポジション数をいくつ持っているかを判定する際に使用します。sell_positionも同様です。
buy_profit、sell_profit
buy_profitは、buyポジションの合計含み損益を意味します。buyポジションを複数持っている場合は、複数ポジションの含み損益を通算した数値になります。クローズ注文を行うかどうか判定する際に使用します。sell_profitも同様です。
current_buy_lot、current_sell_lot
current_buy_lotは、最新のbuyポジションのロット数を意味します。例えば、buyポジションを3つ持っている状況であれば、3つ目のbuyポジションのロット数を意味します。current_sell_lotも同様です。
current_buy_price、current_sell_price
current_buy_priceは、最新のbuyポジションの取得価格を意味します。例えば、buyポジションを3つ持っている状況であれば、3つ目のbuyポジションの取得価格を意味します。current_sell_priceも同様です。
処理概要
この処理を簡単に解説しておくと、全てのポジションを順番に確認していき、
buyポジションだったら、
- buy_positionを1つ増やす。
- buy_profitにそのポジションのprofitを加える。
- current_buy_lotにロット数を記録(上書きを繰り返すので最後のbuyポジションの情報が最終的に残る)
- current_buy_priceに取得価格を記録(上書きを繰り返すので最後のbuyポジションの情報が最終的に残る)
sellポジションだったら、
- sell_positionを1つ増やす。
- sell_profitにそのポジションのprofitを加える。
- current_sell_lotにロット数を記録(上書きを繰り返すので最後のsellポジションの情報が最終的に残る)
- current_sell_priceに取得価格を記録(上書きを繰り返すので最後のsellポジションの情報が最終的に残る)
というような処理を行なっています。
新規エントリー注文
続いて、新規エントリー注文です。エントリー条件はシンプルに、「ポジションを持っていなかったら」という条件のみです。
#### 新規buyエントリー###########################################
if buy_position == 0: # buyポジションがない場合
request = {
'symbol': symbol, # 通貨ペア(取引対象)
'action': mt5.TRADE_ACTION_DEAL, # 成行注文
'type': mt5.ORDER_TYPE_BUY, # 成行買い注文
'volume': first_lot, # ロット数
'price': symbol_tick.ask, # 注文価格
'deviation': slippage, # スリッページ
'comment': 'first_buy', # 注文コメント
'magic': magic_number, # マジックナンバー
'type_time': mt5.ORDER_TIME_GTC, # 注文有効期限
'type_filling': mt5.ORDER_FILLING_IOC, # 注文タイプ
}
result = mt5.order_send(request)
################################################################
#### 新規sellエントリー###########################################
if sell_position == 0: # sellポジションがない場合
request = {
'symbol': symbol, # 通貨ペア(取引対象)
'action': mt5.TRADE_ACTION_DEAL, # 成行注文
'type': mt5.ORDER_TYPE_SELL, # 成行売り注文
'volume': first_lot, # ロット数
'price': symbol_tick.bid, # 注文価格
'deviation': slippage, # スリッページ
'comment': 'first_sell', # 注文コメント
'magic': magic_number, # マジックナンバー
'type_time': mt5.ORDER_TIME_GTC, # 注文有効期限
'type_filling': mt5.ORDER_FILLING_IOC, # 注文タイプ
}
result = mt5.order_send(request)
################################################################
ここでは、order_send関数というのを使用します。「リクエスト構造体(request)」という注文情報のようなものを作成して、「mt5.order_send(request)
」でMT5に送信する、というような処理イメージです。
概要は、buyあるいはsellのそれぞれのポジションを持っていなかったら、その時の価格ですぐに成行注文を行う(成行注文でも価格を指定して注文を行います。)、そのロット数は最初の Inputで設定したfirst_lot(初期ロット)ということです。
注文コメントは設定しなくても問題ありませんが、今回は”first_buy”や”first_sell”というように設定しておきます。
その他、「注文有効期限(type_time)」や「注文タイプ(type_filling)」等について詳しく確認したい場合は、order_send関数を確認してください。ただし、今回の目的においては、この通りの設定のままで特に問題ないかと思います。
追加エントリー(ナンピン)注文
そして、肝心のナンピン注文の設定です。
とは言っても特に難しいことはしておらず、新規エントリー注文との違いは、
- ナンピン幅の条件を満たした場合にのみ注文を行うこと
- ロット数を前回のロット数の1.5倍にしていること
これだけです。
#### 追加buyエントリー#############################################
if buy_position > 0 and symbol_tick.ask < current_buy_price - nanpin_range * point:
request = {
'symbol': symbol, # 通貨ペア(取引対象)
'action': mt5.TRADE_ACTION_DEAL, # 成行注文
'type': mt5.ORDER_TYPE_BUY, # 成行買い注文
'volume': round(current_buy_lot * 1.5+0.001,2), # ロット数
'price': symbol_tick.ask, # 注文価格
'deviation': slippage, # スリッページ
'comment': 'nanpin_buy', # 注文コメント
'magic': magic_number, # マジックナンバー
'type_time': mt5.ORDER_TIME_GTC, # 注文有効期限
'type_filling': mt5.ORDER_FILLING_IOC, # 注文タイプ
}
result = mt5.order_send(request)
##################################################################
#### 追加sellエントリー#############################################
if sell_position > 0 and symbol_tick.bid > current_sell_price + nanpin_range * point:
request = {
'symbol': symbol, # 通貨ペア(取引対象)
'action': mt5.TRADE_ACTION_DEAL, # 成行注文
'type': mt5.ORDER_TYPE_SELL, # 成行売り注文
'volume': round(current_sell_lot * 1.5+0.001,2), # ロット数
'price': symbol_tick.bid, # 注文価格
'deviation': slippage, # スリッページ
'comment': 'nanpin_sell', # 注文コメント
'magic': magic_number, # マジックナンバー
'type_time': mt5.ORDER_TIME_GTC, # 注文有効期限
'type_filling': mt5.ORDER_FILLING_IOC, # 注文タイプ
}
result = mt5.order_send(request)
##################################################################
現在価格がナンピン幅に達しているか
if buy_position > 0 and symbol_tick.ask < current_buy_price - nanpin_range * point:
current_buy_price
は、ポジションの確認で取得した最新ポジションの取得価格です。
nanpin_rangeは、Inputで設定したナンピン幅200です。pointは、各種情報で取得した0.01です。
つまりこのif文は、例えばその価格が1740.21だとすると、
という条件文になります。
ポジションクローズ注文
最後に、ポジションクローズ注文です。
クローズ条件を確認し、条件を満たしていたら各ポジションについて順番にクローズ注文を出していきます。
#### buyクローズ################################################
if buy_position > 0 and buy_profit > profit_target * buy_position:
for i in range(len(positions)):
ticket=positions[i][0]
order_type = positions[i][5]
lot = positions[i][9]
if order_type == 0: #buyポジションをクローズ
request = {
'symbol': symbol, # 通貨ペア(取引対象)
'action': mt5.TRADE_ACTION_DEAL, # 成行注文
'type': mt5.ORDER_TYPE_SELL, # 成行売り注文
'volume': lot, # ロット数
'price': symbol_tick.bid, # 注文価格
'deviation': slippage, # スリッページ
'type_time': mt5.ORDER_TIME_GTC, # 注文有効期限
'type_filling': mt5.ORDER_FILLING_IOC, # 注文タイプ
'position':ticket # チケットナンバー
}
result = mt5.order_send(request)
################################################################
#### sellクローズ################################################
if sell_position > 0 and sell_profit > profit_target * sell_position:
for i in range(len(positions)):
ticket=positions[i][0] # チケットナンバーを取得
order_type = positions[i][5] # buyかsellか取得
lot = positions[i][9] # lot数を取得
if order_type == 1: #sellポジションをクローズ
request = {
'symbol': symbol, # 通貨ペア(取引対象)
'action': mt5.TRADE_ACTION_DEAL, # 成行注文
'type': mt5.ORDER_TYPE_BUY, # 成行買い注文
'volume': lot, # ロット数
'price': symbol_tick.ask, # 注文価格
'deviation': slippage, # スリッページ
'type_time': mt5.ORDER_TIME_GTC, # 注文有効期限
'type_filling': mt5.ORDER_FILLING_IOC, # 注文タイプ
'position':ticket # チケットナンバー
}
result = mt5.order_send(request)
##################################################################
ポジションクローズ条件
if buy_position > 0 and buy_profit > profit_target * buy_position:
buyポジションのクローズ注文を行う条件は、以下の2つを同時に満たした場合です。
- buyポジションを1つ以上持っている
- buyポジションの含み益合計が、利益目標×buyポジション数を上回っている
これを上記算式で表現しています。具体的には、例えばbuyポジション数が3だった場合、利益目標(profit_target)は143円と設定していたので、143円の3倍である429円を、buyポジションの含み益合計が上回った場合に、buyポジションのクローズ注文を行います。
ポジションを決済する関数
エントリー注文と同じく、ここでもorder_send関数を使用します。「リクエスト構造体(request)」にチケットナンバー(ポジションを特定する番号)を加えて、反対売買注文(buyポジションであればsell注文、sellポジションであればbuy注文)を送信しているだけです。
まとめ
以上、これまで見てきたものを組み合わせるだけで、基本的なナンピンマーチンbotは完成です。
いかがでしたでしょうか。思ったよりシンプルな構造ということがよくわかったかと思います。
テクニカル指標の導入
今回は、テクニカル指標を使用しない単純なナンピンマーチンEAの作成方法をご紹介しましたが、MAやRSIのようなテクニカル指標用いて売買判断ロジック取り入れたFX自動売買ツールの作成方法を以下の記事でご紹介しています。
機械学習モデルの導入
さらに、テクニカル指標による売買判断ロジックを発展させて機械学習モデルによる売買判断ロジックに変更することにより、シンプルなMLbot(機械学習を取り入れたFX自動売買ツール)の作成方法もご紹介しています。
EAのバックテスト方法
以下の記事では、作成したFX自動売買ツール(bot/EA)を、Google Colabを使ってPythonでバックテストを行う方法を紹介しています。botやEAの作成に正確なバックテストは不可欠ですので、是非こちらもご覧ください。
MQL4/MQL5でのEA開発
この記事では PythonでのFX自動売買ツール開発をご紹介しましたが、以下の記事では、MQL4やMQL5でナンピンマーチンEAを作成する方法についてもご紹介しています。
この記事のbotと全く同じロジックになっていますので、是非見比べてみてください。
個人的にはPythonの方が慣れているのでコーディングはしやすいのですが、ただ単にEA(自動売買ツール)を使うという面では、MQL4/MQL5で開発してMT4/MT5に直接使う方が使いやすいと思います。
.ipynbファイルのダウンロード
今回ご紹介したコードをまとめたものは以下からダウンロード可能です。
bot001.ipynb
PythonスクリプトをMT5のチャート上で直接実行する方法
MT5(MetaTrader5)では、Pythonスクリプトを通常のMQL5プログラムと同様にチャート上で直接実行できる機能が存在します。その方法を解説しています。