Overall Statistics
Total Trades
4422
Average Win
0.06%
Average Loss
-0.05%
Compounding Annual Return
23.195%
Drawdown
23.700%
Expectancy
0.363
Net Profit
86.865%
Sharpe Ratio
0.938
Loss Rate
37%
Win Rate
63%
Profit-Loss Ratio
1.17
Alpha
0.213
Beta
0.591
Annual Standard Deviation
0.239
Annual Variance
0.057
Information Ratio
0.859
Tracking Error
0.239
Treynor Ratio
0.38
Total Fees
$14757.31
import numpy as np
import pandas as pd


class TrendfollowingEffectsInStocks(QCAlgorithm):
    def Initialize(self):
        
        self.SetStartDate(2015, 1, 1)
        self.SetEndDate(2018,1,1)
        self.SetCash(1000000)
        
        self.UniverseSettings.Resolution = Resolution.Daily
        self.AddUniverse(self.CoarseSelectionFunction)
        
        self.month = 0
        self.count = 30
        self.symbols = []
        self.long_list = []
        # Store the data and indicators for each symbol
        self.symbolData = {}

    def CoarseSelectionFunction(self, coarse):
        
        # Update universe once every month
        if self.Time.month == self.month:
            return self.symbols
        
        self.month = self.Time.month
        
        if len(coarse) == 0:
            self.Log(f'{self.Time}: No data for coarse!')
            return self.symbols
        
        selected = [x for x in coarse if x.HasFundamentalData and x.Price > 3]
        filtered = sorted(selected, key=lambda x: x.DollarVolume, reverse=True)
        
        self.symbols = [x.Symbol for x in filtered[:self.count]]
        return self.symbols
        
        
    def OnData(self, data):

        for symbol in self.symbols:
           if data.ContainsKey(symbol) and data[symbol] is not None:
                close = data[symbol].Close
                symbolData = self.symbolData[symbol]
                symbolData.Prices.Add(close)
                
                if close > symbolData.High:
                    symbolData.High = close
                    self.long_list.append(symbol)
            
            
        for symbol in self.symbols:
            if self.Portfolio.ContainsKey(symbol):
                if self.Portfolio[symbol].Invested and symbol in self.long_list:
                    self.long_list.remove(symbol)
                    
        if len(self.long_list) == 0:
            return
        else:
            for symbol in self.long_list:
                self.SetHoldings(symbol, 1/len(self.long_list))
                    
       
        
        # If there are holdings in portfolio, then stop adding new stocks into long_list.
        # We rebalance the portfolio, and watch all the holdings exit   
        # Take care of the existing holdings and place StopMarketOrder to try to exit
        for symbol in self.symbols:
            if self.Portfolio.ContainsKey(symbol):
                if self.Portfolio[symbol].Invested:
                    self.Log(f'{self.Time}: portfolio has {self.Portfolio[symbol].Quantity} of {str(symbol)}')

                    symbolData = self.symbolData[symbol]
                    if symbolData.StopLoss is None:
                        # To avoid look-ahead bias, we can't use today's close price to place a stop market order because
                        # when we place the order, we wouldn't know that day's close price. So use yesterday's price
                        symbolData.StopPrice = symbolData.Prices[1] - 3.5*symbolData.ATR.Current.Value
                        symbolData.StopLoss = self.StopMarketOrder(symbol, -self.Portfolio[symbol].Quantity, symbolData.StopPrice)
                        self.Log(f'{self.Time}: placed {-self.Portfolio[symbol].Quantity} market stop orders at price {symbolData.StopPrice}')
                    else:
                        self.Transactions.CancelOpenOrders(symbol)
                        
                        # Only update submitted orders
                        if symbolData.StopLoss.Status != OrderStatus.Submitted:
                            continue
                        
                        updateOrderFields = UpdateOrderFields()
                        
                        # If price goes up (price at t-1 > price at t-2) then also adjust the stopPrice up, else keep the stopPrice constant, this is so-called trailing stop loss
                        # Here, we only know yesterday's price when placing stop loss orders. To avoid look-ahead bias, we don't use today's close
                        updateOrderFields.StopPrice = max(symbolData.Prices[1]- symbolData.ATR.Current.Value, symbolData.Prices[2]- symbolData.ATR.Current.Value)
                        self.Log(f"{symbolData.StopLoss} : {updateOrderFields} : {symbolData.Prices}")
                        symbolData.StopLoss.Update(updateOrderFields)


        # Rebalance the holdings to equally weighted
        if sum(x.Invested for x in self.Portfolio.Values) == 0:
            return
        else:
            target = 1 / sum(x.Invested for x in self.Portfolio.Values)
            for holding in self.Portfolio.Values:
                if holding.Invested:
                    self.SetHoldings(holding.Symbol, target)
    
        
    def OnSecuritiesChanged(self, changes):
        # Liquidate positions of removed securities
        # for security in changes.RemovedSecurities:
        #     if security.Invested:
        #         self.Liquidate(security.Symbol)
        
        #Call history request to initialize ATR indicator and prices rolling window
        history = self.History(self.symbols, 252*5, Resolution.Daily)
        high = history.high.unstack(level=0).max()
        close = history.close.unstack(level=0).tail(3).fillna(0)
    
        for symbol in self.symbols:
            ticker = str(symbol)
            if ticker not in close.columns or ticker not in high.index:
                self.symbols.remove(symbol)
                continue
            if symbol not in self.symbolData:
                self.symbolData[symbol] = SymbolData(self, symbol)

            #Set the high
            self.symbolData[symbol].High = high[ticker]
            
            for value in close[ticker]:
                self.symbolData[symbol].Prices.Add(value)
            
            # Warm up the ATR indicator
            self.symbolData[symbol].ATR.Reset()
            for index, row in history.loc[ticker].tail(10).iterrows():
                bar = TradeBar(index, symbol, row.open, row.high, row.low, row.close, row.volume)
                self.symbolData[symbol].ATR.Update(bar)
        

class SymbolData:
    def __init__(self, algorithm, symbol):
        self.ATR = algorithm.ATR(symbol, 10)
        
        # Track the security high
        self.High = 0
        
        # Track the recent 3-days' prices so that we can build trailing stop loss signal
        self.Prices = RollingWindow[float](3)
        
        # Save information for the Stop Loss order
        self.StopPrice = 0
        self.StopLoss = None