楽天証券の口座でデイトレの自動売買に挑戦しようと 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 で取得して作成しました。
【関連ニュース】
- 米上院、台湾との交流制限を解除する法案を可決 トランプ大統領の署名を経て成立へ - フォーカス台湾 [2025-11-19]
- 米、台湾への防空システム「NASAMS」売却確認 7億ドル相当 | ロイター [2025-11-19]
- エヌビディア株4%高、堅調な売上高予想示す-AIバブル懸念払拭に寄与 - Bloomberg [2025-11-20]
- エヌビディア、第4四半期売上高見通しが予想上回る 株価4%高 | ロイター [2025-11-20]
- Gemini 3発表でアルファベット株が急騰、ラリー・ペイジは「世界3位の富豪」に | Forbes JAPAN [2025-11-20]
- 日経平均は5日ぶり反発、エヌビディア決算でAI関連買い戻し | ロイター [2025-11-20]
デイトレ用自作アプリ
以下は株価に関連する情報の流れを示しています。
楽天証券では、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
参考サイト
- マーケットスピード II RSS | 楽天証券のトレーディングツール
- マーケットスピード II RSS 関数マニュアル
- 注文 | マーケットスピード II RSS オンラインヘルプ | 楽天証券のトレーディングツール
- Gymnasium Documentation
- Stable-Baselines3 Docs - Reliable Reinforcement Learning Implementations
- Maskable PPO — Stable Baselines3 - documentation
- PyTorch documentation
- PythonでGUIを設計 | Qtの公式Pythonバインディング
- Python in Excel alternative: Open. Self-hosted. No limits.
- Book - xlwings Documentation





0 件のコメント:
コメントを投稿