方法:Pythonのオブジェクト指向バックテストシステム





有名な英国のトレーダーであり開発者のマイク・ホールズ・ムーア 、彼のブログで、取引所で金融取引戦略をバックテストするためのオブジェクト指向システムを作成する方法に関する記事を書きました。 この資料の主な考えに注目してください。



バックテストとは



バックテストは、過去のパフォーマンスを評価するために、特定の取引戦略を過去の日付に適用するプロセスとして理解されます。 ただし、これはシステムが将来成功するという保証はありません。 それでも、実際の取引で時間とお金を費やすに値しない戦略を「除外」する方法があります。



アルゴリズム取引戦略のパフォーマンスに影響するさまざまなコンポーネントの動作を正常にモデル化できる必要があるため、信頼性の高いバックテストシステムを作成することは簡単ではありません。 不十分なデータ、クライアントとブローカー間の通信チャネルの問題、アプリケーションの実行の遅延-これらは、トランザクションが成功するかどうかに影響を与える可能性のあるわずかな要因です。



これらの要因のすべてが事前に知られているわけではないため、トレーダーが取引所での取引プロセスに他に影響を与えるものを見つけると、通常、この新しい知識を反映するバックテストシステムが追加されます。 この記事では、Pythonを使用してこのような単純なシステムを作成する例を見ていきます。



バックテストシステムの種類



バックテストシステムには主に2つのタイプがあります。 それらの1つは「研究ベース」と呼ばれ、今後の作業で最も有望なものを選択する必要がある場合に、戦略を評価する初期段階の大部分で使用されます。 このような場合、開発速度は作業速度よりも重要であるため、このようなシステムは多くの場合Python、RまたはMatlabで記述されています。



次のタイプは、イベントベースのバックテストです。 彼らの場合、バックテストプロセスは、実際の取引に可能な限り近い(同一でない場合)シナリオに従って行われます。 このシステムは、市場データと注文執行プロセスを現実的にシミュレートし、分析された戦略をより深く評価することを可能にします。



多くの場合、そのようなシステムはC ++で記述されています。これは、作業の速度がすでに重要な役割を果たしているためです。 ただし、Pythonを使用して、非常に高速な操作を必要としない戦略をテストすることはできます(ここでは、この言語でのイベント指向のバックテスターの作成を検討しました)。



Pythonオブジェクト指向バックテスター



バックテスター開発へのオブジェクト指向アプローチには、次の利点があります。





この例では、1つの金融商品(たとえば、株式)のみを使用する戦略を扱うことができる単純なバックテスターを作成します。 このようなシステムでは、次のコンポーネントが必要です。







見やすいように、この場合、リスク管理、注文処理(つまり、指値注文の処理方法がわからない)、またはトランザクションコストの複雑なモデリングに関連するオブジェクトを除外します。 ここでの課題は、後で改善できる基本的なバックテスターを作成することです。



実装



次に、使用される各オブジェクトの実装を検討します。



戦略


Strategyオブジェクトは、価格の予測、平均値への復帰(平均復帰)、勢い、およびボラティリティの戦略を処理する必要があります。 この例で考慮される戦略は、常に時系列、つまり「価格主導」に基づいています。 特に、これは、オブジェクトがオークションに関する情報のティックではなく、OHLCVインジケーターのセットを受け取ることを意味します。 したがって、ここで可能な最大の詳細は1秒のバーです。



さらに、Strategyクラスはシグナリングの推奨事項を生成します。 これは、彼がPortfolioオブジェクトにどのアクションが最適かをアドバイスすることを意味します。 その後、Portfolioクラスは、これらの推奨事項とともにデータを分析して、ポジションに出入りするための一連のシグナルを生成します。



クラスインターフェイスは、 抽象基本クラスの方法論を使用して実装されます 。 Pythonコードはbacktest.pyファイルにあります。 Strategyクラスでは、実装されたサブクラスがgenerate_signalsメソッドを使用する必要があります。



Strategyのインスタンスを作成しないようにするには(これも抽象的です)、abcモジュールのABCMetaおよびabstractmethodオブジェクトを使用する必要があります。 _metaclass_



クラス_metaclass_



をABCMetaに設定し、 abstractmethod



デコレータを使用してgenerate_signals



メソッドをデコレートします。



 # backtest.py from abc import ABCMeta, abstractmethod class Strategy(object): """Strategy —    ,           Strategy     ,      - pandas.          .""" __metaclass__ = ABCMeta @abstractmethod def generate_signals(self): """    ,        ,    (1, -1 or 0).""" raise NotImplementedError("Should implement generate_signals()!")
      
      





ポートフォリオ


Portfolioクラスには、ほとんどの取引ロジックが含まれています。 このバックテスターの場合、このオブジェクトはポジションサイズの決定、リスクの分析、トランザクションコストを担当します。 さらなる開発の過程で、これらのタスクは個別のコンポーネントに分割する必要がありますが、1つのクラスに結合できるようになりました。



このクラスを実装するには、パンダを使用します-このライブラリは、ここで膨大な時間を節約できます。 唯一のポイントは、 for d in …



構文を使用したデータセットの反復を避けることfor d in …



。 実際、NumPyはベクトル化された操作を使用してループを最適化します。 したがって、パンダを使用する場合、直接の反復はほとんど発生しません。



ポートフォリオクラスの目的は、最終的に一連のトランザクションと資本曲線を生成し、パフォーマンスクラスによって分析することです。 これを行うには、クラスはStrategyオブジェクトから多数の推奨事項を受け取る必要があります(より複雑な場合、そのようなオブジェクトが多数存在する可能性があります)。



