Overall Statistics |
Total Trades 46 Average Win 1.70% Average Loss -1.86% Compounding Annual Return 16.521% Drawdown 1.100% Expectancy 0.076 Net Profit 1.383% Sharpe Ratio 3.607 Probabilistic Sharpe Ratio 82.266% Loss Rate 44% Win Rate 56% Profit-Loss Ratio 0.91 Alpha 0.141 Beta -0.03 Annual Standard Deviation 0.032 Annual Variance 0.001 Information Ratio -4.756 Tracking Error 0.157 Treynor Ratio -3.804 Total Fees $0.00 Estimated Strategy Capacity $80000.00 Lowest Capacity Asset ETHEUR XJ |
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 { /// <summary> /// Given two cryptos highly correlated and traded in higher volume - using theoretically, any deviation of this portfolio from /// its mean-value should be corrected. 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. /// </summary> public class ShareClassMeanReversionAlpha : QCAlgorithm { public override void Initialize() { SetStartDate(2019, 1, 1); SetEndDate(2019, 2, 1); SetCash(1000000); SetWarmUp(20); // Setup Universe settings and tickers to be used var symbols = new[] { "ETHUSD", "ETHEUR" } .Select(x => QuantConnect.Symbol.Create(x, SecurityType.Crypto, Market.GDAX)); // Set requested data resolution UniverseSettings.Resolution = Resolution.Daily; 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()); SetRiskManagement(new MaximumDrawdownPercentPerSecurity(0.25m)); //Lean uses a constant fee rate of 0.25% (Taker) and 0.15% (Maker) that Coinbase Pro applies for a 30-day volume of $50k - $100k // where feePercentage is either 0.0025 or 0.0015 //var fee = unitPrice * order.AbsoluteQuantity * feePercentage; // Schedule.On(DateRules.Every(DayOfWeek.Monday, DayOfWeek.Sunday), TimeRules.At(17, 55), () => // { // Log("Mon/Fri at 17:55pm: Fired at: " + Time); // }); } 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<decimal> _positionWindow; private decimal _alpha; private decimal _beta; private bool _invested; public ShareClassMeanReversionAlphaModel( IEnumerable<Symbol> symbols, Resolution resolution = Resolution.Daily) { 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<decimal>(2); } public override IEnumerable<Insight> 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<Insight>(); } // 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<Insight>(); } // 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<Insight>(); } /// <summary> /// Calculate Alpha and Beta, the initial number of shares for each security needed to achieve a 50/50 weighting /// </summary> /// <param name="algorithm"></param> 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}"); } /// <summary> /// Calculate position value and update the SMA indicator and Rolling Window /// </summary> 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; } /// <summary> /// Check to see if the position value has crossed the SMA and then return a boolean value /// </summary> /// <returns></returns> private bool CrossedMean() { return (_positionWindow[0] >= _sma && _positionWindow[1] < _sma) || (_positionWindow[1] >= _sma && _positionWindow[0] < _sma); } } } }