امنح النموذج مدخلات قابلة للمقارنة
السعر 250 لا يعني شيئًا لنموذج: فهو يعتمد على السهم ويميل إلى الصعود مع الزمن، فلا تشبه قيم 2020 قيم 2010 أبدًا. وهندسة السمات تحول البيانات الخام إلى مدخلات قابلة للمقارنة عبر الزمن وبين الأصول: عوائد بدل أسعار وتقلب بدل نطاقات ودرجات معيارية بدل مستويات. وللسمات الجيدة أثر في النتيجة أكبر من اختيار النموذج، ويجب أن تصف كل سمة ما كان معروفًا في ذلك اليوم فقط.
import numpy as np
prices = np.array([100.0, 101.0, 99.0, 102.0, 104.0, 103.0, 106.0])
window = 3
momentum = np.full(len(prices), np.nan)
momentum[window:] = prices[window:] / prices[:-window] - 1 # the return over the last 3 days
print(np.round(momentum, 4)) # nan for the first 3 days: no price 3 days backنوافذ تنظر إلى الوراء
معظم السمات إحصاءات نافذة خلفية: آخر n قيمة حتى اليوم متضمنًا. فالزخم يقارن اليوم بما قبل n يومًا؛ والتقلب المتحرك هو الانحراف المعياري لآخر n عائد؛ والدرجة المعيارية المتحركة تقول بكم انحراف معياري للنافذة يبعد اليوم عن متوسطها. وبـsliding_window_view نحصل على كل النوافذ كصفوف في مصفوفة ونحسب الإحصاء على محور بلا حلقة. وأول n - 1 يوم بلا نافذة كاملة وتبقى nan.
import numpy as np
prices = np.array([100.0, 101.0, 99.0, 102.0, 104.0, 103.0, 106.0])
returns = np.diff(prices) / prices[:-1]
window = 3
windows = np.lib.stride_tricks.sliding_window_view(returns, window) # every 3-day window as a row
vol = np.full(len(returns), np.nan)
vol[window - 1:] = windows.std(axis=1, ddof=1)
print(np.round(vol, 4)) # nan nan then one value per complete window
p_windows = np.lib.stride_tricks.sliding_window_view(prices, window)
z = np.full(len(prices), np.nan)
z[window - 1:] = (prices[window - 1:] - p_windows.mean(axis=1)) / p_windows.std(axis=1, ddof=1)
print(np.round(z, 3)) # how unusual each price is for its last 3 days# Features put prices of different stocks on the same footing
import numpy as np
cheap = np.array([5.0, 5.2, 5.1, 5.4, 5.5])
dear = np.array([500.0, 520.0, 510.0, 540.0, 550.0]) # ten times the price, the same movements
print(cheap[-1] / cheap[-3] - 1, dear[-1] / dear[-3] - 1) # identical momentum, about 0.0784 for both
print(dear[-1] - dear[-3], cheap[-1] - cheap[-3]) # price changes (40.0 and 0.4) would not be comparableالهدف ينظر إلى الأمام
ما يتنبأ به النموذج هو الهدف، وعادة عائد الأيام h التالية: prices[t + h] / prices[t] - 1. وهو العمود الوحيد المسموح له بالنظر إلى الأمام، وقيمته nan لآخر h يوم لأن المستقبل لم يحدث بعد؛ فاحذف هذه الصفوف قبل التدريب. ومن الأخطاء الشائعة سمة تحتوي الهدف بالصدفة، كنافذة مركزية أو إحصاء محسوب على كل البيانات أو عائد مستقبلي أُعيد استخدامه مدخلًا. والاختبار بسيط: يجب ألا تتغير سمة اليوم t عند إضافة أيام لاحقة.
import numpy as np
def forward_return(prices, horizon):
out = np.full(len(prices), np.nan)
out[:-horizon] = prices[horizon:] / prices[:-horizon] - 1
return out
def momentum(prices, window):
out = np.full(len(prices), np.nan)
out[window:] = prices[window:] / prices[:-window] - 1
return out
prices = np.array([100.0, 101.0, 99.0, 102.0, 104.0, 103.0, 106.0])
print(np.round(momentum(prices, 2), 4)) # looks back: known on each day
print(np.round(forward_return(prices, 2), 4)) # looks forward: the target, nan for the last 2 days
# The prefix test: a feature computed on the first 5 days equals the first 5 values of the full computation
print(np.allclose(momentum(prices[:5], 2), momentum(prices, 2)[:5], equal_nan=True)) # Truet، وتحقق منها باختبار البادئة أعلاه.- حوّل الأسعار إلى سمات قابلة للمقارنة: زخم
prices[t] / prices[t - n] - 1وتقلب متحرك ودرجة معيارية متحركة، كل منها على نافذة خلفية تشمل اليوم. - يعطي
sliding_window_viewكل نافذة صفًّا فلا تحتاج الإحصاءات إلى حلقة؛ وأولn - 1يوم تبقىnan. - العائد المستقبلي
prices[t + h] / prices[t] - 1هو الهدف لا سمة أبدًا؛ تحقق من كل سمة باختبار البادئة.
التالي: التحقق المتدرج (walk-forward)، اختبار نموذج بالترتيب الذي يجري به الزمن فعلًا.
المختبر: ما الذي يُقيَّم
- قِس الاتجاه باستخدام
momentum25 نقطة - تتبّع المخاطر الأخيرة باستخدام
rolling_vol25 نقطة - حدّد مدى غرابة اليوم باستخدام
rolling_zscore25 نقطة - عرّف الهدف باستخدام
forward_return25 نقطة