| Overall Statistics |
|
Total Orders 1089 Average Win 0.11% Average Loss -0.23% Compounding Annual Return 6.014% Drawdown 10.100% Expectancy 0.152 Start Equity 1000000 End Equity 1199332.4 Net Profit 19.933% Sharpe Ratio -0.132 Sortino Ratio -0.104 Probabilistic Sharpe Ratio 19.101% Loss Rate 21% Win Rate 79% Profit-Loss Ratio 0.45 Alpha -0.051 Beta 0.389 Annual Standard Deviation 0.075 Annual Variance 0.006 Information Ratio -1.217 Tracking Error 0.096 Treynor Ratio -0.025 Total Fees $1392.60 Estimated Strategy Capacity $0 Lowest Capacity Asset EDA 3300W7MX4X5QE|EDA TGRALZT9E5ID Portfolio Turnover 0.12% Drawdown Recovery 294 |
# region imports
from AlgorithmImports import *
# endregion
class PensiveYellowGreenHorse(QCAlgorithm):
def initialize(self):
self.set_start_date(2023, 1, 1)
self.set_cash(1000000)
self.settings.seed_initial_prices = True
self._target_dte = 45
self._close_dte = 15
self._min_delta = 0.03
self._max_delta = 0.07
self._contracts_per_asset = 2
self._num_holdings = 15
self._open_positions = {}
self._selected_symbols = []
self._spy = self.add_equity("SPY", Resolution.DAILY).symbol
self._universe = self.add_universe(self._select_constituents)
self.schedule.on(self.date_rules.month_start(self._spy),
self.time_rules.after_market_open(self._spy, 30),
self._rebalance)
self.schedule.on(self.date_rules.every_day(self._spy),
self.time_rules.after_market_open(self._spy, 30),
self._manage_positions)
def _select_constituents(self, fundamentals: List[Fundamental]) -> List[Symbol]:
spy_constituents = [f for f in fundamentals
if f.has_fundamental_data
and f.asset_classification.morningstar_sector_code != 0
and f.market_cap > 0]
if not spy_constituents:
return []
sorted_by_volume = sorted(spy_constituents,
key=lambda f: f.dollar_volume,
reverse=True)
self._selected_symbols = [f.symbol for f in sorted_by_volume[:self._num_holdings]]
return self._selected_symbols
def on_securities_changed(self, changes: SecurityChanges) -> None:
for security in changes.added_securities:
if security.symbol in self._selected_symbols:
security.set_data_normalization_mode(DataNormalizationMode.RAW)
def _rebalance(self) -> None:
if not self._selected_symbols:
return
for symbol in self._selected_symbols:
self._open_put_position(symbol)
def _open_put_position(self, underlying: Symbol) -> None:
chain = self.option_chain_provider.get_option_contract_list(underlying, self.time)
if not chain:
return
puts = [x for x in chain if x.id.option_right == OptionRight.PUT]
if not puts:
return
target_expiry = self.time + timedelta(days=self._target_dte)
puts = sorted(puts, key=lambda x: abs((x.id.date - target_expiry).days))
if not puts:
return
expiry_group = [x for x in puts if x.id.date == puts[0].id.date]
underlying_price = self.securities[underlying].price
if underlying_price == 0:
return
valid_contracts = []
for contract in expiry_group:
strike = contract.id.strike_price
if strike < underlying_price:
valid_contracts.append(contract)
if not valid_contracts:
return
valid_contracts = sorted(valid_contracts, key=lambda x: x.id.strike_price, reverse=True)
target_strike_index = int(len(valid_contracts) * 0.10)
if target_strike_index >= len(valid_contracts):
target_strike_index = len(valid_contracts) - 1
selected_contract = valid_contracts[target_strike_index]
if selected_contract in [pos['symbol'] for pos in self._open_positions.values()]:
return
self.add_option_contract(selected_contract, Resolution.DAILY)
ticket = self.market_order(selected_contract, -self._contracts_per_asset)
self._open_positions[selected_contract] = {
'symbol': selected_contract,
'expiry': selected_contract.id.date,
'underlying': underlying,
'ticket': ticket
}
self.debug(f"Sold {self._contracts_per_asset} puts on {underlying.value}: {selected_contract.value}, Strike: {selected_contract.id.strike_price}, Expiry: {selected_contract.id.date}")
def _manage_positions(self) -> None:
positions_to_close = []
for option_symbol, position_info in self._open_positions.items():
days_to_expiry = (position_info['expiry'].date() - self.time.date()).days
if days_to_expiry <= self._close_dte:
positions_to_close.append(option_symbol)
for option_symbol in positions_to_close:
if self.portfolio[option_symbol].invested:
self.liquidate(option_symbol)
self.debug(f"Closed position: {option_symbol.value}")
del self._open_positions[option_symbol]