Overall Statistics
Total Orders
430
Average Win
2.67%
Average Loss
-1.55%
Compounding Annual Return
-28.829%
Drawdown
32.200%
Expectancy
-0.216
Start Equity
100000
End Equity
75828.95
Net Profit
-24.171%
Sharpe Ratio
-1.165
Sortino Ratio
-1.25
Probabilistic Sharpe Ratio
1.729%
Loss Rate
71%
Win Rate
29%
Profit-Loss Ratio
1.72
Alpha
-0.185
Beta
-0.437
Annual Standard Deviation
0.212
Annual Variance
0.045
Information Ratio
-1.526
Tracking Error
0.255
Treynor Ratio
0.565
Total Fees
$587.05
Estimated Strategy Capacity
$1000.00
Lowest Capacity Asset
V 32LNYIJSPF3BA|V U12VRGLO8PR9
Portfolio Turnover
1.27%
#region imports
from AlgorithmImports import *
#endregion

LOOKBACK = 20
RES = Resolution.HOUR

class YouTubeSeries(QCAlgorithm):
    def initialize(self):
        self.set_start_date(2024, 1, 1)
        self.set_cash(100000)

        self.universe_settings.asynchronous = True
        self.universe_settings.data_normalization_mode = DataNormalizationMode.RAW
        self.universe_settings.resolution = RES
        
        universe = self.add_universe(self._select_coarse)
        self.add_universe_options(universe, self._option_filter_function)

        self.Indics = {}
        self.dte = 15
        self.uni_size = 10
        
        self.AddRiskManagement(MaximumUnrealizedProfitPercentPerSecurity(1.00))
        self.AddRiskManagement(MaximumDrawdownPercentPerSecurity(0.50))

    def _select_coarse(self, coarse: List[CoarseFundamental]) -> List[Symbol]:
        selected = [c for c in coarse if c.has_fundamental_data]
        sorted_by_dollar_volume = sorted(selected, key=lambda c: c.dollar_volume, reverse=True)
        symbols = [c.symbol for c in sorted_by_dollar_volume[:self.uni_size]]
        return symbols

    def _option_filter_function(self, universe: OptionFilterUniverse) -> OptionFilterUniverse:
        return universe.IncludeWeeklys().Strikes(5, 5).Expiration(timedelta(self.dte), timedelta(self.dte))

    def on_data(self, data: Slice):

        option_invested = [x.Key for x in self.Portfolio if x.Value.Invested and x.Value.Type==SecurityType.OPTION]
        if option_invested:
            if self.Time + timedelta(5) > option_invested[0].ID.Date:
                self.Liquidate(option_invested[0], "Too close to expiration")
            
        equity_invested = [x.Key for x in self.Portfolio if x.Value.Invested and x.Value.Type==SecurityType.EQUITY]
        if equity_invested:
                self.Liquidate(equity_invested[0], tag="Equity in here")

        for _, chain in data.option_chains.items():

            symbol = chain.underlying.symbol
            spot = chain.underlying.price

            exp = sorted(chain, key = lambda x: x.Expiry, reverse=False)
            if len(exp) == 0: continue
            expiry = exp[0].Expiry

            calls = [i for i in chain if i.Expiry == expiry and i.Right == OptionRight.CALL]
            call_contracts = sorted(calls, key = lambda x: abs(x.Strike - spot))
            if len(call_contracts) == 0: continue
            call_con = call_contracts[0]

            if call_con.ask_price > 10.00: continue

            puts = [i for i in chain if i.Expiry == expiry and i.Right == OptionRight.PUT]
            put_contracts = sorted(puts, key = lambda x: abs(x.Strike - spot))
            if len(put_contracts) == 0: continue
            put_con = put_contracts[0]

            if put_con.ask_price > 10.00: continue

            if not (data.contains_key(symbol) and data[symbol] is not None):
                continue

            if not symbol in self.Indics: continue

            bar = data.bars.get(symbol)
            if bar: 
                self.Indics[symbol].st.update(bar)
                self.Indics[symbol].stch.update(bar)

            if not self.Indics[symbol].st.is_ready or not self.Indics[symbol].stch.is_ready: continue


            if not self.portfolio[call_con.symbol].Invested or not self.portfolio[put_con.symbol].Invested:

                c = self.securities[symbol].close
                st = self.Indics[symbol].st.current.value
                stch = self.Indics[symbol].stch.current.value

                if stch > 70 and st < c:
                    self.market_order(call_con.symbol, 1)

                elif stch < 30 and st > c:
                    self.market_order(put_con.symbol, 1)

        
    def on_securities_changed(self, changes: SecurityChanges) -> None:

        for security in changes.added_securities:
            if security.type == SecurityType.EQUITY:
                self.Indics[security.symbol] = Indics()

                trade_bars = self.history[TradeBar](security.symbol, LOOKBACK+1, RES)
                for trade_bar in trade_bars:
                    self.Indics[security.symbol].st.update(trade_bar)
                    self.Indics[security.symbol].stch.update(trade_bar)

        for security in changes.removed_securities:
            if security.type == SecurityType.EQUITY:
                indic = self.Indics.pop(security.symbol, None)

class Indics:
    def __init__(self):
        self.st = SuperTrend(LOOKBACK, 2, MovingAverageType.Wilders)
        self.stch = Stochastic(LOOKBACK, 10, 20)