Overall Statistics
from Timeframes.Multiple import MultipleTimeframes
from Risk.CompositeRiskManagementModel import CompositeRiskManagementModel
from Risk.TrailingStopRiskManagementModel import TrailingStopRiskManagementModel
from Risk.MaximumDrawdownPercentPerSecurity import MaximumDrawdownPercentPerSecurity
from Portfolio.MeanVarianceOptimizationPortfolioConstructionModel import MeanVarianceOptimizationPortfolioConstructionModel

class StandardAlgorithm(QCAlgorithmFramework):
    
    
    def __init__(self):
        self.Indicators = dict()
        self.Charts = dict()
        self.Consolidators = dict()
        
        self.Alphas = []
        self.SetBroker()
        self.SetRisk()
        self.SetTiming()
        self.SetBacktesting()
        self.SetSecurityInitializer(self.SetSecurities)
    
    
    def SetAlphas(self, *alphas):
        self.Alphas = alphas
        self.SetAlpha(CompositeAlphaModel(*self.Alphas))
        self.SetWarmUp(max(map(lambda alpha: alpha.Indicator.Warmup, self.Alphas)))
    
    
    def SetBroker(self):
        self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Margin)
    
    
    def SetSecurities(self, security):
        security.SetDataNormalizationMode(DataNormalizationMode.Raw)
    
    
    def OnSecuritiesChanged(self, changes):
        for security in changes.AddedSecurities:
            for minutes, timeframe in self.Consolidators.items():
                self.SubscriptionManager.AddConsolidator(security.Symbol.Value, timeframe)
        
        for security in changes.RemovedSecurities:
            for timeframe in self.Consolidators[symbol].values():
                self.SubscriptionManager.RemoveConsolidator(security.Symbol.Value, timeframe)
    
    
    def SetRisk(self):
        trailing_stop = 0.10
        max_drawdown = 0.05
        self.SetRiskManagement(CompositeRiskManagementModel(TrailingStopRiskManagementModel(trailing_stop), MaximumDrawdownPercentPerSecurity(max_drawdown)))
        self.SetPortfolioConstruction(MeanVarianceOptimizationPortfolioConstructionModel())
        self.Allocate = 0.25


    def SetTiming(self):
        self.SetTimeZone(TimeZones.Toronto)
        self.UniverseSettings.Resolution = Resolution.Minute
        self.Consolidators = MultipleTimeframes(lambda minutes: TradeBarConsolidator(TimeSpan.FromMinutes(minutes)))
        
        # Register consolidators with subscription data
        for timeframe in self.Consolidators.values():
            timeframe.DataConsolidated += self.OnDataConsolidated
    
    
    def OnDataConsolidated(self, sender, slice):
        if self.IsWarmingUp: return
        symbol = str(slice.get_Symbol())
        
    
    def SetBacktesting(self):
        self.SetStartDate(2019, 2, 11)
        self.SetEndDate(2019, 2, 15)
        self.SetCash(20000)
from Timeframes.Multiple import MultipleTimeframes
from Alphas.StandardAlphaModel import *
from Indicators.EhlerRsiDiscriminator import EhlerRsiDiscriminator

class RsiAlphaModel(StandardAlphaModel):
    Name = "RSI Alpha"
    Indicator = EhlerRsiDiscriminator


    def Update(self, algorithm, slice):
        # Updates this alpha model with the latest data from the algorithm.
        # This is called each time the algorithm receives data for subscribed securities
        # Generate insights on the securities in universe.
        minutes = slice.Period.seconds / 60 if hasattr(slice, 'Period') else 1
        for symbol in slice.Keys:
            # algorithm.Debug(str(algorithm.Indicators[symbol][self.Indicator.Name][1].IsReady))
            pass
        insights = []
        # data.Period - contains the timestamp representing the consolidated timeframe the data applies to.
        # We need to introduce MTF logic in aggregating confidence. Should we be keeping RollingWindow data
        # in the AlphaModel
        # for underlying in data:
        #     c = self.TrendLimitedThresholdConfidence(data[underlying], 3)
        #     time_delta = minutes * self.TrendConfidence()
        #     insights.append( Insight(data[underlying], time_delta, InsightType.Price, InsightDirection.Up, confidence=c) )
        #     insights.append( Insight(data[underlying], time_delta, InsightType.Price, InsightDirection.Down, confidence=1-c) )
        
        return insights
