日本株の自動売買で見落としがちなのが「発注してから約定するまでの間に相場が急変するリスク」だ。寄り付き前に指値注文を出しても、ザラ場(取引時間中)に悪材料が出て株価が急落すれば、その注文はもはや不利な条件で約定する可能性がある。
本記事では、Kabustation API(auカブコム証券のAPI)を使って、ザラ場中に4段階のチェック(S1〜S4)で注文をリアルタイム監視し、条件を満たさなくなった注文を自動キャンセルする「動的キャンセル機構」の設計と実装を記録する。1,507テスト全PASSで実装完了済みだ。
問題の背景
日本株自動売買の注文フロー
日本株の自動売買では、一般的に以下のフローで注文する。
前日夜: シグナル計算・銘柄スクリーニング
当日朝 08:00: 指値注文を一括発注
09:00: 東証オープン(寄り付き)
09:00〜15:30: ザラ場(取引時間)
15:30: 大引け
問題は「08:00の発注」と「09:00の寄り付き」の間に1時間のギャップがあること、さらにザラ場中も相場は刻々と変化することだ。
具体的なリスク
- ギャップダウン: 前日終値から大きく下げて始まる。SLを超える価格で寄り付く場合がある
- セクター急落: 特定セクターに悪材料。発注済みの同セクター銘柄が一斉に下落
- 指数急落: 日経平均が急落。マーケット全体のリスクオフ
- 流動性の消失: 板が薄くなり、約定品質が著しく悪化
これらの状況で「出しっぱなしの注文」が不利な価格で約定するのを防ぎたい。
4段階チェック(S1〜S4)
動的キャンセル機構は、ザラ場中に定期的(1分間隔)に4段階のチェックを実行し、いずれかの条件に抵触した注文をキャンセルする。
S1: 価格乖離チェック
現在の株価が、注文時に想定した価格から大きく乖離していないかを確認する。
def check_s1_price_deviation(
order_price: float, # 注文価格
current_price: float, # 現在価格
max_deviation_pct: float = 3.0, # 最大許容乖離率(%)
) -> bool:
deviation = abs(current_price - order_price) / order_price * 100
return deviation <= max_deviation_pct
例えば注文時の想定価格が1,000円で、現在価格が970円(-3%以上の下落)なら、この注文はキャンセル対象になる。
S2: ボリュームチェック
出来高が極端に少ない銘柄は、約定後に「板が薄くて逃げられない」リスクがある。
def check_s2_volume(
current_volume: int, # 当日の出来高
avg_volume_20d: int, # 20日平均出来高
min_volume_ratio: float = 0.3, # 最低出来高比率
) -> bool:
if avg_volume_20d == 0:
return False
return current_volume / avg_volume_20d >= min_volume_ratio
当日の出来高が20日平均の30%未満の場合、流動性不足と判断してキャンセルする。
S3: 指数連動チェック
日経平均やTOPIXが急落している場合、個別銘柄の注文を維持するのはリスクが高い。
def check_s3_index_drop(
nikkei_change_pct: float, # 日経平均の前日比変動率
topix_change_pct: float, # TOPIXの前日比変動率
max_index_drop: float = -2.0, # 最大許容下落率(%)
) -> bool:
return nikkei_change_pct >= max_index_drop and topix_change_pct >= max_index_drop
日経平均が前日比-2%以上下落していたら、全注文をキャンセルする。
S4: スコア再計算チェック
最も重要なチェック。発注時に計算したシグナルスコアを、最新のデータで再計算する。スコアが閾値を下回っていたら、もはやエントリーの根拠がないためキャンセルする。
def check_s4_score_revalidation(
current_indicators: dict, # 最新の指標値
min_score: float, # エントリー閾値
) -> bool:
recalculated_score = evaluate_signal(current_indicators)
return recalculated_score >= min_score
これにより「発注時点では有効だったシグナルが、ザラ場中に無効化された」ケースを捕捉できる。
全体のアーキテクチャ
[launchd: 09:05 JST 起動]
│
└─ DynamicCancelMonitor(1分間隔ループ)
│
├─ 未約定注文リスト取得(Kabustation API)
│
└─ 各注文に対して S1→S2→S3→S4 チェック
│
├─ 全チェックPASS → 注文維持
│
└─ いずれか失敗 → 注文キャンセル(Kabustation API)
└─ Slack通知
キャンセルの判断基準
S1〜S4のチェックは「AND条件」ではなく「OR条件」だ。どれか1つでも失敗したらキャンセルする。なぜなら:
- S3(指数急落)が発生しても、S1(価格乖離)がまだ閾値内ということはありえる
- しかし「日経が-3%急落しているが、個別銘柄の価格はまだ動いていない」状況は、数分後に連鎖して暴落する可能性が高い
- リスク回避は「最も厳しいチェックに従う」べきだ
約定済み注文への影響
重要な設計ポイントとして、この機構は未約定の注文のみを対象とする。既に約定したポジションには一切影響しない。約定済みポジションの管理は別の仕組み(SL/TP + トレーリングストップ)で行う。
100株単位制約への対応
日本株は100株単位でしか取引できない。ロットサイズの計算結果が150株の場合、100株に切り捨てるか200株に切り上げるかの判断が必要になる。
def round_to_unit(shares: int, unit: int = 100) -> int:
"""100株単位に丸める(切り捨て)"""
return (shares // unit) * unit
切り捨て(保守的)を採用した。リスク管理上、「少なくトレードする」方が安全だからだ。
テスト戦略
1,507テストを以下の構成で実装した。
| テストカテゴリ | 件数 | 内容 |
|---|---|---|
| S1単体テスト | 150 | 価格乖離の境界値テスト |
| S2単体テスト | 120 | 出来高ゼロ・異常値テスト |
| S3単体テスト | 130 | 指数データ取得失敗時のフォールバック |
| S4単体テスト | 200 | スコア再計算の整合性 |
| 統合テスト | 300 | S1〜S4の組み合わせシナリオ |
| API mock テスト | 200 | Kabustation API障害時の動作 |
| 回帰テスト | 407 | 既存の取引ロジックへの影響ゼロ確認 |
API障害時は「キャンセルしない」(安全側=注文維持)とした。APIが壊れているときに大量キャンセルすると、本来約定すべき有利な注文も消してしまうリスクがあるためだ。
学んだこと
1. 「発注して終わり」は片手落ち
自動売買で注力されがちなのは「いつ、何を、いくらで注文するか」だ。しかし「発注済みの注文を、変化した環境に合わせて管理する」のも同じくらい重要だ。発注〜約定の間にも相場は動いている。
2. 4段階チェックのOR条件は過剰に見えて適切
「どれか1つでも引っかかったらキャンセル」は一見厳しすぎるように見える。しかし、不利な約定1件のコスト(数千円〜数万円の損失)と、キャンセルによる機会損失(利益を逃すリスク)を比較すると、前者の方が遥かに痛い。リスク管理は「厳しすぎるくらいでちょうどいい」。
3. テストの重要性は実装の3倍
1,507テストは実装コード量の3倍近い。しかし「本番で1件の誤キャンセルが発生する」コストを考えると、テストへの投資は十分にペイする。特にS3(指数急落)のテストは、実際の暴落データを使ったシナリオテストが不可欠だった。
まとめ
ザラ場動的キャンセル機構の設計で重要なのは以下の3点だ。
- 4段階チェック(S1〜S4): 価格乖離・出来高・指数連動・スコア再計算の多面的な判断
- OR条件でのキャンセル: 1つでも失敗したらキャンセル。リスク回避は厳しい方が正しい
- 未約定注文のみ対象: 約定済みポジションとは独立した管理。責務の明確な分離
「出した注文を守る」のではなく「環境が変わったら撤退する」。この柔軟性が、ザラ場の急変から資産を守る最後の防衛線だ。