2025-11-20

自動売買への道 (2025-11-20)

楽天証券の口座でデイトレの自動売買に挑戦しようと Windows / Excel 上で利用できる マーケットスピード II RSS を活用して Python であれこれ取り組んでいます。この「自動売買への道」のトピックでは、プログラミングの話題にも踏み込んで、日々の活動をまとめていきます。

今日の日経平均株価

現在値 49,823.94 +1,286.24 +2.65% 15:30
前日終値 48,537.70 11/19 高値 50,574.82 09:28
始値 49,129.29 09:00 安値 49,113.39 09:00

※ 右の 15 分足チャートは Yahoo! Finance のデータを yfinance で取得して作成しました。

【関連ニュース】

デイトレ用自作アプリ

以下は株価に関連する情報の流れを示しています。

株価データの流れ(Windows 11)

楽天証券では、Python からネットワーク越しに直接取引できるような API が提供されていないので、マーケットスピード II RSS を介して取引をする構成を取っています。

強化学習

現在は、強化学習を利用した取引モデルの開発に取り組んでいます。

強化学習システムの概要

リアルタイム利用を想定したシミュレーション

取引モデルの改良はまだまだなのですが、そろそろリアルタイム利用を想定した推論シミュレーションができるようにしておきたいと考え、朝から評価システムを作り始めました。

最小限の機能を実装した推論マシンのシミュレーション

別スレッドで推論処理ができるようにエージェント(WorkerAgent クラス)を準備していたのですが、いざ実装しようとしたところ、不備がたくさん見つかりました。そのため、そちらの修正に手間取ってしまって、GUI 側は最小限の機能しか実装できず、推論結果も今のところ標準出力のみです。明日以降、実装を続けます。

今日のティックデータの事後推論

昨夜、過去のティックデータ全てで学習させたモデル ppo_7011_20251119.zip で、今日取得したティックデータ ticks_20251120.xlsx から 7011(三菱重工業)の分を読み込んで推論させました)。

推論結果の出力例
下記の条件で推論を実施します。
モデル          : models\trained\ppo_7011_20251119.zip
ティックデータ  : collection\ticks_20251120.xlsx
銘柄コード      : 7011

Excel ファイルをデータフレームに読み込みました。
               Time  Price    Volume
0      1.763597e+09   4050   1128700
1      1.763597e+09   4050   1128700
2      1.763597e+09   4041   1194900
3      1.763597e+09   4041   1194900
4      1.763597e+09   4040   1236600
...             ...    ...       ...
19245  1.763620e+09   4112  24645800
19246  1.763620e+09   4113  24647700
19247  1.763620e+09   4113  24647700
19248  1.763620e+09   4115  24651300
19249  1.763620e+09   4115  24651300

[19250 rows x 3 columns]

スレッド内にワーカーエージェントを生成します。
2025-11-20 15:46:17,205 - INFO - modules.agent: model, models\trained\ppo_7011_20251119.zip is used.
Wrapping the env with a `Monitor` wrapper
Wrapping the env in a DummyVecEnv.

環境がリセットされました。
推論ループを開始します。

推論ループを終了しました。
計測時間 :                      5.022 sec
ティック数 :                    3,751 ticks
処理時間 / ティック :   1.339 msec

取引明細
                   注文日時 銘柄コード  売買    約定単価  約定数量   損益
0   2025-11-20 09:04:01  7011  売建  4042.0   1.0  NaN
1   2025-11-20 09:15:15  7011  買埋  4040.0   1.0  2.0
2   2025-11-20 09:15:22  7011  買建  4038.0   1.0  NaN
3   2025-11-20 09:15:28  7011  売埋  4038.0   1.0  0.0
4   2025-11-20 09:15:30  7011  買建  4040.0   1.0  NaN
..                  ...   ...  ..     ...   ...  ...
95  2025-11-20 10:05:40  7011  売埋  4082.0   1.0  0.0
96  2025-11-20 10:05:41  7011  売建  4082.0   1.0  NaN
97  2025-11-20 10:05:42  7011  買埋  4082.0   1.0  0.0
98  2025-11-20 10:05:44  7011  買建  4082.0   1.0  NaN
99  2025-11-20 10:05:46  7011  売埋  4082.0   1.0  0.0

[100 rows x 6 columns]
一株当りの損益 : 29.0 円

今回、1 ティックあたりの推論時間がどのぐらいになるかを、推論用に利用する予定のターゲット PC(CPU は Intel N150)で確認できたことが収穫でした。

LLM(大規模言語モデル)などと違って、そもそもパラメータ数は僅かなので、非力な PC でもなんとかなるはずだと思っていましたが、スレッド間の通信込みで 1 回のアクションを推論するのに 1.3 msec 程度で済んでいるのであればなんとか使えそうです。

もしも、100 msec を超えるようなパフォーマンスしか出なければ、もう少しパワーがある(消費電力も大きい)PC の購入を検討しなければなりませんでした。この結果は開発用に使用しているパワーがある PC より 1 桁大きい程度なので上出来でしょう。

パラメータ数は増える傾向にあるので、継続的にパフォーマンスをモニターしていく予定です。

PySide6 の QThread によるスレッド処理

GUI のライブラリに PySide6 (Qt for Python) を利用しているので、スレッド処理も全面的に Qt が提供している QThread クラスを利用しています。GUI としては、分析用のチャートがなにひとつできていない状況ですが、PySide6 的なスレッド・プログラミングの例を備忘録として残しました。

なお、実践では取引時間が終われば終了するアプリなので、最初にスレッド化して終了時に破棄するような作りで間に合います。しかし、評価用には何回も推論させたり、異なるティックデータを試したりするので、その度にスレッドを作って、推論後に破棄するようにしました。

