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