| Overall Statistics |
|
Total Trades 149 Average Win 3.82% Average Loss -0.97% Compounding Annual Return 297.274% Drawdown 11.100% Expectancy 0.702 Net Profit 166.936% Sharpe Ratio 3.713 Loss Rate 66% Win Rate 34% Profit-Loss Ratio 3.94 Alpha -1.156 Beta 142.726 Annual Standard Deviation 0.318 Annual Variance 0.101 Information Ratio 3.663 Tracking Error 0.318 Treynor Ratio 0.008 Total Fees $5946.30 |
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.minute % 30 == 0
m = self.CurrentPrice.Time.minute == 1
return h #and mfrom 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.03, 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