| Overall Statistics |
|
Total Trades 210 Average Win 3.81% Average Loss -4.14% Compounding Annual Return 625.607% Drawdown 38.400% Expectancy 0.262 Net Profit 150.275% Sharpe Ratio 6.906 Probabilistic Sharpe Ratio 87.376% Loss Rate 34% Win Rate 66% Profit-Loss Ratio 0.92 Alpha 6.921 Beta -0.515 Annual Standard Deviation 0.986 Annual Variance 0.971 Information Ratio 6.571 Tracking Error 1.002 Treynor Ratio -13.208 Total Fees $11793.30 Estimated Strategy Capacity $320000.00 Lowest Capacity Asset UFAB W1U0W5MNRKO5 |
class SymbolData:
def __init__(self, algorithm, symbol):
self.algorithm = algorithm
self.symbol = symbol
# Daily consolidator definition and subscription to data
self.daily_consolidator = TradeBarConsolidator(timedelta(days=1))
self.algorithm.SubscriptionManager.AddConsolidator(self.symbol, self.daily_consolidator)
self.daily_consolidator.DataConsolidated += self.OnDailyBar
# signal resolution consolidator definition and subscription to data
self.consolidator = TradeBarConsolidator(timedelta(minutes=10))
self.algorithm.SubscriptionManager.AddConsolidator(self.symbol, self.consolidator)
self.consolidator.DataConsolidated += self.OnBar
# self.algorithm.ResolveConsolidator
# minute consolidator definition and subscription to data
self.minute_consolidator = TradeBarConsolidator(timedelta(minutes=1))
self.algorithm.SubscriptionManager.AddConsolidator(self.symbol, self.minute_consolidator)
self.minute_consolidator.DataConsolidated += self.OnMinuteBar
# Volume daily SMA
self.vol_sma = SimpleMovingAverage(20)
# 30 min resolution - 10 period SMA for price
self.sma = SimpleMovingAverage(10)
self.algorithm.RegisterIndicator(self.symbol, self.sma, self.consolidator)
self.minute_bars = RollingWindow[TradeBar](10)
self.bars = RollingWindow[TradeBar](3)
self.WarmUpIndicators()
def WarmUpIndicators(self):
# returns a dataframe - to warmup daily volume sma
history = self.algorithm.History(self.symbol, 20, Resolution.Daily)
# to warm up 30 minute price sma and minute bar window
# minute_history = self.algorithm.History(self.symbol, 330, Resolution.Minute)
# # gets rid of symbol/ticker index in df
# minute_history = minute_history.droplevel(0, 0)
# # creates 30 min bars from minute bars
# _30_minute_history = minute_history.resample('30T').ohlc()
# # picks out required columns
# _30_minute_history = _30_minute_history.drop([c for c in _30_minute_history.columns if c not in ['close', 'volume']], 1)
# for bar in minute_history.itertuples():
# time = bar.Index
# close = bar.close
# open = bar.open
# low = bar.low
# high = bar.high
# volume = bar.volume
# min_bar = TradeBar(time, self.symbol, open, high, low, close, volume)
# self.minute_bars.Add(min_bar)
# m
# for row in _30_minute_history.itertuples():
# time = row[0]
# open = row[1]
# high = row[2]
# low = row[3]
# close = row[4]
# volume = row[8]
# _30_min_bar = TradeBar(time, self.symbol, open, high, low, close, volume)
# self.bars.Add(_30_min_bar)
# self.sma.Update(time, close)
for bar in history.itertuples():
time = bar.Index[1]
open = bar.open
high = bar.high
low = bar.low
close = bar.close
volume = bar.volume
daily_bar = TradeBar(time, self.symbol, open, high, low, close, volume)
self.vol_sma.Update(time, volume)
def OnDailyBar(self, sender, bar):
# Updates volume sma with latest daily bar data
self.vol_sma.Update(bar.EndTime, bar.Volume)
def OnBar(self, sender, bar):
# Saves signal resolution bars
self.bars.Add(bar)
def OnMinuteBar(self, sender, bar):
# Saves minute bars
self.minute_bars.Add(bar)
#Find the $ Volume of the signal bar
@property
def RecentDollarVolume(self):
dollar_volume = 0
for bar in list(self.minute_bars):
dollar_volume += bar.Volume * bar.Close
return dollar_volume
# Find Volume of the signal bar
@property
def RecentVolume(self):
actual_volume = 0
for bar in list(self.minute_bars):
actual_volume += bar.Volume
return actual_volume
@property
def CandlePatternSignal(self):
# Close > Open
# Wick < .5*Body
# Body > 1.02*Open
most_recent_bar = self.bars[0]
close = most_recent_bar.Close
open = most_recent_bar.Open
high = most_recent_bar.High
wick = high - close
body = close - open
return close > open and wick < 0.5 * body and close > 1.02 * open
@property
def UnusualVolume(self):
vol_sma = self.vol_sma.Current.Value
recent_volume = self.bars[0].Volume
return recent_volume > 3 * vol_sma
@property
def UnusualVolumeSignal(self):
dolvolLim = bool(self.RecentDollarVolume > 150000) #dollars minimum
actualVolume = bool(self.RecentVolume > 150000) #shares minimum
if dolvolLim and actualVolume and self.CandlePatternSignal and self.UnusualVolume:
return True
return False
#return self.RecentDollarVolume > 500000 and self.CandlePatternSignal and self.UnusualVolume
@property
def findGapUp(self):
#Get the Signal Candle Open price to compare to yesterdays high
most_recent_bar = self.bars[0]
open = most_recent_bar.Open
#Find Yesterdays high to compare to for a gap up or down
history = self.algorithm.History(self.symbol, 1, 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
#daily_bar = TradeBar(time, self.symbol, open, high, low, close, volume)
if open > high:
self.algorithm.Debug(f"Gap Up on {self.symbol} -- 10m Open is {open} > yesterday high of {high}")
return True
if open <= high:
self.algorithm.Debug(f"NO Gap Up on {self.symbol} -- 10m Open is {open} <= yesterday high of {high}")
return False
@property
def IsReady(self):
return self.vol_sma.IsReady and self.sma.IsReady and self.minute_bars.IsReady and \
self.bars.IsReady
def KillConsolidators(self):
self.algorithm.SubscriptionManager.RemoveConsolidator(self.symbol, self.consolidator)
self.algorithm.SubscriptionManager.RemoveConsolidator(self.symbol, self.minute_consolidator)
self.algorithm.SubscriptionManager.RemoveConsolidator(self.symbol, self.daily_consolidator)class TradeManagement:
def __init__(self, algorithm, symbol):
self.algorithm = algorithm
self.symbol = symbol
self.entry_limit_ticket = None
self.stop_loss = None
self.take_profit = None
def LimitOrder(self, quantity, limit_price):
# self.entry_limit_ticket = self.algorithm.LimitOrder(...)
# self.stop_loss = ..
# ....
pass
def CheckAndUpdate(self):
current_market_price = self.algorithm.Securities[self.symbol].Price
if not self.ActivePosition:
return
# stop loss
# take profit
# self.algorithm.Liquidate
def CancelEntryOrder(self):
if self.entry_limit_ticket is not None:
self.entry_limit_ticket.Cancel()
self.stop_loss = None
self.take_profit = None
@property
def ActivePosition(self):
return self.algorithm.Portfolio[self.symbol].Invested
def GetPositionSize(self):
portfolio_value = self.algorithm.Portfolio.TotalPortfolioValue
#...
def Liquidate(self):
self.algorithm.Debug(f"Liquidating.. {self.symbol}....{self.algorithm.Securities[self.symbol].Price}")
self.algorithm.Liquidate(self.symbol)
self.entry_limit_ticket = None
self.stop_loss = None
self.take_profit = None
def CreateEntry(self, quantity):
# initial entry market order
self.algorithm.SetHoldings(self.symbol, quantity)from SymbolData import *
from TradeManagement import *
class LogicalRedOrangeGoshawk(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2021, 1, 1)
self.SetEndDate(2021, 6, 20)
self.SetCash(100000)
self.benchmark = "SPY"
self.AddEquity("SPY", Resolution.Minute)
self.AddUniverse(self.SelectCoarse)
self.UniverseSettings.Resolution = Resolution.Minute
self.symbols = {}
self.trade_managers = {}
self.Schedule.On(self.DateRules.EveryDay(self.benchmark), self.TimeRules.AfterMarketOpen(self.benchmark, 11), self.Rebalance)
self.Schedule.On(self.DateRules.EveryDay(self.benchmark), self.TimeRules.BeforeMarketClose(self.benchmark, 1), self.EoDClose)
def Rebalance(self):
#self.Debug(f"Universe Size... {len(self.symbols)}")
for symbol, symbol_data in self.symbols.items():
if not symbol_data.IsReady:
continue
#trade_manager = self.trade_managers[symbol]
#Unusual Volume Signal
if symbol_data.UnusualVolumeSignal:
#self.SetHoldings(symbol, -.15)
#self.Debug(f"shorting {symbol}")
#Go Short
if symbol_data.findGapUp == True:
self.SetHoldings(symbol, -.15)
self.Debug(f"short {symbol}")
#Go Long
if symbol_data.findGapUp == False:
self.SetHoldings(symbol, .5)
self.Debug(f"Long {symbol}")
# self.Debug(f"{symbol} - $V {symbol_data.RecentDollarVolume}, bar signal {symbol}")
# self.Debug(f"~~~~{symbol} - {self.Time}~~~~~")
# self.Debug(f"Unusual Volume Signal: {symbol_data.UnusualVolumeSignal}")
# self.Debug(f"30 Minute $ Volume: {symbol_data.RecentDollarVolume}")
# self.Debug(f"Candle Pattern: {symbol_data.CandlePatternSignal}")
# self.Debug(f"20 Day VOL SMA: {symbol_data.vol_sma.Current.Value}")
# latest_bar = symbol_data.bars[0]
# self.Debug(f"latest 30 min bar: {latest_bar.EndTime} --- {latest_bar}")
# self.Debug(f"Unusual Volume: {symbol_data.UnusualVolume}")
# self.Debug(f"{self.Time} -- {symbol} has a signal!")
def EoDClose(self):
for symbol, symbol_data in self.symbols.items():
if self.Portfolio[symbol].Invested:
self.Debug(f"End of Day Close... {symbol}")
self.SetHoldings(symbol, 0)
def SelectCoarse(self, coarse):
filtered_by_price = [c for c in coarse if c.AdjustedPrice >= 1 and c.AdjustedPrice <= 7]
return [c.Symbol for c in filtered_by_price]
def OnSecuritiesChanged(self, changes):
for security in changes.AddedSecurities:
symbol = security.Symbol
if symbol not in self.symbols and symbol.Value != self.benchmark:
self.symbols[symbol] = SymbolData(self, symbol)
#self.Debug(symbol)
for security in changes.RemovedSecurities:
symbol = security.Symbol
if symbol in self.symbols:
symbol_data = self.symbols.pop(symbol, None)
symbol_data.KillConsolidators()
def OnData(self, data):
for symbol, symbol_data in self.symbols.items():
if not self.Portfolio[symbol].Invested:
continue
#Short Stoploss,
if self.Portfolio[symbol].IsShort:
if self.Portfolio[symbol].UnrealizedProfitPercent < -.5: #
self.SetHoldings(symbol, 0)
self.Debug(f"{symbol} -- Hit Short Stop Loss - Liquidating")
#Long Stoploss
if self.Portfolio[symbol].IsLong:
#Long Stop Loss
if self.Portfolio[symbol].UnrealizedProfitPercent < -.25: #
self.SetHoldings(symbol, 0)
self.Debug(f"{symbol} -- Hit Short Stop Loss - Liquidating")
#Long Profit Taking - No profit % for now, close at EoD
if self.Portfolio[symbol].UnrealizedProfitPercent >= 3:
self.SetHoldings(symbol, 0)
self.Debug(f"{symbol} -- Hit Profit level - Liquidating")
# If 10% is hit set at stoploss at 0%