BOTの稼働状況の監視(1)定期的にコマンドラインの標準出力をメールで受け取ろう!

自宅やクラウド、WindowsVPSなどでBOTを稼働したまま外出していると、今のBOTの稼働状況が気になることがあります。

売買のタイミングでLINE通知する方法もありますが、私はできればコマンドラインの内容を全部確認したいです。そこで、この記事では、コマンドラインに出力されるログの内容をそのまま6時間おきにメールで転送してBOTの稼働状況を把握する方法を解説します!

▽ コマンドラインの内容を外でも把握したい

▽ メールで定期的に通知する方法

それではやっていきましょう!

全体の流れ

これはあくまで私のやり方ですが、上記のことを実現するために以下の2つの手順を実行しています。

(1)ログをファイルに書き出す

今まではコマンドラインへの出力はすべてprint()文を使っていましたが、pythonには標準のログ用モジュール(logging)が存在します。これを使って、全てのprint文を logger.info(“”)に置き換えると、コマンドラインに出力する内容をリアルタイムで同時にログファイルに書き出すことができます。

(2)ログファイルの中身をメールする

トレードBOT本体とは別のpythonファイル(例:mail.py)を作って、その別BOTに定期的にログファイルの内容を指定のメールアドレスに送信させます。実行は、Windowsタスクスケジューラを使って6時間おきに自動実行します。

1.標準出力の内容をログファイルに書き出す

pythonには標準で logging というログ用のモジュールが用意されています。

このログ用モジュールを使って、いままでprint文で出力していた箇所を、すべてlogging.info に置き換えます。すると、コマンドラインに出力した文を同時にログファイルにも書き出すことができます。

説明だけではピンと来ないと思うので、実際にやってみましょう! 例えば、以下のように1秒おきにprint(“Hello!”)と出力するようなコードを作ってみてください。


import time
while True:

	print("Hello!")
	time.sleep(1)


これを実行すると、以下のように1秒おきにコマンドラインに「Hello!」と出力されます。

ログ用モジュールを使う場合

では全く同じことを、ログ用モジュールを使って書いてみましょう。ログ用モジュールで書き直すと以下のようになります。


import time
from logging import getLogger,Formatter,StreamHandler,FileHandler,INFO

logger = getLogger(__name__)
handlerSh = StreamHandler()
handlerFile = FileHandler("c:/Pydoc/helloBot.log") # ログファイルの出力先とファイル名を指定
handlerSh.setLevel(INFO)
handlerFile.setLevel(INFO)
logger.setLevel(INFO)
logger.addHandler(handlerSh)
logger.addHandler(handlerFile)

while True:

	logger.info("Hello!")
	time.sleep(1)


2行目~10行目までは、ただの「おまじない」だと思ってコピーしていただいても構いません。ログファイルの出力先とファイル名の箇所だけご自身で必要に応じて変更してください。

そして先ほど、print(“Hello!”) と書いた箇所を、logger.info(“Hello!”) に書き換えます。これを実行すると以下のようになります。

先ほどのprint文と同じように、コマンドラインに「Hello!」が出力されています。しかし同時に指定したフォルダに「helloBot.log」というログファイルが出力されている点に注目してください。

このログファイルを開いてみましょう。「Hello!」BOTは実行中のまま開いても構いません。すると以下のように、メモ帳にコマンドラインと同じ内容が出力されているのがわかります。

このファイルを同時にリアルタイムで、別のpythonファイルから読み込んでメールするようなコードを書けば、コマンドラインの出力結果を定期的にメールで受け取ることができるわけです!

▽ (例)Bitflyerの自動売買BOTのログファイル

ログ機能の説明の補足

なお、ここでは「コマンドラインへの標準出力を同時にファイルに書き出す」ということだけがやりたかったので、ログ機能(logging)の説明は最低限にとどめました。Python標準のログ機能についてもっと詳しく知りたい方は、以下の外部記事が参考になると思います。

