Dalam artikel ini kita akan mempertimbangkan strategi perdagangan intraday pertama kita. Ia akan menggunakan idea perdagangan klasik, iaitu
Strategi ini secara amnya mewujudkan
Rasional untuk strategi ini adalah bahawa SPY dan IWM kira-kira mencirikan situasi yang sama, ekonomi sekumpulan syarikat AS yang besar dan kecil. premisnya adalah bahawa jika seseorang mengambil penyebaran harga maka ia harus membalikkan purata, kerana sementara peristiwa
Strategi ini dilaksanakan dalam langkah-langkah berikut:
Mungkin cara terbaik untuk memahami strategi secara mendalam adalah untuk benar-benar melaksanakannya. Bahagian berikut menerangkan kod Python lengkap (fail tunggal) untuk melaksanakan strategi mean-reverting ini. Saya telah memberi komen secara liberal pada kod untuk membantu pemahaman.
Seperti semua tutorial Python / pandas, anda perlu menyediakan persekitaran penyelidikan Python seperti yang dijelaskan dalam tutorial ini. Setelah dipasang, tugas pertama adalah untuk mengimport perpustakaan Python yang diperlukan.
Versi perpustakaan tertentu yang saya gunakan adalah seperti berikut:
# mr_spy_iwm.py
import matplotlib.pyplot as plt
import numpy as np
import os, os.path
import pandas as pd
Fungsi berikut create_pairs_dataframe mengimport dua fail CSV yang mengandungi bar intraday dua simbol. Dalam kes kita ini akan menjadi SPY dan IWM. Ia kemudian membuat pasangan dataframe yang berasingan, yang menggunakan indeks kedua-dua fail asal. Oleh kerana cap masa mereka mungkin berbeza kerana perdagangan dan kesilapan yang terlepas, ini menjamin bahawa kita akan mempunyai data yang sepadan. Ini adalah salah satu faedah utama menggunakan perpustakaan analisis data seperti panda. Kod
# mr_spy_iwm.py
def create_pairs_dataframe(datadir, symbols):
"""Creates a pandas DataFrame containing the closing price
of a pair of symbols based on CSV files containing a datetime
stamp and OHLCV data."""
# Open the individual CSV files and read into pandas DataFrames
print "Importing CSV data..."
sym1 = pd.io.parsers.read_csv(os.path.join(datadir, '%s.csv' % symbols[0]),
header=0, index_col=0,
names=['datetime','open','high','low','close','volume','na'])
sym2 = pd.io.parsers.read_csv(os.path.join(datadir, '%s.csv' % symbols[1]),
header=0, index_col=0,
names=['datetime','open','high','low','close','volume','na'])
# Create a pandas DataFrame with the close prices of each symbol
# correctly aligned and dropping missing entries
print "Constructing dual matrix for %s and %s..." % symbols
pairs = pd.DataFrame(index=sym1.index)
pairs['%s_close' % symbols[0].lower()] = sym1['close']
pairs['%s_close' % symbols[1].lower()] = sym2['close']
pairs = pairs.dropna()
return pairs
Langkah seterusnya adalah untuk menjalankan regresi linear bergulir antara SPY dan IWM. Dalam kes ini IWM adalah ramalan (
Apabila pekali beta bergulir dikira dalam model regresi linear untuk SPY-IWM, kita menambahkannya ke pasangan DataFrame dan membuang baris kosong. Ini membentuk set bar pertama yang sama dengan saiz lookback sebagai ukuran pemangkasan. Kami kemudian membuat penyebaran kedua ETF sebagai unit SPY dan -βi unit IWM. Jelas ini bukan situasi yang realistik kerana kita mengambil jumlah pecahan IWM, yang tidak mungkin dalam pelaksanaan sebenar.
Akhirnya, kita membuat z-score penyebaran, yang dikira dengan mengurangkan purata penyebaran dan menormalkan dengan penyimpangan standard penyebaran. Perhatikan bahawa terdapat bias lookahead yang agak halus yang berlaku di sini. Saya sengaja meninggalkannya dalam kod kerana saya ingin menekankan betapa mudahnya membuat kesilapan seperti itu dalam penyelidikan. Purata dan penyimpangan standard dikira untuk keseluruhan siri masa penyebaran. Jika ini mencerminkan ketepatan sejarah yang benar maka maklumat ini tidak akan tersedia kerana secara tersirat menggunakan maklumat masa depan. Oleh itu, kita harus menggunakan purata bergulir dan stdev untuk mengira z-score.
# mr_spy_iwm.py
def calculate_spread_zscore(pairs, symbols, lookback=100):
"""Creates a hedge ratio between the two symbols by calculating
a rolling linear regression with a defined lookback period. This
is then used to create a z-score of the 'spread' between the two
symbols based on a linear combination of the two."""
# Use the pandas Ordinary Least Squares method to fit a rolling
# linear regression between the two closing price time series
print "Fitting the rolling Linear Regression..."
model = pd.ols(y=pairs['%s_close' % symbols[0].lower()],
x=pairs['%s_close' % symbols[1].lower()],
window=lookback)
# Construct the hedge ratio and eliminate the first
# lookback-length empty/NaN period
pairs['hedge_ratio'] = model.beta['x']
pairs = pairs.dropna()
# Create the spread and then a z-score of the spread
print "Creating the spread/zscore columns..."
pairs['spread'] = pairs['spy_close'] - pairs['hedge_ratio']*pairs['iwm_close']
pairs['zscore'] = (pairs['spread'] - np.mean(pairs['spread']))/np.std(pairs['spread'])
return pairs
Dalam create_long_short_market_signals isyarat dagangan dicipta. Ini dikira dengan pergi panjang penyebaran apabila z-skor negatif melebihi z-skor negatif dan pergi pendek penyebaran apabila z-skor positif melebihi z-skor positif. Isyarat keluar diberikan apabila nilai mutlak z-skor adalah kurang daripada atau sama dengan ambang lain (lebih kecil dalam besar).
Untuk mencapai situasi ini adalah perlu untuk mengetahui, untuk setiap bar, sama ada strategi adalah
Untuk mengulangi di atas Pandas DataFrame (yang memang bukan operasi biasa) adalah perlu untuk menggunakan kaedah iterrows, yang menyediakan penjana di mana untuk mengulangi:
# mr_spy_iwm.py
def create_long_short_market_signals(pairs, symbols,
z_entry_threshold=2.0,
z_exit_threshold=1.0):
"""Create the entry/exit signals based on the exceeding of
z_enter_threshold for entering a position and falling below
z_exit_threshold for exiting a position."""
# Calculate when to be long, short and when to exit
pairs['longs'] = (pairs['zscore'] <= -z_entry_threshold)*1.0
pairs['shorts'] = (pairs['zscore'] >= z_entry_threshold)*1.0
pairs['exits'] = (np.abs(pairs['zscore']) <= z_exit_threshold)*1.0
# These signals are needed because we need to propagate a
# position forward, i.e. we need to stay long if the zscore
# threshold is less than z_entry_threshold by still greater
# than z_exit_threshold, and vice versa for shorts.
pairs['long_market'] = 0.0
pairs['short_market'] = 0.0
# These variables track whether to be long or short while
# iterating through the bars
long_market = 0
short_market = 0
# Calculates when to actually be "in" the market, i.e. to have a
# long or short position, as well as when not to be.
# Since this is using iterrows to loop over a dataframe, it will
# be significantly less efficient than a vectorised operation,
# i.e. slow!
print "Calculating when to be in the market (long and short)..."
for i, b in enumerate(pairs.iterrows()):
# Calculate longs
if b[1]['longs'] == 1.0:
long_market = 1
# Calculate shorts
if b[1]['shorts'] == 1.0:
short_market = 1
# Calculate exists
if b[1]['exits'] == 1.0:
long_market = 0
short_market = 0
# This directly assigns a 1 or 0 to the long_market/short_market
# columns, such that the strategy knows when to actually stay in!
pairs.ix[i]['long_market'] = long_market
pairs.ix[i]['short_market'] = short_market
return pairs
Pada peringkat ini, kita telah mengemas kini pasangan untuk mengandungi isyarat panjang / pendek yang sebenarnya, yang membolehkan kita menentukan sama ada kita perlu berada di pasaran. Sekarang kita perlu membuat portfolio untuk mengesan nilai pasaran kedudukan. Tugas pertama adalah untuk membuat lajur kedudukan yang menggabungkan isyarat panjang dan pendek. Ini akan mengandungi senarai elemen dari (1,0,−1), dengan 1 mewakili kedudukan panjang / pasaran, 0 mewakili tidak ada kedudukan (harus keluar) dan -1 mewakili kedudukan pendek / pasaran. Lajur sym1 dan sym2 mewakili nilai pasaran kedudukan SPY dan IWM pada akhir setiap bar.
Apabila nilai pasaran ETF telah dicipta, kita menjumlahkannya untuk menghasilkan nilai pasaran keseluruhan di akhir setiap bar. Ini kemudian diubah menjadi aliran pulangan oleh kaedah pct_change untuk objek Siri itu. Baris kod berikutnya membersihkan entri yang tidak baik (elemen NaN dan inf) dan akhirnya mengira kurva ekuiti penuh.
# mr_spy_iwm.py
def create_portfolio_returns(pairs, symbols):
"""Creates a portfolio pandas DataFrame which keeps track of
the account equity and ultimately generates an equity curve.
This can be used to generate drawdown and risk/reward ratios."""
# Convenience variables for symbols
sym1 = symbols[0].lower()
sym2 = symbols[1].lower()
# Construct the portfolio object with positions information
# Note that minuses to keep track of shorts!
print "Constructing a portfolio..."
portfolio = pd.DataFrame(index=pairs.index)
portfolio['positions'] = pairs['long_market'] - pairs['short_market']
portfolio[sym1] = -1.0 * pairs['%s_close' % sym1] * portfolio['positions']
portfolio[sym2] = pairs['%s_close' % sym2] * portfolio['positions']
portfolio['total'] = portfolio[sym1] + portfolio[sym2]
# Construct a percentage returns stream and eliminate all
# of the NaN and -inf/+inf cells
print "Constructing the equity curve..."
portfolio['returns'] = portfolio['total'].pct_change()
portfolio['returns'].fillna(0.0, inplace=True)
portfolio['returns'].replace([np.inf, -np.inf], 0.0, inplace=True)
portfolio['returns'].replace(-1.0, 0.0, inplace=True)
# Calculate the full equity curve
portfolio['returns'] = (portfolio['returns'] + 1.0).cumprod()
return portfolio
Peraturanutamafungsi membawa semuanya bersama-sama. fail CSV intraday terletak di datadir laluan. Pastikan untuk mengubah suai kod di bawah untuk menunjuk ke direktori tertentu anda.
Untuk menentukan betapa sensitifnya strategi terhadap tempoh melihat kembali, adalah perlu untuk mengira metrik prestasi untuk julat melihat kembali. Saya telah memilih pulangan peratusan akhir portfolio sebagai ukuran prestasi dan julat melihat kembali di [50,200] dengan peningkatan 10. Anda boleh melihat dalam kod berikut bahawa fungsi sebelumnya dibungkus dalam gelung for merentasi julat ini, dengan ambang yang lain tetap. Tugas akhir adalah menggunakan matplotlib untuk membuat carta garis melihat kembali vs pulangan:
# mr_spy_iwm.py
if __name__ == "__main__":
datadir = '/your/path/to/data/' # Change this to reflect your data path!
symbols = ('SPY', 'IWM')
lookbacks = range(50, 210, 10)
returns = []
# Adjust lookback period from 50 to 200 in increments
# of 10 in order to produce sensitivities
for lb in lookbacks:
print "Calculating lookback=%s..." % lb
pairs = create_pairs_dataframe(datadir, symbols)
pairs = calculate_spread_zscore(pairs, symbols, lookback=lb)
pairs = create_long_short_market_signals(pairs, symbols,
z_entry_threshold=2.0,
z_exit_threshold=1.0)
portfolio = create_portfolio_returns(pairs, symbols)
returns.append(portfolio.ix[-1]['returns'])
print "Plot the lookback-performance scatterchart..."
plt.plot(lookbacks, returns, '-o')
plt.show()
Jadual tempoh melihat balik vs pulangan kini boleh dilihat. Perhatikan bahawa terdapat maksimum
Tiada artikel backtesting akan lengkap tanpa lengkung ekuiti yang miring ke atas! Oleh itu, jika anda ingin merangka lengkung pulangan kumulatif berbanding masa, anda boleh menggunakan kod berikut. Ia akan merangka portfolio akhir yang dihasilkan dari kajian parameter lookback. Oleh itu, perlu memilih lookback bergantung pada carta yang ingin anda lihat. Carta juga merangka pulangan SPY dalam tempoh yang sama untuk membantu perbandingan:
# mr_spy_iwm.py
# This is still within the main function
print "Plotting the performance charts..."
fig = plt.figure()
fig.patch.set_facecolor('white')
ax1 = fig.add_subplot(211, ylabel='%s growth (%%)' % symbols[0])
(pairs['%s_close' % symbols[0].lower()].pct_change()+1.0).cumprod().plot(ax=ax1, color='r', lw=2.)
ax2 = fig.add_subplot(212, ylabel='Portfolio value growth (%%)')
portfolio['returns'].plot(ax=ax2, lw=2.)
fig.show()
Carta lengkung ekuiti berikut adalah untuk tempoh 100 hari:SPY-IWM analisis sensitiviti jangka masa jangkaan untuk nisbah lindung nilai regresi linear
Perlu diperhatikan bahawa penggunaan SPY adalah ketara pada tahun 2009 semasa tempoh krisis kewangan. Strategi ini juga mengalami tempoh yang tidak menentu pada peringkat ini. Juga perlu diperhatikan bahawa prestasi telah merosot sedikit pada tahun lalu kerana sifat SPY yang kuat pada tempoh ini, yang mencerminkan indeks S & P500.
Perhatikan bahawa kita masih perlu mengambil kira kecenderungan lookahead ketika mengira z-score penyebaran. Selanjutnya, semua pengiraan ini telah dijalankan tanpa kos transaksi. Strategi ini pasti akan berfungsi dengan sangat buruk apabila faktor-faktor ini diambil kira. Yuran, penyebaran tawaran / permintaan dan seluncur kini tidak diperhitungkan. Di samping itu strategi ini berdagang dalam unit pecahan ETF, yang juga sangat tidak realistik.
Dalam artikel seterusnya kita akan mencipta backtester yang didorong oleh peristiwa yang lebih canggih yang akan mengambil faktor-faktor ini dan memberi kita keyakinan yang lebih besar dalam lengkung ekuiti dan metrik prestasi kita.