本シリーズの目次
- 第1回: 前提知識などの解説
- 第2回: コード前半部分の解説
- 第3回: コード後半部分の解説 ←本記事
はじめに
本記事は前回からの続きとなっております。前回の記事はこちら
前回記事ではプログラムの前半部分を解説しました。本記事ではプログラムの後半部分を紹介します。
コード全文とトレードノートのテンプレファイルはgithubでダウンロードできます。
プログラム内容
それではコードの解説。いよいよ main
関数の定義に入ります。
main関数その1
def main():
# MT5への接続を初期化
if not mt5.initialize():
print("MT5の接続に失敗しました")
mt5.shutdown()
exit()
まずはMT5と接続します。プログラム実行の際は、MT5を起動させ、口座にログインしている状態にしてください。MT5を閉じた状態で実行すると、モジュールが自分自身でMT5の実行ファイルを見つけようとするらしく、待ち時間が発生します。もしMT5を起動していない状態でプログラムを実行したい場合は
mt5.initialize(
path # MetaTrader 5ターミナルのEXEファイルへのパス
)
あるいは
mt5.initialize(
path # MetaTrader 5ターミナルのEXEファイルへのパス file
timeout=TIMEOUT, # timeout
login=LOGIN, # 口座番号
password="PASSWORD", # パスワード
server="SERVER" # ターミナルで指定されているサーバ名
)
のようにしてください。
main関数その2
while(True):
# エクセルのワークブック、ワークシート、テーブルを取得
try:
wb = openpyxl.load_workbook(FILE_PATH)
except FileNotFoundError:
print("ファイルパスが不適切です")
break
try:
ws = wb[SHEET_NAME]
except KeyError:
print("ワークシート名が不適切です")
break
try:
table = ws.tables[TABLE_NAME]
except KeyError:
print("テーブル名が不適切です")
break
ここから長い While
文に突入します。While
文の外に出たらMT5との接続を切断し、プログラムが終了します。つまり、While
文を break
するとそこで終了です。
上記のコードでは定数として宣言したファイルパス・ワークシート名・テーブル名が適切であるかをチェックしています。不適切であれば break
して終了しています。
main関数その3
# input
from_date_input = input("Enter from_date(e.g. 2024/1/14T14:30:00 or 2024/1/14): ")
from_date = return_shifted_unix_time_from_string(from_date_input)
if(from_date == None):
print("入力がフォーマットに一致しません")
if(ask_yes_no("トレード履歴の出力を続けますか?[y/n]: ")):
continue
else:
break
to_date_input = input("Enter to_date(e.g. 2024/1/14T22:20:00 or 2024/1/14): ")
to_date = return_shifted_unix_time_from_string(to_date_input)
if(to_date == None):
print("入力がフォーマットに一致しません")
if(ask_yes_no("トレード履歴の出力を続けますか?[y/n]: ")):
continue
else:
break
# 取得期間のエラー処理
if from_date > to_date:
print("Error: from_dateがto_dateよりも後になっています")
if(ask_yes_no("トレード履歴の出力を続けますか?[y/n]: ")):
continue
else:
break
# 時刻をTIME_SHIFTだけずらす
from_date += TIME_SHIFT
to_date += TIME_SHIFT
symbol_input = input("Enter symbol(e.g. usdjpy): ")
group_input = "*" + symbol_input.upper() + "*"
from_date
、to_date
、symbol
の入力を受け取り、エラーチェックをしています。ここでTIME_SHIFT
分だけ時刻をずらしているので、入力する時刻はMT5の口座履歴の時刻にあわせます。
symbol
は usd
のように片方の通貨だけ指定することもできるし、実は指定しなくてもOK。
main関数その4
# 取引履歴を取得
deal_history = mt5.history_deals_get(from_date, to_date, group=group_input)
# 取引履歴が取得できているかチェック
if deal_history is None:
print("履歴の取得に失敗しました")
if(ask_yes_no("トレード履歴の出力を続けますか?[y/n]: ")):
continue
else:
break
elif len(deal_history) == 0:
print("該当するデータがありませんでした")
if(ask_yes_no("トレード履歴の出力を続けますか?[y/n]: ")):
continue
else:
break
取引履歴を取得します。うまく取得できなかった場合は最初に戻ることができます。
main関数その5
# テーブルの最終行の1行下の行に出力するようにする
row_writing, _ = coordinate_to_tuple(table.ref.split(":")[1])
row_writing += 1
# テーブル範囲を変更
expand_table_range_by_one_row(table)
# 行の高さを変更
ws.row_dimensions[row_writing].height = ROW_HEIGHT
テーブル範囲を取得して、テーブルの最終行の1行下にデータを書くように設定します。つまり、テーブル最終行が丸々1行空白だったとしても、その空白行にデータは出力されないので注意。
さらにテーブル範囲と行の高さを変更しています。
main関数その6
trade_date = ""
trade_symbol = ""
trade_type = 0
trade_entry_price = 0
trade_stoploss_price = 0
trade_close_price = 0
trade_entry_volume = 0
trade_close_volume = 0
trade_profit = 0
trade_commission = 0
trade_swap = 0
trade_total_profit = 0
trade_riskreward = 0
# トレード履歴をワークシートに追加
for idx, deal in enumerate(deal_history):
# 1個目の取引からトレード日、通貨ペア、エントリー方向を決定する
if idx == 0:
# TIME_SHIFTの分だけ時間をずらす
tmp_date = datetime.fromtimestamp(deal.time - TIME_SHIFT)
trade_date = tmp_date.strftime("%Y/%m/%d")
trade_day_of_the_week = change_weekday_to_japanese(tmp_date.weekday())
trade_symbol = deal.symbol
trade_type = deal.type
# 通貨ペアが異なる場合
if deal.symbol != trade_symbol:
print("Error: 複数の通貨ペアのトレード履歴が含まれます")
if(ask_yes_no("トレード履歴の出力を続けますか?[y/n]: ")):
continue
else:
break
トレード結果のうち、出力したい値を宣言しておきます。そして、先ほど取得した取引履歴の中身を deal
として1つずつ取り出していきます。取引履歴の中で最も古いものから、トレード日・通貨ペア・エントリー方向を決定します。
またすべての deal
で通貨ペアのチェックを行い、もし通貨ペアが一致していなければやり直しとなります。
main関数その7
# エントリー/決済 共通
trade_profit += deal.profit
trade_commission += deal.commission + deal.fee
trade_swap += deal.swap
# エントリーの場合
if (deal.entry == mt5.DEAL_ENTRY_IN and deal.type == trade_type):
trade_entry_volume += deal.volume
trade_entry_price += deal.price*deal.volume
# 注文履歴からsl価格を取得
order = mt5.history_orders_get(ticket=deal.order)
trade_stoploss_price += order[0].sl*deal.volume
# 決済の場合
elif (deal.entry == mt5.DEAL_ENTRY_OUT and deal.type == 1-trade_type):
trade_close_volume += deal.volume
trade_close_price += deal.price*deal.volume
if trade_entry_volume != trade_close_volume:
print("Error: エントリーと決済のロット数が合致しません")
if(ask_yes_no("トレード履歴の出力を続けますか?[y/n]: ")):
continue
else:
break
deal
をエントリーと決済に分類し、それぞれで処理を行います。
if (deal.entry == mt5.DEAL_ENTRY_IN and deal.type == trade_type):
elif (deal.entry == mt5.DEAL_ENTRY_OUT and deal.type == 1-trade_type):
deal_type
(買い、売り)による判定も行っているので、例えばトレード中にリスクヘッジとして両建てをする場合でも、その分については無視することができます。
手数料を表す値が commission
と fee
で2種類あるのですが、違いがよくわからないので1つにまとめてます。すべての deal
の処理が終了した後、エントリーと決済でロット数が一致しているかを確認します。
エントリーの場合は取引履歴 deal
に対応する注文履歴 order
を取得して、order
からストップロス価格を取得しています。
order = mt5.history_orders_get(ticket=deal.order)
trade_stoploss_price += order[0].sl*deal.volume
order
の中の最初の要素分しかストップロス価格を加算していませんが、1つのdeal
に対応する order
は1つしか無いはずなのでおそらくこれで大丈夫だと思います。
main関数その7
trade_type = change_type_to_string(trade_type)
trade_entry_price = round_price(trade_entry_price/trade_entry_volume)
trade_stoploss_price = round_price(trade_stoploss_price/trade_entry_volume)
trade_close_price = round_price(trade_close_price/trade_entry_volume)
trade_total_profit = trade_profit + trade_commission + trade_swap
trade_riskreward = (trade_close_price - trade_entry_price)/(trade_entry_price - trade_stoploss_price)
row = [
trade_date, # 日付
trade_day_of_the_week, # 曜日
trade_symbol, # シンボル
trade_type, # エントリー方向
trade_entry_price, # エントリー価格
trade_stoploss_price, # ストップロス価格
trade_close_price, # 決済価格
trade_entry_volume, # ロット数
trade_profit, # 利益
trade_commission, # 手数料
trade_swap, # スワップ
trade_total_profit, # 総利益
trade_riskreward # リスクリワード
]
計算結果を成形し、出力データをrow
リストに入れます。 row
内の一番前の要素からテーブルの1列目、2列目...という風に書き込まれていくので、要素の順番は大事です。エントリー価格・ストップロス価格・決済価格は、ロット数を重みとした加重平均としています。
main関数その8
# データを出力
for col_writing, value in enumerate(row, start=1):
cell_writing = ws.cell(row=row_writing, column=col_writing)
cell_above = ws.cell(row=row_writing-1, column=col_writing)
copy_cell_style(cell_writing, cell_above)
ws.cell(row=row_writing, column=col_writing, value=value)
# Excelファイルを保存
wb.save(FILE_PATH)
print(f"トレード履歴が {FILE_PATH} に保存されました")
if(ask_yes_no("トレード履歴の出力を続けますか?[y/n]: ")):
continue
else:
break
print("トレード履歴の出力を終了します")
# MT5の終了
mt5.shutdown()
if __name__ == "__main__":
main()
データを出力し、ワークブックを保存します。データはデータ出力が終わったらMT5との接続を切断して終了。お疲れさまでした。
振り返り
プログラムの改善アイデア
- 条件付き書式のコピーをできるようにしたい
- Excelファイルが無ければ作ってくれるようにしたい
- テーブルの項目名を照合して、「日付」なら日付データを入れる、みたいな感じにしたい
くそザコプログラミングなので改善アイデアはいくらでも出てくると思いますが、とりあえずはこれでいいのだ。
最後に
トレード記録を簡略化するためにPythonでプログラムを書いてみました。これをつくってから記録作業が結構楽になったので、よければみなさんも試してみてください。
コード全文とトレードノートのテンプレートはgithubからダウンロードできます。
参考リンク