| Overall Statistics |
|
Total Trades 233 Average Win 7.42% Average Loss -4.29% Compounding Annual Return 153.040% Drawdown 45.800% Expectancy 0.553 Net Profit 625.279% Sharpe Ratio 1.574 Loss Rate 43% Win Rate 57% Profit-Loss Ratio 1.73 Alpha 0.799 Beta -0.479 Annual Standard Deviation 0.48 Annual Variance 0.231 Information Ratio 1.334 Tracking Error 0.5 Treynor Ratio -1.579 Total Fees $3047.79 |
using QuantConnect.Algorithm;
using QuantConnect.Orders;
using QuantConnect.Securities;
using System;
namespace QuantConnect
{
public abstract class FixedSetHoldingsAlgorithm : QCAlgorithm
{
/// <summary>
/// Alias for SetHoldings to avoid the M-decimal errors.
/// </summary>
/// <param name="symbol">string symbol we wish to hold</param>
/// <param name="percentage">double percentage of holdings desired</param>
/// <param name="liquidateExistingHoldings">liquidate existing holdings if neccessary to hold this stock</param>
/// <seealso cref="MarketOrder"/>
public void FixedSetHoldings(Symbol symbol, double percentage, bool liquidateExistingHoldings = false)
{
FixedSetHoldings(symbol, (decimal)percentage, liquidateExistingHoldings);
}
/// <summary>
/// Alias for SetHoldings to avoid the M-decimal errors.
/// </summary>
/// <param name="symbol">string symbol we wish to hold</param>
/// <param name="percentage">float percentage of holdings desired</param>
/// <param name="liquidateExistingHoldings">bool liquidate existing holdings if neccessary to hold this stock</param>
/// <param name="tag">Tag the order with a short string.</param>
/// <seealso cref="MarketOrder"/>
public void FixedSetHoldings(Symbol symbol, float percentage, bool liquidateExistingHoldings = false, string tag = "")
{
FixedSetHoldings(symbol, (decimal)percentage, liquidateExistingHoldings, tag);
}
/// <summary>
/// Alias for SetHoldings to avoid the M-decimal errors.
/// </summary>
/// <param name="symbol">string symbol we wish to hold</param>
/// <param name="percentage">float percentage of holdings desired</param>
/// <param name="liquidateExistingHoldings">bool liquidate existing holdings if neccessary to hold this stock</param>
/// <param name="tag">Tag the order with a short string.</param>
/// <seealso cref="MarketOrder"/>
public void FixedSetHoldings(Symbol symbol, int percentage, bool liquidateExistingHoldings = false, string tag = "")
{
FixedSetHoldings(symbol, (decimal)percentage, liquidateExistingHoldings, tag);
}
/// <summary>
/// Automatically place an order which will set the holdings to between 100% or -100% of *PORTFOLIO VALUE*.
/// E.g. SetHoldings("AAPL", 0.1); SetHoldings("IBM", -0.2); -> Sets portfolio as long 10% APPL and short 20% IBM
/// E.g. SetHoldings("AAPL", 2); -> Sets apple to 2x leveraged with all our cash.
/// </summary>
/// <param name="symbol">Symbol indexer</param>
/// <param name="percentage">decimal fraction of portfolio to set stock</param>
/// <param name="liquidateExistingHoldings">bool flag to clean all existing holdings before setting new faction.</param>
/// <param name="tag">Tag the order with a short string.</param>
/// <seealso cref="MarketOrder"/>
public void FixedSetHoldings(Symbol symbol, decimal percentage, bool liquidateExistingHoldings = false, string tag = "")
{
//Initialize Requirements:
Security security;
if (!Securities.TryGetValue(symbol, out security))
{
Error(symbol.ToString() + " not found in portfolio. Request this data when initializing the algorithm.");
return;
}
//If they triggered a liquidate
if (liquidateExistingHoldings)
{
foreach (var kvp in Portfolio)
{
var holdingSymbol = kvp.Key;
var holdings = kvp.Value;
if (holdingSymbol != symbol && holdings.AbsoluteQuantity > 0)
{
//Go through all existing holdings [synchronously], market order the inverse quantity:
Order(holdingSymbol, -holdings.Quantity, false, tag);
}
}
}
//Only place trade if we've got > 1 share to order.
var quantity = FixedCalculateOrderQuantity(symbol, percentage);
if (Math.Abs(quantity) > 0)
{
MarketOrder(symbol, quantity, false, tag);
}
}
private bool TryOrderQuantity(int orderQuantity, Security security, decimal marginRemaining, decimal targetOrderValue)
{
//note that margin requirements and order value + fees are assumed to be monotonic w.r.t. orderQuantity,
//otherwise binary search would not work and an exhaustive search would be necessary
var order = new MarketOrder(security.Symbol, orderQuantity, UtcTime);
var orderValue = order.GetValue(security);
var orderFees = security.FeeModel.GetOrderFee(security, order);
// calculate the margin required for the order
var marginRequired = security.MarginModel.GetInitialMarginRequiredForOrder(security, order);
return marginRequired <= marginRemaining && orderValue + orderFees <= targetOrderValue;
}
/// <summary>
/// Calculate the order quantity to achieve target-percent holdings.
/// </summary>
/// <param name="symbol">Security object we're asking for</param>
/// <param name="target">Target percentag holdings, this is an unlevered value, so
/// if you have 2x leverage and request 100% holdings, it will utilize half of the
/// available margin</param>
/// <returns>Order quantity to achieve this percentage</returns>
public int FixedCalculateOrderQuantity(Symbol symbol, decimal target)
{
var security = Securities[symbol];
return FixedCalculateOrderQuantity(symbol, target, (int)security.Holdings.Quantity);
}
public int FixedCalculateOrderQuantity(Symbol symbol, decimal target, int initialQuantity)
{
var security = Securities[symbol];
var price = security.Price;
if (price == 0)
price = (security.BidPrice + security.AskPrice) / 2;
// if targeting zero, simply return the negative of the quantity
if (target == 0) return (int)-initialQuantity;
// can't order it if we don't have data
if (price == 0) return 0;
// this is the value in dollars that we want our holdings to have
var targetPortfolioValue = target * Portfolio.TotalPortfolioValue;
var quantity = initialQuantity;
var currentHoldingsValue = price * quantity;
// remove directionality, we'll work in the land of absolutes
var targetOrderValue = Math.Abs(targetPortfolioValue - currentHoldingsValue);
var direction = targetPortfolioValue > currentHoldingsValue ? OrderDirection.Buy : OrderDirection.Sell;
// determine the unit price in terms of the account currency
var unitPrice = new MarketOrder(symbol, 1, UtcTime).GetValue(security);
// calculate the total margin available
var marginRemaining = Portfolio.GetMarginRemaining(symbol, direction);
if (marginRemaining <= 0) return 0;
// compute the initial order quantity
int orderQuantity;
int maxOrderQuantity = (int)(targetOrderValue / unitPrice); //upper bound
int minOrderQuantity = 1; //lower bound
if (TryOrderQuantity(maxOrderQuantity, security, marginRemaining, targetOrderValue))
{
orderQuantity = maxOrderQuantity;
}
else if (!TryOrderQuantity(minOrderQuantity, security, marginRemaining, targetOrderValue))
{
orderQuantity = 0;
}
else
{
//binary search
for (;;)
{
orderQuantity = (maxOrderQuantity + minOrderQuantity) / 2;
if (orderQuantity == minOrderQuantity)
{
orderQuantity = minOrderQuantity;
break;
}
if (TryOrderQuantity(orderQuantity, security, marginRemaining, targetOrderValue))
{
minOrderQuantity = orderQuantity;
}
else
{
maxOrderQuantity = orderQuantity;
}
}
}
//Rounding off Order Quantity to the nearest multiple of Lot Size
if (orderQuantity % Convert.ToInt32(security.SymbolProperties.LotSize) != 0)
{
orderQuantity = orderQuantity - (orderQuantity % Convert.ToInt32(security.SymbolProperties.LotSize));
}
// add directionality back in
return (direction == OrderDirection.Sell ? -1 : 1) * orderQuantity;
}
}
}using QuantConnect.Data;
using QuantConnect.Data.Custom;
using QuantConnect.Data.Market;
using QuantConnect.Indicators;
using QuantConnect.Securities;
using System;
namespace QuantConnect.Algorithm.CSharp
{
public class SmartVIXAlgorithm : FixedSetHoldingsAlgorithm
{
private Security _xiv;
private Security _vxx;
private Security _tqqq;
private Security _spyg;
private Quandl _lastVIX;
private Quandl _lastVXV;
private Quandl _lastV1;
private Quandl _lastV2;
private string _dataVIX = "CBOE/VIX";
private string _dataVXV = "CBOE/VXV";
private string _dataV1 = "CHRIS/CBOE_VX1";
private string _dataV2 = "CHRIS/CBOE_VX2";
private MovingAverageConvergenceDivergence _macd;
private Identity _xivPrice;
private RollingWindow<decimal> _xivPriceHistory = new RollingWindow<decimal>(2);
//for ML
private decimal _prevXivPrice;
private class VIXQuandl : Quandl
{
public VIXQuandl() : base(valueColumnName: "VIX CLOSE")
{
}
}
private class VFQuandl : Quandl
{
public VFQuandl() : base(valueColumnName: "SETTLE")
{
}
}
public override void Initialize()
{
SetStartDate(2015, 9, 1);
SetEndDate(DateTime.Now.Date.AddDays(-1));
SetCash(30000);
_xiv = AddSecurity(SecurityType.Equity, "XIV", Resolution.Minute);
_vxx = AddSecurity(SecurityType.Equity, "VXX", Resolution.Minute);
_tqqq = AddSecurity(SecurityType.Equity, "TQQQ", Resolution.Minute);
_spyg = AddSecurity(SecurityType.Equity, "SPYG", Resolution.Minute);
_macd = MACD(_spyg.Symbol, 12, 26, 9, MovingAverageType.Simple, Resolution.Daily);
_xivPrice = Identity(_xiv.Symbol, Resolution.Daily);
AddData<VIXQuandl>(_dataVIX, Resolution.Daily);
AddData<Quandl>(_dataVXV, Resolution.Daily);
AddData<VFQuandl>(_dataV1, Resolution.Daily);
AddData<VFQuandl>(_dataV2, Resolution.Daily);
Schedule.On(DateRules.EveryDay(_xiv.Symbol), TimeRules.AfterMarketOpen(_xiv.Symbol, 1), Rebalance);
Schedule.On(DateRules.EveryDay(_xiv.Symbol), TimeRules.AfterMarketOpen(_xiv.Symbol, 34), RebalanceXIV);
}
private bool _xivDay;
private decimal _vix = -1;
private void Rebalance()
{
var last_vix = _lastVIX.Price;
var last_vx1 = _lastV1.Price;
var last_vx2 = _lastV2.Price;
var last_vxv = _lastVXV.Price;
if (last_vix == 0 || last_vx1 == 0 || last_vx2 == 0 || last_vxv == 0)
{
Debug("Zero Quandl price");
return;
}
if (_xivPrice.Current == 0)
{
Debug("Zero XIV price");
return;
}
// Calculating the gap between spot vix and the first month vix future
var last_ratio_v_v1 = last_vix / last_vx1;
// Calculating the contango ratio of the front and second month VIX Futures
var last_ratio_v1_v2 = last_vx1 / last_vx2;
// Blending the previous two ratios together using a weighted average
var ratio_weight = 0.7m;
var last_ratio = (ratio_weight * last_ratio_v_v1) + ((1 - ratio_weight) * last_ratio_v1_v2) - 1;
var vix_vxv_ratio = last_vix / last_vxv;
// Retrieve SPY MACD data
var macd = _macd - _macd.Signal;
// Calculate how much vix moved the previous day
decimal vix_ratio;
if (_vix > 0)
vix_ratio = last_vix / _vix - 1;
else
vix_ratio = 0;
_vix = last_vix;
while (!_xivPriceHistory.IsReady)
_xivPriceHistory.Add(_xivPrice.Current);
_xivPriceHistory.Add(_xivPrice.Current);
var xiv_ratio = _xivPriceHistory[0] / _xivPriceHistory[1] - 1;
// Setting thresholds
var threshold_vix_too_low = 10.76m; // 0
var threshold_xiv = -0.049m; // 1
var threshold_vxv_xiv = 0.87m; // 2
var threshold_uvxy = 0.049m; // 3
var threshold_macd = -0.55m; // 3
var threshold_vxv_uvxy = 1.3m; // 4
var threshold_vix_high = 19.9m; // 5
var threshold_vc_low = -0.148m; // 6
var threshold_vc_high = 0.046m; // 8
var threshold_vc_high_2 = -0.06m; // 8
var threshold_xiv_ratio = -0.053m; // 10
var threshold_uvxy_ratio = 0.08m; // 11
Security target_sid = null;
int thecase = -1;
if (last_vix < threshold_vix_too_low) // if VIX is too low, invest in UVXY witht he hope of a spike
{
thecase = 0;
target_sid = _vxx;
}
else if (last_ratio < threshold_xiv) // if contango is high, invest in XIV to gain from decay
{
thecase = 1;
target_sid = _xiv;
}
else if (vix_vxv_ratio < threshold_vxv_xiv) // if short term vol is low compared to mid term, invest in XIV to gain from decay
{
thecase = 2;
target_sid = _xiv;
}
else if (last_ratio > threshold_uvxy && macd > threshold_macd) // if backwardation is high, invest in UVXY to gain from decay
{
thecase = 3;
target_sid = _vxx;
}
else if (vix_vxv_ratio > threshold_vxv_uvxy) // if short term vol is high compared to mid term, invest in UVXY to gain from growth
{
thecase = 4;
target_sid = _vxx;
}
else if (last_vix > threshold_vix_high) // if VIX is too high, invest in XIV expecting that VIX will drop
{
thecase = 5;
target_sid = _xiv;
}
else if (vix_ratio < threshold_vc_low) // Vix down sharply, invest in XIV expecting that futures curve gets pulled down
{
thecase = 6;
target_sid = _xiv;
}
else if (vix_ratio > threshold_vc_high) // Vix up sharply, invest in UVXY expecting that futures curve gets pulled up
{
thecase = 7;
target_sid = _vxx;
}
else if (vix_ratio > threshold_vc_high_2) //have to think
{
thecase = 8;
target_sid = _xiv;
}
else
{
thecase = 9;
target_sid = _vxx;
}
if (target_sid == _xiv && xiv_ratio < threshold_xiv_ratio)
{
// indicators say XIV but it just dropped overnight, so got for TQQQ
thecase = 10;
target_sid = _tqqq;
}
else if (target_sid == _vxx && xiv_ratio > threshold_uvxy_ratio)
{
// indicators say VXX but it just dropped overnight, so got for TQQQ
thecase = 11;
target_sid = _tqqq;
}
if (target_sid != null && !target_sid.HoldStock)
{
if (target_sid == _vxx || target_sid == _tqqq)
{
FixedSetHoldings(_tqqq.Symbol, target_sid == _tqqq ? 1 : 0);
FixedSetHoldings(_vxx.Symbol, target_sid == _vxx ? 1 : 0);
FixedSetHoldings(_xiv.Symbol, 0);
}
else if (target_sid == _xiv)
_xivDay = true;
}
}
private void RebalanceXIV()
{
if (!_xivDay)
return;
_xivDay = false;
FixedSetHoldings(_tqqq.Symbol, 0);
FixedSetHoldings(_vxx.Symbol, 0);
FixedSetHoldings(_xiv.Symbol, 1);
}
private void GetDataIfPossible<V>(DataDictionary<V> dict, Symbol key, ref V dest)
{
V safeToOverwrite;
if (dict.TryGetValue(key, out safeToOverwrite))
dest = safeToOverwrite;
}
public override void OnData(Slice data)
{
// gets all Quandl data from our 'Slice' object
var quandls = data.Get<Quandl>();
if (quandls.Count > 0)
{
GetDataIfPossible(quandls, _dataVIX, ref _lastVIX);
GetDataIfPossible(quandls, _dataVXV, ref _lastVXV);
GetDataIfPossible(quandls, _dataV1, ref _lastV1);
GetDataIfPossible(quandls, _dataV2, ref _lastV2);
}
}
}
}