Sumber dimuat naik... memuat...

Penyelidikan Backtesting Environments dalam Python dengan panda

Penulis:Kebaikan, Dicipta: 2019-03-16 11:58:20, Dikemas kini:

Backtesting adalah proses penyelidikan yang menggunakan idea strategi perdagangan kepada data sejarah untuk memastikan prestasi masa lalu. Khususnya, backtester tidak memberikan jaminan mengenai prestasi masa depan strategi.

Dalam artikel ini (dan yang mengikutinya) sistem backtesting berorientasikan objek asas yang ditulis dalam Python akan digariskan. Sistem awal ini terutamanya akan menjadi bantuan pengajaran, digunakan untuk menunjukkan komponen yang berbeza dari sistem backtesting.

Ringkasan Ujian Kembali

Proses merancang sistem backtesting yang kukuh sangat sukar. Mensimulasikan secara berkesan semua komponen yang mempengaruhi prestasi sistem dagangan algoritma adalah mencabar. Granulariti data yang lemah, ketidakjelasan laluan pesanan di broker, latensi pesanan dan banyak faktor lain bersekongkol untuk mengubah prestasi benar strategi berbanding prestasi backtested.

Apabila membangunkan sistem backtesting, adalah menggoda untuk sentiasa menulis semula dari awal kerana lebih banyak faktor didapati penting dalam menilai prestasi. Tiada sistem backtesting pernah selesai dan penilaian mesti dibuat pada satu ketika semasa pembangunan bahawa cukup faktor telah ditangkap oleh sistem.

Dengan ini kebimbangan dalam fikiran backtester yang dibentangkan di sini akan agak sederhana. apabila kita meneroka isu-isu lanjut (optimumkan portfolio, pengurusan risiko, urus niaga kos urus niaga) backtester akan menjadi lebih kukuh.

Jenis Sistem Ujian Belakang

Terdapat dua jenis sistem backtesting yang akan menarik minat. Yang pertama adalah berasaskan penyelidikan, digunakan terutamanya pada peringkat awal, di mana banyak strategi akan diuji untuk memilih yang untuk penilaian yang lebih serius.

Jenis sistem backtesting kedua adalah berdasarkan peristiwa. iaitu, ia menjalankan proses backtesting dalam gelung pelaksanaan yang serupa (jika tidak sama) dengan sistem pelaksanaan perdagangan itu sendiri. Ia akan memodelkan data pasaran dan proses pelaksanaan pesanan secara realistik untuk memberikan penilaian strategi yang lebih ketat.

Sistem terakhir sering ditulis dalam bahasa berprestasi tinggi seperti C ++ atau Java, di mana kelajuan pelaksanaan adalah penting.

Penguji Balik Penyelidikan Berorientasikan Objek dalam Python

Reka bentuk dan pelaksanaan persekitaran backtesting berasaskan penyelidikan berorientasikan objek akan dibincangkan.

  • Antara muka setiap komponen boleh ditentukan terlebih dahulu, manakala bahagian dalaman setiap komponen boleh diubah (atau diganti) semasa projek berkembang
  • Dengan menentukan antara muka terlebih dahulu, adalah mungkin untuk menguji dengan berkesan bagaimana setiap komponen bertindak (melalui ujian unit)
  • Apabila memperluaskan sistem komponen baru boleh dibina atas atau sebagai tambahan kepada yang lain, sama ada dengan warisan atau komposisi

Pada peringkat ini, backtester direka untuk kemudahan pelaksanaan dan tahap fleksibiliti yang munasabah, dengan mengorbankan ketepatan pasaran yang sebenar. Khususnya, backtester ini hanya akan dapat mengendalikan strategi yang bertindak pada satu instrumen. Kemudian backtester akan diubah suai untuk mengendalikan set instrumen. Untuk backtester awal, komponen berikut diperlukan:

  • Strategi - Kelas Strategi menerima Pandas DataFrame bar, iaitu senarai titik data Open-High-Low-Close-Volume (OHLCV) pada frekuensi tertentu. Strategi akan menghasilkan senarai isyarat, yang terdiri daripada cap masa dan elemen dari set {1,0,−1} yang menunjukkan isyarat panjang, tahan atau pendek.
  • Portfolio - Majoriti kerja backtesting akan berlaku dalam kelas Portfolio. Ia akan menerima satu set isyarat (seperti yang diterangkan di atas) dan membuat satu siri kedudukan, yang diperuntukkan terhadap komponen tunai.
  • Prestasi - Objek Prestasi mengambil portfolio dan menghasilkan satu set statistik mengenai prestasinya. khususnya ia akan mengeluarkan ciri risiko / pulangan (Sharpe, Sortino dan Rasio Maklumat), metrik perdagangan / keuntungan dan maklumat pengambilan.

