Overall Statistics
from Execution.StandardDeviationExecutionModel import StandardDeviationExecutionModel
from Portfolio.MeanVarianceOptimizationPortfolioConstructionModel import MeanVarianceOptimizationPortfolioConstructionModel

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

from QuantConnect import *
from QuantConnect.Algorithm import *
from QuantConnect.Algorithm.Framework import *
from QuantConnect.Algorithm.Framework.Portfolio import PortfolioTarget
from QuantConnect.Algorithm.Framework.Risk import RiskManagementModel

from datetime import *
import numpy as np
import pandas as pd

maximumDrawdownPercent = float(0.03)
profitTargetPercent = float(0.01)
executionReferencePeriod = int(60)
deviations = int(2)
resolution = Resolution.Minute
zero = int(0)
bufferBetweenOrders = int(60) #in universe resolution slices
stopMarketTicket = None
        
class CommunityTrailingStop(QCAlgorithm):

    def Initialize(self):
        
        stockPlot_1 = Chart('Price Vs. Stop')
        stockPlot_2 = Chart('Research')
        stockPlot_3 = Chart('Research 2')
        
        self.SetStartDate(2019, 1, 1)  # Set Start Date
        self.SetCash(1000000)  # Set Strategy Cash
        self.UniverseSettings.Resolution = resolution

        #self.SetExecution(StandardDeviationExecutionModel(executionReferencePeriod, deviations, resolution))

        #self.SetPortfolioConstruction(MeanVarianceOptimizationPortfolioConstructionModel())

        self.__numberOfSymbols = int(1000)
        self.__numberOfSymbolsFine = int(25)
        self.SetUniverseSelection(FineFundamentalUniverseSelectionModel(self.CoarseSelectionFunction, self.FineSelectionFunction, None, None))

        self.RiskManagementDict = {}
        
        self.maximumDictionarySize = self.__numberOfSymbolsFine
        self.equalWeight = round(1/self.__numberOfSymbolsFine,2)
        
        self.dayEndPortfolioValue = self.Portfolio.TotalPortfolioValue
        self.changes = None
    
    def OnData(self, data):

        for Symbol in self.RiskManagementDict:
            
            if not data.ContainsKey(Symbol) or data[Symbol] is None: return
    
            if self.RiskManagementDict[Symbol].timeBuffer != zero:
                self.RiskManagementDict[Symbol].updateBuffer()
                continue
            
            self.TrailingStop(data[Symbol], Symbol)
            
            if not self.Portfolio[Symbol].Invested and self.RiskManagementDict[Symbol].timeBuffer == zero and self.RiskManagementDict[Symbol].continueTrading:
                self.SetHoldings(Symbol, self.equalWeight)

        
    def CoarseSelectionFunction(self, coarse):

        preScreen = [x for x in coarse if (x.HasFundamentalData) 
                                and (float(x.AdjustedPrice) > int(5))]
            
        sortedByDollarVolume = sorted(preScreen, key=lambda x: x.DollarVolume, reverse=True)
    
        # return the symbol objects of the top entries from our sorted collection
        return [ x.Symbol for x in sortedByDollarVolume[:self.__numberOfSymbols] ]     
    
    def FineSelectionFunction(self, fine):

        fine = [x for x in fine if (float(x.FinancialStatements.BalanceSheet.CurrentAssets.Value) > zero) 
                                and (float(x.EarningReports.BasicAverageShares.Value) > zero)
                                and (float(x.FinancialStatements.BalanceSheet.CurrentLiabilities.Value) > zero)
                                and (float(x.FinancialStatements.BalanceSheet.TotalNonCurrentLiabilitiesNetMinorityInterest.Value) > zero)
                                and (x.CompanyReference.IndustryTemplateCode!="B")
                                and (x.CompanyReference.IndustryTemplateCode!="I")]
        for i in fine:
            #calculates the net current asset value per share
            total_liabilities = float(i.FinancialStatements.BalanceSheet.CurrentLiabilities.Value)+float(i.FinancialStatements.BalanceSheet.TotalNonCurrentLiabilitiesNetMinorityInterest.Value)
            i.ncav = (float(i.FinancialStatements.BalanceSheet.CurrentAssets.Value) - total_liabilities)/float(i.EarningReports.BasicAverageShares.Value)
            
        #keeps all symbols that have a NCAV/MV higher than 1.5
        fineScreen = [i for i in fine if (i.ncav > float(1.5))]
        
        sortedByPeRatio = sorted(fineScreen, key=lambda x: x.ValuationRatios.PERatio, reverse=True)
       
        return [ x.Symbol for x in sortedByPeRatio[:self.__numberOfSymbolsFine] ]        

    def OnSecuritiesChanged(self, changes):
        self.Plot("Research", "Active Securities", self.ActiveSecurities.Count)
        self.changes = changes
    
        #Securities Removed
        for x in changes.RemovedSecurities:
            self.Liquidate(x.Symbol)
            if x.Symbol in self.RiskManagementDict:
                del self.RiskManagementDict[x.Symbol]#.pop(x.Symbol)
                
            self.Plot('Research 2', 'Dictionary Size', len(self.RiskManagementDict))
    
        #Securities Added
        for x in self.changes.AddedSecurities:
            self.RiskManagementDict[x.Symbol] = StopManagement(x.Symbol, maximumDrawdownPercent)
            self.Plot('Research 2', 'Dictionary Size', len(self.RiskManagementDict))


    def TrailingStop(self, price, symbol):
           
        if self.Portfolio[symbol].Invested:
                
            if self.RiskManagementDict[symbol].orderID is None:
                
                self.RiskManagementDict[symbol].captureStop(self.StopMarketOrder(symbol, -self.Portfolio[symbol].Quantity, self.Securities[symbol].Holdings.AveragePrice * round(1-maximumDrawdownPercent)), 
                                                            self.Securities[symbol].Holdings.AveragePrice, self.Portfolio[symbol].Quantity)
                                                            
                self.RiskManagementDict[symbol].trackStopMarketOrder(self.RiskManagementDict[symbol].stopMarketTicket.OrderId)

            self.RiskManagementDict[symbol].updateStopPrice(price)

            
    def OnOrderEvent(self, orderEvent):
        
        if orderEvent.Status != OrderStatus.Filled:
            return
        
        for Security in self.RiskManagementDict:
            
            if self.RiskManagementDict[Security].stopMarketTicket is not None and self.RiskManagementDict[Security].stopMarketTicket.OrderId == orderEvent.OrderId:
                self.RiskManagementDict[Security].executedStopOrder(bufferBetweenOrders)
    
    def OnEndOfDay(self):
        for Security in self.RiskManagementDict:
            self.RiskManagementDict[Security].endOfDay()
                
        self.dayEndPortfolioValue = self.Portfolio.TotalPortfolioValue
        
        
