Overall Statistics
Total Trades
18
Average Win
3.31%
Average Loss
-0.91%
Compounding Annual Return
0.008%
Drawdown
6.200%
Expectancy
0.031
Net Profit
0.014%
Sharpe Ratio
0.02
Probabilistic Sharpe Ratio
5.822%
Loss Rate
78%
Win Rate
22%
Profit-Loss Ratio
3.64
Alpha
0
Beta
0
Annual Standard Deviation
0.038
Annual Variance
0.001
Information Ratio
0.02
Tracking Error
0.038
Treynor Ratio
0
Total Fees
$158.78
Estimated Strategy Capacity
$2000000.00
Lowest Capacity Asset
TLT SGNKIKYGE9NP
Portfolio Turnover
2.46%
# region imports
from AlgorithmImports import *
# endregion

class FocusedFluorescentOrangeHyena(QCAlgorithm):

    def Initialize(self):
        
        self.SetStartDate(2004, 8, 26)
        self.SetEndDate(2006, 6, 5) 
        self.SetCash(100000)
        self.UniverseSettings.Resolution = Resolution.Daily
        
        self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Cash)
        self.SetUniverseSelection(SingleTickerSelectionModel())

        self.AddAlpha(MacroTrendSMAAlphaModel(window=100))
        self.SetPortfolioConstruction(MyPortfolioConstructionModel())
        self.SetExecution(MyAwesomeImmediateExecutionModel())
        self.AddRiskManagement(NullRiskManagementModel())



class SingleTickerSelectionModel(ManualUniverseSelectionModel):
    
    def __init__(self):
        tickers = ['TLT']
        symbols = [Symbol.Create(ticker, SecurityType.Equity, Market.USA) for ticker in tickers]
        super().__init__(symbols)


class MacroTrendSMAAlphaModel(AlphaModel):

    def __init__(self, window, resolution = Resolution.Daily):
        """Initialises a new instance of the MacroTrendSMAAlphaModel """
        self.window = window
        self.resolution = resolution
        self.predictionInterval = Time.Multiply(Extensions.ToTimeSpan(resolution), window)
        self.symbolDataBySymbol = {}
        resolutionString = Extensions.GetEnumString(resolution, Resolution)
        self.Name = f'{self.__class__.__name__}({window},{resolutionString})'
        
    def Update(self, algorithm, data):
        """
        Updates the alpha model with the latest data from the algorithm.
        This is called 
        each time the algorithm receives 
        new data for the subscribed securities
        """      
        insights = []
        for symbol, symbolData in self.symbolDataBySymbol.items():            
            if symbolData.IsReady:               
                if symbolData.lookback.IsReady:
                    if symbolData.TurningDown:
                        if symbolData.lookback[0].Price > symbolData.lookback[1].Price:
                            insights.append(Insight.Price(symbolData.Symbol, self.predictionInterval, InsightDirection.Up))
                            algorithm.Log(f'Insight Up: {algorithm.Time.date()} Prior Ind: {symbolData.lookback[1].Price:.3f}, Curr Ind: {symbolData.lookback[0].Price:.3f}')
        
                    elif symbolData.TurningUp:
                        if symbolData.lookback[0].Price < symbolData.lookback[1].Price:
                            insights.append(Insight.Price(symbolData.Symbol, self.predictionInterval, InsightDirection.Flat))
                            algorithm.Log(f'Insight Flat: {algorithm.Time.date()} Prior Ind: {symbolData.lookback[1].Price:.3f}, Curr Ind: {symbolData.lookback[0].Price:.3f}')

                    symbolData.TurningUp = symbolData.lookback[0].Price > symbolData.lookback[1].Price

        return insights

    def OnSecuritiesChanged(self, algorithm, changes):
        """
        Event is fired each time we add or remove securities from the data feed
        """
        for security in changes.AddedSecurities:
            symbolData = self.symbolDataBySymbol.get(security.Symbol)
            if symbolData is None:
                symbolData = SymbolData(security, self.window, algorithm, self.resolution)
                self.symbolDataBySymbol[security.Symbol] = symbolData
            else:
                # A security that was already intialised was re-added, reset the indicators
                symbolData.SMA.Reset()

        for security in changes.RemovedSecurities:
                    data = self.symbolDataBySymbol.pop(security.Symbol, None)
                    if data is not None:
                        # clean up our consolidators
                        data.RemoveConsolidators()

