كيف تساعد Python في استبدال الاستشاريين الماليين

لمواصلة المقالة حول مخاطر التنويع المفرط ، سنقوم بإنشاء أدوات مفيدة لاختيار الأسهم. بعد ذلك ، سنقوم بإعادة توازن بسيطة وإضافة الشروط الفريدة للمؤشرات الفنية ، والتي غالبًا ما تفتقر إلى الخدمات الشعبية. ثم قارن العوائد على الأصول الفردية والمحافظ المختلفة.

في كل هذا نستخدم Pandas وتقليل عدد الدورات. اجمع السلاسل الزمنية وارسم الرسوم البيانية. دعونا نتعرف على المؤشرات المتعددة وسلوكهم. وكل هذا في جوبيتر في بيثون 3.6.

إذا كنت تريد القيام بشيء جيد ، افعل ذلك بنفسك.
فرديناند بورش

ستسمح لك الأداة الموضحة بتحديد الأصول المثالية للمحفظة واستبعاد الأدوات التي يفرضها المستشارون. لكننا سنرى الصورة الكبيرة فقط - دون أخذ السيولة في الاعتبار ، ووقت تجنيد المناصب ، وعمولات السمسرة وتكلفة سهم واحد. بشكل عام ، مع إعادة التوازن الشهرية أو السنوية لكبار الوسطاء ، ستكون التكاليف غير ذات أهمية. ومع ذلك ، قبل التطبيق ، يجب الاستمرار في فحص الاستراتيجية المختارة في الاختبار الخلفي الذي يحركه الحدث ، على سبيل المثال ، Quantopian (QP) ، من أجل القضاء على الأخطاء المحتملة.

لماذا لا على الفور في QP؟ الوقت. هناك ، يستمر أبسط اختبار حوالي 5 دقائق. وسيتيح لك الحل الحالي التحقق من مئات الاستراتيجيات المختلفة بشروط فريدة في دقيقة واحدة.

تحميل البيانات الخام


لتحميل البيانات ، اتبع الطريقة الموضحة في هذه المقالة . أستخدم PostgreSQL لتخزين الأسعار اليومية ، ولكنها الآن مليئة بالمصادر المجانية التي يمكنك من خلالها إنشاء DataFrame الضروري.

كود تحميل سجل الأسعار من قاعدة البيانات متاح في المستودع. سيكون الرابط في نهاية المقال.

هيكل DataFrame


عند العمل مع سجل الأسعار ، لتجميع مريح والوصول إلى جميع البيانات ، فإن أفضل حل هو استخدام مؤشر متعدد (MultiIndex) مع التاريخ والمؤشرات.

df = df.set_index(['dt', 'symbol'], drop=False).sort_index() df.tail(len(df.index.levels[1]) * 2) 


الصورة

باستخدام مؤشر متعدد ، يمكننا الوصول بسهولة إلى سجل الأسعار بالكامل لجميع الأصول ويمكننا تجميع الصفيف بشكل منفصل حسب التاريخ والأصل. يمكننا أيضًا الحصول على سجل أسعار أحد الأصول.

في ما يلي مثال لكيفية تجميع السجل بسهولة حسب الأسبوع والشهر والسنة. ولإظهار كل هذا على الرسوم البيانية لقوى الباندا:

 #      agg_rules = { 'dt': 'last', 'symbol': 'last', 'open': 'first', 'high': 'max', 'low': 'min', 'close': 'last', 'volume': 'sum', 'adj': 'last' } level_values = df.index.get_level_values #  fig = plt.figure(figsize=(15, 3), facecolor='white') df.groupby([pd.Grouper(freq='W', level=0)] + [level_values(i) for i in [1]]).agg( agg_rules).set_index(['dt', 'symbol'], drop=False ).close.unstack(1).plot(ax=fig.add_subplot(131), title="Weekly") df.groupby([pd.Grouper(freq='M', level=0)] + [level_values(i) for i in [1]]).agg( agg_rules).set_index(['dt', 'symbol'], drop=False ).close.unstack(1).plot(ax=fig.add_subplot(132), title="Monthly") df.groupby([pd.Grouper(freq='Y', level=0)] + [level_values(i) for i in [1]]).agg( agg_rules).set_index(['dt', 'symbol'], drop=False ).close.unstack(1).plot(ax=fig.add_subplot(133), title="Yearly") plt.show() 


