| Overall Statistics |
|
Total Trades 2240 Average Win 0.20% Average Loss -0.33% Compounding Annual Return 8.960% Drawdown 31.000% Expectancy 0.141 Net Profit 53.616% Sharpe Ratio 0.491 Loss Rate 29% Win Rate 71% Profit-Loss Ratio 0.61 Alpha 0.085 Beta 1.071 Annual Standard Deviation 0.215 Annual Variance 0.046 Information Ratio 0.401 Tracking Error 0.215 Treynor Ratio 0.099 Total Fees $15435.10 |
import numpy as np
import pandas as pd
class TrendfollowingEffectsInStocks(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2014, 1, 1)
self.SetEndDate(2019,1,1)
self.SetCash(1000000)
self.UniverseSettings.Resolution = Resolution.Daily
self.AddUniverse(self.CoarseSelectionFunction)
self.symbols = []
#Store the maximum prices
self.history = pd.DataFrame()
self.symbolData = {}
self.month = 0
def CoarseSelectionFunction(self, coarse):
# Avoid missing data as it happens on Oct 18th 2018
if len(coarse) == 0:
self.Log(f'{self.Time}: No data for coarse!')
return self.symbols
# update universe once every month
if self.Time.month == self.month:
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[:30]]
self.month = self.Time.month
return self.symbols
def OnData(self, data):
# If no holdings, scan the universe to see if any stock triggers buying signal
self.Log(f'{self.Time} OnData begins')
if not self.Portfolio.Invested:
self.Log(f'{self.Time} not invested')
long_list = []
for symbol in self.symbols:
if data.ContainsKey(symbol) and data[symbol] is not None and symbol in self.symbolData:
self.symbolData[symbol].Prices.Add(data[symbol].Close)
if data[symbol].Close > self.symbolData[symbol].High:
self.symbolData[symbol].High = data[symbol].Close
long_list.append(symbol)
for symbol in long_list:
self.SetHoldings(symbol, 1/len(long_list))
#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
self.StopMarketOrder(symbol, -self.Portfolio[symbol].Quantity , self.symbolData[symbol].Prices[1] - 4.5*self.symbolData[symbol].ATR.Current.Value)
# If there are holdings in portfolio, then stop adding new stocks into long_list. We rebalance the portfolio, and watch all the holdings exit
else:
#Take care of the existing holdings and place StopMarketOrder to try to exit
for symbol in self.symbols:
if symbol in self.Portfolio.Keys:
self.Log(f'{self.Time} {str(symbol)} in the portfolio')
holding = self.Portfolio[symbol]
if holding.Invested and data.ContainsKey(symbol) and data[symbol] is not None and symbol in self.symbolData:
# If price goes up then also adjust the stopPrice up, else keep the stopPrice constant, this is so called trailing stop loss
# Here, to avoid look-ahead bias, we can only know yesterday's price when placing stop loss orders
if self.symbolData[symbol].Prices[1] > self.symbolData[symbol].Prices[2]:
self.StopMarketOrder(symbol, -holding.Quantity , self.symbolData[symbol].Prices[1] - 4.5*self.symbolData[symbol].ATR.Current.Value)
else:
self.StopMarketOrder(symbol, -holding.Quantity , self.symbolData[symbol].Prices[2] - 4.5*self.symbolData[symbol].ATR.Current.Value)
# Rebalance the holdings to equally weighted
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)
self.symbolData[symbol].ATR.Reset()
#Set the high
self.symbolData[symbol].High = high[ticker]
for value in close[ticker]:
self.symbolData[symbol].Prices.Add(value)
#Set the ATR indicator
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.Symbol = symbol
self.ATR = algorithm.ATR(symbol, 10)
self.High = 0
#Track the recent 3-days' prices so that we can build trailing stop loss signal
self.Prices = RollingWindow[float](3)