Overall Statistics
Total Trades
2
Average Win
0%
Average Loss
0%
Compounding Annual Return
213.127%
Drawdown
1.900%
Expectancy
0
Net Profit
3.072%
Sharpe Ratio
10.117
Sortino Ratio
18.98
Probabilistic Sharpe Ratio
91.635%
Loss Rate
0%
Win Rate
0%
Profit-Loss Ratio
0
Alpha
0.273
Beta
2.65
Annual Standard Deviation
0.149
Annual Variance
0.022
Information Ratio
11.042
Tracking Error
0.094
Treynor Ratio
0.57
Total Fees
$0.00
Estimated Strategy Capacity
$9000.00
Lowest Capacity Asset
SPXW 32DTGRDSQMWZY|SPX 31
Portfolio Turnover
0.36%
from QuantConnect.Algorithm import QCAlgorithm
from QuantConnect.Data.Custom import *
from QuantConnect.Orders import *
from QuantConnect.Securities.Option import OptionPriceModels
from datetime import timedelta, datetime
import csv
import io
from QuantConnect import OptionRight


'''
REFERENCE:
https://www.quantconnect.com/docs/v2/writing-algorithms/trading-and-orders/order-types/combo-leg-limit-orders
https://www.quantconnect.com/docs/v2/writing-algorithms/securities/asset-classes/us-equity/handling-data
'''
class SPXWTradingAlgorithm(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2023, 12, 20)
        self.SetCash(1000000)
        
        spx = self.AddIndex("SPX").Symbol
        option = self.AddIndexOption(spx, "SPXW") # SPXW is the target non-standard contract
        option.SetFilter(lambda universe: universe.IncludeWeeklys().Strikes(-2, 2).Expiration(timedelta(days=0), timedelta(days=15))) # Note: IncludeWeeklys and get a good range of days
        
        self.symbol = option.Symbol
        option.PriceModel = OptionPriceModels.CrankNicolsonFD()
        option.EnableGreekApproximation = True
        self.Schedule.On(self.DateRules.EveryDay(self.symbol), self.TimeRules.Every(timedelta(days=1)), self.Trade)
        self.last_row = None

        self.SetWarmup(timedelta(days=5))

        # Log the last row during initialization
        self.Trade()
        if self.last_row is not None:
            self.Debug("Last row during initialization: " + str(self.last_row))

    def Trade(self):
        if self.IsWarmingUp:
            return
        
        csv_string = self.Download('https://docs.google.com/spreadsheets/d/1wwadCU8msu6FEUJt1ANoZS2qMO2MWiheARrdm7zaQlM/export?format=csv')
        csv_reader = csv.DictReader(io.StringIO(csv_string))
        for i, order in enumerate(csv_reader):
            self.last_row = order

        if self.last_row is None:
            self.Debug("Last row is None")
            return

        slice = self.CurrentSlice
        expiry = datetime.strptime(str(self.last_row['TWS Contract Date']), '%Y%m%d')

    
        if self.symbol in slice.OptionChains:
            optionchain = slice.OptionChains[self.symbol]
            contracts = [i for i in optionchain if i.Expiry == expiry]
            
            if len(contracts) < 1:
                self.Debug(f"Not enough option contracts for order: {self.last_row}")
                return
            
            contracts.sort(key=lambda x: abs(x.Strike - float(self.last_row['Strike 1'])))
            quantity = int(self.last_row['Order Quantity'])
            if self.Securities.ContainsKey(contracts[0].Symbol.Value):
                contract = self.Securities[contracts[0].Symbol.Value]
                legs = []
                total_limit_price = 0
                quantities = [1, -1]
                for i in range(1, 3):
                    if self.last_row[f'Right {i}']:
                        right = OptionRight.Call if self.last_row[f'Right {i}'] == 'C' else OptionRight.Put
                        limit_price = (contract.AskPrice + contract.BidPrice) / 2
                        legs.append(Leg.Create(contracts[i-1].Symbol.Value, quantities[i-1], limit_price))
                        total_limit_price += limit_price
                        self.Debug(f"Added leg: {contracts[i-1].Symbol.Value}, {right}, { quantities[i-1]}")

                ticket = self.ComboLegLimitOrder(legs, quantity)
                
                for individual_ticket in ticket:
                    if individual_ticket.Status == OrderStatus.Filled: # check the status of Order and then debug values
                        self.Debug(f"Symbol: {individual_ticket.Symbol.Value}; Quantity filled: {individual_ticket.QuantityFilled}; Fill price: {individual_ticket.AverageFillPrice}")
 
    def OnOrderEvent(self, orderEvent):
        if orderEvent.Status == OrderStatus.Filled:
            self.Debug("Order filled: " + str(orderEvent))