Overall Statistics
Total Trades
12
Average Win
0.04%
Average Loss
-0.02%
Compounding Annual Return
15.292%
Drawdown
1.000%
Expectancy
1.988
Net Profit
1.104%
Sharpe Ratio
4.028
Loss Rate
20%
Win Rate
80%
Profit-Loss Ratio
2.73
Alpha
0.344
Beta
-14.53
Annual Standard Deviation
0.028
Annual Variance
0.001
Information Ratio
3.432
Tracking Error
0.028
Treynor Ratio
-0.008
Total Fees
$11.75
using QuantConnect.Securities.Option;

namespace QuantConnect.Algorithm.CSharp
{
    /// <summary>
    /// Basic template algorithm simply initializes the date range and cash. This is a skeleton
    /// framework you can use for designing an algorithm.
    /// </summary>
    public class ShortStraddleDeltaHedge : QCAlgorithm
    {
        private decimal PREMIUM = 0.01m;
        private int MAX_EXPIRY = 30;


        private int _no_K = 2;
        private decimal LEV = 1.0m;
        private decimal DELTA_THRESHOLD = 0.05m;
        private decimal MIN_THRESHOLD_LIQUIDATE = 0.05m;
        private decimal OPTION_MULTIPLE = 100.0m;

        private bool _assignedOption = false;

        private DateTime expiry;
        private DateTime last_trading_day;

        private Equity equity;
        private Symbol equity_symbol;
        private Symbol option_symbol;
        private Symbol put;
        private Symbol call;

        private decimal previous_delta = 0.0m;
        private decimal delta;


        /// <summary>
        /// Initialise the data and resolution required, as well as the cash and start-end dates for your algorithm. All algorithms must initialized.
        /// </summary>
        public override void Initialize()
        {

            SetStartDate(2017, 1, 1);  //Set Start Date
            SetEndDate(2017, 12, 30);    //Set End Date
            SetCash(100000);             //Set Strategy Cash

            equity = AddEquity("SPY", Resolution.Minute);
            equity.SetDataNormalizationMode(DataNormalizationMode.Raw); // IMPORTANT: default
            equity_symbol = equity.Symbol;

            // Add options
            var option = AddOption("SPY", Resolution.Minute);
            option_symbol = option.Symbol;
            // Debug("Options Symbol: " + option.Symbol.ToString());
            // set our strike/expiry filter for this option chain
            option.SetFilter(u => u.IncludeWeeklys()
                             .Strikes(-this._no_K, this._no_K)
                             .Expiration(TimeSpan.Zero, TimeSpan.FromDays(MAX_EXPIRY)));
            option.PriceModel = OptionPriceModels.CrankNicolsonFD();

            SetWarmUp(TimeSpan.FromDays(3)); //Warm up 7 days of data.

            put = null;
            call = null;

			// schedule the closing of options on last trading day
            Schedule.On(DateRules.EveryDay(equity_symbol),
                TimeRules.BeforeMarketClose(equity_symbol, 10),
                        () =>{
                            this.close_options();
                        });
        }

        /**
         *  Liquidate opts (with some value) and underlying
         */
        private void close_options()
        {
            if (last_trading_day != Time.Date) return;

            Log("On last trading day: liquidate options with value and underlying");

            // liquidate options (if invested and in the money [otherwise their price is min of $0.01)
            foreach (Securities.SecurityHolding holding in Portfolio.Values)
            {
                if (holding.Invested)
                {
                    if (Securities[holding.Symbol].AskPrice >= MIN_THRESHOLD_LIQUIDATE)
                    {
                        Liquidate(holding.Symbol);
                    }
                }
            }

            if (Portfolio[this.equity_symbol].Invested)
            {
                Liquidate(equity.Symbol);
            }
        }



