※本記事のコードや情報は執筆時点の仕様に基づいています。投資は自己責任であり、必ずデモ環境や少額資金でテストした上で運用してください。
株式投資やシステムトレードにおいて、損失を限定するための損切り(ストップロス)は最も重要なリスク管理手法の一つです。しかし、「買値から5%下がったら売る」と頭では理解していても、実際の相場では感情に左右されてルールを破ってしまうケースが後を絶ちません。
この問題を解決する最も確実な方法は、損切り判定をプログラムに任せることです。Pythonで損切りロジックをコード化しておけば、感情を排除した機械的な判定が可能になります。
この記事では、固定パーセンテージ方式・ATR(平均真の値幅)方式・トレーリングストップ方式の3種類の損切りロジックを、コピペで動くPythonコードとともに解説します。
損切りロジックの基本概念
コードの実装に入る前に、損切りロジックの設計思想を整理します。
なぜ損切りをプログラム化するのか
裁量トレードにおける損切り失敗の原因は、ほぼすべてが心理的バイアスに起因します。
- 損失回避バイアス: 「もう少し待てば戻るかもしれない」と損失確定を先送りする
- アンカリング効果: 買値に固執し、現在の価格を客観的に評価できない
- サンクコスト効果: 「ここまで耐えたのだから」と撤退判断を遅らせる
プログラムにはこれらの心理的バイアスが存在しません。事前に定義したルールどおりに、即座に判定を下します。
損切り方式の種類と特徴
代表的な損切り方式は以下の3種類です。
| 方式 | 概要 | 適性 |
|---|---|---|
| 固定パーセンテージ方式 | 買値から一定割合下落したら損切り | 初心者向け・シンプル |
| ATR方式 | ボラティリティに応じて損切り幅を動的調整 | 中級者向け・相場適応型 |
| トレーリングストップ方式 | 高値更新に追従して損切りラインを引き上げ | 利益追従・トレンドフォロー向け |
この記事では3種類すべてのPython実装を順番に解説します。
共通の前提条件
すべてのコードは以下の前提で実装しています。
- Python 3.12以上がインストール済みであること
- yfinance と pandas がインストール済みであること(
pip install yfinance pandas) - 実際の発注処理は含まない(判定ロジックのみ)
- 日本株の日足データを使用する
固定パーセンテージ方式の実装
最もシンプルな損切りロジックから解説します。「買値から○%下落したら損切りシグナルを出す」という方式です。
ロジックの計算式
固定パーセンテージ方式の計算式は以下のとおりです。
損切りライン = 買値 × (1 - 損切り率)
例えば、買値が1,000円で損切り率が5%の場合、損切りラインは950円になります。現在価格が950円を下回った時点で損切りシグナルが発生します。
【コピペOK】固定パーセンテージ方式のコード
以下のコードを stop_loss_fixed.py として保存・実行してください。
import yfinance as yf
import pandas as pd
# ==============================
# 設定エリア
# ==============================
SYMBOL = "7203.T" # トヨタ自動車
ENTRY_PRICE = 2800.0 # 買値(エントリー価格)
STOP_LOSS_RATE = 0.05 # 損切り率(5%)
# ==============================
# 損切りライン計算
# ==============================
def calc_stop_loss_fixed(entry_price, stop_loss_rate):
"""固定パーセンテージ方式の損切りライン算出"""
stop_loss_line = entry_price * (1 - stop_loss_rate)
return round(stop_loss_line, 1)
# ==============================
# 判定処理
# ==============================
def judge_stop_loss():
stop_line = calc_stop_loss_fixed(ENTRY_PRICE, STOP_LOSS_RATE)
print(f"=== 固定パーセンテージ方式 損切り判定 ===")
print(f"銘柄 : {SYMBOL}")
print(f"買値 : {ENTRY_PRICE:,.1f} 円")
print(f"損切り率 : {STOP_LOSS_RATE:.1%}")
print(f"損切りライン : {stop_line:,.1f} 円")
print()
# 直近の株価を取得
ticker = yf.Ticker(SYMBOL)
df = ticker.history(period="5d")
if df.empty:
print("株価データを取得できませんでした。")
return
latest_close = df["Close"].iloc[-1]
latest_date = df.index[-1].strftime("%Y-%m-%d")
print(f"取得日 : {latest_date}")
print(f"直近終値 : {latest_close:,.1f} 円")
print()
# 損切り判定
if latest_close <= stop_line:
loss = ENTRY_PRICE - latest_close
loss_rate = loss / ENTRY_PRICE
print(f"【損切りシグナル発生】")
print(f" 損失額 : {loss:,.1f} 円 / 株")
print(f" 損失率 : {loss_rate:.2%}")
else:
margin = latest_close - stop_line
print(f"【保有継続】")
print(f" 損切りラインまでの余裕 : {margin:,.1f} 円")
if __name__ == "__main__":
judge_stop_loss()
コードの動作解説
このコードは以下の流れで処理を行います。
ENTRY_PRICEとSTOP_LOSS_RATEから損切りラインを算出する- yfinanceで指定銘柄の直近終値を取得する
- 終値が損切りライン以下であれば「損切りシグナル」を出力する
- 損切りラインより上であれば「保有継続」と余裕幅を出力する
ENTRY_PRICEは実際の買値に、STOP_LOSS_RATEは自身のリスク許容度に合わせて変更してください。一般的には3%〜10%の範囲で設定されることが多いです。
ATR方式の実装
ATR(Average True Range)方式は、相場のボラティリティ(値動きの大きさ)に応じて損切り幅を動的に調整する方式です。
ATRとは何か
ATRは「真の値幅(True Range)」の移動平均値です。True Rangeは以下の3つの値のうち最大値として算出されます。
- 当日高値 − 当日安値
- |当日高値 − 前日終値|
- |当日安値 − 前日終値|
ATRが大きい(ボラティリティが高い)銘柄では損切り幅を広く、ATRが小さい(ボラティリティが低い)銘柄では損切り幅を狭く設定します。
ATR方式の計算式
損切りライン = 買値 − (ATR × 倍率)
倍率は一般的に1.5〜3.0倍が使用されます。倍率が大きいほど損切り幅が広くなり、ノイズによる早期損切りを回避しやすくなります。
【コピペOK】ATR方式のコード
以下のコードを stop_loss_atr.py として保存・実行してください。
import yfinance as yf
import pandas as pd
# ==============================
# 設定エリア
# ==============================
SYMBOL = "7203.T" # トヨタ自動車
ENTRY_PRICE = 2800.0 # 買値(エントリー価格)
ATR_PERIOD = 14 # ATR計算期間(日)
ATR_MULTIPLIER = 2.0 # ATR倍率
# ==============================
# ATR計算
# ==============================
def calc_atr(df, period):
"""ATR(Average True Range)を計算"""
high = df["High"]
low = df["Low"]
close = df["Close"]
tr1 = high - low
tr2 = abs(high - close.shift(1))
tr3 = abs(low - close.shift(1))
true_range = pd.concat([tr1, tr2, tr3], axis=1).max(axis=1)
atr = true_range.rolling(window=period).mean()
return atr
# ==============================
# 判定処理
# ==============================
def judge_stop_loss_atr():
print(f"=== ATR方式 損切り判定 ===")
print(f"銘柄 : {SYMBOL}")
print(f"買値 : {ENTRY_PRICE:,.1f} 円")
print(f"ATR期間 : {ATR_PERIOD} 日")
print(f"ATR倍率 : {ATR_MULTIPLIER} 倍")
print()
# 株価データ取得(ATR計算に十分な期間)
ticker = yf.Ticker(SYMBOL)
df = ticker.history(period="3mo")
if df.empty:
print("株価データを取得できませんでした。")
return
# ATR計算
df["ATR"] = calc_atr(df, ATR_PERIOD)
latest_atr = df["ATR"].iloc[-1]
latest_close = df["Close"].iloc[-1]
latest_date = df.index[-1].strftime("%Y-%m-%d")
# 損切りライン算出
stop_line = ENTRY_PRICE - (latest_atr * ATR_MULTIPLIER)
stop_line = round(stop_line, 1)
print(f"取得日 : {latest_date}")
print(f"直近終値 : {latest_close:,.1f} 円")
print(f"直近ATR : {latest_atr:,.1f} 円")
print(f"損切り幅 : {latest_atr * ATR_MULTIPLIER:,.1f} 円")
print(f"損切りライン : {stop_line:,.1f} 円")
print()
# 損切り判定
if latest_close <= stop_line:
loss = ENTRY_PRICE - latest_close
loss_rate = loss / ENTRY_PRICE
print(f"【損切りシグナル発生】")
print(f" 損失額 : {loss:,.1f} 円 / 株")
print(f" 損失率 : {loss_rate:.2%}")
else:
margin = latest_close - stop_line
print(f"【保有継続】")
print(f" 損切りラインまでの余裕 : {margin:,.1f} 円")
# 直近5日間のATR推移を表示
print()
print("--- 直近5日間のATR推移 ---")
recent = df[["Close", "ATR"]].tail(5).copy()
recent["Close"] = recent["Close"].round(1)
recent["ATR"] = recent["ATR"].round(1)
print(recent.to_string())
if __name__ == "__main__":
judge_stop_loss_atr()
ATR倍率の選び方
ATR倍率の設定は、トレードスタイルによって異なります。
| トレードスタイル | 推奨ATR倍率 | 特徴 |
|---|---|---|
| スキャルピング | 1.0〜1.5 | 損切り幅が狭く、回転率重視 |
| スイングトレード | 2.0〜2.5 | バランス型。最も一般的 |
| ポジショントレード | 2.5〜3.0 | 損切り幅が広く、長期保有向け |
ATR倍率が小さすぎると、日常的な値動きのノイズで頻繁に損切りが発生します。反対に大きすぎると、1回の損失額が膨らみます。バックテストで最適値を検証することを推奨します。
トレーリングストップ方式の実装
トレーリングストップは、株価が上昇するにつれて損切りラインも引き上がる仕組みです。利益を伸ばしながらも、反転時には確実に利益(または最小限の損失)を確定できます。
トレーリングストップの仕組み
通常の損切りラインは買値を基準に固定されますが、トレーリングストップでは保有期間中の最高値を基準に損切りラインが更新されます。
損切りライン = 保有期間中の最高値 × (1 - トレーリング率)
株価が上昇すれば損切りラインも上がりますが、株価が下落しても損切りラインは下がりません。つまり、損切りラインは一方向にしか動かないという特徴があります。
【コピペOK】トレーリングストップ方式のコード
以下のコードを stop_loss_trailing.py として保存・実行してください。
import yfinance as yf
import pandas as pd
# ==============================
# 設定エリア
# ==============================
SYMBOL = "7203.T" # トヨタ自動車
ENTRY_PRICE = 2800.0 # 買値(エントリー価格)
ENTRY_DATE = "2025-01-06" # 買い日(この日以降のデータで判定)
TRAILING_RATE = 0.07 # トレーリング率(7%)
# ==============================
# トレーリングストップ計算
# ==============================
def calc_trailing_stop(df, entry_price, trailing_rate):
"""トレーリングストップの損切りラインを日次で計算"""
results = []
highest = entry_price # 保有期間中の最高値を追跡
for date, row in df.iterrows():
close = row["Close"]
high = row["High"]
# 最高値を更新
if high > highest:
highest = high
# 損切りラインを算出(最高値基準)
stop_line = highest * (1 - trailing_rate)
# 判定
signal = "損切り" if close <= stop_line else "保有継続"
results.append({
"Date": date,
"Close": round(close, 1),
"High": round(high, 1),
"Highest": round(highest, 1),
"StopLine": round(stop_line, 1),
"Signal": signal
})
# 損切りシグナルが出たら終了
if signal == "損切り":
break
return pd.DataFrame(results)
# ==============================
# メイン処理
# ==============================
def main():
print(f"=== トレーリングストップ方式 損切りシミュレーション ===")
print(f"銘柄 : {SYMBOL}")
print(f"買値 : {ENTRY_PRICE:,.1f} 円")
print(f"エントリー日 : {ENTRY_DATE}")
print(f"トレーリング率 : {TRAILING_RATE:.1%}")
print()
# 株価データ取得
ticker = yf.Ticker(SYMBOL)
df = ticker.history(start=ENTRY_DATE)
if df.empty:
print("株価データを取得できませんでした。")
return
# トレーリングストップ計算
result_df = calc_trailing_stop(df, ENTRY_PRICE, TRAILING_RATE)
# 結果表示
print("--- 日次トレーリングストップ推移 ---")
display_df = result_df.copy()
display_df["Date"] = display_df["Date"].dt.strftime("%Y-%m-%d")
display_df = display_df.set_index("Date")
print(display_df.tail(20).to_string())
print()
# 最終結果サマリー
last_row = result_df.iloc[-1]
if last_row["Signal"] == "損切り":
pnl = last_row["Close"] - ENTRY_PRICE
pnl_rate = pnl / ENTRY_PRICE
print(f"【損切りシグナル発生】")
print(f" 発生日 : {last_row['Date'].strftime('%Y-%m-%d')}")
print(f" 終値 : {last_row['Close']:,.1f} 円")
print(f" 損益 : {pnl:+,.1f} 円 / 株({pnl_rate:+.2%})")
else:
pnl = last_row["Close"] - ENTRY_PRICE
pnl_rate = pnl / ENTRY_PRICE
print(f"【保有継続中】")
print(f" 最高値 : {last_row['Highest']:,.1f} 円")
print(f" 損切りライン : {last_row['StopLine']:,.1f} 円")
print(f" 含み損益 : {pnl:+,.1f} 円 / 株({pnl_rate:+.2%})")
if __name__ == "__main__":
main()
コードの動作解説
このスクリプトの処理フローは以下のとおりです。
- エントリー日以降の日足データをyfinanceで取得する
- 日ごとに「保有期間中の最高値」を更新し続ける
- 最高値から
TRAILING_RATE分下落した価格を損切りラインとして算出する - 終値が損切りラインを割り込んだ日に「損切りシグナル」を出力して処理を終了する
- まだ割り込んでいなければ「保有継続中」として現在の状況を出力する
ENTRY_DATEは実際にポジションを取った日付に合わせて変更してください。トレーリング率は7%〜10%が一般的ですが、銘柄のボラティリティに応じて調整が必要です。
3方式の比較と使い分け
それぞれの方式には明確な向き不向きがあります。以下の比較表を参考に、自身のトレードスタイルに合った方式を選択してください。
| 比較項目 | 固定パーセンテージ | ATR方式 | トレーリングストップ |
|---|---|---|---|
| 実装の難易度 | ★☆☆ | ★★☆ | ★★☆ |
| 相場適応力 | 低い | 高い | 高い |
| 利益追従性 | なし | なし | あり |
| パラメータ数 | 1つ(損切り率) | 2つ(期間・倍率) | 1つ(トレーリング率) |
| 推奨場面 | 初心者・検証用 | ボラティリティ変動が大きい銘柄 | トレンドフォロー戦略 |
まずは固定パーセンテージ方式で損切りロジックの基本を理解し、その後ATR方式やトレーリングストップに拡張していくのが推奨される学習順序です。
よくあるエラーと対処法
yfinanceでデータが空(Empty DataFrame)になる
df.empty が True になるケースは、主に以下の原因で発生します。
- 銘柄コードが間違っている(日本株は
証券コード.Tの形式) - 取得期間が市場の休業日のみに該当している
- ネットワーク接続に問題がある
銘柄コードの末尾に .T(東証)を付け忘れていないか確認してください。
ENTRY_PRICEと実際の株価スケールが合わない
ENTRY_PRICE に現実離れした値を設定すると、すべての判定が「損切り」または「保有継続」に偏ります。yfinanceで取得した直近の終値を参考にして、妥当な買値を設定してください。
損切りラインが買値より上になってしまう
トレーリングストップ方式で、エントリー後に大きく上昇した場合、損切りラインが買値を上回ることがあります。これは正常な動作です。
この状態は「損切りラインが利益確定ラインに転化した」ことを意味します。この時点で損切りが発動しても、結果的に利益が確定します。
まとめ
Pythonで実装できる損切りロジックの3方式について、以下の要点を整理します。
- 固定パーセンテージ方式: 最もシンプル。「買値 × (1 – 損切り率)」で算出
- ATR方式: ボラティリティに応じて損切り幅を動的調整。相場環境への適応力が高い
- トレーリングストップ方式: 保有期間中の最高値を追跡し、損切りラインを引き上げる。利益追従に優れている
いずれの方式も、コード上では「計算」と「判定」のみを行い、実際の発注処理は含んでいません。まずはこの判定ロジックを基盤として検証を重ね、信頼性を確認したうえで発注機能との連携に進んでください。
損切りルールをプログラムで明文化することは、感情に左右されない規律あるトレードの第一歩です。この記事のコードをベースに、自身の投資戦略に合ったパラメータを検証してください。

