Overall Statistics
Total Trades
96
Average Win
0.48%
Average Loss
-0.09%
Compounding Annual Return
9.423%
Drawdown
0.900%
Expectancy
0.576
Net Profit
2.540%
Sharpe Ratio
1.815
Probabilistic Sharpe Ratio
71.167%
Loss Rate
75%
Win Rate
25%
Profit-Loss Ratio
5.31
Alpha
0.028
Beta
0.116
Annual Standard Deviation
0.028
Annual Variance
0.001
Information Ratio
-1.79
Tracking Error
0.082
Treynor Ratio
0.438
Total Fees
$240.00
Estimated Strategy Capacity
$1400000.00
Lowest Capacity Asset
SPY XQ55MD94802U|SPY R735QTJ8XC9X
using System;
using QuantConnect.Data;
using QuantConnect.Securities;
using QuantConnect.Securities.Option;
using QuantConnect.Securities.Option.StrategyMatcher;

namespace QuantConnect {   
    
    
    public class SpyATMSpread: QCAlgorithm {
        
        const int INITIAL_CAPITAL = 50000;		//baseline capital
        const int NUM_CONTRACTS = 10;			//total number of contracts per position
		private decimal lastBuyPrice = 0;		//price of last transaction buy side
        private decimal lastSellPrice = 0;		//price of last transaction sell side
        private decimal posPrice = 0;			//purchase price of position
        private decimal PortfolioVal = 0;
        
        private decimal OpenPrice = 0;				//open price of day, used later
        private decimal FirstCandleHighPrice = 0;	
        private decimal FirstCandleLowPrice = 0;
        private decimal SecondCandleClosePrice = 0;
    
        private Symbol spySymbol;			
        private List<OptionContract> Contracts;	//contracts updated each min
        
        public override void Initialize() {
        		
            SetStartDate(2021,4, 5);         //start date
            SetEndDate(2021, 7, 15);		 //end date
            SetCash(INITIAL_CAPITAL);
            SetWarmUp(TimeSpan.FromDays(30), Resolution.Minute);
            Option option = (Option) null;
    		
            option = AddOption("SPY", Resolution.Minute);			//add spy option to universe
        	option.SetFilter(universe => from symbol in universe	//filter out to 3 days of options
        		.WeeklysOnly()
        		.Strikes(-20,20)
        		.Expiration(TimeSpan.Zero, TimeSpan.FromDays(3))
        		select symbol);
    		spySymbol = option.Symbol;
           
            option.PriceModel = OptionPriceModels.BjerksundStensland();	//
            var optionMuliplier = option.ContractMultiplier;
     
    		Schedule.On( DateRules.Every(DayOfWeek.Monday, DayOfWeek.Wednesday, DayOfWeek.Friday),	TimeRules.At(10,02),  
	    		() => ExecuteTrade(spySymbol,0, NUM_CONTRACTS, "") );
	    		
    	
    		Schedule.On( DateRules.EveryDay(), TimeRules.At(9,30), () =>  PortfolioVal = Portfolio.TotalPortfolioValue);
    		//Schedule.On( DateRules.MonthEnd(), TimeRules.At(9,00),  ListHoldings );
    		//Schedule.On( DateRules.EveryDay(), TimeRules.At(9,30), () =>  Debug("Time: " + Time + "Total Value: " + Portfolio.TotalPortfolioValue));
			//Schedule.On( DateRules.EveryDay(), TimeRules.At(13,58), () =>  Debug("Time: " + Time + "Total Value: " + Portfolio.TotalPortfolioValue));
			//Schedule.On( DateRules.EveryDay(), TimeRules.At(14,02), () =>  Debug("Time: " + Time + "Total Value: " + Portfolio.TotalPortfolioValue));
    	}

