Overall Statistics
Total Trades
0
Average Win
0%
Average Loss
0%
Compounding Annual Return
0%
Drawdown
0%
Expectancy
0
Net Profit
0%
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
$0.00
from System import *
from QuantConnect import *
from QuantConnect.Orders import *
from QuantConnect.Algorithm import *
from QuantConnect.Algorithm.Framework import *
from QuantConnect.Algorithm.Framework.Execution import * 
from QuantConnect.Algorithm.Framework.Risk import * 
from QuantConnect.Algorithm.Framework.Selection import *

from HistoricalReturnsAlphaModel import HistoricalReturnsAlphaModel
from BlackLittermanPortfolioConstructionModel import BlackLittermanPortfolioConstructionModel
from datetime import timedelta
import numpy as np
import pandas as pd
from scipy.optimize import minimize
from CustomFundamentalPortfolioSelectionModel import CustomFundamentalPortfolioSelectionModel
from MacdAlphaModel import MacdAlphaModel

class MeanVarianceOptimizationAlgorithm(QCAlgorithmFramework):
    '''Mean Variance Optimization Algorithm.'''

    def Initialize(self):
        ''' Initialise the data and resolution required, as well as the cash and start-end dates for your algorithm. All algorithms must initialized.'''

        # Set requested data resolution
        self.UniverseSettings.Resolution = Resolution.Minute
        # self.DebugMode = True
        self.SetStartDate(2015, 10, 1)   #Set Start Date
        self.SetEndDate(2015, 10, 6)     #Set End Date
        self.SetCash(10000000)            #Set Strategy Cash

        # tickers = ['IWD', 'MTUM', 'IWN', 'IWM', 'EFA', 'EEM']#, 'IEF', 'SPY', 'LQD', 'TLT', 'DBC', 'GLD', 'VNQ']
        # symbols = [ Symbol.Create(ticker, SecurityType.Equity, Market.USA) for ticker in tickers ]
        self.UniverseSettings.Resolution = Resolution.Daily

        self.minimum_weight = -1
        self.maximum_weight = 1

        # set algorithm framework models
        # self.SetUniverseSelection( ManualUniverseSelectionModel(symbols) )
        self.SetUniverseSelection(CustomFundamentalPortfolioSelectionModel())
        
        # self.SetAlpha( HistoricalReturnsAlphaModel(resolution = Resolution.Daily) )
        # self.SetAlpha(CompositeAlphaModel([ConstantAlphaModel(InsightType.Price, InsightDirection.Up, timedelta(minutes = 60), 0.025, None), 
        #                                   HistoricalReturnsAlphaModel(resolution = Resolution.Daily),
        #                                   MacdAlphaModel()]))
        self.SetAlpha(CompositeAlphaModel( [ConstantAlphaModel(InsightType.Price, InsightDirection.Up, timedelta(minutes = 60), 0.0025, None), 
                                            #ConstantAlphaModel(InsightType.Price, InsightDirection.Down, timedelta(minutes = 60), 0.001, None),]))
                                            #MacdAlphaModel(10, 20, 6, MovingAverageType.Simple, Resolution.Daily), 
                                            HistoricalReturnsAlphaModel(resolution = Resolution.Daily),
                                            MacdAlphaModel()]))
                                           
        self.SetPortfolioConstruction( BlackLittermanPortfolioConstructionModel() )
        
        self.SetExecution( ImmediateExecutionModel() )
        
        self.SetRiskManagement( NullRiskManagementModel() )


    #def OnOrderEvent(self, orderEvent):
    #    if orderEvent.Status == OrderStatus.Filled:
    #        self.Debug(orderEvent.ToString())

    def maximum_sharpe_ratio(self, returns):
        '''Maximum Sharpe Ratio optimization method'''

        # Objective function
        fun = lambda weights: -self.sharpe_ratio(returns, weights)
        
        # Constraint #1: The weights can be negative, which means investors can short a security.
        constraints = [{'type': 'eq', 'fun': lambda w: np.sum(w) - 1}]

        size = returns.columns.size
        x0 = np.array(size * [1. / size])
        bounds = tuple((self.minimum_weight, self.maximum_weight) for x in range(size))
        
        opt = minimize(fun,                         # Objective function
                       x0,                          # Initial guess
                       method='SLSQP',              # Optimization method:  Sequential Least SQuares Programming
                       bounds = bounds,             # Bounds for variables 
                       constraints = constraints)   # Constraints definition

        weights = pd.Series(opt['x'], index = returns.columns)

        return opt, weights

    def sharpe_ratio(self, returns, weights):
        annual_return = np.dot(np.matrix(returns.mean()), np.matrix(weights).T).item()
        annual_volatility = np.sqrt(np.dot(weights.T, np.dot(returns.cov(), weights)))
        return annual_return/annual_volatility
# QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
# Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from clr import AddReference
AddReference("System")
AddReference("QuantConnect.Algorithm")
AddReference("QuantConnect.Common")
AddReference("QuantConnect.Indicators")

from System import *
from QuantConnect import *
from QuantConnect.Indicators import *
from QuantConnect.Algorithm import *
from QuantConnect.Algorithm.Framework import *
from QuantConnect.Algorithm.Framework.Alphas import *
from QuantConnect.Algorithm.Framework.Portfolio import PortfolioTarget
from datetime import timedelta
import numpy as np
import pandas as pd
from scipy.optimize import minimize
from itertools import groupby
from numpy import dot, transpose
from numpy.linalg import inv

### <summary>
### Provides an implementation of Mean-Variance portfolio optimization based on modern portfolio theory.
### The interval of weights in optimization method can be changed based on the long-short algorithm.
### The default model uses the last three months daily price to calculate the optimal weight 
### with the weight range from -1 to 1 and minimize the portfolio variance with a target return of 2%
### </summary>
class BlackLittermanPortfolioConstructionModel:
    def __init__(self, *args, **kwargs):
        """Initialize the model
        Args:
            lookback(int): Historical return lookback period
            resolution: The resolution of the history price 
            minimum_weight(float): The lower bounds on portfolio weights
            maximum_weight(float): The upper bounds on portfolio weights
            risk_free_rate(float): The risk free rate
            self.tau(float): A scalar number indicating the uncertainty of the CAPM prior """
        self.lookback = kwargs['lookback'] if 'lookback' in kwargs else 63
        # self.period = kwargs['period'] if 'period' in kwargs else 15
        self.resolution = kwargs['resolution'] if 'resolution' in kwargs else Resolution.Daily
        self.minimum_weight = kwargs['minimum_weight'] if 'minimum_weight' in kwargs else -1
        self.maximum_weight = kwargs['maximum_weight'] if 'maximum_weight' in kwargs else 1
        self.risk_free_rate = kwargs['risk_free_rate'] if 'risk_free_rate' in kwargs else 0
        self.tau = kwargs['risk_free_rate'] if 'risk_free_rate' in kwargs else 0.025
        self.symbolDataDict = {}
        self.pendingRemoval = []

    def CreateTargets(self, algorithm, insights):
        """ 
        Create portfolio targets from the specified insights
        Args:
            algorithm: The algorithm instance
            insights: The insights to create portoflio targets from
        Returns: 
            An enumerable of portoflio targets to be sent to the execution model
        """
        algorithm.Log("Begin Try Catch")
        try:
            algorithm.Lom("CHANGE:: Purposefully calling undefined method")
        except:
            algorithm.Error(str(sys.exc_info()[0]))
        algorithm.Log("End Try Catch")
        
        for symbol in self.pendingRemoval:
            yield PortfolioTarget.Percent(algorithm, symbol, 0)
        self.pendingRemoval.clear()

        price = {}
        for symbol, data in self.symbolDataDict.items():
            price[str(symbol)] = data.PriceSeries()
        df_price = pd.DataFrame(price)#.astype(float)
        returns = df_price.pct_change().dropna()

  
        symbols = list(returns.columns)

        # market capitalization weight
        W = np.array([1/len(symbols)]*len(symbols))
        # annualized return
        annual_return = np.sum(((1 + returns.mean())**252 - 1) * W)
        algorithm.Log("annual_return "+ str(annual_return))
        # annualized variance of return
        annual_variance = dot(W.T, dot(returns.cov()*252, W))
        # the risk aversion coefficient
        risk_aversion = (annual_return - self.risk_free_rate ) / annual_variance
        
        algorithm.Log("risk_aversion " + str(risk_aversion))
        # the covariance matrix of excess returns (N x N matrix)
        cov = returns.cov()*252
        
        # the implied excess equilibrium return Vector (N x 1 column vector)
        equilibrium_return = dot(dot(risk_aversion, cov), W)
        
        algorithm.Log("equilibrium_return " + str(equilibrium_return))
        
        # generate the link matrix of views P
        view = {}
        insights = sorted(insights, key = lambda x: x.SourceModel)
        for model, group in groupby(insights, lambda x: x.SourceModel):
            view[model] = {str(symbol): 0 for symbol in symbols}
            for insight in group:
                view[model][str(insight.Symbol)] = insight.Direction 
        view = pd.DataFrame(view).T
        algorithm.Log("This is view "+str(view.values))
        
        # normalize the view matrix by row
        up_view = view[view>0].fillna(0) 
        down_view = view[view<0].fillna(0)
        normalize_up_view = up_view.apply(lambda x: x/sum(x), axis=1).fillna(0)
        normalize_down_view = down_view.apply(lambda x: -x/sum(x), axis=1).fillna(0)
        # link matrix: a matrix that identifies the assets involved in the views (K x N matrix)
        P = normalize_up_view + normalize_down_view
        # drop the rows with all zero views (flat direction)
        P = P[~(P == 0).all(axis=1)]   
        algorithm.Log("This is P "+str(P.values))
       
        # the estimated return vector for every different view (K x 1 column vector)
        Q = []
        for model, group in groupby(insights, lambda x: x.SourceModel):
            if model in P.index:
                 Q.append(sum([P.loc[model][str(insight.Symbol)]*insight.Magnitude for insight in group]))
        P = np.array(P)        
        Q = np.array(Q)
        algorithm.Log("This is Q "+str(Q))
        # The diagonal covariance matrix of error terms from the expressed views
        omega = dot(dot(dot(self.tau, P), cov), transpose(P))
        algorithm.Log("This is omega "+str(omega))
        if np.linalg.det(omega) == 0:
            expected_return = equilibrium_return
            algorithm.Log("expected return is equilibrium_return "+str(expected_return))
           
        else:
            A = inv(dot(self.tau, cov)) + dot(dot(np.transpose(P), inv(omega)), P)
            B = dot(inv(dot(self.tau, cov)), equilibrium_return) + dot(dot(np.transpose(P), inv(omega)), Q)
            # The new combined expected return vector
            expected_return = dot(inv(A), B)
            algorithm.Log("expected return "+str(expected_return))

        # The optimization method processes the data frame
        opt, weights = self.maximum_sharpe_ratio(expected_return, returns)
        algorithm.Log("opt "+str(weights))
        
        # Create portfolio targets from the specified insights
        for insight in insights:
            weight = weights[str(insight.Symbol)]
            yield PortfolioTarget.Percent(algorithm, insight.Symbol, weight)


    def OnSecuritiesChanged(self, algorithm, changes):
        '''Event fired each time the we add/remove securities from the data feed
        Args:
            algorithm: The algorithm instance that experienced the change in securities
            changes: The security additions and removals from the algorithm'''

        # clean up data for removed securities
        for removed in changes.RemovedSecurities:
            self.pendingRemoval.append(removed.Symbol)
            symbolData = self.symbolDataDict.pop(removed.Symbol, None)

        for symbol in self.symbolDataDict.keys():
            self.symbolDataDict[symbol].Add(algorithm.Time, algorithm.Securities[symbol].Close)
        # initialize data for newly added securities
        symbols = [ x.Symbol for x in changes.AddedSecurities]
        history = algorithm.History(symbols, self.lookback, self.resolution)
        for symbol in symbols:
            if symbol not in self.symbolDataDict.keys():
                symbolData = SymbolData(symbol, self.lookback)
                self.symbolDataDict[symbol] = symbolData
                symbolData.WarmUpHisotryWindow(history.loc[str(symbol)])


    def maximum_sharpe_ratio(self, expected_return, returns):
        '''Maximum Sharpe Ratio optimization method'''

        # Objective function
        fun = lambda weights: -self.sharpe_ratio(expected_return, returns, weights)

        # Constraint #1: The weights can be negative, which means investors can short a security.
        constraints = [{'type': 'eq', 'fun': lambda w: np.sum(w) - 1}]

        size = returns.columns.size
        x0 = np.array(size * [1. / size])
        bounds = tuple((self.minimum_weight, self.maximum_weight) for x in range(size))

        opt = minimize(fun,                         # Objective function
                       x0,                          # Initial guess
                       method='SLSQP',              # Optimization method:  Sequential Least SQuares Programming
                       bounds = bounds,             # Bounds for variables 
                       constraints = constraints)   # Constraints definition

        weights = pd.Series(opt['x'], index = returns.columns)

        return opt, weights

    def sharpe_ratio(self, expected_return, returns, weights):
        annual_return = np.dot(np.matrix(expected_return), np.matrix(weights).T).item()
        annual_volatility = np.sqrt(np.dot(weights.T, np.dot(returns.cov(), weights)))
        return annual_return/annual_volatility


