Penelitian Lingkungan Backtesting di Python dengan panda

Penulis:Kebaikan, Dibuat: 2019-03-16 11:58:20, Diperbarui:

Backtesting adalah proses penelitian menerapkan ide strategi trading pada data historis untuk memastikan kinerja masa lalu. khususnya, backtester tidak memberikan jaminan tentang kinerja strategi di masa depan. mereka bagaimanapun merupakan komponen penting dari proses penelitian pipa strategi, memungkinkan strategi untuk disaring sebelum dimasukkan ke dalam produksi.

Dalam artikel ini (dan yang berikutnya) sistem backtesting berorientasi objek dasar yang ditulis dalam Python akan diuraikan. Sistem awal ini terutama akan menjadi bantuan pengajaran, yang digunakan untuk menunjukkan berbagai komponen sistem backtesting.

Tinjauan Ringkas

Proses merancang sistem backtesting yang kuat sangat sulit. Mengsimulasikan secara efektif semua komponen yang mempengaruhi kinerja sistem perdagangan algoritmik adalah tantangan. Granularitas data yang buruk, ketidakjelasan penyaluran order di broker, latensi order dan segudang faktor lain bersekongkol untuk mengubah kinerja true dari strategi versus kinerja backtested.

Ketika mengembangkan sistem backtesting, tergoda untuk terus-menerus "menulis ulang dari awal" karena lebih banyak faktor yang ditemukan penting dalam menilai kinerja.

Dengan memperhatikan masalah ini, backtester yang disajikan di sini akan menjadi agak sederhana.

Jenis Sistem Backtesting

Pada umumnya ada dua jenis sistem backtesting yang akan menarik. yang pertama adalah berbasis penelitian, digunakan terutama pada tahap awal, di mana banyak strategi akan diuji untuk memilih yang untuk penilaian yang lebih serius.

Jenis kedua sistem backtesting adalah berbasis peristiwa. yaitu, ia melakukan proses backtesting dalam loop eksekusi yang mirip (jika tidak identik) dengan sistem eksekusi perdagangan itu sendiri.

Sistem terakhir sering ditulis dalam bahasa berkinerja tinggi seperti C ++ atau Java, di mana kecepatan eksekusi sangat penting.

Backtester Penelitian Berorientasi Objek di Python

Desain dan implementasi lingkungan backtesting berbasis penelitian berorientasi objek akan dibahas.

  • Antarmuka dari setiap komponen dapat ditentukan sebelumnya, sementara bagian dalam dari setiap komponen dapat dimodifikasi (atau diganti) seiring kemajuan proyek
  • Dengan menentukan antarmuka di depan, dimungkinkan untuk secara efektif menguji bagaimana setiap komponen berperilaku (melalui pengujian unit)
  • Ketika memperluas sistem komponen baru dapat dibangun di atas atau sebagai tambahan dari yang lain, baik oleh warisan atau komposisi

Pada tahap ini backtester dirancang untuk kemudahan implementasi dan tingkat fleksibilitas yang wajar, dengan mengorbankan akurasi pasar yang sebenarnya. Secara khusus, backtester ini hanya akan dapat menangani strategi yang bertindak pada satu instrumen. Kemudian backtester akan dimodifikasi untuk menangani serangkaian instrumen. Untuk backtester awal, komponen berikut diperlukan:

  • Strategi - Kelas Strategi menerima Pandas DataFrame dari bar, yaitu daftar titik data Open-High-Low-Close-Volume (OHLCV) pada frekuensi tertentu. Strategi akan menghasilkan daftar sinyal, yang terdiri dari timestamp dan elemen dari set {1,0,−1} yang menunjukkan sinyal panjang, tahan atau pendek masing-masing.
  • Portfolio - Sebagian besar pekerjaan backtesting akan terjadi di kelas Portfolio. Ini akan menerima serangkaian sinyal (seperti yang dijelaskan di atas) dan membuat serangkaian posisi, dialokasikan terhadap komponen tunai.
  • Kinerja - Objek Kinerja mengambil portofolio dan menghasilkan seperangkat statistik tentang kinerjanya. khususnya akan menghasilkan karakteristik risiko / pengembalian (Sharpe, Sortino dan Rasio Informasi), metrik perdagangan / keuntungan dan informasi penarikan.

Apa yang hilang?

