Overall Statistics
Total Trades
106
Average Win
2.18%
Average Loss
-0.50%
Compounding Annual Return
-40.713%
Drawdown
15.000%
Expectancy
1.167
Net Profit
-5.654%
Sharpe Ratio
-0.849
Sortino Ratio
-1.232
Probabilistic Sharpe Ratio
24.809%
Loss Rate
60%
Win Rate
40%
Profit-Loss Ratio
4.37
Alpha
-0.732
Beta
-1.382
Annual Standard Deviation
0.255
Annual Variance
0.065
Information Ratio
0.435
Tracking Error
0.361
Treynor Ratio
0.157
Total Fees
$0.00
Estimated Strategy Capacity
$380000.00
Lowest Capacity Asset
SPXW Y6E4PYLX0PKE|SPX 31
Portfolio Turnover
2.38%
# region imports
from AlgorithmImports import *
# endregion

'''
PROBLEM STATEMENT:
https://www.quantconnect.com/forum/discussion/8399/optionstrategies-limit-orders/p1

REFERENCES:
https://github.com/QuantConnect/Lean/blob/master/Algorithm.Python/IndexOptionIronCondorAlgorithm.py
https://github.com/QuantConnect/Lean/blob/83f9499b4a8faf3a1344b6f22284a35ba91d2e1b/Common/Securities/Option/StrategyMatcher/OptionStrategyDefinitions.cs#L61-L70
https://www.quantconnect.com/docs/v2/writing-algorithms/trading-and-orders/order-types/combo-limit-orders
'''


class CrawlingAsparagusButterfly(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2023, 2, 1)
        self.SetEndDate(2023, 3, 13)
        self.SetCash(100000)

        ticker = self.AddIndex("SPX", Resolution.Minute).Symbol
        option = self.AddIndexOption(ticker, "SPXW", Resolution.Minute)
        self.spy = option.Symbol
        option.SetFilter(lambda x: x.WeeklysOnly().Strikes(-5, 5).Expiration(0, 14))
    
    def OnData(self, data: Slice):
        if self.Portfolio.Invested: return

        chain = data.OptionChains.get(self.spy)
        if not chain: return

        # get minimum expiry
        expiry = min([x.Expiry for x in chain])
        chain = [x for x in chain if x.Expiry == expiry]

        # sort by strike to get K1 and K2 where K2>K1 to make a call spread
        calls = sorted([x for x in chain if x.Right == OptionRight.Call], key=lambda x: x.Strike, reverse=True)
        if len(calls) < 2 : return

        # create Bear call legs #short C1(K1) and long C2(K2)
        
        self.Log(f"Strike Call 1 Leg (short): {calls[-1].Strike}  | Strike Call 2 Leg (Long): {calls[0].Strike}  | Expiry: {calls[0].Expiry}")
        self.Log(f"Strike spread: {calls[0].Strike - calls[-1].Strike}")
        legs = [
            Leg.Create(calls[-1].Symbol, -1),
            Leg.Create(calls[0].Symbol, 1),
        ]

        # Calculate limit price
        limit_price = round(sum([self.Securities[leg.Symbol].Close for leg in legs]) * 0.95, 2)

        # Place order
        self.ComboLimitOrder(legs, 1, limit_price)