| Overall Statistics |
|
Total Trades 20 Average Win 0.02% Average Loss -0.03% Compounding Annual Return 7.590% Drawdown 0.100% Expectancy 0.330 Net Profit 0.118% Sharpe Ratio 7.179 Probabilistic Sharpe Ratio 90.324% Loss Rate 23% Win Rate 77% Profit-Loss Ratio 0.73 Alpha 0.046 Beta -0.007 Annual Standard Deviation 0.007 Annual Variance 0 Information Ratio 2.456 Tracking Error 0.291 Treynor Ratio -7.619 Total Fees $51.80 |
"""
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
9:'UpdateSubmitted', # order update is submitted, waiting for confirmation
}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/Inputs |
#--------------------------#
#individual trade parameters
RO = 7 # Risk Out
RT = 15 # Profit Target
SL = 20 # Disaster Stop
direction = 1 #long = 1, short = -1
#entry parameters
#-------------------------#
# Note: script works well for bars/setups that are contained w/in a 100-handle block.
# It isn't perfect for bars/setups that transition between 100 handle blocks.
#------------------------#
broken_marker = 12 # spot on 100 handle block that breaks (based on bar close). For transitions between 100-handle blocks, the low will always be <100
target = 26 # profit target, code will exit trade if we hit this w/o filling entry order. also used as limit for high/low of opening bar
pullback = -4 # pullback amount for entry, negative numbers = no pullback vs marker.... entryPrice = self.floor100(tradeBar.Low) + broken_marker - pullback*direction
extreme_for_opening_bar = 2 # extreme value (high/low) on the entry bar for method #2
method = 1 #criteria for placing an order:
#(method #1) close and open on opposite sides of broken_marker, extreme of bar does not hit target
#(method #2) for a long trade: close above broken_marker, high < target, low > extreme_for_opening_bar (reverse for short trade)
# early exit parameters
distance_to_risk_marker = 25 # distance to risk marker to lean against. Always a positive number, trade direction will ensure orders are adjusted in the correct manner. To disable early exits, make this value > disaster stop
distance_above_risk_marker_for_exit = 4 #how far above (for longs) the risk marker to move limit orders when an early exit is triggered
#go again parameters
MAX_GO_AGAINS = 3 # maximum number of times the script will go again if a trade gets risk out-->stop out
EXPIRY_MIN_DAYS = 100 # days
EXPIRY_LIQUIDATE_DAYS = 5 # days (5 days matches TOS continuous contract)
class MultidimensionalVentralThrustAssembly(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2020, 4, 18)
self.SetEndDate(2020, 4, 22)
self.SetCash(1000000)
self.SetTimeZone("America/Los_Angeles")
futureNQ = self.AddFuture(Futures.Indices.NASDAQ100EMini, Resolution.Second)
futureNQ.SetFilter(timedelta(5), timedelta(90))
futureNQ.FeeModel = ConstantFeeModel(0)
self.spy = self.AddEquity("SPY", Resolution=Resolution.Second)
self.SetBenchmark("SPY")
self.consolidators = dict()
self.order_tickets = dict()
# daily cleanup of open spots, open positions.
self.Schedule.On(self.DateRules.EveryDay("SPY"), self.TimeRules.BeforeMarketClose("SPY", 14), self.onEndOfDay)
# globals for code functionality. Should really try to find a better solution...
self.openSpot = 100000000
self.goAgainCount = 0
self.goAgain = False
self.earlyExit = False
# globals for logging stats
self.total = 0 #total trades
self.RO = 0 # risk out
self.ROGA = {0:0, 1:0, 2:0, 3:0} # risk out on go again
self.DS = 0 # hit disaster stop count
self.DSGA = {0:0, 1:0, 2:0, 3:0} #hit disaster stop on go again count
self.PT = 0 # hit runner target count
self.PTGA = {0:0, 1:0, 2:0, 3:0} # hit runner target on go again count
self.GA = {0:0, 1:0, 2:0, 3:0} # go again count
self.EE = 0 #early exit count (risk marker broke)
def OnData(self,slice):
# only run during market hours,
# don't trade first 5min and don't trade last 30min
#------------------------------------------------------#
start_time = time(hour=6, minute=35)
end_time = time(hour=12, minute=30)
if not start_time < self.Time.time() < end_time:
self.Liquidate()
return
# 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:
# 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_LIQUIDATE_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
# check if algo is already invested in symbol
# if so, we don't need to do anything with the new data
#------------------------------------------------------#
chain_base_sym = chain.Value.Symbol.Value
if chain_base_sym in base_symbol_invested: continue
# code for handling entry order that hasn't been filled, or
# going again if we got risk out/stop out
# ----------------------------------------------------- #
# code to exit a limit order if our open spot is closed before we get filled
if front.Symbol.Value in self.order_tickets:
openingOrder = self.order_tickets[front.Symbol.Value].ticket
if (front.LastPrice - self.openSpot) * direction >= 0:
openingOrder.Cancel()
self.openSpot = 100000000 #just a big number that will never get hit, it will get reset w/ a new order
# code to go again after a risk-out stop-out
elif self.goAgain:
qty = 2*direction
entryPrice = self.openSpot - target + broken_marker - pullback*direction
if ((entryPrice - 0.25*direction) - front.LastPrice)*direction < 0:
newTicket = self.StopMarketOrder(sym, qty, entryPrice, 'go again (' + str(self.goAgainCount) + ')')
else:
newTicket = self.LimitOrder(sym, qty, entryPrice,'go again (' + str(self.goAgainCount) + ')')
self.order_tickets[sym.Value] = symbolOrderData(sym.Value, newTicket, 'Limit')
self.goAgain = False
self.goAgainCount = self.goAgainCount + 1
return
def On2MinDataConsolidated(self, sender, tradeBar):
# only run during market hours
# don't trade first 5min and don't trade last 30min
#----------------------------------------------------#
start_time = time(hour=6, minute=35)
end_time = time(hour=12, minute=30)
if not start_time < self.Time.time() < end_time: return
sym = tradeBar.Symbol
# code for early exit if a risk marker breaks #
# also prevents more than 1 trade at a time #
# ------------------------------------------- #
if self.Portfolio[sym].Invested:
orderData = self.order_tickets[sym.Value]
entry = orderData.ticket.AverageFillPrice
risk_marker = entry - (distance_to_risk_marker*direction)
if (tradeBar.Close - risk_marker)*direction < 0 and sym.Value in self.order_tickets:
self.earlyExit = True
updateOrderFields = UpdateOrderFields()
updateOrderFields.LimitPrice = risk_marker + distance_above_risk_marker_for_exit*direction
updateOrderFields.Tag = 'marker broke, early exit at (-' + str(distance_to_risk_marker - distance_above_risk_marker_for_exit) + ')'
orderData.limit_order.Update(updateOrderFields)
orderData.runner_order.Update(updateOrderFields)
return # don't run the code below, we're already in a trade
if sym.Value in self.order_tickets: return #already have an order ticket submitted, don't need the code below
# code for entering trades based on 2min bar properties #
# ----------------------------------------------------- #
normBar = self.normBarTo100Handle(tradeBar)
#####
extreme = normBar.High
other_extreme = normBar.Low
if direction == -1: #if we're going short, reverse the assignments
extreme = normBar.Low
other_extreme = normBar.High
if method==1:
if not ((normBar.Close - broken_marker)*direction > 0 and\
(target - extreme)*direction > 0 and\
(broken_marker - normBar.Open)*direction > 0): return
elif method ==2:
if not ((normBar.Close - broken_marker)*direction > 0 and\
(target - extreme)*direction > 0 and\
(other_extreme - extreme_for_opening_bar)*direction > 0): return
else:
self.Log('you did not pick a valid method for starting a trade...')
self.Debug('you did not pick a valid method for starting a trade...')
qty = 2*direction
self.openSpot = self.floor100(tradeBar.Low) + target
# add the order data to the symbol data dict
#------------------------------------------------------#
entryPrice = self.floor100(tradeBar.Low) + broken_marker - pullback*direction
if (entryPrice - tradeBar.Close) * direction < 0:
newTicket = self.LimitOrder(sym, qty, entryPrice, 'opening order')
else:
newTicket = self.StopMarketOrder(sym, qty, entryPrice, 'opening order')
self.order_tickets[sym.Value] = symbolOrderData(sym.Value, newTicket, 'Limit')
self.goAgain = False
self.goAgainCount = 0
return
def OnOrderEvent(self, orderEvent):
"""
This function is triggered automatically every time an order event occurs.
"""
if OrderStatusCodes[orderEvent.Status] in ['Submitted', 'CancelPending','UpdateSubmitted']: 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.Debug('order is nonetype: {}'.format(k))
del self.order_tickets[k] # delete order ticket data
return
# if original 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)
self._place_bracket_order(k,symbol,price,qty)
elif OrderStatusCodes[order.Status] == 'Canceled' and orderEvent.OrderId == orig_order_id:
self.Debug('canceled the original limit order')
self.goAgain = False
self.goAgainCount = 0
del self.order_tickets[k]
return
# Check if we have filled/canceled all our orders.
# If so, delete the order_tickets[k] object
#------------------------------------------------------#
elif OrderStatusCodes[orderData.limit_order.Status] in ['Filled', 'Canceled'] and \
OrderStatusCodes[orderData.runner_order.Status] in ['Filled', 'Canceled'] and \
OrderStatusCodes[orderData.stop_market_order.Status] in ['Filled', 'Canceled']:
del self.order_tickets[k]
#debug stuff
elif orderData.stop_market_order.Status not in OrderStatusCodes.keys():
self.Debug('Found and order status that is not in OrderStatusCodes: ' + str(orderData.stop_market_order))
# Otherwise, one of the exit orders was filled,
# so let's update the other order tickets
# and log some stats for use later...
#------------------------------------------------------#
# first, if stop loss is hit, cancel other orders
elif (orderData.bracket_submit) and \
(OrderStatusCodes[orderData.stop_market_order.Status]=='Filled'):
if orderData.take_profit_done and self.goAgainCount < MAX_GO_AGAINS:
self.goAgain = True
self.GA[self.goAgainCount] = self.GA[self.goAgainCount] + 1
# the code to re-enter trades is in the OnData section
#logging/stats
elif not orderData.take_profit_done and OrderStatusCodes[orderEvent.Status]=='Filled':
self.DS = self.DS + 1
if self.goAgainCount > 0:
self.DSGA[self.goAgainCount] = self.DSGA[self.goAgainCount] + 1
self.Transactions.CancelOpenOrders(symbol)
# second, if take profit is hit, change stop loss to breakeven and adjust quantity to 1
elif (orderData.bracket_submit) and \
(OrderStatusCodes[orderData.limit_order.Status]=='Filled') and \
not orderData.take_profit_done and not self.earlyExit:
updateOrderFields = UpdateOrderFields()
updateOrderFields.StopPrice = orderData.ticket.AverageFillPrice + 0.25*direction
updateOrderFields.Tag = 'stop out'
qty = orderData.stop_market_order.Quantity
if qty%2==0:
updateOrderFields.Quantity = orderData.stop_market_order.Quantity * 1/2
orderData.stop_market_order.Update(updateOrderFields)
orderData.take_profit_done = True
elif not orderData.take_profit_done:
#we had a partial fill, let's just exit everyting after take profit is hit
self.Debug('partial fill...cancelling bracket orders for: {}'.format(self.Time, k))
self.Liquidate(symbol)
#logging/stats
if not self.earlyExit:
self.RO = self.RO + 1
if self.goAgainCount > 0:
self.ROGA[self.goAgainCount] = self.ROGA[self.goAgainCount] + 1
else:
self.EE = self.EE + 1
# third, if runner target is hit, cancel orders and reset order_tickets
elif (orderData.bracket_submit) and \
(OrderStatusCodes[orderData.runner_order.Status]=='Filled'):
# self.Transactions.CancelOpenOrders(symbol)
orderData.stop_market_order.Cancel()
self.Liquidate(symbol)
#logging/stats
if not self.earlyExit:
self.PT = self.PT + 1
if self.goAgainCount > 0:
self.PTGA[self.goAgainCount] = self.PTGA[self.goAgainCount] + 1
else:
self.EE = self.EE + 1
def OnSecuritiesChanged(self, changes):
# this code rolls in and out of contracts, keeping 2min bar consolidator
for security in changes.AddedSecurities:
if hasattr(security, 'Expiry'):
if (security.Expiry - self.Time) < timedelta(days=EXPIRY_LIQUIDATE_DAYS): #roll to next contract 5 days early (copy behavior of ToS)
self.Debug('rolling out of...' + str(security.Expiry) + '...today is ' + str(self.Time))
continue
consolidator = TradeBarConsolidator(timedelta(minutes=2))
consolidator.DataConsolidated += self.On2MinDataConsolidated
self.SubscriptionManager.AddConsolidator(security.Symbol, consolidator)
self.consolidators[security.Symbol] = consolidator
for security in changes.RemovedSecurities:
self.Debug('now we are rolling out of' + str(security.Symbol) + '...' + str(security.Expiry) + '...today is ' + str(self.Time))
consolidator = self.consolidators.pop(security.Symbol)
self.SubscriptionManager.RemoveConsolidator(security.Symbol, consolidator)
consolidator.DataConsolidated -= self.On2MinDataConsolidated
def OnEndOfAlgorithm(self):
#Stats!!!
self.Log('RO = ' + str(RO) + '# Risk Out')
self.Log('RT = ' + str(RT) + ' # Profit Target')
self.Log('SL = ' + str(SL) + ' # Disaster Stop')
self.Log('direction = ' + str(direction) + ' #long = 1, short = -1')
self.Log('broken_marker = ' + str(broken_marker) + ' # spot on 100 handle block that breaks (based on bar close). For transitions between 100-handle blocks, the low will always be <100')
self.Log('target = ' + str(target) + ' # profit target, code will exit trade if we hit this w/o filling entry order')
self.Log('pullback = ' + str(pullback) + ' # pullback amount for entry, negative numbers = no pullback vs marker.... entryPrice = self.floor100(tradeBar.Low) + broken_marker - pullback*direction')
self.Log('extreme_for_opening_bar = ' + str(extreme_for_opening_bar) + ' # extreme value (high/low) on the entry bar for method #2')
self.Log('method = ' + str(method) + ' #criteria for placing an order:')
self.Log('#(method #1) close and open on opposite sides of broken_marker, extreme of bar does not hit target')
self.Log('#(method #2) for a long trade: close above broken_marker, high < target, low > extreme_for_opening_bar (reverse for short trade) ')
self.Log('# early exit parameters')
self.Log('distance_to_risk_marker = ' + str(distance_to_risk_marker) + ' # distance to risk marker to lean against. Always a positive number, trade direction will ensure orders are adjusted in the correct manner')
self.Log('distance_above_risk_marker_for_exit = ' + str(distance_above_risk_marker_for_exit) + ' #how far above (for longs) the risk marker to move limit orders when an early exit is triggered')
self.Log('#go again parameters')
self.Log('MAX_GO_AGAINS = ' + str(MAX_GO_AGAINS) + ' # maximum number of times the script will go again if a trade gets risk out-->stop out')
self.Log('total: ' + str(self.total))
self.Log('RO: ' + str(self.RO))
self.Log('PT: ' + str(self.PT))
self.Log('EE: ' + str(self.EE))
self.Log('DS: ' + str(self.DS))
self.Log('GA: ' + str(self.GA))
self.Log('ROGA: ' + str(self.ROGA))
self.Log('PTGA: ' + str(self.PTGA))
self.Log('DSGA: ' + str(self.DSGA))
self.Log('Risk Out %: ' + str(round(self.RO/self.total,2)))
self.Log('Runner Target %: ' + str(round(self.PT/self.total,2)))
self.Log('Disaster Stop %: ' + str(round(self.DS/self.total,2)))
if self.DS > 0:
pf = (RO*self.RO + RT*self.PT) / (2*SL*self.DS)
self.Log('Profit Factor: ' + str(round(pf,2)) + '\n')
t = 0
for i in self.GA: t=t+self.GA[i]
self.Log('Go Again %: ' + str(round(t/self.total,2)))
if self.GA[0] > 0:
self.Log('RO on Go Again (1): ' + str(round(self.ROGA[1]/self.GA[0],2)))
self.Log('DS on Go Again (1): ' + str(round(self.DSGA[1]/self.GA[0],2)))
self.Log('PT on Go Again (1): ' + str(round(self.PTGA[1]/self.GA[0],2)))
if self.DSGA[1] > 0:
pf = (RO*self.ROGA[1] + RT*self.PTGA[1]) / (2*SL*self.DSGA[1])
self.Log('Profit Factor: ' + str(round(pf,2)) + '\n')
else: self.Log('did not go again (1) on any trades')
if self.GA[1] > 0:
self.Log('RO on Go Again (2): ' + str(round(self.ROGA[2]/self.GA[1],2)))
self.Log('DS on Go Again (2): ' + str(round(self.DSGA[2]/self.GA[1],2)))
self.Log('Runner on Go Again (2): ' + str(round(self.PTGA[2]/self.GA[1],2)))
if self.DSGA[2] > 0:
pf = (RO*self.ROGA[2] + RT*self.PTGA[2]) / (2*SL*self.DSGA[2])
self.Log('Profit Factor: ' + str(round(pf,2)) + '\n')
else: self.Log('did not go again (2) on any trades')
if self.GA[2] > 0:
self.Log('RO on Go Again (3): ' + str(round(self.ROGA[3]/self.GA[2],2)))
self.Log('Runner on Go Again (3): ' + str(round(self.PTGA[3]/self.GA[2],2)))
self.Log('DS on Go Again (3): ' + str(round(self.DSGA[3]/self.GA[2],2)))
if self.DSGA[3] > 0:
pf = (RO*self.ROGA[3] + RT*self.PTGA[3]) / (2*SL*self.DSGA[3])
self.Log('Profit Factor: ' + str(round(pf,2)) + '\n')
else: self.Log('did not go again (3) on any trades')
return
def onEndOfDay(self):
self.counter = 0
self.Liquidate()
return
def to100Handle(self, number):
return number - Math.Floor(number/100)*100
def to10Handle(self, number):
return number - Math.Floor(number/10)*10
def floor100(self, number):
return Math.Floor(number/100)*100
def floor10(self, number):
return Math.Floor(number/10)*10
def normBarTo100Handle(self, Bar):
normBar = Bar.Clone()
if self.floor100(Bar.Low) == self.floor100(Bar.High): # if we don't move between 100 handle blocks
normBar.Open = self.to100Handle(Bar.Open)
normBar.Close = self.to100Handle(Bar.Close)
normBar.Low = self.to100Handle(Bar.Low)
normBar.High = self.to100Handle(Bar.High)
# transtion between 100 handle block (produce #'s >100, and low as a part of 100 handle block)
elif (self.floor100(Bar.Low) < self.floor100(Bar.High)):
if self.floor100(normBar.Open) == self.floor100(Bar.High):
normBar.Open = self.to100Handle(normBar.Open) + 100
else:
normBar.Open = self.to100Handle(normBar.Open)
if self.floor100(normBar.Close) == self.floor100(Bar.High):
normBar.Close = self.to100Handle(normBar.Close) + 100
else:
normBar.Close = self.to100Handle(normBar.Close)
normBar.High = self.to100Handle(normBar.High) + 100
normBar.Low = self.to100Handle(Bar.Low)
normBar.Volume = Bar.Volume
return normBar
#--------------------------#
# 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
self._place_bracket_order(k,sym,price,qty)
return
def _place_bracket_order(self,k,symbol,price,qty):
if qty%2==0:
profit_target = price + direction*d.Decimal(RO)
limitTicket = self.LimitOrder(symbol, -1*qty/2, profit_target, 'risk out')
self.order_tickets[k].add_limit_order(limitTicket)
runner_target = price + direction*d.Decimal(RT)
runnerTicket = self.LimitOrder(symbol, -1*qty/2, runner_target, 'runner')
self.order_tickets[k].add_runner_order(runnerTicket)
else:
profit_target = price + direction*d.Decimal(RO)
limitTicket = self.LimitOrder(symbol, -1*qty, profit_target, 'partial fill...')
self.order_tickets[k].add_limit_order(limitTicket)
self.order_tickets[k].add_runner_order(limitTicket) #partial fill, just need to populate the value w/ something...
self.total = self.total + 1
self.earlyExit = False
stop_loss = price - direction*d.Decimal(SL)
stopTicket = self.StopMarketOrder(symbol, -1*qty, stop_loss, 'disaster stop')
self.order_tickets[k].add_stop_market_order(stopTicket)
self.order_tickets[k].is_bracket()
# self.Log('check bracket >> bracket order submitted: {}'.format(k))
class symbolOrderData:
"""
Class object to hold symbol bracket order data
Attributes:
-----------
symbol: str
ticket: ticket object for opening order
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
runner_order: None or ticker 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_runner_order: set runner_order to runnerTicket
add_stop_market_order: set stop_market_order attr to stopMarketTicket
is_bracket: if limit, runner, 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.take_profit_done = False
self.limit_order = None
self.runner_order = None
self.stop_market_order = None
def add_limit_order(self, limitTicket):
self.limit_order = limitTicket
return
def add_runner_order(self, runnerTicket):
self.runner_order = runnerTicket
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.runner_order is not None) and (self.stop_market_order is not None):
self.bracket_submit = True
return