| Overall Statistics |
|
Total Trades 499 Average Win 0.05% Average Loss -0.09% Compounding Annual Return -3.759% Drawdown 33.400% Expectancy -0.608 Net Profit -7.367% Sharpe Ratio 0.026 Loss Rate 75% Win Rate 25% Profit-Loss Ratio 0.56 Alpha 0.124 Beta -7.042 Annual Standard Deviation 0.28 Annual Variance 0.078 Information Ratio -0.033 Tracking Error 0.28 Treynor Ratio -0.001 Total Fees $1706.35 |
from QuantConnect.Data.UniverseSelection import *
import pandas as pd
from decimal import Decimal
class RSISignalAlgorithm(QCAlgorithm):
def Initialize(context):
# // Set parameters for RSI Indicator and Signals, thereof.
context.rsiPeriod = 21
context.lowerThreshold = 40
context.upperThreshold = 80
# // Other parameters required for backtesting/project
context.MinDollarVolume = 10000000 # 10M ADV
context.PerTradeEquity = Decimal(0.01) # Percent of Equity per Trade
context.MinCashHoldings = Decimal(0.02)
context.StopLossPercent = Decimal(0.1) # 10% Stop Loss
context.TrailingLeadPercent = Decimal(0.1) # Trailing Stop after 10%
context.TrailingStopPercent = Decimal(0.05) # at 5%
# // Set the initial cash, start, and end dates for backtesting
context.SetCash(1000000) # 1M starting capital
context.SetStartDate(2016, 7, 1) # Backtest start date
context.SetEndDate(2018, 7, 1) # Backtest end date
# // Subscribe to data for securities returned in our selected universe
# // We are deliberately setting Leverage to 1, as leverage was not
# // specified for this project.
context.UniverseSettings.Resolution = Resolution.Daily
context.UniverseSettings.MinimumTimeInUniverse = 0
context.UniverseSettings.Leverage = 1
context.UniverseSettings.FillForward = False
context.AddUniverse(context.universe_filter_coarse)
# // Use raw prices for all securities.
# context.SetSecurityInitializer(
# lambda x: x.SetDataNormalizationMode(DataNormalizationMode.Raw))
# // Placeholder to collect indicator and other data for securities
context.Data = {}
# // Placeholder to collect various stats for securities for later use
context.TrailingLeads = {}
context.StopLossOrders = {}
context.TrailingLeadReached = {}
# // Finally, set a warm up period for the entire universe
context.SetWarmUp(context.rsiPeriod)
def universe_filter_coarse(context, coarse_data):
# // Select all securities with Dollar Volume above specified threshold
# // We make sure to arrange them according to decreasing ADV.
selected = filter(lambda x: x.DollarVolume > context.MinDollarVolume, coarse_data)
selected = sorted(list(selected), key=lambda x: x.DollarVolume, reverse=True)
# // Initialize indicator and other data for all selected securities
for cf in selected:
if cf.Symbol not in context.Data:
context.Data[cf.Symbol] = SymbolData(cf.Symbol, context.rsiPeriod,
context.upperThreshold, context.lowerThreshold)
context.Data[cf.Symbol].update(cf)
# // Since, we are going to buy securities with 1% of our equity on each
# // trade, and we will be only doing this when we have enough cash
# // available, we can return all available securities for our universe.
return [ x.Symbol for x in selected ]
# // Whenever new securities are added or removed in our universe,
# // this function will be called with corresponding changes.
def OnSecuritiesChanged(context, changes):
for security in changes.RemovedSecurities:
data = context.Data[security.Symbol]
# // Sell a security, if we are invested in it and indicator
# // value crossed the specified threshold for selling.
if security.Invested and data.aboveThreshold:
# context.Debug("Selling Ticker = %s, with Indicator Value = %s" % (data.symbol, data.rsi))
context.Liquidate(security.Symbol, "SELL Threshold")
context.mark_security_sold(security.Symbol)
for security in changes.AddedSecurities:
data = context.Data[security.Symbol]
# // We will buy a security with 1% percent of our equity, when:
# // - we have enough cash available (say, 2% of our equity)
# // - indicator value crossed below the specified threshold for buy
# // - we are not already holding the security (not specified in project)
cash = context.Portfolio.Cash
equity = context.Portfolio.TotalPortfolioValue
cash_threshold = context.MinCashHoldings * equity
if not security.Invested and data.belowThreshold and cash > cash_threshold:
quantity = int(context.PerTradeEquity * equity/data.price)
# // buy with a LimitOrder? or MarketOnOpen order? - Not sure.
# // Going with MarketOnOpen order at the moment.
context.MarketOnOpenOrder(security.Symbol, quantity, "BUY Threshold")
# context.Debug("Buying Ticker = %s, with Indicator Value = %s" % (data.symbol, data.rsi))
# // Also, since we need stop loss - place a StopMarketOrder right away.
# // We, also, need to store these stop market orders in a dict,
# // so that we can cancel them later, if required.
stop_price = data.price * (1 - context.StopLossPercent)
trailing_lead = data.price * (1 + context.TrailingLeadPercent)
context.StopLossOrders[data.symbol] = context.StopMarketOrder(
data.symbol, -quantity, stop_price, "STOP LOSS")
context.TrailingLeadReached[data.symbol] = False
context.TrailingLeads[data.symbol] = trailing_lead
# // This function is called on each tick, and is a good place to add logic for
# // trailing stop loss functionality. At each tick, for each security, we
# // check if we have reached a new trailing lead price, otherwise, we check
# // if we have hit trailing stop price and sell the security as well as cancel
# // any stop loss we may have (this is very important).
def OnData(context, _data):
for symbol, data in context.Data.items():
# // check if we are holding the security?
# // At every buy, we issue a stop loss order and cache this ticket.
if context.Portfolio[symbol].HoldStock:
if symbol in context.StopLossOrders:
sl_ticket = context.StopLossOrders[symbol]
tp_reached = context.TrailingLeadReached[symbol]
trailing_lead = context.TrailingLeads[symbol]
trailing_stop = trailing_lead * (1 - context.TrailingStopPercent)
# // stop loss was filled, we should remove any tracking
# // information for this symbol.
if sl_ticket.Status == "Filled":
context.mark_security_sold(symbol)
# // Trailing Lead was hit earlier and now price is below the
# // trailing stop price - we need to sell and cancel our
# // stop limit order.
elif tp_reached and data.price < trailing_stop:
context.Liquidate(symbol, "TRAIL STOP")
context.mark_security_sold(symbol)
# // Trailing Stop has not yet been hit. If price exceeds
# // trailing lead, we update it.
elif data.price >= trailing_lead:
trailing_lead = data.price
context.TrailingLeadReached[symbol] = True
# else:
# context.Debug("Security in Portfolio without a StopLoss: %s" % symbol)
# // when a security is marked as sold, we should ensure that we delete any
# // remnants of it from the cached data/dicts we preserved for later use.
# // Also, cancel StopLoss order created for this security when buying.
def mark_security_sold(context, symbol):
sl_ticket = context.StopLossOrders[symbol]
if sl_ticket.Status != "Filled":
sl_ticket.Cancel()
context.StopLossOrders.pop(symbol)
context.TrailingLeads.pop(symbol)
context.TrailingLeadReached.pop(symbol)
class SymbolData(object):
def __init__(self, symbol, rsiPeriod, upperThreshold, lowerThreshold):
self.symbol = symbol
self.price = 0
self.rsi = RelativeStrengthIndex(rsiPeriod)
self.upperThreshold = upperThreshold
self.lowerThreshold = lowerThreshold
self.aboveThreshold = False
self.belowThreshold = False
def update(self, data):
self.price = data.Price
if self.rsi.Update(data.EndTime, data.Price):
rsi = self.rsi.Current.Value
self.aboveThreshold = rsi > self.upperThreshold
self.belowThreshold = rsi < self.lowerThreshold