from Timeframes.Multiple import MultipleTimeframes
class StandardAlphaModel(AlphaModel):
    
    
    def __init__(self, resolution = Resolution.Daily):
        self.resolution = resolution
        self.symbolData = {}
        self.Charts = {}

    
    # S curve starting at 1 heading towards 0
    def TrendConfidence(self, x):
        return -0.5 * (1 + math.sin((math.pi * x) - (math.pi / 2))) + 1

    
    # Equidistant thresholds within 0-1 range
    # (ex: 3 means one at 16.7%, 50% and )
    def ThresholdConfidence(self, x, t=3):
        return -abs(math.sin(t * x * math.pi)) + 1
    
    
    # Limit the threshold confidence to the trend confidence
    def TrendLimitedThresholdConfidence(self, x, t=3):
        trend_confidence = this.TrendConfidence(x)
        threshold_confidence = this.ThresholdConfidence(x, t)
        return threshold_confidence if threshold_confidence <= trend_confidence else trend_confidence
    
    
    def Update(self, algorithm, data):
        for symbol, sd in self.symbolData.items():
            for resolution, indicator in sd.items():
                algorithm.Plot(sd.Chart.Name, Extensions.GetEnumString(resolution, Resolution), indicator.Value)
            
            if sd.Security.Price == 0:
                continue

            # direction = InsightDirection.Flat
            # normalized_signal = sd.MACD.Signal.Current.Value / sd.Security.Price

            # 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
            # insights.append(insight)
    
    
    def OnSecuritiesChanged(self, algorithm, changes):
        for added in changes.AddedSecurities:
            self.symbolData[added.Symbol] = SymbolData(self, algorithm, added)
            
        for removed in changes.RemovedSecurities:
            del self.symbolData[removed.Symbol]
            for resolution in algorithm.Consolidators.values():
                # Clean up our consolidator
                algorithm.SubscriptionManager.RemoveConsolidator(removed.Symbol, resolution)


class SymbolData:
    def __init__(self, alpha, algorithm, security):
        self.Security = security
        self.Timeframes = {}
        
        self.Chart = Chart('[{}] {}'.format(security.Symbol, alpha.Indicator.__class__.__name__), ChartType.Stacked)
        # for minutes, resolution in algorithm.Consolidators.items():
        #     self.Timeframes[resolution] = alpha.Indicator()
        #     self.Chart.AddSeries(Series(Extensions.GetEnumString(resolution, Resolution), SeriesType.Line))
        #     algorithm.RegisterIndicator(security.Symbol, self.Timeframes[resolution], resolution)
        algorithm.AddChart(self.Chart)
import math
import numpy as np
from Indicators.StandardIndicator import StandardIndicator

def EhlerRSI(symbol, **kwargs):
    symbol_id = symbol if isinstance(symbol, str) else symbol.Value
    name = CreateIndicatorName(symbol, "[{0}] {1}".format(symbol_id, EhlerRsiDiscriminator.Name), kwargs['resolution'])
    indicator = EhlerRsiDiscriminator(name, **kwargs);
    RegisterIndicator(symbol_id, indicator, kwargs['resolution']);
    return indicator


