見出し画像

停止しない自動売買ボットの作り方。python 3

自動売買ボットはいろいろな原因(バグ、API変更、サーバーリソースなど)で良く停止しますが、停止したまま気づかないと、大きく損失したりします。

python3で停止しないボットの作り方です。

方針

ヘルスチェックを使います。一定時間ごとにボットの状態が正常であることを報告し、一定時間報告がなければ再起動させます。正常かどうかは、データが最新か、最後にループが回った時刻が最近か、などで判断します。

python3 実装コード

import threading
import sys
import os
import traceback
import time
import psutil

class PanicManager:
   def __init__(self, logger=None):
       self.monitors = {}
       self.logger = logger
       self.lock = threading.Lock()
       self.thread = threading.Thread(target=self.run)
       self.thread.start()

   def register(self, tag=None, start_time=None, interval=None):
       self.logger.debug('panic_manager register tag {} start_time {} sec interval {} sec'.format(tag, start_time, interval))
       with self.lock:
           self.monitors[tag] = {
               'start_at': time.time(),
               'ping_at': None,
               'start_time': start_time,
               'interval': interval
           }

   def ping(self, tag=None):
       with self.lock:
           self.monitors[tag]['ping_at'] = time.time()

   def panic(self):
       restart_program(logger=self.logger)

   def run(self):
       while True:
           self.logger.debug('panic_manager loop')
           now = time.time()
           with self.lock:
               for tag in self.monitors:
                   monitor = self.monitors[tag]
                   if monitor['ping_at']:
                       if now - monitor['ping_at'] > monitor['interval']:
                           self.logger.error('{} ping delayed. restarting'.format(tag))
                           self.panic()
                   else:
                       if now - monitor['start_at'] > monitor['start_time']:
                           self.logger.error('{} start delayed. restarting'.format(tag))
                           self.panic()
           time.sleep(5)

# https://stackoverflow.com/questions/11329917/restart-python-script-from-within-itself
def restart_program(logger=None):
   try:
       p = psutil.Process(os.getpid())
       for f in p.open_files() + p.connections():
           os.close(f.fd)
   except Exception as e:
       logger.error('exception ' + traceback.format_exc())
   python = sys.executable
   os.execl(python, python, *sys.argv)

使い方

# 初期化
# start_time: 最初の報告までの猶予(秒)
# interval: 2回目以降報告までの猶予(秒)
# tag: 監視対象を表す任意のtag

self.panic_manager = PanicManager(logger=logger)
self.panic_manager.register(tag='trader', start_time=5 * 60, interval=60)
# 定期的に生存報告
# ok: 正常かどうか。ボットごとにカスタマイズ

if ok:
    self.panic_manager.ping('trader')

注意

restart_programはなんかハンドルが閉じていないっぽいです。数ヶ月動かして、再起動を何度も繰り返すと、linuxのハンドル制限にひっかかり、再起動できなくなります。

ライセンス

記事中ソースコードのライセンスはCC0