
Strategy object, which generates trading signals ( Signals ), and the Portfolio object, which generates orders ( Orders ) based on them, must use the same interface to access market data in the context of historical testing and real-time work.DataHandler object, which provides an interface for subclasses to transfer market data to the rest of the system. In this configuration, the handler of any subclass can simply be “thrown away”, and this does not affect the work of the components responsible for the strategy and portfolio processing.HistoricCSVDataHandler , QuandlDataHandler , SecuritiesMasterDataHandler , InteractiveBrokersMarketFeedDataHandler and so on. Here we consider only the creation of a CSV handler with historical data that will load the corresponding CSV file of intraday financial data in bar format (price values ​​Low, High, Close, as well as Volume trading volume and OpenInterest open interest). Based on these data, with each “heartbeat” of the system (heartbeat), it is possible to carry out an in-depth analysis using the components of Strategy and Portfolio , which will avoid various distortions.DataHandler generates DataHandler events, you also need to import event.py: # data.py import datetime import os, os.path import pandas as pd from abc import ABCMeta, abstractmethod from event import MarketEvent DataHandler is an abstract base class (ABK), which means that it is impossible to create an instance directly. This can only be done with subclasses. The rationale for this is that ABK provides an interface for subordinate DataHandler, which they must use, which allows for compatibility with other classes that can interact with._metaclass_ property. Also using the decorator @abstractmethod indicates that the method will be redefined in subclasses (exactly the same way as a fully virtual method in C ++).get_latest_bars and update_bars . The first one returns the last N bars from the current “heart beat” system timestamp, which is useful for performing calculations for Strategy classes. The latter method provides an analysis mechanism for overlaying bar information on a new data structure, which completely eliminates predictive distortions. If an attempt is made to create a class instance, an exception will be thrown: # data.py class DataHandler(object): """ DataHandler —   ,       (       )  ()  DataHandler       (OLHCVI)     .       ,    ,     .             . """ __metaclass__ = ABCMeta @abstractmethod def get_latest_bars(self, symbol, N=1): """   N    latest_symbol  ,     . """ raise NotImplementedError("Should implement get_latest_bars()") @abstractmethod def update_bars(self): """            . """ raise NotImplementedError("Should implement update_bars()") DataHandler class DataHandler next step is to create a handler for historical CSV files. HistoricCSVDataHandler will take many CSV files (one for each financial instrument) and convert them to the DataFrames for pandas.Event Queue , in which to publish market information MarketEvent , the absolute path to the CSV-files and the list of tools. Here is the initialization of the class: # data.py class HistoricCSVDataHandler(DataHandler): """ HistoricCSVDataHandler    CSV-        «» ,    . """ def __init__(self, events, csv_dir, symbol_list): """       CSV-   . ,      'symbol.csv',  symbol —   . : events -  . csv_dir -      CSV-. symbol_list -   . """ self.events = events self.csv_dir = csv_dir self.symbol_list = symbol_list self.symbol_data = {} self.latest_symbol_data = {} self.continue_backtest = True self._open_convert_csv_files() union and reindex methods are used: # data.py def _open_convert_csv_files(self): """  CSV-  ,    pandas DataFrames   .    ,      DTN IQFeed,      . """ comb_index = None for s in self.symbol_list: #  CSV-   ,    self.symbol_data[s] = pd.io.parsers.read_csv( os.path.join(self.csv_dir, '%s.csv' % s), header=0, index_col=0, names=['datetime','open','low','high','close','volume','oi'] ) #    «»  if comb_index is None: comb_index = self.symbol_data[s].index else: comb_index.union(self.symbol_data[s].index) # Set the latest symbol_data to None self.latest_symbol_data[s] = [] # Reindex the dataframes for s in self.symbol_list: self.symbol_data[s] = self.symbol_data[s].reindex(index=comb_index, method='pad').iterrows() _get_new_bar method creates a generator for creating a formatted version of the data in bars. This means that subsequent method calls result in a new bar (and so on until the end of the data line of the instruments is reached): # data.py def _get_new_bar(self, symbol): """     -  : (sybmbol, datetime, open, low, high, close, volume). """ for b in self.symbol_data[symbol]: yield tuple([symbol, datetime.datetime.strptime(b[0], '%Y-%m-%d %H:%M:%S'), b[1][0], b[1][1], b[1][2], b[1][3], b[1][4]]) DataHndler that you need to implement is get_latest_bars . It simply displays the list of the last N bars from the latest_symbol_data structure. Setting N = 1 allows you to get the current bar: # data.py def get_latest_bars(self, symbol, N=1): """  N     latest_symbol,  Nk,   . """ try: bars_list = self.latest_symbol_data[symbol] except KeyError: print "That symbol is not available in the historical data set." else: return bars_list[-N:] update_bars ; this is the second abstract method from DataHandler . It generates events ( MarketEvent ) that go into the queue, as the last bars are added to the latest_symbol_data : # data.py def update_bars(self): """            . """ for s in self.symbol_list: try: bar = self._get_new_bar(s).next() except StopIteration: self.continue_backtest = False else: if bar is not None: self.latest_symbol_data[s].append(bar) self.events.put(MarketEvent()) DataHandler - a dedicated object that is used by other components of the system to track market data. Stragety , Portfolio and ExecutionHandler objects require current market information, so it makes sense to work with it centrally to avoid possible duplication of storage.Strategy object encapsulates all calculations related to the processing of market data to create advisory signals to the Portfolio object. At this stage of developing an event-oriented back tester, there are no concepts for indicators or filters that are used in technical analysis. To implement them, you can create a separate data structure, but this is beyond the scope of this article.SignalEvents objects. To create a strategy hierarchy, you must import NumPy, pandas, a Queue object, the abstract base tools tool and SignalEvent: # strategy.py import datetime import numpy as np import pandas as pd import Queue from abc import ABCMeta, abstractmethod from event import SignalEvent Strategy defines a virtual method calculate_signals . It is used to handle the creation of SignalEvent objects based on updated market data: # strategy.py class Strategy(object): """ Strategy —   ,     ()    .    Strategy             (OLHCVI),   DataHandler.          ,        —  Strategy      ,     . """ __metaclass__ = ABCMeta @abstractmethod def calculate_signals(self): """      . """ raise NotImplementedError("Should implement calculate_signals()") Strategy pretty simple. The first example of using subclasses in the Strategy object is to use buy and hold strategies and create the corresponding class BuyAndHoldStrategy . He will buy a specific stock on a particular day and hold the position. Thus, only one signal is generated per share.__init__ ) requires the market data handler bars and the event queue object events : # strategy.py class BuyAndHoldStrategy(Strategy): """   ,              .       Strategy      . """ def __init__(self, bars, events): """   buy and hold. : bars -  DataHandler,      events -   . """ self.bars = bars self.symbol_list = self.bars.symbol_list self.events = events #        ,   True self.bought = self._calculate_initial_bought() BuyAndHoldStrategy strategy is BuyAndHoldStrategy , the bought dictionary contains a set of keys for each tool, which are set to False. When a certain instrument is bought (a long position is opened), the key is transferred to the True position. This allows the Strategy object to understand whether a position is open: # strategy.py def _calculate_initial_bought(self): """     bought    False. """ bought = {} for s in self.symbol_list: bought[s] = False return bought calculate_signals implemented in this particular class. The method passes through all the instruments in the list and gets the last bar from the bars handler. It then checks to see if the instrument has been “bought” (whether we are in the market for it or not), and then the SignalEvent signal object is SignalEvent . Then it is placed in the event queue, and the bought dictionary is updated with the appropriate information (True for the instrument purchased): # strategy.py def calculate_signals(self, event): """  "Buy and Hold"     .  ,          . : event -  MarketEvent. """ if event.type == 'MARKET': for s in self.symbol_list: bars = self.bars.get_latest_bars(s, N=1) if bars is not None and bars != []: if self.bought[s] == False: # (Symbol, Datetime, Type = LONG, SHORT or EXIT) signal = SignalEvent(bars[0][0], bars[0][1], 'LONG') self.events.put(signal) self.bought[s] = True Portfolio hierarchy that will track profit and loss by position (profit and loss, PnL).Source: https://habr.com/ru/post/264141/
All Articles