Overall Statistics
Total Trades
1021
Average Win
0.78%
Average Loss
-0.24%
Compounding Annual Return
0.080%
Drawdown
32.000%
Expectancy
0.031
Net Profit
0.349%
Sharpe Ratio
0.064
Probabilistic Sharpe Ratio
0.985%
Loss Rate
76%
Win Rate
24%
Profit-Loss Ratio
3.31
Alpha
-0.017
Beta
0.213
Annual Standard Deviation
0.116
Annual Variance
0.014
Information Ratio
-0.595
Tracking Error
0.181
Treynor Ratio
0.035
Total Fees
$1095.18
Estimated Strategy Capacity
$4100000000.00
Lowest Capacity Asset
SPY R735QTJ8XC9X
Portfolio Turnover
16.30%
using QuantConnect.Data.Market;
using QuantConnect.Indicators;

namespace KeltnerChannelUsingFramework
{
    public class HighLowBarIndicator : BarIndicator
    {
        public HighLowBarIndicator() : this(nameof(HighLowBarIndicator)) { }

        public HighLowBarIndicator(string name) : base(name)
        {
        }

        public decimal High { get; private set; }
        public decimal Low { get; private set; }

        public override bool IsReady => true;

        protected override decimal ComputeNextValue(IBaseDataBar input)
        {
            High = input.High;
            Low = input.Low;

            // just return mid-point
            return (High + Low) / 2;
        }
    }
}
using QLNet;
using QuantConnect;
using QuantConnect.Algorithm;
using QuantConnect.Algorithm.Framework.Execution;
using QuantConnect.Algorithm.Framework.Portfolio;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Orders;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace KeltnerChannelUsingFramework
{
    public class HighLowLimitOrderExecutionModel : ExecutionModel
    {
        private readonly PortfolioTargetCollection _targetsCollection = new PortfolioTargetCollection();
        private readonly Dictionary<Symbol, SymbolData> _symbolData = new Dictionary<Symbol, SymbolData>();

        public override void Execute(QCAlgorithm algorithm, IPortfolioTarget[] targets)
        {
            _targetsCollection.AddRange(targets);

            if (!_targetsCollection.IsEmpty)
            {
                foreach (var target in _targetsCollection.OrderByMarginImpact(algorithm))
                {
                    // calculate remaining quantity to be ordered
                    var unorderedQuantity = OrderSizing.GetUnorderedQuantity(algorithm, target);

                    if (!_symbolData.TryGetValue(target.Symbol, out var symbolData))
                    {
                        continue;
                    }

                    var price = unorderedQuantity > 0
                        ? symbolData.HighLowBarIndicator.High
                        : symbolData.HighLowBarIndicator.Low;

                    algorithm.LimitOrder(target.Symbol, unorderedQuantity, price);
                }

                _targetsCollection.ClearFulfilled(algorithm);
            }
        }

        /// <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)
        {
            foreach (var added in changes.AddedSecurities)
            {
                if (!_symbolData.ContainsKey(added.Symbol))
                {
                    _symbolData[added.Symbol] = new SymbolData(algorithm, added);
                }
            }

            foreach (var removed in changes.RemovedSecurities)
            {
                // clean up removed security data
                SymbolData data;
                if (_symbolData.TryGetValue(removed.Symbol, out data))
                {
                    if (IsSafeToRemove(algorithm, removed.Symbol))
                    {
                        _symbolData.Remove(removed.Symbol);
                        algorithm.SubscriptionManager.RemoveConsolidator(removed.Symbol, data.Consolidator);
                    }
                }
            }
        }

        /// <summary>
        /// Determines if it's safe to remove the associated symbol data
        /// </summary>
        protected virtual bool IsSafeToRemove(QCAlgorithm algorithm, Symbol symbol)
        {
            // confirm the security isn't currently a member of any universe
            return !algorithm.UniverseManager.Any(kvp => kvp.Value.ContainsMember(symbol));
        }
    }
}
using QuantConnect.Algorithm.Framework.Alphas;
using QuantConnect.Algorithm;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Data;
using QuantConnect.Indicators;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using QuantConnect;

namespace KeltnerChannelUsingFramework
{
    public class KelthnerChannelFreeBarAlphaModel : AlphaModel
    {
        Dictionary<Symbol, KeltnerChannels> _channels = new Dictionary<Symbol, KeltnerChannels>();
        TimeSpan _insightPeriod;