        public override void OnData(Slice slice) {
        	
        	OptionChain chain;
           	CaptureProfitLoss();
           	
			if ( slice.OptionChains.TryGetValue(spySymbol, out chain) ) {
                if (chain.Count() == 0) 
                    return;
        	
        		//available contracts to build spread
                Contracts = chain
                    .OrderBy(c => c.Expiry)
                    .ToList();
            
                //Debug("PutContractsSize: " + put_Contracts.Count());
                
                if (Contracts.Count == 0) 
                    return;
               
            } else {
            	return;
            }
            
            //record first 15 min high and low.
            if (Time.Hour == 9 && Time.Minute == 31) {
            	FirstCandleLowPrice = Securities[spySymbol.Underlying].Price;
            	FirstCandleHighPrice = FirstCandleLowPrice;
            	OpenPrice = FirstCandleLowPrice;
            	
                //Debug("Open Price: " + OpenPrice + " Time: " + Time);
                
            //record second candle high and low
            } else if (Time.Hour == 9 && Time.Minute > 30 && Time.Minute < 45) {
            	if ( FirstCandleHighPrice < Securities[spySymbol.Underlying].Price) {
            		FirstCandleHighPrice = Securities[spySymbol.Underlying].Price;
            	} else if ( FirstCandleLowPrice >  Securities[spySymbol.Underlying].Price) {
            		FirstCandleLowPrice = Securities[spySymbol.Underlying].Price;
            	}
            	//Debug("First Candle: " + FirstCandleClosePrice + " Time: " + Time);
            	
            //close price of second candle 
            } else if (Time.Hour == 10 && Time.Minute == 0) {
            	SecondCandleClosePrice = Securities[spySymbol.Underlying].Price;
            	//Debug("Second Candle: " + SecondCandleClosePrice + " Time: " + Time);
            } else if ( Time.Hour == 11 ) {
            	FirstCandleHighPrice = 0;
            	SecondCandleClosePrice = 0;
            }
            
            
            if(IsMarketOpen(spySymbol)) {
            	
            	if(Time.Hour == 15 && Time.Minute == 30) {
            		Liquidate(null, "End of Day Close");
            	}
            }
			
    	}
    	
    	public override void OnOrderEvent(OrderEvent orderEvent){
    		
    		var order = Transactions.GetOrderById(orderEvent.OrderId);
    		var quan = orderEvent.FillQuantity;
    		var fillPrice = orderEvent.FillPrice;
    		var sym = orderEvent.Symbol;
    		
    		//detect order purchase pricing
    		if (orderEvent.Status != OrderStatus.Filled){
    			//Debug("Order failed: " + sym + " Time: " + Time + " Quan: " + orderEvent.Quantity + " Portfolio: " + Portfolio.TotalPortfolioValue);
            	return;
	        } else {
        		if (orderEvent.Direction == OrderDirection.Buy) {
                	lastBuyPrice = fillPrice;
	            } else if  (orderEvent.Direction == OrderDirection.Sell) {
                	lastSellPrice = fillPrice;
	            }
                //ListHoldings();
                //Debug("Order failed: " + sym + " Time: " + Time + " Quan: " + orderEvent.Quantity + " UnderlyingPrice: " + Securities[sym.Underlying].Price + " Portfolio: " + Portfolio.TotalPortfolioValue);
    		}
    	
    	}
    	
     	public void ExecuteTrade(Symbol sym, int duration, int quan, string tag) {

    		decimal CurPrice = Securities[ spySymbol.Underlying ].Price;
    		
    		//use linq to create possible order
    		if (Contracts != null && Contracts.Count != 0 ) {
	    
	    		var spread = (from short_p in Contracts
    			join short_c in Contracts on short_p.UnderlyingSymbol equals short_c.UnderlyingSymbol
    			join long_p in Contracts on short_p.UnderlyingSymbol equals long_p.UnderlyingSymbol
    			join long_c in Contracts on short_p.UnderlyingSymbol equals long_c.UnderlyingSymbol
    			where short_p.Expiry == short_c.Expiry &&
    				short_p.Expiry == long_p.Expiry &&
    				long_p.Expiry == long_c.Expiry &&
    				(short_p.Expiry.Date-Time.Date).Days == duration &&
    				
    				short_p.Strike == Math.Floor(CurPrice) &&		//just out of the money puts
    				short_c.Strike == Math.Ceiling(CurPrice) &&		//just out of the money calls
    				long_p.Strike == short_p.Strike - 5 &&  		//5 point wide spread
    				long_c.Strike == short_c.Strike + 5 &&			//5 point wide spread
    				
    				short_p.Right == OptionRight.Put &&
    				long_p.Right == OptionRight.Put &&
    				long_c.Right == OptionRight.Call &&
    				short_c.Right == OptionRight.Call 

    			orderby Math.Abs((short_p.Expiry.Date-Time.Date).Days - duration),
    				 Math.Abs(short_p.Strike - CurPrice)
    			select new Tuple<OptionContract, OptionContract, OptionContract, OptionContract>( short_p, long_p, short_c, long_c ) )
            	.FirstOrDefault();
            	
            	//place an order if high low criteria met
	    		if ( IsMarketOpen(sym) ) {
	    			if (spread != null) {
		    			if (spread.Item1 != null && spread.Item2 != null && spread.Item3 != null && spread.Item4 != null) {
		        			//build order for put spread, bullish
		        			if (SecondCandleClosePrice > FirstCandleHighPrice) { 
			        			Order(spread.Item1.Symbol, -quan, false, ("Price " + CurPrice + " Low: " + FirstCandleLowPrice + " High: " + FirstCandleHighPrice) );
			        			Order(spread.Item2.Symbol, quan );
			        			posPrice = lastSellPrice - lastBuyPrice;
		        			} 
		        			//build order for call spread, bearish
		        			else if (SecondCandleClosePrice < FirstCandleLowPrice ) {
		        				Order(spread.Item3.Symbol, -quan, false, ("Price " + CurPrice + " Low: " + FirstCandleLowPrice + " High: " + FirstCandleHighPrice) );
			        			Order(spread.Item4.Symbol, quan );
			        			posPrice = lastSellPrice - lastBuyPrice;
		        			}
		               	} else {
		               		Debug("No Contracts in field");
		               	}
		               	Debug("Position Price: " + posPrice);
	    			}
	    		} 
    		}	
    	}
    	
