Overall Statistics
Total Trades
10871
Average Win
0.16%
Average Loss
-0.15%
Compounding Annual Return
61.186%
Drawdown
20.000%
Expectancy
0.146
Net Profit
211.863%
Sharpe Ratio
2.614
Loss Rate
44%
Win Rate
56%
Profit-Loss Ratio
1.04
Alpha
0.481
Beta
0.683
Annual Standard Deviation
0.189
Annual Variance
0.036
Information Ratio
2.509
Tracking Error
0.189
Treynor Ratio
0.725
Total Fees
$0.00
import json
import math
import datetime
from collections import OrderedDict

from QuantConnect import Orders
from QuantConnect.Brokerages import *
from QuantConnect.Orders.Fees import *

class ZeroSlippageModel:
    def __init__(self, algorithm):
        self.algorithm = algorithm
    def GetSlippageApproximation(self, asset, order):
        return 0

class Algorithm(QCAlgorithm):

    def Initialize(self):

        self.SetStartDate(2016, 1, 5)
        self.SetEndDate(2018, 5, 23)
        self.SetCash(1_000_000)

        # json_data = self.Download("http://api.analyticsventures.com/maxwell-verbs-10.json") # 2016-01-05 -- 2018-05-23
        json_data = self.Download("http://api.analyticsventures.com/maxwell-verbs-10-limitorders.json")
        # json_data = self.Download("http://api.analyticsventures.com/maxwell-verbs-30.json")
        # json_data = self.Download("http://api.analyticsventures.com/maxwell-verbs-10-limit-20.json")
        # json_data = self.Download("http://api.analyticsventures.com/maxwell-verbs-10-limit-1.json")
        # json_data = self.Download("http://api.analyticsventures.com/test-verbs.json") # 2018-06-07 -- 2018-07-12

        self.verbs = OrderedDict(json.loads(json_data))

        self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage)

        self.SetSecurityInitializer(self.ZeroFeesSecurityInitializer)

        all_tickers = sorted(set([l[0] for x in self.verbs.values() for l in x]))
        for ticker in all_tickers:
            security = self.AddEquity(ticker)
            security.SetSlippageModel(ZeroSlippageModel(self))

        # We have to put in the MOCs (if we're using MOCs) 16 or more mins before close,
        # or else the behavior is wildly different and we only occasionally get to see
        # a brief error explaining why.
        self.Schedule.On(
            self.DateRules.EveryDay(),
            self.TimeRules.At(15, 58),
            self.placeExitOrders
        )

        self.Schedule.On(
            self.DateRules.EveryDay(),
            self.TimeRules.At(9, 35),
            self.placeEntryOrders
        )

        self.UniverseSettings.Resolution = Resolution.Minute

        self.AddUniverse("earnings-universe", self.universeSelector)


    def ZeroFeesSecurityInitializer(self, security):
        """ Initialize the security with zero fees. """
        security.SetFeeModel(ConstantFeeModel(0))
        return security


    def universeSelector(self, dt):

        today = dt.date().isoformat()

        if today not in self.verbs.keys():
            self.Debug(f"universe: no trades today.")
            self.verbs_today = []
            self.universe = []
        else:
            self.verbs_today = self.verbs[today]
            self.universe = [x[0] for x in self.verbs_today]
            tickers_for_today = ', '.join(self.universe)
            self.Debug(f"universe: {tickers_for_today}")

        for ticker in self.universe:
            security = self.AddEquity(ticker)
            security.SetSlippageModel(ZeroSlippageModel(self))

        #self.placeEntryOrders()

        return self.universe


    def getPrice(self, symbol):
        price = float(self.Securities[symbol].Price)
        return price


    def placeEntryOrders(self):

        """
            Can call this more or less any time the market isn't open,
            but the dates work out better if we call it in the "after
            midnight" time slice of the market being closed, rather
            than in the "before midnight" time slice. In practice
            this will end up being called by some part of the QC
            event sceduling system at 00:00:00 (i.e., midnight).
        """

        if len(self.universe) == 0:
            return

        self.orders = []

        active_prices = {}
        active_verbs = {}

        for ticker, verb in self.verbs_today:

            try:
                price = self.getPrice(ticker)
            except Exception as e:
                self.Debug(f"[!] entry: getting price failed for ticker '{ticker}'.")
                continue

            #if price < 1:
            #    # This is an experiment with Yury to try to minimize the amount we lose to fees.
            #    continue

            if price < 0:
                self.Debug(f"[!] entry: price {price} for ticker {ticker} is negative.")
                continue

            if price == 0:
                self.Debug(f"[!] entry: price {price} for ticker {ticker} is zero.")
                continue

            active_prices[ticker] = price
            active_verbs[ticker] = verb

        num_tickers = len(active_verbs)

        if num_tickers == 0:
            self.Debug(f"[!] entry: had {len(self.universe)} tickers for today but getting the price failed for all of them.")
            return

        current_cash = float(self.Portfolio.Cash)

        frac_per_day = 0.90

        cash_per_ticker = (frac_per_day * current_cash) / num_tickers

        for ticker, verb in active_verbs.items():

            price = active_prices[ticker]

            try:
                sign = +1 if verb == 'long' else -1
                shares = (+sign) * math.floor(cash_per_ticker / price)

                if shares == 0: continue

                #self.Debug(f"entry: placing market order for {shares} shares of {ticker}")
                #order = self.MarketOnOpenOrder(ticker, shares, asynchronous = True)
                #order = self.MarketOnOpenOrder(ticker, shares)

                #order = self.MarketOrder(ticker, shares)
                limitPrice = price * (1 + (-sign) * 0.001)
                order = self.LimitOrder(ticker, shares, limitPrice)

                order.sign = sign
                order.shares = shares
                order.ticker = ticker
                self.orders.append(order)
            except:
                pass
                #self.Debug(f"nightTimeCallback: Couldn't place MarketOrder for {ticker}")


    def placeExitOrders(self):

        """
            Can be called any time when the market is open.
            In practice, we'll usually end up calling this
            close to market close, since its job is to place
            the orders that will sell off our positions at
            the end of the day. If we're using market on close
            orders, we can call this any time the market is open.
            If we're using a strategy that just liquidates all our
            existing holdings at the moment we call self.Liquidate(),
            we'll have to manually ensure this is called as close to
            market close as possible.
        """

        if len(self.verbs_today) == 0:
            # self.Debug(f"exit: No verbs today. Doing nothing.")
            return

        for order in self.orders:
            ticker = order.ticker
            #shares1 = -order.shares
            #shares2 = -self.Portfolio[ticker].Quantity
            #self.Debug(f"exit: {ticker}: shares1 is {shares1}, shares2 is {shares2}")
            #shares = -self.Portfolio[ticker].Quantity
            try:
                #self.MarketOnCloseOrder(ticker, shares)
                self.Liquidate(ticker)
                #self.Debug(f"exit: Placed MarketOnCloseOrder for {shares} shares of {ticker}")
            except:
                self.Debug(f"exit: Couldn't place MarketOnCloseOrder for {ticker}")


    def decodeEventType(self, orderEvent):
        """
            Turns the numeric values we receive from
            orderEvent.Status into a human readable string so we
            can tell what kind of event is being passed when the
            OnOrderEvent method is automatically called on fills
        """

        statuses = ('New', 'Submitted', 'PartiallyFilled', 'Filled', 
                    'Canceled', 'None', 'Invalid', 'CancelPending')

        order_statuses = {
            getattr(Orders.OrderStatus, status): status
            for status in statuses
        }

        return order_statuses[orderEvent.Status]


    def OnOrderEvent(self, orderEvent):

        symbol = orderEvent.Symbol
        filled = orderEvent.FillQuantity
        eventType = self.decodeEventType(orderEvent)

        if eventType in ('Submitted',):
            return

        if eventType == 'Filled':
            # self.Debug(f"OnOrderEvent: Got {eventType} event for {symbol}, fill quantity: {filled}.")
            return

        if eventType == 'Invalid':
            # self.Debug(f"OnOrderEvent: Got {eventType} event for {symbol}.")
            return