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
Probabilistic 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
3.413
Tracking Error
0.191
Treynor Ratio
0
Total Fees
$0.00
Estimated Strategy Capacity
$0
Lowest Capacity Asset
# 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 AlgorithmImports import *
from QuantConnect.Logging import *
from enum import Enum

class RsiAlphaModel(AlphaModel):
    Name = "RsiAlphaModel"
    '''Uses Wilder's RSI to create insights.
    Using default settings, a cross over below 30 or above 70 will trigger a new insight.'''

    def __init__(self,
                 period = 14,
                 resolution = Resolution.Minute):
        '''Initializes a new instance of the RsiAlphaModel class
        Args:
            period: The RSI indicator period'''
        self.period = period
        self.resolution = resolution
        self.insightPeriod = Time.Multiply(Extensions.ToTimeSpan(resolution), period)
        self.symbolDataBySymbol ={}

        resolutionString = Extensions.GetEnumString(resolution, Resolution)
        self.Name = '{}({},{})'.format(self.__class__.__name__, period, resolutionString)

        self.Log(f'Alpha: initialize()')

    def Update(self, algorithm: QCAlgorithm, data: Slice) -> List[Insight]:
        '''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'''

        self.algorithm.Log(f'Alpha: update()')

        insights = []
        for symbol, symbolData in self.symbolDataBySymbol.items():
            rsi = symbolData.RSI
            previous_state = symbolData.State
            state = self.GetState(rsi, previous_state)

            if state != previous_state and rsi.IsReady:
                if state == State.TrippedLow:
                    insights.append(Insight.Price(symbol, self.insightPeriod, InsightDirection.Up))
                if state == State.TrippedHigh:
                    insights.append(Insight.Price(symbol, self.insightPeriod, InsightDirection.Down))

            symbolData.State = state

        return insights


    def OnSecuritiesChanged(self, algorithm, changes):
        '''Cleans out old security data and initializes the RSI for any newly added securities.
        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
        symbols = [ x.Symbol for x in changes.RemovedSecurities ]
        if len(symbols) > 0:
            for subscription in algorithm.SubscriptionManager.Subscriptions:
                if subscription.Symbol in symbols:
                    self.symbolDataBySymbol.pop(subscription.Symbol, None)
                    subscription.Consolidators.Clear()

        # initialize data for added securities

        addedSymbols = [ x.Symbol for x in changes.AddedSecurities if x.Symbol not in self.symbolDataBySymbol]
        if len(addedSymbols) == 0: return

        history = algorithm.History(addedSymbols, self.period, self.resolution)

        for symbol in addedSymbols:
            rsi = algorithm.RSI(symbol, self.period, MovingAverageType.Wilders, self.resolution)

            if not history.empty:
                ticker = SymbolCache.GetTicker(symbol)

                if ticker not in history.index.levels[0]:
                    Log.Trace(f'RsiAlphaModel.OnSecuritiesChanged: {ticker} not found in history data frame.')
                    continue

                for tuple in history.loc[ticker].itertuples():
                    rsi.Update(tuple.Index, tuple.close)

            self.symbolDataBySymbol[symbol] = SymbolData(symbol, rsi)

            self.algorithm.Log(f'Alpha: OSC Sym,RSI = {symbol},{rsi}')

    def GetState(self, rsi, previous):
        ''' Determines the new state. This is basically cross-over detection logic that
        includes considerations for bouncing using the configured bounce tolerance.'''
        if rsi.Current.Value > 70:
            return State.TrippedHigh
        if rsi.Current.Value < 30:
            return State.TrippedLow
        if previous == State.TrippedLow:
            if rsi.Current.Value > 35:
                return State.Middle
        if previous == State.TrippedHigh:
            if rsi.Current.Value < 65:
                return State.Middle

        return previous


class SymbolData:
    '''Contains data specific to a symbol required by this model'''
    def __init__(self, symbol, rsi):
        self.Symbol = symbol
        self.RSI = rsi
        self.State = State.Middle


class State(Enum):
    '''Defines the state. This is used to prevent signal spamming and aid in bounce detection.'''
    TrippedLow = 0
    Middle = 1
    TrippedHigh = 2
# 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 AlgorithmImports import *

class SpreadExecutionModel(ExecutionModel):
    '''Execution model that submits orders while the current spread is tight.
       Note this execution model will not work using Resolution.Daily since Exchange.ExchangeOpen will be false, suggested resolution is Minute
    '''

    def __init__(self, acceptingSpreadPercent=0.005):
        '''Initializes a new instance of the SpreadExecutionModel class'''
        self.targetsCollection = PortfolioTargetCollection()
        
        # Gets or sets the maximum spread compare to current price in percentage.
        self.acceptingSpreadPercent = Math.Abs(acceptingSpreadPercent)

    def Execute(self, algorithm, targets):
        '''Executes market orders if the spread percentage to price is in desirable range.
       Args:
           algorithm: The algorithm instance
           targets: The portfolio targets'''
           
        # update the complete set of portfolio targets with the new targets
        self.targetsCollection.AddRange(targets)

        # for performance we check count value, OrderByMarginImpact and ClearFulfilled are expensive to call
        if self.targetsCollection.Count > 0:
            for target in self.targetsCollection.OrderByMarginImpact(algorithm):
                symbol = target.Symbol
                
                # calculate remaining quantity to be ordered
                unorderedQuantity = OrderSizing.GetUnorderedQuantity(algorithm, target)
                
                # check order entry conditions
                if unorderedQuantity != 0:
                    # get security information
                    security = algorithm.Securities[symbol]
                    if self.SpreadIsFavorable(security):
                        algorithm.MarketOrder(symbol, unorderedQuantity)

            self.targetsCollection.ClearFulfilled(algorithm)
            
    def SpreadIsFavorable(self, security):
        '''Determines if the spread is in desirable range.'''
        # Price has to be larger than zero to avoid zero division error, or negative price causing the spread percentage < 0 by error
        # Has to be in opening hours of exchange to avoid extreme spread in OTC period
        return security.Exchange.ExchangeOpen \
            and security.Price > 0 and security.AskPrice > 0 and security.BidPrice > 0 \
            and (security.AskPrice - security.BidPrice) / security.Price <= self.acceptingSpreadPercent
#region imports
from AlgorithmImports import *
from clr import AddReference
from clr import GetClrType as typeof
AddReference("System.Core")
AddReference("QuantConnect.Common")
AddReference("QuantConnect.Algorithm")
from System import *
from QuantConnect import *
from QuantConnect.Algorithm import QCAlgorithm
from QuantConnect import Symbol
from QuantConnect.Data.UniverseSelection import *
from QuantConnect.Securities.Option import OptionPriceModels
from datetime import datetime, timedelta
from io import StringIO
import pandas as pd
import math
from Selection.UniverseSelectionModel import UniverseSelectionModel
#endregion
    
class CustomSecurityInitializer(BrokerageModelSecurityInitializer):
    def __init__(self, brokerage_model, security_seeder, algorithm):
        super().__init__(brokerage_model, security_seeder)
        self.algorithm = algorithm
        self.period = self.algorithm.period + 1

    def Initialize(self, security):
        if security.Type == SecurityType.Base:
            return

        if security.Type == SecurityType.Equity:
            security.SetDataNormalizationMode(DataNormalizationMode.Raw)
            security.VolatilityModel = StandardDeviationOfReturnsVolatilityModel(self.period)
            history = self.algorithm.History(security.Symbol, self.period, Resolution.Daily)
            #self.algorithm.Log(f'{self.algorithm.Time}| CSI: {security} | {security.Type}')            
            for index, row in history.iterrows():
                trade_bar = TradeBar(index[1], security.Symbol, row.open, row.high, row.low, row.close, row.volume)    
                security.VolatilityModel.Update(security, trade_bar)

        if security.Type == SecurityType.Option:
            security.PriceModel = OptionPriceModels.CrankNicolsonFD()
            self.algorithm.security = security
            

        # The superclass method will get the last know prices            
        super().Initialize(security)

        # Let's check out the prices
        message = f'{self.algorithm.Time} :: Security Initializer :: Symbol: {security.Symbol.Value} | Price: {security.Price} | Ask: {security.AskPrice} | Bid: {security.BidPrice}'
        if self.algorithm.LiveMode:
            self.algorithm.Log(message)
            self.algorithm.Log(f'{self.algorithm.Time}| CSI: {security} | {security.Type}')

#alertedOptions went here

#OnSecuritiesChanged went here
            

class OptionAlerts(PythonData):

    def __init__(self):
        self.count = 0
        self.last_date = datetime.min
        

    def GetSource(self, config, date, isLiveMode):
        url = "https://files.mailparser.io/d/lztyzgbf?dl=1" if isLiveMode else \
            "https://files.mailparser.io/d/ofsoxaee?dl=1"

        return SubscriptionDataSource(url, SubscriptionTransportMedium.RemoteFile)
    
    def Reader(self, config, line, date, isLiveMode):
        
        if not isLiveMode:
            # backtest gets data from csv file in Mailparser
            if not line.startswith('"Message ID"'):
                csv = line.split(';')
                alert = OptionAlerts()
                alert.Time = datetime.strptime(csv[11], '"%m-%d-%Y %H:%M:%S"')
                alert.EndTime = alert.Time + timedelta(minutes=1)
                alert["ID"] = str(csv[0])
                alert["Size"] = int(csv[3])
                alert["Ticker"] = str(csv[4])
                alert["Strike"] = float(csv[5])
                alert["Right"] = str(csv[6])
                alert["Expiry"] = datetime.strptime(csv[7], "%Y-%m-%d")
                alert["TradePrice"] = float(csv[8])
                alert["Premium"] = float(csv[9])
                alert["Side"] = str(csv[10])
                
                if alert["Right"] == str("put"):
                    optionRight = 1
                if alert["Right"] == str("call"):
                    optionRight = 0
                
                underlying = Symbol.Create(alert["Ticker"], SecurityType.Equity, Market.USA)
                alert.Symbol = Symbol.CreateOption(underlying, Market.USA, 0, optionRight, alert["Strike"], alert["Expiry"])#  alert["Premium"]) using alias to store premium?
                
                return alert
            else: pass

        if self.last_date != date:
            # reset our counter for the new day
            self.last_date = date
            self.count = 0
        
        if not line.startswith('"Message ID"'):
            csv = line.split(';')
            alert = OptionAlerts()
            alert.Time = datetime.strptime(csv[1], '"%Y-%m-%d %H:%M:%S"')
            alert.EndTime = alert.Time + timedelta(minutes=1)
            alert["ID"] = str(csv[0])
            alert["Size"] = int(csv[3])
            alert["Ticker"] = str(csv[4])
            alert["Strike"] = float(csv[5])
            alert["Right"] = str(csv[6])
            alert["Expiry"] = datetime.strptime(csv[7], "%Y-%m-%d")
            alert["TradePrice"] = float(csv[8])
            alert["Premium"] = float(csv[9])
            alert["Side"] = str(csv[10])
            
            if alert["Right"] == str("put"):
                optionRight = 1
            if alert["Right"] == str("call"):
                optionRight = 0
            
            underlying = Symbol.Create(alert["Ticker"], SecurityType.Equity, Market.USA)
            alert.Symbol = Symbol.CreateOption(underlying, Market.USA, 0, optionRight, alert["Strike"], alert["Expiry"])#, alert["Premium"]) using alias to store premium?
            
            return alert
        else: pass


            
# 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 AlgorithmImports import *
from Portfolio.EqualWeightingPortfolioConstructionModel import *

class AccumulativeInsightPortfolioConstructionModel(EqualWeightingPortfolioConstructionModel):
    '''Provides an implementation of IPortfolioConstructionModel that allocates percent of account
    to each insight, defaulting to 3%.
    For insights of direction InsightDirection.Up, long targets are returned and
    for insights of direction InsightDirection.Down, short targets are returned.
    By default, no rebalancing shall be done.
    Rules:
        1. On active Up insight, increase position size by percent
        2. On active Down insight, decrease position size by percent
        3. On active Flat insight, move by percent towards 0
        4. On expired insight, and no other active insight, emits a 0 target'''

    def __init__(self,  rebalance = None, portfolioBias = PortfolioBias.LongShort, percent = 0.03):
        '''Initialize a new instance of AccumulativeInsightPortfolioConstructionModel
        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)
            percent: percent of portfolio to allocate to each position'''
        super().__init__(rebalance)
        self.portfolioBias = portfolioBias
        self.percent = abs(percent)
        self.sign = lambda x: -1 if x < 0 else (1 if x > 0 else 0)

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

        insights = sorted(self.InsightCollection.GetActiveInsights(self.currentUtcTime), key=lambda insight: insight.GeneratedTimeUtc)

        for insight in insights:
            targetPercent = 0
            if insight.Symbol in percentPerSymbol:
                targetPercent = percentPerSymbol[insight.Symbol]
                if insight.Direction == InsightDirection.Flat:
                    # We received a Flat
                    # if adding or subtracting will push past 0, then make it 0
                    if abs(targetPercent) < self.percent:
                        targetPercent = 0
                    else:
                        # otherwise, we flatten by percent
                        targetPercent += (-self.percent if targetPercent > 0 else self.percent)
            targetPercent += self.percent * insight.Direction

            # adjust to respect portfolio bias
            if self.portfolioBias != PortfolioBias.LongShort and self.sign(targetPercent) != self.portfolioBias:
                targetPercent = 0

            percentPerSymbol[insight.Symbol] = targetPercent

        return dict((insight, percentPerSymbol[insight.Symbol]) for insight in activeInsights)

    def CreateTargets(self, algorithm, insights):
        '''Create portfolio targets from the specified insights
        Args:
            algorithm: The algorithm instance
            insights: The insights to create portfolio targets from
        Returns:
            An enumerable of portfolio targets to be sent to the execution model'''
        self.currentUtcTime = algorithm.UtcTime
        return super().CreateTargets(algorithm, insights)
# 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 AlgorithmImports import *

class MaximumDrawdownPercentPerSecurity(RiskManagementModel):
    '''Provides an implementation of IRiskManagementModel that limits the drawdown per holding to the specified percentage'''

    def __init__(self, maximumDrawdownPercent = 0.1):
        '''Initializes a new instance of the MaximumDrawdownPercentPerSecurity class
        Args:
            maximumDrawdownPercent: The maximum percentage drawdown allowed for any single security holding'''
        self.maximumDrawdownPercent = -abs(maximumDrawdownPercent)

    def ManageRisk(self, algorithm, targets):
        '''Manages the algorithm's risk at each time step
        Args:
            algorithm: The algorithm instance
            targets: The current portfolio targets to be assessed for risk'''
        targets = []
        for kvp in algorithm.Securities:
            security = kvp.Value

            if not security.Invested:
                continue

            pnl = security.Holdings.UnrealizedProfitPercent
            if pnl < self.maximumDrawdownPercent:
                # liquidate
                targets.append(PortfolioTarget(security.Symbol, 0))

        return targets
# region imports
from AlphaModelRSI import *
from ExecutionModelSpread import *
from PortfolioModelAccumulative import *
from RiskModelMaxDrawdownPercent import *
from OptionAlertsAndInitializer import *
from AlgorithmImports import *
# endregion

'''NOTES:
        Alerts enter universe
        
    TASKS:
        [] Start on Alpha
        [] Subscribe to underlying at hourly resolution'''

class leanBWcciRSI(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2022, 2, 10)  # Set Start Date
        self.SetEndDate(2022, 2, 20) # Set End Date
        self.SetCash(100000)  # Set Strategy Cash
        self.SetBrokerageModel(BrokerageName.TradierBrokerage, AccountType.Cash)

        #Global variables and functions
        self.period = 30

        #self.AddEquity("SPY", Resolution.Minute)

        # Get up to date prices on alerted options
        self.SetSecurityInitializer(CustomSecurityInitializer(
            self.BrokerageModel,
            FuncSecuritySeeder(self.GetLastKnownPrices),
            self))
        
        # add a custom universe data source 
        self.UniverseSettings.Resolution = Resolution.Minute
        self.AddUniverse(OptionAlerts, "universe-option-alerts", Resolution.Minute, self.alertedOptions)
        self.AddData(OptionAlerts, "option-alerts", Resolution.Minute)
        # Active Alpha Modules
        self.AddAlpha(RsiAlphaModel())
        self.AddRiskManagement(MaximumDrawdownPercentPerSecurity())

        # Portfolio and Execution models need Null unless margin
        self.SetPortfolioConstruction(NullPortfolioConstructionModel())
        self.SetExecution(NullExecutionModel())
    
    def alertedOptions(self, data):
        options = [ alert.Symbol for alert in data ]
        underlyings = list(set([x.Underlying for x in options]))
        return options + underlyings 

    def OnSecuritiesChanged(self, changes):
    
        for security in changes.RemovedSecurities:
            # delete Alert data from dataframe
            self.Log(f'{self.Time}| Removed {security} from universe')
            
        for security in changes.AddedSecurities:
            
            self.SecurityInitializer.Initialize(security)
            
            if security.Symbol.SecurityType == SecurityType.Equity:
                
                self.underlying = security.Symbol.Value
                self.Log(f'{self.Time}| Equity added to Universe: {self.underlying}')
                
            if security.Symbol.SecurityType == SecurityType.Option:
                
                self.security = security
                self.Log(f'{self.Time}| Option added to Universe: {security}')
    
    def OnData(self, data: Slice):
        pass