GUIで表示できるとヘタレなシステムでもカッコよく見えますよね!たっきん(Twitter)です!
前回は状態遷移表を作成して注文情報を管理する「order_manager」ノードの作りこみを行いました。
しかし、注文を出せるだけではシステムトレードとしては成り立ちません。
注文を出すには当然ながら注文を出すための判断材料が必要になります。
なので今回はその判断材料になるためのベースとなるローソク足データの取得、管理、そしてモニターするためのノード群の作りこみを行っていきます。
具体的には、以下3つのノードになります。
- candlestick_serviceノード
- candlestick_managerノード
- gui_monitorノード
<シストレ・アーキテクチャ ver.0.3>
ローソク足データを取得するノード[candlestick_service]
OANDAにはローソク足データを取得するためのAPIとしてInstrumentsCandlesがありますが、このAPIをROSノード化したのが「candlestick_manager」ノードとなります。
このノードとのROS通信はServiceで行います。
現時点ではローソク足データ取得用Service「candles」しかありませんが、後々オープンオーダーとオープンポジションを取得するためのService「order_book」を追加していく予定です。
ちなみに、ローソク足データ取得用Service「candles」の入出力仕様はこんな感じ。
<candles Service入出力仕様>
・Request
パラメータ名 | 用途 |
---|---|
gran_msg | 時間足(日足、1時間足、1分足など) |
inst_msg | 通貨ペア(USD/JPY、EUR/JPY、EUR/USDなど) |
dt_from | ローソク足データ取得開始日時 |
dt_to | ローソク足データ取得終了日時 |
・Response
パラメータ名 | 用途 |
---|---|
result | Service処理結果(成功 or 失敗) |
frc_msg | Service処理失敗時の理由コード |
cndl_msg_list | ローソク足データのリスト |
ついでに言っておくとOANDA APIのInstrumentsCandlesには取得可能なデータ数の上限が5000件までとなっていますが、ROSノード化するときにデータ数が5000件を超える場合は分割して取得できるように作っておいたので実質無制限のデータ件数を取得できます。
当然ながらOANDAのサーバー側が過去いつまでのデータを保持しているかによって限度はありますけどね。
ローソク足データを管理するノード[candlestick_manager]
「candlestick_manager」はその名の通りローソク足データを管理するノードです。
1分足、1時間足、日足などの各時間足毎のローソク足データを過去数年分保持し、チャートの傾向を解析するための機能を持たせようと考えています。
現時点ではまだ実装できていませんが、このノードにも後々オープンオーダー/オープンポジションのデータの管理、解析機能を追加していく予定です。
ローソク足データをモニターするノード[gui_monitor]
「candlestick_manager」でローソク足データを管理し解析したとしても、その結果が目に見える形で出てこないとちゃんと解析できているのかわからないですよね!
そう思って急遽ローソク足データや解析結果を可視化してモニターするためのノード「gui_monitor」を作ることにしました。
このノードはアーキテクチャ設計 ver.0.3で新規に追加したノードになります。
そして、このノードの役割ですがモニター以外の機能は持たせません。
そうすることによってシストレ本体との依存関係をなくし、このノードを起動しなくてもシストレ本体のシステムを稼働できるようにしたいからです。
また、モニターするわけなので当然ながらGUIツールを使う必要があります。
今回使用したGUIツールはQtです。
Qtを選んだ理由はネット上でROSとQtを連携させて動かくことに成功した人がいるからです。
実績ってやっぱ大事ですよね!
ROSとQtを連携させる上での課題
ROSのTopic通信であるSubscriber、Service通信、タイマー割り込み処理は「rclpy.spin()」が実行されていないと、コールバック関数が呼ばれなくなりプロセスが停止してしまいます。
しかし、この関数を実行すると他の処理がブロックされてしまい、Qt側のウィンドウ更新等のプロセスが全て停止してしまいます。
一方、Qt側にもウィンドウを更新するためのコールバック関数を呼び出すためには「app.exec_()」を実行する必要がありますが、こちらもROS同様に他の処理をブロックしてしまい、今度はROS側のプロセスが停止してしまいます。
この問題についてネット上で解決策を探したところ、ノンブロッキングで処理できる「rclpy.spin_once()」、「app.processEvent()」を使えばいいらしい。
参考 [技ラボ]ROS2 + Qt Quickを使ったシステム開発
ROS | Qt | |
---|---|---|
ブロッキング | rclpy.spin() | app.exec_() |
ノンブロッキング | rclpy.spin_once() | app.processEvent() |
これら2つのノンブロッキング関数を同じWhileループ内で呼び出すことでROSとQtの両方のプロセスが実行できるらしい。
実装例はこんな感じ。
while rclpy.ok():
rclpy.spin_once(node)
app.processEvent()
sleep()
まぁこれでも良いとは思うのですが、僕個人的にはあまりwhileを使いたくありません。
理由は処理負荷が増えるからです。
実装例のようにsleepを挟むことで処理負荷を減らせますが、その代償としてプロセスにディレイが発生してしまいます。
そこで僕が考えたのが単純にブロッキング処理の「rclpy.spin()」と「app.exec_()」をマルチスレッド処理させればいけるんじゃないか説。
物は試しでさっそく実装してみたところ、ROSとQtが互いのプロセスをブロックすることなく上手く動いた。
どうやら僕の説は間違っていなかったようですね!
しかし、マルチスレッド処理する上で気を付けなければならないのが排他制御を怠らないこと。
これは、一度バグりだすと解析が困難になることもあり厄介です。
今回作成している「gui_monitor」はROSで受信してQtで表示させるだけのシンプルな処理にしようと思っているため、排他制御も複雑にはならないと考えマルチスレッド処理させる案でいきます。
実装例はこんな感じ。
import threading
ros_th = threading.Thread(target=rclpy.spin, args=(node,))
ros_th.start()
app.exec_()
動作確認
それでは今回作成した、3つのノードを動かしてみましょう!
「gui_monitor」ノードは起動した時点でウィンドウが立ち上がります。
現時点ではまだ開発途中となっているため、日足、4時間足、1時間足のローソク足チャートしか表示できませんが逐次他の機能も追加していきます。
さいごに
今回は、QtというGUIツールをROS2と連携して動かせるかどうかを確認したところ、無事に動かすことに成功しましました。
やっぱりGUIがあると見た目が良くなって開発してる感が出ますよね!
今後はこのGUIにオープンオーダー、オープンポジション、テクニカル解析結果の表示機能も追加していく予定です。
シストレ開発の進捗もいい感じで来ていますので、迅速かつ丁寧に作りこんでいきたいと思います!
また、今回作成したノードのコードはGitHubに上げてますので興味あったら覗いてみてください。
GitHub candlestick_serviceノード:candlestick_service.py
GitHub candlestick_managerノード:candlestick_manager.py
GitHub gui_monitorノード:gui_monitor.py
コメント