シストレどうですか

  Algorithmic Trading for Dummies

FXボット(シストレ)作成編 ー エラーを通知する

前回の予告どおり、APIの解説もだんだんネタ切れになってきましたので、自動売買プログラムを作成するために必要な機能について考えていきたいと思います。 まずは、エラーが発生した場合に通知する機能について調べてみました。

稼働中のボットに何かよからぬ事態が発生した場合、本人に通知が行く機能があればとても便利です。バグやWeb API サーバー側の問題等でボットが停止した場合でもすばやい対処ができます。
(家に帰って確認してみたらとんでもない事になっていたりしたら大変です。だからといって会議中やデート中にちょろちょろ携帯をいじっていれば相手からの評価も⤵ばかりです。)

いきなり通知処理?!なんて思わずお付き合い下さい。
なぜと言われても深い意味は全然なく、初回のネタとしては簡単でまとめやすかったというこちらの都合のみです。

何を使って通知する?

簡単に通知できる機能を実装しようとした場合、大きく分けてEメールかチャットツール系しか選択肢がないと思われますのでこの二つについて調べてみました。

Eメール

いきなりですが、あまり詳しく書きません。次にでてくるチャットツール系のほうが簡単だからです。

理由としては、

  • 別の外部モジュール(smtplib)が必要になる。(チャットツールはOANDA APIと同じrequestsで処理が可能)
  • セキュリティレベルを下げる必要があったりする。(Gmailの場合)
  • 日本語が送れない。(もちろん送る方法もありますが)

Gmailでの設定方法・テスト

まず自分のGoogle IDに対して、 https://myaccount.google.com/lesssecureapps から安全性の低いアプリの許可を有効にしておきます。

f:id:jantzen:20210130091430j:plain

それから、以下のような送信用プログラムを使いました。

#外部モジュール
import smtplib
  
#定数変数の設定
USER = 'hiroshijantzen@gmail.com' #自分のメールIDに置き換える
PASSWORD = '********************' #自分のパスワードに置き換える
  
mail_sender = USER
mail_receiver = 'hiroshijantzen@gmail.com' #自分自身宛
mail_subject = 'test subject'
mail_body = 'test body'
mail_message = '''\
Subject: %s
%s
''' %(mail_subject, mail_body)
  
#メール送信処理
server = smtplib.SMTP_SSL('smtp.gmail.com', 465)
server.login(USER, PASSWORD)
server.sendmail(mail_sender, mail_receiver, mail_message)
server.close()

送信は無事成功しましたが、次にメールサブジェクトや本文を日本語に変えた後、再度実行するとエラーになってしまいました。
MIMEを使えば送信可能ですが、外部モジュールをさらに追加する事になり処理もさらに追加しないといけないし面倒くさいのでやめました。

チャットツール

次にチャットツールを使ったものでテストしてみました。
今回は日本ではお馴染みの「LINE」とゲーマーの間で人気のある(?)「Discord」の2種類で試してみました。

LINEはともかく、SNS系は若い人に聞いたほうがいろいろ知っているだろうという事で息子におススメを聞いたところDiscordは簡単で使いやすいと言われたのでただそれだけの理由で選んでみました。
LINEだと普段のコミュニケーションツールとして使っている方も多いので他のライン通知と混じってしまうのもうざいかなという意味もありますが。

LINE

LINEトークンの発行

まずはLINEもOANDA APIと同様にトークンの発行が必要ですのでまずはそれを取得します。

  1. https://notify-bot.line.me/ja/へアクセスしログイン
      ログイン後、本人確認をする。
      f:id:jantzen:20210203045557j:plain
  2. メイン画面から「マイページ」へ
      f:id:jantzen:20210203051039j:plain
  3. トークンを発行する」ボタンを押す
      f:id:jantzen:20210203051613j:plain:w250
  4. トークン名」に好きな名前をつけて、「1:1でLINE Notifyから通知を受け取る」を選び、「発行する」ボタンを押す。
      f:id:jantzen:20210203051616j:plain:w250
  5. 「コピー」ボタンを押して、トークンをどこかに保存しておく。
      f:id:jantzen:20210203052755j:plain:w250

これでトークンの取得は完了です。
送信するのに必要な情報は、URL”https://notify-api.line.me/api/notify”とこのトークンのみです。

テスト

以下のようなプログラムを作成し送信テストを行いました。

#外部モジュール
import requests
  
#定数の設定
LINE_NOTIFY_URL = 'https://notify-api.line.me/api/notify'
LINE_TOKEN = '~'   #👈自分で取得したトークンに置き換えてください。
  
#引数の設定
headers = {'Authorization': 'Bearer ' + LINE_TOKEN}
data = {'message': 'テスト'}
  
#送信
requests.post(LINE_NOTIFY_URL, headers = headers, data=data)

先程登録したLineトークン名と共にメッセージがLINE Notifyに届きました。

f:id:jantzen:20210130103553j:plain:w250

日本語も問題なく使えてとてもシンプルですね。

Discord

Discordではウェブフックが必要になりますが、Discordの登録方法も合わせて紹介しておきます。
(登録手順もいろいろ変わっているようなので)

アカウントの登録とウェブフックの取得