Seperti yang dapat dilihat backtester ini tidak termasuk referensi untuk manajemen portofolio / risiko, penanganan eksekusi (yaitu tidak ada batas pesanan) atau tidak akan memberikan pemodelan canggih biaya transaksi. Ini tidak banyak masalah pada tahap ini. Hal ini memungkinkan kita untuk mendapatkan akrab dengan proses pembuatan backtester berorientasi objek dan Pandas / NumPy perpustakaan.

Pelaksanaan

Kami sekarang akan melanjutkan untuk menguraikan implementasi untuk setiap objek.

Strategi

Objek Strategi harus cukup generik pada tahap ini, karena akan menangani strategi peramalan, rata-rata-reversi, momentum dan volatilitas. Strategi yang dipertimbangkan di sini akan selalu berbasis deret waktu, yaitu dikuasai oleh harga. Persyaratan awal untuk backtester ini adalah bahwa kelas Strategi turunan akan menerima daftar bar (OHLCV) sebagai input, bukan tik (harga perdagangan per perdagangan) atau data buku pesanan. Dengan demikian granularitas terbaik yang dipertimbangkan di sini akan menjadi bar 1 detik.

Kelas Strategi juga akan selalu menghasilkan rekomendasi sinyal. Ini berarti bahwa ia akan menyarankan contoh portofolio dalam arti pergi panjang / pendek atau memegang posisi. Fleksibilitas ini akan memungkinkan kita untuk membuat beberapa Strategi advisors yang memberikan seperangkat sinyal, yang lebih maju Kelas Portfolio dapat menerima untuk menentukan posisi yang sebenarnya dimasukkan.

Antarmuka kelas akan diberlakukan dengan menggunakan metodologi kelas dasar abstrak. Kelas dasar abstrak adalah objek yang tidak dapat diinstansikan dan dengan demikian hanya kelas turunan yang dapat dibuat.backtest.py. Kelas Strategi mengharuskan bahwa setiap subkelas menerapkan generate_signals metode.

Untuk mencegah kelas Strategy dari instansiasi langsung (karena abstrak!) adalah perlu untuk menggunakan ABCMeta dan abstrak obyek metode dari modul abc.Metakelasuntuk sama dengan ABCMeta dan kemudian mendekorasi generate_signals metode dengan abstrak metode dekorator.

# backtest.py

from abc import ABCMeta, abstractmethod

class Strategy(object):
    """Strategy is an abstract base class providing an interface for
    all subsequent (inherited) trading strategies.

    The goal of a (derived) Strategy object is to output a list of signals,
    which has the form of a time series indexed pandas DataFrame.

    In this instance only a single symbol/instrument is supported."""

    __metaclass__ = ABCMeta

    @abstractmethod
    def generate_signals(self):
        """An implementation is required to return the DataFrame of symbols 
        containing the signals to go long, short or hold (1, -1 or 0)."""
        raise NotImplementedError("Should implement generate_signals()!")

Sementara antarmuka di atas sederhana, akan menjadi lebih rumit ketika kelas ini diwarisi untuk setiap jenis strategi tertentu.

Portfolio

Di mana sebagian besar logika perdagangan akan berada di kelas portofolio. Untuk backtester penelitian ini, portofolio bertanggung jawab untuk menentukan ukuran posisi, analisis risiko, manajemen biaya transaksi dan penanganan eksekusi (yaitu pasar terbuka, pasar tertutup). Pada tahap selanjutnya tugas-tugas ini akan dipecah menjadi komponen terpisah. Saat ini mereka akan digulung ke dalam satu kelas.

Kelas ini menggunakan panda secara luas dan memberikan contoh besar di mana perpustakaan dapat menghemat banyak waktu, terutama dalam hal boilerplate data wrangling. Sebagai sisi lain, trik utama dengan panda dan NumPy adalah untuk menghindari iterasi di atas setiap dataset menggunakan d di... sintaksis. Ini karena NumPy (yang mendasari panda) mengoptimalkan looping oleh operasi vektorisasi. Dengan demikian Anda akan melihat sedikit (jika ada!)

Tujuan dari kelas Portfolio adalah untuk akhirnya menghasilkan urutan perdagangan dan kurva ekuitas, yang akan dianalisis oleh kelas Performance. Untuk mencapai hal ini harus disediakan dengan daftar rekomendasi perdagangan dari objek Strategi.

