どんな記事
はじめて作成したBOTを、はじめてAWS Cloud9で稼働してみて気がついたことと、その対応のまとめ。長期間に渡って安定稼働するための注意点でもあると思います。今後も気がついたことがあれば追記していく予定です。
Cloud9の環境を python2.7 → python3.6
大まかな切り替えは以前の記事で完了していたが、alias
とupdate-alternatives
の変更も必要だった。以下の記事を参考に簡単に対応できた。
Pythonのaliasを変更
$ vi ~/.bashrc
alias python=python27
をalias python=python36
に変更する。$ source ~/.bashrc
update-alternativesの設定
そもそもupdate-alternatives
が何なのか。はじめて見た単語だったので調べてみた。ここでは、「類似のプログラムの内どちらを使用するかを指定する」ということをしているよう。
で、設定方法。
$ sudo update-alternatives --config python
バージョン別のPythonがいくつか表示されるので、/usr/bin/python3.6
の番号を入力する。
ta-libのインストール
AWS cloud9 ta-libのインストールでつまづいたが、「Cloud9でTA-Libをインストールする - Qiita」で紹介されているコードですんなり解決できた。
wget http://prdownloads.sourceforge.net/ta-lib/ta-lib-0.4.0-src.tar.gz tar -zxvf ta-lib-0.4.0-src.tar.gz cd ta-lib ./configure --prefix=/usr make sudo make install sudo bash -c "echo "/usr/local/lib64" >> /etc/ld.so.conf" sudo /sbin/ldconfig sudo pip install ta-lib
Google APIへの接続
数日間、実際に継続運用してみると、短期間のテストでは見えなかった問題がいくつか発生した。それぞれ以下の方法で対処し、現在は問題なく稼働している。
トークン のリフレッシュが必要に
修正後のコードも記録しておく。gs_client.login()
が該当のコード。
n = 0
while True :
try :
gs_client = gspread.authorize( self.google_credentials )
gs_sh = gs_client.open_by_key( self.spreadsheet_key ).worksheet( sh_name )
if self.google_credentials.access_token_expired :
gs_client.login()
return gs_sh.append_row( values ,'USER_ENTERED' )
except APIError as e :
self.__logger.error( f"SheetのAPIでエラー発生 : {e}" )
self.__logger.error( f"{2**n}秒待機してやり直します" )
time.sleep( 2 ** n )
n = n + 1
except RequestException as e :
self.__logger.error( f"Requestでエラー発生 : {e}" )
self.__logger.error( f"{2**n}秒待機してやり直します" )
time.sleep( 2 ** n )
n = n + 1
503エラーが発生 → APIの制限オーバー?
トークンリフレッシュが実装できて安心していたところ、今度は別のエラーが発生。以下が原因。
出典元: Search returns error "503: Service Unavailable" - Google Search Appliance Help
つまり、APIの制限をオーバーしているらしい。
- 500回/100秒 1プロジェクト(500 requests per 100 seconds per project)
- 100回/100秒 1ユーザー(100 requests per 100 seconds per user)
- 日ごとの制限はなし(There is no daily usage limit)
回数を減らして無事解決。
稼働状況の把握
当初は、Cloud9上でのprint
とGoogleシートのみで稼働状況を把握していたが以下に改善を行った。
稼働詳細:テキストのログ
- 方法:サーバーにテキストでログを残す
- 目的:正常に動作しているかの確認やエラー箇所の特定に使用する。大量にログを残しておく。
いくつかの記事を参考に、「Pythonのロギングを覚えた - Qiita」に掲載されているコードを組み込んだ。
def setup_logger(name, logfile='LOGFILENAME.txt'): logger = logging.getLogger(name) logger.setLevel(logging.DEBUG) # create file handler which logs even DEBUG messages fh = logging.FileHandler(logfile) fh.setLevel(logging.DEBUG) fh_formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(filename)s - %(name)s - %(funcName)s - %(message)s') fh.setFormatter(fh_formatter) # create console handler with a INFO log level ch = logging.StreamHandler() ch.setLevel(logging.INFO) ch_formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s', '%Y-%m-%d %H:%M:%S') ch.setFormatter(ch_formatter) # add the handlers to the logger logger.addHandler(fh) logger.addHandler(ch) return logger
# 各モジュールの最初のほうで、グローバルに宣言しておく logger = setup_logger(__name__)
以下は参考にした記事。
保留中の課題
大きな問題ではないが、以下が解決できていない。
- 課題:UTCの時刻でログに記録される
- 試したこと:Cloud9のタイムゾーンをUTC(国際標準時)からJST(日本標準時)へ変更
稼働概要:Discordに通知
- 方法:注文と売買をdiscordに通知
- 目的:サクッと動作を確認する。スマホにPush通 知がくるので便利。
「LINE通知よりも便利なDiscord通知を使ってみよう!|asim|note」で簡単に実装できた。Discordは他にも使えそう。便利。
# coding: utf-8 import requests def discord(message): # Discordで発行したWebhookのURLを入れる discord_webhook_url = 'あなたのWebhookURL' data = {"content": " " + message + " "} requests.post(discord_webhook_url, data=data) discord('discord通知テスト')
現在のポジションと損益:OANDA
- 方法:OANDA
- 目的:サクッと損益やチャート、トレード状況を確認する。
これは特に作業をしたわけではない。スマホにアプリを入れるだけ。この他にも、TradingViewのストラテジーでシグナルを確認できるようにもしている。
資金の推移と売買の履歴:Googleシート
- 方法:Googleシート
- 目的:統計を自動算出。損益の推移やトレード履歴を確認する。
元から実装していたもの。分析用。OANDAだけでは欲しい情報が記録されないので、OANDA+BOTの情報をすべて記録しておく。
システムのデーモン化
システムがエラーを吐かずに停止することが何回かあった。以下の仮説を立て、調査と対応を行った。
- 「Run」で起動していることが原因?(起動時間の制限みたいなものがあり、勝手に停止してしまう?)
- エラーを吐いていないだけで、エラーは起きている?
実際の原因は特定できなかったが、起動方法を変更とデーモン化で同様の問題は起こらなくなった。デーモン化とは、特定のプログラムを永続化すること。
再起動でBOT独自の情報を引き継ぐように
今回の対応を行うにあたって、OANDAに保存されないBOT独自の情報を引き継ぐロジックを作成した。と言っても複雑なことをしたわけではなく、情報を都度サーバーに保存し、再起動時に読み込む処理を作成した。
def __backup( self ,data ) :
data = str( data ).replace( 'nan' ,'np.nan' )
self.__ss_backup( str( { 'datetime': self.__get_now() ,'FLAGS': data } ) )
self.__txt_write( self.backup_file ,data )
def __load_backup( self ) :
s = self.__txt_read( self.backup_file )
if not s==False :
# 文字列をコードとして実行する
exec( f'self.FLAGS = {s}' )
self.__count_positions()
self.__is_init_on_tick = False
self.__logger.info('ポジションとオーダーを読み込みました')
else :
# 初回起動としてOANDA側の初期化
self.exit_all()
self.cancel_all()
self.__logger.info('情報がないため初回の起動として処理します')
def __on_tick_start( self ) :
if self.__is_init_on_tick : self.__load_backup()
シェルスクリプトでpythonファイルを複数回動かす
Linux系はまったく詳しくなくて、調べてはじめて知ったことが多い。シェルスクリプトとはターミナルで実行できるスクリプトのことで、組み方でPythonファイルの無限ループを設定することもできる。
具体的には、以下のコードをシェルスクリプトファイル(「YOUR_BOT.sh」など)として保存し、
while true; do
python ./YOUR_BOT_FILE.py; sleep 5;
echo "YOUR_BOT_FILE.py を再起動します"
done
以下のコードをターミナルで実行する。
$ ./YOUR_BOT.sh
すると、設定したPythonファイルが手動で停止するまで実行され続ける。
上記のサンプルは、直下に保存したファイルのみを対象とする。他の階層にあるファイルは別途パスを指定する必要がある。また、ターミナル上で「Ctrl+c」を2回押すことで停止することができる。
以下は参考にした記事。
サービスに登録する
前述のシェルスクリプトを用いた方法では、(おそらく)単一のプログラムにしか対応できない。「サービスへの登録」を行うことで複数のプログラムをデーモン化することができると理解している。(間違っているかもしれない)
今のところ単独のロジックしか回していないので対応は保留しているが、今後のために参考になりそうなリンクを残しておく。また、対応が完了したところで、この記事も更新したいと思う。
その他の対応方法
今回は実装しなかったが、以下のような方法もあるよう。
- 記事をシェア