Kiểm tra lại một Crossover Moving Average trong Python với pandas

Tác giả:Tốt, Tạo: 2019-03-27 15:11:40, Cập nhật:

Trong bài viết này chúng tôi sẽ sử dụng các máy móc chúng tôi đã giới thiệu để thực hiện nghiên cứu về một chiến lược thực tế, cụ thể là Moving Average Crossover trên AAPL.

Chiến lược chéo trung bình di chuyển

Kỹ thuật giao dịch chuyển động trung bình là một chiến lược chuyển động đơn giản cực kỳ nổi tiếng.

Chiến lược như được nêu ở đây chỉ dài. Hai bộ lọc trung bình động đơn giản riêng biệt được tạo ra, với các khoảng thời gian nhìn lại khác nhau, của một chuỗi thời gian cụ thể. Các tín hiệu mua tài sản xảy ra khi trung bình động nhìn lại ngắn hơn vượt quá trung bình động nhìn lại dài hơn. Nếu trung bình dài hơn sau đó vượt quá trung bình ngắn hơn, tài sản được bán lại. Chiến lược hoạt động tốt khi một chuỗi thời gian bước vào một giai đoạn xu hướng mạnh và sau đó từ từ đảo ngược xu hướng.

Đối với ví dụ này, tôi đã chọn Apple, Inc. (AAPL) như là chuỗi thời gian, với một lookback ngắn 100 ngày và một lookback dài 400 ngày. Đây là ví dụ được cung cấp bởi thư viện giao dịch thuật toán zipline. Do đó, nếu chúng ta muốn thực hiện backtester của riêng chúng ta, chúng ta cần đảm bảo rằng nó phù hợp với kết quả trong zipline, như một phương tiện xác thực cơ bản.

Thực hiện

Hãy chắc chắn làm theo hướng dẫn trước đây ở đây, mô tả cách cấu trúc phân cấp đối tượng ban đầu cho backtester, nếu không mã dưới đây sẽ không hoạt động.

  • Python - 2.7.3
  • Số - 1.8.0
  • panda - 0.12.0
  • matplotlib - 1.1.0

Việc thực hiện ma_cross.py đòi hỏibacktest.pyTừ hướng dẫn trước. Bước đầu tiên là nhập các mô-đun và đối tượng cần thiết:

# 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

Như trong hướng dẫn trước đây, chúng ta sẽ phân cấp lớp cơ sở trừu tượng Chiến lược để tạo ra MovingAverageCrossStrategy, chứa tất cả các chi tiết về cách tạo ra các tín hiệu khi các đường trung bình động của AAPL giao nhau.

Đối tượng này yêu cầu một short_window và một long_window để hoạt động. Các giá trị đã được đặt thành mặc định lần lượt 100 ngày và 400 ngày, đó là các tham số tương tự được sử dụng trong ví dụ chính của zipline.

Các đường trung bình động được tạo bằng cách sử dụng hàm panda rolling_mean trên giá đóng cửa của cổ phiếu AAPL. Một khi các đường trung bình động đã được xây dựng, chuỗi tín hiệu được tạo ra bằng cách đặt cột bằng 1.0 khi đường trung bình động ngắn lớn hơn đường trung bình động dài, hoặc 0.0 nếu không. Từ đây các lệnh vị trí có thể được tạo ra để đại diện cho tín hiệu giao dịch.

# 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 được phân loại từ Portfolio, được tìm thấy trongbacktest.py. Nó gần như giống hệt với việc thực hiện được mô tả trong hướng dẫn trước, ngoại trừ việc các giao dịch bây giờ được thực hiện trên cơ sở Close-to-Close, chứ không phải là cơ sở Open-to-Open. Để biết chi tiết về cách xác định đối tượng Portfolio, hãy xem hướng dẫn trước. Tôi đã để lại mã để hoàn chỉnh và để hướng dẫn này tự chứa:

# 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

Bây giờ khi các lớp MovingAverageCrossStrategy và MarketOnClosePortfolio đã được xác định, mộtchínhNgoài ra, hiệu suất của chiến lược sẽ được kiểm tra thông qua một biểu đồ của đường cong vốn chủ sở hữu.

Đối tượng Panda DataReader tải xuống giá OHLCV của cổ phiếu AAPL trong giai đoạn từ ngày 1 tháng 1 năm 1990 đến ngày 1 tháng 1 năm 2002, tại thời điểm đó các tín hiệu DataFrame được tạo ra để tạo ra các tín hiệu chỉ dài. Sau đó danh mục đầu tư được tạo ra với cơ sở vốn ban đầu 100.000 USD và lợi nhuận được tính trên đường cong vốn chủ sở hữu.

Bước cuối cùng là sử dụng matplotlib để vẽ một biểu đồ hai chữ số của cả giá AAPL, được chồng lên với các đường trung bình động và tín hiệu mua / bán, cũng như đường cong vốn chủ sở hữu với các tín hiệu mua / bán tương tự.

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

Các đầu ra đồ họa của mã là như sau. Tôi đã sử dụng lệnh IPython %paste để đưa nó trực tiếp vào bảng điều khiển IPython trong khi ở Ubuntu, để đầu ra đồ họa vẫn còn trong tầm nhìn. Các dấu tăng màu hồng đại diện cho việc mua cổ phiếu, trong khi các dấu giảm màu đen đại diện cho việc bán lại:imgAAPL Moving Average Crossover Performance từ 1990-01-01 đến 2002-01-01

Như có thể thấy, chiến lược mất tiền trong giai đoạn này, với năm giao dịch đi lại. Điều này không đáng ngạc nhiên khi xem xét hành vi của AAPL trong giai đoạn này, có xu hướng giảm nhẹ, sau đó tăng đáng kể bắt đầu từ năm 1998.

Trong các bài viết tiếp theo, chúng tôi sẽ tạo ra một phương tiện tinh vi hơn để phân tích hiệu suất, cũng như mô tả cách tối ưu hóa các khoảng thời gian xem lại các tín hiệu trung bình động riêng lẻ.


Thêm nữa