class SymbolData:
    """
    Contains data specific to a symbol required by this model
    """
    def __init__(self, security, window, algorithm, resolution):
        self.Security = security
        self.Symbol = security.Symbol
        self.algorithm = algorithm
        # rolling window to store the SMA values below
        self.lookback = RollingWindow[IndicatorDataPoint](2)
        self.SMAConsolidator = algorithm.ResolveConsolidator(security.Symbol, resolution)
        algorithm.SubscriptionManager.AddConsolidator(security.Symbol, self.SMAConsolidator)

        self.SMA = SimpleMovingAverage(security.Symbol, window)
        # This line is causing the problem I think. I want to instuct the auto updating of the lookback to hold the SMA values       
        self.SMA.Updated += (lambda sender, updated: self.lookback.Add(updated))
        algorithm.RegisterIndicator(security.Symbol, self.SMA, self.SMAConsolidator);
        algorithm.WarmUpIndicator(security.Symbol, self.SMA, resolution);
        
        # True if SMA indicator is turning up and false if not
        self.TurningUp = False

    def RemoveConsolidator(self):
        self.algorithm.SubscriptionManager.RemoveConsolidator(self.Security.Symbol, self.SMAConsolidator)

    def IsReady(self):
        return self.SMA.IsReady
      
    @property
    def TurningDown(self):
        return not self.TurningUp



class MyPortfolioConstructionModel(PortfolioConstructionModel):
    '''Provides an implementation of IPortfolioConstructionModel that gives equal weighting to all securities.
    The target percent holdings of each security is 1/N where N is the number of securities.
    For insights of direction InsightDirection.Up, long targets are returned and
    for insights of direction InsightDirection.Down, short targets are returned.'''

    def __init__(self, rebalance = None, portfolioBias = PortfolioBias.Long):
        '''Initialize a new instance of EqualWeightingPortfolioConstructionModel
        Args:
            rebalance: Rebalancing parameter. If it is a timedelta, date rules or Resolution, it will be converted into a function.
                              If None will be ignored.
                              The function returns the next expected rebalance time for a given algorithm UTC DateTime.
                              The function returns null if unknown, in which case the function will be called again in the
                              next loop. Returning current time will trigger rebalance.
            portfolioBias: Specifies the bias of the portfolio (Short, Long/Short, Long)'''
        super().__init__()
        self.portfolioBias = portfolioBias

        # If the argument is an instance of Resolution or Timedelta
        # Redefine rebalancingFunc
        rebalancingFunc = rebalance
        if isinstance(rebalance, int):
            rebalance = Extensions.ToTimeSpan(rebalance)
        if isinstance(rebalance, timedelta):
            rebalancingFunc = lambda dt: dt + rebalance
        if rebalancingFunc:
            self.SetRebalancingFunc(rebalancingFunc)

    def DetermineTargetPercent(self, activeInsights):
        '''Will determine the target percent for each insight
        Args:
            activeInsights: The active insights to generate a target for'''
        result = {}

        # give equal weighting to each security
        count = sum(x.Direction != InsightDirection.Flat and self.RespectPortfolioBias(x) for x in activeInsights)
        percent = 0 if count == 0 else 1.0 / count
        for insight in activeInsights:
            result[insight] = (insight.Direction if self.RespectPortfolioBias(insight) else InsightDirection.Flat) * percent
        return result

    def RespectPortfolioBias(self, insight):
        '''Method that will determine if a given insight respects the portfolio bias
        Args:
            insight: The insight to create a target for
        '''
        return self.portfolioBias == PortfolioBias.LongShort or insight.Direction == self.portfolioBias




class MyAwesomeImmediateExecutionModel(ExecutionModel):
    '''Provides an implementation of IExecutionModel that immediately submits market orders to achieve the desired portfolio targets'''

    def __init__(self):
        '''Initializes a new instance of the ImmediateExecutionModel class'''
        self.targetsCollection = PortfolioTargetCollection()

    def Execute(self, algorithm, targets):
        '''Immediately submits orders for the specified portfolio targets.
        Args:
            algorithm: The algorithm instance
            targets: The portfolio targets to be ordered'''

        # for performance we check count value, OrderByMarginImpact and ClearFulfilled are expensive to call
        self.targetsCollection.AddRange(targets)
        if not self.targetsCollection.IsEmpty:
            for target in self.targetsCollection.OrderByMarginImpact(algorithm):
                security = algorithm.Securities[target.Symbol]
                # calculate remaining quantity to be ordered
                quantity = OrderSizing.GetUnorderedQuantity(algorithm, target, security)
                if quantity != 0:
                    aboveMinimumPortfolio = BuyingPowerModelExtensions.AboveMinimumOrderMarginPortfolioPercentage(security.BuyingPowerModel, security, quantity, algorithm.Portfolio, algorithm.Settings.MinimumOrderMarginPortfolioPercentage)
                    if aboveMinimumPortfolio:
                        algorithm.MarketOrder(security, quantity)
                    elif not PortfolioTarget.MinimumOrderMarginPercentageWarningSent:
                        # will trigger the warning if it has not already been sent
                        PortfolioTarget.MinimumOrderMarginPercentageWarningSent = False

            self.targetsCollection.ClearFulfilled(algorithm)