Dalam artikel ini kita akan membahas strategi trading intraday pertama kita. Ini akan menggunakan ide trading klasik, yaitu
Strategi ini secara luas menciptakan
Alasan untuk strategi ini adalah bahwa SPY dan IWM kira-kira mencirikan situasi yang sama, yaitu ekonomi dari sekelompok perusahaan AS dengan kapitalisasi besar dan kecil. Premisnya adalah bahwa jika seseorang mengambil spread harga maka itu harus membalikkan rata-rata, karena sementara peristiwa
Strategi ini dilaksanakan dalam langkah-langkah berikut:
Mungkin cara terbaik untuk memahami strategi secara mendalam adalah untuk benar-benar menerapkannya. Bagian berikut menggambarkan kode Python lengkap (file tunggal) untuk menerapkan strategi mean-reverting ini. Saya telah berkomentar secara liberal kode untuk membantu pemahaman.
Seperti halnya semua tutorial Python/pandas, Anda harus memiliki pengaturan lingkungan penelitian Python seperti yang dijelaskan dalam tutorial ini. Setelah pengaturan, tugas pertama adalah mengimpor perpustakaan Python yang diperlukan. Untuk backtest ini, matplotlib dan panda diperlukan.
Versi perpustakaan tertentu yang saya gunakan adalah sebagai 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 mengimpor dua file CSV yang berisi bilah intraday dari dua simbol. Dalam kasus kami ini akan menjadi SPY dan IWM. Ini kemudian membuat pasangan dataframe terpisah, yang menggunakan indeks dari kedua file asli. Karena timestamp mereka kemungkinan berbeda karena perdagangan yang terlewatkan dan kesalahan, ini menjamin bahwa kita akan memiliki data yang cocok. Ini adalah salah satu manfaat utama dari menggunakan perpustakaan data analyis seperti panda. Kode
# 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 selanjutnya adalah melakukan regresi linier bergulir antara SPY dan IWM. Dalam hal ini IWM adalah prediktor (
Setelah koefisien beta bergulir dihitung dalam model regresi linier untuk SPY-IWM, kita menambahkannya ke pasangan DataFrame dan menjatuhkan baris kosong. Ini merupakan set pertama bar sama dengan ukuran lookback sebagai ukuran trimming.
Akhirnya, kita membuat z-score dari spread, yang dihitung dengan mengurangi rata-rata spread dan menormalkan dengan standar deviasi spread. Perhatikan bahwa ada bias lookahead yang agak halus terjadi di sini. Saya sengaja meninggalkannya dalam kode karena saya ingin menekankan betapa mudahnya membuat kesalahan seperti itu dalam penelitian. Rata-rata dan standar deviasi dihitung untuk seluruh rentetan waktu spread. Jika ini mencerminkan akurasi historis yang benar maka informasi ini tidak akan tersedia karena secara implisit menggunakan informasi masa depan. Oleh karena itu kita harus menggunakan mean rolling dan stdev untuk menghitung 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 sinyal perdagangan dibuat. Ini dihitung dengan pergi panjang spread ketika z-score negatif melebihi z-score negatif dan pergi pendek spread ketika z-score positif melebihi z-score positif.
Untuk mencapai situasi ini, perlu diketahui, untuk setiap bar, apakah strategi adalah
Untuk mengulangi pada Pandas DataFrame (yang memang TIDAK merupakan operasi umum) perlu menggunakan metode iterrows, yang menyediakan generator 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 tahap ini kita telah memperbarui pasangan untuk berisi sinyal panjang/pendek yang sebenarnya, yang memungkinkan kita untuk menentukan apakah kita perlu berada di pasar. Sekarang kita perlu membuat portofolio untuk melacak nilai pasar posisi. Tugas pertama adalah membuat kolom posisi yang menggabungkan sinyal panjang dan pendek. Ini akan berisi daftar elemen dari (1,0,−1), dengan 1 mewakili posisi panjang/pasar, 0 mewakili tidak ada posisi (harus keluar) dan -1 mewakili posisi pendek/pasar. Kolom sym1 dan sym2 mewakili nilai pasar posisi SPY dan IWM pada akhir setiap bar.
Setelah nilai pasar ETF telah dibuat, kita menambahkannya untuk menghasilkan nilai pasar total di akhir setiap bar. Ini kemudian diubah menjadi aliran pengembalian oleh metode pct_change untuk objek Seri itu. Baris kode berikutnya membersihkan entri yang buruk (elemen NaN dan inf) dan akhirnya menghitung kurva ekuitas 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. file CSV intraday terletak di jalur datadir. pastikan untuk memodifikasi kode di bawah ini untuk menunjuk ke direktori tertentu Anda.
Untuk menentukan seberapa sensitif strategi terhadap periode lookback, perlu menghitung metrik kinerja untuk rentang lookback. Saya telah memilih pengembalian persentase total akhir portofolio sebagai ukuran kinerja dan rentang lookback di [50,200] dengan kenaikan 10. Anda dapat melihat dalam kode berikut bahwa fungsi sebelumnya dibungkus dalam loop for di seluruh rentang ini, dengan ambang batas lainnya tetap. Tugas akhir adalah menggunakan matplotlib untuk membuat bagan garis lookback vs return:
# 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()
Jika kita melihat situasi di mana lookback independen dari return ini akan menjadi penyebab keprihatinan:SPY-IWM linear regression hedge-ratio lookback period sensitivity analysis (Analisis sensitivitas periode)
Tidak ada artikel backtesting yang akan lengkap tanpa kurva ekuitas yang miring ke atas! Jadi jika Anda ingin memetakan kurva pengembalian kumulatif vs waktu, Anda dapat menggunakan kode berikut. Ini akan memetakan portofolio akhir yang dihasilkan dari studi parameter lookback. Dengan demikian, Anda perlu memilih lookback tergantung pada grafik mana yang ingin Anda visualisasikan.
# 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()
Grafik kurva ekuitas berikut adalah untuk periode 100 hari:SPY-IWM linear regression hedge-ratio lookback period sensitivity analysis (Analisis sensitivitas periode)
Perlu dicatat bahwa pengurangan SPY signifikan pada tahun 2009 selama periode krisis keuangan. Strategi juga mengalami periode volatilitas pada tahap ini. Juga dicatat bahwa kinerja telah memburuk sedikit pada tahun lalu karena sifat tren yang kuat dari SPY pada periode ini, yang mencerminkan indeks S&P500.
Perhatikan bahwa kita masih harus memperhitungkan bias lookahead saat menghitung z-score dari spread. Selanjutnya, semua perhitungan ini telah dilakukan tanpa biaya transaksi. Strategi ini pasti akan sangat buruk sekali faktor-faktor ini dipertimbangkan. Biaya, bid / ask spread dan slippage saat ini tidak diperhitungkan. Selain itu strategi tersebut diperdagangkan dalam unit pecahan ETF, yang juga sangat tidak realistis.
Dalam artikel berikutnya kita akan membuat backtester yang didorong oleh peristiwa yang jauh lebih canggih yang akan memperhitungkan faktor-faktor ini dan memberi kita kepercayaan yang jauh lebih besar dalam kurva ekuitas dan metrik kinerja kita.