Overall Statistics
Total Trades
28
Average Win
17.49%
Average Loss
-7.78%
Compounding Annual Return
-71.896%
Drawdown
94.800%
Expectancy
-0.750
Net Profit
-93.581%
Sharpe Ratio
-0.647
Probabilistic Sharpe Ratio
0.052%
Loss Rate
92%
Win Rate
8%
Profit-Loss Ratio
2.25
Alpha
-0.053
Beta
-2.325
Annual Standard Deviation
0.714
Annual Variance
0.51
Information Ratio
-0.738
Tracking Error
0.864
Treynor Ratio
0.199
Total Fees
$33.67
Estimated Strategy Capacity
$3900000.00
Lowest Capacity Asset
GOOCV VP83T1ZUHROL
using System;
using System.Linq;
using QuantConnect.Data;
using QuantConnect.Data.Market;
using System.Collections.Generic;
using QuantConnect.Securities.Option.StrategyMatcher;
using QuantConnect.Orders;
using QuantConnect.Securities.Option;
using QuantConnect.Securities.Positions;
using QuantConnect.Securities.Equity;

namespace QuantConnect.Algorithm.CSharp.S89
{
    /// <summary>
    /// Regression algorithm exercising an equity Call Calendar Spread option strategy and asserting it's being detected by Lean and works as expected
    /// </summary>
    public class OptionEquityCallCalendarSpreadRegressionAlgorithmS89 : OptionEquityBaseStrategyRegressionAlgorithmS89
    {

        /// <summary>
        /// OnData event is the primary entry point for your algorithm. Each new data point will be pumped in here.
        /// </summary>
        /// <param name="slice">Slice object keyed by symbol containing the stock data</param>
        public override void OnData(Slice slice)
        {
            if(equity != null)
            {
            	this.RemoveSecurity(equity.Symbol);
	            equity = null;
            }

            if (!Portfolio.Invested)
            {
                OptionChain chain;
                if (IsMarketOpen(_optionSymbol) && slice.OptionChains.TryGetValue(_optionSymbol, out chain))
                {
                    var contracts = chain
                        .Where(contract => contract.Right == OptionRight.Call)
                        .OrderBy(x => x.Expiry)
                        .ThenBy(x => x.Strike)
                        .ToList();

                    var shortCall = contracts.FirstOrDefault();
                    var longCall = contracts.FirstOrDefault(contract => contract.Expiry > shortCall.Expiry && contract.Strike == shortCall.Strike);
                    if(shortCall == null || longCall == null) return;

                    var initialMargin = Portfolio.MarginRemaining;

                    MarketOrder(shortCall.Symbol, -10);
                    AssertDefaultGroup(shortCall.Symbol, -10);
                    MarketOrder(longCall.Symbol, +10);

                    AssertOptionStrategyIsPresent(OptionStrategyDefinitions.CallCalendarSpread.Name, 10);

                    var freeMarginPostTrade = Portfolio.MarginRemaining;
                    var expectedMarginUsage = 0;
                    if (expectedMarginUsage != Portfolio.TotalMarginUsed)
                    {
                        throw new Exception("Unexpect margin used!");
                    }

                    // we payed the ask and value using the assets price
                    var priceSpreadDifference = GetPriceSpreadDifference(shortCall.Symbol, longCall.Symbol);
                    if (initialMargin != (freeMarginPostTrade + expectedMarginUsage + _paidFees - priceSpreadDifference))
                    {
                        throw new Exception("Unexpect margin remaining!");
                    }
                }
            }
        }


    }
    /// <summary>
    /// Base class for equity option strategy regression algorithms which holds some basic shared setup logic
    /// </summary>
    public abstract class OptionEquityBaseStrategyRegressionAlgorithmS89 : QCAlgorithm
    {
        protected Equity equity;
        protected decimal _paidFees;
        protected Symbol _optionSymbol;

        public override void Initialize()
        {
            SetStartDate(2019, 02, 24);
            SetEndDate(2021, 04, 24);
            SetCash(200000);

            equity = AddEquity("GOOG", leverage: 4);
            var option = AddOption(equity.Symbol);
            _optionSymbol = option.Symbol;

            // set our strike/expiry filter for this option chain
            option.SetFilter(u => u.Strikes(-2, +2)
                                   // Expiration method accepts TimeSpan objects or integer for days.
                                   // The following statements yield the same filtering criteria
                                   .Expiration(0, 180));
        }

        protected void AssertOptionStrategyIsPresent(string name, int? quantity = null)
        {
            if (Portfolio.PositionGroups.Where(group => group.BuyingPowerModel is OptionStrategyPositionGroupBuyingPowerModel)
                .Count(group => ((OptionStrategyPositionGroupBuyingPowerModel)@group.BuyingPowerModel).ToString() == name
                    && (!quantity.HasValue || Math.Abs(group.Quantity) == quantity)) != 1)
            {
                throw new Exception($"Option strategy: '{name}' was not found!");
            }
        }

        protected void AssertDefaultGroup(Symbol symbol, decimal quantity)
        {
            if (Portfolio.PositionGroups.Where(group => group.BuyingPowerModel is SecurityPositionGroupBuyingPowerModel)
                .Count(group => group.Positions.Any(position => position.Symbol == symbol && position.Quantity == quantity)) != 1)
            {
                throw new Exception($"Default groupd for symbol '{symbol}' and quantity '{quantity}' was not found!");
            }
        }

        protected decimal GetPriceSpreadDifference(params Symbol[] symbols)
        {
            var spreadPaid = 0m;
            foreach (var symbol in symbols)
            {
                var security = Securities[symbol];
                var actualQuantity = security.Holdings.AbsoluteQuantity;
                var spread = 0m;
                if (security.Holdings.IsLong)
                {
                    if (security.AskPrice != 0)
                    {
                        spread = security.Price - security.AskPrice;
                    }
                }
                else if (security.BidPrice != 0)
                {
                    spread = security.BidPrice - security.Price;
                }
                spreadPaid += spread * actualQuantity * security.SymbolProperties.ContractMultiplier;
            }

            return spreadPaid;
        }

        /// <summary>
        /// Order fill event handler. On an order fill update the resulting information is passed to this method.
        /// </summary>
        /// <param name="orderEvent">Order event details containing details of the evemts</param>
        /// <remarks>This method can be called asynchronously and so should only be used by seasoned C# experts. Ensure you use proper locks on thread-unsafe objects</remarks>
        public override void OnOrderEvent(OrderEvent orderEvent)
        {
            if (orderEvent.Status == OrderStatus.Filled)
            {
                _paidFees += orderEvent.OrderFee.Value.Amount;
                if (orderEvent.Symbol.SecurityType.IsOption())
                {
                    var security = Securities[orderEvent.Symbol];
                    var premiumPaid = orderEvent.Quantity * orderEvent.FillPrice * security.SymbolProperties.ContractMultiplier;
                    Log($"{orderEvent}. Premium paid: {premiumPaid}");
                    return;
                }
            }
            Log($"{orderEvent}");
        }
    }
}