First Hour Breakout - Python

#region Namespaces
# ---------- DON'T REMOVE OR EDIT THESE LINES -------------------
# These lines are required for integrating Python with our .NET platform.
import clr
clr.AddReference("Tickblaze.Model")
import ScriptCode
from TradingStrategyAPI import *
from AssemblyTradingStrategy_6112_ImportedScripts import *
# ---------------------------------------------------------------
#endregion

## <summary>
## Trading Strategy scripts are used for trading one symbol at a time such that each symbol gets its own strategy instance. 
## Common use-cases include momentum strategies, crossover strategies and overbought / oversold strategies, all of which need to evaluate only a single symbol at a time in order to make trading decisions.
## </summary>
class MyTradingStrategy(ScriptCode.TradingStrategyScriptBase):  # NEVER CHANGE THE CLASS NAME
    #region Variables
    # Variables Content
    #endregion

    #region OnInitialize
    ## <summary>
    ## This function is used for accepting the script parameters and for initializing the script prior to all other function calls.
    ## Once the script is assigned to a Desktop, its parameter values can be specified by the user and can be selected for optimization. 
    ## </summary>
    ## --------------------------------------------------------------------------------------------------
    ##                                 INSTRUCTIONS - PLEASE READ CAREFULLY
    ## --------------------------------------------------------------------------------------------------
    ## YOU MUST SET A PARAM TAG FOR EACH PARAMETER ACCEPTED BY THIS FUNCTION.
    ## ALL PARAM TAGS SHOULD BE SET IN THE 'OnInitialize' REGION, RIGHT ABOVE THE 'OnInitialize' FUNCTION.
    ## THE ORDER OF THE TAGS MUST MATCH THE ORDER OF THE ACTUAL PARAMETERS.

    ## REQUIRED ATTRIBUTES:
    ## (1) name: The exact parameter name.
    ## (2) type: The type of data to collect from the user: 
    ## Set to "Integer" when the data type is 'int'
    ## Set to "IntegerArray" when the data type is 'int[]'
    ## Set to "DateTime" when the data type is 'long' (The 'long' data type can only be used for date/time representation)
    ## Set to "DateTimeArray" when the data type is 'long[]' (The 'long' data type can only be used for date/time representation)
    ## Set to "Boolean" when the data type is 'bool'
    ## Set to "BooleanArray" when the data type is 'bool[]'
    ## Set to "Double" when the data type is 'double'
    ## Set to "DoubleArray" when the data type is 'double[]'
    ## Set to "String" when the data type is 'string'
    ## Set to "StringArray" when the data type is 'string[]'
    ## Set to "Indicator" when the data type is 'Indicator'
    ## Set to "Pattern" when the data type is 'Pattern'
    ## Set to "Signal" when the data type is 'Signal'
    ## Set to "Drawing" when the data type is 'Drawing'

    ## OPTIONAL ATTRIBUTES:
    ## (3) default: The default parameter value is only valid when the type is Integer, Boolean, Double, String or an API Type. 
    ## (4) min: The minimum parameter value is only valid when the type is Integer or Double.
    ## (5) max: The maximum parameter value is only valid when the type is Integer or Double.

    ## EXAMPLE: <param name="" type="" default="" min="" max="">Enter the parameter description here.</param> 
    ### --------------------------------------------------------------------------------------------------
	## <param name="overnightStartTime" type="Integer" default="0" min="0" max="2400">The start of the overnight period in 24-hr hhmm notation in the time zone of the underlying symbol.</param>
	## <param name="tradingStartTime" type="Integer" default="830" min="0" max="2400">The start of the trading session in 24-hr hhmm notation in the time zone of the underlying symbol.</param>
	## <param name="tradingEndTime" type="Integer" default="1515" min="0" max="2400">The end of the trading session in 24-hr hhmm notation in the time zone of the underlying symbol.</param>
	## <param name="enableVolatilityBiasFilter" type="Boolean" default="True">Indicates whether to enable the volatility bias filter.</param>
	## <param name="overnightVolumeSMAPeriods" type="Integer" default="5" min="1">The number of periods used to calculate overnight volume SMA.</param>
	## <param name="enableShorting" type="Boolean" default="True">Indicates whether to enable the trading strategy to short symbols.</param>
	## <param name="enableLonging" type="Boolean" default="True">Indicates whether to enable the trading strategy to long symbols.</param>
    def OnInitialize(self,
            overnightStartTime,
            tradingStartTime,
            tradingEndTime,
            enableVolatilityBiasFilter,
            overnightVolumeSMAPeriods,
            enableShorting,
            enableLonging):
		# Set the script parameters to script variables.
        self._enableVolatilityBiasFilter = enableVolatilityBiasFilter
        self._overnightVolumeSMAPeriods = overnightVolumeSMAPeriods
        self._enableShorting = enableShorting
        self._enableLonging = enableLonging
		# Calculate the hour value of the trading start time.
        self._tradingStartTimeHour = tradingStartTime / 100
		# Calculate the minute value of the trading start time.
        self._tradingStartTimeMinute = tradingStartTime % 100
		# Calculate the hour value of the trading end time.
        self._tradingEndTimeHour = tradingEndTime / 100
		# Calculate the minute value of the trading end time.
        self._tradingEndTimeMinute = tradingEndTime % 100
		# Create for holding the overnight volume values.
        self._overnightVolumeValues = []
		# Set the time zone to the time zone of the underlying symbol.
        DateTimeSetTimeZone(SymbolTimeZone())
        # Remove all of the indicators from the chart so that we don't get duplicates.
        ChartIndicatorRemoveAll(SymbolIndex())
		# Create the overnight volume indicator.
        self._overnightVolume = IndicatorOV(self, SymbolIndex(), overnightStartTime, tradingStartTime)
		# Plot the indicator on the underlying symbol's chart.
        indicatorItemID = ChartIndicatorPlot(SymbolIndex(), self._overnightVolume, "Overnight Volume", - 1, 2)
		# Set the indicator pen.
        ChartIndicatorSetPenByIndex(SymbolIndex(), indicatorItemID, 0, C_Color.LIGHT_BLUE, C_DashStyle.SOLID, 1)
		# Set the indicator style.
        ChartIndicatorSetPlotStyle(SymbolIndex(), indicatorItemID, C_PlotStyle.BOX_ZERO)
		# Create a variable to hold whether the collection of overnight volume values has been initialized.
        self._initiallyLoaded = False
        # Create a variable to hold whether a volatility bias has been detected.
        self._volatilityBiasDetected = False
        # Create a variable to hold whether a closing order has been fired but not yet filled.
        self._waitingToClose = False
    #endregion

    #region OnBarUpdate
    ## <summary>
    ## This function is called after each new bar of each symbol assigned to the Desktop strategy. 
    ## It should evaluate the specified symbol and its new bar in order to determine whether to generate new orders for it. 
    ## Never create indicators, signals or patterns from OnBarUpdate, for performance reasons those should be created from OnInitialize.
    ## </summary>
    ## <param name="symbolIndex" type="Integer">The index of the symbol in the strategy symbol table</param>
    ## <param name="dataSeries" type="Integer">The number indicating the data series from which the symbol was updated. 
    ## According to the Desktop strategy data series settings: 0 for the main data series, 1 for the second data series, etc. (See the DataSeriesSwitch function).</param>
    ## <param name="completedBars" type="Integer">The number of completed bars for the specified symbol since the last call to OnBarUpdate.
    ## Always 1 unless the bar type can generate multiple completed bars from a single tick/minute/day update (depending on the underlying bar source).</param>
    def OnBarUpdate(self, symbolIndex, dataSeries, completedBars):
		# Create a variable to hold the start of the current day's trading session.
        tradingStartTime = DateTimeCreate(DateTimeYear(DateTimeCurrent()), DateTimeMonth(DateTimeCurrent()), DateTimeDay(DateTimeCurrent()), self._tradingStartTimeHour, self._tradingStartTimeMinute, 0)
		# Create a variable to hold the end of the current day's trading session.
        tradingEndTime = DateTimeCreate(DateTimeYear(DateTimeCurrent()), DateTimeMonth(DateTimeCurrent()), DateTimeDay(DateTimeCurrent()), self._tradingEndTimeHour, self._tradingEndTimeHour, 0)
		
		# Initialize the collection of overnight volume values if necessary.
        self.InitializeIfNecessary(tradingStartTime)
		
		# Check whether it is currently the period to monitor the high and low values.
        if tradingStartTime < DateTimeCurrent() and DateTimeCurrent() <= DateTimeAddMinutes(tradingStartTime, 60):
			# Check whether it is time to calculate the overnight volume SMA and check for a volatility bias.
            if DataEndDateTime(1) <= tradingStartTime and DataIsComplete(0):
				# Reset the value of the high during the first hour of trading.
                self._firstHourHigh = -float("inf")
				# Reset the value of the low during the first hour of trading.
                self._firstHourLow = float("inf")
				# Record whether a volatility bias has occurred provided that enought time has passed to calculate the overnight volume SMA.
                self._volatilityBiasDetected = self._overnightVolume[0] > (sum(self._overnightVolumeValues) / len(self._overnightVolumeValues)) \
                                                if len(self._overnightVolumeValues) == self._overnightVolumeSMAPeriods else False
				# Add the most recent overnight volume value to the collection of values.
                self._overnightVolumeValues.append(self._overnightVolume[0])
				# Check whether the collection of volume values is greater than the SMA lookback period.
                if len(self._overnightVolumeValues) > self._overnightVolumeSMAPeriods:
					# Remove the oldest overnight volume value.
                    self._overnightVolumeValues.pop(0)
                    
			# Check whether a volatility bias has been detected or the volatility bias filter is disabled.
            if (self._enableVolatilityBiasFilter and self._volatilityBiasDetected) or not self._enableVolatilityBiasFilter:
				# Record a new high if the high of the current bar is greater than the current high of the first hour of trading.
                self._firstHourHigh = max(DataHigh(0), self._firstHourHigh)
				# Record a new low if the low of the current bar is less than the current low of the first hour of trading.
                self._firstHourLow = min(DataLow(0), self._firstHourLow)
                
		# Check whether it is after the first hour of trading but before the cutoff time for sending entry orders.
        elif DateTimeAddMinutes(tradingStartTime, 60) < DateTimeCurrent() and DateTimeCurrent() <= DateTimeAddMinutes(tradingEndTime, -30):
			# Check whether a volatility bias has been detected or the volatility bias filter is disabled and there is not already an open position nor pending order.
            if ((self._enableVolatilityBiasFilter and self._volatilityBiasDetected) or not self._enableVolatilityBiasFilter) and not PositionExists(C_PositionStatus.OPEN) and not OrderExists(C_Status.PENDING, None):
				# Check whether the underlying symbol trades below the low of the first hour of trading.
                if self._enableShorting and DataLow(0) < self._firstHourLow:
					# Generate a sell short market order while assuming that a position sizing script will assign the quantity. 
                    BrokerMarket(C_ActionType.SELL_SHORT, 0, C_TIF.DAY, "Time to sell short")
				# Check whether the underlying symbol trades above the high of the first hour of trading.
                elif self._enableLonging and self._firstHourHigh < DataHigh(0):
					# Generate a buy market order while assuming that a position sizing script will assign the quantity.
                    BrokerMarket(C_ActionType.BUY, 0, C_TIF.DAY, "Time to buy")
                    
		# Check whether it is time to close open positions, an open position exists, and the strategy has not already requested that the open position be closed.
        elif DateTimeCurrent() >= DateTimeAddMinutes(tradingEndTime, -1) and PositionExists(C_PositionStatus.OPEN) and not self._waitingToClose:
			# Close the open position.
            BrokerClosePosition("Time to close.")
			# Record that the strategy is waiting for the position to be closed.
            self._waitingToClose = True
    #endregion

    #region OnOrderFillUpdate
    ## <summary>
    ## This function is called for each new order fill.
    ## </summary>
    ## <param name="symbolIndex" type="Integer">The symbol index</param>
    ## <param name="orderIndex" type="Integer">The order index</param>
    ## <param name="orderFillIndex" type="Integer">The order fill index</param>
    def OnOrderFillUpdate(self, symbolIndex, orderIndex, orderFillIndex):
        # OnOrderFillUpdate Content
        pass
    #endregion

    #region OnOrderUpdate
    ## <summary>
    ## This function is called when an order is executed or cancelled.
    ## </summary>
    ## <param name="symbolIndex" type="Integer">The symbol index</param>
    ## <param name="orderIndex" type="Integer">The order index</param>
    ## <param name="status" type="C_Status">The updated status of the order</param>
    def OnOrderUpdate(self, symbolIndex, orderIndex, status):
        # OnOrderUpdate Content
        pass
    #endregion

    #region OnPositionUpdate
    ## <summary>
    ## This function is called when a position is opened or closed. 
    ## </summary>
    ## <param name="symbolIndex" type="Integer">The symbol index</param>
    ## <param name="positionIndex" type="Integer">The position index</param>
    ## <param name="status" type="C_PositionStatus">The updated status of the position</param>
    def OnPositionUpdate(self, symbolIndex, positionIndex, status):
		# Check whether the position just closed.
        if status == C_PositionStatus.CLOSED:
			# Record that the strategy is no longer waiting for the position to be closed.
            self._waitingToClose = False
    #endregion

    #region OnSessionUpdate
    ## <summary>
    ## This function is called when a session is opened or closed.
    ## </summary>
    ## <param name="symbolIndex" type="Integer">The symbol index whose session is updated</param>
    ## <param name="status" type="C_SessionStatus">The session status</param>
    def OnSessionUpdate(self, symbolIndex, status):
        # OnSessionUpdate Content
        pass
    #endregion

    #region OnNewsUpdate
    ## <summary>
    ## This function is called when a news update is received and only if the NO_NEWS_UPDATES comment is removed.
    ## </summary>
    ## <param name="symbolIndex" type="Integer">The symbol index for the update</param>
    ## <param name="dateTime" type="DateTime">The date/time in which the update was received by the platform</param>
    ## <param name="title" type="String">The update title</param>
    ## <param name="message" type="String">The update message</param>   
    ## <param name="type" type="C_MessageType">The update message type</param>
    def OnNewsUpdate(self, symbolIndex, dateTime, title, message, type):
        # OnNewsUpdate Content
        # [NO_NEWS_UPDATES] - Delete this comment to enable news updates to this strategy.
        pass
    #endregion

    #region OnRSSUpdate
    ## <summary>
    ## This function is called when an RSS update is received and only if the NO_RSS_UPDATES comment is removed.
    ## </summary>
    ## <param name="symbolIndex" type="Integer">The symbol index for the update</param>
    ## <param name="dateTime" type="DateTime">The date/time in which the update was received by the platform</param>
    ## <param name="title" type="String">The update title</param>
    ## <param name="message" type="String">The update message</param>   
    ## <param name="type" type="C_MessageType">The message type</param>
    def OnRSSUpdate(self, symbolIndex, dateTime, title, message, type):
        # OnRSSUpdate Content
        # [NO_RSS_UPDATES] - Delete this comment to enable RSS updates to this strategy.
        pass
    #endregion

    #region OnAlertUpdate
    ## <summary>
    ## This function is called when an alert update is received and only if the NO_ALERT_UPDATES comment is removed.
    ## </summary>
    ## <param name="symbolIndex" type="Integer">The symbol index for the update</param>
    ## <param name="dateTime" type="DateTime">The date/time in which the update was received by the platform</param>
    ## <param name="message" type="String">The update message</param>   
    ## <param name="type" type="C_MessageType">The update message type</param>
    def OnAlertUpdate(self, symbolIndex, dateTime, message, type):
        # OnAlertUpdate Content
        # [NO_ALERT_UPDATES] - Delete this comment to enable alert updates to this strategy.
        pass
    #endregion

    #region OnJournalUpdate
    ## <summary>
    ## This function is called when a journal update is received and only if the NO_JOURNAL_UPDATES comment is removed.
    ## </summary>
    ## <param name="symbolIndex" type="Integer">The symbol index for the update</param>
    ## <param name="dateTime" type="DateTime">The date/time in which the update was received by the platform</param>
    ## <param name="title" type="String">The update title</param>
    ## <param name="message" type="String">The update message</param>   
    ## <param name="type" type="C_MessageType">The update message type</param>
    def OnJournalUpdate(self, symbolIndex, dateTime, title, message, type):
        # OnJournalUpdate Content
        # [NO_JOURNAL_UPDATES] - Delete this comment to enable journal updates to this strategy.
        pass
    #endregion

    #region OnDataConnectionUpdate
    ## <summary>
    ## This function is called when a data connection update is received and only if the NO_DATA_CONNECTION_UPDATES comment is removed.
    ## </summary>
    ## <param name="symbolIndex" type="Integer">The symbol index for the update</param>
    ## <param name="dateTime" type="DateTime">The date/time in which the update was received by the platform</param>
    ## <param name="message" type="String">The update message</param>   
    ## <param name="type" type="C_MessageType">The update message type</param>
    def OnDataConnectionUpdate(self, symbolIndex, dateTime, message, type):
        # OnDataConnectionUpdate Content
        # [NO_DATA_CONNECTION_UPDATES] - Delete this comment to enable data connection updates to this strategy.
        pass
    #endregion

    #region OnBrokerConnectionUpdate
    ## <summary>
    ## This function is called when a broker connection update is received and only if the NO_BROKER_CONNECTION_UPDATES comment is removed.
    ## </summary>
    ## <param name="dateTime" type="DateTime">The date/time in which the update was received by the platform</param>
    ## <param name="message" type="String">The update message</param>   
    ## <param name="type" type="C_MessageType">The update message type</param>
    def OnBrokerConnectionUpdate(self, dateTime, message, type):
        # OnBrokerConnectionUpdate Content
        # [NO_BROKER_CONNECTION_UPDATES] - Delete this comment to enable broker connection updates to this strategy.
        pass
    #endregion

    #region OnShutdown
    ## <summary>
    ## This function is called when the script is shutdown.
    ## </summary>
    def OnShutdown(self):
        # OnShutdown Content
        pass
    #endregion
    
    def InitializeIfNecessary(self, tradingStartTime):
    	# Check whether it is necessary to initialize the overnight volume values and it has not already been done.
        if (StrategyMode() == C_StrategyMode.LIVE or StrategyMode() == C_StrategyMode.PLAYBACK) and not self._initiallyLoaded:
    		# Check whether it is past the overnight window.
            if tradingStartTime < DateTimeCurrent():
    			# Add the most recent overnight volume value to the collection of values.
                self._overnightVolumeValues.append(self._overnightVolume[0])
    		# Check whether the strategy needs to go back in time to fill up the collection of overnight volume values.
            if len(self._overnightVolumeValues) != self._overnightVolumeSMAPeriods:
    			# Create a variable the hold the bar shift index.
                tempBarShift = 1
    			# Iterate backwards until the collection is full or there is no more volume history.
                while len(self._overnightVolumeValues) < self._overnightVolumeSMAPeriods and self._overnightVolume[tempBarShift] != 0:
    				# Check whether the overnight volume of the current barshift is greater than the overnight volume one barshift into the future.
                    if self._overnightVolume[tempBarShift] > self._overnightVolume[tempBarShift - 1]:
    					# Add the current overnight volume value to the front of the collection.
                        self._overnightVolumeValues.insert(0, self._overnightVolume[tempBarShift])
    				# Increment the bar shift variable.
                    tempBarShift = tempBarShift + 1
                    
    		# Check whether it is past the time to check for a volatility bias.
            if tradingStartTime < DateTimeCurrent():
    			# Record whether a volatility bias has occurred provided that the collection of overnight volume values is full.
                self._volatilityBiasDetected = self._overnightVolume[0] > (sum(self._overnightVolumeValues) / len(self._overnightVolumeValues)) \
                                                if len(self._overnightVolumeValues) == self._overnightVolumeSMAPeriods else False
    			# Check whether a volatility bias has been detected or the volatility bias filter is disabled.
                if (self._enableVolatilityBiasFilter and self._volatilityBiasDetected) or not self._enableVolatilityBiasFilter:
    				# Initialize the first hour high.
                    self._firstHourHigh = -float("inf")
    				# Initialize the first hour low.
                    self._firstHourLow = float("inf")
    				# Create a variable the hold the bar shift index.
                    barShift = 1
    				# Iterate backwards until the the start time of trading is reached.
                    while tradingStartTime < DataEndDateTime(barShift):
    					# Check whether it is currently the period to monitor the high and low values.
                        if DataEndDateTime(barShift) <= DateTimeAddMinutes(tradingStartTime, 60):
    						# Record a new high if the high of the current bar is greater than the current high of the first hour of trading.
                            self._firstHourHigh = max(DataHigh(barShift), self._firstHourHigh)
    						# Record a new low if the low of the current bar is less than the current low of the first hour of trading.
                            self._firstHourLow = min(DataLow(barShift), self._firstHourLow)
    					# Increment the bar shift variable.
                        barShift = barShift + 1
    		# Record that the overnight volume values have been initialized.
            self._initiallyLoaded = True