【参考】別スレッドに推論専用エージェントを実装した例
class Prophet(QMainWindow):
    __app_name__ = "Prophet"
    __version__ = "0.0.2"
    __author__ = "Fuhito Suguri"
    __license__ = "MIT"

    requestResetEnv = Signal()
    requestPostProcs = Signal()
    sendTradeData = Signal(float, float, float)

    def __init__(self):
        super().__init__()
        self.logger = logging.getLogger(__name__)  # モジュール固有のロガーを取得
        self.res = res = AppRes()
        self.df = None
        self.row = 0
        self.t_start = 0

        self.setWindowIcon(QIcon(os.path.join(res.dir_image, "inference.png")))
        title_win = f"{self.__app_name__} - {self.__version__}"
        self.setWindowTitle(title_win)

        self.toolbar = toolbar = ToolBarProphet(res)
        toolbar.clickedPlay.connect(self.on_start)
        self.addToolBar(toolbar)

        # _/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_
        # 強化学習モデル用スレッド
        self.thread = None
        self.worker = None

    def finished_trading(self):
        t_end = perf_counter()  # ループ終了時刻
        t_delta = t_end - self.t_start
        print("\n推論ループを終了しました。")
        print(f"計測時間 :\t\t\t{t_delta:,.3f} sec")
        print(f"ティック数 :\t\t\t{self.row - 1 :,d} ticks")
        print(f"処理時間 / ティック :\t{t_delta / (self.row - 1) * 1_000:.3f} msec")

        # 後処理をリクエスト
        self.requestPostProcs.emit()

    def on_start(self):
        """
        スタートボタンがクリックされた時の処理
        :return:
        """
        # 選択されたモデルと過去ティックデータ、
        # およびモデルに紐づく銘柄コードを取得
        dict_info = self.toolbar.getInfo()
        path_model: str = dict_info["path_model"]
        path_excel: str = dict_info["path_excel"]
        code: str = dict_info["code"]

        print("\n下記の条件で推論を実施します。")
        print(f"モデル\t\t: {path_model}")
        print(f"ティックデータ\t: {path_excel}")
        print(f"銘柄コード\t: {code}")

        # Excel ファイルをデータフレームに読み込む
        self.df = get_excel_sheet(path_excel, code)
        print("\nExcel ファイルをデータフレームに読み込みました。")
        print(self.df)

        # スレッドの開始
        print("\nスレッド内にワーカーエージェントを生成します。")
        self.start_thread(path_model)

        # エージェント環境のリセット
        self.requestResetEnv.emit()

    def post_process(self, dict_result: dict):
        """
        推論後の処理
        :param dict_result:
        :return:
        """
        print("\n取引明細")
        df_transaction: pd.DataFrame = dict_result["transaction"]
        print(df_transaction)
        print(f"一株当りの損益 : {df_transaction['損益'].sum()} 円")

        # スレッドの終了
        self.stop_thread()

    def send_first_tick(self):
        """
        環境をリセットした後の最初のティックデータ送信
        :return:
        """
        print("\n環境がリセットされました。")
        self.row = 0
        print("推論ループを開始します。")
        self.t_start = perf_counter()  # ループ開始時刻
        self.send_one_tick()

    def send_one_tick(self):
        """
        ひとつずつティックデータを送ってリアルタイムをシミュレート
        :return:
        """
        # データフレームからティックデータを1セット取得
        ts = float(self.df["Time"].iloc[self.row])
        price = float(self.df["Price"].iloc[self.row])
        volume = float(self.df["Volume"].iloc[self.row])
        # エージェントにティックデータを送る
        self.sendTradeData.emit(ts, price, volume)
        # 行位置をインクリメント
        self.row += 1
        # 行位置が最後であれば終了(最後の行は使わない)
        if self.row >= len(self.df):
            self.finished_trading()

    def start_thread(self, path_model: str):
        """
        スレッドの開始
        :param path_model:
        :return:
        """
        self.thread = QThread(self)
        self.worker = WorkerAgent(path_model, True)
        self.worker.moveToThread(self.thread)

        self.requestResetEnv.connect(self.worker.resetEnv)
        self.requestPostProcs.connect(self.worker.postProcs)
        self.sendTradeData.connect(self.worker.addData)

        self.worker.completedResetEnv.connect(self.send_first_tick)
        self.worker.completedTrading.connect(self.finished_trading)
        self.worker.readyNext.connect(self.send_one_tick)
        self.worker.sendResults.connect(self.post_process)
        
        self.thread.start()

    def stop_thread(self):
        """
        スレッド終了
        :return:
        """
        self.requestResetEnv.disconnect()
        self.requestPostProcs.disconnect()
        self.sendTradeData.disconnect()

        self.thread.quit()
        self.thread.wait()

        if self.worker is not None:
            self.worker.deleteLater()
            self.worker = None

        if self.thread is not None:
            self.thread.deleteLater()
            self.thread = None

参考サイト

  1. マーケットスピード II RSS | 楽天証券のトレーディングツール
  2. マーケットスピード II RSS 関数マニュアル
  3. 注文 | マーケットスピード II RSS オンラインヘルプ | 楽天証券のトレーディングツール
  4. Gymnasium Documentation
  5. Stable-Baselines3 Docs - Reliable Reinforcement Learning Implementations
  6. Maskable PPO — Stable Baselines3 - documentation
  7. PyTorch documentation
  8. PythonでGUIを設計 | Qtの公式Pythonバインディング
  9. Python in Excel alternative: Open. Self-hosted. No limits.
  10. Book - xlwings Documentation
にほんブログ村 株ブログ 株日記へ
PVアクセスランキング にほんブログ村

0 件のコメント:

コメントを投稿