| Overall Statistics |
|
Total Trades 12327 Average Win 0.31% Average Loss -0.33% Compounding Annual Return -22.286% Drawdown 50.700% Expectancy -0.024 Net Profit -45.159% Sharpe Ratio -0.624 Loss Rate 49% Win Rate 51% Profit-Loss Ratio 0.92 Alpha -0.973 Beta 38.739 Annual Standard Deviation 0.32 Annual Variance 0.103 Information Ratio -0.687 Tracking Error 0.32 Treynor Ratio -0.005 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-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, 31),
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)
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