        /// <summary>
        /// OnData event is the primary entry point for your algorithm. Each new data point will be pumped in here.
        /// </summary>
        /// <param name="data">Slice object keyed by symbol containing the stock data</param>
        public override void OnData(Slice slice)
        {
            try
            {
                if (IsWarmingUp) return;
                // #1. deal with any early assignments 
                if (_assignedOption)
                {
                    // can't figure out Linq Portfolio.Where(x => x.Value.Invested).Foreach(x => {}) ??
                    foreach (Securities.SecurityHolding holding in Portfolio.Values)
                    {
                        if (holding.Invested)
                        {
                            Liquidate(holding.Symbol);
                        }
                    }
                    _assignedOption = false;
                }

                // #2. sell options, if none
                if (!Portfolio.Invested)
                {
                    //Log("get contract");
                    get_contracts(slice);

                    if (call == null || put == null)
                    {
                        return;
                    }

                    decimal unit_price = Securities[equity_symbol].Price * OPTION_MULTIPLE;
                    int qnty = Convert.ToInt32(Portfolio.TotalPortfolioValue / unit_price);

                    if (call != null) Sell(call, qnty);
                    if (put != null) MarketOrder(put, -qnty);
                }

                // # 3. delta-hedge any existing option
                if (Portfolio.Invested && HourIsMinute(10, 1))
                {
                    this.get_greeks(slice);
                    if (Math.Abs(previous_delta - delta) > DELTA_THRESHOLD)
                    {
                    	
                        Debug("delta_hedging: call: "+call+", put: "+put+", delta: "+ delta);
                        Log("delta_hedging: call: "+call+", put: "+put+", delta: "+ delta);
                        SetHoldings(equity_symbol, delta);
                        previous_delta = delta; // update previous
                    }
                }


            }
            catch (Exception e)
            {
                Error(e.ToString());
            }
        }

        /**
         *
         */
        private bool HourIsMinute(int hour, int minute)
        {
            return Time.Hour == hour && Time.Minute == minute;
        }

        /**
         * Get ATM call and put 
         */
        private void get_contracts(Slice slice)
        {
            // OptionChain chain;
            foreach (KeyValuePair<Symbol, OptionChain> chains in slice.OptionChains)
            {
                if (chains.Key != option_symbol) continue;

                OptionChain chain = chains.Value;

                decimal spot_price = chain.Underlying.Price;
                Log("spot_price: " + spot_price);

                // 1. get furthest expiry
                OptionContract[] sortedByExpiry = chain
                    .OrderByDescending(x => x.Expiry).ToArray();

                expiry = sortedByExpiry[0].Expiry.Date;
                Debug("Expiry: " + expiry.ToShortDateString());
                last_trading_day = this.getLastTradingDay(expiry);

                // // get contracts with further expiry and sort them by strike
                OptionContract[] sortedByStrike = chain
                    .Where(x => x.Expiry == expiry)
                    .OrderBy(x => x.Strike).ToArray();
				
				Log("Expiry used "+expiry.ToShortDateString()+" and shortest "+sortedByExpiry[sortedByExpiry.Length-1].Expiry.Date.ToShortDateString());
				Debug("Expiry used "+expiry.ToShortDateString()+" and shortest "+sortedByExpiry[sortedByExpiry.Length-1].Expiry.Date.ToShortDateString());

                // // 2a. get the ATM closest CALL to short
                OptionContract[] callOptionContracts = sortedByStrike.Where(x => x.Right == OptionRight.Call &&
                                                            x.Strike >= spot_price).ToArray();

                if (callOptionContracts.Length > 0)
                {
                    call = callOptionContracts[0].Symbol;
                }

                // 2b. get the ATM closest put to short
                OptionContract[] putOptionContracts = sortedByStrike.Where(x => x.Right == OptionRight.Put &&
                                                            x.Strike <= spot_price).ToArray();

                if (putOptionContracts.Length > 0)
                {
                    put = putOptionContracts[putOptionContracts.Length - 1].Symbol;
                }
            }
        }

        /**
         *
         */
        private void get_greeks(Slice slice)
        {
            if (put == null || call == null) return;
            OptionChain chain;
            if (slice.OptionChains.TryGetValue(option_symbol, out chain))
            {
                var traded_contracts = chain.Where(x => x.Symbol == put ||
                                            x.Symbol == call);
                if (traded_contracts == null) return;
                delta = traded_contracts.Sum(x => x.Greeks.Delta);
            }
        }

        /**
         *
         */
        private DateTime getLastTradingDay(DateTime expiry)
        {
            Debug("Checking Expiry: " + expiry.ToShortDateString());
            //# American options cease trading on the third Friday, at the close of business 
            //# - Weekly options expire the same day as their last trading day, which will usually be a Friday (PM-settled), [or Mondays? & Wednesdays?]
            //# 
            //# SPX cash index options (and other cash index options) expire on the Saturday following the third Friday of the expiration month. 
            //# However, the last trading day is the Thursday before that third Friday. Settlement price Friday morning opening (AM-settled).
            //# http://www.daytradingbias.com/?p=84847
            if (DayOfWeek.Saturday == expiry.DayOfWeek)
            {
                Debug("Expiry on Saturday");
                return expiry.AddDays(-1);
            }

            if (TradingCalendar.GetDaysByType(TradingDayType.PublicHoliday, expiry, expiry)
               .ToList().Count != 0)
            {
                Debug("Expiry on Holiday");
                return expiry.AddDays(-1);
            }

            return expiry;
        }
        
        /**
         *
         */ 
    }
}