        public KelthnerChannelFreeBarAlphaModel(TimeSpan? insightPeriod = null)
        {
            _insightPeriod = insightPeriod ?? TimeSpan.FromDays(3);
        }

        public override void OnSecuritiesChanged(QCAlgorithm algorithm, SecurityChanges changes)
        {
            foreach (var security in changes.AddedSecurities)
            {
                var symbol = security.Symbol;

                _channels.Add(symbol, algorithm.KCH(symbol, 20, 2.25m, MovingAverageType.Exponential, algorithm.UniverseSettings.Resolution));
            }
        }

        public override IEnumerable<Insight> Update(QCAlgorithm algorithm, Slice data)
        {
            var insights = new List<Insight>();
            foreach (var (symbol, channel) in _channels)
            {
                if (!data.Bars.ContainsKey(symbol))
                {
                    continue;
                }

                if (!channel.IsReady)
                {
                    continue;
                }

                var bar = data.Bars[symbol];
                var isLong = algorithm.Portfolio[symbol].Quantity >= 0;
                var isShort = algorithm.Portfolio[symbol].Quantity <= 0;
                if (bar.Low > channel.UpperBand.Current && isLong)
                {
                    // sell
                    insights.Add(Insight.Price(symbol, _insightPeriod, InsightDirection.Down));
                }

                if (bar.High < channel.LowerBand.Current && isShort)
                {
                    // buy
                    insights.Add(Insight.Price(symbol, _insightPeriod, InsightDirection.Up));
                }
            }

            return insights;
        }
    }
}
using KeltnerChannelUsingFramework;
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.Data.UniverseSelection;
using QuantConnect.Indicators;
using System;
using System.Collections.Generic;

namespace QuantConnect.Algorithm.CSharp
{
    public class KeltnerChannelUsingFramework : QCAlgorithm
    {
        public override void Initialize()
        {
            SetStartDate(2019, 1, 1); // Set Start Date
            SetEndDate(2023, 5, 1); // Set Start Date
            //SetEndDate(2021, 3, 31);
            SetCash(100000); // Set Strategy Cash

            UniverseSettings.Resolution = Resolution.Hour;
            UniverseSettings.DataNormalizationMode = DataNormalizationMode.Raw;
            UniverseSettings.ExtendedMarketHours = false;

            AddUniverseSelection(new ManualUniverseSelectionModel(QuantConnect.Symbol.Create("SPY", SecurityType.Equity, Market.USA)));
            AddRiskManagement(new MaximumDrawdownPercentPerSecurity());
            SetExecution(new HighLowLimitOrderExecutionModel());
            SetAlpha(new KelthnerChannelFreeBarAlphaModel());
            SetPortfolioConstruction(new EqualWeightingPortfolioConstructionModel(Resolution.Hour, PortfolioBias.LongShort));

            SetBrokerageModel(Brokerages.BrokerageName.InteractiveBrokersBrokerage, AccountType.Margin);
            SetBenchmark("SPY");

            SetWarmUp(90);
        }

        /// OnData event is the primary entry point for your algorithm. Each new data point will be pumped in here.
        /// Slice object keyed by symbol containing the stock data
        public override void OnData(Slice data)
        {

        }
    }
}
using QuantConnect.Algorithm;
using QuantConnect.Data;
using QuantConnect.Data.Consolidators;
using QuantConnect.Indicators;
using QuantConnect.Securities;

namespace KeltnerChannelUsingFramework
{
    /// <summary>
    /// Symbol data for this Execution Model
    /// </summary>
    class SymbolData
    {
        /// <summary>
        /// Security
        /// </summary>
        public Security Security { get; }

        /// <summary>
        /// HighLow indicator
        /// </summary>
        public HighLowBarIndicator HighLowBarIndicator { get; set; }

        /// <summary>
        /// Data Consolidator
        /// </summary>
        public IDataConsolidator Consolidator { get; }

        /// <summary>
        /// Initialize a new instance of <see cref="SymbolData"/>
        /// </summary>
        public SymbolData(QCAlgorithm algorithm, Security security)
        {
            Security = security;
            Consolidator = algorithm.ResolveConsolidator(security.Symbol, security.Resolution);

            var indicator = new HighLowBarIndicator();
            HighLowBarIndicator = indicator;

            algorithm.RegisterIndicator(security.Symbol, indicator, Consolidator);

        }
    }
}