Vix Predicts Stock Index Returns - 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 MultiSymbolTradingStrategyAPI import *
from AssemblyMultiSymbolTradingStrategy_6014_ImportedScripts import *
# ---------------------------------------------------------------
#endregion

## <summary>
## Multi-Symbol Trading Strategy scripts are used for simultaneously trading a group of symbols from a single strategy instance. 
## Common use-cases include pair trading strategies, basket trading strategies and dynamic index strategies, all of which need to evaluate multiple symbols at the same time in order to make trading decisions.
## </summary>
class MyMultiSymbolTradingStrategy(ScriptCode.MultiSymbolTradingStrategyScriptBase):  # 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 an integer.
    ## Set to "IntegerArray" when the data type is an integer list.
    ## Set to "DateTime" when the data type is is an integer representing a date/time.
    ## Set to "DateTimeArray" when the data type is an integer list representing a list of date/time.
    ## Set to "Boolean" when the data type is a boolean.
    ## Set to "BooleanArray" when the data type is a list of booleans.
    ## Set to "Double" when the data type is a number.
    ## Set to "DoubleArray" when the data type is a list of numbers.
    ## Set to "String" when the data type is a string.
    ## Set to "StringArray" when the data type is a list of strings.

    ## 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="volatilityIndexID" type="String" default="^VIX">The symbol ID used for the volatility index.</param>
	## <param name="volatilityLookback" type="Integer" default="252" min="1">The length the volatility index history.</param>
	## <param name="upperPercentileCutoff" type="Double" default="90" min="0" max="99.99">The percentile of the volatility index closes above which the close is considered high.</param>
	## <param name="lowerPercentileCutoff" type="Double" default="10" min="0" max="99.99">The percentile of the volatility index closes below which the close is considered low.</param>
	## <param name="equityIndexID" type="String" default="SPY">The symbol ID used for the equity index.</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>
	## <param name="stopLoss" type="Double" default="0">The percent distance from the entry price in which to place a stop loss order. (0 to ignore). </param>
	## <param name="takeProfit" type="Double" default="0">The percent distance from the entry price in which to place a take profit order. (0 to ignore). </param>
    def OnInitialize(self,
            volatilityIndexID,
            volatilityLookback,
            upperPercentileCutoff,
            lowerPercentileCutoff,
            equityIndexID,
            enableShorting,
            enableLonging,
            stopLoss,
            takeProfit):
		# Set the script parameter to a variable.
        self._volatilityLookback = volatilityLookback
		# Set the script parameter to a variable.
        self._enableShorting = enableShorting
		# Set the script parameter to a variable.
        self._enableLonging = enableLonging
		# Set the script parameter to a variable.
        self._stopLoss = stopLoss
		# Set the script parameter to a variable.
        self._takeProfit = takeProfit
		# Create an array to hold all symbol indexes that match the volatility index ID.
        volatilitySymbolIndexes = SymbolFindIndex(volatilityIndexID)
		# Check that at least one symbol index matching the volatility index ID was found.
        if len(volatilitySymbolIndexes) >= 1:
			# Record the symbol index of the volatility index.
            self._volatilitySymbolIndex = volatilitySymbolIndexes[0]
		# Create an array to hold all symbol indexes that match the equity index ID.
        equitySymbolIndexes = SymbolFindIndex(equityIndexID)
		# Check that at least one symbol index matching the equity index ID was found.
        if len(equitySymbolIndexes) >= 1:
			# Record the symbol index of the equity index.
            self._equitySymbolIndex = equitySymbolIndexes[0]
		# Check whether both the volatility and equity indexes were provided.
        if self._volatilitySymbolIndex != -1 and self._equitySymbolIndex != -1:
            self._symbolsFound = True
		# Create for holding the historical closes of the volatility index.
        self._volatilityIndexHistory = []
		# Calculate the index of the sorted volatility index close above which the close is considered high.
        self._upperIndexCutoff = int(self._volatilityLookback * upperPercentileCutoff / 100)
		# Calculate the index of the sorted volatility index close below which the close is considered low.
        self._lowerIndexCutoff = int(self._volatilityLookback * lowerPercentileCutoff / 100)
    #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.</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>
    ## <param name="isLastSymbol" type="Boolean">Indicates whether this is the last symbol to be updated for the current bar. 
    ## The parameter is valid when the bars for different symbols have matching timestamps, e.g. 1m, 5m, 1d, 1w, etc.</param>
    def OnBarUpdate(self, symbolIndex, dataSeries, completedBars, isLastSymbol):
		# Switch the API functions to work with the current symbol.
        SymbolSwitch(symbolIndex)
		# Check whether the bar is complete and the current symbol in the volatility index.
        if DataIsComplete(0) and symbolIndex == self._volatilitySymbolIndex:
			# Check whether the list is completely empty.
            if len(self._volatilityIndexHistory) == 0:
				# Create a variable the hold the number of bar shifts used to fill the close history of the volatility index.
                barShift = 0
				# Iterate backwards until the volatility index is full or there is no more price history.
                while len(self._volatilityIndexHistory) < self._volatilityLookback and DataClose(barShift) != 0:
					# Add the current close to the list of historical closes.
                    self._volatilityIndexHistory.append(DataClose(barShift))
					# Increment the bar shift variable.
                    barShift = barShift + 1
            else:
				# Add the current close to the list of historical closes.
                self._volatilityIndexHistory.append(DataClose(0))
				# Check whether the list of historical closes has too many elements.
                if len(self._volatilityIndexHistory) > self._volatilityLookback:
					# Remove the oldest close from the list.
                    self._volatilityIndexHistory.pop(0)
                    
		# Check whether all of the symbols have been updated, the volatility and equity indexes symbol IDs were found, and enough history has passed to calculate the percentile cutoffs.
        if isLastSymbol and self._symbolsFound and len(self._volatilityIndexHistory) == self._volatilityLookback:
			# Switch the API functions to work with the current symbol.
            SymbolSwitch(symbolIndex)
			# Check whether the bar is complete.
            if DataIsComplete(0):
				# Record the most recent close of the volatility index.
                volatilityIndexClose = self._volatilityIndexHistory[-1]
				# Create a copy of the volatility index history to be sorted.
                self._sortedVolatilityIndexHistory = list(self._volatilityIndexHistory)
				# Sort the volatility index closes by ascending values.
                self._sortedVolatilityIndexHistory = sorted(self._sortedVolatilityIndexHistory)
				# Switch the API functions to work with the equity index symbol.
                SymbolSwitch(self._equitySymbolIndex)
				# Check the volatility index closed at or above the upper cutoff.
                if volatilityIndexClose >= self._sortedVolatilityIndexHistory[self._upperIndexCutoff]:
					# Check whether a short position exists. 
                    if PositionExistsInDirection(C_PositionStatus.OPEN, C_Direction.SHORT_SIDE):
						# Generate a closing buy to cover market order while assuming that a position sizing script will assign the quantity.
                        BrokerMarket(C_ActionType.BUY_TO_COVER, 0, C_TIF.DAY, "Time to close.")
					# Check whether the strategy can go long, there is not a long position open, and there is not a pending order.
                    if self._enableLonging and not PositionExistsInDirection(C_PositionStatus.OPEN, C_Direction.LONG_SIDE) and not OrderExists(C_Status.PENDING, None):
						# Buy the current symbol while assuming that a position sizing script will assign the quantity.
                        orderIndex = BrokerMarket(C_ActionType.BUY, 0, C_TIF.DAY, "Time to buy.")
						# Set a stop loss on the order.
                        BrokerSetStopLossPercent(orderIndex, self._stopLoss, True, "Stop loss")
						# Set a take profit on the order. 
                        BrokerSetTakeProfitPercent(orderIndex, self._takeProfit, True, "Profit target")
                        
				# Check whether the volatility index closed at or below the lower cutoff.
                elif volatilityIndexClose <= self._sortedVolatilityIndexHistory[self._lowerIndexCutoff]:
					# Check whether a long position exists. 
                    if PositionExistsInDirection(C_PositionStatus.OPEN, C_Direction.LONG_SIDE):
						# Generate a closing sell market order while assuming that a position sizing script will assign the quantity.
                        BrokerMarket(C_ActionType.SELL, 0, C_TIF.DAY, "Time to close.")
					# Check whether the strategy can go short, there is not a short position open, and there is not a pending order.
                    if self._enableShorting and not PositionExistsInDirection(C_PositionStatus.OPEN, C_Direction.SHORT_SIDE) and not OrderExists(C_Status.PENDING, None):
						# Sell short the current symbol while assuming that a position sizing script will assign the quantity.
                        orderIndex = BrokerMarket(C_ActionType.SELL_SHORT, 0, C_TIF.DAY, "Time to sell.")
						# Set a stop loss on the order. 
                        BrokerSetStopLossPercent(orderIndex, self._stopLoss, True, "Stop loss")
						# Set a take profit on the order. 
                        BrokerSetTakeProfitPercent(orderIndex, self._takeProfit, True, "Profit target")
				# Check whether the volatility index closed between the lower and upper cutoffs.
                elif self._sortedVolatilityIndexHistory[self._lowerIndexCutoff] < volatilityIndexClose and volatilityIndexClose < self._sortedVolatilityIndexHistory[self._upperIndexCutoff]:
					# Close any open positions.
                    BrokerClosePosition("Time to close.")
    #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 underlying symbol index of the order</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 underlying symbol index of the position</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):
        # OnPositionUpdate Content
        pass
    #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 update 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 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