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