Backtesting Moving Average Crossover di Python dengan panda

Penulis:Kebaikan, Dibuat: 2019-03-27 15:11:40, Diperbarui:

Dalam artikel ini kita akan menggunakan mesin yang kita perkenalkan untuk melakukan penelitian tentang strategi yang sebenarnya, yaitu Moving Average Crossover pada AAPL.

Strategi Crossover Rata-rata Bergerak

Teknik Moving Average Crossover adalah strategi momentum yang sangat dikenal.

Strategi seperti yang diuraikan di sini hanya panjang. Dua filter rata-rata bergerak sederhana yang terpisah dibuat, dengan periode lookback yang bervariasi, dari serangkaian waktu tertentu. Sinyal untuk membeli aset terjadi ketika rata-rata bergerak lookback yang lebih pendek melebihi rata-rata bergerak lookback yang lebih lama. Jika rata-rata yang lebih lama kemudian melebihi rata-rata yang lebih pendek, aset dijual kembali. Strategi ini bekerja dengan baik ketika serangkaian waktu memasuki periode tren yang kuat dan kemudian perlahan membalikkan tren.

Untuk contoh ini, saya telah memilih Apple, Inc. (AAPL) sebagai deret waktu, dengan lookback pendek 100 hari dan lookback panjang 400 hari. Ini adalah contoh yang disediakan oleh perpustakaan perdagangan algoritmik zipline. Jadi jika kita ingin menerapkan backtester kita sendiri kita perlu memastikan bahwa itu cocok dengan hasil di zipline, sebagai sarana validasi dasar.

Pelaksanaan

Pastikan untuk mengikuti tutorial sebelumnya di sini, yang menjelaskan bagaimana hierarki objek awal untuk backtester dibangun, jika tidak kode di bawah ini tidak akan bekerja.

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

Implementasi ma_cross.py membutuhkanbacktest.pyLangkah pertama adalah mengimpor 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 Strategi abstrak kelas dasar untuk menghasilkan MovingAverageCrossStrategy, yang berisi semua rincian tentang cara menghasilkan sinyal ketika rata-rata bergerak AAPL menyeberang satu sama lain.

Objek ini membutuhkan short_window dan long_window untuk dioperasikan. Nilai-nilai telah ditetapkan untuk default masing-masing 100 hari dan 400 hari, yang merupakan parameter yang sama yang digunakan dalam contoh utama zipline.

Rata-rata bergerak dibuat dengan menggunakan fungsi panda rolling_mean pada harga penutupan batang saham AAPL. Setelah rata-rata bergerak individual telah dibangun, Seri sinyal dihasilkan dengan menetapkan kolom sama dengan 1.0 ketika rata-rata bergerak pendek lebih besar dari rata-rata bergerak panjang, atau 0.0 sebaliknya. Dari ini pesanan posisi dapat dihasilkan untuk mewakili sinyal perdagangan.

# 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

The MarketOnClosePortfolio diklasifikasikan dari Portfolio, yang ditemukan dibacktest.py. Ini hampir identik dengan implementasi yang dijelaskan dalam tutorial sebelumnya, dengan pengecualian bahwa perdagangan sekarang dilakukan pada dasar Close-to-Close, bukan pada dasar Open-to-Open. Untuk rincian tentang bagaimana objek Portfolio didefinisikan, lihat tutorial sebelumnya.

# 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 setelah kelas MovingAverageCrossStrategy dan MarketOnClosePortfolio telah didefinisikan,utamafungsi akan dipanggil untuk mengikat semua fungsi bersama-sama Selain itu kinerja strategi akan diperiksa melalui plot kurva ekuitas.

Objek panda DataReader mengunduh harga OHLCV saham AAPL untuk periode 1 Januari 1990 hingga 1 Januari 2002, pada saat itu sinyal DataFrame dibuat untuk menghasilkan sinyal yang hanya panjang.

Langkah terakhir adalah menggunakan matplotlib untuk memetakan grafik dua digit dari kedua harga AAPL, yang dilapisi dengan rata-rata bergerak dan sinyal beli / jual, serta kurva ekuitas dengan sinyal 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()

Output grafis dari kode adalah sebagai berikut. Saya menggunakan perintah IPython %paste untuk menempatkan ini langsung ke konsol IPython saat di Ubuntu, sehingga output grafis tetap terlihat. Upticks merah muda mewakili pembelian saham, sementara downticks hitam mewakili menjualnya kembali:imgAAPL Moving Average Crossover Performance dari 1990-01-01 sampai 2002-01-01

Seperti yang dapat dilihat, strategi ini kehilangan uang selama periode tersebut, dengan lima perdagangan pulang pergi. Hal ini tidak mengherankan mengingat perilaku AAPL selama periode tersebut, yang mengalami kecenderungan penurunan ringan, diikuti oleh peningkatan yang signifikan mulai tahun 1998.

Dalam artikel berikutnya kita akan menciptakan cara yang lebih canggih untuk menganalisis kinerja, serta menjelaskan bagaimana mengoptimalkan periode lookback dari sinyal rata-rata bergerak individu.


Lebih banyak