シャープレシオ(Sharpe ratio)は、リターンをリスク(ボラティリティ)で割った リスク調整後リターン の定番指標です。本記事では定義を整理し、日次データから年率シャープレシオを計算するまでの流れを Python でまとめます。
目次
- 定義
- 日次から年率への換算
- 評価の目安
- サンプルデータの準備
- 年率シャープレシオの計算
- 関数化
- ローリングシャープレシオ
- ソルティノレシオ(参考)
定義
シャープレシオは「超過リターンの平均」を「超過リターンの標準偏差」で割った値です。
- : 戦略・銘柄のリターン
- : リスクフリーレート(risk-free rate、無リスク資産の利回り)
- : 標準偏差
リスクフリーレートは「同じ期間に何もせず置いておけば得られる利回り」の代理として、短期国債金利などを使います。日本円の運用では、近年の短期金利が極めて低いため と近似する文献も多くあります。
日次から年率への換算
日次データから年率シャープを出すときは、平均を 252 倍、標準偏差を 倍します。
この変形は「リターンが i.i.d.(独立同分布)」という仮定で成り立つ近似です。実際のリターンには自己相関があるため厳密ではありませんが、実務の標準的な目安として使われます。
評価の目安
シャープレシオの絶対値は戦略・市場・期間に大きく依存するため、機械的な「合格ライン」はありません。それでも、よく言及される目安は次のとおりです。
| 年率シャープレシオ | 一般的な解釈 |
|---|---|
| 0 未満 | リスクフリーに劣る |
| 0 〜 0.5 | リスクに見合ったリターンが弱い |
| 0.5 〜 1.0 | 平均的・実用範囲 |
| 1.0 以上 | 良好 |
| 2.0 以上 | 非常に高い(過剰最適化を疑う材料にもなる) |
過去のバックテストで 3 や 4 を超える数値が出たときは、データリーク・過剰最適化・銘柄バイアスを最初に疑うのが安全です。
サンプルデータの準備
t 分布で日次リターン(1000 営業日)を生成します。実データを使う場合は、戦略の日次リターン列(#11-3「移動平均クロス戦略を 5 銘柄でバックテスト」 の strategy_ret のような列)に置き換えます。
import numpy as npimport pandas as pd
rng = np.random.default_rng(seed=11)n_days = 1000
returns = pd.Series( rng.standard_t(df=8, size=n_days) * 0.011 + 0.0004, # 日次平均をやや正に index=pd.date_range("2022-01-04", periods=n_days, freq="B"), name="ret",)年率シャープレシオの計算
リスクフリーレートを年率 0.5%(日本円短期金利の例)として、日次に変換した上で計算します。
TRADING_DAYS = 252
rf_annual = 0.005rf_daily = rf_annual / TRADING_DAYS
excess = returns - rf_dailymean_d = excess.mean()std_d = excess.std(ddof=0)
sharpe_annual = (mean_d / std_d) * np.sqrt(TRADING_DAYS) if std_d > 0 else float("nan")print(f"年率シャープレシオ: {sharpe_annual:.3f}")ddof=0 は母標準偏差です。ddof=1 の場合と数値はほぼ変わりませんが、慣例の違いに注意します。
関数化
複数の戦略・銘柄で繰り返し使うため、関数にまとめます。
def annualized_sharpe( returns: pd.Series, rf_annual: float = 0.0, trading_days: int = 252,) -> float: """日次リターン Series から年率シャープレシオを返す。
Notes ----- `ddof=0` で母標準偏差を取り、i.i.d. 仮定で sqrt(trading_days) 倍する。 """ rf_daily = rf_annual / trading_days excess = returns.dropna() - rf_daily std = excess.std(ddof=0) if std == 0: return float("nan") return (excess.mean() / std) * np.sqrt(trading_days)
print(annualized_sharpe(returns, rf_annual=0.005))ローリングシャープレシオ
戦略の安定性を確認したい場合、ローリング窓でシャープを計算します。
WINDOW = 252 # 約 1 年
rolling_mean = (returns - rf_daily).rolling(WINDOW).mean()rolling_std = (returns - rf_daily).rolling(WINDOW).std(ddof=0)rolling_sharpe = (rolling_mean / rolling_std) * np.sqrt(TRADING_DAYS)
print(rolling_sharpe.tail())特定期間だけ高いシャープが出ている戦略は、別の期間で機能しなくなる可能性が高い指標です。
ソルティノレシオ(参考)
シャープレシオは「上下両方の変動」をリスクと見なすため、上方変動も等しくペナルティになります。下方リスクのみを分母にした派生指標が ソルティノレシオ(Sortino ratio) です。
下方標準偏差 は、リスクフリーレート(またはゼロ)を下回った日のリターンだけから計算します。
def annualized_sortino(returns: pd.Series, rf_annual: float = 0.0, trading_days: int = 252) -> float: rf_daily = rf_annual / trading_days excess = returns.dropna() - rf_daily downside = excess[excess < 0] dd_std = np.sqrt((downside ** 2).mean()) # 下方リスク if dd_std == 0: return float("nan") return (excess.mean() / dd_std) * np.sqrt(trading_days)
print(annualized_sortino(returns, rf_annual=0.005))注意点
- 標準偏差ゼロの罠: ポジションを取らない期間が長いと標準偏差がゼロに近づき、シャープが発散します。
if std == 0のチェックが必須です - リスクフリーレートの設定: 円建ての場合は短期金利が低く、 で代用しても結果はほぼ変わりませんが、指標の比較対象がドル建ての場合は揃える こと
- i.i.d. 仮定の限界: 自己相関の強い戦略(モメンタム戦略など)では、年率換算が過大評価になる傾向があります
- 手数料・スリッページ込み: バックテストで使うリターン列は、コスト控除後にしてから計算します
生成AI へのプロンプト例
複数戦略の比較表を作りたいときの例です。
入力 DataFrame に次の列があります:- date (datetime64)- strategy (str)- ret (float, 日次リターン、コスト控除後)
各 strategy について、次の列を持つ DataFrame を返す関数sharpe_table(df, rf_annual=0.0) を書いてください。
戻り値の列:- strategy- n_days- ann_return: 年率平均リターン (mean * 252)- ann_vol: 年率ボラ (std * sqrt(252))- sharpe: 年率シャープレシオ- sortino: 年率ソルティノレシオ
要件:- pandas 2.2 / numpy 系- 標準偏差は ddof=0- 標準偏差が 0 の戦略は NaN を返す- docstring を日本語で書くまとめ
- シャープレシオは超過リターンを標準偏差で割った値
- 日次から年率への換算は「日次シャープ × 」
- 1.0 を超えれば良好、3.0 以上が出たら過剰最適化を疑う
- 標準偏差ゼロ・i.i.d. 仮定の限界を意識して使う
- ソルティノレシオは下方リスクのみを使う派生指標