class SymbolData:
    '''Contains data specific to a symbol required by this model'''
    def __init__(self, symbol, lookback):
        self.Symbol = symbol
        self.Window = RollingWindow[IndicatorDataPoint](lookback)

    def WarmUpHisotryWindow(self, history):
        for tuple in history.itertuples():
            item = IndicatorDataPoint(self.Symbol, tuple.Index, float(tuple.close))
            self.Window.Add(item)
    
    def Add(self, time, value):
        item = IndicatorDataPoint(self.Symbol, time, value)
        self.Window.Add(item)

    def PriceSeries(self):
        data = [float(x.Value) for x in self.Window]
        index = [x.EndTime for x in self.Window]
        return pd.Series(data, index=index)
from clr import AddReference
AddReference("System")
AddReference("QuantConnect.Algorithm")
AddReference("QuantConnect.Common")
AddReference("QuantConnect.Indicators")

from System import *
from QuantConnect import *
from QuantConnect.Util import PythonUtil
from QuantConnect.Orders import *
from QuantConnect.Indicators import *
from QuantConnect.Algorithm import *
from QuantConnect.Algorithm.Framework import *
from QuantConnect.Algorithm.Framework.Execution import *
from QuantConnect.Algorithm.Framework.Portfolio import *
from QuantConnect.Algorithm.Framework.Risk import *
from QuantConnect.Algorithm.Framework.Selection import *
from QuantConnect.Algorithm.Framework.Alphas import *
from QuantConnect.Data.UniverseSelection import *
from datetime import timedelta
import numpy as np

