Bagaimana Python membantu menggantikan konsultan keuangan

Untuk melanjutkan artikel tentang bahaya diversifikasi berlebihan, kami akan membuat alat pemilihan stok yang berguna. Setelah itu, kami akan membuat penyeimbangan ulang sederhana dan menambahkan kondisi unik dari indikator teknis, yang seringkali kurang dalam layanan populer. Dan kemudian membandingkan pengembalian aset individu dan portofolio berbeda.

Dalam semua ini kami menggunakan Panda dan meminimalkan jumlah siklus. Kelompokkan deret waktu dan gambarkan grafiknya. Mari berkenalan dengan multi-indeks dan perilaku mereka. Dan semua ini di Jupyter dengan Python 3.6.

Jika Anda ingin melakukan sesuatu dengan baik, lakukan sendiri.
Ferdinand Porsche

Alat yang dijelaskan akan memungkinkan Anda untuk memilih aset yang optimal untuk portofolio dan mengecualikan alat yang dikenakan oleh konsultan. Tetapi kita hanya akan melihat gambaran besarnya - tanpa memperhitungkan likuiditas akun, waktu untuk merekrut posisi, komisi broker dan biaya satu saham. Secara umum, dengan penyeimbangan ulang bulanan atau tahunan dari broker besar itu akan menjadi biaya yang tidak signifikan. Namun, sebelum menerapkan, strategi yang dipilih harus tetap diperiksa di backtester yang digerakkan oleh peristiwa, misalnya, Quantopian (QP), untuk menghilangkan potensi kesalahan.

Kenapa tidak segera di QP? Waktu Di sana, tes paling sederhana berlangsung sekitar 5 menit. Dan solusi saat ini akan memungkinkan Anda untuk memeriksa ratusan strategi berbeda dengan kondisi unik dalam satu menit.

Memuat data mentah


Untuk memuat data, ambil metode yang dijelaskan dalam artikel ini. Saya menggunakan PostgreSQL untuk menyimpan harga harian, tetapi sekarang penuh dengan sumber gratis dari mana Anda dapat membuat DataFrame yang diperlukan.

Kode untuk mengunduh riwayat harga dari database tersedia di repositori. Tautan akan berada di akhir artikel.

Struktur DataFrame


Ketika bekerja dengan riwayat harga, untuk pengelompokan yang nyaman dan akses ke semua data, solusi terbaik adalah dengan menggunakan multi-indeks (MultiIndex) dengan tanggal dan ticker.

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


gambar

Dengan menggunakan multi-indeks, kita dapat dengan mudah mengakses seluruh riwayat harga untuk semua aset dan dapat mengelompokkan array secara terpisah berdasarkan tanggal dan aset. Kami juga bisa mendapatkan riwayat harga untuk satu aset.

Berikut adalah contoh bagaimana Anda dapat dengan mudah mengelompokkan riwayat berdasarkan minggu, bulan, dan tahun. Dan untuk menunjukkan semua ini pada grafik oleh pasukan Pandas:

 #      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() 


gambar

Untuk menampilkan area dengan legenda grafik dengan benar, kami mentransfer level indeks dengan ticker ke level kedua di atas kolom menggunakan perintah Series (). Unstack (1). Dengan DataFrame (), angka seperti itu tidak akan berfungsi, tetapi solusinya ada di bawah.

Ketika dikelompokkan berdasarkan periode standar, Pandas menggunakan tanggal kalender terbaru dari grup dalam indeks, yang sering berbeda dari tanggal sebenarnya. Untuk memperbaikinya, perbarui indeks.

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

Contoh memperoleh riwayat harga aset tertentu (kami mengambil semua tanggal, ticker QQQ, dan semua kolom):

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

Volatilitas aset bulanan


Sekarang kita dapat melihat beberapa baris pada grafik perubahan harga setiap aset untuk periode yang menarik bagi kita. Untuk melakukan ini, kami mendapatkan persentase perubahan harga dengan mengelompokkan kerangka data berdasarkan tingkat multi-indeks dengan ticker aset.

 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() 

gambar

Bandingkan pengembalian aset


Sekarang kita akan menggunakan metode jendela Series (). Rolling () dan menampilkan pengembalian aset untuk periode tertentu:

Kode python
 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) 


gambar

Metode penyeimbangan ulang portofolio


Jadi kami mendapat yang paling enak. Dalam contoh, kita akan melihat hasil portofolio dalam alokasi modal untuk saham yang telah ditentukan antara beberapa aset. Dan juga menambahkan kondisi unik di mana kita akan meninggalkan beberapa aset pada saat distribusi modal. Jika tidak ada aset yang sesuai, maka kami mengasumsikan bahwa broker memiliki modal dalam cache.

