シグナルは何かを予測するか
シグナルを使って戦略を作る前に、それが正しい方向を指しているかを確かめます。毎日、シグナルは各銘柄にスコアを付け、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点