Overall Statistics |
Total Trades 16 Average Win 0.46% Average Loss -0.33% Compounding Annual Return 5.506% Drawdown 1.100% Expectancy 0.500 Net Profit 1.301% Sharpe Ratio 1.585 Probabilistic Sharpe Ratio 64.255% Loss Rate 38% Win Rate 62% Profit-Loss Ratio 1.40 Alpha 0.074 Beta -0.067 Annual Standard Deviation 0.029 Annual Variance 0.001 Information Ratio -2.168 Tracking Error 0.171 Treynor Ratio -0.686 Total Fees $251.14 Estimated Strategy Capacity $14000000.00 Lowest Capacity Asset TQQQ UK280CGTCB51 |
''' Left to do - --1. 2 Day close rule- not closing, also make it close at the end of the 2nd day 2. Stoploss didnt work 2020-08-05 15:59:00 : Entering SRNE VL1GER10SC4L on {self.Time}...Entry Price: 13.74, Take Profit: 11.49, StopLoss: 14.865 Shoud've hit stoploss @ 14.87 on 8/7/2020 but holds position entire time ---3. Stoploss should be latest_daily_bar.High + 0.5 * atr vs self.entry_price + 0.5 * atr ---4. Allow for 2 positions at a time @ 15% equity each ''' from SymbolData import SymbolData from TradeManagement import TradeManagement class CryingBlueFlamingo(QCAlgorithm): def Initialize(self): self.SetStartDate(2020, 6, 20) # Set Start Date self.SetEndDate(2020, 9, 15) # Set End Date self.SetCash(1000000) # Set Strategy Cash self.benchmark = "SPY" self.AddEquity(self.benchmark) # fast way of modelling margin of shorting # SetHoldings(0.5) -> 50% of the 30% allowed is allocated aka 15% is allocated # self.Settings.FreePortfolioValuePercentage = 0.30 self.AddUniverse(self.CoarseSelection) self.UniverseSettings.Resolution = Resolution.Minute # self.UniverseSettings.ExtendHours = True self.universe_size = 30 self.symbol_data = {} self.trade_managers = {} self.max_positions = 2 self.total_short_margin_allocation = 0.30 # Want to open a short position before the market closes self.Schedule.On(self.DateRules.EveryDay(self.benchmark), self.TimeRules.BeforeMarketClose(self.benchmark, 1), self.Rebalance) #self.Schedule.On(self.DateRules.EveryDay(self.benchmark), self.TimeRules.BeforeMarketClose(self.benchmark, 0), self.CheckClose) #self.Schedule.On(self.DateRules.EveryDay(self.benchmark), self.TimeRules.AfterMarketOpen(self.benchmark, 1), self.CheckOpen) def Rebalance(self): '''Fires everyday 1 minute before market close''' for symbol, symbol_data in self.symbol_data.items(): if not symbol_data.IsReady: continue if symbol.Value == 'SRNE': self.Debug(f"SRNE: {self.Portfolio[symbol].Invested}, {self.Portfolio[symbol].Quantity}, {self.trade_managers[symbol].days_active}") signal = self.CalculateSignal(symbol_data) #hammer_signal = self.CalculateSignal(symbol_data) trade_manager = self.trade_managers[symbol] number_of_open_positions = len([symbol for symbol in self.Portfolio.Keys if self.Portfolio[symbol].Invested]) # number_of_open_orders = # Go short if there is a Hanging Man Signal if signal and not self.Portfolio[symbol].Invested and number_of_open_positions < self.max_positions: # trade_manager = self.trade_managers[symbol] #trade_manager.CreateEntry(-10) portfolio_allocation = self.total_short_margin_allocation / self.max_positions trade_manager.CreateEntry(-portfolio_allocation) self.Debug(f"{symbol}, {self.Portfolio[symbol].Invested}") if self.Portfolio[symbol].Invested: trade_manager.days_active += 1 self.Debug(f"{symbol} : days active: {trade_manager.days_active}") ''' # Go long if there is a hammer signal if hammer_signal: trade_manager = self.trade_managers[symbol] trade_manager.CreateEntry(10) ''' def OnData(self, data): for symbol, trade_manager in self.trade_managers.items(): if not self.Portfolio[symbol].Invested: continue current_price = self.Securities[symbol].Price stop_loss = trade_manager.stop_loss take_profit = trade_manager.take_profit days_active = trade_manager.days_active if current_price > stop_loss or current_price < take_profit: trade_manager.Liquidate() if days_active is 3: trade_manager.Liquidate() #trade_manager.OnMarketCloseLiquidate() self.Debug(f"{symbol} -- held for {days_active}...Liquidating at market close") #Finds a Red Hanging Man candle whose High is higher than the prior 5 days highs, and above the 20SMA. def CalculateSignal(self, symbol_data): # Daily bars bars = symbol_data.bar_window # Minute bars #self.Debug(f"Rolling window for {symbol_data.symbol} on {self.Time} is size {symbol_data.minute_bar_window.Count}") symbol_data.CalculateOHLC() max_price = max([x.High for x in symbol_data.minute_bar_window]) #self.Debug(f"Max price over minute consolidators for {symbol_data.symbol} on {self.Time} is {max_price}") low_price = min([x.Low for x in symbol_data.minute_bar_window]) #self.Debug(f"Low price over minute consolidators for {symbol_data.symbol} on {self.Time} is {low_price}") latest_daily_bar = symbol_data.summary_bar latest_consolidator = symbol_data.todays_minute_bars[0] first_consolidator = symbol_data.todays_minute_bars[-1] number_of_bars_today = len(symbol_data.todays_minute_bars) ''' self.Debug(f"{symbol_data.symbol} - {latest_daily_bar.Time} -> {latest_daily_bar.EndTime} Todays' daily bar: {latest_daily_bar}") ''' # self.Debug(f"bars collected today: {number_of_bars_today}") # self.Debug(f"EARLIEST Minute Bar Consolidator OHLC for {symbol_data.symbol} on {self.Time} has total period of {first_consolidator.Period} and start time of {first_consolidator.Time} ending at {first_consolidator.EndTime}") # self.Debug(f"LATEST Minute Bar Consolidator OHLC for {symbol_data.symbol} on {self.Time} has total period of {latest_consolidator.Period} and start atime of {latest_consolidator.Time} ending at {latest_consolidator.EndTime}") # self.Debug(f"Minute Bar Consolidator OHLC for {symbol_data.symbol} on {self.Time} is {latest_daily_bar}") # self.Debug(f"Daily Bar Consolidator OHLC for {symbol_data.symbol} on {self.Time} is {bars[0]}") # self.Debug(f"Minute Bar Consolidator for {symbol_data.symbol} has terminated") # checking if the high of the latest daily bar is greater than the high of all following bars uptrend = all([latest_daily_bar.High > bar.High for bar in list(bars)[:6]]) downtrend = all([latest_daily_bar.Low < bar.Low for bar in list(bars)[:6]]) red_bar = latest_daily_bar.Close < latest_daily_bar.Open #green_bar = latest_daily_bar.Close > latest_daily_bar.Open if red_bar: body = abs(latest_daily_bar.Open - latest_daily_bar.Close) shadow = abs(latest_daily_bar.Close - latest_daily_bar.Low) wick = abs(latest_daily_bar.High - latest_daily_bar.Open) #dayATR = abs(latest_daily_bar.High - latest_daily_bar.Low) hanging_man = (shadow > 2 * body) and (wick < 0.3 * body) ''' if green_bar: body = abs(latest_daily_bar.Close - latest_daily_bar.Open) shadow = abs(latest_daily_bar.Open - latest_daily_bar.Low) wick = abs(latest_daily_bar.High - latest_daily_bar.Close) dayATR = abs(latest_daily_bar.High - latest_daily_bar.Low) green_hammer = (shadow > 2 * body) and (wick < 0.3 * body) ''' sma = (sum([b.Close for b in list(bars)[:-1]]) + latest_daily_bar.Close) / 10 # latest_market_price price = self.Securities[symbol_data.symbol].Price above_sma = latest_daily_bar.Close > sma below_sma = latest_daily_bar.Close < sma #Hanging Man Signal signal = red_bar and uptrend and hanging_man and above_sma #Hammer Signal #hammer_signal = green_bar and downtrend and green_hammer and below_sma if signal: self.Debug(f" Signal Candle for {symbol_data.symbol} on {self.Time} is - Body: {body} , Wick: {wick} , shadow: {shadow}") self.Debug(f"Minute Bar Consolidator OHLC for Signal Day {symbol_data.symbol} on {self.Time} is {latest_daily_bar}") return signal ''' if hammer_signal: return hammer_signal ''' def CoarseSelection(self, coarse): # list of ~8500 stocks (coarse data) # coarse is a list of CoarseFundamental objects # Descending order sorted_by_liquidity = sorted(coarse, key=lambda c:c.DollarVolume, reverse=True) most_liquid_coarse = sorted_by_liquidity[:self.universe_size] # needs to return a list of Symbol object most_liquid_symbols = [c.Symbol for c in most_liquid_coarse] return most_liquid_symbols def OnSecuritiesChanged(self, changes): '''Fires after universe selection if there are any changes''' for security in changes.AddedSecurities: symbol = security.Symbol if symbol not in self.symbol_data and symbol.Value != self.benchmark: self.symbol_data[symbol] = SymbolData(self, symbol) self.trade_managers[symbol] = TradeManagement(self, symbol) for security in changes.RemovedSecurities: symbol = security.Symbol if self.Portfolio[symbol].Invested: self.trade_managers[symbol].Liquidate() # if not self.Portfolio[symbol].Invested: self.RemoveSecurityFromDictionaries(symbol) def RemoveSecurityFromDictionaries(self, symbol): if symbol in self.symbol_data: symbol_data_object = self.symbol_data.pop(symbol, None) symbol_data_object.KillDailyConsolidator() symbol_data_object.KillMinuteConsolidator() if symbol in self.trade_managers: self.trade_managers.pop(symbol, None)
from SymbolData import * class TradeManagement: def __init__(self, algorithm, symbol): self.algorithm = algorithm self.symbol = symbol self.days_active = 0 self.entry_price = None self.stop_loss = None self.take_profit = None def CreateEntry(self, quantity): # initial entry market order #self.algorithm.MarketOrder(self.symbol, quantity) self.algorithm.SetHoldings(self.symbol, quantity) current_price = self.algorithm.Securities[self.symbol].Price symbol_data = self.algorithm.symbol_data[self.symbol] # Update our 1 period ATR with latest bar, so we have today's range symbol_data.atr.Update(symbol_data.summary_bar) atr = symbol_data.atr.Current.Value self.entry_price = current_price summary_bar = self.algorithm.symbol_data[self.symbol].summary_bar self.stop_loss = summary_bar.High + 0.5 * atr #self.stop_loss = self.summary_bar.high + .05 * atr # Use High from the current day self.take_profit = self.entry_price - 1 * atr self.algorithm.Debug(f"Entering {self.symbol} on {{self.Time}}...Entry Price: {current_price}, Take Profit: {self.take_profit}, StopLoss: {self.stop_loss}") def Liquidate(self): self.algorithm.Debug(f"Liquidating.. {self.symbol}....{self.algorithm.Securities[self.symbol].Price}") self.algorithm.Liquidate(self.symbol) self.entry_price = None self.stop_loss = None self.take_profit = None self.days_active = 0 # in_universe = False # # checking if symbol shows up in any of the defined universes # for universe in self.algorithm.UniverseManager.Values: # if self.symbol in universe.Members.Keys: # in_universe = True # # if that symbol does not exist in any universe, # # we remove subscriptions and remove from list after liquidation # if not in_universe: # self.algorithm.RemoveSecurityFromDictionaries(self.symbol) #If the security has been held for 2 days, close the position at the end of the 2nd day def OnMarketCloseLiquidate(self): self.algorithm.Debug(f"Liquidating with on Mkt Close.. {self.symbol}....{self.algorithm.Securities[self.symbol].Price}") self.algorithm.MarketOnCloseOrder(self.symbol, -self.algorithm.Securities[self.Symbol].Holdings.Quantity) self.entry_price = None self.stop_loss = None self.take_profit = None self.days_active = 0
class SymbolData: '''Containers to hold relevant data for each symbol''' def __init__(self, algorithm, symbol): self.algorithm = algorithm self.symbol = symbol # self.minute_consolidator = self.algorithm.SubscriptionManager.ResolveConsolidator(Resolution.Minute) self.minute_consolidator = TradeBarConsolidator(timedelta(minutes=1)) self.algorithm.SubscriptionManager.AddConsolidator(self.symbol, self.minute_consolidator) self.minute_consolidator.DataConsolidated += self.OnMinuteBar # defines daily consolidator and then registers to receive data self.daily_consolidator = TradeBarConsolidator(timedelta(days=1)) self.algorithm.SubscriptionManager.AddConsolidator(symbol, self.daily_consolidator) self.daily_consolidator.DataConsolidated += self.OnDailyBar # 1. instantiantes a SimpleMovingAverage object # 2. subscribes it to receive data self.sma = SimpleMovingAverage(10) # Test 10 vs 20 self.algorithm.RegisterIndicator(symbol, self.sma, self.daily_consolidator) self.atr = AverageTrueRange(1) self.algorithm.RegisterIndicator(symbol, self.atr, self.daily_consolidator) # holds recent bars self.bar_window = RollingWindow[TradeBar](10) self.minute_bar_window = RollingWindow[TradeBar](500) self.summary_bar = None self.WarmUpIndicators() def WarmUpIndicators(self): # returns a dataframe history = self.algorithm.History(self.symbol, 20, Resolution.Daily) for bar in history.itertuples(): time = bar.Index[1] open = bar.open high = bar.high low = bar.low close = bar.close volume = bar.volume trade_bar = TradeBar(time, self.symbol, open, high, low, close, volume) self.sma.Update(time, close) self.atr.Update(trade_bar) self.bar_window.Add(trade_bar) def OnDailyBar(self, sender, bar): '''Fires each time our daily_consolidator produces a bar that bar is passed in through the bar parameter''' # save that bar to our rolling window self.bar_window.Add(bar) def OnMinuteBar(self, sender, bar): '''Fires each time our minute_consolidator produces a bar that bar is passed in through the bar parameter''' # save that bar to our rolling window self.minute_bar_window.Add(bar) # sorted(self.minute_bar_window, key = lambda thing: thing.Time) def KillDailyConsolidator(self): self.algorithm.SubscriptionManager.RemoveConsolidator(self.symbol, self.daily_consolidator) def KillMinuteConsolidator(self): self.algorithm.SubscriptionManager.RemoveConsolidator(self.symbol, self.minute_consolidator) def IsReady(self): return self.sma.IsReady and self.atr.IsReady and self.bar_window.IsReady and self.minute_bar_window.IsReady def CalculateOHLC(self): # Rolling window open bars = list(self.minute_bar_window) todays_bars = [bar for bar in bars if bar.Time.day == self.algorithm.Time.day] # desecending in time, larger indices -> further in past todays_bars_sorted = sorted(todays_bars, key=lambda b:b.Time, reverse=True) if len(todays_bars_sorted) == 0: self.algorithm.Debug(f"{self.symbol} -- {len(todays_bars_sorted)}") opening_bar = todays_bars_sorted[-1] open = opening_bar.Open # Rolling window close closing_bar = todays_bars_sorted[0] close = closing_bar.Close # High and low over period high = max([x.High for x in todays_bars_sorted]) low = min([x.Low for x in todays_bars_sorted]) # Calculate volume volume = sum([x.Volume for x in todays_bars_sorted]) # Time time = opening_bar.Time period = TimeSpan.FromMinutes((self.algorithm.Time - time).seconds // 60) # Create a summary trade bar self.summary_bar = TradeBar(time, self.symbol, open, high, low, close, volume, period) @property def todays_minute_bars(self): bars = list(self.minute_bar_window) # self.Debug(f"Filtering bars for {self.symbol} ON....{self.algorithm.Time.day}") todays_bars = [bar for bar in bars if bar.Time.day == self.algorithm.Time.day] # desecending in time, larger indices -> further in past todays_bars_sorted = sorted(todays_bars, key=lambda b:b.Time, reverse=True) return todays_bars_sorted