σμimplied vol · KKmax(S−K,0)udβρᵢⱼfat tailsPnL · drawdownyield · TdSₜ = μSₜ dt + σSₜ dWₜC = S·N(d₁) − K·e⁻ʳᵀ·N(d₂)σₚ² = wᵀΣwd₁ = [ln(S/K) + (r + σ²⁄2)T] ⁄ σ√T∂V/∂t + ½σ²S²·∂²V/∂S² + rS·∂V/∂S − rV = 0Sharpe = (Rₚ − R_f) ⁄ σₚVaR₉₉ = μ − 2.33σρ = Cov(X,Y) ⁄ σₓσᵧE[Rₚ] = Σ wᵢ·E[Rᵢ]φ(x) = e^(−x²/2) ⁄ √2πE[Rᵢ] = R_f + βᵢ(E[Rₘ] − R_f)dXₜ = θ(μ − Xₜ)dt + σ dWₜσ²ₜ = ω + α·ε²ₜ₋₁ + β·σ²ₜ₋₁C − P = S − K·e⁻ʳᵀΔ = ∂V/∂S Γ = ∂²V/∂S²p = (e^(rΔt) − d) ⁄ (u − d)rₜ = ln(Pₜ ⁄ Pₜ₋₁)E[Xₜ₊₁ | ℱₜ] = Xₜf* = (bp − q) ⁄ bz = (x − μ) ⁄ σP(N=k) = λᵏe^(−λ) ⁄ k!Θ = −∂V/∂Tν = S·φ(d₁)·√Tmin wᵀΣw s.t. wᵀμ = μ*∫Σ∂∞λ
σμimplied vol · KKmax(S−K,0)udβρᵢⱼfat tailsPnL · drawdownyield · TdSₜ = μSₜ dt + σSₜ dWₜC = S·N(d₁) − K·e⁻ʳᵀ·N(d₂)σₚ² = wᵀΣwd₁ = [ln(S/K) + (r + σ²⁄2)T] ⁄ σ√T∂V/∂t + ½σ²S²·∂²V/∂S² + rS·∂V/∂S − rV = 0Sharpe = (Rₚ − R_f) ⁄ σₚVaR₉₉ = μ − 2.33σρ = Cov(X,Y) ⁄ σₓσᵧE[Rₚ] = Σ wᵢ·E[Rᵢ]φ(x) = e^(−x²/2) ⁄ √2πE[Rᵢ] = R_f + βᵢ(E[Rₘ] − R_f)dXₜ = θ(μ − Xₜ)dt + σ dWₜσ²ₜ = ω + α·ε²ₜ₋₁ + β·σ²ₜ₋₁C − P = S − K·e⁻ʳᵀΔ = ∂V/∂S Γ = ∂²V/∂S²p = (e^(rΔt) − d) ⁄ (u − d)rₜ = ln(Pₜ ⁄ Pₜ₋₁)E[Xₜ₊₁ | ℱₜ] = Xₜf* = (bp − q) ⁄ bz = (x − μ) ⁄ σP(N=k) = λᵏe^(−λ) ⁄ k!Θ = −∂V/∂Tν = S·φ(d₁)·√Tmin wᵀΣw s.t. wᵀμ = μ*∫Σ∂∞λ
uantCraft Academyロードマップ
Ctrl K
AO
+1.2k 人の学習者
ログイン参加する ↗
ロードマップ
AO
+1.2k 人の学習者
ログイン
  • English
  • Français
  • Español
  • العربية
  • Português
  • Deutsch
  • 日本語
  • 中文
Python ロードマップ›モジュール 7 · アルファ研究とリスク›トピック 7.3·~55 分上級

情報係数

ロードマップ上の位置モジュール 7/11 · トピック 3/9
7.3 情報係数
—ラボスコア— / 100合計XP—連続日数—

シグナルは何かを予測するか

シグナルを使って戦略を作る前に、それが正しい方向を指しているかを確かめます。毎日、シグナルは各銘柄にスコアを付け、1日後には各銘柄にリターンが出ます。情報係数(IC)は、銘柄間でのスコアとその後のリターンの相関です。-1から1の値を取り、0なら予測力はありません。実際のシグナルは弱く、ICが0.02〜0.05でも、多くの銘柄と多くの日で繰り返されるため有用です。

import numpy as np

signal = np.array([0.3, -1.2, 0.8, 0.1, 1.5])                  # today's score for five stocks
returns = np.array([0.01, -0.02, 0.005, -0.004, 0.03])         # their returns over the next day

s = signal - signal.mean()
r = returns - returns.mean()
ic = np.sum(s * r) / np.sqrt(np.sum(s ** 2) * np.sum(r ** 2))   # the Pearson correlation
print(round(float(ic), 4))
print(round(float(np.corrcoef(signal, returns)[0, 1]), 4))     # the same number

順位IC:外れ値に強い

ピアソン相関は極端な値に左右されます。スコアが桁外れの銘柄や、1日で40%跳ねた銘柄が1つあるだけで大きく動きます。順位ICは各配列を値の順位(最小が1)に置き換えて、順位同士の相関を取ります。数値の大きさに関係なく、シグナルが上位に置いた銘柄が実際に上位になるかだけを問うので、シグナルへのどんな単調増加変換にも影響されません。多くのクオンツチームが報告するのはこちらで、スピアマン相関のことです。

