見出し画像

エクセル+楽天RSS2システムトレード、サーバ通信切断エラーからの自動復帰

自動売買システムとしてエクセル+楽天RSS2を使っている。
原因不明のサーバ通信切断エラーで稀に(ただ、起きるときは続けて起きる)システムが勝手に停止してしまう問題がある。
(楽天RSS2のWEBページに対処方法が書かれているが、特に効果がなかった)
もし相場の大事な局面で自動トレードが停止していると困ることになるので、なんとか自動復帰できないか検討した。

結果、エクセルVBAマクロとPythonを組合わせ、一応、自動復帰できる実装が出来たので報告します。ただし、エクセル、VBA、Pythonの通信にはハンドシェイクがなく、時間待ち(wait)を使って一つの処理が終わったであろう時間後に次の動作を実行するような作りなので、たまに予期せぬ状態に陥って、復帰できない場合があります。

なお、私はプログラマーではないのと、VBAも最近触り始めたばかり、ましてやPythonは先々週初めて触った、という素人なので、頓珍漢なことをしていたらご容赦いただくとともに、ぜひ改善案を教えてください。
なお、この情報によって何か損失が生じても一切の責任は負いかねますので、もし参考にされる方がいらっしゃる場合は十分にご確認をお願いします。

■コンセプト
・できるだけVBAマクロを使って構築
・足りないところをPythonで補う
・ChatGPTやネット情報を頼りにする

■VBAマクロの仕事
・エクセルのセルに展開されている4本値データをクリアする
・RSS2と接続し、新たに4本値データを読込みなおす
・もし正しく読み込めない場合は、再度読込み直す
・4本値データが正しく読込めたら、「発注可」に切替える

■Pythonの仕事
・マーケットスピード2(以下MS2)からサーバ通信切断エラーのポップアップが出力されたら、ポップアップのOKを押す
・MS2にログインする
・上記VBAマクロを起動する
・再び切断エラーが起きるまで待つ

以下、詳細について。

■最初にエクセルシートの簡単な説明。(なお、このシートはRSS2で日経先物ミニの4本値データを取得するだけのサンプルです。実際のトレードシステムでは、4本値データを元に売買サインを計算し、RSS2で自動注文するようになっています。)

楽天RSS2の説明は省くが、下記のように4本値データが自動的に取得できる。

最終行をセルC2に表示している。これはRSS2が正常に4本値を正しく読込めた証拠となる大事な部分。
RSS2は時々4本値を正常に読込めないことがある。特に、最後の”--------"が出ないことや、読込位置がズレることなど。
その場合、最終行の数値が正常時の数値とは異なるため、正常な数値と比較することで読込正常を判断できる。

RSS2エクセルシートサンプル

RSS2の『接続中』ボタンと『発注可』ボタンをエクセルVBAで押すために、クイックアクセスツールバーに登録する必要がある。
ここに登録すると、ALT+数字(上から1,2,3・・・)をキーボードから入力することで、実行できるようになるらしい。
下記は登録済みの状態。『接続中』ボタンは”ALT+5”、『発注可』ボタンは”ALT+6"で押すことが出来るようになる。これで、Pythonを使わずとも、VBAのコードでRSS2の状態を変更できるようにできた。

クイックアクセスツールバー設定

シートの中央上部にある、赤文字『STARTUP』がVBAマクロ起動ボタン。『DUMMY』ボタンは後述する理由でPythonがエクセルシートをアクティブにするために押すダミーボタン。

もう一つ重要なことがある。楽天RSS2の仕組みでは、エクセルシートを閉じると発注IDがリセット?される。エクセルシートをいったん閉じてしまうと、同じ発注IDが使えてしまうので、2重発注が発生してしまう。
なので、今回の処理では、エクセルを閉じないで処理するようにしている。

■VBAマクロの処理内容は上述の「VBAマクロの仕事」に記載の通り。
コードは以下の通り。詳しくはコメントを参照。

(大したことのない)プログラムが、わざわざ複数のブロックに分かれているのは、VBAで単純にタイミング調整のためにウェイト入れると(例えば、Application.Wait Now() + TimeValue("00:00:05"))、RSS2の動き自体が止まってしまい、4本値データの更新や自動発注が止まってしまうため。そこで、Application.OnTimeを使ってRSS2を止めずにウェイトかける。

