| Overall Statistics |
|
Total Trades 3 Average Win 0.03% Average Loss 0% Compounding Annual Return 0.802% Drawdown 0.300% Expectancy 0 Net Profit 0.120% Sharpe Ratio 0.913 Probabilistic Sharpe Ratio 48.570% Loss Rate 0% Win Rate 100% Profit-Loss Ratio 0 Alpha -0.011 Beta 0.124 Annual Standard Deviation 0.008 Annual Variance 0 Information Ratio -2.516 Tracking Error 0.055 Treynor Ratio 0.061 Total Fees $3.00 |
using System;
using System.Linq;
using QuantConnect.Data;
using QuantConnect.Data.Market;
using QuantConnect.Securities;
using QuantConnect.Securities.Equity;
using QuantConnect.Securities.Option;
using System.Collections.Generic;
namespace QuantConnect.Algorithm.CSharp
{
/// <summary>
/// Test dynamic adjustment to the option universe to try to improve options algo backtest performance
/// Test the following sequence:
/// 1. Add option universe using underlying symbol
/// 2. Set universe filter to required strike range and expiration
/// 3. Select option from the Slice OptionChain (eg. by Expiration, Delta)
/// 4. Reduce the option universe using SetFilter to the minimum subscribed set
/// 5. Add the selected option to the subscription
/// 6. For next trade:
/// 7. Close the current option position and unsubscribe the selected option
/// 8. Repeat process from step 2
///
/// This sequence shows an issue that the minimised option universe filter is only applied after the
/// monthly options expiration has occurred, instead of being applied immediately from the next Slice.
///
/// </summary>
public class OptionUniverseTestAlgorithm : QCAlgorithm
{
String _underlyingSymbolName;
Symbol _equitySymbol=null;
Symbol _optionContractSymbol=null;
OptionContract _optionContract;
Option _option = null;
Option _optionUniverse;
Symbol _optionUniverseSymbol=null;
bool _setOptionUniverseFilterOnly=false;
TimeSpan timeSpanSelect50DaysToExpiry = new TimeSpan(50, 0, 0, 0);
public override void Initialize()
{
SetStartDate(2017, 6, 1);
SetEndDate(2017, 7, 25);
SetCash(100000);
_underlyingSymbolName = "SPY";
var equity = AddEquity(_underlyingSymbolName, Resolution.Minute);
equity.SetDataNormalizationMode(DataNormalizationMode.Raw);
_equitySymbol = equity.Symbol;
AddOptionUniverseForUnderlying(isSetFilterOnly:false);
SetWarmup(TimeSpan.FromDays(30));
}
public override void OnData(Slice slice)
{
if (IsWarmingUp)
{
return;
}
if (_optionContractSymbol != null && !IsMarketOpen(_optionContractSymbol))
{
return;
}
if (_optionUniverseSymbol != null && !IsMarketOpen(_optionUniverseSymbol))
{
return;
}
if (Time.Hour==9 && (Time.Minute==50 || Time.Minute==31 || Time.Minute==32) )
{
Debug($"{Time}: OnData: slice.Count={slice.Count}");
}
if (_optionContractSymbol==null && _optionUniverseSymbol==null)
{
AddOptionUniverseForUnderlying(isSetFilterOnly:false);
return;
}
if (_optionContractSymbol==null && _setOptionUniverseFilterOnly)
{
// Update option universe filter only...
AddOptionUniverseForUnderlying(isSetFilterOnly:_setOptionUniverseFilterOnly);
_setOptionUniverseFilterOnly = false;
return;
}
if (_optionContractSymbol!=null && _option==null)
{
// Subscribe only to the specific selected contract...
Debug($"AddOptionContract:{_optionContractSymbol}");
_option = AddOptionContract(_optionContractSymbol, Resolution.Minute);
_option.PriceModel = OptionPriceModels.BjerksundStensland();
Debug($"_option.Symbol={_option.Symbol}");
return;
}
if (_optionContractSymbol!=null)
{
if ((_optionContract.Expiry - Time).TotalDays < 5)
{
Debug($"{Time}: OnData: INFO: Sell : rollover as DTE < 5");
MarketOrder(_optionContractSymbol, -1);
Debug($"RemoveSecurity:{_option.Symbol}");
RemoveSecurity(_option.Symbol);
_optionContractSymbol = null;
_option = null;
_setOptionUniverseFilterOnly = true;
return;
}
}
if (!Portfolio.Invested && _optionContractSymbol!=null)
{
MarketOrder(_optionContractSymbol, 1);
Debug($"{Time}: Buy: optionContractSymbol={_optionContractSymbol}");
}
if (Time.Hour==15 && Time.Minute==45)
{
if (_optionContractSymbol != null)
{
foreach (var kvp in slice.OptionChains)
{
OptionChain chain = kvp.Value;
Debug($"chain.Contracts.Count={chain.Contracts.Count}");
LogOptionChain(chain);
OptionContract contract = chain
.Where(x => x.Symbol == _optionContractSymbol).FirstOrDefault();
LogOptionContract(contract);
}
}
}
if (_optionUniverseSymbol!=null && !Portfolio.Invested && _optionContractSymbol==null && Time.Hour==15 && Time.Minute==45 )
{
Debug($"{Time}: OnData: slice.Count={slice.Count}");
foreach (var kvp in slice)
{
Debug($"---> slice: {Time}, k={kvp.Key.Value}, v={kvp.Value}");
}
Debug($"{Time}: slice.OptionChains.Count={slice.OptionChains.Count}");
OptionChain chain=null;
if (!slice.OptionChains.TryGetValue(_optionUniverseSymbol, out chain))
{
Debug($"{Time}: ERROR: No chain found for {_optionUniverseSymbol}");
return;
}
Debug($"{Time}: chain.Contracts.Count={chain.Contracts.Count}");
Debug($"{Time}: chain.FilteredContracts.Count={chain.FilteredContracts.Count}");
LogOptionChain(chain);
// Select
TimeSpan timeSpanSelect = timeSpanSelect50DaysToExpiry;
_optionContract = chain
.OrderBy(x => Math.Abs(timeSpanSelect.Days - x.Expiry.Subtract(Time).Days))
.Where(x => x.Right == OptionRight.Call)
.FirstOrDefault();
if (_optionContract == null)
{
Debug($"{Time}: ERROR: Closest to {timeSpanSelect.Days} DTE Option selection failed: No option found in chain: optionContract==null");
return;
}
Debug($"{Time}: INFO: Closest to {timeSpanSelect.Days} DTE selection found DTE={(_optionContract.Expiry - Time)} symbol={_optionContract.Symbol}");
if ((_optionContract.Expiry - Time).TotalDays < 15)
{
Debug($"{Time}: INFO: Skipping Option selection as DTE<15 : found DTE={(_optionContract.Expiry - Time)} Symbol={_optionContract.Symbol} Delta={_optionContract.Greeks.Delta:0.00}" );
return;
}
Debug($"{Time}: INFO: Valid Option selection found DTE={(_optionContract.Expiry - Time)} Symbol={_optionContract.Symbol} Delta={_optionContract.Greeks.Delta:0.00}" );
_optionContractSymbol = _optionContract.Symbol;
Debug($"{Time}: MinimiseFilterOptionUniverse");
_optionUniverse.SetFilter(0,0,TimeSpan.Zero, TimeSpan.FromDays(0));
}
}
// ------------------------------------------------------------------------------
void LogOptionChain(OptionChain chain)
{
foreach (var contract in chain)
{
LogOptionContract(contract);
}
}
// ------------------------------------------------------------------------------
void LogOptionContract(OptionContract contract)
{
{
Debug(
String.Format(@"{0},Time={1} ULPrice={2} Last={3} d={4:0.000} IV={5:0.000}",
contract.Symbol.Value,
contract.Time,
contract.UnderlyingLastPrice,
contract.LastPrice,
contract.Greeks.Delta,
contract.ImpliedVolatility
));
}
}
// ------------------------------------------------------------------------------
void AddOptionUniverseForUnderlying(bool isSetFilterOnly)
{
if (!isSetFilterOnly)
{
_optionUniverse = AddOption(_underlyingSymbolName);
_optionUniverseSymbol = _optionUniverse.Symbol;
_optionUniverse.PriceModel = OptionPriceModels.BjerksundStensland();
Debug($"{Time}: AddOption: _optionUniverseSymbol={_optionUniverseSymbol}");
}
Debug($"{Time}: AddOptionUniverseForUnderlying: SetFilter");
_optionUniverse.SetFilter(u =>
{
return u.Expiration(TimeSpan.Zero, TimeSpan.FromDays(50))//some issue requires this to be set to 80 and not 50 to get the next monthly options?
.Strikes(-1, +1)
.Contracts(c => c.Where(s => s.ID.OptionRight == OptionRight.Call));
});
return;
}
}
}