| Overall Statistics |
|
Total Trades 4 Average Win 0% Average Loss 0% Compounding Annual Return 0% Drawdown 0% Expectancy 0 Net Profit 0% Sharpe Ratio 0 Probabilistic Sharpe Ratio 0% Loss Rate 0% Win Rate 0% Profit-Loss Ratio 0 Alpha 0 Beta 0 Annual Standard Deviation 0 Annual Variance 0 Information Ratio 0 Tracking Error 0 Treynor Ratio 0 Total Fees $4.00 Estimated Strategy Capacity $140000000.00 Lowest Capacity Asset GOOCV VP83T1ZUHROL |
from AlgorithmImports import *
from OrderStatuses import OrderStatusCodes
'''
Class to handle bracket orders
TODO: CREATE STOP LOSS AND PROFIT ORDERS BASED ON THE ACTUAL FILL PRICE
'''
class BracketOrder:
def __init__(self, algorithm, symbol, totQuantity, placementPrice, limitPrice, stopPrice, profitPrice):
self.algorithm = algorithm
self.symbol = symbol
self.totQuantity = totQuantity
self.placementPrice = placementPrice
self.limitPrice = limitPrice
self.stopPrice = stopPrice
self.profitPrice = profitPrice
# Initialize future properties
self.filledQuantity = 0
self.parentTicket = None
self.stopOrderTicket = None
self.profitOrderTicket = None
# Place initial order
self.PlaceInitialOrder()
'''
Place initial order
'''
def PlaceInitialOrder(self):
self.parentTicket = self.algorithm.StopLimitOrder(self.symbol, self.totQuantity, self.placementPrice, self.limitPrice, tag="Initial order")
self.algorithm.bracketOrders[self.parentTicket.OrderId] = self
self.algorithm.Debug(f"Parent order placed for {self.symbol}")
return
'''
Handle order changes
Orderstatus enum:
https://www.quantconnect.com/docs/algorithm-reference/trading-and-orders#Trading-and-Orders-Tracking-Order-Events
'''
def HandleOrderChange(self, orderEvent):
# If the orderstatus is PartiallyFilled or Filled it will have a filledQuantity > 0
# Currently make no changes for order statuses that does not (partially) fill an order
eventFillQuantity = orderEvent.FillQuantity
if (orderEvent.FillQuantity == 0):
return
'''
Handle scenario where parent order is updated
Add/update stop and profit orders
'''
if (orderEvent.OrderId == self.parentTicket.OrderId):
self.algorithm.Debug(f"Parent order ({self.symbol}) {OrderStatusCodes[orderEvent.Status]}, quantity: {eventFillQuantity}")
self.filledQuantity += eventFillQuantity
# Place/add to stop order (negative because we are selling)
self.PlaceStopOrder(-eventFillQuantity)
# Place/add to profit order (negative because we are selling)
self.PlaceProfitOrder(-eventFillQuantity)
return
'''
Handle scenario where stop loss is hit
- Cancel all open orders on the symbol
- Liquidate portfolio of that stock (should not be necessary, but extra precaution)
'''
if (orderEvent.OrderId == self.stopOrderTicket.OrderId):
self.CancelTrade()
self.algorithm.Debug(f"Stop order ({self.symbol}) {OrderStatusCodes[orderEvent.Status]}, quantity: {eventFillQuantity}")
return
'''
Handle scenario where profit target is hit
- Cancel all open orders on the symbol (for now)
- Should in reality check if profit is partially filled (not equal to self.filledQuantity), and update stop loss order
- Should probably cancel parent order in case it is partially filled
'''
if (orderEvent.OrderId == self.profitOrderTicket.OrderId):
self.algorithm.Debug(f"Profit order ({self.symbol}) {OrderStatusCodes[orderEvent.Status]}, quantity: {eventFillQuantity}")
self.CancelTrade()
return
'''
Place/add to stop market order when the parent order is (partially) filled
'''
def PlaceStopOrder(self, stopQuantity):
# Create new profit order ticket if non exists
if (self.stopOrderTicket is None):
self.stopOrderTicket = self.algorithm.StopMarketOrder(self.symbol, stopQuantity, self.stopPrice, tag=f"Stop order ({self.stopPrice})")
# Connect the stop order ticket to this object
self.algorithm.bracketOrders[self.stopOrderTicket.OrderId] = self
self.algorithm.Debug(f"Stop order placed ({self.symbol})")
return
# Update existing stop order ticket
updateSettings = UpdateOrderFields()
updateSettings.Quantity = stopQuantity
response = self.stopOrderTicket.Update(updateSettings)
self.algorithm.Debug(f"Stop order updated ({self.symbol})")
return
'''
Place/add to profit taking order when the parent order is (partially) filled
'''
def PlaceProfitOrder(self, profitQuantity):
# Create new profit order ticket if non exists
if (self.profitOrderTicket is None):
self.profitOrderTicket = self.algorithm.LimitOrder(self.symbol, profitQuantity, self.profitPrice, tag=f"Profit order ({self.profitPrice})")
# Connect the stop order ticket to this object
self.algorithm.bracketOrders[self.profitOrderTicket.OrderId] = self
self.algorithm.Debug(f"Profit order placed ({self.symbol})")
return
# Update existing profit order ticket
updateSettings = UpdateOrderFields()
updateSettings.Quantity = profitQuantity
response = self.profitOrderTicket.Update(updateSettings)
self.algorithm.Debug(f"Profit order updated ({self.symbol})")
return
'''
Cancel trade
'''
def CancelTrade(self):
# Cancel open orders and liquidate position (pre-caution)
self.algorithm.Transactions.CancelOpenOrders(self.symbol)
self.algorithm.Liquidate(self.symbol, tag="LIQUIDATION ORDER INSIDE")
# Remove order IDs from bracketOrders dictionary
# self.algorithm.bracketOrders.pop(self.parentTicket.OrderId, None)
# self.algorithm.bracketOrders.pop(self.profitOrderTicket.OrderId, None)
# self.algorithm.bracketOrders.pop(self.stopOrderTicket.OrderId, None)
return
#region imports
from AlgorithmImports import *
#endregion
OrderStatusCodes = {
0:'NEW', # new order pre-submission to the order processor
1:'SUBMITTED', # order submitted to the market
2:'PARTIALLY FILLED', # 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:'CANCEL PENDING', # order waiting for confirmation of cancellation
9:'UPDATE SUBMITTED' # Order update submitted to the market
}
from AlgorithmImports import *
from BracketOrder import BracketOrder
class TestBracketOrder(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2022, 6, 1) # Set Start Date
self.SetEndDate(2022, 6, 1) # Set End Date
self.SetCash(100000) # Set Strategy Cash
# Ensure securities are set with adjusted data normalization
self.SetSecurityInitializer(lambda x: x.SetDataNormalizationMode(DataNormalizationMode.Adjusted))
# Add SPY for scheduled events
self.AddEquity("SPY", resolution=Resolution.Minute, extendedMarketHours=True)
# Create scheduled event to liquidate open orders and positions at end of day
self.Schedule.On(self.DateRules.EveryDay("SPY"),
self.TimeRules.BeforeMarketClose("SPY", 30),
self.EndOfDay)
# Add testing assets
self.assets = ["GOOG", "AMZN"]
for asset in self.assets:
self.AddEquity(asset, Resolution.Minute, extendedMarketHours=True)
# Initiate bracket order handling dictionary
self.bracketOrders = {}
self.tickets = []
self.liquidatedOrderIds = []
self.currentLiquidated = None
def OnData(self, data: Slice):
"""OnData event is the primary entry point for your algorithm. Each new data point will be pumped in here.
Arguments:
data: Slice object keyed by symbol containing the stock data
"""
for bar in data.Bars.Values:
if (bar.Time.hour == 9 and bar.Time.minute == 30 and bar.Symbol != "SPY"):
symbol = bar.Symbol
# Create stop limit order on break of max open/close
placementPrice = max(bar.Close, bar.Open)
limitPrice = placementPrice + 0.05
qty = 100
# Place order - initiate bracket order object
BracketOrder(self, symbol, qty, placementPrice, limitPrice, placementPrice * 0.98, placementPrice * 1.02)
# self.Debug(f"Symbol: {bar.Symbol}, Time Bar {bar.Time}, CLOSE {bar.Close}")
def OnOrderEvent(self, orderEvent: OrderEvent):
# If portfolio liquidation happens from scheduled event at the end of the day, no bracketorder object is created and can not be found in the bracketorders dictionary
# Just ignore liquidation orders - they are tagged by "LIQUIDATION ORDER"
order = self.Transactions.GetOrderById(orderEvent.OrderId)
if ("LIQUIDATION ORDER" in order.Tag):
return
# If the order status is NEW or SUBMITTED, no changes are made to the order
if (orderEvent.Status == OrderStatus.New or orderEvent.Status == OrderStatus.Submitted):
return
# For any other order statuses we expect the order to be in the dictionary of orders
bracketOrder = self.bracketOrders[orderEvent.OrderId]
# Make changes in the bracket order object
bracketOrder.HandleOrderChange(orderEvent)
'''
Liquidate portfolio and open orders at end of day
'''
def EndOfDay(self):
self.Transactions.CancelOpenOrders()
self.currentLiquidated = self.Liquidate(tag="LIQUIDATION ORDER")
self.Debug(f"Liquidation performed: {self.currentLiquidated}")
#region imports
from AlgorithmImports import *
#endregion
'''
Random stuff
if (orderEvent.OrderId not in self.bracketOrders):
if (orderEvent.FillQuantity != 0):
self.Debug(f"{self.Time}, Order quantity not zero: {orderEvent.FillQuantity}")
assert (1 == 2), "Order quantity not zero"
else:
return
// Place your key bindings in this file to override the defaultsauto[]
[
{
"key": "ctrl+k",
"command": "editor.action.commentLine",
"when": "editorTextFocus && !editorReadonly"
},
{
"key": "ctrl+/",
"command": "-editor.action.commentLine",
"when": "editorTextFocus && !editorReadonly"
}
]
# Order partially filled - cancel remaining parent order, update stop order
self.filledQuantity += eventFillQuantity
cancelRes = self.parentTicket.Cancel("Canceled remaining parent order")
# Update existing stop order ticket
updateSettings = UpdateOrderFields()
updateSettings.Quantity = self.filledQuantity
updateRes = self.stopOrderTicket.Update(updateSettings)
return
'''