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