الصورة

لعرض المنطقة بشكل صحيح مع وسيلة إيضاح التخطيط ، ننقل مستوى الفهرس باستخدام مؤشرات إلى المستوى الثاني أعلى الأعمدة باستخدام الأمر Series (). Unstack (1) الأمر. باستخدام DataFrame () ، لن يعمل هذا الرقم ، ولكن الحل أدناه.

عند التجميع حسب الفترات القياسية ، يستخدم Pandas أحدث تاريخ تقويم للمجموعة في الفهرس ، والذي يختلف غالبًا عن التواريخ الفعلية. لإصلاح ذلك ، قم بتحديث الفهرس.

 monthly = df.groupby([pd.Grouper(freq='M', level=0), level_values(1)]).agg(agg_rules) \ .set_index(['dt', 'symbol'], drop=False) 

مثال للحصول على سجل الأسعار لأصل معين (نأخذ جميع التواريخ ، ومؤشر QQQ وجميع الأعمدة):

 monthly.loc[(slice(None), ['QQQ']), :] #    

تقلبات الأصول الشهرية


يمكننا الآن إلقاء نظرة على بعض الخطوط على الرسم البياني للتغير في سعر كل أصل خلال الفترة التي تهمنا. للقيام بذلك ، نحصل على النسبة المئوية لتغيرات الأسعار من خلال تجميع إطار البيانات حسب مستوى متعدد المؤشرات مع مؤشر الأصول.

 monthly = df.groupby([pd.Grouper(freq='M', level=0), level_values(1)]).agg( agg_rules).set_index(['dt', 'symbol'], drop=False) #     .   . monthly['pct_close'] = monthly.groupby(level=1)['close'].pct_change().fillna(0) #  ax = monthly.pct_close.unstack(1).plot(title="Monthly", figsize=(15, 4)) ax.axhline(0, color='k', linestyle='--', lw=0.5) plt.show() 

الصورة

قارن عوائد الأصول


الآن سنستخدم طريقة نافذة السلسلة (). Rolling () ونعرض العائد على الأصول لفترة معينة:

كود بايثون
 rolling_prod = lambda x: x.rolling(len(x), min_periods=1).apply(np.prod) #   monthly = df.groupby([pd.Grouper(freq='M', level=0), level_values(1)]).agg( agg_rules).set_index(['dt', 'symbol'], drop=False) #     .   .   1. monthly['pct_close'] = monthly.groupby(level=1)['close'].pct_change().fillna(0) + 1 #  DataFrame    2007  fltr = monthly.dt >= '2007-01-01' test = monthly[fltr].copy().set_index(['dt', 'symbol'], drop=False) #  dataframe    test.loc[test.index.levels[0][0], 'pct_close'] = 1 #    1 #    test['performance'] = test.groupby(level=1)['pct_close'].transform(rolling_prod) - 1 #  ax = test.performance.unstack(1).plot(title="Performance (Monthly) from 2007-01-01", figsize=(15, 4)) ax.axhline(0, color='k', linestyle='--', lw=0.5) plt.show() #       test.tail(len(test.index.levels[1])).sort_values('performance', ascending=False) 


الصورة

طرق إعادة توازن المحفظة


لذلك وصلنا إلى أكثر اللذيذ. في الأمثلة ، سنلقي نظرة على نتائج المحفظة في تخصيص رأس المال للأسهم المحددة مسبقًا بين العديد من الأصول. ونضيف أيضًا ظروفًا فريدة نتخلى بموجبها عن بعض الأصول في وقت توزيع رأس المال. إذا لم تكن هناك أصول مناسبة ، فإننا نفترض أن لدى الوسيط رأس المال في ذاكرة التخزين المؤقت.

من أجل استخدام طرق Pandas لإعادة التوازن ، نحتاج إلى تخزين مشاركات التوزيع وشروط إعادة التوازن في DataFrame مع البيانات المجمعة. الآن خذ بعين الاعتبار وظائف إعادة التوازن التي سنمررها إلى DataFrame (). الطريقة () التطبيق:

كود بايثون
 def rebalance_simple(x): #     data = x.unstack(1) return (data.pct_close * data['size']).sum() / data['size'].sum() def rebalance_sma(x): #   ,   SMA50 > SMA200 data = x.unstack(1) fltr = data['sma50'] > data['sma200'] if not data[fltr]['size'].sum(): return 1 #   ,    return (data[fltr].pct_close * data[fltr]['size']).sum() / data[fltr]['size'].sum() def rebalance_rsi(x): #   ,   RSI100 > 50 data = x.unstack(1) fltr = data['rsi100'] > 50 if not data[fltr]['size'].sum(): return 1 #   ,    return (data[fltr].pct_close * data[fltr]['size']).sum() / data[fltr]['size'].sum() def rebalance_custom(x, df=None): #         data = x.unstack(1) for s in data.index: if data['dt'][s]: fltr_dt = df['dt'] < data['rebalance_dt'][s] #   values = df[fltr_dt].loc[(slice(None), [s]), 'close'].values data.loc[s, 'custom'] = 0 #    if len(values) > len(values[np.isnan(values)]): #  RSI  100  data.loc[s, 'custom'] = talib.RSI(values, timeperiod=100)[-1] fltr = data['custom'] > 50 if not data[fltr]['size'].sum(): return 1 #   ,    return (data[fltr].pct_close * data[fltr]['size']).sum() / data[fltr]['size'].sum() def drawdown(chg, is_max=False): #    total = len(chg.index) rolling_max = chg.rolling(total, min_periods=1).max() daily_drawdown = chg/rolling_max - 1.0 if is_max: return daily_drawdown.rolling(total, min_periods=1).min() return daily_drawdown 


من أجل:

  • rebalance_simple هي أبسط وظيفة توزع ربحية كل أصل في الأسهم.
  • rebalance_sma هي وظيفة توزع رأس المال بين الأصول التي يزيد متوسطها المتحرك 50 يومًا عن 200 يومًا في وقت إعادة التوازن.
  • rebalance_rsi - دالة توزع رأس المال بين الأصول التي تزيد قيمة مؤشر القوة النسبية فيها عن 100 يوم عن 50.
  • rebalance_custom هي أبطأ وظيفة وأكثرها شمولية ، حيث سنحسب قيم المؤشر من سجل أسعار الأصول اليومية في وقت إعادة التوازن. هنا يمكنك استخدام أي شروط وبيانات. حتى تنزيل كل مرة من مصادر خارجية. ولكن لا يمكنك الاستغناء عن دورة.
  • السحب - وظيفة مساعدة ، تظهر أقصى سحب في المحفظة.

في وظائف إعادة التوازن ، نحتاج إلى مجموعة من جميع البيانات للتاريخ موزعة حسب الأصول. DataFrame (). ستطبق الطريقة () ، التي سنحسب بواسطتها نتائج المحافظ ، مصفوفة إلى وظيفتنا ، حيث ستصبح الأعمدة فهرس الصف. وإذا قمنا بعمل مؤشر متعدد ، حيث سيكون مؤشر الأسعار هو المستوى صفر ، فسيأتي إلينا مؤشر متعدد. يمكننا توسيع هذا الفهرس المتعدد في مصفوفة ثنائية الأبعاد والحصول على بيانات الأصل المقابل في كل سطر.

الصورة

إعادة موازنة المحفظة


يكفي الآن تهيئة الظروف اللازمة وإجراء حساب لكل محفظة في الدورة. بادئ ذي بدء ، نحسب المؤشرات في تاريخ الأسعار اليومي:

 #    1  ,      df['sma50'] = df.groupby(level=1)['close'].transform(lambda x: talib.SMA(x.values, timeperiod=50)).shift(1) df['sma200'] = df.groupby(level=1)['close'].transform(lambda x: talib.SMA(x.values, timeperiod=200)).shift(1) df['rsi100'] = df.groupby(level=1)['close'].transform(lambda x: talib.RSI(x.values, timeperiod=100)).shift(1) 

الآن سنقوم بتجميع القصة لفترة إعادة التوازن المطلوبة باستخدام الطرق الموضحة أعلاه. في الوقت نفسه ، سنأخذ قيم المؤشرات في بداية الفترة من أجل استبعاد النظر إلى المستقبل.

نحن نصف هيكل المحافظ ونشير إلى إعادة التوازن المطلوبة. سنقوم بحساب المحافظ في دورة ، حيث نحتاج إلى تحديد أسهم وشروط فريدة:

كود بايثون
 #  :  ,  ,  portfolios = [ {'symbols': [('SPY', 0.8), ('AGG', 0.2)], 'func': rebalance_sma, 'name': 'Portfolio 80/20 SMA50x200'}, {'symbols': [('SPY', 0.8), ('AGG', 0.2)], 'func': rebalance_rsi, 'name': 'Portfolio 80/20 RSI100>50'}, {'symbols': [('SPY', 0.8), ('AGG', 0.2)], 'func': partial(rebalance_custom, df=df), 'name': 'Portfolio 80/20 Custom'}, {'symbols': [('SPY', 0.8), ('AGG', 0.2)], 'func': rebalance_simple, 'name': 'Portfolio 80/20'}, {'symbols': [('SPY', 0.4), ('AGG', 0.6)], 'func': rebalance_simple, 'name': 'Portfolio 40/60'}, {'symbols': [('SPY', 0.2), ('AGG', 0.8)], 'func': rebalance_simple, 'name': 'Portfolio 20/80'}, {'symbols': [('DIA', 0.2), ('QQQ', 0.3), ('SPY', 0.2), ('IWM', 0.2), ('AGG', 0.1)], 'func': rebalance_simple, 'name': 'Portfolio DIA & QQQ & SPY & IWM & AGG'}, ] for p in portfolios: #    rebalance['size'] = 0. for s, pct in p['symbols']: #       rebalance.loc[(slice(None), [s]), 'size'] = pct #            rebalance_perf = rebalance.stack().unstack([1, 2]).apply(p['func'], axis=1) #    p['performance'] = (rebalance_perf.rolling(len(rebalance_perf), min_periods=1).apply(np.prod) - 1) #    p['drawdown'] = drawdown(p['performance'] + 1, is_max=True) 


هذه المرة نحتاج إلى القيام بخدعة بمؤشرات الأعمدة والصفوف للحصول على الفهرس المتعدد المطلوب في وظيفة إعادة التوازن. سنحقق ذلك من خلال استدعاء أساليب DataFrame (). Stack (). Unstack ([1، 2]) بالتسلسل. سينقل هذا الرمز الأعمدة إلى فهرس متعدد أحرف صغيرة ، ثم يعيد الفهرس المتعدد باستخدام مؤشرات وأعمدة بالترتيب المطلوب.

حقائب جاهزة للرسوم البيانية


الآن يبقى رسم كل شيء. للقيام بذلك ، قم بتشغيل دورة المحفظة مرة أخرى ، والتي تعرض البيانات على الرسوم البيانية. في النهاية سنرسم SPY كمعيار للمقارنة.

كود بايثون
 fig = plt.figure(figsize=(15, 4), facecolor='white') ax_perf = fig.add_subplot(121) ax_dd = fig.add_subplot(122) for p in portfolios: p['performance'].rename(p['name']).plot(ax=ax_perf, legend=True, title='Performance') p['drawdown'].rename(p['name']).plot(ax=ax_dd, legend=True, title='Max drawdown') #       print(f"{p['name']}: {p['performance'][-1]*100:.2f}% / {p['drawdown'][-1]*100:.2f}%") # SPY,   rebalance.loc[(slice(None), ['SPY']), :].set_index('dt', drop=False).performance. \ rename('SPY').plot(ax=ax_perf, legend=True) drawdown(rebalance.loc[(slice(None), ['SPY']), :].set_index('dt', drop=False).performance + 1, is_max=True).rename('SPY').plot(ax=ax_dd, legend=True) ax_perf.axhline(0, color='k', linestyle='--', lw=0.5) ax_dd.axhline(0, color='k', linestyle='--', lw=0.5) plt.show() 


الصورة

الخلاصة


يسمح لك الرمز المدروس بتحديد العديد من هياكل المحفظة وظروف إعادة التوازن. بمساعدتها ، يمكنك التحقق بسرعة مما إذا كان ، على سبيل المثال ، يستحق الاحتفاظ بالذهب (GLD) أو الأسواق الناشئة (EEM) في محفظة. جربها بنفسك ، أضف شروطك الخاصة للمؤشرات أو حدد المعلمات الموصوفة بالفعل. (ولكن تذكر خطأ الناجي وأن ملائمة البيانات السابقة قد لا ترقى إلى مستوى التوقعات في المستقبل). ثم قرر من تثق في محفظتك - Python أو المستشارين الماليين؟

المستودع: rebalance.portfolio

Source: https://habr.com/ru/post/ar419979/


All Articles