[Quitta]Pythonのログ出力のまとめ
Pythonでのロギング機能を実装してみる

2.出力したファイルの内容をメールで転送する

次に別のpythonファイルを作って、さきほどのログファイルの内容を定期的に指定のメールアドレスに転送するスクリプトを作ります。

監視用のpythonコードは、本体BOTとは全く関係のない機能なので、切り離して別プロセスで実行します。こうしておけば、複数BOTを運用するときでも複数のログを同時に監視できますし、万が一、トラブルで止まったりしても、本体BOTに影響を与えないので安心です。

Gメールを送信するpythonコード

Pythonでメールを送るのは、GmailのようなWebメールを送信元として使うのであれば、全く難しくありません。
以下のようなコードを作るだけです。


import smtplib
from email.message import EmailMessage
from datetime import datetime

with open( "helloBot.log" ) as file:   # さっきのログファイルを指定して読み込み
	msg = EmailMessage()
	msg.set_content(file.read())

msg["Subject"] = "BOT稼働状況の通知:{}".format(datetime.now().strftime("%Y-%m-%d-%H-%M"))
msg["From"] = "xxxxxxxxxxxx@gmail.com"         # 送信元のアドレス
msg["To"] = "xxxxxxxxxxxx@gmail.com"           # 受け取りたいアドレス

server = smtplib.SMTP("smtp.gmail.com",587)    # これはGmailのSMTPなら共通
server.starttls()
server.login("Account", "PassWord")            # Gmailのアカウント名とパスワード
server.sendmail( msg["From"],msg["To"],msg.as_string() )
server.close()


メールの送信には、SMTPというプロトコルを使います。

GmailのようなWebメールであれば、メール送信サーバーは「smtp.gmail.com」、TLS/STARTTLSのポートは「587」と決まっているので、上記のようなコードを書いて、アカウント名とパスワードを入れれば、どこからでもpythonでメールの送信を実行できます。

なお、以下のページを参考にさせていただきました。
ありがとうございます。

Python3公式ドキュメント(smtplib)
Python3公式ドキュメント(email使用例)
[Quitta]Pythonでメール送信~Gmail編~

実行手順

ではこのコードを、「mail.py」などの別ファイルで保存して実行してみましょう!

より実践っぽく試したい方は、さきほどの「helloBot.py」を動かしたまま、Anacondaプロンプトをもう1画面立ち上げて、同時に「mail.py」を実行してみるとわかりやすいと思います。

▽ 左画面「helloBot.py」実行中、右画面「mail.py」実行

このように並行して複数のpythonプログラムを別のコマンドラインから実行することを、「別プロセス」といいます。

「別プロセスってよく聞くけどどういう意味だろうな?」と思っっていた方は、このようにコマンドプロンプトの画面を複数立ち上がる状況をイメージすればわかりやすいと思います。

実行結果

以下のように受信したいアドレス宛にメールが届いていれば成功です!
ちゃんとコマンドラインに標準出力されているのと同じ内容が届いていることを確認してください。

もしログインがブロックされた場合

なお、アカウントによってはPython経由でのGmailアカウントへのログインがブロックされることがあります。

間違って不審なアプリからのログインだと判断された場合、「ブロックされたログインについてご確認ください」という警告メールが届きます。

この場合、ログインを許可させるためには、同じメールの下の方の文章にある「安全性の低いアプリへのアクセスを許可」をクリックして、アプリからのログインを許可する必要があります。

しかし、これをするとGmailのセキュリティレベルが下がってしまいます。

そのため、個人的には「普段使いのメールアドレスを送信元アドレスに指定しない方がいい」と思います。つまりBOT稼働状況の通知用に新しい専用のメールアドレスを作った方がいいです。私はそうしました。

3.Windowsタスクスケジューラで自動実行する

さて、最後のステップです!
さきほど作成した「mail.py」をWindowsのタスクスケジューラに登録して、定期的に自動で実行して貰いましょう! 私は1日に4回ほど状況を教えて欲しいので、6時間おきに設定しています。

