| Overall Statistics |
|
Total Trades 42 Average Win 31.88% Average Loss -28.18% Compounding Annual Return -73.749% Drawdown 88.900% Expectancy 0.015 Net Profit -73.941% Sharpe Ratio -0.451 Probabilistic Sharpe Ratio 5.888% Loss Rate 52% Win Rate 48% Profit-Loss Ratio 1.13 Alpha -0.507 Beta -1.385 Annual Standard Deviation 1.044 Annual Variance 1.09 Information Ratio -0.41 Tracking Error 1.086 Treynor Ratio 0.34 Total Fees $2018.25 Estimated Strategy Capacity $110000.00 |
# ----------------------------------------------------------------------
#
# Custom Buying power model to solve insufficient funds problem. There is a fix coming in December/January
#
# ----------------------------------------------------------------------
class CustomBuyingPowerModel(BuyingPowerModel):
def GetMaximumOrderQuantityForTargetBuyingPower(self, parameters):
quantity = super().GetMaximumOrderQuantityForTargetBuyingPower(parameters).Quantity
quantity = np.floor(quantity / 100) * 100
return GetMaximumOrderQuantityResult(quantity)
def HasSufficientBuyingPowerForOrder(self, parameters):
return HasSufficientBuyingPowerForOrderResult(True)# Your New Python File
from datetime import timedelta
from QuantConnect.Orders import OrderDirection
import numpy as np
import re
class OrderManagement:
def __init__(self, algorithm):
self.algo = algorithm
def open_hedge(self):
slice = self.algo.CurrentSlice
for i in slice.OptionChains:
chain = i.Value
# spot_price = chain.Underlying.Price
contracts = [x for x in chain if x.Right == 1]
contracts = sorted(contracts, key=lambda x: (x.Expiry, x.Greeks.Delta))
self.algo.hedge["contract"] = min(contracts, key=lambda x: abs(x.Greeks.Delta-(-0.30)))
if self.algo.hedge["contract"] is None:
return
qty = (self.algo.Portfolio.Cash * 0.60) / (self.algo.hedge["contract"].AskPrice * 100)
if qty < 1:
return
else:
self.algo.Debug(f"H - S {self.algo.hedge['contract'].Strike} Ex {self.algo.hedge['contract'].Expiry} D{round(self.algo.hedge['contract'].Greeks.Delta, 3)}")
self.algo.longHedgeOrder = self.algo.Buy(self.algo.hedge["contract"].Symbol, np.floor(qty))
self.algo.hedge["position"] = True
self.algo.hedge["quantity"] = self.algo.longHedgeOrder.Quantity
def open_spread(self, shortContract, longContract):
# Get our margin
margin = self.algo.Portfolio.GetBuyingPower(
shortContract.Symbol, OrderDirection.Sell)
# Get the quantities
qty = margin * self.algo.investPercent / \
((shortContract.BidPrice + longContract.AskPrice) * 100)
# Check that contracts are not the same
if qty < 1:
return
else:
# Log out what our contracts are:
self.algo.Debug(f"S - Expiry: {shortContract.Expiry} Delta: {round(shortContract.Greeks.Delta, 6)} macd:{round(self.algo.macd.Signal.Current.Value, 2)}")
self.algo.Debug(f"L - Expiry: {longContract.Expiry} Delta: {round(longContract.Greeks.Delta, 6)}")
# Perform the order
self.algo.longOrder = self.algo.Buy(longContract.Symbol, np.floor(qty))
self.algo.shortOrder = self.algo.Sell(shortContract.Symbol, np.floor(qty))
# Set in position as true so we don't continue buying
self.algo.inPosition = True
# Store the net Credit
self.algo.netCredit = (
np.abs(self.algo.shortOrder.AverageFillPrice) - np.abs(self.algo.longOrder.AverageFillPrice)) * np.abs(self.algo.longOrder.QuantityFilled) * 100
# Set the openPortfolioValue for Profit Calculations
self.algo.openPortfolioValue = self.algo.Portfolio.TotalPortfolioValue
# store expiry
self.algo.spread_expiry = shortContract.Expiry
def close_all(self):
# kill all positions
self.algo.Liquidate()
self.algo.inPosition = Falseclass PositionManagement:
def __init__(self, algorithm):
self.algo = algorithm
self.previousGainCategory = 0
def hedge_handler(self):
# helper variables
unreal = self.algo.Portfolio[self.algo.hedge["contract"].Symbol].UnrealizedProfit
cost = self.algo.Portfolio[self.algo.hedge["contract"].Symbol].HoldingsCost
qty = int(round(self.algo.hedge["quantity"] / 4))
# wait for the gain categories to be set
if self.algo.hedge["gainCategory"] == None:
return
# its selling more than we have.
if self.algo.Portfolio[self.algo.hedge["contract"].Symbol].Quantity > 0:
if self.algo.hedge["gainCategory"] == -1:
self.algo.Liquidate()
reset(self)
elif self.algo.hedge["gainCategory"] == self.previousGainCategory:
return
elif self.algo.hedge["gainCategory"] > self.previousGainCategory:
# handler any remainder quantities.
if self.algo.Portfolio[self.algo.hedge["contract"].Symbol].Quantity < qty:
qty = self.algo.Portfolio[self.algo.hedge["contract"].Symbol].Quantity
self.algo.MarketOrder(self.algo.hedge["contract"].Symbol, -qty)
self.algo.Debug(f'Hedge Sell | gain%: {safe_div(unreal, cost)} | scale:{self.algo.hedge["gainCategory"]} @ {self.algo.Time}')
# set the previous gain category to the current catehory
self.previousGainCategory = self.algo.hedge["gainCategory"]
else:
self.algo.Liquidate()
reset(self)
def safe_div(x, y):
if x == 0:
return 0
return round(x / y, 2)
def reset(self):
# reset our hedge as we have none left
self.previousGainCategory = 0
self.algo.hedge["position"] = False
self.algo.hedge["profile"] = 1
self.algo.hedge["gainCategory"] = None
self.algo.hedge["contract"] = None
self.algo.hedge["scale"] = 0
self.algo.hedge["stopLoss"] = 0
self.algo.hedge["quantity"] = 0from signals import *
from position import *
from order import *
from lib import CustomBuyingPowerModel
from datetime import timedelta
from QuantConnect.Data.Custom.CBOE import CBOE
from QuantConnect.Securities.Option import OptionPriceModels
from QuantConnect.Indicators import RollingWindow
from QuantConnect.Data.Market import TradeBar
from QuantConnect.Algorithm import *
from QuantConnect import *
from System import *
from clr import AddReference
AddReference("System")
AddReference("QuantConnect.Algorithm")
AddReference("QuantConnect.Common")
class OptionsAlgorithm(QCAlgorithm):
def Initialize(self):
"""
Initializes the algorithm
Args:
self: write your description
"""
# Base QuantConnect Parameters
self.SetStartDate(2018, 1, 1)
self.SetEndDate(2019, 1, 1)
self.SetCash(150000)
# Base Algorithm Paramters # needs to be set in Quantconnect GUI
self.investPercent = float(self.GetParameter("investPercent")) # default 0.9
self.shortDelta = float(self.GetParameter("shortDelta"))/-100 # default -0.25
self.longDelta = float(self.GetParameter("longDelta"))/-100 # default -0.15
self.t1DTE = int(self.GetParameter("t1DTE")) # default 25
self.t2DTE = int(self.GetParameter("t2DTE")) # default 45
# Handlers:
self.order = OrderManagement(self)
self.position = PositionManagement(self)
self.signal = SignalManagement(self)
# Helper Variables
self.netCredit = None
self.spread_expiry = None
self.inPosition = False
self.hedge = {
"position": False,
"quantity": None,
"contract": None,
"gainCategory": None,
"profile": 1,
"stopLoss": 0,
"scale": 0,
}
self.stoppedOut = False
self.openPortfolioValue = None
# Set Option Instruments
self.symbol = "SPY"
self.option = self.AddOption(self.symbol, Resolution.Minute)
self.option.PriceModel = OptionPriceModels.CrankNicolsonFD()
self.option.SetFilter(-40, -10, timedelta(self.t1DTE), timedelta(self.t2DTE))
self.option.SetBuyingPowerModel(CustomBuyingPowerModel.CustomBuyingPowerModel())
self.vix = self.AddData(CBOE, "VIX", Resolution.Daily)
# Set Other Securities
self.SetSecurityInitializer(lambda x: x.SetDataNormalizationMode(DataNormalizationMode.Raw))
self.equity = self.AddEquity(self.symbol, Resolution.Daily)
self.equity.SetDataNormalizationMode(DataNormalizationMode.Raw)
# Initialize TA Parameters
self.EMA10 = self.EMA(self.symbol, 10, Resolution.Daily)
self.EMA25 = self.EMA(self.symbol, 25, Resolution.Daily)
self.SMA50 = self.SMA(self.symbol, 50, Resolution.Daily)
self.SMA125 = self.SMA(self.symbol, 125, Resolution.Daily)
self.macd = self.MACD(self.symbol, 12, 26, 9, resolution = Resolution.Daily)
self.windowSPX = RollingWindow[TradeBar](2)
self.windowCross = RollingWindow[float](2)
# Set warmup for Greeks and Indicators
self.SetWarmUp(timedelta(days=125))
# Check exits everyday
self.Schedule.On(self.DateRules.EveryDay(
self.symbol), self.TimeRules.AfterMarketOpen(self.symbol, 5), self.balance)
def OnData(self, slice):
# do some warmup checks
if self.Time.hour == 9 and self.Time.minute == 31:
if self.IsWarmingUp:
return
# add slice
self.windowSPX.Add(slice[self.symbol])
# add our ema to a window
self.windowCross.Add(self.EMA10.Current.Value)
# wait for windows to be ready
if not self.windowSPX.IsReady and not self.windowCross.IsReady:
return
self.Log(f"o: {self.Securities['SPY'].Open} h: {self.Securities['SPY'].High} l: {self.Securities['SPY'].Low} c: {self.Securities['SPY'].Close}")
# check for buy signals
signal = self.signal.spread_buy()
if self.inPosition:
self.balance
else:
if signal:
# reset our stopped out variable so we can get into a hedge if we stop out.
self.stoppedOut = False
self.get_contracts(slice)
def get_contracts(self, slice):
# find contracts
shortContract = None
longContract = None
for i in slice.OptionChains:
chain = i.Value
contracts = [x for x in chain if x.Right == 1]
contracts = sorted(contracts, key=lambda x: (x.Expiry, x.Greeks.Delta))
shortContract = min(contracts, key=lambda x: abs(x.Greeks.Delta-self.shortDelta))
longContract = min(contracts, key=lambda x: abs(x.Greeks.Delta-self.longDelta))
# Check that contracts are not the same
if shortContract != longContract:
self.order.open_spread(shortContract, longContract)
def balance(self):
# Determine if we need to hedge
if not self.hedge["position"] and self.stoppedOut:
signal = self.signal.hedge_buy()
if signal:
self.order.open_hedge()
if self.hedge["position"]:
signal = self.signal.hedge_sell()
if signal:
self.position.hedge_handler()
# check that we have a spread open
if self.openPortfolioValue is not None and self.inPosition:
# Generate signals to sell
signal = self.signal.spread_sell()
if signal:
self.order.close_all()from QuantConnect import TradingDayType
class SignalManagement:
def __init__(self, algorithm):
self.algo = algorithm
self.profiles = [
{"hedge": [{0, 1}, {1, 2}, {2, 3}, {3, 4}], "range": [1, 2, 3, 4]},
{"hedge": [{0, 1}, {0.3, 2}, {0.6, 3}, {0.9, 4}], "range": [0.3, 0.6, 0.9, 1.2]}
]
self.tolerance = 0.1
def spread_buy(self):
# helper variables
currEma10 = self.algo.windowCross[0]
pastEma10 = self.algo.windowCross[1]
# Conditions
c1 = self.algo.SMA125.Current.Value < self.algo.Securities[self.algo.symbol].Open
c2 = self.algo.EMA25.Current.Value < self.algo.Securities[self.algo.symbol].Open
c3 = self.algo.EMA10.Current.Value > self.algo.EMA25.Current.Value
c4 = pastEma10 < self.algo.EMA25.Current.Value and currEma10 > self.algo.EMA25.Current.Value
c5 = self.algo.macd.Signal.Current.Value <= 2.1
if c1 and c2 and c3 and c5:
return True
# if c4:
# self.algo.Debug('we got a cross')
# return True
def spread_sell(self):
# helper variables
currClose = self.algo.windowSPX[0].Close
oldClose = self.algo.windowSPX[1].Close
days_till_expiry = list(self.algo.TradingCalendar.GetDaysByType(TradingDayType.BusinessDay, self.algo.Time, self.algo.Time + (self.algo.spread_expiry - self.algo.Time)))
# Conditions
c1 = safe_div(self.algo.Portfolio.TotalUnrealizedProfit, self.algo.netCredit) > 0.7
c2 = safe_div(self.algo.Portfolio.TotalUnrealisedProfit, self.algo.Portfolio.TotalPortfolioValue) < -0.5
c3 = self.algo.EMA10.Current.Value < self.algo.EMA25.Current.Value and self.algo.Portfolio.TotalUnrealisedProfit < 0
c4 = ((currClose - oldClose) / oldClose) < -0.03
c5 = len(days_till_expiry) > 7
c6 = self.algo.macd.Signal.Current.Value > 2.1
if c1:
# self.algo.Debug('Sell Signal | 80% max profit')
return True
if c2:
self.algo.Debug(f"Sell | -50 percent stop loss @ {self.algo.Time}")
self.algo.stoppedOut = True
return True
if c3:
self.algo.Debug('Sell | CROSS')
# self.algo.stoppedOut = False
return True
# if c4:
# self.algo.Debug(f'Sell Signal | SPX falling {round(((currClose - oldClose) / oldClose), 2) * 100}% old close: {oldClose}. curr close: {currClose} @ {self.algo.Time}')
# self.algo.stoppedOut = True
# return True
if c5 and c6:
self.algo.Debug(f"Sell | macd: {round(self.algo.macd.Signal.Current.Value, 2)} days left: {len(days_till_expiry)}")
self.algo.stoppedOut = True
return True
def hedge_buy(self):
# helper variables
currEma10 = self.algo.windowCross[0]
pastEma10 = self.algo.windowCross[1]
c1 = pastEma10 > self.algo.EMA25.Current.Value and currEma10 < self.algo.EMA25.Current.Value
# Conditions
if c1:
self.algo.Debug(f"Hedge Buy Signal: 10EMA is {round(self.algo.EMA10.Current.Value, 2)} below 25 EMA {round(self.algo.EMA25.Current.Value, 2)} date: {self.algo.Time.date}")
return True
def hedge_sell(self):
# helper variables
unreal = self.algo.Portfolio[self.algo.hedge['contract'].Symbol].UnrealizedProfit
cost = self.algo.Portfolio[self.algo.hedge['contract'].Symbol].HoldingsCost
profitPercent = safe_div(unreal, cost)
if self.algo.vix.Close > 35:
if self.algo.hedge["profile"] == 1:
# if a hedge went from non-volatile to volatile you need to restart the range checks
# relative to where we are. so if you sold at 30% in non-V and it is volatile you're
# hedge startegy needs to reset to the lowest bound in the gainCategory.
self.algo.hedge["gainCategory"] = 0
self.algo.hedge["profile"] = 0
closestTuple = min(enumerate(self.profiles[self.algo.hedge["profile"]]["range"]), key=lambda x: abs(x[1]-profitPercent))
self.algo.Debug(f"profit {profitPercent} profile: {self.algo.hedge['profile']} gc: {self.algo.hedge['gainCategory']}")
if ((closestTuple[1] - self.tolerance) <= profitPercent):
if closestTuple[0] == len(self.profiles[0]["range"])-1:
self.algo.hedge["gainCategory"] = -1
return True
if profitPercent < (closestTuple[1] + self.tolerance):
self.algo.hedge["gainCategory"] = closestTuple[0]
hedgeProfile = self.profiles[self.algo.hedge["profile"]]["hedge"]
if closestTuple[0] == 0 or self.algo.hedge["stopLoss"] >= list(hedgeProfile[closestTuple[0]-1])[0]:
self.algo.hedge["stopLoss"] = list(hedgeProfile[closestTuple[0]])[0]
self.algo.hedge["scale"] = list(hedgeProfile[closestTuple[0]])[1]
return True
def safe_div(x, y):
if x == 0:
return 0
return round(x / y, 2)