すでにアカウントを取得している人は、7.からで大丈夫です。

  1. https://discord.com/registerの「アカウント作成」画面から登録。
      登録画面に必要事項を記入し「はい」で次に進む。
      f:id:jantzen:20210203054634j:plain:w300
      登録したメールアドレスに認証用のメールが途中どこかの時点で届くようですが、しなくても次の手続きに進めてしまいましたので、受信次第認証してください。
  2. 「初めてのDiscordサーバーを登録する」画面から”オリジナル”を選択
      f:id:jantzen:20210203112203p:plain:w400
  3. 「Tell us more about your server」画面から「For me and my friends」を選ぶ。
      f:id:jantzen:20210203112246p:plain:w400
  4. 「会話をスタート」の画面では「スキップ」を押し、次へ進む。
      f:id:jantzen:20210203112255p:plain:w400
  5. 作成できたら「私をサーバーへ連れてって」を押す。
      f:id:jantzen:20210203112300p:plain:w400
  6. 「テキストチャンネルを作成」画面で「チャンネルの種類」で「テキストチャンネル」を選び「チャンネル名」に好きな名前を入力したら「チャンネルを作成」ボタンを押す。
      f:id:jantzen:20210203112304p:plain:w400
  7. サーバーのメイン画面のテキストチャンネルのエリアに先程作成したチャンネル名が表示されているので、それを選択すると設定用のギアが表示されるのでそれをクリックする。
      f:id:jantzen:20210203112309p:plain:w700
  8. 左側のメニューから「連携サービス」を選び、「ウェブフックの作成」ボタンを押す。
      f:id:jantzen:20210203120204p:plain:w700
      f:id:jantzen:20210203120209p:plain:w700
  9. 「お名前」を好きな名前に変更し、「ウエブフックURLをコピー」ボタンを押し、コピーしたウェブフックをペーストして保存する。変更した内容の保存も忘れずに。
      f:id:jantzen:20210203120214p:plain:w700

テスト

LINEの時と同じようなプログラムを作成し送信テストを行いました。

#外部モジュール
import requests
  
#定数の設定
DISCORD_URL =  '~’ #👈自分で取得したwebhookに置き換えてください。(https://discord.com/api/webhooks/で始まる)
 
#引数の設定
data = {'message': 'テスト'}
  
#送信
requests.post(DISCORD_URL, data=data)

同様にDiscordにもメッセージが届きました。

f:id:jantzen:20210204051116j:plain:w250

LINEもDiscordも簡単にメッセージが送信できることが確認できました。


応用例

これでボットからメッセージを送信する方法が理解できたと思います。
簡単な例として、OANDAから最新のレートを取得してそれをDiscordに通知するというプログラムを作成してみました。

#外部モジュール
import requests
    
def discord_notify(message):
    
        DISCORD_URL =  'https://~'  #自分で取得したwebhookにおきかえ
        data = {'content' :  message}
          
        try:
                #メッセージの送信
                response_body = requests.post(DISCORD_URL, data=data)
                response_body.raise_for_status()
          
        except Exception as e:
                #Discordに問題があった場合、ラインへ送信
                LINE_NOTIFY_URL = 'https://notify-api.line.me/api/notify'
                LINE_TOKEN = '99999999999999999999999999999999'  #取得したトークンコードにおきかえ
                headers = {'Authorization': 'Bearer ' + LINE_TOKEN}
                data = {'message': e}
                requests.post(LINE_NOTIFY_URL, headers = headers, data=data)        
  
def main():
  
        #OANDAアカウント情報 (自分の口座情報におきかえ)
        API_Token = '9999999999999999999999999999999999999999999999999999999999999999'
        API_AccountID = '999-999-99999999-999'
        API_URL =  "https://api-fxpractice.oanda.com"
          
        url = API_URL + "/v3/accounts/%s/pricing?instruments=USD_JPY" %str(API_AccountID)
        headers = {"Authorization" : "Bearer " + API_Token}
  
        try:        
                #最新レートの取得
                response_body = requests.get(url, headers=headers)
                response_body.raise_for_status()
                
                #ビッド・アスクレートの取得
                rate_check = response_body.json()['prices']
                new_bid = rate_check[0]["bids"][0]["price"]
                new_ask = rate_check[0]["asks"][0]["price"]
        
                #レート情報の送信
                discord_notify('bid: %s \n ask: %s' %(new_bid, new_ask))
  
        except Exception as e:
                #エラー情報の送信
                discord_notify('エラーだよ!\n内容: %s' %e)
  
if __name__ == "__main__":
  
        main()

これをこのまま実行した場合とわざと通貨名を間違えてタイプ(USD_JPY→USD_JPX)した場合と両方で試したところ、以下のようなメッセージがDiscordに届きました。

f:id:jantzen:20210204095014p:plain:w250

Discordの送信が失敗した場合はLINEにそのエラーを送信する仕様にしてあります。(両方いれたかったので)
またテスト用なのですべての処理をひとまとめにしていますが、通知の部分は必要に応じて自分用の機能を追加するなりして外部モジュール化しておけば便利ですね。


まとめ

ボットになにか異変があった場合に通知する簡単な方法について試してみました。
本格的なプログラマーでもない限り、いろいろな障害やバグに対応できるような機能を組み込んだプログラムを作成する事もなかなか大変ですし、 何かあった場合にあとは手動でなんとかするという仕組みがあるだけでも安心です。
もちろんエラーの通知だけでなく、発注や手仕舞い等の取引の情報を通知する事も出来ますので、会議中でもデート中でも何かあった場合は、すぐトイレに駆け込んでなんとかして下さい。
ボットが稼働しているサーバーに何かあった場合等は通知できずに停止してしまう状況も考えれられますが、その場合はプロセスやサーバー本体を監視させるような別の対策も何か必要になります。 (また寝ている間はどうするのかも考えたほうがいいかもしれませんね。目覚まし時計ならすとか・・・🤔)

Discordは、discord.pyというラッパーも用意されているようですので、逆にDiscordからボットに対して何か情報を送信することも簡単にできそうです。
このような仕組みを取り込んでいければ、双方向に会話できるのようになるのでよりボットらしくなります。