/*
* 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 QuantConnect.Algorithm.Framework.Alphas;
using QuantConnect.Algorithm.Framework.Execution;
using QuantConnect.Algorithm.Framework.Portfolio;
using QuantConnect.Algorithm.Framework.Risk;
using QuantConnect.Algorithm.Framework.Selection;
using QuantConnect.Data;
using QuantConnect.Indicators;
using QuantConnect.Orders.Fees;
using System;
using System.Collections.Generic;
using System.Linq;
namespace QuantConnect.Algorithm.CSharp.Alphas
{
///
/// A number of companies publicly trade two different classes of shares
/// in US equity markets. If both assets trade with reasonable volume, then
/// the underlying driving forces of each should be similar or the same. Given
/// this, we can create a relatively dollar-neutral long/short portfolio using
/// the dual share classes. Theoretically, any deviation of this portfolio from
/// its mean-value should be corrected, and so the motivating idea is based on
/// mean-reversion. Using a Simple Moving Average indicator, we can
/// compare the value of this portfolio against its SMA and generate insights
/// to buy the under-valued symbol and sell the over-valued symbol.
///
/// This alpha is part of the Benchmark Alpha Series created by QuantConnect which are open sourced so the community and client funds can see an example of an alpha.
///
public class ShareClassMeanReversionAlpha : QCAlgorithm
{
public override void Initialize()
{
SetStartDate(2019, 1, 1);
SetCash(100000);
// Set zero transaction fees
SetSecurityInitializer(security => security.FeeModel = new ConstantFeeModel(0));
SetWarmUp(20);
// Setup Universe settings and tickers to be used
var symbols = new[] { "VIA", "VIAB" }
.Select(x => QuantConnect.Symbol.Create(x, SecurityType.Equity, Market.USA));
// Set requested data resolution
UniverseSettings.Resolution = Resolution.Minute;
SetUniverseSelection(new ManualUniverseSelectionModel(symbols));
// Use ShareClassMeanReversionAlphaModel to establish insights
SetAlpha(new ShareClassMeanReversionAlphaModel(symbols));
// Equally weigh securities in portfolio, based on insights
SetPortfolioConstruction(new EqualWeightingPortfolioConstructionModel());
// Set Immediate Execution Model
SetExecution(new ImmediateExecutionModel());
// Set Null Risk Management Model
SetRiskManagement(new NullRiskManagementModel());
}
private class ShareClassMeanReversionAlphaModel : AlphaModel
{
private const double _insightMagnitude = 0.001;
private readonly Symbol _longSymbol;
private readonly Symbol _shortSymbol;
private readonly TimeSpan _insightPeriod;
private readonly SimpleMovingAverage _sma;
private readonly RollingWindow _positionWindow;
private decimal _alpha;
private decimal _beta;
private bool _invested;
public ShareClassMeanReversionAlphaModel(
IEnumerable symbols,
Resolution resolution = Resolution.Minute)
{
if (symbols.Count() != 2)
{
throw new ArgumentException("ShareClassMeanReversionAlphaModel: symbols parameter must contain 2 elements");
}
_longSymbol = symbols.ToArray()[0];
_shortSymbol = symbols.ToArray()[1];
_insightPeriod = resolution.ToTimeSpan().Multiply(5);
_sma = new SimpleMovingAverage(2);
_positionWindow = new RollingWindow(2);
}
public override IEnumerable Update(QCAlgorithm algorithm, Slice data)
{
// Check to see if either ticker will return a NoneBar, and skip the data slice if so
if (data.Bars.Count < 2)
{
return Enumerable.Empty();
}
// If Alpha and Beta haven't been calculated yet, then do so
if (_alpha == 0 || _beta == 0)
{
CalculateAlphaBeta(algorithm);
}
// Update indicator and Rolling Window for each data slice passed into Update() method
if (!UpdateIndicators(data))
{
return Enumerable.Empty();
}
// Check to see if the portfolio is invested. If no, then perform value comparisons and emit insights accordingly
if (!_invested)
{
//Reset invested boolean
_invested = true;
if (_positionWindow[0] > _sma)
{
return Insight.Group(new[]
{
Insight.Price(_longSymbol, _insightPeriod, InsightDirection.Down, _insightMagnitude),
Insight.Price(_shortSymbol, _insightPeriod, InsightDirection.Up, _insightMagnitude),
});
}
else
{
return Insight.Group(new[]
{
Insight.Price(_longSymbol, _insightPeriod, InsightDirection.Up, _insightMagnitude),
Insight.Price(_shortSymbol, _insightPeriod, InsightDirection.Down, _insightMagnitude),
});
}
}
// If the portfolio is invested and crossed back over the SMA, then emit flat insights
else if (_invested && CrossedMean())
{
_invested = false;
}
return Enumerable.Empty();
}
///
/// Calculate Alpha and Beta, the initial number of shares for each security needed to achieve a 50/50 weighting
///
///
private void CalculateAlphaBeta(QCAlgorithm algorithm)
{
_alpha = algorithm.CalculateOrderQuantity(_longSymbol, 0.5);
_beta = algorithm.CalculateOrderQuantity(_shortSymbol, 0.5);
algorithm.Log($"{algorithm.Time} :: Alpha: {_alpha} Beta: {_beta}");
}
///
/// Calculate position value and update the SMA indicator and Rolling Window
///
private bool UpdateIndicators(Slice data)
{
var positionValue = (_alpha * data[_longSymbol].Close) - (_beta * data[_shortSymbol].Close);
_sma.Update(data[_longSymbol].EndTime, positionValue);
_positionWindow.Add(positionValue);
return _sma.IsReady && _positionWindow.IsReady;
}
///
/// Check to see if the position value has crossed the SMA and then return a boolean value
///
///
private bool CrossedMean()
{
return (_positionWindow[0] >= _sma && _positionWindow[1] < _sma)
|| (_positionWindow[1] >= _sma && _positionWindow[0] < _sma);
}
}
}
}