class CustomFundamentalPortfolioSelectionModel:

    def CreateUniverses(self, algorithm):
        algorithm.AddUniverse(self.SelectCoarse, self.SelectFine)
        return []

    def SelectCoarse(self, coarse):
        sortedByDollarVolume = sorted(coarse, key=lambda c: c.DollarVolume, reverse=True)
        return [ x.Symbol for x in sortedByDollarVolume[:20] ]

    def SelectFine(self, fine):
        sortedByEarningYield = sorted(fine, key=lambda f: f.ValuationRatios.EarningYield, reverse=True)
        # algorithm.Log("universe "+str([x.Symbol.Value for x in sortedByEarningYield[:5]))
        return [ x.Symbol for x in sortedByEarningYield[:5] ]
# QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
# Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from clr import AddReference
AddReference("QuantConnect.Algorithm.Framework")
AddReference("QuantConnect.Indicators")
AddReference("QuantConnect.Common")

from QuantConnect import *
from QuantConnect.Indicators import *
from QuantConnect.Algorithm.Framework.Alphas import *
from datetime import timedelta

class HistoricalReturnsAlphaModel:
    '''Uses Historical returns to create insights.'''

    def __init__(self, *args, **kwargs):
        '''Initializes a new default instance of the HistoricalReturnsAlphaModel class.
        Args:
            lookback(int): Historical return lookback period
            resolution: The resolution of historical data'''
        self.lookback = kwargs['lookback'] if 'lookback' in kwargs else 1
        self.resolution = kwargs['resolution'] if 'resolution' in kwargs else Resolution.Daily
        self.predictionInterval = Time.Multiply(Extensions.ToTimeSpan(self.resolution), self.lookback)
        self.symbolDataBySymbol = {}

    def Update(self, algorithm, data):
        '''Updates this alpha model with the latest data from the algorithm.
        This is called each time the algorithm receives data for subscribed securities
        Args:
            algorithm: The algorithm instance
            data: The new data available
        Returns:
            The new insights generated'''
        insights = []

        for symbol, symbolData in self.symbolDataBySymbol.items():
            if symbolData.CanEmit:

                direction = InsightDirection.Flat
                magnitude = symbolData.Return
                if magnitude > 0: direction = InsightDirection.Up
                if magnitude < 0: direction = InsightDirection.Down

                insights.append(Insight.Price(symbol, self.predictionInterval, direction, magnitude, None))

        return insights

    def OnSecuritiesChanged(self, algorithm, changes):
        '''Event fired each time the we add/remove securities from the data feed
        Args:
            algorithm: The algorithm instance that experienced the change in securities
            changes: The security additions and removals from the algorithm'''

        # clean up data for removed securities
        for removed in changes.RemovedSecurities:
            symbolData = self.symbolDataBySymbol.pop(removed.Symbol, None)
            if symbolData is not None:
                symbolData.RemoveConsolidators(algorithm)

        # initialize data for added securities
        symbols = [ x.Symbol for x in changes.AddedSecurities ]
        history = algorithm.History(symbols, self.lookback, self.resolution)
        if history.empty: return

        tickers = history.index.levels[0]
        for ticker in tickers:
            symbol = SymbolCache.GetSymbol(ticker)

            if symbol not in self.symbolDataBySymbol:
                symbolData = SymbolData(symbol, self.lookback)
                self.symbolDataBySymbol[symbol] = symbolData
                symbolData.RegisterIndicators(algorithm, self.resolution)
                symbolData.WarmUpIndicators(history.loc[ticker])