    	public void CaptureProfitLoss(){
    		
    		Dictionary<Symbol, SecurityHolding> option_invested = Portfolio
    			.Where(x => x.Value.Invested == true &&
    				x.Value.Type == SecurityType.Option )
       			.OrderBy(x => x.Key.ID.StrikePrice)
    			.Reverse().ToDictionary();
    	
    		decimal value = 0;
    		decimal profit = 0;
    		decimal credit = 0;
    		decimal cost = 0;
    		decimal pandl = 0;
    		
    		//check if we have open positions
    		if (option_invested.Count > 0) { 
	    		
	    		//iterate through option holdings
	    		foreach(var pos in option_invested) {
	    			pandl += Portfolio[pos.Key].TotalCloseProfit();
	    		
	    			
	    			if (Portfolio[pos.Key].IsShort) {
	    				value += Portfolio[pos.Key].Price;
	    				cost += Portfolio[pos.Key].AveragePrice;
	    				
	    			} else if (Portfolio[pos.Key].IsLong) {
	    				value -= Portfolio[pos.Key].Price;
	    				cost -= Portfolio[pos.Key].AveragePrice;
	    			}
	    			//profit += Portfolio[pos.Key].UnrealizedProfit;
	    			//cost += Portfolio[pos.Key].HoldingsCost;
	    			//value += Portfolio[pos.Key].TotalCloseProfit();
	    			
	    		}
	    		
	    		//close for profit at 50%
	    		if ( cost >= value * 2M  ){
	    			//Debug("Take Profit " + Time);
	    			Liquidate(null, "Take Profit");
	    		} // close for loss at ~70 loss
	    		else if (  pandl <= -70M )  {
	    			//Debug("Stop loss: " + Time);
	    			Liquidate(null, "Stop Loss" );
	    		}
	    		
	    		//Debug("Position Price: " + posPrice + " Value: " + value);
	    		
    		}
    		
    	}
    	
    	//Are you holding any options of symbol
    	public bool isInvested(Symbol sym) {
    		
    		Dictionary<Symbol, SecurityHolding> option_invested = Portfolio
    			.Where(x => x.Value.Symbol.Underlying == sym.Underlying && 
    				x.Value.Type == SecurityType.Option &&
    				x.Value.Invested == true)
				.ToDictionary();
			
    		if (option_invested.Count() > 0)
    			return true;
    	
    		return false;
          
    	}
    	
    	public bool isMarketOpenDay(Symbol sym, int d) {
    		var hours = Securities[sym.Canonical].Exchange.Hours;
    		var todayClose = hours.GetNextMarketClose(Time.Date, false);
			
			if (hours.IsDateOpen(Time.Date.AddDays(d))){
				var tomorrowOpen = hours.GetNextMarketOpen(Time.Date.AddDays(d), false);
    			var tomorrowClose = hours.GetNextMarketClose(Time.Date.AddDays(d), false);
    			
    			return true;
			}
			
			return false;
		
    	}
    
    	
    	public void ListHoldings() {
    		Dictionary<Symbol, SecurityHolding> option_invested = Portfolio
    			.Where(x => x.Value.Invested == true )
       			.OrderBy(x => x.Key)
    			.Reverse().ToDictionary();
    		
			foreach(KeyValuePair<Symbol, SecurityHolding> entry in option_invested) {
    			Log(entry.Value.Symbol + " Quan: " + entry.Value.Quantity + " Margin: " + Portfolio.MarginRemaining + " Time: " + Time);
    		}
    		Log("____________");
    		
    	}
    	
    	public decimal MidPrice(OptionContract s, OptionContract l) {
    		return ((s.AskPrice - l.AskPrice) + (s.BidPrice - l.BidPrice)) / 2;
    	}
    
	}
	


}