Overall Statistics |
Total Trades 4983 Average Win 0.16% Average Loss -0.12% Compounding Annual Return 13.159% Drawdown 22.100% Expectancy 0.215 Net Profit 89.423% Sharpe Ratio 0.772 Probabilistic Sharpe Ratio 28.639% Loss Rate 48% Win Rate 52% Profit-Loss Ratio 1.35 Alpha 0.118 Beta -0.058 Annual Standard Deviation 0.145 Annual Variance 0.021 Information Ratio 0.074 Tracking Error 0.193 Treynor Ratio -1.942 Total Fees $39920.97 |
import numpy as np from collections import deque class Starter(QCAlgorithm): def Initialize(self): # Environment setup. self.SetStartDate(2014, 11, 1) self.SetCash(1000000) self.SetBrokerageModel(AlphaStreamsBrokerageModel()) self.SetExecution(ImmediateExecutionModel()) self.SetPortfolioConstruction(EqualWeightingPortfolioConstructionModel()) self.UniverseSettings.Resolution = Resolution.Minute self.symbols = {} # Parameters. self.barResolution = Resolution.Hour # Use 1H tradebars. self.insightFrequency = self.TimeRules.At(10, 30) # Emit insights daily at 10:30am. self.warmupBars = 100 # Initialize with 100 historical tradebars. self.isLongOnly = True # Only use long positions. # Selection. # self.SetUniverseSelection(LiquidETFUniverse()) # Or, self.AddEquities(['QQQ', 'QTEC']) self.AddEquities(['SPY', 'QQQ', 'QTEC']) self.SetBenchmark('SPY') # Main insight loop. self.Schedule.On(self.DateRules.EveryDay(), self.insightFrequency, self.PublishInsights) def Logger(self, msg): self.Debug('[{}] {}'.format(self.Time, msg)) def ActiveSymbols(self): # Returns a tuple list of active symbols, ready for trading. return [(str(x.Symbol), self.symbols[str(x.Symbol)]) for x in self.ActiveSecurities.Values \ if self.IsMarketOpen(x.Symbol) and \ str(x.Symbol) in self.symbols.keys() and \ self.Securities[x.Symbol].Price > 0 and \ self.Status != Status.Wait] def OnSecuritiesChanged(self, changes): symbolsAdded = [x.Symbol for x in changes.AddedSecurities] symbolsRemoved = [x.Symbol for x in changes.RemovedSecurities] # Warm up new symbols. self.AddSymbols(symbolsAdded) # Discard removed symbols. self.RemoveSymbols(symbolsRemoved) def AddEquities(self, equities): # Adds a list of equities to the strategy. self.AddSymbols([self.AddEquity(equity).Symbol for equity in equities]) def AddSymbols(self, symbols): # Warms up a list of symbols and adds them to the symbols dict. df = self.History(symbols, self.warmupBars, self.barResolution) for symbol in symbols: self.symbols[str(symbol)] = Symbol(str(symbol), symbol.ID, self) if str(symbol.ID) not in df.index.get_level_values(0): continue self.symbols[str(symbol)].HistoricalDataWarmup(df.loc[symbol], self) def RemoveSymbols(self, symbols): # Removes a list of symbols and unsubscribes their tradebar consolidators. for symbol in symbols: self.SubscriptionManager.RemoveConsolidator(symbol, self.symbols[symbol].bar) self.symbols[symbol].pop() def PublishInsights(self): # Emit insights from active symbols and update status. insights = [] for symbol, symbolData in self.ActiveSymbols(): status = symbolData.status duration = symbolData.exp_max - self.Time if symbolData.exp_max != None else Time.EndOfTimeTimeSpan isInvested = self.Portfolio[symbol].Invested isLong = self.Portfolio[symbol].IsLong isExpired = symbolData.exp_max != None and self.Time > symbolData.exp_max isClose = (status == Status.Sell and isLong) or (status == Status.Buy and not isLong) or status == Status.Close if not isInvested: if isExpired: # Update the expired symbols from the portfolio environment. symbolData.Reset(Status.Ready) continue if status == Status.Buy: # Push buy orders. insights.append(Insight.Price(symbol, duration, InsightDirection.Up, symbolData.magnitude, None, None)) symbolData.status = Status.Bought symbolData.orderAt = self.Time elif status == Status.Sell and not self.isLongOnly: # Push sell orders. insights.append(Insight.Price(symbol, duration, InsightDirection.Down, symbolData.magnitude, None, None)) symbolData.status = Status.Sold symbolData.orderAt = self.Time else: # Updated expired (closed) positions and process close events for current holdings. if isExpired or isClose: insights.append(Insight.Price(symbol, timedelta(1), InsightDirection.Flat)) symbolData.Reset(Status.Ready) self.EmitInsights(insights) class Symbol: def __init__(self, symbol, symbolId, alg): # Setup. self.symbol = symbol self.id = symbolId self.Reset(Status.Wait) self.Initialize(alg) # Subscribe to tradebars. self.bar = TradeBarConsolidator(Extensions.ToTimeSpan(alg.barResolution)) self.bar.DataConsolidated += lambda sender, tradebar: self.HandleBars(tradebar, alg) alg.SubscriptionManager.AddConsolidator(symbol, self.bar) def Initialize(self, alg): self.barQ = deque(maxlen=alg.warmupBars) self.rsi = alg.RSI(self.symbol, 30, alg.barResolution) def HandleBars(self, bar, alg, isWarmup=False): # Main event loop. self.rsi.Update(bar.Time, bar.Close) self.barQ.append(bar) if isWarmup or len(self.barQ) < self.barQ.maxlen: return rsiValue = self.rsi.Current.Value close = np.array([bar.Close for bar in self.barQ]) stdDev = np.std(close) if self.status == Status.Ready: if rsiValue < 30: self.status = Status.Buy self.magnitude = stdDev / close[-1] self.exp_max = alg.Time + timedelta(weeks=4) # Close orders open longer than 4 weeks. self.exp_min = alg.Time + timedelta(weeks=1) # Keep orders open at least 1 week. elif self.status == Status.Bought: if self.exp_min != None and alg.Time < self.exp_min: return if rsiValue > 70: self.status = Status.Sell # Immediately close if RSI crosses upper limit. def HistoricalDataWarmup(self, history, alg): # Stream historical warm up data. for t, r in history.iterrows(): o, h, l, c, v = r['open'], r['high'], r['low'], r['close'], r['volume'] bar = TradeBar(t, self.symbol, o, h, l, c, v) self.HandleBars(bar, alg, isWarmup=True) self.status = Status.Ready def Reset(self, status): self.exp_max = None self.exp_min = None self.magnitude = None self.orderAt = None self.status = status class Status(Enum): Wait = 0 # Warming up or not ready for trading. Ready = 1 # Ready to trade. Buy = 2 # Signal a buy recommendation (close if sold.) Sell = 3 # Signal a sell recommendation (close if bought.) Close = 4 # Signal a close recommendation. Bought = 5 # Symbol holds a long position. Sold = 6 # Symbol holds a short position.