| Overall Statistics |
|
Total Trades 74 Average Win 0.15% Average Loss -0.14% Compounding Annual Return 21.289% Drawdown 0.600% Expectancy 0.226 Net Profit 1.208% Sharpe Ratio 4.448 Loss Rate 39% Win Rate 61% Profit-Loss Ratio 1.01 Alpha -0.048 Beta 13.171 Annual Standard Deviation 0.034 Annual Variance 0.001 Information Ratio 4.01 Tracking Error 0.034 Treynor Ratio 0.012 Total Fees $194.25 |
"""
This file contains QuantConnect order codes for easy conversion and more
intuitive custom order handling
References:
https://github.com/QuantConnect/Lean/blob/master/Common/Orders/OrderTypes.cs
https://github.com/QuantConnect/Lean/blob/master/Common/Orders/OrderRequestStatus.cs
"""
OrderTypeKeys = [
'Market', 'Limit', 'StopMarket', 'StopLimit', 'MarketOnOpen',
'MarketOnClose', 'OptionExercise',
]
OrderTypeCodes = dict(zip(range(len(OrderTypeKeys)), OrderTypeKeys))
OrderDirectionKeys = ['Buy', 'Sell', 'Hold']
OrderDirectionCodes = dict(zip(range(len(OrderDirectionKeys)), OrderDirectionKeys))
## NOTE ORDERSTATUS IS NOT IN SIMPLE NUMERICAL ORDER
OrderStatusCodes = {
0:'New', # new order pre-submission to the order processor
1:'Submitted', # order submitted to the market
2:'PartiallyFilled', # partially filled, in market order
3:'Filled', # completed, filled, in market order
5:'Canceled', # order cancelled before filled
6:'None', # no order state yet
7:'Invalid', # order invalidated before it hit the market (e.g. insufficient capital)
8:'CancelPending', # order waiting for confirmation of cancellation
}import pandas as pd
import numpy as np
import decimal as d
from datetime import datetime, timedelta, time
from order_codes import (OrderTypeCodes, OrderDirectionCodes, OrderStatusCodes)
#--------------------------#
# Globals |
#--------------------------#
PT = 0.005 # percent
SL = 0.005 # percent
EXPIRY_MIN_DAYS = 30 # days
EXPIRY_LIQUIDATE_DAYS = 10 # days
#--------------------------#
# ALGORITHM |
#--------------------------#
class BasicTemplateAlgorithm(QCAlgorithm):
'''Basic template algorithm simply initializes the date range and cash'''
def Initialize(self):
self.SetStartDate(2016,7, 7) #Set Start Date
self.SetEndDate(2016,7,30) #Set End Date
self.SetCash(500000) #Set Strategy Cash
# brokerage model
self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage,
AccountType.Margin)
# Find more symbols here: http://quantconnect.com/data
futureES = self.AddFuture(Futures.Indices.SP500EMini, Resolution.Minute)
futureES.SetFilter(TimeSpan.Zero, TimeSpan.FromDays(185))
futureNQ = self.AddFuture(Futures.Indices.NASDAQ100EMini, Resolution.Minute)
futureNQ.SetFilter(TimeSpan.Zero, TimeSpan.FromDays(185))
self.order_tickets = dict()
def OnData(self, slice):
# only run during market hours
#------------------------------------------------------#
start_time = time(hour=9, minute=35)
end_time = time(hour=16, minute=0)
if not start_time < self.Time.time() < end_time: return
# check if holdings need to be sold based on expiry
#------------------------------------------------------#
self._check_holdings_expiry()
# check if bracket order has been sent
# sometimes OnOrderEvent doesn't send the bracket order
#------------------------------------------------------#
self._confirm_bracket_order()
base_symbol_invested = [self._get_base_symbol(x.Symbol) \
for x in self.Portfolio.Values if x.Invested]
for chain in slice.FutureChains:
# check if algo is already in invested in symbol
#------------------------------------------------------#
chain_base_sym = chain.Value.Symbol.Value
if chain_base_sym in base_symbol_invested: continue
# find the front contract expiring no earlier than in N days
#------------------------------------------------------#
contracts = [i for i in chain.Value \
if i.Expiry > self.Time+timedelta(EXPIRY_MIN_DAYS)]
# if there is more than one contract,
# trade the one with the closest expiry date
#------------------------------------------------------#
if len(contracts) > 0:
front = sorted(contracts, key=lambda x: x.Expiry, reverse=True)[0]
sym = front.Symbol
self.Debug('front contract: {}'.format(sym))
# compute order quantity based on portfolio percentage
#------------------------------------------------------#
qty = self._calc_order_quantity(sym, pct=0.01)
if qty is None:
self.Debug('price is zero for: {}'.format(sym.Value))
return
# if quantity is good we place a market order and
# add the order data to the symbol data dict
#------------------------------------------------------#
self.Debug('front contract qty: {}'.format(qty))
newTicket = self.MarketOrder(sym, qty)
self.order_tickets[sym.Value] = \
symbolOrderData(sym.Value, newTicket, 'Market', contract=front)
def OnOrderEvent(self, orderEvent):
"""
This function is triggered automatically every time an order event occurs.
"""
self.Log(str(orderEvent))
if OrderStatusCodes[orderEvent.Status] == 'Submitted':
return
k = str(orderEvent.Symbol.Value)
symbol = str(orderEvent.Symbol)
if (not k in self.order_tickets.keys()):
self.Log('missing key in order tickets: {}'.format(k))
self.Log('order tickets keys: {}'.format(self.order_tickets.keys()))
return
elif (k in self.order_tickets.keys()):
orderData = self.order_tickets[k]
orig_order_id = orderData.ticket.OrderId
order = self.Transactions.GetOrderTicket(orig_order_id)
# sometimes order is nonetype due to security price
# is equal to zero
#------------------------------------------------------#
if not order:
self.Log('order is nonetype: {}'.format(k))
del self.order_tickets[k] # delete order ticket data
return
# if order is filled but bracket order not submitted,
# submit bracket order
#------------------------------------------------------#
if (OrderStatusCodes[order.Status]=='Filled') and \
(not orderData.bracket_submit):
if (orderEvent.OrderId == orig_order_id):
price = orderEvent.FillPrice
qty = orderEvent.FillQuantity
#qty = self.CalculateOrderQuantity(k, 0.0)
profit_target = price * d.Decimal(1+PT)
limitTicket = self.LimitOrder(symbol, -1*qty, profit_target)
self.order_tickets[k].add_limit_order(limitTicket)
stop_loss = price * d.Decimal(1-SL)
stopTicket = self.StopMarketOrder(symbol, -1*qty, stop_loss)
self.order_tickets[k].add_stop_market_order(stopTicket)
self.order_tickets[k].is_bracket()
self.Log('bracket order submitted: {}'.format(self.Time, k))
# Otherwise, one of the exit orders was filled,
# so cancel the open orders
#------------------------------------------------------#
elif (orderData.bracket_submit) and \
(OrderStatusCodes[orderEvent.Status]=='Filled'):
self.Log('cancelling bracket orders for: {}'.format(self.Time, k))
self.Transactions.CancelOpenOrders(symbol)
self.order_tickets[k].bracket_submit=False
del self.order_tickets[k]
#--------------------------#
# ALGORITHM HELPER FUNCS |
#--------------------------#
def _calc_order_quantity(self, sym, pct):
"""
Compute order quantity based on percentage of portfolio value.
This is required because SetHoldings doesn't return an order ticket.
This function returns None when security price is less than 1.
"""
price = float(self.Securities[sym].Price)
self.Log('{} calc order quantity price: {}'.format(sym, price))
if price < 1: return None
qty = (pct * float(self.Portfolio.TotalPortfolioValue)) / price
return int(qty)
def _get_base_symbol(self, symbol):
"""
Get minimum symbol for comparisons.
Example: 'ES XXXXXXXX' will return 'ES'
"""
sym = str(symbol).split(' ')[0]
return sym
def _check_holdings_expiry(self):
"""
Check if current holdings are expiring within certain number
of days. If so liquidate the contract, cancel open orders and delete
order data from dict.
"""
invested = [x.Symbol for x in self.Portfolio.Values if x.Invested]
#self.Log('[{}] invested:\n{}'.format(self.Time, invested))
for sym in invested:
k = sym.Value
if (k in self.order_tickets.keys()):
expiry = self.order_tickets[k].contract.Expiry
if (expiry < self.Time + timedelta(EXPIRY_LIQUIDATE_DAYS)):
self.Debug('expiry too close liquidating: {}'.format(k))
self.Liquidate(sym)
self.Transactions.CancelOpenOrders(sym)
del self.order_tickets[k]
return
def _confirm_bracket_order(self):
"""
confirm bracket orders have been submitted.
sometimes OnOrderEvent is not sending orders
"""
invested = [x.Symbol for x in self.Portfolio.Values if x.Invested]
#self.Log('[{}] invested:\n{}'.format(self.Time, invested))
for sym in invested:
k = sym.Value
if (k in self.order_tickets.keys()):
orderData = self.order_tickets[k]
orig_order_id = orderData.ticket.OrderId
order = self.Transactions.GetOrderTicket(orig_order_id)
if (not orderData.bracket_submit):
price = order.AverageFillPrice
qty = order.QuantityFilled
profit_target = price * d.Decimal(1+PT)
limitTicket = self.LimitOrder(sym, -1*qty, profit_target)
self.order_tickets[k].add_limit_order(limitTicket)
stop_loss = price * d.Decimal(1-SL)
stopTicket = self.StopMarketOrder(sym, -1*qty, stop_loss)
self.order_tickets[k].add_stop_market_order(stopTicket)
self.order_tickets[k].is_bracket()
self.Log('check bracket >> bracket order submitted: {}'.format(k))
return
class symbolOrderData:
"""
Class object to hold symbol bracket order data
Attributes:
-----------
symbol: str
ticket: ticket object
does not work with setHoldings
order_type: str
must match QC order types in order_codes.py
bracket_submit: bool
toggled when bracket order is submitted
limit_order: None or ticket object
stop_market_order: None or ticket object
contract: None or futures contract object
Methods:
--------
add_limit_order: set limit_order attr to limitTicket
add_stop_market_order: set stop_market_order attr to stopMarketTicket
is_bracket: if limit and stopMarket orders are submitted toggle
bracket_submit attr
"""
def __init__(self, symbol, ticket, order_type, contract=None):
self.symbol = symbol
self.ticket = ticket
self.order_type = order_type
if contract:
self.contract = contract
self.bracket_submit = False
self.limit_order = None
self.stop_market_order = None
def add_limit_order(self, limitTicket):
self.limit_order = limitTicket
return
def add_stop_market_order(self, stopMarketTicket):
self.stop_market_order = stopMarketTicket
return
def is_bracket(self):
if (self.limit_order is not None) and (self.stop_market_order is not None):
self.bracket_submit = True
return