Overall Statistics
Total Trades
461
Average Win
0.32%
Average Loss
-0.74%
Compounding Annual Return
-14.689%
Drawdown
11.500%
Expectancy
-0.494
Net Profit
-8.776%
Sharpe Ratio
-2.079
Sortino Ratio
-2.35
Probabilistic Sharpe Ratio
1.219%
Loss Rate
65%
Win Rate
35%
Profit-Loss Ratio
0.44
Alpha
-0.158
Beta
0.01
Annual Standard Deviation
0.075
Annual Variance
0.006
Information Ratio
-2.196
Tracking Error
0.124
Treynor Ratio
-16.065
Total Fees
$440.20
Estimated Strategy Capacity
$0
Lowest Capacity Asset
ASTM 32FX6MX22I7AE|ASTM R735QTJ8XC9X
Portfolio Turnover
1.34%
# region imports
from AlgorithmImports import *
# endregion
import math, numpy as np
from datetime import timedelta
from math import floor
from decimal import Decimal

from datetime import timedelta
from QuantConnect.DataSource import *


class ChainedUniverseAlgorithm(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2023, 8, 2)
        self.SetCash(100000)
        self.UniverseSettings.Asynchronous = True
        self.UniverseSettings.DataNormalizationMode = DataNormalizationMode.Raw

        universe1 = self.AddUniverse(self.FundamentalFunction)
        self.AddUniverseOptions(universe1, self.OptionFilterFunction)
        universe2 = self.AddUniverse(self.FundamentalFunction2)
        self.AddUniverseOptions(universe2, self.OptionFilterFunction)

        self.day = 1
        self.universe1 = []
        self.universe2 = []

    def FundamentalFunction(self, fundamental: List[Fundamental]) -> List[Symbol]:
        filtered = (f for f in fundamental if not np.isnan(f.ValuationRatios.PERatio))
        sorted_by_pe_ratio = sorted(filtered, key=lambda f: f.ValuationRatios.PERatio)
        self.universe1 = [f.Symbol for f in sorted_by_pe_ratio[:10]]
        return self.universe1

    def FundamentalFunction2(self, fundamental: List[Fundamental]) -> List[Symbol]:
        filtered = (f for f in fundamental if not np.isnan(f.ValuationRatios.PERatio))
        sorted_by_pe_ratio = sorted(filtered, key=lambda f: f.ValuationRatios.PERatio, reverse=True)
        self.universe2 = [f.Symbol for f in sorted_by_pe_ratio[:10]]
        return self.universe2

    def OptionFilterFunction(self, option_filter_universe: OptionFilterUniverse) -> OptionFilterUniverse:
        return option_filter_universe.Strikes(-2, +2).Expiration(30, 60)

    def OnData(self, data: Slice) -> None:
       
        if self.Time.weekday() == 4:

            if self.Time.hour == 10 and self.Time.minute == 30:

                self.day = -1

        if self.day < 0:
        
            for symbol, chain in data.OptionChains.items():
                if self.Portfolio[chain.Underlying.Symbol].Invested:
                    self.Liquidate(chain.Underlying.Symbol)

                spot = chain.Underlying.Price
                atm_strike = sorted(chain, key=lambda x: abs(x.Strike - chain.Underlying.Price))[0].Strike
                calls = [i for i in chain if i.Strike == atm_strike and i.Right == OptionRight.Call]
                puts = [i for i in chain if i.Strike == atm_strike and i.Right == OptionRight.Put]

                if not calls or not puts:
                    continue
                
                if chain.Underlying.Symbol in self.universe1:
                    symbol1 = calls[0].Symbol
                    self.MarketOrder(symbol1, 1)
                    symbol2 = puts[0].Symbol
                    self.MarketOrder(symbol2, 1)
                if chain.Underlying.Symbol in self.universe2:
                    symbol1 = calls[0].Symbol
                    self.MarketOrder(symbol1, -1)
                    symbol2 = puts[0].Symbol
                    self.MarketOrder(symbol2, -1)

            self.day = 1