【システム実装検証⑤】Pythonで現物グリッドトレードの「決済売り発注」を実装する

前回は、kabuステーションの検証環境(テストポート:18081)を利用し、致命的な誤発注を防ぎながら「新規買い注文」を安全に送信するプログラムを実装しました。
今回はいよいよ、自動売買システムの心臓部となる「買い注文が約定した後に呼び出されることを想定した決済ロジック(利益確定の売り発注)」をPythonでどのように実装するのかを解説します。

※本記事では「APIを通じた売り注文の発注ロジック」そのものに焦点を当てています。どの建玉に対して既に売り注文を出したかを管理する「約定の重複管理(二重発注の防止)」については、次回以降の運用フェーズで解説します。

当システムのグリッドルール(1329 日経平均ETF)

プログラムを組む前に、当システムがどのようなルールで売買のサイクルを回しているのか、そのパラメータを定義しておきます。
当システムでは、値動きの安定した「1329(iShares Core 日経225 ETF)」の現物取引を対象とし、以下のような保守的なルールを設定しています。

  • 稼働レンジ: 5,000円 〜 6,140円の間でのみ稼働
  • 買い下がり幅(グリッド): 5円刻みで買い注文を配置
  • 利益確定幅: 買値から「+100円」上昇した地点で決済売り

この「買値+100円」という利益確定(決済)の指値注文を市場へ送り出すロジックを構築します。

決済売り注文(利益確定)の現場仕様Pythonコード

前回の買い注文コードをベースに、「売り(Side: 1)」の注文を送信する関数を作成します。引数として「買値(buy_price)」を受け取り、そこに利確幅(100円)を上乗せした価格で発注します。
今回は実運用を見据え、「APIが異常時にHTMLを返してきてパースエラーで落ちる」という証券API特有のリスクを防ぐ処理も追加しています。

import requests
import config

# システムの基本パラメータ定義
SYMBOL = "1329"
PROFIT_WIDTH = 100  # 利確幅(100円)

def execute_profit_take_order(buy_price, qty=1):
    """指定した買値に利確幅(100円)を乗せた売り指値注文を発注する"""
    # 予期せぬfloat型の混入を防ぎ、API仕様に合わせるためintでキャスト
    sell_price = int(buy_price + PROFIT_WIDTH)

    print(f"【決済ロジック稼働】買値: {buy_price}円 -> 利確設定: {sell_price}円")

    # 検証環境のポート(18081)を指定
    url = f"http://localhost:{config.KABU_PORT}/kabusapi/sendorder"

    try:
        with open(config.KABU_TOKEN_PATH, 'r', encoding='utf-8') as f:
            token = f.read().strip()

        headers = {'X-API-KEY': token, 'Content-Type': 'application/json'}

        # 売り注文のデータ作成(不要なパラメータは排除し堅牢に記述)
        order_data = {
            "Password": config.KABU_ORDER_PASSWORD,
            "Symbol": SYMBOL,
            "Exchange": 1,
            "SecurityType": 1,
            "Side": 1,            # 1: 売(※必ず数値で指定)
            "CashMargin": 1,      # 1: 現物
            "DelivType": 2,       # 2: お預り金
            "AccountType": 4,     # 4: 特定口座
            "Qty": qty,           # 注文数量(※ETFのため1口単位で取引可能)
            "Price": sell_price,  # 【重要】計算した利確価格
            "ExpireDay": 0,       # 0: 本日中有効(当日限り)
            "FrontOrderType": 20  # 20: 指値
        }

        response = requests.post(url, headers=headers, json=order_data, timeout=5)
        response.raise_for_status()

        # API異常時のHTML返却(JSONデコードエラー)を安全に防ぐ
        try:
            result = response.json()
        except ValueError:
            print("発注エラー: APIから不正なレスポンス(JSONデコード失敗)が返却されました。")
            return None

        # 業務エラーの検知(API異常時の型崩れも防ぐ判定)
        if not isinstance(result, dict) or result.get('Code', 0) != 0:
            if isinstance(result, dict):
                print(f"発注エラー: [Code:{result.get('Code')}] {result.get('Message')}")
            else:
                print("発注エラー: APIから予期せぬデータ形式が返却されました。")
            return None

        order_id = result.get('OrderId')
        print(f"成功!利益確定の売り注文を送信しました。[注文番号: {order_id}]")
        return order_id

    except Exception as e:
        print(f"決済発注処理中にエラーが発生しました: {e}")
        return None

# テスト実行用(例:5000円で約定した建玉の決済注文を出す検証)
if __name__ == "__main__":
    execute_profit_take_order(buy_price=5000)

コードの重要ポイントと「見えないエラー」の回避

今回のコードは、実際に長期間システムを稼働させる上で直面する「見えないエラー」を回避するための設計になっています。

  • 呼値(価格刻み)の制約: 実際の日本株には、価格帯によって「1円刻み」「5円刻み」といった呼値の制約が存在します。計算した sell_price が呼値に合致しないと注文が弾かれる場合があります(※1329 ETFは比較的低価格帯で推移するため、呼値の制約が問題になるケースは限定的です)。
  • JSONデコードエラー対策: 証券会社のAPIは、サーバー過負荷などの異常時に、HTTPステータスが「200 OK」であっても内容がJSONではなく「HTMLのエラーページ」を返してくることがあります(より堅牢にする場合は Content-Type の確認も有効です)。本コードでは ValueError で安全にキャッチしてプログラムの異常終了を防いでいます。

現物取引における「発注エラー」とセーフティネット

プログラムによる発注は「二重発注(同じ注文を何度も出してしまうこと)」のリスクが常に伴います。
当システムが信用取引ではなく「現物取引」に限定している大きな理由の一つが、このエラーへの耐性です。

現物取引の場合、「証券口座に保有していない株は売れない(空売りできない)」という絶対的な物理ルールが存在します。万が一プログラムが暴走して売り注文を二重に出してしまったとしても、実際の保有数量以上の売り注文を出そうとした瞬間にAPI側から明確なエラー(残高不足)が返され、注文がシステム的に弾かれます。

ただし、この仕様はあくまで致命的なミスを防ぐセーフティネットに過ぎません。
APIリトライの無限ループやロジック不備により「同一ポジションに対してエラーとなる売り注文を過剰に試行し続け、ログが爆発する」といったリスクはあるため、アプリケーション側での厳密な約定管理は依然として必須です。

【実運用におけるリトライの注意点】
※証券APIは短時間に大量リクエストを送ると通信制限(レートリミット)がかかる場合があるため、通信エラー時のリトライ処理には数秒〜数分程度の待機時間(バックオフ)を設ける設計が強く推奨されます。

今回の検証コードはあくまで「売り注文を出す」単体のテストであり、実運用では約定IDなどをキーとした厳密な重複管理(二重発注防止)が不可欠となります。
次回からは【システム運用検証】として、これらのロジックを統合し、安全に24時間稼働させるための全体管理フェーズへと進みます。