Overall Statistics
/*
 * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
 * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); 
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
*/

using System;
using System.Linq;
using QuantConnect.Data;
using QuantConnect.Data.Market;
using QuantConnect.Orders;
using QuantConnect.Securities;

namespace QuantConnect.Algorithm.CSharp
{
    public class LongCallOnStockDipAlgorithm : QCAlgorithm
    {
        private const int NUM_DAYS = 5;
        
        private const string UnderlyingTicker = "GOOG";
        public readonly Symbol Underlying = QuantConnect.Symbol.Create(UnderlyingTicker, SecurityType.Equity, Market.USA);
        public Symbol OptionSymbol;
        private const decimal GoalRate = 0.3M;
        private const decimal GoalFactor = 1 + GoalRate;
        private Maximum MaxWindow = new Maximum(NUM_DAYS);
        private DateTime LastPlot = DateTime.MinValue;
        private string OrderTrackingDebugStr = "";
        Chart plotter = new Chart("Plotter");
        bool HasOrderOrHolding = false;

        public override void Initialize()
        {

            SetStartDate(2015, 08, 1);
            SetEndDate(2015, 08, 31);
            SetCash(100000);
            
            
            // Setup underlying equity
            var equity = AddEquity(UnderlyingTicker);
            Securities[UnderlyingTicker].SetDataNormalizationMode(DataNormalizationMode.Raw); // Required for options backtesting
            
            
            // Must add option universe filter
            var option = AddOption(UnderlyingTicker);
            OptionSymbol = option.Symbol;
            option.SetFilter(universe => from symbol in universe

                                        // default is monthly expirations only
                                        .Expiration(TimeSpan.FromDays(30), TimeSpan.FromDays(50))
                                         where symbol.ID.OptionRight == OptionRight.Call
                                         where Math.Abs(symbol.ID.StrikePrice - universe.Underlying.Price) < 20
                                         select symbol);

            // use the underlying equity as the benchmark
            SetBenchmark(equity.Symbol);
            
            
            // Use a daily consolidator to track the maximum value per day
            var dailyConsolidator = new TradeBarConsolidator(TimeSpan.FromDays(1));
            dailyConsolidator.DataConsolidated += OnDataDaily;
            SubscriptionManager.AddConsolidator(equity.Symbol, dailyConsolidator);

			// Plot useful values
            plotter.AddSeries(new Series("Current-Value", SeriesType.Line, index: 0));	
            plotter.AddSeries(new Series(NUM_DAYS.ToString()+"_MaxValue", SeriesType.Line, index: 0));
            plotter.AddSeries(new Series("PercentOfMax", SeriesType.Line, index: 1));
            plotter.AddSeries(new Series("HoldingGoal", SeriesType.Line, index: 2));
            plotter.AddSeries(new Series("HoldingValue", SeriesType.Line, index: 2));
            AddChart(plotter);
            
            
            // Useful for debugging.. Show price action on open position.
            Schedule.On(DateRules.EveryDay(UnderlyingTicker),TimeRules.Every(new TimeSpan(2, 0, 0)), () => // Every 2 hours
            {
                ShowOrderTracking();
            });
        }
        
        
        
        public override void OnData(Slice slice)
        {
        	if ((MaxWindow.IsReady) && (slice.Bars.ContainsKey(UnderlyingTicker)))
            {
                var currentChangePercent = ((slice.Bars[UnderlyingTicker].Close - MaxWindow.Current.Value) / MaxWindow.Current.Value);
				
				if ((Time - LastPlot).TotalSeconds > (900)) // Rate limit plotting to 15 minutes
				{
					LastPlot = Time;
                    Plot("Plotter", "Current-Value", slice.Bars[UnderlyingTicker].Close);
                    Plot("Plotter", NUM_DAYS.ToString()+"_MaxValue", MaxWindow.Current.Value);
                    Plot("Plotter", "PercentOfMax", currentChangePercent);
				}
                ExecuteLong(slice, currentChangePercent);
                ExecuteShort(slice);
            }
        }
        private void OnDataDaily(object sender, TradeBar consolidated)
        {
        	// Track the daily maximum value 
            if (consolidated.Symbol == UnderlyingTicker)
            {
                MaxWindow.Update(Time, consolidated.High);
            }
        }
        
        


        public void ExecuteLong(Slice slice, Decimal percentOfMax)
        {

        	if (HasOrderOrHolding) { return; } // We already have a position

            if ((percentOfMax < -0.025m)
               && (percentOfMax > -0.125m)  // -2.5 to -12.5%
               )
            {

                OptionChain chain;
                if (slice.OptionChains.TryGetValue(OptionSymbol, out chain))
                {
                    var contract = (
                        from optionContract in chain.OrderByDescending(x => x.Strike)
                        //where optionContract.Right == OptionRight.Call  // not necessary here since its in our filter already
                        //where Math.Abs(optionContract.Strike - chain.Underlying.Price) < 20 // not necessary here since its in our filter already
                        select optionContract
                        ).FirstOrDefault();

                    if (contract != null)
                    {
                    	HasOrderOrHolding = true;
                        MarketOrder(contract.Symbol, 1);
                    }
                }
            }

        }

        public void ExecuteShort(Slice slice)
        {

        	foreach (var security_option in Securities)
            {
            	if ((security_option.Value.Symbol.SecurityType == SecurityType.Option) && (security_option.Value.Invested))
            	{
            		Decimal midPrice = MidPrice(security_option.Value.BidPrice, security_option.Value.AskPrice);
            		var currentGoal = ((security_option.Value.Holdings.AveragePrice * security_option.Value.Holdings.AbsoluteQuantity * 100) * GoalFactor) + security_option.Value.Holdings.TotalFees;
            		var currentValue = (midPrice * security_option.Value.Holdings.AbsoluteQuantity * 100);
            	
            		Plot("Plotter", "HoldingGoal", currentGoal);
                    Plot("Plotter", "HoldingValue", currentValue);
                    
            		OrderTrackingDebugStr = String.Format("{0} - POSITION IN {1} {2:0.00} {3} {4:MM/dd/yy} -- Cost {5:0.00} -- Will exit at {6:0.00} and current value={7:0.00}", 
            			Time.ToString(),
            			security_option.Value.Symbol.Underlying,
                        security_option.Value.Symbol.ID.StrikePrice, 
                        security_option.Value.Symbol.ID.OptionRight, 
                        security_option.Value.Symbol.ID.Date,
                        security_option.Value.Holdings.AbsoluteHoldingsCost,
                        currentGoal,
                        currentValue
                        );
                        
                        
            		if (currentValue >= currentGoal)
            		{
            			HasOrderOrHolding = false;
            			Liquidate();
            		}
            	}
            }
        }
        
        private void ShowOrderTracking()
        {
        	if (HasOrderOrHolding)
        	  Debug(OrderTrackingDebugStr);
        }

        

        public override void OnOrderEvent(OrderEvent orderEvent)
        {
            Log(orderEvent.ToString());
        }
        
        private Decimal MidPrice(Decimal Low, Decimal High)
        {
            return Math.Round(((High - Low) / 2) + Low, 2);
        }
        
        
    }
}