Uji balik crossover purata bergerak di Python dengan panda

Penulis:Kebaikan, Dicipta: 2019-03-27 15:11:40, Dikemas kini:

Dalam artikel ini kita akan menggunakan mesin yang kita memperkenalkan untuk menjalankan penyelidikan mengenai strategi sebenar, iaitu Moving Average Crossover pada AAPL.

Strategi Crossover Purata Bergerak

Teknik Moving Average Crossover adalah strategi momentum yang sangat terkenal. Ia sering dianggap sebagai contoh Hello World untuk perdagangan kuantitatif.

Strategi seperti yang digariskan di sini adalah panjang sahaja. Dua penapis purata bergerak mudah yang berasingan dicipta, dengan tempoh belakang yang berbeza, dari siri masa tertentu. Isyarat untuk membeli aset berlaku apabila purata bergerak belakang yang lebih pendek melebihi purata bergerak belakang yang lebih lama. Jika purata yang lebih lama kemudiannya melebihi purata yang lebih pendek, aset dijual kembali. Strategi ini berfungsi dengan baik apabila siri masa memasuki tempoh trend yang kuat dan kemudian perlahan-lahan membalikkan trend.

Untuk contoh ini, saya telah memilih Apple, Inc. (AAPL) sebagai siri masa, dengan pandangan pendek 100 hari dan pandangan panjang 400 hari. Ini adalah contoh yang disediakan oleh perpustakaan perdagangan algoritma zipline. Oleh itu jika kita ingin melaksanakan backtester kita sendiri kita perlu memastikan bahawa ia sepadan dengan hasil dalam zipline, sebagai cara asas pengesahan.

Pelaksanaan

Pastikan anda mengikuti tutorial sebelumnya di sini, yang menerangkan bagaimana hierarki objek awal untuk backtester dibina, jika tidak kod di bawah tidak akan berfungsi.

  • Python - 2.7.3
  • NumPy - 1.8.0
  • panda - 0.12.0
  • Matplotlib - 1.1.0

Pelaksanaan ma_cross.py memerlukanbacktest.pyLangkah pertama adalah untuk mengimport modul dan objek yang diperlukan:

# ma_cross.py

import datetime
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

from pandas.io.data import DataReader
from backtest import Strategy, Portfolio

Seperti dalam tutorial sebelumnya kita akan subclass Strategy abstrak kelas asas untuk menghasilkan MovingAverageCrossStrategy, yang mengandungi semua butiran tentang bagaimana untuk menjana isyarat apabila purata bergerak AAPL bersilang antara satu sama lain.

Objek ini memerlukan short_window dan long_window untuk beroperasi. Nilai telah ditetapkan kepada lalai 100 hari dan 400 hari masing-masing, yang merupakan parameter yang sama digunakan dalam contoh utama zipline.

Purata bergerak dicipta dengan menggunakan fungsi panda rolling_mean pada harga penutupan bar saham AAPL. Setelah purata bergerak individu telah dibina, Siri isyarat dihasilkan dengan menetapkan lajur sama dengan 1.0 apabila purata bergerak pendek lebih besar daripada purata bergerak panjang, atau 0.0 sebaliknya.

# ma_cross.py

class MovingAverageCrossStrategy(Strategy):
    """    
    Requires:
    symbol - A stock symbol on which to form a strategy on.
    bars - A DataFrame of bars for the above symbol.
    short_window - Lookback period for short moving average.
    long_window - Lookback period for long moving average."""

    def __init__(self, symbol, bars, short_window=100, long_window=400):
        self.symbol = symbol
        self.bars = bars

        self.short_window = short_window
        self.long_window = long_window

    def generate_signals(self):
        """Returns the DataFrame of symbols containing the signals
        to go long, short or hold (1, -1 or 0)."""
        signals = pd.DataFrame(index=self.bars.index)
        signals['signal'] = 0.0

        # Create the set of short and long simple moving averages over the 
        # respective periods
        signals['short_mavg'] = pd.rolling_mean(bars['Close'], self.short_window, min_periods=1)
        signals['long_mavg'] = pd.rolling_mean(bars['Close'], self.long_window, min_periods=1)

        # Create a 'signal' (invested or not invested) when the short moving average crosses the long
        # moving average, but only for the period greater than the shortest moving average window
        signals['signal'][self.short_window:] = np.where(signals['short_mavg'][self.short_window:] 
            > signals['long_mavg'][self.short_window:], 1.0, 0.0)   

        # Take the difference of the signals in order to generate actual trading orders
        signals['positions'] = signals['signal'].diff()   

        return signals

MarketOnClosePortfolio dikelaskan daripada Portfolio, yang terdapat dibacktest.py. Ia hampir sama dengan pelaksanaan yang diterangkan dalam tutorial sebelumnya, dengan pengecualian bahawa perdagangan kini dijalankan pada asas Close-to-Close, dan bukannya asas Open-to-Open. Untuk butiran mengenai bagaimana objek Portfolio ditakrifkan, lihat tutorial sebelumnya. Saya telah meninggalkan kod untuk kelengkapan dan untuk menjaga tutorial ini mandiri:

# ma_cross.py

