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