إعادة تشغيل التاريخ
يسأل الاختبار الرجعي عمّا كانت ستربحه قاعدة تداول على بيانات سابقة. والنسخة المتجهة تعمل على مصفوفات كاملة دفعة واحدة: بلا حلقة على الأيام، فقط إشارة لكل يوم ومركز لكل يوم وضرب واحد في العوائد. وهي قصيرة وسريعة وسهلة التحقق، فهي أول أداة لغربلة الأفكار. وحدودها (بلا أوامر ولا نقد ولا تنفيذ جزئي) هي بالضبط ما يضيفه الموضوع التالي.
import numpy as np
returns = np.array([0.01, 0.02, -0.01, 0.03]) # the asset's daily returns
signal = np.array([2.5, 0.4, -1.0, 0.0]) # any score: positive = buy, negative = sell
position = np.sign(signal) # +1, +1, -1, 0
print(position)تأخير يوم واحد
الإشارة المحسوبة من إغلاق اليوم لا يمكن تداولها إلا بعد هذا الإغلاق، فتربح عائد الغد لا عائد اليوم. وفي المصفوفات هذا إزاحة: strategy[t] = position[t - 1] * returns[t] ولا مركز قبل اليوم الأول. ونسيان ذلك يتيح للاستراتيجية أن تتداول بعائد رأته أصلًا فيبدو الاختبار رائعًا. والإزاحة سطر واحد، وهو أهم سطر في الدالة كلها.
# Yesterday's position earns today's return
import numpy as np
returns = np.array([0.01, 0.02, -0.01, 0.03])
position = np.array([1.0, 1.0, -1.0, 0.0])
strategy = np.zeros(len(returns))
strategy[1:] = position[:-1] * returns[1:]
print(strategy) # [ 0. 0.02 -0.01 -0.03]
print(position * returns) # the same-day version: [ 0.01 0.02 0.01 0. ] looks better, and is cheatingرأس المال والإحصاءات
تتراكم العوائد اليومية: قيمة كل يوم هي البداية مضروبة في الحاصل التراكمي لـ1 + العائد. ومن منحنى رأس المال هذا تلخص ثلاثة أرقام الاختبار. العائد الكلي هو القيمة النهائية ناقص 1. ونسبة شارب هي متوسط العائد اليومي مقسومًا على انحرافه المعياري مضروبًا في sqrt(periods) للتسنية. وأقصى تراجع هو أسوأ هبوط عن قمة سابقة، equity / running max - 1، أي الخسارة التي كان المستثمر سيعيشها.
# Equity curve and the three statistics for a short run
import numpy as np
strategy = np.array([0.0, 0.02, -0.01, -0.03, 0.04])
equity = np.cumprod(1 + strategy)
print(np.round(equity, 5))
print(round(float(equity[-1] - 1), 5)) # total return
print(round(float(strategy.mean() / strategy.std(ddof=1) * np.sqrt(252)), 3)) # annualised Sharpe
print(round(float((equity / np.maximum.accumulate(equity) - 1).min()), 5)) # maximum drawdown# A moving-average rule on a simulated price path, with its statistics
import numpy as np
rng = np.random.default_rng(7)
returns = rng.normal(0.0004, 0.01, size=500)
prices = 100 * np.cumprod(1 + returns)
fast = np.convolve(prices, np.ones(10) / 10, mode="valid")[-(len(prices) - 49):] # 10-day mean, aligned
slow = np.convolve(prices, np.ones(50) / 50, mode="valid") # 50-day mean
position = np.zeros(len(prices))
position[49:] = np.sign(fast - slow)
strategy = np.zeros(len(returns))
strategy[1:] = position[:-1] * returns[1:]
equity = np.cumprod(1 + strategy)
drawdown = equity / np.maximum.accumulate(equity) - 1
print(round(float(equity[-1] - 1), 4), round(float(strategy.mean() / strategy.std(ddof=1) * np.sqrt(252)), 4), round(float(drawdown.min()), 4))- المركز = إشارة السيجنال؛ وعائد الاستراتيجية
position[t - 1] * returns[t]: عائد اليوم يعود إلى قرار الأمس. - رأس المال هو
start * cumprod(1 + r)؛ ويلخصه العائد الكلي وشارب السنوي وأقصى تراجع. - الاختبارات المتجهة غرابيل سريعة: تتجاهل التكاليف والتنفيذ، وكثرة التجارب على تاريخ واحد تنتج حظًا.
التالي: اختبار رجعي مدفوع بالأحداث، إعادة تشغيل التاريخ شمعة بشمعة مع النقد والأوامر والتنفيذ.
المختبر: ما الذي يُقيَّم
- حوّل الإشارة إلى مركز باستخدام
signal_to_position25 نقطة - اربح بمركز الأمس باستخدام
strategy_returns25 نقطة - راكِم الأيام باستخدام
equity_curve25 نقطة - لخّص الاختبار باستخدام
backtest_stats25 نقطة