Бактестирование кроссовера скользящей средней в Python с пандами

Автор:Доброта, Создано: 2019-03-27 15:11:40, Обновлено:

В этой статье мы будем использовать механизм, который мы представили для проведения исследования реальной стратегии, а именно пересечения скользящей средней на AAPL.

Стратегия перекрестного использования скользящей средней

Метод пересечения скользящей средней является чрезвычайно известной упрощенной стратегией импульса.

Стратегия, описанная здесь, является только длинной. Два отдельных простых фильтра скользящих средних, с различными периодами обратного обзора, создаются для определенной временной серии. Сигналы для покупки актива возникают, когда более короткая скользящая средняя превышает более длинную скользящую среднюю. Если более длинная средняя впоследствии превышает более короткую среднюю, актив продается обратно.

Для этого примера я выбрал Apple, Inc. (AAPL) в качестве временного ряда, с коротким обратным взглядом на 100 дней и длинным обратным взглядом на 400 дней. Это пример, предоставленный библиотекой алгоритмической торговли zipline. Таким образом, если мы хотим реализовать свой собственный backtester, мы должны убедиться, что он соответствует результатам в zipline, как основное средство проверки.

Использование

Убедитесь, что вы следуете предыдущему руководству здесь, которое описывает, как строится первоначальная иерархия объектов для backtester, иначе код ниже не будет работать.

  • Python - 2.7.3
  • NumPy - 1.8.0
  • Панды - 0.12.0
  • Matplotlib - 1.1.0

Реализация ma_cross.py требуетbacktest.pyПервый шаг - импортировать необходимые модули и объекты:

# 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

Как и в предыдущем уроке, мы собираемся разделить на подклассы абстрактный базовый класс Стратегия, чтобы произвести MovingAverageCrossStrategy, который содержит все детали о том, как генерировать сигналы, когда скользящие средние AAPL пересекают друг друга.

Объект требует короткого_окна и длинного_окна для работы. Значения установлены на 100 дней и 400 дней соответственно, которые являются теми же параметрами, которые используются в главном примере zipline.

Движущиеся средние создаются с использованием функции panda rolling_mean на барах [Close] ценой закрытия акций AAPL. После того, как отдельные движущиеся средние были построены, серия сигналов генерируется путем установки столбца равна 1,0 когда короткая движущаяся средняя больше длинной движущейся средней, или 0.0 в противном случае. Из этого можно генерировать ордера на позиции для представления торговых сигналов.

# 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 является подразделом от Portfolio, который находится вbacktest.py. Он практически идентичен реализации, описанной в предыдущем руководстве, за исключением того, что сделки теперь выполняются на основе Close-to-Close, а не на основе Open-to-Open.

# 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

Теперь, когда были определены классы MovingAverageCrossStrategy и MarketOnClosePortfolio,главныйКроме того, эффективность стратегии будет рассматриваться с помощью графика кривой собственности.

Объект panda DataReader загружает цены на акции AAPL по OHLCV за период с 1 января 1990 года по 1 января 2002 года, после чего создаются сигналы DataFrame для генерирования сигналов только длинных.

Последним шагом является использование matplotlib для составления двузначного графика цен AAPL, наложенного на скользящие средние и сигналы покупки / продажи, а также кривой акций с теми же самыми сигналами покупки / продажи.

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

Графический выход кода выглядит следующим образом. Я использовал команду IPython %paste, чтобы поместить его непосредственно в консоль IPython, пока я нахожусь в Ubuntu, чтобы графический выход оставался в поле зрения. Розовые подъемы представляют покупку акций, а черные понижения представляют продажу:imgСредняя скользящая кроссоверная производительность AAPL с 1990 по 2002 год

Как видно, стратегия теряет деньги в течение периода, с пятью сделками туда-обратно. Это не удивительно, учитывая поведение AAPL в течение периода, которое было на небольшой тенденции к снижению, а затем значительный подъем, начиная с 1998 года. Период обратной связи сигналов скользящих средних довольно большой, и это повлияло на прибыль от окончательной торговли, которая в противном случае могла бы сделать стратегию прибыльной.

В последующих статьях мы создадим более сложный способ анализа производительности, а также опишем, как оптимизировать периоды обратной связи отдельных сигналов скользящих средних.


Больше