# The rank of each value: argsort gives the order, assigning 1..n through it gives the ranks
import numpy as np

values = np.array([30.0, 10.0, 20.0, 5.0])
order = np.argsort(values)                       # positions from smallest to largest: [3 1 2 0]
ranks = np.empty(len(values))
ranks[order] = np.arange(1, len(values) + 1)
print(order, ranks)                              # [3 1 2 0] [4. 2. 3. 1.]
# Ranks via argsort, and a rank IC that shrugs off an extreme value
import numpy as np

def ranks(values):
    order = np.argsort(values)
    out = np.empty(len(values))
    out[order] = np.arange(1, len(values) + 1)
    return out

def ic(a, b):
    a, b = a - a.mean(), b - b.mean()
    return float(np.sum(a * b) / np.sqrt(np.sum(a ** 2) * np.sum(b ** 2)))

signal = np.array([0.3, -1.2, 0.8, 0.1, 1.5])
returns = np.array([0.01, -0.02, 0.005, -0.004, 0.03])
print(ranks(signal))                                           # [3. 1. 4. 2. 5.]
print(round(ic(signal, returns), 4), round(ic(ranks(signal), ranks(returns)), 4))

signal_with_outlier = signal.copy()
signal_with_outlier[4] = 1000.0                                # one absurd score
print(round(ic(signal_with_outlier, returns), 4), round(ic(ranks(signal_with_outlier), ranks(returns)), 4))

時間をまたぐ安定性:ICのインフォメーション・レシオ

1日のICはノイズが大きいので、毎日計算して系列を作り、平均とその安定性の両方を見ます。ICIRは平均ICをその標準偏差で割ったもので、少数の日に大きく当たるシグナルより、毎日少しずつ当たるシグナルを高く評価します。シグナル自体のシャープ・レシオのようなものです。日次ICの系列が平均0.03、標準偏差0.06なら、ICIRは0.5です。

# A daily rank-IC series for a weak signal, and its ICIR
import numpy as np

def ranks(values):
    out = np.empty(len(values))
    out[np.argsort(values)] = np.arange(1, len(values) + 1)
    return out

def ic(a, b):
    a, b = a - a.mean(), b - b.mean()
    return float(np.sum(a * b) / np.sqrt(np.sum(a ** 2) * np.sum(b ** 2)))

rng = np.random.default_rng(8)
daily_ics = []
for day in range(250):
    signal = rng.normal(size=300)                              # 300 stocks
    returns = 0.01 * (0.05 * signal + rng.normal(size=300))    # the signal explains a sliver of the return
    daily_ics.append(ic(ranks(signal), ranks(returns)))
daily_ics = np.array(daily_ics)
print(round(float(daily_ics.mean()), 4), round(float(daily_ics.std(ddof=1)), 4))
print(round(float(daily_ics.mean() / daily_ics.std(ddof=1)), 3))
良いICは小さく、大きすぎれば警告市場はほぼ効率的なので、ICが0.1を超えるのは発見というよりバグである可能性が高いです。先読みバイアス、予測対象のリターンから作ったシグナル、データの誤りを疑ってください。実際のICは期間が延びると減衰し、コスト控除後の値のほうが重要です。計算は必ず、シグナルを当てはめていないデータで行いましょう。
  • ICは、今日の銘柄ごとのシグナルとその後のリターンの相関。0.02〜0.05は有用、0.1超は疑わしい
  • 順位ICは順位同士の相関(argsort を2回)。外れ値に強く、シグナルのどんな単調増加変換にも影響されない
  • ICIR=多数の日にわたる平均IC/ICの標準偏差。安定した腕を評価する。評価は必ず標本外で行う

次はヒストリカルVaRです。最悪の日を除いてポートフォリオが超えないはずの損失額です。

ラボ:採点される内容

  • ranks で値に順位を付ける25点
  • ic でシグナルと結果の相関を測る25点
  • rank_ic で外れ値の影響を避ける25点
  • icir で安定性を判断する25点
エクスプローラー · lab-95-1
▾ lab-95-1
…
bash · ブラウザ内
learner@quantcraft:~/lab-95-1$
Pythonは未読み込みPythonLn 1, Col 1Spaces: 4UTF-8

このトピックをリセットしますか?

lab-95-1 の内容はすべてスターターファイルに戻り、ターミナルも消去されます。現在の解答は失われ、元に戻せません。

リセットされるもの
  • main.py → スターターコード
  • ターミナル履歴とテスト出力
  • 未提出の変更
保持されるもの
  • ラボの最高スコア
  • XPと連続日数
  • 講義の進捗とメモ

コードを実行するにはログインしてください。

レクチャーはすべて無料で読めます。コードの実行、テスト、ラボの提出にはアカウントの作成かログインが必要です。

  • ブラウザ内IDEとbashターミナル。インストール不要
  • 即時採点、XP、ロードマップでの現在地
アカウントを作成ログイン
または
GitHubで続ける