Kelas portofolio perlu diberitahu bagaimana modal akan digunakan untuk satu set sinyal perdagangan tertentu, bagaimana menangani biaya transaksi dan bentuk pesanan yang akan digunakan. Objek Strategi beroperasi pada batang data dan dengan demikian asumsi harus dibuat mengenai harga yang dicapai pada saat eksekusi pesanan. Karena harga tinggi/rendah dari setiap batang tidak diketahui a priori hanya mungkin untuk menggunakan harga buka dan tutup untuk perdagangan.

Selain asumsi tentang pemesanan yang dipenuhi, backtester ini akan mengabaikan semua konsep batasan margin / broker dan akan berasumsi bahwa mungkin untuk pergi panjang dan pendek dalam instrumen apa pun secara bebas tanpa batasan likuiditas.

Daftar berikut terusbacktest.py:

# backtest.py

class Portfolio(object):
    """An abstract base class representing a portfolio of 
    positions (including both instruments and cash), determined
    on the basis of a set of signals provided by a Strategy."""

    __metaclass__ = ABCMeta

    @abstractmethod
    def generate_positions(self):
        """Provides the logic to determine how the portfolio 
        positions are allocated on the basis of forecasting
        signals and available cash."""
        raise NotImplementedError("Should implement generate_positions()!")

    @abstractmethod
    def backtest_portfolio(self):
        """Provides the logic to generate the trading orders
        and subsequent equity curve (i.e. growth of total equity),
        as a sum of holdings and cash, and the bar-period returns
        associated with this curve based on the 'positions' DataFrame.

        Produces a portfolio object that can be examined by 
        other classes/functions."""
        raise NotImplementedError("Should implement backtest_portfolio()!")

Pada tahap ini kelas dasar abstrak Strategi dan Portofolio telah diperkenalkan.

Kami akan mulai dengan menghasilkan subkelas Strategi yang disebut RandomForecastStrategy, yang satu-satunya tugasnya adalah menghasilkan sinyal panjang / pendek yang dipilih secara acak! Meskipun ini jelas merupakan strategi perdagangan yang tidak masuk akal, ini akan memenuhi kebutuhan kita dengan menunjukkan kerangka kerja backtesting berorientasi objek. Dengan demikian kami akan memulai file baru yang disebut random_forecast.py, dengan daftar untuk forecaster acak sebagai berikut:

# random_forecast.py

import numpy as np
import pandas as pd
import Quandl   # Necessary for obtaining financial data easily

from backtest import Strategy, Portfolio

class RandomForecastingStrategy(Strategy):
    """Derives from Strategy to produce a set of signals that
    are randomly generated long/shorts. Clearly a nonsensical
    strategy, but perfectly acceptable for demonstrating the
    backtesting infrastructure!"""    
    
    def __init__(self, symbol, bars):
    	"""Requires the symbol ticker and the pandas DataFrame of bars"""
        self.symbol = symbol
        self.bars = bars

    def generate_signals(self):
        """Creates a pandas DataFrame of random signals."""
        signals = pd.DataFrame(index=self.bars.index)
        signals['signal'] = np.sign(np.random.randn(len(signals)))

        # The first five elements are set to zero in order to minimise
        # upstream NaN errors in the forecaster.
        signals['signal'][0:5] = 0.0
        return signals

Sekarang setelah kita memiliki sistem peramalan konkrit, kita harus membuat implementasi dari objek portofolio. Objek ini akan mencakup sebagian besar kode backtesting. Hal ini dirancang untuk membuat dua DataFrames terpisah, yang pertama adalah kerangka posisi, yang digunakan untuk menyimpan jumlah setiap instrumen yang dipegang di setiap bar tertentu. Yang kedua, portofolio, sebenarnya berisi harga pasar dari semua kepemilikan untuk setiap bar, serta penghitungan uang tunai, dengan asumsi modal awal. Ini pada akhirnya memberikan kurva ekuitas untuk menilai kinerja strategi.

Objek portofolio, meskipun sangat fleksibel dalam antarmuka, membutuhkan pilihan khusus ketika menyangkut bagaimana menangani biaya transaksi, pesanan pasar dll. Dalam contoh dasar ini saya telah mempertimbangkan bahwa akan mungkin untuk pergi panjang / pendek instrumen dengan mudah tanpa batasan atau margin, membeli atau menjual langsung pada harga terbuka bar, nol biaya transaksi (mencakup slippage, biaya dan dampak pasar) dan telah menentukan jumlah saham langsung untuk membeli untuk setiap perdagangan.