なお、これはWindowsの場合の手順です。一般のサーバーを使っている場合はcronの設定で同様のことができます。

1)タスクスケジューラを探す

WindowsVPSの方は、まず左下のメニューを右クリックして「検索」をクリックします。すると右上に検索窓が出ますので、「タスクスケジューラ」を検索します。普通のWindowsOSの方はスタートメニューから検索するだけです。

2)タスクスケジューラの設定

あとは基本的な流れは、別の記事「Windowsのタスクスケジューラを使ってpythonを定期的に自動実行しよう!」で解説したのと同じ内容なので、そちらを参考にしてください。

そちらを読んでいただく前提で、少し違うところだけ解説しておきます。

6時間おきの実行方法

Windowsタスクスケジューラーには、トリガーは「毎日」「毎週」「毎月」の選択肢しかなく、「6時間おき」というのは存在しません。そこで、このトリガーの画面では「1回限り」を選択します。時間は6時間後くらいを指定しておけばいいでしょう。

そして全てのタスクの登録作業が終わったら、左メニューの「タスク スケジューラライブラリ」から先ほど作成したタスクを探します。見つけたら選択してダブルクリックしてください。

すると以下のようなウィンドウが立ち上がると思うので、「トリガー」タブを選択して「編集」をクリックします。

そして詳細設定の箇所で、「繰り返し間隔」を6時間にし、「継続時間」を「無期限」に設定します。これで1回限りのトリガーのタスクを、6時間おきにずっと繰り返し実行することが可能になります。

まとめ

さて、これでWindowsVPSなどの外部サーバーでBOTを稼働したまま、外出していても、定期的に実行状況をメールで配信して貰うことができるようになりました。出先でもスマホで確認できるので便利です。

次回は、自動売買BOTがエントリーしたり決済をしたタイミング、またはBOT側で把握してるポジション情報と実際のポジション情報が一致しなくなった場合などのトラブル発生時にLINEでそれを通知する方法を解説します!

「BOTの稼働状況の監視(1)定期的にコマンドラインの標準出力をメールで受け取ろう!」への4件のフィードバック

  1. ryotaさん

    はじめまして
    いつも楽しく拝見させていただいております。
    そういえば、noteも公開されたんですね!
    ぜひ購入させていただこうと思います!

    ずっとお聞きしたいと思っていたのですが、ryotaさんは、BitFlyerFXの自動売買トレードにおける以下の問題をどのようにお考えですか?

    ※ noteの中で、この点に言及されていらっしゃるようでしたらご容赦下さい。

    ・SFDへの対応(フィルター、計算等)
    ・成行注文の大滑りへの対応

    1. 返信遅くなってすみませんでした….。m(__)m

      私はSFDは特にエントリーに際して気にしていません。チャネルブレイクアウトと関係のない理由で恣意的にシグナルを選別したくないからですが、本当は気にした方がいいかもしれません。成行注文はなるべくバックテストで考慮するようにしています。よろしくお願いします!

  2. 初めてコメントさせていただきます。
    サイトを参考に勉強させていただいております。
    上記のprint文をlogger.infoに書き換えるところですが、print_price(i)のところもlogger.infoに書き換えてよろしいですか。

    1. コメントが遅くなってごめんなさいm(__)m!
      print_price() 自体は書き換えないでください。print_price()の関数の中で呼び出している print文を、logger.info()に書き換えてください。

      # 時間と高値・安値・終値を表示する関数
      def print_price( data ):
      	logger.info( "時間: " + datetime.fromtimestamp(data["close_time"]).strftime('%Y/%m/%d %H:%M') + " 高値: " + str(data["high_price"]) + " 安値: " + str(data["low_price"]) + " 終値: " + str(data["close_price"]) )
      

      よろしくお願いします!

リョータ へ返信する コメントをキャンセル

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です