Dim lastline As Integer  '4本値データ読込み最終行
Dim ScheduleTime 'waitタイマー用


'マクロスタートポイント
Sub startup()
    
    '4本値データを読込むセルをクリアする
    Worksheets("sheet1").Range("A6:G65535").Clear
    
    'waitする
    ScheduleTime = Now() + TimeValue("00:00:05")
    Application.OnTime ScheduleTime, "main"
    
End Sub

Sub main()
    'ALT+5を送信しRSS2を「接続中」に切替える
    SendKeys "%{5}"

    'waitする
    ScheduleTime = Now() + TimeValue("00:00:10")
    Application.OnTime ScheduleTime, "main2"

End Sub



Sub main2()
    '最終行数をlastlineに読み込む
    lastline = Worksheets("sheet1").Range("C2").Value
    
    'lastlineの正常値は今回は35、正しければ
    If lastline = 35 Then
        
        'ALT+6を送信しRSS2を「発注可」に切替える
        SendKeys "%{6}", True
        
        'ポップアップメッセージにエンターでOKする
        SendKeys "{ENTER}"
        
    Else
        
        'lastlineが正常値ではない場合、再度RSS2に接続し直す
        SendKeys "%{5}"
        
        'startupに戻って切断エラーを待つ
        ScheduleTime = Now() + TimeValue("00:00:10")
        Application.OnTime ScheduleTime, "startup"
            
    End If

End Sub



'エクセルシートをアクティブにするためにPythonが押すダミーボタン用マクロ
Sub dummy()

End Sub


■Pythonプログラムの内容
処理内容は上述した『Pythonの仕事』に記載した通り。
先々週初めてPythonに触って驚いたのは、GUIを操作するにあたって、画像認識でGUIのパーツを認識し、アクション出来るライブラリ(pyautogui)があること。画像認識技術がこんな風に使われるなんて驚き。