Apa yang hilang?

Seperti yang dapat dilihat, backtester ini tidak termasuk sebarang rujukan kepada pengurusan portfolio / risiko, penanganan pelaksanaan (iaitu tidak ada perintah had) dan juga tidak akan menyediakan pemodelan kos transaksi yang canggih. Ini tidak banyak masalah pada peringkat ini. Ia membolehkan kita membiasakan diri dengan proses membuat backtester berorientasikan objek dan perpustakaan Pandas / NumPy. Pada masa yang akan diperbaiki.

Pelaksanaan

Kami kini akan meneruskan untuk menggariskan pelaksanaan untuk setiap objek.

Strategi

Objek Strategi mestilah agak generik pada peringkat ini, kerana ia akan mengendalikan ramalan, pembalikan purata, momentum dan strategi turun naik. Strategi yang dipertimbangkan di sini akan sentiasa berasaskan siri masa, iaitu price driven. Keperluan awal untuk backtester ini adalah bahawa kelas Strategi yang berasal akan menerima senarai bar (OHLCV) sebagai input, dan bukannya tik (harga perdagangan-oleh-dagang) atau data buku pesanan. Oleh itu, granulariti terbaik yang dipertimbangkan di sini akan menjadi bar 1 saat.

Kelas Strategi juga akan sentiasa menghasilkan cadangan isyarat. Ini bermakna ia akan menasihati contoh Portfolio dalam erti pergi panjang / pendek atau memegang kedudukan. Fleksibiliti ini akan membolehkan kita membuat beberapa Strategy advisors yang menyediakan satu set isyarat, yang kelas Portfolio yang lebih maju boleh menerima untuk menentukan kedudukan sebenar yang dimasukkan.

Antara muka kelas akan dikuatkuasakan dengan menggunakan metodologi kelas asas abstrak. Kelas asas abstrak adalah objek yang tidak dapat di-instansikan dan dengan itu hanya kelas turunan yang boleh dibuat. Kod Python diberikan di bawah dalam fail yang dipanggilbacktest.py. Kelas Strategy memerlukan bahawa mana-mana subclass melaksanakan generate_signals kaedah.

Untuk mengelakkan kelas Strategy daripada menjadi instan secara langsung (kerana ia abstrak!) adalah perlu untuk menggunakan ABCMeta dan abstrak obyek kaedah dari modul abc.Meta-kelasuntuk sama dengan ABCMeta dan kemudian menghiasi generate_signals kaedah dengan abstrakmethod 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()!")

Walaupun antara muka di atas adalah mudah, ia akan menjadi lebih rumit apabila kelas ini diwarisi untuk setiap jenis strategi tertentu.

Portfolio

Kelas Portfolio adalah di mana sebahagian besar logik perdagangan akan berada. Untuk backtester penyelidikan ini, Portfolio bertanggungjawab untuk menentukan saiz kedudukan, analisis risiko, pengurusan kos transaksi dan penanganan pelaksanaan (iaitu pesanan pasaran terbuka, pasaran ditutup). Pada peringkat kemudian tugas-tugas ini akan dipecah menjadi komponen berasingan.

Kelas ini menggunakan panda secara meluas dan memberikan contoh yang baik di mana perpustakaan boleh menjimatkan banyak masa, terutamanya berkaitan dengan boilerplate data wrangling. Sebagai sisi lain, trik utama dengan panda dan NumPy adalah untuk mengelakkan mengulangi set data apa pun menggunakan sintaks for d in... Ini kerana NumPy (yang mendasari panda) mengoptimumkan gelung dengan operasi vektor. Oleh itu, anda akan melihat sedikit (jika ada!) pengulangan langsung ketika menggunakan panda.

