Event-Driven Backtesting with Python - Part VI

Author: , Created: 2019-03-26 09:13:08, Updated:

This article continues the discussion of event-driven backtesters in Python. In the previous article we considered a portfolio class hierarchy that handled current positions, generated trading orders and kept track of profit and loss (PnL).

In this article we will study the execution of these orders, by creating a class hierarchy that will represent a simulated order handling mechanism and ultimately tie into a brokerage or other means of market connectivity.

The ExecutionHandler described here is exceedingly simple, since it fills all orders at the current market price. This is highly unrealistic, but serves as a good baseline for improvement.

As with the previous abstract base class hierarchies, we must import the necessary properties and decorators from the abc library. In addition we need to import the FillEvent and OrderEvent:

# execution.py

import datetime
import Queue

from abc import ABCMeta, abstractmethod

from event import FillEvent, OrderEvent The ExecutionHandler is similar to previous abstract base classes and simply has one pure virtual method, execute_order:

# execution.py

class ExecutionHandler(object):
    """
    The ExecutionHandler abstract class handles the interaction
    between a set of order objects generated by a Portfolio and
    the ultimate set of Fill objects that actually occur in the
    market. 

    The handlers can be used to subclass simulated brokerages
    or live brokerages, with identical interfaces. This allows
    strategies to be backtested in a very similar manner to the
    live trading engine.
    """

    __metaclass__ = ABCMeta

    @abstractmethod
    def execute_order(self, event):
        """
        Takes an Order event and executes it, producing
        a Fill event that gets placed onto the Events queue.

        Parameters:
        event - Contains an Event object with order information.
        """
        raise NotImplementedError("Should implement execute_order()")

In order to backtest strategies we need to simulate how a trade will be transacted. The simplest possible implementation is assumes all orders are filled at the current market price for all quantities. This is clearly extremely unrealistic and a big part of improving backtest realism will come from designing more sophisticated models of slippage and market impact.

Note that the FillEvent is given a value of None for the fill_cost (see the penultimate line in execute_order) as the we have already taken care of the cost of fill in the NaivePortfolio object described in the previous article. In a more realistic implementation we would make use of the “current” market data value to obtain a realistic fill cost.

I have simply utilised ARCA as the exchange although for backtesting purposes this is purely a placeholder. In a live execution environment this venue dependence would be far more important:

# execution.py

class SimulatedExecutionHandler(ExecutionHandler):
    """
    The simulated execution handler simply converts all order
    objects into their equivalent fill objects automatically
    without latency, slippage or fill-ratio issues.

    This allows a straightforward "first go" test of any strategy,
    before implementation with a more sophisticated execution
    handler.
    """
    
    def __init__(self, events):
        """
        Initialises the handler, setting the event queues
        up internally.

        Parameters:
        events - The Queue of Event objects.
        """
        self.events = events

    def execute_order(self, event):
        """
        Simply converts Order objects into Fill objects naively,
        i.e. without any latency, slippage or fill ratio problems.

        Parameters:
        event - Contains an Event object with order information.
        """
        if event.type == 'ORDER':
            fill_event = FillEvent(datetime.datetime.utcnow(), event.symbol,
                                   'ARCA', event.quantity, event.direction, None)
            self.events.put(fill_event)

This concludes the class hierarchies necessary to produce an event-driven backtester. In the next article we will discuss how to calculate a set of performance metrics for the backtested strategy.


More