| Overall Statistics |
|
Total Orders 12 Average Win 0.02% Average Loss -0.34% Compounding Annual Return -5.939% Drawdown 0.300% Expectancy -0.289 Start Equity 100000 End Equity 99771 Net Profit -0.229% Sharpe Ratio -5.9 Sortino Ratio -3.488 Probabilistic Sharpe Ratio 0.014% Loss Rate 33% Win Rate 67% Profit-Loss Ratio 0.07 Alpha -0.022 Beta -0.025 Annual Standard Deviation 0.008 Annual Variance 0 Information Ratio -6.627 Tracking Error 0.152 Treynor Ratio 1.854 Total Fees $6.00 Estimated Strategy Capacity $5100000.00 Lowest Capacity Asset QQQ XUKO1IB6NHWM|QQQ RIWIV7K5Z9LX Portfolio Turnover 5.63% |
# region imports
from AlgorithmImports import *
# endregion
from System.Drawing import Color
from datetime import timedelta
from collections import deque
class DancingBrownCow(QCAlgorithm):
def initialize(self):
self.set_start_date(2021, 12, 18)
self.set_end_date(2022, 1, 1)
self.set_cash(100000)
self.qqq = self.add_equity("QQQ", Resolution.Minute)
self.qqq.set_data_normalization_mode(mode = DataNormalizationMode.RAW) # Options only support raw data
self.symbol = self.qqq.symbol
# Consolidate data
self.consolidate(self.symbol, timedelta(days=1), self.consolidation_handler)
# Set brokerage model
self.set_brokerage_model(BrokerageName.QuantConnectBrokerage, AccountType.MARGIN)
#self.SetBrokerageModel(BrokerageName.TradierBrokerage, AccountType.Margin)
self.set_benchmark(self.symbol)
self.filtered_contracts = []
self.implied_volatiliy = []
self.contract_added = set()
self.put_contract = None
self.call_contract = None
self.call_contract_order = None
self.put_contract_order = None
self.days_before_expiration = datetime.min
self.iv_scaling = 1.5 # How much we need to scale IV
# Schedule events
self.schedule.on(date_rule = self.schedule.date_rules.every_day(self.symbol),
time_rule = self.schedule.time_rules.before_market_open(self.symbol, 30),
callback = self.add_contracts)
self.schedule.on(date_rule = self.schedule.date_rules.every_day(self.symbol),
time_rule = self.schedule.time_rules.after_market_close(self.symbol, 30),
callback = self.clean_contracts)
# Import the necessary module before using Custom color
stockPlot = Chart('Trade Plot')
stockPlot.add_series(CandlestickSeries('Price', index = 0, unit = '$'))
#stockPlot.add_series(Series('Close Price', SeriesType.Line, '$', Color.Orange))
stockPlot.add_series(Series('Upper-Band', SeriesType.Scatter, '$', Color.Red, ScatterMarkerSymbol.Triangle))
stockPlot.add_series(Series('Lower-Band', SeriesType.Scatter, '$', Color.Blue, ScatterMarkerSymbol.TriangleDown))
self.AddChart(stockPlot)
# Define the consolidation handler
def consolidation_handler(self, consolidated_bar: TradeBar):
# Plot candlestick and plot debugging data
price = consolidated_bar
self.Plot('Trade Plot', 'Price', open = price.open, high = price.high, low = price.low, close = price.close)
# Print whether close price within the bar
if self.call_contract and self.put_contract:
closed_inside = (self.call_contract[0].ID.strike_price > price.close and self.put_contract[0].ID.strike_price < price.close)
self.log(f"Open: {price.open}, Close: {price.close}, Upper: {self.call_contract[0].ID.strike_price}, Lower: {self.put_contract[0].ID.strike_price}, Success: {closed_inside}")
# Populate upper and lower bands
if self.call_contract:
self.plot('Trade Plot', 'Upper-Band', self.call_contract[0].ID.strike_price)
if self.put_contract:
self.plot('Trade Plot', 'Lower-Band', self.put_contract[0].ID.strike_price)
def clean_contracts(self):
# Close all pending contracts
if self.call_contract:
self.Transactions.CancelOpenOrders(self.call_contract[0])
if self.put_contract:
self.Transactions.CancelOpenOrders(self.put_contract[0])
# First clean contracts. We are populating it every day
self.filtered_contracts = []
self.implied_volatiliy = []
self.contract_added = set()
self.put_contract_order = None
self.call_contract_order = None
def add_contracts(self):
# Rest contracts here, since we would like to use them in consolidated data
self.put_contract = None
self.call_contract = None
# Add option chain contracts using todays date
# One disadvantage is option chain providers don't have greeks
contracts = self.option_chain_provider.get_option_contract_list(self.symbol, date = self.time)
# Get last known price
self.underlying_price = self.get_last_known_price(self.securities[self.symbol]).price
# Get contracts that expire same day
closest_contracts = sorted([p for p in contracts if (p.ID.date - self.time).days == 0], key = lambda x: x.ID.date)
# Filter contract based on price closenest
closest_contracts = [(p, abs(self.underlying_price - p.ID.strike_price))
for p in closest_contracts if abs(self.underlying_price - p.ID.strike_price) < self.underlying_price * 0.05]
# Sort based on closenest
closest_contracts = sorted(closest_contracts, key = lambda x: x[1])
# Get contract values onlu
self.filtered_contracts = [c[0] for c in closest_contracts]
# Now add this data to contract
for contract in self.filtered_contracts:
if contract not in self.contract_added:
self.contract_added.add(contract)
option = self.add_option_contract(contract)
# Set early assignment model to Null
#option.set_option_assignment_model(NullOptionAssignmentModel())
def on_data(self, data: Slice):
# This has to be here otherwise data is not available
if self.symbol not in data or (self.symbol in data and data[self.symbol] is None):
#self.log(self.Time.hour)
#self.log(data[self.symbol])
return
# Sell underlying stock if it has been assigned
if self.portfolio[self.symbol].invested:
self.liquidate(self.symbol)
# First get undelying security price. For each bar
curr_price = data[self.symbol].price
# Check if we need to exit position
if self.call_contract and self.portfolio[self.call_contract[0]].invested:
# Check PL of this contract
# if self.Securities[self.call_contract[0]].Holdings.UnrealizedProfitPercent < -1: #If 100% loss then exit
if curr_price > self.call_contract[0].ID.strike_price:
self.liquidate(self.call_contract[0])
self.log(f"Close position on {self.call_contract[0].value}")
if self.put_contract and self.portfolio[self.put_contract[0]].invested:
#if self.securities[self.put_contract[0]].Holdings.UnrealizedProfitPercent < -1:
if curr_price < self.put_contract[0].ID.strike_price:
self.liquidate(self.put_contract[0])
self.log(f"Close position on {self.put_contract[0].value}")
# We found contracts on this day
if len(self.filtered_contracts) > 0:
if self.time.hour * 60 + self.time.minute >= 10 * 60:
average_IV = 8.8827
# Sell put contract
if self.put_contract is None:
# Sort put options that are closest to curr_price + iv
put_contracts = sorted([x for x in self.filtered_contracts
if (curr_price - average_IV - x.ID.strike_price) > 0
and x.ID.option_right == OptionRight.PUT],
key = lambda x: abs(curr_price - average_IV - x.ID.strike_price))
if len(put_contracts) > 0:
self.put_contract = put_contracts
else:
if self.put_contract and self.put_contract_order is None and not self.portfolio[self.put_contract[0]].invested:
# Get last ask price and set limit order
put_ask = self.Securities[self.put_contract[0]].AskPrice
put_bid = self.Securities[self.put_contract[0]].BidPrice
put_price = np.round((put_ask + put_bid) * 0.5, 2)
# Set limit order
self.put_contract_order = self.limit_order(symbol = self.put_contract[0],
quantity = -1,
limit_price = put_price)
# Check if we have not sold any options yet
if self.call_contract is None:
# Sort call options that are closest to current price
call_contracts = sorted([x for x in self.filtered_contracts
if (x.ID.strike_price - curr_price - average_IV) > 0
and x.ID.option_right == OptionRight.CALL],
key = lambda x: (x.ID.strike_price - curr_price - average_IV))
if len(call_contracts) > 0:
self.call_contract = call_contracts
# Now sell option contract
else:
if self.call_contract and self.call_contract_order is None and not self.portfolio[self.call_contract[0]].invested:
# Get last ask price and set limit order
call_ask = self.Securities[self.call_contract[0]].AskPrice
call_bid = self.Securities[self.call_contract[0]].BidPrice
call_price = np.round((call_ask + call_bid) * 0.5, 2)
# Set limit sell
self.call_contract_order = self.limit_order(symbol = self.call_contract[0],
quantity = -1,
limit_price = call_price)