なお、ChatGPTさんにインストールからコードの中身までいろいろ教えてもらって作成した。ChatGPTはホントスゴイ。利用禁止とか、半年開発停止とか言い出す人がいるのもわからなくもない。多くの仕事がなくなりそう、あるいは、ターミネータのスカイネットみたいになっちゃうとか(笑。法的なことも含めて、きちんと評価できる状況にならないとマズそうということか。

GUIを画像認識で操作するための認識用画像は、画面キャプチャで作成し、カレントディレクトリに置いておく。(〇〇.png)

例えば、エラーメッセージのポップアップ。このキャプチャ画像は実際に使うPCでキャプチャし、アプリが出すポップアップと画素サイズを合わせないと動かなかった。最初4Kのデスクトップで作成したプログラムと画像を24時間運転のノートPC(HD画面)に持っていったらダメだった。

また、画像認識精度を設定する"confidence="だが、0.9以上とかが推奨らしいが、精度を0.7とかに落とさないとうまく認識できなかった。

サーバエラーポップアップキャプチャ画像

コードは後述のようにした。注意点としては、パスワードが平文で書込まれているので流出に注意。

import win32gui
import win32con
import win32api
import pyautogui
import time


#サブルーチン エクセルファイルを最前面に切替え、最前面表示固定を解除
def foreground():
    hwnd = win32gui.FindWindow(None, "システムトレードサンプル.xlsm - Excel")
    win32gui.SetWindowPos(hwnd,win32con.HWND_TOPMOST,0,0,0,0,win32con.SWP_NOMOVE | win32con.SWP_NOSIZE)

    left, top, right, bottom = win32gui.GetWindowRect(hwnd)
    pyautogui.moveTo(left+60, top + 10)
    pyautogui.click()


    # MS2が接続エラーした場合、ポップアップが出るよう最前面固定の解除
    win32gui.SetWindowPos(hwnd, win32con.HWND_NOTOPMOST, 0,0, 0, 0, win32con.SWP_NOMOVE | win32con.SWP_NOSIZE)


#メイン処理
while True:
    # エラーメッセージボックスが出るまで待つ
    while not pyautogui.locateOnScreen('.\ms2_error_message_box.png', confidence=0.7):
        time.sleep(1)

    # エラーメッセージボックスが見つかったら、OKボタンをクリックする
    ok_button_location = pyautogui.locateOnScreen('.\ms2_ok_button.png', confidence=0.7)
    if ok_button_location:
        pyautogui.click(ok_button_location)

        # ログイン画面が表示されるまで待つ
        while not pyautogui.locateOnScreen('.\ms2_login.png', confidence=0.7):
            time.sleep(1)

        # パスワードを入力してログインする
        password_location = pyautogui.locateOnScreen('.\ms2_password_field.png', confidence=0.7)
        if password_location:
            pyautogui.click(password_location)

            #ここにパスワードを平文で記載
            pyautogui.write('password')

            pyautogui.press('enter')

        #MS2の画面が出そろうまで待つ
        time.sleep(10)
	
	# エクセルをフォアグラウンドに切り替える
        foreground()


        # 念のためエクセルのDUMMYを押してフォアグラウンドを再設定
        dummy_button_location = pyautogui.locateOnScreen('.\dummy.png', confidence=0.7)
        time.sleep(10)
        if dummy_button_location:
            print("dummy")
            pyautogui.click(dummy_button_location,button='left',clicks=1)

        # エクセルのSTARTUPを押す
        startup_button_location = pyautogui.locateOnScreen('.\startup.png', confidence=0.7)
        time.sleep(10)
        if startup_button_location:
            print("startup")
            pyautogui.click(startup_button_location,button='left',clicks=1)

        # エクセルのDUMMYを押してPythonコマンドプロンプトからエクセルにフォアグラウンドを移動
        dummy_button_location = pyautogui.locateOnScreen('.\dummy.png', confidence=0.7)
        time.sleep(10)
        if dummy_button_location:
            print("dummy")
            pyautogui.click(dummy_button_location,button='left',clicks=1)

■全体でハマったところ
何分、VBAもPythonも素人なのでいろいろとハマった。
特に重要な部分として、PythonのpyautoguiでWindowsアプリを操作する場合、当然だが、操作したいアプリが画面上で見えていなければならない。
別のアプリの後ろに隠れていると、GUIが操作できないので当然といえば当然。

なので、エクセルのマクロ実行ボタンを認識するために、エクセルを最前面に出す必要がある。しかし、ネットで見つけたサンプルコードでは、エクセルが最前面に出た後、最前面で固定されてしまい、MS2がエラーメッセージ出した際に、ポップアップが画面に出てこない問題があった。
なので、エクセルを最前面に出した後、最前面固定を解除する方法をとった。

しかし、その状態でコマンドプロンプトで実行したPythonプログラムが動くと、Windowsのフォーカスがコマンドプロンプトに移ってしまい、エクセルのVBAマクロがRSS2を『接続中』や『発注可』に切替えるための、"ALT+5"などのクイックアクセスツールバーのコマンドがエクセル自身に入らず、コマンドプロンプトに入力されてしまう問題が発生。回避手段として、エクセルにダミーのボタンを設置し、そこをPythonで押すことで、フォーカスをエクセルに戻す方法をとった。今考えると、Pythonのプログラムをバックグラウンドで実行し、コマンドプロンプトにフォーカスが移らないようにすれば良いのかもしれない。(動作確認のための、画面表示をやめればよいのかも)

■未解決部分
最初に書いた通り、エクセル、VBAマクロ、Pythonの間の通信はハンドシェイクがない(ackなし?)ので、一定のwait時間を空けて動いたものとして処理が進むようになっている。なので、想定wait時間を超えて処理が進まなかったりすると、状態遷移がおかしくなってしまう。例えば、VBAマクロが接続と切断を繰り返すようになったりとか。

もう一つ、MS2が出すサーバ接続エラーのポップアップだが、なぜか最前面に表示されなくなるモードが存在する。その場合、当然自動復帰できなくなる。これに関しては、エクセルの画面を小さくして、MS2のポップアップが隠れないような配置にあらかじめしておくことで対処可能。
(ほかのアプリのポップアップが出ちゃうとダメなのですが)

とりあえず以上です。







いいなと思ったら応援しよう!