class SymbolData:
    '''Contains data specific to a symbol required by this model'''
    def __init__(self, symbol, lookback):
        self.Symbol = symbol
        self.ROC = RateOfChange('{}.ROC({})'.format(symbol, lookback), lookback)
        self.Consolidator = None
        self.previous = 0

    def RegisterIndicators(self, algorithm, resolution):
        self.Consolidator = algorithm.ResolveConsolidator(self.Symbol, resolution)
        algorithm.RegisterIndicator(self.Symbol, self.ROC, self.Consolidator)

    def RemoveConsolidators(self, algorithm):
        if self.Consolidator is not None:
            algorithm.SubscriptionManager.RemoveConsolidator(self.Symbol, self.Consolidator)

    def WarmUpIndicators(self, history):
        for tuple in history.itertuples():
            self.ROC.Update(tuple.Index, tuple.close)

    @property
    def Return(self):
        return float(self.ROC.Current.Value)

    @property
    def CanEmit(self):
        if self.previous == self.ROC.Samples:
            return False

        self.previous = self.ROC.Samples
        return self.ROC.IsReady

    def __str__(self, **kwargs):
        return '{}: {:.2%}'.format(self.ROC.Name, (1 + self.Return)**252 - 1)
# QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
# Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from clr import AddReference
AddReference("QuantConnect.Common")
AddReference("QuantConnect.Algorithm.Framework")
AddReference("QuantConnect.Indicators")

