Overall Statistics
Total Trades
45
Average Win
28.88%
Average Loss
-2.74%
Compounding Annual Return
386651.944%
Drawdown
26.300%
Expectancy
3.949
Net Profit
506.179%
Sharpe Ratio
5.384
Probabilistic Sharpe Ratio
99.984%
Loss Rate
57%
Win Rate
43%
Profit-Loss Ratio
10.55
Alpha
6.998
Beta
-1.916
Annual Standard Deviation
1.803
Annual Variance
3.25
Information Ratio
5.166
Tracking Error
2.152
Treynor Ratio
-5.064
Total Fees
$0.00
/*
 * 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 System.Collections.Generic;
using System.Linq;
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.Brokerages;
using QuantConnect.Data;
using QuantConnect.Data.Consolidators;
using QuantConnect.Data.Market;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Indicators;
using QuantConnect.Orders.Fees;

namespace QuantConnect.Algorithm.CSharp.Alphas
{
    /// <summary>
    /// This is a demonstration algorithm. It trades UVXY.
    /// Dual Thrust alpha model is used to produce insights.
    /// Those input parameters have been chosen that gave acceptable results on a series
    /// of random backtests run for the period from Oct, 2016 till Feb, 2019.
    /// </summary>
    class VIXDualThrustAlpha : QCAlgorithm
    {
        // -- STRATEGY INPUT PARAMETERS --
        private decimal _k1 = 0.63m;
        private decimal _k2 = 0.63m;
        private int _rangePeriod = 20;
        private int _consolidatorBars = 30;

        // -- INITIALIZE --
        public override void Initialize()
        {
            // Settings
            SetStartDate(2020, 1, 1);
            //SetEndDate(2020, 1, 1);
            SetSecurityInitializer(s => s.SetFeeModel(new ConstantFeeModel(0m)));
            SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Margin);

            // Universe Selection
            UniverseSettings.Resolution = Resolution.Minute;   // it's minute by default, but lets leave this param here
            var symbols = new[] { QuantConnect.Symbol.Create("UVXY", SecurityType.Equity, Market.USA) };
            SetUniverseSelection(new ManualUniverseSelectionModel(symbols));

            // Warming up
            var resolutionInTimeSpan = UniverseSettings.Resolution.ToTimeSpan();
            var warmUpTimeSpan = resolutionInTimeSpan.Multiply(_consolidatorBars).Multiply(_rangePeriod);
            SetWarmUp(warmUpTimeSpan);

            // Alpha Model
            SetAlpha(new DualThrustAlphaModel(_k1, _k2, _rangePeriod, UniverseSettings.Resolution, _consolidatorBars));

            // Portfolio Construction
            SetPortfolioConstruction(new EqualWeightingPortfolioConstructionModel());

            // Execution
            SetExecution(new ImmediateExecutionModel());

            // Risk Management
            SetRiskManagement(new MaximumDrawdownPercentPerSecurity(0.03m));
        }
    }

    /// <summary>
    /// Alpha model that uses dual-thrust strategy to create insights
    /// https://medium.com/@FMZ_Quant/dual-thrust-trading-strategy-2cc74101a626
    /// or here:
    /// https://www.quantconnect.com/tutorials/strategy-library/dual-thrust-trading-algorithm
    /// </summary>
    public class DualThrustAlphaModel : AlphaModel
    {
        private readonly decimal _k1;
        private readonly decimal _k2;
        private readonly TimeSpan _consolidatorTimeSpan;
        private readonly int _rangePeriod;
        private readonly Dictionary<Symbol, SymbolData> _symbolDataBySymbol;

        /// <summary>
        /// Initializes a new instance of the  class
        /// </summary>
        /// <param name="k1">Coefficient for upper band</param>
        /// <param name="k2">Coefficient for lower band</param>
        /// <param name="rangePeriod">Amount of last bars to calculate the range</param>
        /// <param name="resolution">The resolution of data sent into the EMA indicators</param>
        /// <param name="barsToConsolidate">If we want alpha o work on trade bars whose length is
        /// different from the standard resolution - 1m 1h etc. - we need to pass this parameters along
        /// with proper data resolution</param>
        public DualThrustAlphaModel(
            decimal k1,
            decimal k2,
            int rangePeriod,
            Resolution resolution = Resolution.Daily,
            int barsToConsolidate = 1
            )
        {
            // coefficient that used to determinte upper and lower borders of a breakout channel
            _k1 = k1;
            _k2 = k2;

            // period the range is calculated over
            _rangePeriod = rangePeriod;

            // initialize with empty dict.
            _symbolDataBySymbol = new Dictionary<Symbol, SymbolData>();

            // time for bars we make the calculations on
            _consolidatorTimeSpan = resolution.ToTimeSpan().Multiply(barsToConsolidate);
        }

        /// <summary>
        /// Updates this alpha model with the latest data from the algorithm.
        /// This is called each time the algorithm receives data for subscribed securities
        /// </summary>
        /// <param name="algorithm">The algorithm instance</param>
        /// <param name="data">The new data available</param>
        /// <returns>The new insights generated</returns>
        public override IEnumerable<Insight> Update(QCAlgorithm algorithm, Slice data)
        {
            var insights = new List<Insight>();

            // in 5 days after emission an insight is to be considered expired
            int insightCloseAddDays = 5;

            foreach (var symbolData in _symbolDataBySymbol.Values)
            {
                var range = symbolData.Range;
                var symbol = symbolData.Symbol;
                var security = algorithm.Securities[symbol];

                if (symbolData.IsReady)
                {
                    // buying condition
                    // - (1) price is above upper line
                    // - (2) and we are not long. this is a first time we crossed the line lately
                    if (security.Price > symbolData.UpperLine && !algorithm.Portfolio[symbol].IsLong)
                    {
                        DateTime insightCloseTimeUtc = algorithm.UtcTime.AddDays(insightCloseAddDays);
                        insights.Add(Insight.Price(symbolData.Symbol, insightCloseTimeUtc, InsightDirection.Up));
                    }

                    // selling condition
                    // - (1) price is lower that lower line
                    // - (2) and we are not short. this is a first time we crossed the line lately
                    if (security.Price < symbolData.LowerLine && !algorithm.Portfolio[symbol].IsShort)
                    {
                        DateTime insightCloseTimeUtc = algorithm.UtcTime.AddDays(insightCloseAddDays);
                        insights.Add(Insight.Price(symbolData.Symbol, insightCloseTimeUtc, InsightDirection.Down));
                    }
                }
            }

            return insights;
        }

        /// <summary>
        /// Event fired each time the we add/remove securities from the data feed
        /// </summary>
        /// <param name="algorithm">The algorithm instance that experienced the change in securities</param>
        /// <param name="changes">The security additions and removals from the algorithm</param>
        public override void OnSecuritiesChanged(QCAlgorithm algorithm, SecurityChanges changes)
        {
            // added
            foreach (var added in changes.AddedSecurities)
            {
                SymbolData symbolData;
                if (!_symbolDataBySymbol.TryGetValue(added.Symbol, out symbolData))
                {
                    // add symbol/symbolData pair to collection
                    symbolData = new SymbolData(_rangePeriod, _consolidatorTimeSpan)
                    {
                        Symbol = added.Symbol,
                        K1 = _k1,
                        K2 = _k2
                    };

                    _symbolDataBySymbol[added.Symbol] = symbolData;

                    //register consolidator
                    algorithm.SubscriptionManager.AddConsolidator(added.Symbol, symbolData.GetConsolidator());
                }
            }

            // removed
            foreach (var removed in changes.RemovedSecurities)
            {
                SymbolData symbolData;
                if (_symbolDataBySymbol.TryGetValue(removed.Symbol, out symbolData))
                {
                    // unsibscribe consolidator from data updates
                    algorithm.SubscriptionManager.RemoveConsolidator(removed.Symbol, symbolData.GetConsolidator());

                    // remove item from dictionary collection
                    if (!_symbolDataBySymbol.Remove(removed.Symbol))
                    {
                        algorithm.Error("Unable to remove data from collection: DualThrustAlphaModel");
                    }
                }
            }
        }

        /// <summary>
        /// Contains data specific to a symbol required by this model
        /// </summary>
        private class SymbolData
        {
            // rolling to contain items over the looking back period
            private readonly RollingWindow<TradeBar> _rangeWindow;

            // we calculate our logic on bars
            private readonly TradeBarConsolidator _consolidator;

            // current range value
            public decimal Range { get; private set; }

            // upper Line
            public decimal UpperLine { get; private set; }

            // lower Line
            public decimal LowerLine { get; private set; }

            // symbol value
            public Symbol Symbol { get; set; }

            // k1
            public decimal K1 { private get; set; }

            // k2
            public decimal K2 { private get; set; }

            // data is ready when rolling window is ready
            public bool IsReady => _rangeWindow.IsReady;

            /// <summary>
            /// Main constructor for the class
            /// </summary>
            /// <param name="rangePeriod">Range period</param>
            /// <param name="consolidatorResolution">Time length of consolidator</param>
            public SymbolData(int rangePeriod, TimeSpan consolidatorResolution)
            {
                _rangeWindow = new RollingWindow<TradeBar>(rangePeriod);
                _consolidator = new TradeBarConsolidator(consolidatorResolution);

                // event fired at new consolidated trade bar
                _consolidator.DataConsolidated += (sender, consolidated) =>
                {
                    // add new tradebar to
                    _rangeWindow.Add(consolidated);

                    if (IsReady)
                    {
                        var hh = _rangeWindow.Select(x => x.High).Max();
                        var hc = _rangeWindow.Select(x => x.Close).Max();
                        var lc = _rangeWindow.Select(x => x.Close).Min();
                        var ll = _rangeWindow.Select(x => x.Low).Min();

                        Range = Math.Max(hh - lc, hc - ll);

                        UpperLine = consolidated.Close + K1 * Range;
                        LowerLine = consolidated.Close - K2 * Range;
                    }
                };
            }

            /// <summary>
            /// Returns the interior consolidator
            /// </summary>
            public TradeBarConsolidator GetConsolidator()
            {
                return _consolidator;
            }
        }
    }
}