| Overall Statistics |
|
Total Trades 1902 Average Win 0.15% Average Loss -0.12% Compounding Annual Return 8.367% Drawdown 8.800% Expectancy 0.115 Net Profit 13.719% Sharpe Ratio 0.95 Probabilistic Sharpe Ratio 45.218% Loss Rate 51% Win Rate 49% Profit-Loss Ratio 1.27 Alpha 0.076 Beta -0.019 Annual Standard Deviation 0.075 Annual Variance 0.006 Information Ratio -0.595 Tracking Error 0.267 Treynor Ratio -3.758 Total Fees $2636.58 Estimated Strategy Capacity $260000.00 Lowest Capacity Asset FXA TJSL8DEZVWBP |
dev_mode = False # for Shile's use, keep False
if dev_mode:
from AlgorithmImports import *
# ---indicies---
market = 'SPY'
silver = 'SLV'
gold = 'GLD'
utility = 'XLU'
industrial = 'XLI'
safe = 'FXF' # safe currency
risk = 'FXA' # risk currency
debt_short = 'SHY'
debt_inflation = 'TIP'
metal = 'DBB'
inp = 'IGE' # input
cash = 'UUP'
# ---equities
equities = ['AAPL', 'AMGN', 'AXP', 'BA', 'CAT', 'CRM', 'CSCO', 'CVX', 'DIS', 'DOW', 'GS', 'HD', 'IBM', 'INTC', 'JNJ', 'JPM', 'KO', 'MCD', 'MMM', 'MRK', 'MSFT', 'NKE', 'PG', 'TRV', 'UNH', 'V', 'VZ', 'WBA', 'WMT']
#equities = ['ATVI', 'ADBE', 'AMD', 'ALGN', 'ALXN', 'AMZN', 'AMGN', 'AAL', 'ADI', 'AAPL', 'AMAT', 'ASML', 'ADSK', 'ADP', 'AVGO', 'BIDU', 'BIIB', 'BMRN', 'CDNS', 'CERN', 'CHKP', 'CHTR', 'TCOM', 'CTAS', 'CSCO', 'CTXS', 'CMCSA', 'COST', 'CSX', 'CTSH', 'DLTR', 'EA', 'EBAY', 'EXC', 'EXPE', 'FAST', 'FB', 'FISV', 'GILD', 'GOOG', 'GOOGL', 'HAS', 'HSIC', 'ILMN', 'INCY', 'INTC', 'INTU', 'ISRG', 'IDXX', 'JBHT', 'JD', 'KLAC', 'KHC', 'LRCX', 'LBTYA', 'LBTYK', 'LULU', 'MELI', 'MAR', 'MCHP', 'MDLZ', 'MNST', 'MSFT', 'MU', 'MXIM', 'MYL', 'NTAP', 'NFLX', 'NTES', 'NVDA', 'NXPI', 'ORLY', 'PAYX', 'PCAR', 'BKNG', 'PYPL', 'PEP', 'QCOM', 'REGN', 'ROST', 'SIRI', 'SWKS', 'SBUX', 'NLOK', 'SNPS', 'TTWO', 'TSLA', 'TXN', 'TMUS', 'ULTA', 'UAL', 'VRSN', 'VRSK', 'VRTX', 'WBA', 'WDC', 'WDAY', 'WYNN', 'XEL', 'XLNX']
#'A','AAP','AAPL','ABC','ABMD','ABT','ADBE','ADI','ADM','ADP','ADSK','AGN','AGR','AKAM','ALB','ALGN','ALK','ALLE','ALV','ALXN','AMAT','AMD','AME','AMGN','AMT','ANET','ANSS','AOS','APD','APTV','ARW','ASH','ATO','ATVI','AVB','AVY','AYI','AZO','BAX','BBY','BG','BIIB','BIO','BKNG','BKR','BMRN','BSX','BURL','BWA','CAH','CCEP','CDNS','CDW','CERN','CF','CGNX','CHD','CHRW','CL','CLX','CMI','COG','COO','COP','CPRI','CPRT','CRM','CSCO','CSX','CTAS','CTSH','CTVA','CTXS','CVS','CVX','CXO','DAL','DD','DGX','DHI','DHR','DLTR','DOV','DOW','DOX','DRE','DVA','DVN','EBAY','ECL','EIX','EL','ELAN','EMR','EOG','EQIX','ETN','EW','EXC','EXPD','EXPE','FANG','FAST','FB','FCX','FDX','FFIV','FISV','FL','FLEX','FLS','FLT','FMC','FTI','FTNT','FTV','GDDY','GILD','GLW','GNTX','GOOG','GOOGL','GPC','GPS','GRMN','GRUB','GWW','HAL','HAS','HD','HEI','HEIA','HES','HFC','HOLX','HP','HPE','HPQ','HSIC','HSY','IBM','IDXX','IEX','IFF','ILMN','INCY','INFO','INTC','INTU','IP','IPG','IPGP','IR','ISRG','IT','ITW','JAZZ','JBHT','JBL','JCI','JNJ','JNPR','JWN','KDP','KEYS','KHC','KLAC','KMB','KNX','KO','KSS','KSU','LDOS','LEA','LEN','LIN','LKQ','LLY','LOW','LRCX','LULU','LUV','LW','LYB','M','MAS','MCD','MCK','MDLZ','MDT','MDU','MHK','MKC','MLM','MMM','MNST','MOS','MPC','MRK','MRO','MRVL','MSFT','MSI','MTD','MU','MXIM','NBL','NEM','NKE','NLOK','NOV','NOW','NSC','NTAP','NUE','NVDA','NVR','NWS','NWSA','NXPI','ODFL','OGE','OMC','ORCL','ORLY','OXY','PAYC','PAYX','PCG','PEG','PEP','PFE','PG','PHM','PKG','PKI','PLD','PNR','PNW','PPG','PRGO','PSA','PSX','PTC','PVH','PXD','QCOM','QRVO','REGN','RHI','RL','RMD','ROK','ROL','ROP','ROST','SBUX','SCCO','SHW','SLB','SNA','SNPS','ST','STE','STLD','STX','SWK','SWKS','SYK','TEL','TFX','TGT','TIF','TJX','TMO','TOL','TPR','TRMB','TSCO','TSLA','TT','TWTR','TXN','TYL','UA','UAA','UAL','ULTA','UNP','UPS','VAR','VFC','VLO','VMC','VMW','VRSN','VRTX','WAB','WAT','WBA','WBC','WDAY','WDC','WHR','WLK','WY','XEC','XOM','XRAY','XRX','XYL','YNDX','ZBRA']
# ---safeties
safeties = ['FXF', 'FXA', 'UUP']
#safeties = ['FXF', 'FXA']
# ---invest
Invest = 100000
#StartDay = 2020,1,1
# --universe resolution
resolution = Resolution.Daily # don't touch unless you are removing all options logic
InOut_resolution = Resolution.Daily
returns_resolution = Resolution.Daily
# disable select indicators
disableSupertrend = False
disableSqueeze = False
disableInAndOut = False # setting this to True -> go long always except 10% drawdown
# ---in and out parameters
# parameters found from file from you
bull = True # set False for bear
inOutLookbackBull = 30
inOutLookbackBear = 5
waitDaysConstant = 1 # WAITD_CONSTANT from your file
iniWaitDays = 1# INI_WAIT_DAYS from your file
minWaitDays = 1 # 60 from the `min(60, self.WDadjvar)` from your file
# ---supertrend parameters
superTrendPeriod = 1
superTrendMultiple = 3
superTrendUseHA = True # use Heiken-Ashii for superTrend
# ---squeeze parameters
squeezeTrendPeriod = 1
squeezeBBMultiple = 2 # BollingerBands
squeezeKeltMultiple = 3 # Kelter Channel (originally 1.5, increased to increase bullish trades)
# the lower the BBMultiple and the higher the KeltMultiple
# the more likely squeeze will allow trades
# ---portfolio parameters
# for the returns based portfolio allocation
max_drawdown = 0.5 # max drawdown allowed before liquidation is signaled
drawdown_waitdays = 1 # number of days to stay out of the market after drawdown liquidation
drawdown_lookback = 1 # lookback for drawdon
max_alloc = .05 # max allocation to any given stock
returns_lookback = 1 # lookback for returns
# ---general parameters
rebalance = 1 # how often you want to update weights for rebalancing, refresh in&out signal
options_weight = 0 # what % of portfolio for options
equities_weight = 1 - options_weight
# specific options parameters
optionright = OptionRight.Call
price_strike_ratio = .95
'''
price_strike_ratio = (current equity price) / (strike price)
If call option and price_strike_ratio = .9
that means we want calls whose underlying is 90% of the strike price - which means it is .1 (10%) OTM
Similary, call option and price_strike_ratio = 1.1 - that means we want calls 10% ITM
Reverse the logic for puts
'''
expiry_liquidation = 7 # how many days before expiry to sell option
min_expiry = 50 # option will expire in at least this many days before buying
debug = False # show debug messages
dev_mode = False
if dev_mode:
from AlgorithmImports import *
from collections import deque
from datetime import datetime
from typing import Union
import pandas as pd
class MySuperTrend:
def __init__(self, period, multiple, movingAverageType=MovingAverageType.Simple):
self.Name = "SuperTrend"
self.Time = datetime.min
self.Value = 0
self.multiplier = multiple
self.atr = AverageTrueRange(period, movingAverageType)
self.values = deque(maxlen=period)
self.previousTrailingLowerBand = 0
self.previousTrailingUpperBand = 0
self.previousClose = 0
self.previousTrend = 0
def __repr__(self):
return "{0} -> IsReady: {1}. Time: {2}. Value: {3}".format(self.Name, self.IsReady, self.Time, self.Value)
def Update(self, input:TradeBar):
self.Time = input.EndTime
self.atr.Update(input)
superTrend = 0
currentClose = input.Close
currentBasicLowerBand = (input.Low + input.High) / 2 - self.multiplier * self.atr.Current.Value
currentBasicUpperBand = (input.Low + input.High) / 2 + self.multiplier * self.atr.Current.Value
if self.previousClose > self.previousTrailingLowerBand:
currentTrailingLowerBand = max(currentBasicLowerBand, self.previousTrailingLowerBand)
else:
currentTrailingLowerBand = currentBasicLowerBand
if self.previousClose < self.previousTrailingUpperBand:
currentTrailingUpperBand = min(currentBasicUpperBand, self.previousTrailingUpperBand)
else:
currentTrailingUpperBand = currentBasicUpperBand
if currentClose > currentTrailingUpperBand:
currentTrend = 1
elif currentClose < currentTrailingLowerBand:
currentTrend = -1
else:
currentTrend = self.previousTrend
if currentTrend == 1:
superTrend = currentTrailingLowerBand
elif currentTrend == -1:
superTrend = currentTrailingUpperBand
self.previousTrailingLowerBand = currentTrailingLowerBand
self.previousTrailingUpperBand = currentTrailingUpperBand
self.previousClose = currentClose
self.previousTrend = currentTrend
if not self.atr.IsReady:
return 0
self.Value = superTrend
return self.IsReady
@property
def IsReady(self):
return self.atr.IsReady and self.Value != 0
class Squeeze:
'''
.Value = 1 iff "squeezed" else .Value = 0
Tells us if we are in or out of squeeze
Is Squeeze: lower BB > lower Keltner and upper BB < upper Keltner
'''
def __init__(self, period, squeezeBBMultiple, squeezeKeltMultiple, movingAverageType=MovingAverageType.Simple):
'''
Value = 1 iff "squeezed" else .Value = 0
'''
self.Name = "Squeeze"
self.Time = datetime.min
self.Value = 0
self.bb = BollingerBands(period, squeezeBBMultiple, movingAverageType)
self.kelt = KeltnerChannels(period, squeezeKeltMultiple, movingAverageType)
def __repr__(self):
return "{0} -> IsReady: {1}. Time: {2}. Value: {3}".format(self.Name, self.IsReady, self.Time, self.Value)
def Update(self, input:TradeBar):
self.Time = input.EndTime
self.kelt.Update(input)
self.bb.Update(input.EndTime, input.Close)
isSqueeze = self.bb.UpperBand.Current.Value > self.kelt.UpperBand.Current.Value
self.Value = int(isSqueeze)
return self.IsReady
@property
def IsReady(self):
return self.kelt.IsReady and self.bb.IsReady
class Drawdown:
def __init__(self, period:int):
'''
drawdown indicator for past `period` values
Call Update with floats that represent returns
'''
self.values = deque(maxlen=period)
self.Value = 0
def Update(self, input:Union[TradeBar, float]):
if isinstance(input, float):
self.values.append(input)
else:
self.values.append(input.Close)
# https://stackoverflow.com/questions/36750571/calculate-max-draw-down-with-a-vectorized-solution-in-python
cum_returns = (1 + pd.Series(self.values)).cumprod()
self.Value = 1 - cum_returns.div(cum_returns.cummax()).iloc[-1] # drawdown
return self.IsReady
@property
def IsReady(self):
return len(self.values) == self.values.maxlen
from collections import deque
from collections.abc import Iterable
from datetime import datetime
from typing import Deque, Dict, List, Union
import numpy as np
import pandas as pd
#from indicators import Drawdown
dev_mode = False
if dev_mode:
from AlgorithmImports import *
class InAndOut:
def __init__(self, algo:QCAlgorithm, InOut_resolution, symbols:List[str], period:int, iniWaitDays, minWaitDays, waitDaysConst, bull=True):
self.Time = datetime.min
self.period = period
self.iniWaitDays = iniWaitDays
self.minWaitDays = minWaitDays
self.waitDaysConst = waitDaysConst
self.bull = bull
(self.market, self.silver, self.gold, self.utility,
self.industrial, self.safe, self.risk, self.debt_short,
self.debt_inflation, self.metal, self.input, self.cash) = symbols
self.bull_signal_indices = [self.industrial, self.metal, self.input]
self.history: dict[Symbol, Deque[float]] = {}
history_df = algo.History(symbols, period, resolution)
for symbol in symbols:
if symbol in history_df and len(history_df[symbol]) > 0:
self.history[symbol] = deque(history_df[symbol]['close'], maxlen=period)
else:
self.history[symbol] = deque(maxlen=period)
self.wait_days = 0
def Update(self, input:Slice):
self.Time = input.Time
for symbol, history in self.history.items():
if input.Bars.ContainsKey(symbol):
history.append(input[symbol].Close)
def is_bullish(self):
'''
returns (true iff "bullish", wait_days)
'''
history_dict = {symbol: pd.Series(data) for symbol, data in self.history.items()}
# (100 day returns / 11 day centered sma shifted by 60 days) - 1
cust_returns_dict: dict[Union[Symbol, str], pd.Series] = {}
for symbol in history_dict:
if len(history_dict[symbol]) != self.period:
return False, 0
hist_series = history_dict[symbol]
cust_returns_dict[symbol] = (hist_series / hist_series.rolling(5, center=True).mean().shift(10)).dropna() - 1
history_dict[symbol] = history_dict[symbol][-len(cust_returns_dict[symbol]):] # make all series the same length
gold_min_silver = 'gold_min_silver'
industrial_min_utility = 'industrial_min_utility'
risk_min_safe = 'risk_min_safe'
cash_inverse = 'cash_inverse'
cust_returns_dict[gold_min_silver] = cust_returns_dict[self.gold] - cust_returns_dict[self.silver]
cust_returns_dict[industrial_min_utility] = cust_returns_dict[self.industrial] - cust_returns_dict[self.utility]
cust_returns_dict[risk_min_safe] = cust_returns_dict[self.risk] - cust_returns_dict[self.safe]
cust_returns_dict[cash_inverse] = -1 * cust_returns_dict[self.cash]
# values are true if last return is < 1 percentile
is_extreme_returns_dict: dict[Union[Symbol, str], bool] = {}
for symbol, returns in cust_returns_dict.items():
is_extreme_returns_dict[symbol] = returns.iloc[-1] < np.percentile(returns, 1)
inflation = 'inflation'
history_dict[inflation] = cust_returns_dict[self.debt_short] - cust_returns_dict[self.debt_inflation]
isabovemedian_dict = {
symbol: (series.iloc[-1] > series.median()) for symbol, series in history_dict.items()
}
interest_expected = 'interest_expected'
if is_extreme_returns_dict[self.debt_short] and isabovemedian_dict[self.metal] and isabovemedian_dict[self.input]:
is_extreme_returns_dict[interest_expected] = False
else:
is_extreme_returns_dict[interest_expected] = is_extreme_returns_dict[self.debt_short]
gold_min_silver_adj = 'gold_min_silver_adj'
if is_extreme_returns_dict[gold_min_silver] and isabovemedian_dict[inflation]:
is_extreme_returns_dict[gold_min_silver_adj] = False
else:
is_extreme_returns_dict[gold_min_silver_adj] = is_extreme_returns_dict[gold_min_silver]
def wait_days_helper(symbol0, symbol1):
series0 = cust_returns_dict[symbol0]
series1 = cust_returns_dict[symbol1]
if series0.iloc[-1] > 0 and series1.iloc[-1] < 0 and series1.iloc[-2] > 0:
return self.iniWaitDays
else:
return 1
self.wait_days = int(
max(
self.wait_days/2,
self.iniWaitDays * max (
1,
wait_days_helper(self.gold, self.silver),
wait_days_helper(self.utility, self.industrial),
wait_days_helper(self.safe, self.risk)
)
)
)
signals = self.bull_signal_indices + [gold_min_silver_adj, industrial_min_utility, risk_min_safe, cash_inverse]
bullish = any([is_extreme_returns_dict[signal] for signal in signals])
return bullish, min(self.minWaitDays, self.wait_days)
def is_bearish(self):
'''
returns (true iff "bearish", wait_days)
'''
market_returns = pd.Series(self.history[self.market]).pct_change().dropna()
volatililty = .6 * np.sqrt(252) * np.log1p(market_returns).std()
returns_lookback = int(min(
(1-volatililty)*self.waitDaysConst,
self.period
))
wait_days = int(volatililty * self.waitDaysConst)
signals = [self.silver, self.gold, self.industrial, self.utility, self.metal, self.cash]
returns = {}
for signal in signals:
data = self.history[signal]
if len(data) < returns_lookback:
return False, 0
returns[signal] = pd.Series(data).pct_change(returns_lookback).iloc[-1]
def compare(symbol0, symbol1):
return returns[symbol0] < returns[symbol1]
compares = compare(self.silver, self.gold) and compare(self.industrial, self.utility) and compare(self.metal, self.cash)
return compares, wait_days
def minmax(self, n1, min_val, max_val):
return max(min(n1, min_val), max_val)
def get_signal(self):
'''
returns (true iff "bullish" and bull==True, wait_days)
returns (true iff "bearish" and bull==False, wait_days)
'''
if self.bull:
return self.is_bullish()
else:
return self.is_bearish()
class ReturnsManager:
def __init__(self, algo, period, returns_resolution, max_drawdown, drawdown_lookback, max_alloc):
'''
Manages asset weighting. Get weights with the `GetWeights()` method.
Also tells us if the `max_drawdown` has been reached with `IsSell()` method
'''
self.algo = algo
self.period = period
self.drawdown_lookback = drawdown_lookback
#self.resolution = resolution
# one day returns
self.daily_returns_dict: Dict[Symbol, RateOfChange] = {}
# `period` day returns
self.returns_dict: Dict[Symbol, RateOfChange] = {}
self.dd = Drawdown(self.drawdown_lookback)
self.weights_dict: Dict[Symbol, float] = {}
self.max_drawdown = max_drawdown
self.max_alloc = max_alloc
def Update(self, input:Slice):
portfolio_returns_cross_section = 0
# update daily returns and period returns
for symbol in self.returns_dict:
if input.Bars.ContainsKey(symbol):
daily_returns = self.daily_returns_dict[symbol]
daily_returns.Update(input.Time, input[symbol].Close)
self.returns_dict[symbol].Update(input.Time, input[symbol].Close)
if symbol in self.weights_dict and daily_returns.IsReady:
# weighted returns of one item in the portfolio
item_returns = self.weights_dict[symbol] * daily_returns.Current.Value
portfolio_returns_cross_section += item_returns
if portfolio_returns_cross_section:
self.dd.Update(portfolio_returns_cross_section)
def UpdateWeights(self):
# used to divide weights to make them sum to 1
total_weight = sum(
returns.Current.Value
for returns in self.returns_dict.values()
if returns.IsReady
)
if not total_weight:
return {}
self.weights_dict = {
symbol: min(returns.Current.Value / total_weight, self.max_alloc)
for symbol, returns in self.returns_dict.items()
}
return self.weights_dict
def GetWeights(self):
return self.weights_dict
def IsSell(self):
return self.dd.Value > self.max_drawdown
@property
def IsReady(self):
return np.any([returns.IsReady for symbol, returns in self.returns_dict.items()]) and len(self.weights_dict) > 0
def WarmUp(self, symbols:Union[Symbol, None]):
'''
warmups the symbol/symbols indicators
'''
if not isinstance(symbols, Iterable):
symbols = [symbols]
hist = self.algo.History(symbols, self.period, self.resolution)
for symbol in symbols:
if symbol not in hist.index or not len(hist.loc[symbol]):
continue
closes = hist.loc[symbol]['close']
for dt, close in closes.iteritems():
self.daily_returns_dict[symbol].Update(dt, close)
self.returns_dict[symbol].Update(dt, close)
def AddSecurity(self, symbol:Symbol):
'''
registers the new symbol to the ReturnsManager
'''
if symbol not in self.returns_dict:
self.daily_returns_dict[symbol] = RateOfChange(1)
self.returns_dict[symbol] = RateOfChange(self.period)
# self.WarmUp(symbol)
#import configs as cfg
dev_mode = False
if dev_mode:
from AlgorithmImports import *
from datetime import timedelta
from typing import Dict, List
#from indicators import MySuperTrend, Squeeze
#from aggregate_indicators import InAndOut, ReturnsManager
import operator
class AdaptableRedSnake(QCAlgorithm):
def load_configs_and_indicators(self):
# OVERRIDE CONFIGS HERE
# EXAMPLE: returns_lookback = self.GetParameter('returns_lookback')
self.Cash = Invest
index_tickers = [market, silver, gold, utility, industrial, safe, risk, debt_short, debt_inflation, metal, inp, cash]
self.indices = [
self.AddEquity(ticker, resolution).Symbol for ticker in index_tickers
]
inOutLookback = inOutLookbackBull if bull else inOutLookbackBear
self.inandout = InAndOut(self, resolution, self.indices, inOutLookback, iniWaitDays, minWaitDays, waitDaysConstant, bull)
self.returnsmanager = ReturnsManager(self, returns_lookback, resolution, max_drawdown, drawdown_lookback, max_alloc)
self.equities = [
self.AddEquity(ticker, resolution).Symbol for ticker in equities
]
for equity in self.equities:
self.Securities[equity].SetDataNormalizationMode(DataNormalizationMode.Raw)
self.safeties = [
self.AddEquity(ticker, resolution).Symbol for ticker in safeties
]
for safety in self.safeties:
self.Securities[safety].SetDataNormalizationMode(DataNormalizationMode.Raw)
self.symbolData = {
symbol: SymbolData(self, symbol, resolution,
superTrendPeriod, superTrendMultiple,
squeezeTrendPeriod, squeezeBBMultiple,
squeezeKeltMultiple, superTrendUseHA)
for symbol in self.equities + self.safeties
}
self.universe = self.equities
self.UniverseSettings.DataNormalizationMode = DataNormalizationMode.Raw
self.next_reentry = self.Time
self.was_bull = False
self.inout_signal = False
self.inout_waitdays = 0
for symbol in self.equities + self.safeties:
self.returnsmanager.AddSecurity(symbol)
self.SetWarmUp(max(
inOutLookbackBear, inOutLookbackBull, returns_lookback
), InOut_resolution)
self.SetWarmUp(max(
superTrendPeriod, squeezeTrendPeriod
), resolution)
def Initialize(self):
# self.SetSecurityInitializer(
# lambda x: x.SetDataNormalizationMode(DataNormalizationMode.Raw)
# if x.Type == SecurityType.Equity else None
# )
self.load_configs_and_indicators()
self.market = market
self.SetBenchmark(self.market)
self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage)
#self.SetWarmUp(252, Resolution.Hour)
self.SetStartDate(2020, 1, 1)
#self.SetStartDate(StartDay)
self.SetCash(Invest)
# self.Schedule.On(self.DateRules.WeekStart(), self.TimeRules.Midnight, self.WeekleyFn)
self.days_count = 0
self.curr_day = -1
# equity Symbol: option Symbol
self.options: Dict[Symbol, Symbol] = {}
# flag to make sure "rebalance" logic is called immediately
self.init_rebalance = True
def Print(self, msg):
if debug:
self.Debug(msg)
def OnData(self, data:Slice):
for symbol, symbolData in self.symbolData.items():
if data.Bars.ContainsKey(symbol):
symbolData.Update(data[symbol])
if self.was_bull and not self.IsWarmingUp:
self.rebalance(data)
if self.curr_day == self.Time.day:
return
self.curr_day = self.Time.day
self.CheckOptions()
# EQUITIES LOGIC
if not any([data.Bars.ContainsKey(symbol) for symbol in self.equities]):
return
self.days_count += 1
self.inandout.Update(data)
self.returnsmanager.Update(data)
if self.init_rebalance or self.days_count % rebalance == 0:
self.init_rebalance = False
self.returnsmanager.UpdateWeights()
self.inout_signal, self.inout_waitdays = self.inandout.get_signal()
if self.IsWarmingUp:
return
if self.returnsmanager.IsSell():
self.Liquidate()
self.next_reentry = self.Time + timedelta(days=drawdown_waitdays)
elif (self.inout_signal or disableInAndOut):
debug and self.Print('Bull Condition Reached')
if not self.was_bull and self.Time >= self.next_reentry:
self.Print('Going Bull')
self.SetOptions()
self.go_bull()
self.was_bull = True
elif self.was_bull:
self.Print(f'Going Bear:')
self.SetOptions()
self.go_bear()
self.was_bull = False
self.next_reentry = self.Time + timedelta(days=self.inout_waitdays)
self.buy_options = True
self.PlotSeries()
def BuyOptions(self, data:Slice, equities:List[Symbol]):
# OPTIONS LOGIC
options: List[Symbol] = []
for equity, option in self.options.items():
if option is None or not data.ContainsKey(option) or equity not in equities:
continue
options.append(option)
invested_options = {
kvp.Key.Underlying: kvp.Key for kvp in self.Portfolio
if kvp.Value.Invested and kvp.Key.SecurityType == SecurityType.Option
}
for option in options:
if not option.Underlying in invested_options.keys() and not self.Securities[option].Invested:
if options_weight:
self.SetHoldings(option, (int(self.was_bull) * 2 - 1) * options_weight / len(options))
def CheckOptions(self):
'''
Sell options near expiry, set new options for liquidated ones
'''
invested_options = {
kvp.Key.Underlying: kvp.Key for kvp in self.Portfolio
if kvp.Value.Invested and kvp.Key.SecurityType == SecurityType.Option
}
for equity, option in invested_options.items():
if option.ID.Date - self.Time <= timedelta(days=expiry_liquidation):
self.Liquidate(option)
if option in self.options.keys():
self.SetOptions(equity)
def SetOptions(self, equity=None):
'''
Set new options in the self.options dictionary
'''
if self.IsWarmingUp:
return
self.Print('Setting Options')
if equity is None:
equities = self.equities + self.safeties
else:
equities = [equity]
for equity in equities:
contracts = self.OptionChainProvider.GetOptionContractList(equity, self.Time)
if not contracts:
continue
equity_price = self.Securities[equity].Price
if optionright == OptionRight.Call:
compare = operator.lt if price_strike_ratio < 1 else operator.gt
else:
compare = operator.lt if price_strike_ratio >= 1 else operator.gt
def filter(id: SecurityIdentifier):
return (
id.OptionRight == optionright
and compare(equity_price / id.StrikePrice, price_strike_ratio)
and id.Date - self.Time > timedelta(days=min_expiry)
)
# filter
contracts = [contract for contract in contracts if filter(contract.ID)]
if not contracts:
continue
# sort by date
contracts = sorted(contracts, key=lambda x: x.ID.Date)
# sort by closest strike.
contracts = sorted(contracts, key=lambda x: abs(equity_price - x.ID.StrikePrice))
if contracts:
contract = contracts[0]
self.AddOptionContract(contract, Resolution.Minute)
self.options[equity] = contract
return self.options
def PlotSeries(self):
self.Plot('BullBear', 'bull=1,bear=0', int(self.was_bull))
def go_bear(self):
if self.IsWarmingUp:
return
self.Liquidate()
self.universe = self.safeties
self.UniverseSettings.DataNormalizationMode = DataNormalizationMode.Raw
for symbol in self.safeties:
self.SetHoldings(symbol, equities_weight/len(self.safeties))
self.was_bull = False
def go_bull(self):
if self.IsWarmingUp:
return
self.Liquidate()
for symbol, symbolData in self.symbolData.items():
if symbolData.IsBuy(self.Securities[symbol].Price):
weight = self.returnsmanager.GetWeights().get(symbol, 0)
if weight:
self.SetHoldings(symbol, weight * equities_weight)
def rebalance(self, data:Slice):
bought = []
for symbol, symbolData in self.symbolData.items():
if symbol not in self.universe:
continue
isbuy = symbolData.IsBuy(self.Securities[symbol].Price)
if not isbuy:
self.Liquidate(symbol)
elif not self.Portfolio[symbol].Invested:
weight = self.returnsmanager.GetWeights().get(symbol, 0)
self.SetHoldings(symbol, weight * equities_weight)
bought.append(symbol)
self.BuyOptions(data, bought)
class SymbolData:
def __init__(self, algo:QCAlgorithm, symbol, resolution, periodST, multipleST, periodSQ, BBmultipleSQ, KELTmultipleSQ, useHA):
self.symbol = symbol
self.supertrend = MySuperTrend(periodST, multipleST)
self.squeeze = Squeeze(periodSQ, BBmultipleSQ, KELTmultipleSQ)
self.algo = algo
self.useHA = useHA
if useHA:
self.ha = HeikinAshi(symbol)
def Update(self, input:TradeBar):
if self.useHA:
self.ha.Update(input)
if self.ha.IsReady:
haBar = TradeBar(self.algo.Time, self.symbol,
self.ha.Open.Current.Value, self.ha.High.Current.Value,
self.ha.Low.Current.Value, self.ha.Close.Current.Value, self.ha.Volume.Current.Value)
self.supertrend.Update(haBar)
else:
self.supertrend.Update(input)
self.squeeze.Update(input)
def IsBuy(self, price):
return (self.squeeze.Value or disableSqueeze) or (price > self.supertrend.Value or disableSupertrend)