Overall Statistics |
Total Trades 101 Average Win 8.59% Average Loss -2.03% Compounding Annual Return 82.683% Drawdown 16.900% Expectancy 0.567 Net Profit 192.340% Sharpe Ratio 1.657 Loss Rate 70% Win Rate 30% Profit-Loss Ratio 4.22 Alpha 0.821 Beta -16.28 Annual Standard Deviation 0.334 Annual Variance 0.111 Information Ratio 1.608 Tracking Error 0.334 Treynor Ratio -0.034 Total Fees $3967.96 |
STATUS = dict() attrs = [ "Canceled", "CancelPending", "Filled", "Invalid", "New", "None", "PartiallyFilled", "Submitted", ] for attr in attrs: STATUS[getattr(OrderStatus, attr)] = attr toCamelCase = lambda string:''.join(x for x in string.title() if not x.isspace()) PORTFOLIO = [ "Cash", "Total Portfolio Value", "Total Unrealized Profit", "Total Fees", # "Total Unlevered Absolute Holdings Cost", "Total Margin Used", "Margin Remaining", ] def delim(): return '-' * 20 def get_order_id(order): if hasattr(order, 'OrderId'): return order.OrderId elif hasattr(order, 'Id'): return order.Id else: return "?" def header(order): t = "{} {}" h = order.Tag.upper() i = get_order_id(order) return t.format(h, i) def price(t, p, h): t = "{}: ${:.2f} on '{}'" return t.format(h, p, t) def join(strings): return "\n".join(strings) def portfolio(time, p, q): logs = [] logs.append("Portfolio Quantity: {}".format(q)) t = "Total Portfolio Value: ${:.2f}" for attr in PORTFOLIO: t = "{}: ${:.2f}" k, v = attr, getattr(p, toCamelCase(attr)) logs.append(t.format(k, v)) return logs def order_sum(time, verb, order, price=None, trade_return=None): logs, t = [], "{} {} {}" order_id = get_order_id(order) s = t.format(verb, order.Tag, order_id) hasprice = hasattr(order, 'Price') and order.Price > 0 if hasprice or price is not None: t = " at ${:.2f}" s += t.format(order.Price if hasprice else price) if trade_return != None: gain, ratio = trade_return t = " with ${:.2f} gain ({:.2f}%)" s += t.format(gain, ratio * 100) t = " on '{}'" s += t.format(time) logs.append(s) return logs def order(orderEvent, order): logs, t = [], " at price ${:.2f}" s = "Order Quanity: {}".format(order.Quantity) if order.Price: s += t.format(order.Price) logs.append(s) if orderEvent.FillQuantity: s = "Order Quanity Filled: {}".format(orderEvent.FillQuantity) if orderEvent.FillPrice: s += t.format(orderEvent.FillPrice) logs.append(s) return logs def strategy( curr_price, last_price=None, sma=None, sma_reso=None, curr_mae=None, last_mae=None, header="Trade", ): logs = [] t = "{} {} {}" logs.append(t.format(delim(), header, delim())) t = "Current Price: ${:.2f} on '{}'" s = t.format(curr_price.Close, curr_price.Time) logs.append(s) if last_price != None: t = "Last Price: ${:.2f} on '{}'" s = t.format(last_price.Close, last_price.Time) logs.append(s) if sma != None: t = "SMA({} {}): {:.4f} on '{}'" s = t.format( sma_reso[0], sma_reso[1], sma.Current.Value, sma.Current.Time, ) logs.append(s) if curr_mae != None: t = "Current MA E Line: {} = {:.4f}" s = t.format(*curr_mae) logs.append(s) if last_mae != None: t = "Last MA E Line: {} = {:.4f}" s = t.format(*last_mae) logs.append(s) return logs def flutter(thresh, zone, last_trade): lower, upper = zone t = "Flutter for {} {} within {:.4f} - {:.4f} ({:.2f}%)" s = t.format(last_trade.Tag, last_trade.Id, lower, upper, thresh * 100) return [s] def flutter_exit(currentFlutter, lastFlutter, price): if not currentFlutter and lastFlutter: t = "Exited flutter zone at ${:.2f}" return t.format(price)
from utils import * from QuantConnect import * from QuantConnect.Parameters import * from QuantConnect.Data.Market import * from datetime import datetime, timedelta from dateutil.parser import parse from decimal import Decimal from collections import deque import logger as log import numpy as np LP = False LO = False def zero_lines(start=.05, step=.05): '''generate zero lines infinitely''' while True: yield start start += start * step class ShortSellRevolver(QCAlgorithm): @property def zero_line(self): for l in zero_lines(start=.05, step=self.ZL): if l > self.CurrentPrice.Close: return l # Event Handlers def OnData(self, data): self.LogEnable() self.CurrentPrice = data[self.symbol] if not self.IsReady(self.CurrentPrice): return self.UpdateTrailStopLoss() if self.IsWakeUp(): self.OnWakeUp(self.CurrentPrice) def OnWakeUp(self, bar): self.Logs = [] orderTicket = None # initial short if not self.InitialShort: self.InitialShortTrade() # check flutter zone if self.CurrentTrade and self.FlutterThreshold > 0: l = log.flutter( self.FlutterThreshold, self.ComputeFlutterZone(), self.CurrentTrade, ) self.Logs.extend(l) if self.inFlutterZone(self.CurrentPrice.Close): return # trade if self.IsExchangeOpen(self.symbol): self.Queue.append(self.zero_line) should_buy, should_sell = self.Indicate() could_buy, could_sell = self.CouldTrade() buy, sell = should_buy and could_buy, could_sell and should_sell if buy or sell: info = [','.join(map(str, item)) for item in enumerate(self.Queue)] for i in map(',ZeroLine,{}'.format, info): self.QuickLog(i) self.Transactions.CancelOpenOrders(self.symbol) self.HoldingsAdj = self.Holdings self.Trade(buy, sell, logstrat=False) if buy or sell else None # update price self.LastPrice = self.CurrentPrice def OnOrderEvent(self, orderEvent): order = self.Transactions.GetOrderById(orderEvent.OrderId) if OrderStatus.Submitted == order.Status: self.OnOrderSubmitted(orderEvent, order) if OrderStatus.Filled == order.Status: self.OnOrderFilled(orderEvent, order) if OrderStatus.Canceled == order.Status: self.LogStatus(orderEvent, "Canceled") if OrderStatus.Invalid == order.Status: self.LogStatus(orderEvent, "Invalid", with_port=LP, with_order=LO) buy, sell = self.Indicate() if not buy and not sell: self.RetryInvalidOrder(order) def OnOrderSubmitted(self, orderEvent, order): if OrderTag.InitialShort == order.Tag: self.InitialShort = True if order.Tag in OpenTradeTags: self.HoldingsBook[order.Tag] = self.HoldingsAdj self.CurrentTradeTicket = order self.LogStatus( orderEvent, "Submitted", with_port=False, with_order=False) def OnOrderFilled(self, orderEvent, order): self.Transactions.CancelOpenOrders(self.symbol) self.HoldingsAdj = self.Holdings self.TrailStopLossTicket = None self.TrailStopLossPrice = None self.LogStatus( orderEvent, with_port=LP, with_order=LO, with_trade=True) if order.Tag in OpenTradeTags: self.CurrentTradeTicket = None self.CurrentTrade = order self.SetTakeProfit(order) self.SetStopLoss(order, trade=self.StopLossTrade) self.SetTrailStopLoss(order, trade=self.TrailStopLossTrade) if order.Tag in CloseTradeTags: self.CurrentTrade = None self.CurrentTradeTicket = None # Settings def Initialize(self): self.SetCash(100000) self.SetTime() self.SetParameters() self.SetSecurities() self.SetIndicators() self.WarmUpIndicators() self.SetLogger() def SetTime(self): end = self.GetParam("End Date", False) end = strptime(end) if end else datetime.now() start = self.GetParam("Start Date", "2018-01-01", strptime) self.SetStartDate(start) self.SetEndDate(end) def SetParameters(self): # External self.symbol = self.GetParam("Symbol", "UVXY") self.Holdings = self.GetParam("Holdings", 0.95, float) self.Interval = self.GetParam("Interval", 0.1, float) self.ZL = self.GetParam("ZL", .05, float) self.FlutterThreshold = self.GetParam("Flutter", 0, Decimal) self.TakeProfit = self.GetParam("TP", 0, Decimal) self.StopLoss = self.GetParam("SL", 0, Decimal) self.TrailStopLoss = self.GetParam("TSL", 0, Decimal) self.StopLossTrade = self.GetParam("CDSL", "no") == "yes" self.TrailStopLossTrade = self.GetParam("CDTSL", "no") == "yes" self.SmaReso = self.GetParam("SMA", "50d", parse_resolution) # Internal self.TrailStopLossThresh = .0001 # self.GetParam("TSL Update", .05, float) self.HoldingsBook = {} self.HoldingsAdj = self.Holdings self.InitialShort = False self.CurrentPrice = None self.CurrentMae = None self.Queue = deque(maxlen=2) self.LastPrice = None self.LastMae = None self.CurrentTrade = None self.CurrentTradeTicket = None self.TrailStopLossTicket = None self.TrailStopLossPrice = None self.EventOrders = [] self.Logs = [] def SetSecurities(self): self.AddEquity(self.symbol, Resolution.Hour, Market.USA, True, 1, True) def SetLogger(self): self.LogFrom = self.GetParam("Log from", False) self.LogTo = self.GetParam("Log to") self.LogEnabled = False # Indicators def SetIndicators(self): unit, attr = self.SmaReso resolution = getattr(Resolution, attr) self.sma = self.SMA(self.symbol, unit, resolution) def WarmUpIndicators(self): self.Debug("Warming up indicators...") unit, attr = self.SmaReso resolution = getattr(Resolution, attr) tradeBarHistory = self.History([self.symbol], unit, resolution) for index, tradeBar in tradeBarHistory.loc[self.symbol].iterrows(): self.sma.Update(index, tradeBar["close"]) # MA Envelopes def GenerateIntervals(self): index = int() start = self.LowestEnvelopeLine while True: yield Decimal(start), index start += self.Interval index += 1 def NameMae(self, index): line = (self.LowestEnvelopeLine + float(index) * self.Interval) - 1 return "{:.0f}%".format(line * 100) def ComputeMaeLog(self, ma, price): lines = [] for name, line in generate_mae_log(float(ma)): item = (name, line) lines.append(item) if line > price: break return lines def ComputeMae(self, base, price): EnvelopeLines = [] for interval, index in self.GenerateIntervals(): line = base * interval EnvelopeLines.append((index, line)) if price < line: return EnvelopeLines # Flutter def inFlutterZone(self, price): lower, upper = self.ComputeFlutterZone() return within_range(lower, price, upper) def ComputeFlutterZone(self): return compute_envelope( self.CurrentTrade.Price, self.FlutterThreshold, ) # Strategy def Indicate(self): buy, sell = False, False if self.Queue.maxlen == len(self.Queue): buy, sell = np.argmax(self.Queue), np.argmin(self.Queue) return buy, sell def CompareMae(self, current, last): '''Returns signal as boolean tuple (buy, sell)''' # if price crossed aboved envelope line, then signal to buy if current > last: return True, False # else if price crossed below envelope line, then signal to sell elif current < last: return False, True # else do not signal to buy or sell since price hasn't crossed an envelope line else: return False, False def Trade(self, buy, sell, tag=None, logstrat=True): _tag = OrderTag.Short if sell else OrderTag.Long o = Decimal(1.001 if sell else 0.999) p = self.CurrentPrice.Close * o h = self.HoldingsAdj * (-1 if sell else 1) q = self.CalculateOrderQuantity(self.symbol, h) self.CurrentTradeTicket = self.PlaceOrder( quantity=q, price=p, tag=tag or _tag, stop=False) if logstrat: self.LogStrategy(self.CurrentTradeTicket) def CouldTrade(self): ExchangeOpen = self.Securities[self.symbol].Exchange.ExchangeOpen isEmpty = self.IsEmpty(self.symbol) and self.CurrentTradeTicket is None isShort, isLong = self.IsShort(self.symbol), self.IsLong(self.symbol) couldBuy, couldSell = isEmpty or isShort, isEmpty or isLong return couldBuy, couldSell def _Indicate(self): buy, sell = False, False if self.LastMae: currentMae = parse_mae_name(self.CurrentMae[0]) lastMae = parse_mae_name(self.LastMae[0]) shouldBuy, shouldSell = self.CompareMae(currentMae, lastMae) couldBuy, couldSell = self.CouldTrade() buy, sell = shouldBuy and couldBuy, shouldSell and couldSell return buy, sell def InitialShortTrade(self): buy, sell = False, True self.Trade(buy, sell, OrderTag.InitialShort, logstrat=False) # Loggers def LogEnable(self): log_range = (self.LogFrom, self.LogTo) if self.LogFrom: self.LogEnabled = within_ranges(self.Time, log_range) def TradeLogs(self, order): logs = [] status = "Opened" if order.Tag in OpenTradeTags: status += " {:.0f}%".format( self.HoldingsBook[order.Tag] * 100) if order.Tag in self.HoldingsBook else '' logs.extend(log.order_sum(self.Time, status, order)) if self.CurrentTrade is not None: tr = self.ComputeChange(order.Price, self.CurrentTrade.Price, self.CurrentTrade.Direction) lastOrder = self.Transactions.GetOrderById(self.CurrentTrade.Id) l = log.order_sum(self.Time, "Closed", lastOrder, trade_return=tr) logs.extend(l) return logs def LogStatus( self, orderEvent, status="", with_order=False, with_port=False, with_trade=False, ): order = self.Transactions.GetOrderById(orderEvent.OrderId) if self.LogEnabled: logs = [] if with_trade: logs.extend(self.TradeLogs(order)) else: if order.Tag in OpenTradeTags: status += " {:.0f}%".format( self.HoldingsBook[order.Tag] * 100) if order.Tag in self.HoldingsBook else '' is_tsl = OrderTag.TrailStopLoss in order.Tag p = self.TrailStopLossPrice if is_tsl else None logs.extend(log.order_sum(self.Time, status, order, price=p)) if with_order: logs.extend(log.order(orderEvent, order)) if with_port: logs.extend( log.portfolio( self.Time, self.Portfolio, self.PortQuant(order.Symbol.Value), )) if len(logs) > 1: logs.insert(0, "...") self.Debug(log.join(logs)) def LogStrategy(self, orderTicket=None): if orderTicket and self.LogEnabled: self.Logs = log.strategy( self.CurrentPrice, last_price=self.LastPrice, sma=self.sma, sma_reso=self.SmaReso, curr_mae=self.CurrentMae, last_mae=self.LastMae, header=log.header(orderTicket), ) + self.Logs self.Debug(log.join(self.Logs)) # Orders def PlaceOrder(self, quantity, price=None, tag=None, stop=None, market=None): is_open = self.IsExchangeOpen(self.symbol) a = False or (True if True else None) if market is None: market = True if is_open else False method = self.GetOrderMethod(stop, market) params = order_params(self.symbol, quantity, price, tag, stop, market) return method(*params) def GetOrderMethod(self, stop=True, market=True): if not stop and market: return self.MarketOrder elif stop and market: return self.StopMarketOrder elif not stop and not market: return self.LimitOrder else: return self.StopLimitOrder def SetTakeProfit(self, order): if self.TakeProfit != 0 and order is not None: trade = self.TakeProfit < 0 d = -1 if order.Direction else 1 o = abs(self.TakeProfit) * d p = order.Price * (1 + o) q = self.CalculateOrderQuantity(order.Symbol.Value, 0) self.Debug("Take profit price: {:.2f}".format(p)) ticket = self.PlaceOrder( quantity=q, price=p, tag=OrderTag.TakeProfit, stop=False, market=False) def SetStopLoss(self, order, trade=False): if self.StopLoss != 0 and order is not None: trade = self.StopLoss < 0 d = 1 if order.Direction else -1 o = abs(self.StopLoss) * d p = order.Price * (1 + o) h = self.HoldingsAdj * d if trade else 0 q = self.CalculateOrderQuantity(order.Symbol.Value, h) tag = OrderTag.LongStopLoss if order.Direction else OrderTag.ShortStopLoss tag = tag if trade else OrderTag.StopLoss ticket = self.PlaceOrder(quantity=q, price=p, tag=tag, stop=True) def SetTrailStopLoss(self, order, trade=False): if self.TrailStopLoss != 0 and order is not None: trade = self.TrailStopLoss < 0 d = 1 if order.Direction else -1 o = abs(self.TrailStopLoss) * d p = self.TrailStopLossPrice p = order.Price * (1 + o) if p is None else p h = self.HoldingsAdj * d if trade else 0 q = self.CalculateOrderQuantity(order.Symbol.Value, h) tag = OrderTag.LongTrailStopLoss if order.Direction else OrderTag.ShortTrailStopLoss tag = tag if trade else OrderTag.TrailStopLoss self.TrailStopLossTicket = self.PlaceOrder( quantity=q, price=p, tag=tag, stop=True) self.TrailStopLossPrice = p def UpdateTrailStopLoss(self): if self.TrailStopLossTicket is not None: d = 1 if self.CurrentTrade.Direction else -1 o = abs(self.TrailStopLoss) * d p = self.CurrentPrice.Close * (1 + o) diff, ratio = self.ComputeChange( p, self.TrailStopLossPrice, self.CurrentTrade.Direction, ) if ratio > self.TrailStopLossThresh: info = "Updating {} from ${:.2f} to ${:.2f} after ${:.2f} change ({:.2f}%)..." info = info.format(self.TrailStopLossTicket.Tag, self.TrailStopLossPrice, p, diff, ratio * 100) updateOrder = UpdateOrderFields() updateOrder.StopPrice = p updateOrder.LimitPrice = p self.TrailStopLossTicket.Update(updateOrder) self.TrailStopLossPrice = p def RetryInvalidOrder(self, order): self.HoldingsAdj = self.HoldingsAdj - 0.05 if OrderTag.TrailStopLoss in order.Tag: trade = self.TrailStopLossTrade self.SetTrailStopLoss(self.CurrentTrade, trade=trade) elif OrderTag.StopLoss in order.Tag: trade = self.StopLossTrade self.SetStopLoss(self.CurrentTrade, trade=trade) elif OrderTag.TakeProfit == order.Tag: self.SetTakeProfit(self.CurrentTrade) else: self.RetryTrade(order) def RetryTrade(self, order): buy, sell = not order.Direction, order.Direction orderTicket = self.Trade(buy, sell, order.Tag, logstrat=False) def ComputeChange(self, closePrice, openPrice, direction): diff = closePrice - openPrice diff = diff * (-1 if direction else 1) ratio = diff / openPrice return diff, ratio # Extensions def IsExchangeOpen(self, symbol): return self.Securities[symbol].Exchange.ExchangeOpen def NoOpenOrders(self, symbol): return self.Transactions.GetOpenOrders(symbol) def IsEmpty(self, symbol): return not self.Portfolio[symbol].Invested def IsShort(self, symbol): return self.Portfolio[symbol].Invested and self.Portfolio[ symbol].IsShort def IsLong(self, symbol): return self.Portfolio[symbol].Invested and self.Portfolio[symbol].IsLong def PortQuant(self, symbol): invested = not self.IsEmpty(symbol) return self.Portfolio[symbol].Quantity if invested else 0 def GetParam(self, value, default=None, dtype=None): value = self.GetParameter(value) or default return value if dtype is None else dtype(value) def QuickLog(self, *a): if self.LogEnabled: self.Debug(("{}" * len(a)).format(*a)) # Helpers def IsReady(self, bar): if not bar: self.Debug("Waiting on trade bar...") return False if not self.sma.IsReady: self.Debug("Waiting on SMA...") return False return True def IsWakeUp(self): h = self.CurrentPrice.Time.hour % 1 == 0 m = self.CurrentPrice.Time.minute == 1 return h #and m
from datetime import datetime, timedelta from dateutil.parser import parse all = [ "TIMEDELTA", "withinDateRange", "strptime", "parse_timedelta", "parse_resolution", "OrderTag", "OpenOrderTypes", "CloseOrderTypes", "TradeTags", "within_ranges", ] TIMEDELTA = dict() for k, v in [ ("Second", "seconds"), ("Minute", "minutes"), ("Hour", "hours"), ("Daily", "days"), ]: TIMEDELTA[getattr(Resolution, k)] = timedelta(**{v: 1}) withinDateRange = lambda d1, x, d2: d1 <= x and x <= d2 within_range = lambda l, x, u: l <= x and x <= u strptime = lambda x: datetime.strptime(x.strip(), '%Y-%m-%d') isntdigit = lambda char: not str.isdigit(char) sign = lambda x: (1, -1)[x < 0] def within_ranges(time, *ranges): in_range = [ withinDateRange( parse(r[0]), time, parse(r[1]) if r[1] else datetime.now(), ) for r in ranges ] return True if any(in_range) else False def parse_time(string): value = filter(str.isdigit, string) value = int(''.join(value)) unit = filter(isntdigit, string) unit = ''.join(unit).strip().lower() return value, unit def parse_timedelta(string): v, unit = parse_time(string) if unit.startswith("s"): return timedelta(seconds=v) elif unit.startswith("m"): return timedelta(minutes=v) elif unit.startswith("h"): return timedelta(hours=v) elif unit.startswith("d"): return timedelta(days=v) elif unit.startswith("w"): return timedelta(weeks=v) else: raise Exception("Could not parse timedelta") def parse_resolution(string): v, unit = parse_time(string) if unit.startswith("s"): unit = "Second" elif unit.startswith("m"): unit = "Minute" elif unit.startswith("h"): unit = "Hour" elif unit.startswith("d"): unit = "Daily" else: raise Exception("Could not parse resolution") return v, unit within_range = lambda l, x, u: l <= x and x <= u def compute_envelope(price, thresh): lower, upper = 1 - thresh, 1 + thresh return price * lower, price * upper def within_flutter_zone(current_price, last_trade_price, thresh=0.01): lower, upper = compute_envelope( last_trade_price, thresh, ) print("Upper: {}\nLower: {}".format(lower, upper)) return within_range(lower, current_price, upper) def name_mae(index, interval): return "{:.0f}%".format(index * interval * 100) def generate_mae_log(ma, lower=-300, interval=0.05, upper=1000): line, lines = ma, [ma] while len(lines) < abs(lower) + 1: line = line - line * interval lines.append(line) for i, l in enumerate(sorted(lines)): name = name_mae(i + lower, interval) yield name, l line, lower = ma, 1 while lower <= upper: line = line + line * interval name = name_mae(lower, interval) yield name, line lower += 1 def compute_mae_log(ma, price): lines = [] for name, line in generate_mae_log(ma): lines.append((name, line)) if line > price: break return lines def parse_mae_name(string): return float(string.strip("%")) def order_params(symbol, quantity, price=None, tag=None, stop=None, market=None, price_offset=None): params = [symbol, quantity] if not stop and market: params.append(False) if stop: params.append(price) if not market: if price_offset is not None: price = price * price_offset params.append(price) params.append(tag) return params class OrderTag: Short = "short" Long = "long" StopLoss = "stop-loss" TrailStopLoss = "trailing stop-loss" TakeProfit = "take-profit" InitialShort = "initial short" LongStopLoss = "stop-loss long" ShortStopLoss = "stop-loss short" LongTrailStopLoss = "trailing stop-loss long" ShortTrailStopLoss = "trailing stop-loss short" OpenTradeTags = [ OrderTag.Long, OrderTag.Short, OrderTag.InitialShort, OrderTag.ShortStopLoss, OrderTag.LongStopLoss, OrderTag.LongTrailStopLoss, OrderTag.ShortTrailStopLoss, ] CloseTradeTags = [ OrderTag.TrailStopLoss, OrderTag.StopLoss, OrderTag.TakeProfit ] def clock_modulo(h, m, i): M = 60 ih = i // M vh = 0 == h % ih if ih != 0 else True vm = m == i % M return vh and vm