| Overall Statistics |
|
Total Trades 132 Average Win 0.10% Average Loss -0.11% Compounding Annual Return -1.659% Drawdown 1.600% Expectancy -0.057 Net Profit -0.428% Sharpe Ratio -0.775 Loss Rate 50% Win Rate 50% Profit-Loss Ratio 0.89 Alpha -0.069 Beta 2.768 Annual Standard Deviation 0.021 Annual Variance 0 Information Ratio -1.712 Tracking Error 0.02 Treynor Ratio -0.006 Total Fees $0.00 |
import math
import datetime
import QuantConnect
from QuantConnect import Time
from QuantConnect import Orders
from QuantConnect.Brokerages import *
from QuantConnect.Orders.Fees import *
from QuantConnect.Data.UniverseSelection import *
class Algorithm(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2017, 7, 31)
self.SetEndDate(2017, 11, 1)
self.SetCash(1_000_000)
self.SetSecurityInitializer(self.ZeroFeesSecurityInitializer)
self.ticker = "AAPL"
self.shares = 1000
aapl = self.AddEquity(self.ticker, Resolution.Minute, fillDataForward = True, extendedMarketHours = True)
spy = self.AddEquity("SPY", Resolution.Minute, fillDataForward = True, extendedMarketHours = True)
# You can control the Data Normalization Mode for each asset individually. This is done with the: security.SetDataNormalizationMode() method.
# The accepted values are: Raw, Adjusted, SplitAdjusted and TotalReturn.
aapl.SetDataNormalizationMode(DataNormalizationMode.Raw)
spy.SetDataNormalizationMode(DataNormalizationMode.Raw)
# If we schedule this event at 9:30, and place a LimitOrder once 9:30
# arrives, Quantconnect ends up returning the open of the previous day's
# 15:59 minute bar when we request the open price. This is unfortunate,
# and different from what they do when we place a MarketOrder.
# As a result, we'll place a LimitOrder at 9:31, and compare the results
# to a local backtest in which we place a LimitOrder at the same time.
self.Schedule.On(
self.DateRules.EveryDay("SPY"),
self.TimeRules.At(9, 31),
self.placeEntryOrders
)
self.Schedule.On(
self.DateRules.EveryDay("SPY"),
self.TimeRules.At(15, 59),
self.placeExitOrders
)
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 isSkippedDay(self):
""" We need to skip the first day to let QuantConnect warm up,
or else it thinks the price of any ticker is 0 on the first
day of the backtest.
"""
year, month, day = self.Time.year, self.Time.month, self.Time.day
if year == 2017 and month == 7 and day == 31:
return True
else:
return False
def placeEntryOrders(self):
self.Debug(f"{self.Time}: placeEntryOrders: entering")
if self.isSkippedDay():
self.Debug(f"{self.Time}: placeEntryOrders: exiting early")
return
current_cash = float(self.Portfolio.Cash)
ticker = self.ticker
shares = self.shares
price = self.getPrice(ticker)
stoploss_percent = 0.01
stoploss_price = price * (1 - stoploss_percent)
stoploss_price = round(stoploss_price, 2)
try:
volume = self.getVolume(self.ticker)
self.Log(f"{self.Time}: placeEntryOrders: placing order for {shares} shares of {ticker} at price {price}, stop loss price {stoploss_price} (vol: {volume})")
self.MarketOrder(ticker, shares)
self.StopMarketOrder(ticker, -shares, stoploss_price)
except ZeroDivisionError:
return
def placeExitOrders(self):
self.Debug(f"{self.Time}: placeExitOrders: entering")
if self.isSkippedDay():
self.Debug(f"{self.Time}: placeExitOrders: exiting early")
return
ticker = self.ticker
volume = self.getVolume(ticker)
price = self.getClose(ticker)
self.Log(f"{self.Time}: placeExitOrders: Liquidating {ticker} at price {price} (vol: {volume})")
self.Liquidate(ticker)
def universeSelector(self, dt):
today = dt.date().isoformat()
return [self.ticker]
def getVolume(self, symbol):
volume = float(self.Securities[symbol].Volume)
return volume
def getPrice(self, symbol):
price = float(self.Securities[symbol].Price)
return price
def getClose(self, symbol):
price = float(self.Securities[symbol].Close)
return price
def getOpen(self, symbol):
price = float(self.Securities[symbol].Open)
return price
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):
ticker = orderEvent.Symbol.Value # Need the ".Value" to actually make this a real str object
filled = orderEvent.FillQuantity
eventType = self.decodeEventType(orderEvent)
order = self.Transactions.GetOrderById(orderEvent.OrderId)
orderId = orderEvent.OrderId
createdTime = order.CreatedTime
# self.Debug(f"{self.Time}: OnOrderEvent: Got '{eventType}' event for {ticker}")
if eventType == 'Filled':
fillQuantity = orderEvent.FillQuantity
fillPrice = orderEvent.FillPrice
side = 'BUY' if fillQuantity > 0 else 'SELL'
nowPrice = self.getPrice(self.ticker)
nowOpen = self.getOpen(self.ticker)
nowClose = self.getClose(self.ticker)
nowVolume = self.getVolume(self.ticker)
self.Debug(f"{self.Time}: OnOrderEvent: fillPrice for {side} order is {fillPrice}, (shares: {fillQuantity})")
self.Debug(f" * Now: open = {nowOpen}, close = {nowClose}, price = {nowPrice} (vol: {nowVolume})")