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);
            }
            
        }
    }
}