| Overall Statistics |
|
Total Orders 14 Average Win 7.90% Average Loss -2.07% Compounding Annual Return 20.609% Drawdown 9.700% Expectancy 1.754 Start Equity 100000 End Equity 126704.3 Net Profit 26.704% Sharpe Ratio 0.715 Sortino Ratio 0.571 Probabilistic Sharpe Ratio 53.219% Loss Rate 43% Win Rate 57% Profit-Loss Ratio 3.82 Alpha 0.035 Beta 0.427 Annual Standard Deviation 0.13 Annual Variance 0.017 Information Ratio -0.312 Tracking Error 0.136 Treynor Ratio 0.218 Total Fees $50.70 Estimated Strategy Capacity $0 Lowest Capacity Asset MSFT YMQUHT7C9AHY|MSFT R735QTJ8XC9X Portfolio Turnover 0.18% |
# Strategy is Buy 1 month ATM call option if you there is a price break out
# and close shortly before expiration
# region imports
from AlgorithmImports import *
# endregion
class HyperActiveYellowGreenLeopard(QCAlgorithm):
def initialize(self):
self.set_start_date(2023, 8, 6)
self.set_end_date(2025, 1, 31)
self.set_cash(100000)
equity = self.add_equity("MSFT", Resolution.MINUTE)
equity.set_data_normalization_mode(DataNormalizationMode.RAW)
self.equity = equity.symbol
option = self.add_option("MSFT", Resolution.DAILY)
#set filter on option data needed
#first two arguments tell how much below and above the current asset price we want to consider
# for strike rice, as for the below we want ATM options so we keep compact strike price range
# next we set dats to expiration between 20 and 40 days
option.set_filter(-3,3, timedelta(20), timedelta(40))
#indicator to keep track of high price of underlying over the past month
#inbuit max helper indicator to used and we specify equity as data source
#here we use daily resolution as smaller intra day variations are not that important
#since we want to track high prices we mention that as the last argument
self.high = self.max(self.equity, 21, Resolution.DAILY, Field.High)
def on_data(self, data: Slice):
if not self.high.is_ready:
return
#we use portfolio object to create a list of open option positions
#we save those that are of the type option and invested
option_invested = [x.key for x in self.portfolio if x.value.invested and x.value.type==SecurityType.OPTION ]
#if option_invested is not empty it means we have an open option position
# so we check if its about to expire since we dont want to exercise the option
if option_invested:
if self.time + timedelta(4) >= option_invested[0].id.date:
self.liquidate(option_invested[0], "Too close to expiration")
return #return if we are already invested and there is more time left
if self.securities[self.equity].price > self.high.current.value: #self.high is a max indicator
for i in data.option_chains: #we check all available option chain and pass to method BuyCall
chains = i.value
self.BuyCall(chains) #BuyCall is a udf
#finds the right call option and buys it
def BuyCall(self, chains):
#we first filter by expiration dates and take the futhest expiration date available
expiry = sorted(chains, key = lambda x: x.Expiry, reverse = True)[0].Expiry
#we are not taking any put option
calls = [i for i in chains if i.Expiry == expiry and i.right== OptionRight.CALL]
#we choose the option closest to the underlying price
calls_contracts = sorted(calls, key = lambda x: abs(x.strike - x.underlying_last_price))
if len(calls_contracts)==0:
return
self.call = calls_contracts[0]
if self.call.ask_price==0:
return
quantity = self.portfolio.total_portfolio_value / self.call.ask_price
quantity = int(0.05 * quantity/100)
self.buy(self.call.symbol, quantity)
#if you let an option to expire , quantconnect automatically exercises
#since we dont want option to exercise because in the strategy we close them early enough
#below is an example if you want to handle order exercise differently
#you can do so in on_order_event
# def on_order_event(self, order_event):
# order = self.transactions.get_order_by_id(order_event.order_id)
# #to check if order event is an exercise , check order type
# #in case you don't want any share you can liquidate
# if order.type == order_type.option_exercise:
# self.liquidate()