class StopManagement:
    
    def __init__(self, symbol, riskTolerance):
        self.Symbol = symbol
        self.StopPrice = zero
        self.HighestPrice = zero
        self.LowestPrice = zero
        self.riskTolerance = round(1-riskTolerance,2)
        self.stopMarketTicket = None
        self.orderID = None
        self.timeBuffer = zero
        self.continueTrading = True
        self.LongEquity = True

    def trackStopMarketOrder(self, orderID):
        self.orderID = orderID
        
    def updateStopPrice(self, price):
        if self.LongEquity:
            if price.High > self.HighestPrice:
                self.HighestPrice = price.High
                self.StopPrice = self.HighestPrice * self.riskTolerance
                
                if self.stopMarketTicket is not None:
                    updateFields = UpdateOrderFields()
                    updateFields.StopPrice = self.StopPrice
                    self.stopMarketTicket.Update(updateFields)
                    
        if not self.LongEquity:
            if price.Low < self.LowestPrice:
                self.LowestPrice = price.Low
                self.StopPrice = self.LowestPrice * round(1/self.riskTolerance,2)
                
                if self.stopMarketTicket is not None:
                    updateFields = UpdateOrderFields()
                    updateFields.StopPrice = self.StopPrice
                    self.stopMarketTicket.Update(updateFields)            

    def trackOrder(self, orderID):
        self.orderID = orderID
        
    def captureStop(self, stopMarketTicket, avgprice, qty):
        
        if qty >= zero:
            self.LongEquity = True
            self.HighestPrice = avgprice
            self.StopPrice = self.HighestPrice * self.riskTolerance
            
        elif qty < zero:
            self.LongEquity = False        
            self.LowestPrice = avgprice
            self.StopPrice = self.LowestPrice * round(1/self.riskTolerance,2)
            
        self.stopMarketTicket = stopMarketTicket

    def executedStopOrder(self, bufferBetweenOrders):
        self.stopMarketTicket = None
        self.orderID = None
        self.timeBuffer = bufferBetweenOrders
        self.continueTrading = False
        
    def updateBuffer(self):
        self.timeBuffer = self.timeBuffer - int(1)
        
    def endOfDay(self):
        self.continueTrading = True
        
    def reset(self):
        self.StopPrice = zero
        self.HighestPrice = zero
        self.LowestPrice = zero
        self.stopMarketTicket = None
        self.orderID = None
        self.continueTrading = True
        self.LongEquity = True