نسبة التغير
يصعب مقارنة الأسعار: حركة بخمسة دولارات تعني شيئًا في سهم بعشرين دولارًا وشيئًا آخر في سهم بخمسمئة. العائد البسيط يزيل المقياس. هو تغير السعر كنسبة من السعر السابق، فيعني +2% الشيء نفسه في أي أصل.
في pandas ينقل prices.shift(1) كل قيمة خطوة للأسفل، فيرى كل صف السعر السابق. والقسمة عليه تحسب كل العوائد دفعة واحدة، والأول NaN لأنه لا يوجد سعر سابق. ويفعل prices.pct_change() الشيء نفسه باستدعاء واحد.
import pandas as pd
prices = pd.Series([100.0, 102.0, 100.98, 103.0], index=["Mon", "Tue", "Wed", "Thu"])
returns = prices / prices.shift(1) - 1
print(returns.round(4))
# Mon NaN
# Tue 0.02
# Wed -0.01
# Thu 0.02
# dtype: float64
print(prices.pct_change().round(4).tolist()) # [nan, 0.02, -0.01, 0.02]العوائد تتراكب ولا تُجمع
يوم بنسبة +2% يليه يوم بنسبة -1% لا يعطي +1% خلال يومين. فكل عائد يُطبَّق على سعر بداية مختلف، لذلك تُضرب عوامل النمو 1 + r: العائد الكلي هو (1 + r1) * (1 + r2) * ... - 1. تجاهل NaN في الصف الأول (يتجاهله prod) فتساوي النتيجة سعر النهاية مقسومًا على سعر البداية ناقص واحد. وجمع العوائد يقترب من ذلك لكنه خاطئ، والفجوة تكبر مع التقلب.
# Compounding the returns gives the same total as the end price over the start price
returns = prices.pct_change().dropna()
print(round((1 + returns).prod() - 1, 6)) # 0.03
print(round(prices.iloc[-1] / prices.iloc[0] - 1, 6)) # 0.03
print(round(returns.sum(), 6)) # 0.030004 close, but not the same
print((1 + returns).cumprod().round(4).tolist()) # [1.02, 1.0098, 1.03] the growth of 1 dollar# Gains and losses are not symmetric: +10% then -10% does not get you back
up, down = 0.10, -0.10
print(round((1 + up) * (1 + down) - 1, 4)) # -0.01
# After a -50% loss you need +100% to recover
print(1 / (1 - 0.5) - 1) # 1.0عبر الأصول وعبر السنوات
تُجمع العوائد البسيطة عبر الأصول: عائد المحفظة في فترة واحدة هو مجموع الوزن * العائد لمكوناتها. ومع Series يطابق weights * returns العناصر بالتسمية فلا يهم ترتيب الرموز (استخدام .values يفقدك هذه الحماية). أما عبر الزمن فلا تُجمع بل تتراكب، ولذلك تُرفع عامل النمو إلى القوة أيام_التداول / الأيام لذكر معدل سنوي انطلاقًا من فترة أقصر.
# Weights times returns, matched by label (the order of the symbols differs on purpose)
weights = pd.Series({"AAPL": 0.5, "MSFT": 0.3, "TSLA": 0.2})
day = pd.Series({"TSLA": -0.03, "AAPL": 0.012, "MSFT": 0.004})
print(round((weights * day).sum(), 4)) # 0.0012 correct: AAPL with AAPL, TSLA with TSLA
print(round((weights.values * day.values).sum(), 4)) # -0.0106 wrong: position by position, TSLA met AAPL's weight# Annualising: 5% in 60 trading days, if it kept compounding at that pace for a year of 252 days
total, days = 0.05, 60
print(round((1 + total) ** (252 / days) - 1, 4)) # 0.2274daily_returns.- العائد البسيط هو
السعر / السعر السابق - 1:prices / prices.shift(1) - 1أوprices.pct_change()؛ والعنصر الأولNaN. - عبر الزمن تتراكب العوائد:
(1 + returns).prod() - 1وليسreturns.sum()أبدًا. - عبر الأصول تُجمع بالأوزان:
(weights * returns).sum()مع المطابقة بالتسمية.
التالي: العوائد اللوغاريتمية، بديل يُجمع عبر الزمن وهو الخيار المعتاد في الإحصاء.
المختبر: ما الذي يُقيَّم
- حوّل الأسعار إلى عوائد الفترات باستخدام
simple_returns25 نقطة - اجمع العوائد تركيبيًا في إجمالي باستخدام
cumulative_return25 نقطة - رجّح عوائد الأصول في رقم واحد باستخدام
portfolio_return25 نقطة - عبّر عن العائد كمعدل سنوي باستخدام
annualized_return25 نقطة