class MarketOnClosePortfolio(Portfolio):
    """Encapsulates the notion of a portfolio of positions based
    on a set of signals as provided by a Strategy.

    Requires:
    symbol - A stock symbol which forms the basis of the portfolio.
    bars - A DataFrame of bars for a symbol set.
    signals - A pandas DataFrame of signals (1, 0, -1) for each symbol.
    initial_capital - The amount in cash at the start of the portfolio."""

    def __init__(self, symbol, bars, signals, initial_capital=100000.0):
        self.symbol = symbol        
        self.bars = bars
        self.signals = signals
        self.initial_capital = float(initial_capital)
        self.positions = self.generate_positions()
        
    def generate_positions(self):
        positions = pd.DataFrame(index=signals.index).fillna(0.0)
        positions[self.symbol] = 100*signals['signal']   # This strategy buys 100 shares
        return positions
                    
    def backtest_portfolio(self):
        portfolio = self.positions*self.bars['Close']
        pos_diff = self.positions.diff()

        portfolio['holdings'] = (self.positions*self.bars['Close']).sum(axis=1)
        portfolio['cash'] = self.initial_capital - (pos_diff*self.bars['Close']).sum(axis=1).cumsum()

        portfolio['total'] = portfolio['cash'] + portfolio['holdings']
        portfolio['returns'] = portfolio['total'].pct_change()
        return portfolio

Sekarang bahawa kelas MovingAverageCrossStrategy dan MarketOnClosePortfolio telah ditakrifkan,utamafungsi akan dipanggil untuk mengikat semua fungsi bersama-sama.

Objek panda DataReader memuat turun harga OHLCV saham AAPL untuk tempoh 1 Januari 1990 hingga 1 Januari 2002, pada ketika itu isyarat DataFrame dibuat untuk menjana isyarat panjang sahaja.

Langkah terakhir adalah menggunakan matplotlib untuk merangka plot dua digit kedua-dua harga AAPL, bertindih dengan purata bergerak dan isyarat beli / jual, serta lengkung ekuiti dengan isyarat beli / jual yang sama.

# ma_cross.py

if __name__ == "__main__":
    # Obtain daily bars of AAPL from Yahoo Finance for the period
    # 1st Jan 1990 to 1st Jan 2002 - This is an example from ZipLine
    symbol = 'AAPL'
    bars = DataReader(symbol, "yahoo", datetime.datetime(1990,1,1), datetime.datetime(2002,1,1))

    # Create a Moving Average Cross Strategy instance with a short moving
    # average window of 100 days and a long window of 400 days
    mac = MovingAverageCrossStrategy(symbol, bars, short_window=100, long_window=400)
    signals = mac.generate_signals()

    # Create a portfolio of AAPL, with $100,000 initial capital
    portfolio = MarketOnClosePortfolio(symbol, bars, signals, initial_capital=100000.0)
    returns = portfolio.backtest_portfolio()

    # Plot two charts to assess trades and equity curve
    fig = plt.figure()
    fig.patch.set_facecolor('white')     # Set the outer colour to white
    ax1 = fig.add_subplot(211,  ylabel='Price in $')
    
    # Plot the AAPL closing price overlaid with the moving averages
    bars['Close'].plot(ax=ax1, color='r', lw=2.)
    signals[['short_mavg', 'long_mavg']].plot(ax=ax1, lw=2.)

    # Plot the "buy" trades against AAPL
    ax1.plot(signals.ix[signals.positions == 1.0].index, 
             signals.short_mavg[signals.positions == 1.0],
             '^', markersize=10, color='m')

    # Plot the "sell" trades against AAPL
    ax1.plot(signals.ix[signals.positions == -1.0].index, 
             signals.short_mavg[signals.positions == -1.0],
             'v', markersize=10, color='k')

    # Plot the equity curve in dollars
    ax2 = fig.add_subplot(212, ylabel='Portfolio value in $')
    returns['total'].plot(ax=ax2, lw=2.)

    # Plot the "buy" and "sell" trades against the equity curve
    ax2.plot(returns.ix[signals.positions == 1.0].index, 
             returns.total[signals.positions == 1.0],
             '^', markersize=10, color='m')
    ax2.plot(returns.ix[signals.positions == -1.0].index, 
             returns.total[signals.positions == -1.0],
             'v', markersize=10, color='k')

    # Plot the figure
    fig.show()

Keluaran grafik kod adalah seperti berikut. Saya menggunakan perintah IPython %paste untuk meletakkan ini terus ke dalam konsol IPython semasa di Ubuntu, supaya output grafik tetap dilihat. Upticks merah jambu mewakili membeli stok, manakala downticks hitam mewakili menjualnya kembali:imgAAPL Moving Average Crossover Performance dari 1990-01-01 hingga 2002-01-01

Seperti yang dapat dilihat, strategi itu kehilangan wang sepanjang tempoh itu, dengan lima perdagangan pulang pergi. Ini tidak menghairankan memandangkan tingkah laku AAPL sepanjang tempoh itu, yang mempunyai kecenderungan menurun sedikit, diikuti dengan peningkatan yang ketara bermula pada tahun 1998.

Dalam artikel seterusnya kita akan mencipta cara yang lebih canggih untuk menganalisis prestasi, serta menerangkan bagaimana untuk mengoptimumkan tempoh melihat balik dari isyarat purata bergerak individu.


Lebih lanjut