Tujuan kelas Portfolio adalah untuk menghasilkan urutan dagangan dan kurva ekuiti, yang akan dianalisis oleh kelas Prestasi. Untuk mencapai ini, ia mesti disediakan dengan senarai cadangan perdagangan dari objek Strategi. Kemudian, ini akan menjadi kumpulan objek Strategi.

Kelas Portfolio perlu diberitahu bagaimana modal akan digunakan untuk satu set isyarat perdagangan tertentu, bagaimana menangani kos transaksi dan bentuk pesanan yang akan digunakan. Objek Strategi beroperasi pada bar data dan oleh itu anda perlu membuat andaian mengenai harga yang dicapai semasa pelaksanaan pesanan. Oleh kerana harga tinggi / rendah mana-mana bar tidak diketahui terlebih dahulu, hanya mungkin untuk menggunakan harga buka dan tutup untuk perdagangan.

Sebagai tambahan kepada andaian mengenai pesanan yang dipenuhi, backtester ini akan mengabaikan semua konsep sekatan margin / broker dan akan menganggap bahawa adalah mungkin untuk pergi panjang dan pendek dalam mana-mana instrumen secara bebas tanpa sebarang sekatan kecairan.

Senarai berikut berterusanbacktest.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 peringkat ini, kelas asas abstrak Strategi dan Portfolio telah diperkenalkan.

Kami akan mula dengan menjana subkelas Strategi yang dipanggil RandomForecastStrategy, yang satu-satunya tugasnya adalah untuk menghasilkan isyarat panjang / pendek yang dipilih secara rawak! Walaupun ini jelas merupakan strategi perdagangan yang tidak masuk akal, ia akan memenuhi keperluan kami dengan menunjukkan rangka kerja pengujian belakang berorientasikan objek. Oleh itu, kami akan memulakan fail baru yang dipanggil random_forecast.py, dengan senarai untuk ramalan rawak seperti 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 kita mempunyai sistem ramalan konkrit, kita mesti membuat pelaksanaan objek Portfolio. Objek ini akan merangkumi majoriti kod backtesting. Ia direka untuk membuat dua DataFrames yang berasingan, yang pertama adalah bingkai kedudukan, yang digunakan untuk menyimpan kuantiti setiap instrumen yang dipegang di mana-mana bar tertentu. Yang kedua, portfolio, sebenarnya mengandungi harga pasaran semua pegangan untuk setiap bar, serta pengiraan tunai, dengan mengandaikan modal awal. Ini akhirnya menyediakan kurva ekuiti untuk menilai prestasi strategi.

Objek Portfolio, walaupun sangat fleksibel dalam antara muka, memerlukan pilihan khusus mengenai bagaimana untuk mengendalikan kos transaksi, pesanan pasaran dan lain-lain. Dalam contoh asas ini, saya telah menganggap bahawa ia akan mungkin untuk pergi panjang / pendek instrumen dengan mudah tanpa sekatan atau margin, membeli atau menjual terus pada harga terbuka bar, kos transaksi sifar (termasuk slippage, yuran dan kesan pasaran) dan telah menentukan jumlah stok secara langsung untuk membeli untuk setiap perdagangan.

Berikut adalah kesinambungan senarai 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 perlukan untuk menjana lengkung ekuiti berdasarkan sistem tersebut.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)

Keluaran program adalah seperti berikut. anda akan berbeza dari output di bawah bergantung pada julat tarikh yang anda pilih dan benih rawak yang digunakan:

          SPY  holdings    cash  total   returns

Tarikh
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 kes ini strategi kehilangan wang, yang tidak menghairankan memandangkan sifat stokastik ramalan! Langkah seterusnya adalah untuk membuat objek Prestasi yang menerima contoh Portfolio dan menyediakan senarai metrik prestasi yang berdasarkan keputusan untuk menapis strategi atau tidak.

Kami juga boleh meningkatkan objek Portfolio untuk mempunyai penanganan kos transaksi yang lebih realistik (seperti komisen dan slippage Interactive Brokers). Kami juga boleh memasukkan enjin ramalan secara mudah ke dalam objek Strategi, yang akan (mudah-mudahan) menghasilkan hasil yang lebih baik.


Lebih lanjut