from QuantConnect import *
from QuantConnect.Indicators import *
from QuantConnect.Algorithm.Framework.Alphas import *


class MacdAlphaModel:
    '''Defines a custom alpha model that uses MACD crossovers. The MACD signal line
    is used to generate up/down insights if it's stronger than the bounce threshold.
    If the MACD signal is within the bounce threshold then a flat price insight is returned.'''

    def __init__(self,
                 fastPeriod = 12,
                 slowPeriod = 26,
                 signalPeriod = 9,
                 movingAverageType = MovingAverageType.Exponential,
                 resolution = Resolution.Daily):
        ''' Initializes a new instance of the MacdAlphaModel class
        Args:
            fastPeriod: The MACD fast period
            slowPeriod: The MACD slow period</param>
            signalPeriod: The smoothing period for the MACD signal
            movingAverageType: The type of moving average to use in the MACD'''
        self.fastPeriod = fastPeriod
        self.slowPeriod = slowPeriod
        self.signalPeriod = signalPeriod
        self.movingAverageType = movingAverageType
        self.resolution = resolution
        self.insightPeriod = Time.Multiply(Extensions.ToTimeSpan(resolution), fastPeriod)
        self.bounceThresholdPercent = 0.01
        self.symbolData = {};

        resolutionString = Extensions.GetEnumString(resolution, Resolution)
        movingAverageTypeString = Extensions.GetEnumString(movingAverageType, MovingAverageType)
        self.Name = '{}({},{},{},{},{})'.format(self.__class__.__name__, fastPeriod, slowPeriod, signalPeriod, movingAverageTypeString, resolutionString)


    def Update(self, algorithm, data):
        ''' Determines an insight for each security based on it's current MACD signal
        Args:
            algorithm: The algorithm instance
            data: The new data available
        Returns:
            The new insights generated'''
        for key, sd in self.symbolData.items():
            if sd.Security.Price == 0:
                continue

            direction = InsightDirection.Flat
            normalized_signal = sd.MACD.Signal.Current.Value / sd.Security.Price
            # algorithm.Log(str(algorithm.Time) + ":" + str(normalized_signal) + ":" + str(sd.MACD.Signal.Current.Value))
            if normalized_signal > self.bounceThresholdPercent:
                direction = InsightDirection.Up
            elif normalized_signal < -self.bounceThresholdPercent:
                direction = InsightDirection.Down

            # ignore signal for same direction as previous signal
            if direction == sd.PreviousDirection:
                continue

            insight = Insight.Price(sd.Security.Symbol, self.insightPeriod, direction)
            sd.PreviousDirection = insight.Direction
            yield insight


    def OnSecuritiesChanged(self, algorithm, changes):
        '''Event fired each time the we add/remove securities from the data feed.
        This initializes the MACD for each added security and cleans up the indicator for each removed security.
        Args:
            algorithm: The algorithm instance that experienced the change in securities
            changes: The security additions and removals from the algorithm'''
        for added in changes.AddedSecurities:
            self.symbolData[added.Symbol] = SymbolData(algorithm, added, self.fastPeriod, self.slowPeriod, self.signalPeriod, self.movingAverageType, self.resolution)

        for removed in changes.RemovedSecurities:
            data = self.symbolData.pop(removed.Symbol, None)
            if data is not None:
                # clean up our consolidator
                algorithm.SubscriptionManager.RemoveConsolidator(removed.Symbol, data.Consolidator)

class SymbolData:
    def __init__(self, algorithm, security, fastPeriod, slowPeriod, signalPeriod, movingAverageType, resolution):
        self.Security = security
        self.MACD = MovingAverageConvergenceDivergence(fastPeriod, slowPeriod, signalPeriod, movingAverageType)

        self.Consolidator = algorithm.ResolveConsolidator(security.Symbol, resolution)
        algorithm.RegisterIndicator(security.Symbol, self.MACD, self.Consolidator)

        self.PreviousDirection = None