ポートフォリオクラスには、特定のトレーディングシグナルセットに資本を適用する方法、取引コストの計上方法、および使用する交換注文の種類を伝える必要があります。 Strategyオブジェクトはデータバーで機能するため、注文の実行時に存在する価格に基づいて仮定する必要があります。 現在のバーの最高価格と最低価格は先験的に不明であるため、(前のバーの)始値と終値のみを使用できます。 ただし、実際には、成行注文(マーケット)を使用する場合、特定の価格での注文の実行を保証することは不可能であるため、ここでの価格は仮定に過ぎません。



この場合、バックテスターは、流動性の制限なしで金融商品のロングおよびショートのポジションを開くことができると仮定して、ブローカー側の保証範囲と制限の概念に関連するすべてを無視します。 もちろん、これは非現実的な仮定であるため、プロジェクトの開発中に削除する必要があります。



コードの学習を続けましょう。



 # backtest.py class Portfolio(object): """      (   ),        Strategy.""" __metaclass__ = ABCMeta @abstractmethod def generate_positions(self): """    ,            . """ raise NotImplementedError("Should implement generate_positions()!") @abstractmethod def backtest_portfolio(self): """              (   ) —     , /    .. Produces a portfolio object that can be examined by other classes/functions.""" raise NotImplementedError("Should implement backtest_portfolio()!")
      
      





これらは、抽象基本クラスStrategyおよびPortfolioの基本的な説明です。 次に、システムがテスト戦略をより効率的に処理できるように、これらのクラスの専用実装を作成します。



RandomForecastStrategy



と呼ばれるStrategyのサブクラスを作成することから始めましょう-その唯一のタスクは、株式を売買するためのランダム信号を生成することです。 一見、これは意味がありませんが、このような単純な戦略は、オブジェクト指向のバックテストフレームワークの動作を示しています。



ランダムな推奨事項を含むモジュールコードで新しいファイルrandom_forecast.py



を作成します。



 # 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): """  Strategy         long  short.       """ 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): """  pandas DataFrame,    .""" signals = pd.DataFrame(index=self.bars.index) signals['signal'] = np.sign(np.random.randn(len(signals))) #         NaN-: signals['signal'][0:5] = 0.0 return signals
      
      





推奨事項を作成するためのテストシステムを受け取ったら、Portfolioオブジェクトの実装を作成する必要があります。 このオブジェクトには、バックテストコードのほとんどが含まれます。 2つの独立したデータフレームを作成します。最初のデータフレームにはポジションが含まれ、バーの間に売買された楽器の数を保存するために使用されます。 次に-ポートフォリオには、各バーのすべてのポジションの市場価格と利用可能な資金の量が含まれます。 これにより、資本曲線を作成して、戦略のパフォーマンスを評価できます。



ポートフォリオオブジェクトを実装するには、トランザクションコスト、成行注文などの処理方法を決定する必要があります。 この例では、保証範囲の制限なしでロングおよびショートのポジションをオープンでき、バーの始値で明確に売買し、取引コストはゼロ(価格スリッページ、ブローカー、為替コミッションなどは破棄されます)、およびトランザクションごとに購入または販売が直接示されます。



次は、 random_forecast.py



ファイルの続きです。



 # random_forecast.py class MarketOnOpenPortfolio(Portfolio): """ Portfolio   ,   100   ,       .  ,    ,          . : symbol - ,   . bars -     . signals -  pandas  (1, 0, -1)   . initial_capital -     .""" 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'           100 ,    {1, 0, -1}    .""" positions = pd.DataFrame(index=signals.index).fillna(0.0) positions[self.symbol] = 100*signals['signal'] return positions def backtest_portfolio(self): """       —           ( ).          —       .   portfolio,     .""" #   'pos_diff'   portfolio     ,      ,      portfolio = self.positions*self.bars['Open'] pos_diff = self.positions.diff() #             'holdings'  'cash' portfolio['holdings'] = (self.positions*self.bars['Open']).sum(axis=1) portfolio['cash'] = self.initial_capital - (pos_diff*self.bars['Open']).sum(axis=1).cumsum() #       ('cash')          portfolio['total'] = portfolio['cash'] + portfolio['holdings'] portfolio['returns'] = portfolio['total'].pct_change() return portfolio
      
      





ここで、 _main_



関数を使用してすべてを接続する必要があります。



 if __name__ == "__main__": #    SPY (ETF,      S&P500)  Quandl (     'pip install Quandl' symbol = 'SPY' bars = Quandl.get("GOOG/NYSE_%s" % symbol, collapse="daily") #          SPY rfs = RandomForecastingStrategy(symbol, bars) signals = rfs.generate_signals() #   SPY portfolio = MarketOnOpenPortfolio(symbol, bars, signals, initial_capital=100000.0) returns = portfolio.backtest_portfolio() print returns.tail(10)
      
      





プログラムの出力を以下に示します(それぞれの場合、異なる選択された日付範囲とランダム化の使用により異なります):







この場合、戦略が損失をもたらすことは明らかであり、その特徴を考慮すると、驚くことではありません。 このテストケースをより効率的なバックテストにするには、Portfolioからの入力を受け入れるPerformanceオブジェクトを作成し、戦略のフィルタリングに関する決定に基づいてパフォーマンスメトリックのセットを発行する必要もあります。



さらに、Portfolioオブジェクトを改善して、トランザクションコストに関する情報(スリッページやブローカーのコミッションなど)をより現実的に考慮に入れることができます。 「予測エンジン」をStrategyオブジェクトに直接含めることもできます。これにより、より良い結果を得ることができます。



今日は以上です! ご清聴ありがとうございました。 ブログを購読することを忘れないでください。



取引ロボットの作成に関するその他の記事:






All Articles