نافذة تنزلق
سعر اليوم لا يعني الكثير وحده؛ يقارنه المتداولون بالماضي القريب. تحتفظ النافذة المتحركة بآخر n ملاحظة وتتقدم خطوة خطوة، فيحصل كل تاريخ على إحصاء محسوب من تاريخه القريب: متوسط متحرك أو تقلب حديث أو قمة حديثة. وفي pandas تصف series.rolling(n) النافذة، ويحسبها إحصاء مثل .mean() عند كل موضع دفعة واحدة.
import pandas as pd
prices = pd.Series(
[100.0, 102, 101, 105, 107, 106, 110],
index=pd.bdate_range("2026-03-02", periods=7),
name="close",
)
moving_average = prices.rolling(3).mean()
print(moving_average.round(2).tolist())
# [nan, nan, 101.0, 102.67, 104.33, 106.0, 107.67]
print(prices.rolling(3).max().tolist()) # [nan, nan, 102.0, 105.0, 107.0, 107.0, 110.0] the 3-day highبداية السلسلة
المواضع الأولى n - 1 ليس لها نافذة كاملة بعد، لذا تكون النتيجة NaN فيها. وهذا صادق: متوسط سعرين ليس متوسط 3 أيام. وإن أردت قيمة أبكر فعلًا فإن min_periods=1 تحسب بما هو متاح، وتستخدم expanding() كل ما منذ البداية بدل نافذة ثابتة. والإحصاءات التي تحتاج نقاطًا عدة، كالانحراف المعياري، تبقى NaN حتى تصل ملاحظات كافية. والسلسلة التي تبدأ أصلًا بـ NaN، مثل العوائد البسيطة، تؤخر أول نافذة صالحة خطوة.
# min_periods=1: use whatever is available at the start
print(prices.rolling(3, min_periods=1).mean().round(2).tolist()) # [100.0, 101.0, 101.0, 102.67, 104.33, 106.0, 107.67]
# expanding(): everything since the first day, not a fixed window
print(prices.expanding().mean().round(2).tolist()) # [100.0, 101.0, 101.0, 102.0, 103.0, 103.5, 104.43]
# Rolling volatility of the returns: the first return is NaN, so three returns exist only from the 4th day
returns = prices.pct_change()
print(returns.rolling(3).std().round(4).tolist()) # [nan, nan, nan, 0.0249, 0.0248, 0.0246, 0.0237]من النوافذ إلى الإشارات
تصبح الإحصاءات المتحركة إشارات تداول عند مقارنتها. يبيّن المعيار z كم يبعد سعر اليوم عن متوسطه الحديث بوحدات انحرافه المعياري الحديث: (price - rolling mean) / rolling std. أنشئ كائن النافذة مرة واحدة (r = prices.rolling(3)) واستخدمه مرتين. ويقارن تقاطع المتوسطات المتحركة متوسطًا سريعًا بآخر بطيء: ما دام السريع أعلى فالاتجاه الحديث صاعد. تعطي المقارنة قيمًا منطقية، ويحوّلها astype("int64") إلى إشارة 0/1. والمقارنات مع NaN تساوي False، فتبقى الإشارة 0 حتى يوجد المتوسطان.
# z-score of each price against its own 3-day window
rolling = prices.rolling(3)
zscore = (prices - rolling.mean()) / rolling.std()
print(zscore.round(2).tolist()) # [nan, nan, 0.0, 1.12, 0.87, 0.0, 1.12]# Moving-average crossover: 1 while the 2-day average is above the 4-day average
fast = prices.rolling(2).mean()
slow = prices.rolling(4).mean()
signal = (fast > slow).astype("int64")
print(signal.tolist()) # [0, 0, 0, 1, 1, 1, 1]
# Trade on the next day: the signal of today's close cannot be traded before tomorrow
print(signal.shift(1).fillna(0).astype("int64").tolist()) # [0, 0, 0, 0, 1, 1, 1]center=True فتضع الصف الحالي في وسط النافذة فتستخدم أسعارًا مستقبلية: تبدو رائعة على الرسم ولا يمكن التداول بها. والإشارة المحسوبة من إغلاق اليوم يجب تطبيقها غدًا، ومن هنا shift(1).series.rolling(n).mean()(أوstd()أوmax()...) تحسب إحصاءً على آخرnملاحظة عند كل موضع.- العناصر الأولى
n - 1تكونNaN؛ ويغيّرmin_periodsوexpanding()ذلك عمدًا. - تحوّل المعايير z وتقاطعات المتوسطات المتحركة النوافذ إلى إشارات؛ أزِح الإشارة قبل التداول بها وتجنب
center=True.
التالي: التعامل مع البيانات المفقودة، أي ما نفعله بقيم NaN التي تحتويها جداول الأسعار الحقيقية دائمًا.
المختبر: ما الذي يُقيَّم
- امسح الأسعار باستخدام
moving_average25 نقطة - قِس المخاطر الأخيرة باستخدام
rolling_volatility25 نقطة - قيّس كل سعر مقابل نافذته باستخدام
rolling_zscore25 نقطة - حوّل متوسطين إلى إشارة باستخدام
ma_crossover25 نقطة