Untuk menggunakan metode Pandas untuk penyeimbangan kembali, kita perlu menyimpan pembagian distribusi dan kondisi penyeimbangan kembali dalam DataFrame dengan data yang dikelompokkan. Sekarang pertimbangkan fungsi penyeimbangan ulang yang akan kami sampaikan ke metode DataFrame (). Terapkan ():

Kode python
 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 


Dalam rangka:

  • rebalance_simple adalah fungsi paling sederhana yang akan mendistribusikan profitabilitas setiap aset dalam saham.
  • rebalance_sma adalah fungsi yang mendistribusikan modal di antara aset yang rata-rata bergeraknya 50 hari lebih tinggi dari 200 hari pada saat rebalancing.
  • rebalance_rsi - fungsi yang mendistribusikan modal di antara aset yang nilai indikator RSI untuk 100 hari di atas 50.
  • rebalance_custom adalah fungsi paling lambat dan paling universal, di mana kami akan menghitung nilai indikator dari riwayat harga aset harian pada saat rebalancing. Di sini Anda dapat menggunakan kondisi dan data apa pun. Bahkan unduh setiap waktu dari sumber eksternal. Tetapi Anda tidak dapat melakukannya tanpa siklus.
  • drawdown - fungsi tambahan, menunjukkan drawdown maksimum dalam portofolio.

Dalam fungsi penyeimbangan kembali, kita membutuhkan larik semua data untuk tanggal menurut aset. Metode DataFrame (). Apply (), dimana kami akan menghitung hasil dari portofolio, akan meneruskan array ke fungsi kami, di mana kolom akan menjadi indeks baris. Dan jika kita membuat multi-indeks, di mana tickers akan menjadi level nol, maka multi-indeks akan datang kepada kita. Kami dapat memperluas multi-indeks ini menjadi array dua dimensi dan mendapatkan data aset terkait pada setiap baris.

gambar

Penyeimbangan ulang portofolio


Sekarang cukup untuk menyiapkan kondisi yang diperlukan dan membuat perhitungan untuk setiap portofolio dalam siklus. Pertama-tama, kami menghitung indikator pada riwayat harga harian:

 #    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) 

Sekarang kita akan mengelompokkan cerita untuk periode penyeimbangan yang diinginkan menggunakan metode yang dijelaskan di atas. Pada saat yang sama, kami akan mengambil nilai indikator di awal periode untuk mengecualikan melihat ke masa depan.

Kami menggambarkan struktur portofolio dan menunjukkan penyeimbangan kembali yang diinginkan. Kami akan menghitung portofolio dalam satu siklus, karena kami perlu menentukan bagian dan kondisi unik:

Kode python
 #  :  ,  ,  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) 


Kali ini kita perlu melakukan trik dengan indeks kolom dan baris untuk mendapatkan multi-indeks yang diinginkan dalam fungsi penyeimbangan kembali. Kami akan mencapai ini dengan memanggil metode DataFrame (). Stack (). Unstack ([1, 2]) secara berurutan. Kode ini akan mentransfer kolom ke multi-indeks huruf kecil, dan kemudian mengembalikan multi-indeks dengan ticker dan kolom dalam urutan yang diinginkan.

Tas kerja yang sudah jadi untuk bagan


Sekarang tinggal menggambar semuanya. Untuk melakukan ini, jalankan siklus portofolio lagi, yang menampilkan data pada grafik. Pada akhirnya kami akan menggambar SPY sebagai patokan untuk perbandingan.

Kode python
 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() 


gambar

Kesimpulan


Kode yang dipertimbangkan memungkinkan Anda untuk memilih berbagai struktur portofolio dan kondisi penyeimbangan kembali. Dengan bantuannya, Anda dapat dengan cepat memeriksa apakah, misalnya, ada baiknya memegang emas (GLD) atau pasar berkembang (EEM) dalam portofolio. Cobalah sendiri, tambahkan kondisi Anda sendiri untuk indikator atau pilih parameter yang sudah dijelaskan. (Tapi ingat kesalahan penyintas dan bahwa pemasangan data masa lalu mungkin tidak sesuai dengan harapan di masa depan.) Dan kemudian memutuskan kepada siapa Anda mempercayai portofolio Anda - Python atau konsultan keuangan?

Repositori: rebalance.portfolio

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


All Articles