class EhlerRsiDiscriminator(StandardIndicator):
    Warmup = 7
    Name = "Ehler's RSI + Discriminator"
    
    
    def __init__(self, double_smoothing=True, roofing_upper=48, roofing_lower=10, overbought=70, oversold=30):
        super(EhlerRsiDiscriminator, self).__init__()
        self.DoubleSmoothing = double_smoothing
        self.RoofingUpper = roofing_upper
        self.RoofingLower = roofing_lower
        self.Overbought = overbought
        self.Oversold = oversold
        self.Source = RollingWindow[Decimal](self.Warmup)
        self.Filtr = RollingWindow[float](3)
        self.Highpass = RollingWindow[float](3)
        self.Period = RollingWindow[Decimal](2)
        self.SmoothedPeriod = RollingWindow[Decimal](2)
        self.Smooth = RollingWindow[Decimal](7)
        self.Detrend = RollingWindow[Decimal](7)
        self.HL2 = RollingWindow[Decimal](4)
        self.I1 = RollingWindow[Decimal](7)
        self.I2 = RollingWindow[Decimal](7)
        self.Q1 = RollingWindow[Decimal](7)
        self.Q2 = RollingWindow[Decimal](7)
        self.Re = RollingWindow[Decimal](2)
        self.Im = RollingWindow[Decimal](2)
        self.Irma = 0
        self.RoofedSource = RollingWindow[float](2)
        
        # Start with history
        self.FillWindows(self.Filtr, self.Highpass, self.Period, self.SmoothedPeriod, self.Smooth, self.Detrend, self.I1, self.I2, self.Q1, self.Q2, self.Re, self.Im, self.RoofedSource)


    def __repr__(self):
        return "{0} -> IsReady: {1}. Value: {2}".format(self.Name, self.IsReady, self.Value)
    
    
    # Rolling Moving Average (or Wells Wilders MA)
    def WWma(self, src, l):
        l = 1 if l == 0 else l
        self.Irma = (self.Irma * (l - 1) + src) / l
        return self.Irma
    
    
    # RSI function.
    def EhRSI(self, l):
        change = self.RoofedSource[0] - self.RoofedSource[1]
        up = self.WWma(max(change, 0), l)
        down = self.WWma(-min(change, 0), l)
        return 100 if down == 0 else 0 if up == 0 else 100 - (100 / (1 + up / down))
    
    
    def EhlersSuperSmootherFilter(self, lower):
        a1 = math.exp(-math.pi * math.sqrt(2) / lower)
        coeff3 = -pow(a1, 2)
        coeff2 = 2 * a1 * math.cos(math.sqrt(2) * math.pi / lower)
        coeff1 = 1 - coeff2 - coeff3
        self.Filtr.Add(coeff1 * (self.Highpass[0] + self.Highpass[1]) / 2 + coeff2 * self.Filtr[1] + coeff3 * self.Filtr[2])
        return self.Filtr[0]
        

    def EhlersRoofingFilter(self, upper, lower):
        alpha1 = (math.cos(math.sqrt(2) * math.pi / upper) + math.sin(math.sqrt(2) * math.pi / upper) - 1) / math.cos(math.sqrt(2) * math.pi / upper)
        self.Highpass.Add(pow(1 - alpha1 / 2, 2) * (self.Source[0] - 2 * self.Source[1] + self.Source[2]) + 2 * (1 - alpha1) * self.Highpass[1] - pow(1 - alpha1, 2) * self.Highpass[2])
        return self.EhlersSuperSmootherFilter(lower) if self.DoubleSmoothing else self.Highpass[0]
    
    
    def ComputeNextValue(self, data):
        self.Source.Add(data.Close)
        self.HL2.Add((data.High + data.Low) / 2)
        self.IsReady = self.Source.IsReady
        if not self.IsReady: return
        
        #
        # --- Start the Homodyne Discriminator Caculations
        #
        # Mutable Variables (non-series)
        C1 = 0.0962
        C2 = 0.5769
        Df = 0.5
        
        C3 = (self.Period[1] * 0.075 + 0.54)
        self.Smooth.Add(((self.HL2[0] * 4.0) + (self.HL2[1] * 3.0) + (self.HL2[2] * 2.0) + (self.HL2[3])) / 10.0)
        self.Detrend.Add((self.Smooth[0] * C1 + self.Smooth[2] * C2 - self.Smooth[4] * C2 - self.Smooth[6] * C1) * C3)
        
        # Compute InPhase and Quadrature components
        self.Q1.Add((self.Detrend[0] * C1 + self.Detrend[2] * C2 - self.Detrend[4] * C2 - self.Detrend[6] * C1) * C3)
        self.I1.Add(self.Detrend[3])
        
        # Advance Phase of I1 and Q1 by 90 degrees
        jI = (self.I1[0] * C1 + self.I1[2] * C2 - self.I1[4] * C2 - self.I1[6] * C1) * C3
        jQ = (self.Q1[0] * C1 + self.Q1[2] * C2 - self.Q1[4] * C2 - self.Q1[6] * C1) * C3
        
        # Smooth i and q components before applying discriminator
        self.I2.Add(0.2 * (self.I1[0] - jQ) + 0.8 * self.I2[1])
        self.Q2.Add(0.2 * (self.Q1[0] + jI) + 0.8 * self.Q2[1])
        
        # Extract Homodyne Discriminator
        Re = self.I2[0] * self.I2[1] + self.Q2[0] * self.Q2[1]
        Im = self.I2[0] * self.Q2[1] - self.Q2[0] * self.I2[1]
        self.Re.Add(0.2 * Re + 0.8 * self.Re[1])
        self.Im.Add(0.2 * Im + 0.8 * self.Im[1])
        
        Dp = 6.28318 / math.atan(self.Im[0] / self.Re[0]) if (self.Re[0] != 0 and self.Im[0] != 0) else 0
        II = self.Period[1]
        Dp = max(max(min(min(Dp, 1.5 * II), 50), 0.6667 * II), 6)
        self.Period.Add(Dp * 0.2 + self.Period[1] * 0.8)
        self.SmoothedPeriod.Add(0.33 * self.Period[0] + self.SmoothedPeriod[1] * 0.67)
        
        RsiPeriod = round((self.SmoothedPeriod[0] * Df) - 1)
        self.RoofedSource.Add(self.EhlersRoofingFilter(self.RoofingUpper, self.RoofingLower))
        self.Value = self.EhRSI(RsiPeriod)
        return self.Value
class StandardIndicator:
    Name = "Null Indicator"
    
    def __init__(self):
        self.Value = 0
        self.IsReady = False
    
    
    def FillWindows(self, *args):
        for window in args:
            for i in range(window.Size):
                window.Add(0)
    
    
    # @classmethod
    def ChartName(self, symbol):
        return "[{0}] {1}".format(symbol, self.Name)
from collections import OrderedDict
class MultipleTimeframes(OrderedDict):
    def __init__(self, proc = lambda:None):
        timeframes = [1, 5, 15, 30, 60, 240, 390, 1950]
        super(MultipleTimeframes, self).__init__(dict(zip(timeframes, [proc(x) for x in timeframes])))
from Algorithms.StandardAlgorithm import StandardAlgorithm
from Alphas.RsiAlphaModel import RsiAlphaModel
from Execution.StandardDeviationExecutionModel import StandardDeviationExecutionModel

class MainAlgorithm(StandardAlgorithm):


    def Initialize(self):
        super(MainAlgorithm, self).__init__()
        
        self.SetExecution(StandardDeviationExecutionModel(60, 2, Resolution.Minute))
        self.SetAlphas(RsiAlphaModel())
        
        # Securities traded
        symbols = [ Symbol.Create("CRON", SecurityType.Equity, Market.USA) ]
        self.SetUniverseSelection( ManualUniverseSelectionModel(symbols) )