Dans l'article précédent, nous avons décrit le concept d'un backtester axé sur les événements. Le reste de cette série d'articles se concentrera sur chacune des hiérarchies de classes distinctes qui composent le système global.
Comme indiqué dans l'article précédent, le système de trading utilise deux boucles while - une externe et une interne.
La classe mère s'appelle Event. C'est une classe de base et ne fournit aucune fonctionnalité ou interface spécifique. Dans les implémentations ultérieures, les objets Event développeront probablement une plus grande complexité et nous protégeons ainsi la conception de tels systèmes en créant une hiérarchie de classes.
# event.py
class Event(object):
"""
Event is base class providing an interface for all subsequent
(inherited) events, that will trigger further events in the
trading infrastructure.
"""
pass
L'événement MarketEvent hérite de l'événement et fournit peu plus qu'une auto-identification qu'il s'agit d'un événement de type
# event.py
class MarketEvent(Event):
"""
Handles the event of receiving a new market update with
corresponding bars.
"""
def __init__(self):
"""
Initialises the MarketEvent.
"""
self.type = 'MARKET'
Un événement SignalEvent nécessite un symbole de ticker, un horodatage pour sa génération et une direction pour informer un objet Portfolio.
# event.py
class SignalEvent(Event):
"""
Handles the event of sending a Signal from a Strategy object.
This is received by a Portfolio object and acted upon.
"""
def __init__(self, symbol, datetime, signal_type):
"""
Initialises the SignalEvent.
Parameters:
symbol - The ticker symbol, e.g. 'GOOG'.
datetime - The timestamp at which the signal was generated.
signal_type - 'LONG' or 'SHORT'.
"""
self.type = 'SIGNAL'
self.symbol = symbol
self.datetime = datetime
self.signal_type = signal_type
L'OrdreEvent est légèrement plus complexe qu'un SignalEvent car il contient un champ de quantité en plus des propriétés susmentionnées de SignalEvent. La quantité est déterminée par les contraintes de portefeuille.
# event.py
class OrderEvent(Event):
"""
Handles the event of sending an Order to an execution system.
The order contains a symbol (e.g. GOOG), a type (market or limit),
quantity and a direction.
"""
def __init__(self, symbol, order_type, quantity, direction):
"""
Initialises the order type, setting whether it is
a Market order ('MKT') or Limit order ('LMT'), has
a quantity (integral) and its direction ('BUY' or
'SELL').
Parameters:
symbol - The instrument to trade.
order_type - 'MKT' or 'LMT' for Market or Limit.
quantity - Non-negative integer for quantity.
direction - 'BUY' or 'SELL' for long or short.
"""
self.type = 'ORDER'
self.symbol = symbol
self.order_type = order_type
self.quantity = quantity
self.direction = direction
def print_order(self):
"""
Outputs the values within the Order.
"""
print "Order: Symbol=%s, Type=%s, Quantity=%s, Direction=%s" % \
(self.symbol, self.order_type, self.quantity, self.direction)
L'événement FillEvent est l'événement le plus complexe. Il contient un horodatage du moment où un ordre a été exécuté, le symbole de l'ordre et l'échange sur lequel il a été exécuté, la quantité d'actions négociées, le prix réel de l'achat et la commission engagée.
La commission est calculée en utilisant les commissions des Interactive Brokers. Pour les commandes US API, cette commission est de 1,30 USD minimum par commande, avec un taux forfaitaire de 0,013 USD ou 0,08 USD par action selon que la taille de la transaction est inférieure ou supérieure à 500 unités d'actions.
# event.py
class FillEvent(Event):
"""
Encapsulates the notion of a Filled Order, as returned
from a brokerage. Stores the quantity of an instrument
actually filled and at what price. In addition, stores
the commission of the trade from the brokerage.
"""
def __init__(self, timeindex, symbol, exchange, quantity,
direction, fill_cost, commission=None):
"""
Initialises the FillEvent object. Sets the symbol, exchange,
quantity, direction, cost of fill and an optional
commission.
If commission is not provided, the Fill object will
calculate it based on the trade size and Interactive
Brokers fees.
Parameters:
timeindex - The bar-resolution when the order was filled.
symbol - The instrument which was filled.
exchange - The exchange where the order was filled.
quantity - The filled quantity.
direction - The direction of fill ('BUY' or 'SELL')
fill_cost - The holdings value in dollars.
commission - An optional commission sent from IB.
"""
self.type = 'FILL'
self.timeindex = timeindex
self.symbol = symbol
self.exchange = exchange
self.quantity = quantity
self.direction = direction
self.fill_cost = fill_cost
# Calculate commission
if commission is None:
self.commission = self.calculate_ib_commission()
else:
self.commission = commission
def calculate_ib_commission(self):
"""
Calculates the fees of trading based on an Interactive
Brokers fee structure for API, in USD.
This does not include exchange or ECN fees.
Based on "US API Directed Orders":
https://www.interactivebrokers.com/en/index.php?f=commission&p=stocks2
"""
full_cost = 1.3
if self.quantity <= 500:
full_cost = max(1.3, 0.013 * self.quantity)
else: # Greater than 500
full_cost = max(1.3, 0.008 * self.quantity)
full_cost = min(full_cost, 0.5 / 100.0 * self.quantity * self.fill_cost)
return full_cost
Dans le prochain article de la série, nous allons examiner comment développer une hiérarchie de classes DataHandler de marché qui permet à la fois le backtesting historique et le trading en direct, via la même interface de classe.