2026-03-08

自動売買への道 (2026-03-08)

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

デイトレ用自作アプリ

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

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

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

乖離度のチャートは必要か?

発注条件概略
銘柄数 3
信用区分 一般信用(いちにち)
注文数量 100 株
価格区分 成行
制  約 ナンピン禁止 (GUI)

本アプリは、短周期の移動平均線 MA1 と売買高加重平均価格 VWAP のクロス・シグナルで売買判断をします。

同時に 3 銘柄をスクロールバー無しに表示できる設計なので、現在のところ、一般信用売建銘柄(いちにち)の売建可能数量が多いものから 3 銘柄を選定して運用しています。

開発当初、MA1 と VWAP の乖離度のトレンド表示に妙にこだわってしまったので、下記のように、切り替えて表示できるようにしています。

アプリの株価の移動平均線 MA1 と 売買高加重平均価格 VWAP のトレンド [2026-03-06]
アプリの株価の移動平均線 MA1 と 売買高加重平均価格 VWAP の乖離度トレンド [2026-03-06]

一ヶ月程度使ってみて、乖離度のチャートをほとんど見なくなってしまったことに気が付きました。寄り付いた直後を除けば VWAP の変化は緩やかです。長短二周期の移動平均線のクロス・シグナルを見る場合と異なり、MA1 と VWAP の乖離度のチャートは不要なのかもしれません。

不要な機能は取り除くのが筋ですが…、他のテクニカル指標を導入するときのために残しておこうかと考えています。

モメンタムのような指標が欲しいと思っているので、ティックデータでモメンタムを定義・評価をして、使えそうであれば、乖離度の代わりに表示するつもりです。

デイトレ用取引アプリの構成概略 ─ Kabuto 0.5 へ

現在のアプリは、エントリ:手動、エグジット:自動手動)の構成になっています。

マーケットスピードⅡ RSS / Excel とのやりとり
  • 複数銘柄(20 銘柄程度)のティックデータを収集
    • 4 本足データは収集していません。
      • 素人考えかもしれませんが、ティックデータを取得できるのに、デイトレで四本足データでローソク足チャートを作るはもったいないと考えています。
  • アプリが取得する情報は、(原則)時刻、株価と出来高のみ
    • ティックデータの取得頻度は、マーケットスピードⅡ RSS のデフォルトの更新頻度の 2 秒
    • 収集したティックデータは、銘柄コードをシート名にした新規 Excel ブック (.xlsx) に自動保存
    • 発注の約定確認は、Python と Excel VBA とで最低限のやり取りをしています。
  • 信用建玉発注・返済(成行注文)
    • 取引対象の銘柄は、ティックデータを収集する銘柄の中から選択
      • 取引対象の銘柄数に上限なし。ただし、スクロールなしに一画面で値動きを見られるのは 3 銘柄まで。
    • Excel 側に、RSS の VBA 関数で発注・約定確認をするユーザー VBA 関数を実装
    • Python 側からVBA 関数を実行。
  • 含み益の情報は Excel から取得せず、アプリ内部で見込み額を算出
    • RSS と本アプリの xlwings が同一の Excel シートへアクセスするため、一定頻度で COM の衝突が発生します。
      • 衝突による例外発生時にリトライ回数を設定してはいますが、Excel とのやり取りを必要最低限に抑えようとしています。
      • 現在のところ、建玉発注時の建玉価格や返済価格の情報すらアプリ側で取得していません。機を見て約定価格を取得するように改善する予定です。
株価チャート
  • 株価トレンド
    • 最新株価の
    • 短周期移動平均線 MA1 と 売買高加重平均価格 VWAP のトレンド線
  • MA1 と VWAP の乖離トレンド(切り替えて表示)
    • MA1 - VWAP 乖離のトレンド線
    • ただし、モメンタム系の指標に置き換える予定です。
  • MA1 と VWAP がクロスしたタイミングの縦線
    • 直近のゴールデン・クロスとデッド・クロスを色を変えて表示
  • 一般の取引ツールと異なり、当日の取引時間幅に固定したチャート
    • デイトレは取引時間内の値動きに集中したいので、どうしてもこのようなチャートにしたかった。
手動エントリ
  • 買 建売 建 ボタンをクリックして手動でエントリ
    • クリック可能なボタンの制御は、ナンピンができないロジックになっている。
    • 寄り付き後、設定期間(デフォルトは 300 step = 600 秒)は売買ボタンをロックして売買禁止。
  • 建玉があるときに 返 済 ボタンをクリックすれば手動で返済(エグジット)することも可能
自動エグジット
  • 移動平均線がクロスしたタイミングで建玉があれば返済
    • ただし、ゴールデンクロス時に買建、デッドクロス時に売建の建玉を持っている場合は返済しない。
  • トレーリングストップによる利確
    • ドローダウンとドローダウン比率を設定して運用
    • トレーリングストップは、含み損益 \(profit\) が 最大含み益 \(profit_{max}\)  からどれだけ落ちたら利確するかを決める仕組みです。
    • \[ \text{drawdown} = profit_{max} - profit \]
    • \[ \text{drawdown_ratio} = \frac{drawdown}{profit_{max}} \]
  • 含み損がしきい値(マイナス側)を超えればロスカット
  • 含み損が連続して設定回数を超えればロスカット

レビュー機能の追加

毎日の取引結果を JupyterLab でデータを読み込んでレビューしていましたが、レビュー機能をアプリ側で実装しようと、モックアップを作り始めています。

JupyterLab によるレビューと同等機能を実現したモックアップ