Berikut adalah kelanjutan dari daftar random_forecast.py:

# random_forecast.py

class MarketOnOpenPortfolio(Portfolio):
    """Inherits Portfolio to create a system that purchases 100 units of 
    a particular symbol upon a long/short signal, assuming the market 
    open price of a bar.

    In addition, there are zero transaction costs and cash can be immediately 
    borrowed for shorting (no margin posting or interest requirements). 

    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):
    	"""Creates a 'positions' DataFrame that simply longs or shorts
    	100 of the particular symbol based on the forecast signals of
    	{1, 0, -1} from the signals DataFrame."""
        positions = pd.DataFrame(index=signals.index).fillna(0.0)
        positions[self.symbol] = 100*signals['signal']
        return positions
                    
    def backtest_portfolio(self):
    	"""Constructs a portfolio from the positions DataFrame by 
    	assuming the ability to trade at the precise market open price
    	of each bar (an unrealistic assumption!). 

    	Calculates the total of cash and the holdings (market price of
    	each position per bar), in order to generate an equity curve
    	('total') and a set of bar-based returns ('returns').

    	Returns the portfolio object to be used elsewhere."""

    	# Construct the portfolio DataFrame to use the same index
    	# as 'positions' and with a set of 'trading orders' in the
    	# 'pos_diff' object, assuming market open prices.
        portfolio = self.positions*self.bars['Open']
        pos_diff = self.positions.diff()

        # Create the 'holdings' and 'cash' series by running through
        # the trades and adding/subtracting the relevant quantity from
        # each column
        portfolio['holdings'] = (self.positions*self.bars['Open']).sum(axis=1)
        portfolio['cash'] = self.initial_capital - (pos_diff*self.bars['Open']).sum(axis=1).cumsum()

        # Finalise the total and bar-based returns based on the 'cash'
        # and 'holdings' figures for the portfolio
        portfolio['total'] = portfolio['cash'] + portfolio['holdings']
        portfolio['returns'] = portfolio['total'].pct_change()
        return portfolio

Ini memberi kita semua yang kita butuhkan untuk menghasilkan kurva ekuitas berdasarkan sistem seperti itu.utamaFungsi:

if __name__ == "__main__":
    # Obtain daily bars of SPY (ETF that generally 
    # follows the S&P500) from Quandl (requires 'pip install Quandl'
    # on the command line)
    symbol = 'SPY'
    bars = Quandl.get("GOOG/NYSE_%s" % symbol, collapse="daily")

    # Create a set of random forecasting signals for SPY
    rfs = RandomForecastingStrategy(symbol, bars)
    signals = rfs.generate_signals()

    # Create a portfolio of SPY
    portfolio = MarketOnOpenPortfolio(symbol, bars, signals, initial_capital=100000.0)
    returns = portfolio.backtest_portfolio()

    print returns.tail(10)

Output dari program adalah sebagai berikut. Anda akan berbeda dari output di bawah ini tergantung pada rentang tanggal yang Anda pilih dan random seed yang digunakan:

          SPY  holdings    cash  total   returns

Tanggal
2014-01-02 -18398 -18398 111486 93088 0.000097 2014-01-03 18321 18321 74844 93165 0.000827 2014-01-06 18347 18347 74844 93191 0.000279 2014-01-07 18309 18309 74844 93153 -0.000408 2014-01-08 -18345 -18345 111534 93189 0.000386 2014-01-09 -18410 -18410 111534 93124 -0.000698 2014-01-10 -18395 -18395 111534 93139 0.000161 2014-01-13 -18371 -18371 111534 93163 0.000258 2014-01-14 -18228 -18228 111534 93306 0.001535 2014-01-15 18410 18410 74714 93124 -0.001951

Dalam hal ini strategi kehilangan uang, yang tidak mengherankan mengingat sifat stokastik dari peramal! Langkah selanjutnya adalah untuk membuat objek Performance yang menerima contoh portofolio dan menyediakan daftar metrik kinerja yang didasarkan pada keputusan untuk menyaring strategi atau tidak.

Kami juga dapat meningkatkan objek Portfolio untuk memiliki penanganan yang lebih realistis dari biaya transaksi (seperti komisi dan slippage Interactive Brokers). Kami juga dapat langsung memasukkan mesin peramalan ke dalam objek Strategi, yang (mudah-mudahan) akan menghasilkan hasil yang lebih baik.


Lebih banyak