| Overall Statistics |
|
Total Trades 222 Average Win 5.53% Average Loss -1.38% Compounding Annual Return 22.861% Drawdown 26.200% Expectancy 1.279 Net Profit 550.250% Sharpe Ratio 0.977 Loss Rate 54% Win Rate 46% Profit-Loss Ratio 4.00 Alpha 0.11 Beta 0.889 Annual Standard Deviation 0.194 Annual Variance 0.038 Information Ratio 0.654 Tracking Error 0.153 Treynor Ratio 0.214 Total Fees $1689.75 |
/*
* 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 QuantConnect.Orders;
using QuantConnect.Orders.Fees;
namespace QuantConnect.Securities.Option
{
/// <summary>
/// Represents a simple option margining model.
/// </summary>
/// <remarks>
/// Options are not traded on margin. Margin requirements exist though for those portfolios with short positions.
/// Current implementation covers only single long/naked short option positions.
/// </remarks>
public class CustomOptionMarginModel : SecurityMarginModel
{
// initial margin
private const decimal OptionMarginRequirement = 1;
private const decimal NakedPositionMarginRequirement = 0.0000001m;
private const decimal NakedPositionMarginRequirementOtm = 0.0000002m;
/// <summary>
/// Initializes a new instance of the <see cref="OptionMarginModel"/>
/// </summary>
/// <param name="requiredFreeBuyingPowerPercent">The percentage used to determine the required unused buying power for the account.</param>
public CustomOptionMarginModel(decimal requiredFreeBuyingPowerPercent = 0)
{
RequiredFreeBuyingPowerPercent = requiredFreeBuyingPowerPercent;
}
/// <summary>
/// Gets the current leverage of the security
/// </summary>
/// <param name="security">The security to get leverage for</param>
/// <returns>The current leverage in the security</returns>
public override decimal GetLeverage(Security security)
{
// Options are not traded on margin
return 1;
}
/// <summary>
/// Sets the leverage for the applicable securities, i.e, options.
/// </summary>
/// <param name="security"></param>
/// <param name="leverage">The new leverage</param>
public override void SetLeverage(Security security, decimal leverage)
{
// Options are leveraged products and different leverage cannot be set by user.
throw new InvalidOperationException("Options are leveraged products and different leverage cannot be set by user");
}
/// <summary>
/// Gets the total margin required to execute the specified order in units of the account currency including fees
/// </summary>
/// <param name="parameters">An object containing the portfolio, the security and the order</param>
/// <returns>The total margin in terms of the currency quoted in the order</returns>
protected override decimal GetInitialMarginRequiredForOrder(
InitialMarginRequiredForOrderParameters parameters)
{
//Get the order value from the non-abstract order classes (MarketOrder, LimitOrder, StopMarketOrder)
//Market order is approximated from the current security price and set in the MarketOrder Method in QCAlgorithm.
var fees = parameters.Security.FeeModel.GetOrderFee(
new OrderFeeParameters(parameters.Security,
parameters.Order)).Value;
var feesInAccountCurrency = parameters.CurrencyConverter.
ConvertToAccountCurrency(fees).Amount;
var value = parameters.Order.GetValue(parameters.Security);
var orderValue = value * GetInitialMarginRequirement(parameters.Security, value);
return orderValue + Math.Sign(orderValue) * feesInAccountCurrency;
}
/// <summary>
/// Gets the margin currently alloted to the specified holding
/// </summary>
/// <param name="security">The security to compute maintenance margin for</param>
/// <returns>The maintenance margin required for the </returns>
protected override decimal GetMaintenanceMargin(Security security)
{
return security.Holdings.AbsoluteHoldingsCost * GetMaintenanceMarginRequirement(security, security.Holdings.HoldingsCost);
}
/// <summary>
/// Gets the margin cash available for a trade
/// </summary>
/// <param name="portfolio">The algorithm's portfolio</param>
/// <param name="security">The security to be traded</param>
/// <param name="direction">The direction of the trade</param>
/// <returns>The margin available for the trade</returns>
protected override decimal GetMarginRemaining(SecurityPortfolioManager portfolio, Security security, OrderDirection direction)
{
return 100000000m;
var result = portfolio.MarginRemaining;
if (direction != OrderDirection.Hold)
{
var holdings = security.Holdings;
//If the order is in the same direction as holdings, our remaining cash is our cash
//In the opposite direction, our remaining cash is 2 x current value of assets + our cash
if (holdings.IsLong)
{
switch (direction)
{
case OrderDirection.Sell:
result +=
// portion of margin to close the existing position
GetMaintenanceMargin(security) +
// portion of margin to open the new position
security.Holdings.AbsoluteHoldingsValue * GetInitialMarginRequirement(security, security.Holdings.HoldingsValue);
break;
}
}
else if (holdings.IsShort)
{
switch (direction)
{
case OrderDirection.Buy:
result +=
// portion of margin to close the existing position
GetMaintenanceMargin(security) +
// portion of margin to open the new position
security.Holdings.AbsoluteHoldingsValue * GetInitialMarginRequirement(security, security.Holdings.HoldingsValue);
break;
}
}
}
result -= portfolio.TotalPortfolioValue * RequiredFreeBuyingPowerPercent;
return result < 0 ? 0 : result;
}
/// <summary>
/// The percentage of an order's absolute cost that must be held in free cash in order to place the order
/// </summary>
protected override decimal GetInitialMarginRequirement(Security security)
{
return GetInitialMarginRequirement(security, security.Holdings.HoldingsValue);
}
/// <summary>
/// The percentage of the holding's absolute cost that must be held in free cash in order to avoid a margin call
/// </summary>
public override decimal GetMaintenanceMarginRequirement(Security security)
{
return GetMaintenanceMarginRequirement(security, security.Holdings.HoldingsValue);
}
/// <summary>
/// The percentage of an order's absolute cost that must be held in free cash in order to place the order
/// </summary>
private decimal GetInitialMarginRequirement(Security security, decimal holding)
{
return GetMarginRequirement(security, holding);
}
/// <summary>
/// The percentage of the holding's absolute cost that must be held in free cash in order to avoid a margin call
/// </summary>
private decimal GetMaintenanceMarginRequirement(Security security, decimal holding)
{
return GetMarginRequirement(security, holding);
}
/// <summary>
/// Private method takes option security and its holding and returns required margin. Method considers all short positions naked.
/// </summary>
/// <param name="security">Option security</param>
/// <param name="value">Holding value</param>
/// <returns></returns>
private decimal GetMarginRequirement(Security security, decimal value)
{
return 0m;
var option = (Option) security;
if (value == 0m ||
option.Close == 0m ||
option.StrikePrice == 0m ||
option.Underlying == null ||
option.Underlying.Close == 0m)
{
return 0m;
}
if (value > 0m)
{
return OptionMarginRequirement;
}
var absValue = -value;
var optionProperties = (OptionSymbolProperties) option.SymbolProperties;
var underlying = option.Underlying;
// inferring ratios of the option and its underlying to get underlying security value
var multiplierRatio = underlying.SymbolProperties.ContractMultiplier / optionProperties.ContractMultiplier;
var quantityRatio = optionProperties.ContractUnitOfTrade;
var priceRatio = underlying.Close / (absValue / quantityRatio);
var underlyingValueRatio = multiplierRatio * quantityRatio * priceRatio;
// calculating underlying security value less out-of-the-money amount
var amountOTM = option.Right == OptionRight.Call
? Math.Max(0, option.StrikePrice - underlying.Close)
: Math.Max(0, underlying.Close - option.StrikePrice);
var priceRatioOTM = amountOTM / (absValue / quantityRatio);
var underlyingValueRatioOTM = multiplierRatio * quantityRatio * priceRatioOTM;
return OptionMarginRequirement +
option.Holdings.AbsoluteQuantity * Math.Max(NakedPositionMarginRequirement * underlyingValueRatio,
NakedPositionMarginRequirementOtm * underlyingValueRatio - underlyingValueRatioOTM);
}
}
}using System;
using System.Collections.Generic;
using System.Linq;
using QuantConnect;
using QuantConnect.Data;
using QuantConnect.Data.Market;
using QuantConnect.Interfaces;
using QuantConnect.Orders;
using QuantConnect.Securities.Option;
namespace QuantConnect.Algorithm.CSharp
{
public class OptionExperiment3 : QCAlgorithm
{
private Symbol _optionSymbol;
private String ticker = "SPY";
private int min_expiry = 40;
private int max_expiry = 50;
private decimal short_moneyness = 0.98m;
private decimal long_moneyness = 0.8m;
private decimal short_premium_pcnt = 0.02m;
private decimal long_premium_pcnt = 0.01m;
private string liquidate = null;
private OptionContract ShortPut = null;
private OptionContract LongPut = null;
private List<OptionContract> PrevLongPuts = new List<OptionContract>();
private decimal MaxLoss = 0;
private decimal MaxWin = 0;
private decimal mult = 1;
private decimal TakeProfit = 0.95m;
public override void Initialize()
{
// SetStartDate(2015, 7, 7);
// SetEndDate(2016, 11, 1);
SetStartDate(2010, 1, 1);
// SetStartDate(2018, 1, 1);
SetCash(60000);
SetSecurityInitializer(x => {
if (x is Option)
{
x.MarginModel = new CustomOptionMarginModel();
}
});
var equity = AddEquity(ticker, Resolution.Minute);
equity.SetLeverage(10000000);
var option = AddOption(ticker, Resolution.Minute);
option.MarginModel = new CustomOptionMarginModel();
_optionSymbol = option.Symbol;
Securities[ticker].SetDataNormalizationMode(DataNormalizationMode.Raw);
option.SetFilter(universe => from symbol in universe
.IncludeWeeklys()
.Expiration(TimeSpan.FromDays(min_expiry), TimeSpan.FromDays(max_expiry))
.Strikes(-500, +1)
select symbol);
option.PriceModel = OptionPriceModels.CrankNicolsonFD();
SetWarmup(TimeSpan.FromDays(365));
SetBenchmark(ticker);
}
public override void OnData(Slice slice)
{
if (IsWarmingUp) return;
ProcessPrevLongPuts();
ProcessCurrentShortPut();
if (liquidate != null)
{
ProcessLiquidate();
return;
}
if (ShortPut == null)
{
Invest(slice);
}
}
public void ProcessPrevLongPuts()
{
PrevLongPuts.RemoveAll(put => {
if (put == null || Portfolio[put.Symbol] == null || !Portfolio[put.Symbol].Invested)
{
return true;
}
if (Portfolio[put.Symbol].UnrealizedProfit >= 0)
{
this.Liquidate(put.Symbol);
return true;
}
if (put != null && put.Expiry < Time.AddDays(1))
{
this.Liquidate(put.Symbol);
return true;
}
return false;
});
}
public void ProcessCurrentShortPut()
{
if (ShortPut == null)
{
return;
}
if (ShortPut.Expiry < Time.AddDays(1))
{
liquidate = "Liq @ 1 DTE";
return;
}
if (MaxWin > 0 && this.Portfolio.TotalUnrealizedProfit > MaxWin * TakeProfit) {
liquidate = "Take Profit @ .95";
return;
}
}
public void ProcessLiquidate()
{
if (Portfolio[ticker] != null && Portfolio[ticker].Invested) {
this.Liquidate(ticker);
}
if (ShortPut != null)
{
this.Liquidate(ShortPut.Symbol, liquidate);
ShortPut = null;
}
if (LongPut != null) {
// PrevLongPuts.Add(LongPut);
this.Liquidate(LongPut.Symbol, liquidate);
LongPut = null;
}
MaxWin = 0;
MaxLoss = 0;
liquidate = null;
}
public void Invest(Slice slice)
{
OptionChain chain;
if (slice.OptionChains.TryGetValue(_optionSymbol, out chain))
{
ShortPut = chain
.Where(x => x.Right == OptionRight.Put
&& x.Strike > 0
&& x.BidPrice > 0
&& x.BidPrice > chain.Underlying.Price * short_premium_pcnt)
.OrderBy(x => Math.Abs(chain.Underlying.Price * short_moneyness - x.Strike))
.ThenByDescending(x => x.Expiry)
.FirstOrDefault();
if (ShortPut == null || ShortPut.Expiry < Time.AddDays(min_expiry))
{
ShortPut = null;
LongPut = null;
return;
}
LongPut = chain
.Where(x => x.Right == OptionRight.Put
&& x.Expiry == ShortPut.Expiry
&& chain.Underlying.Price * long_moneyness >= x.Strike
&& x.AskPrice <= ShortPut.BidPrice * long_premium_pcnt)
.OrderByDescending(x => x.Strike)
.FirstOrDefault();
if (LongPut == null)
{
ShortPut = null;
LongPut = null;
return;
}
this.Debug("Underlying: " + chain.Underlying.Price + ", Short strike: " + ShortPut.Strike.ToString() + ", Long strike: " + LongPut.Strike.ToString());
var width = ShortPut.Strike - LongPut.Strike;
var cash = Portfolio.CashBook["USD"].Amount;
var count = Math.Floor(cash / width / 100.00m);
var LongOrder = this.MarketOrder(LongPut.Symbol, 1.00m * mult * count, asynchronous: false);
var ShortOrder = this.MarketOrder(ShortPut.Symbol, -1.00m * mult * count, asynchronous: false);
MaxLoss = width * 100 * count;
MaxWin = (ShortOrder.AverageFillPrice - LongOrder.AverageFillPrice) * 100.00m * count;
}
}
public override void OnAssignmentOrderEvent(OrderEvent assignmentEvent)
{
liquidate = "Liq assigned shares";
}
}
public class QuandlVix : Quandl
{
public QuandlVix() : base(valueColumnName: "vix close") { }
}
}