当初は、レビュー機能を本体アプリ (Kabuto) に取り込む予定で作り始めたのですが、しばらくは独立したサブ・アプリとしてプロジェクト内で共存させた方が良いかもしれないと考えるようになりました。

本体アプリは、本番環境でリアルタイム処理をさせることが最優先なので、チャート作成でも軽量で高速な PyQtGraph を利用しています。一方、レビュー目的では細かな調整ができる Matplotlib を利用するつもりです。

リアルタイム処理とレビュー処理を同一アプリに実装してしまえば、最低限の処理を一気通貫で自動化できるメリットがありますが、その結果、無闇にアプリを肥大化させて、メモリ使用量的には重くしてしまう懸念があります。

本番環境には非力な低電力消費の Windows PC を利用しているので、個々のアプリを専業化させた方が良いと考えました。

最終的に本体アプリ (Kabuto) 側に吸収できる余地は残しますが、レビュー機能は別アプリにしてしばらくは運用することにします。

Matplotlib についての備忘録

Matplotlib を使用する場合、大引け後に本体アプリが取得したデータからレビュー用の画像を生成・保存するケースを想定していました。保存するだけであれば、JupyterLab 上で使っていたコードと同等レベルで大丈夫かな、と考えたのですが、念の為、メモリーリーク対策を Google Gemini に相談しました。そのときのやり取りを、備忘録として共有します。

Matplotlib では明示的に plt.close(fig) を行わないと、メモリリークの直接的な原因になります。

特に GUI アプリや自動売買システムのように、長時間起動したまま繰り返しチャートを生成する環境では、この管理は必須と言えます。


なぜリークするのか?

Matplotlib の pyplot インターフェース(plt)を使用すると、内部のマネージャーが作成されたすべての Figure オブジェクトへの参照を保持し続けます。

  • 参照が残る: ユーザーが変数 fig を使い終わっても、plt 側が「いつでも plt.show() で表示できるように」と、リストに fig を保持し続けます。
  • GC(ガベージコレクション)が効かない: Python の標準的なメモリ解放の仕組み(参照カウント)では、plt が参照を握っている限り、その fig はメモリから消えません。

クラス内で実装する場合の「鉄則」

保存がメインの処理であれば、以下のような構成にすることをお勧めします。

import matplotlib.pyplot as plt

class ChartGenerator:
    def save_report(self, df, filename):
        # 1. 作成(plt.subplots は内部で fig を plt の管理下に置く)
        fig, ax = plt.subplots(figsize=(12, 8))
        
        try:
            # 2. 描画処理
            ax.plot(df['close'])
            fig.savefig(filename)
        finally:
            # 3. 確実な解放
            # 正常終了してもエラーが起きても、必ず close する
            plt.close(fig)

さらに安全な方法

もし GUI アプリ(特に PySide6 など)の中で動かすのであれば、そもそも plt(マネージャー)を介さずに、純粋なオブジェクト指向 API を使うことでリークのリスクを物理的に排除できます。

from matplotlib.figure import Figure
from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas

def save_pure_oop(df, filename):
    # plt を介さず、直接 Figure オブジェクトを作る
    # これは plt の管理リスト(マネージャー)に登録されません
    fig = Figure(figsize=(12, 8))
    canvas = FigureCanvas(fig)
    ax = fig.add_subplot(111)
    
    ax.plot(df['close'])
    
    # 保存。この fig は関数のスコープを抜ければ自然に GC の対象になります
    fig.savefig(filename)
なるほど。アプリは PySide6 ベースだから FigureCanvasQTAgg を利用すればいいんだね。

その通りです!FigureCanvasQTAgg を使うのが、PySide6 アプリケーションにおける「正解」です。

これを使えば、pyplot という「グローバルな状態(館長)」を完全に排除して、PySide6 のウィジェット体系の中に Matplotlib を組み込めます。

FigureCanvasQTAgg を使うメリット

  1. メモリ管理の自動化:
    FigureCanvas は単なる QWidget(あるいはその派生)として扱えるため、PySide6 の親ウィジェットが破棄されれば、連動して Figure もメモリから解放されます。plt.close() に神経質にならずに済みます。
  2. スレッドセーフな運用:
    plt(pyplot)はメインスレッドでしか安定しませんが、オブジェクト指向的に Figure を直接扱えば、データ処理と描画のロジックを分離しやすくなります。

具体的な実装イメージ

保存がメインの場合でも、このようにウィジェットとして定義しておくと、アプリ画面への表示と画像保存の両立が非常にスムーズです。

from PySide6.QtWidgets import QWidget, QVBoxLayout
from matplotlib.figure import Figure
from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg as FigureCanvas

class ChartWidget(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        # 1. Figure を直接生成 (plt は使わない)
        self.fig = Figure(figsize=(12, 8))
        # 2. Canvas に Figure をセット
        self.canvas = FigureCanvas(self.fig)
        
        layout = QVBoxLayout(self)
        layout.addWidget(self.canvas)
        
        self.ax = self.fig.add_subplot(111)

    def update_chart(self, df):
        self.ax.clear()
        self.ax.plot(df['close'])
        self.canvas.draw()

    def export_image(self, path):
        # Canvas が管理している Figure をそのまま保存
        self.fig.savefig(path)

This is the right way. のような英語を「正解」という日本語にしているだけなのかもしれませんが、簡単に「正解」という表現をするのが気に入りません。

PySide6 上で Matplotlib の Qt 向けバックエンド・ライブラリ (FigureCanvasQTAgg) を利用していましたが、それをオブジェクト指向プログラミング用 API として特に意識していなかったので Gemini のアドバイスは新鮮でした。

参考サイト

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

0 件のコメント:

コメントを投稿