| Overall Statistics |
|
Total Trades 730 Average Win 4.11% Average Loss -4.47% Compounding Annual Return 1578.626% Drawdown 44.400% Expectancy 0.220 Net Profit 1596.784% Sharpe Ratio 16.345 Probabilistic Sharpe Ratio 96.370% Loss Rate 36% Win Rate 64% Profit-Loss Ratio 0.92 Alpha 0 Beta 0 Annual Standard Deviation 1.133 Annual Variance 1.284 Information Ratio 16.345 Tracking Error 1.133 Treynor Ratio 0 Total Fees $12861.01 Estimated Strategy Capacity $140000.00 |
import pandas
import json
import io
import constants
STOCK_LIMIT = 3
MINIMUM_VOLUME = 100000
MARGIN_REQUIREMENT = 0.5
SHORT_LOSS_TOLERANCE = 0.33
LONG_LOSS_TOLERANCE = 0.02
EQUAL_WEIGHT = False
STOP_LOSS = False
BUY_INCREASE = False
HEALTH_TIMER = 5
LOSS_TOLERANCE = 0.1
class BurtonTrade(QCAlgorithm):
def Initialize(self):
self.SetCash(10000)
self.SetStartDate(2019, 3, 16)
self.SetEndDate(2020, 3, 15)
self.AddEquity("SPY")
self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Margin)
self.UniverseSettings.ExtendedMarketHours = True
self.DefaultOrderProperties.TimeInForce = TimeInForce.Day
self.AddUniverseSelection(ScheduledUniverseSelectionModel(self.DateRules.EveryDay("SPY"), self.TimeRules.At(9, 25), self.TopMovers))
self.gainers = []
self.openShortOrders = {}
self.closeShortOrders = {}
self.openLongOrders = {}
self.closeLongOrders = {}
self.plotDelta = round(((self.EndDate.replace(tzinfo=None) - self.StartDate.replace(tzinfo=None)).days) * 1440 / 4000)
self.benchmarkPerformance = self.Portfolio.TotalPortfolioValue
self.lastPortfolioHealth = self.Portfolio.TotalPortfolioValue
self.lastBenchmarkValue = None
self.Schedule.On(self.DateRules.EveryDay("SPY"), self.TimeRules.AfterMarketOpen("SPY", 0), self.PlaceTrades)
self.Schedule.On(self.DateRules.EveryDay("SPY"), self.TimeRules.BeforeMarketClose("SPY", 5), self.CloseTrades)
self.Schedule.On(self.DateRules.EveryDay("SPY"), self.TimeRules.Every(timedelta(minutes=self.plotDelta or 1)), self.PlotData)
self.Schedule.On(self.DateRules.EveryDay("SPY"), self.TimeRules.Every(timedelta(minutes=HEALTH_TIMER)), self.PortfolioHealth)
if not self.LiveMode: self.backtestSymbols = pandas.read_csv(io.StringIO(self.Download(constants.BACKTEST_URL)))
def TopMovers(self, dateTime):
symbols = []
if self.LiveMode:
movers = json.loads(self.Download(constants.BENZINGA_URL + str(STOCK_LIMIT * 2)))
gainers = sorted(movers["result"]["gainers"], key=lambda x: x["changePercent"], reverse=True)
for gainer in gainers:
if gainer["volume"] > MINIMUM_VOLUME: symbols.append(Symbol.Create(gainer["symbol"], SecurityType.Equity, Market.USA))
else:
data = self.backtestSymbols
for symbol in data[data.columns[data.columns.get_loc(dateTime.strftime("%F"))]].to_list():
symbols.append(Symbol.Create(symbol.replace(" ", ""), SecurityType.Equity, Market.USA))
return symbols[:STOCK_LIMIT]
def PlaceTrades(self):
self.lastPortfolioHealth = self.Portfolio.TotalPortfolioValue
for kvp in self.ActiveSecurities:
if kvp.Key == "SPY": continue
symbol = kvp.Key
security = kvp.Value
opening = security.Price
try: close = self.History(symbol, 1, Resolution.Daily)['close'][0]
except: continue
self.Debug("Symbol: " + str(symbol) + " Close: " + str(close) + " Opening: " + str(opening))
gain = int(round(((opening - close) / (close or 1)) * 100))
if gain > 10: self.gainers.append(SecurityWeight(security, gain))
totalGain = sum([x.gain for x in self.gainers])
self.Debug("Total Gain: " + str(totalGain))
for gainer in self.gainers:
self.Debug(str(security.Symbol) + " Gain: " + str(gainer.gain) + " Percent: " + str(gainer.gain / totalGain))
security = gainer.security
symbol = security.Symbol
weight = ((gainer.gain / totalGain) if not EQUAL_WEIGHT else (1 / STOCK_LIMIT)) * (1 - MARGIN_REQUIREMENT)
quantity = self.CalculateOrderQuantity(symbol, weight)
openShort = self.LimitOrder(symbol, -quantity, security.Price * 1.05)
self.openShortOrders[str(openShort.OrderId)] = weight
def OnOrderEvent(self, orderEvent):
if orderEvent.Status == OrderStatus.Filled:
symbol = orderEvent.Symbol
orderId = str(orderEvent.OrderId)
fillPrice = orderEvent.FillPrice
fillQuantity = abs(orderEvent.FillQuantity)
if STOP_LOSS and orderId in self.openShortOrders:
gain = next((x for x in self.gainers if x.security.Symbol == symbol), None).gain
#closeShort = self.StopMarketOrder(symbol, fillQuantity, fillPrice * min((1 + SHORT_LOSS_TOLERANCE * gain / 100), 1.5))
#closeShort = self.StopMarketOrder(symbol, fillQuantity, fillPrice * (1 + SHORT_LOSS_TOLERANCE * gain / 100))
stop = fillPrice * (1 + SHORT_LOSS_TOLERANCE * gain / 100)
closeShort = self.StopLimitOrder(symbol, fillQuantity, stop, stop * 1.1)
self.Debug("Short Loss - Symbol: " + str(symbol) + " Gain: " + str(gain) + " Buy Price: " + str(fillPrice) + " Sell Price: " + str(fillPrice * (1 + SHORT_LOSS_TOLERANCE * gain / 100)))
# todo figure out a good, max stopping point (i.e. if it goes up 1000%, no? -- have a maximum gain, that min thing might also be a problem)
# todo stop limit order?
self.closeShortOrders[str(closeShort.OrderId)] = self.openShortOrders[orderId]
if BUY_INCREASE:
if orderId in self.closeShortOrders:
quantity = self.CalculateOrderQuantity(symbol, self.closeShortOrders[orderId])
openLong = self.LimitOrder(symbol, quantity, self.Securities[symbol].Price)
self.openLongOrders[str(openLong.OrderId)] = openLong
self.Debug("Sold Short - Symbol: " + str(symbol) + " Price: " + str(fillPrice))
elif orderId in self.openLongOrders:
self.Debug("Long Order - Symbol: " + str(symbol) + " Buy Price: " + str(fillPrice))
closeLong = self.StopLimitOrder(symbol, -fillQuantity, fillPrice * (1 - LONG_LOSS_TOLERANCE), fillPrice * (1 - LONG_LOSS_TOLERANCE * 2))
self.closeLongOrders[str(symbol)] = CloseLongPosition(closeLong, fillPrice)
elif next((x for x in self.closeLongOrders.values() if str(x.closeLong.OrderId) == orderId), None) is not None:
self.Debug("Long Order - Symbol: " + str(symbol) + " Sold Price: " + str(fillPrice))
del self.closeLongOrders[str(symbol)]
def OnData(self, data):
for symbol, longPosition in self.closeLongOrders.items():
currentPrice = self.Securities[symbol].Close
if currentPrice > longPosition.highestPrice:
self.Debug("Updating Order for Symbol: " + str(symbol) + " at Price: " + str(currentPrice))
updateOrder = UpdateOrderFields()
updateOrder.StopPrice = currentPrice * (1 - LONG_LOSS_TOLERANCE)
updateOrder.LimitPrice = currentPrice * (1 - LONG_LOSS_TOLERANCE * 2)
longPosition.highestPrice = currentPrice
longPosition.closeLong.Update(updateOrder)
def CloseTrades(self):
self.gainers.clear()
self.openShortOrders.clear()
self.closeShortOrders.clear()
self.closeLongOrders.clear()
self.Liquidate()
def PlotData(self):
benchmark = self.Securities["SPY"].Price
if benchmark == 0: return
if self.lastBenchmarkValue is not None: self.benchmarkPerformance = self.benchmarkPerformance * (benchmark / self.lastBenchmarkValue)
self.lastBenchmarkValue = benchmark
self.Plot("Performance", "BurtonTrade", self.Portfolio.TotalPortfolioValue)
self.Plot("Performance", "S&P 500", self.benchmarkPerformance)
def PortfolioHealth(self):
if self.Portfolio.TotalPortfolioValue < self.lastPortfolioHealth * (1 - LOSS_TOLERANCE):
for kvp in self.ActiveSecurities:
symbol = kvp.Key
security = kvp.Value
if security.Price > self.Portfolio[symbol].AveragePrice: self.Liquidate(symbol)
class SecurityWeight:
def __init__(self, security, gain):
self.security = security
self.gain = gain
class CloseLongPosition:
def __init__(self, closeLong, highestPrice):
self.closeLong = closeLong
self.highestPrice = highestPriceBENZINGA_API_KEY = "" BENZINGA_URL = "https://api.benzinga.com/api/v1/market/movers?session=PRE_MARKET&apikey=" + BENZINGA_API_KEY + "&maxResults=" BACKTEST_URL_LARGE = "https://tinyurl.com/uece8" BACKTEST_URL_SMALL = "https://tinyurl.com/53r2hmj4" BACKTEST_URL = "https://tinyurl.com/e4h7kwaw"