معرفة أكثر من اللازم
انحياز النظر إلى المستقبل هو أن يستخدم الاختبار الرجعي معلومات لم تكن متاحة وقت القرار. وهو أشيع سبب لأن تبدو استراتيجية رائعة على الورق وتخسر في الواقع. وقلّما يكون سطرًا واضحًا؛ بل يختبئ في المحاذاة (إشارة إغلاق اليوم تُتداول عند هذا الإغلاق)، وفي إحصاءات تُحسب على العينة كلها، وفي بيانات صُححت لاحقًا، وفي مؤشرات اختيرت بعد وقوع الأحداث. والعلاج عادة: لكل رقم يستخدمه قرار اسأل متى أصبح معروفًا.
import numpy as np
rng = np.random.default_rng(5)
returns = rng.normal(0, 0.01, size=250) # pure noise: no strategy can really make money here
signal = returns.copy() # "signal" = the return of the same day
position = np.sign(signal)
biased = np.prod(1 + position * returns) - 1 # trades on the return it is about to earn
honest_position = np.concatenate([[0.0], position[:-1]])
honest = np.prod(1 + honest_position * returns) - 1
print(round(float(biased), 2), round(float(honest), 2)) # 6.0 0.01: a 600% "return" on noise, against 1%المحاذاة: تأخير يوم
علاج انحياز المحاذاة هو التأخير: قيمة حُسبت في اليوم t لا تُستخدم إلا من اليوم t + 1. وفي المصفوفات هو إزاحة مع أصفار في البداية، out[t] = signal[t - periods]. والاختبار فظ. فسلسلة الضجيج لا ميزة فيها، فأي قاعدة تجني ثروة من الضجيج تغش. قارن النسخة المنحازة بالمتأخرة على البيانات نفسها، والفرق هو حجم الانحياز.
# lag_signal: delay a series, with zeros where there is nothing to look back at
import numpy as np
def lag_signal(signal, periods=1):
out = np.zeros(len(signal))
if periods < len(signal):
out[periods:] = signal[:len(signal) - periods]
return out
s = np.array([1.0, -1.0, 1.0, 1.0, -1.0])
print(lag_signal(s)) # [ 0. 1. -1. 1. 1.]
print(lag_signal(s, 2)) # [ 0. 0. 1. -1. 1.]# trailing_mean: the mean of the last `window` values, using only today and the past
import numpy as np
def trailing_mean(values, window):
total = np.cumsum(values)
out = np.full(len(values), np.nan)
out[window - 1:] = (total[window - 1:] - np.concatenate([[0.0], total[:len(values) - window]])) / window
return out
x = np.array([1.0, 2.0, 6.0, 7.0])
print(trailing_mean(x, 2)) # [ nan 1.5 4. 6.5]
print(trailing_mean(x[:3], 2)) # [ nan 1.5 4. ]: adding day 4 changed nothing before itيجب أن تنمو الإحصاءات مع الزمن
التسرب الثاني خفي. توحيد سلسلة بمتوسط العينة كلها وانحرافها يستخدم أرقامًا لا توجد إلا في النهاية: فالقيم الأولى تُحجَّم بمعلومات من المستقبل. استخدم الماضي فقط: نافذة متحركة (آخر n قيمة حتى اليوم) أو متوسعة (كل شيء حتى اليوم). والاختبار لأي متغير كهذا: حسابه على أول k يوم فقط يجب أن يعطي أول k قيمة من الحساب الكامل.
# Whole-sample z-scores leak the future; expanding ones do not
import numpy as np
x = np.array([1.0, 3.0, 2.0, 8.0, 4.0])
whole = (x - x.mean()) / x.std()
n = np.arange(1, len(x) + 1)
mean = np.cumsum(x) / n
var = np.cumsum(x ** 2) / n - mean ** 2
with np.errstate(divide="ignore", invalid="ignore"):
expanding = (x - mean) / np.sqrt(var)
expanding[0] = np.nan
print(np.round(whole, 3))
print(np.round(expanding, 3))
# Prefix test: computing on x[:3] gives the first three expanding values, but not the first three whole-sample ones
print(np.round((x[:3] - x[:3].mean()) / x[:3].std(), 3))- انحياز النظر إلى المستقبل هو استخدام أي معلومة قبل معرفتها؛ والمحاذاة هي صورته الكلاسيكية: إشارة اليوم
tتُتداول من اليومt + 1. - يجب أن تُبنى المتغيرات من الماضي فقط: نوافذ متحركة أو متوسعة، لا متوسط العينة كلها أو انحرافها.
- اختبار البادئة: متغير أو اختبار على أول
kيوم يجب أن يساوي أولkنتيجة من التشغيل الكامل؛ وسلسلة الضجيج يجب ألا تربح.
التالي: تكاليف المعاملات، أول ما يجب أن يدفعه اختبار رجعي نظيف.
المختبر: ما الذي يُقيَّم
- أخّر الإشارة باستخدام
lag_signal25 نقطة - قِس الغش باستخدام
biased_vs_honest25 نقطة - احسب متوسط الماضي فقط باستخدام
trailing_mean25 نقطة - وحِّد دون تلصص باستخدام
expanding_zscore25 نقطة