Overall Statistics |
Total Orders 9626 Average Win 0.11% Average Loss -0.02% Compounding Annual Return 15.248% Drawdown 2.300% Expectancy 0.204 Start Equity 10000000 End Equity 11523294.80 Net Profit 15.233% Sharpe Ratio 2.237 Sortino Ratio 5.388 Probabilistic Sharpe Ratio 97.193% Loss Rate 83% Win Rate 17% Profit-Loss Ratio 6.14 Alpha 0.097 Beta -0.039 Annual Standard Deviation 0.042 Annual Variance 0.002 Information Ratio 0.133 Tracking Error 0.121 Treynor Ratio -2.412 Total Fees $693808.04 Estimated Strategy Capacity $1800000.00 Lowest Capacity Asset EEFT R735QTJ8XC9X Portfolio Turnover 108.49% |
#region imports using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Globalization; using System.Drawing; using QuantConnect; using QuantConnect.Algorithm.Framework; using QuantConnect.Algorithm.Framework.Selection; using QuantConnect.Algorithm.Framework.Alphas; using QuantConnect.Algorithm.Framework.Portfolio; using QuantConnect.Algorithm.Framework.Execution; using QuantConnect.Algorithm.Framework.Risk; using QuantConnect.Algorithm.Selection; using QuantConnect.Parameters; using QuantConnect.Benchmarks; using QuantConnect.Brokerages; using QuantConnect.Util; using QuantConnect.Interfaces; using QuantConnect.Algorithm; using QuantConnect.Indicators; using QuantConnect.Data; using QuantConnect.Data.Consolidators; using QuantConnect.Data.Custom; using QuantConnect.DataSource; using QuantConnect.Data.Fundamental; using QuantConnect.Data.Market; using QuantConnect.Data.UniverseSelection; using QuantConnect.Notifications; using QuantConnect.Orders; using QuantConnect.Orders.Fees; using QuantConnect.Orders.Fills; using QuantConnect.Orders.Slippage; using QuantConnect.Scheduling; using QuantConnect.Securities; using QuantConnect.Securities.Equity; using QuantConnect.Securities.Future; using QuantConnect.Securities.Option; using QuantConnect.Securities.Forex; using QuantConnect.Securities.Crypto; using QuantConnect.Securities.Interfaces; using QuantConnect.Storage; using QCAlgorithmFramework = QuantConnect.Algorithm.QCAlgorithm; using QCAlgorithmFrameworkBridge = QuantConnect.Algorithm.QCAlgorithm; #endregion namespace QuantConnect.Algorithm.CSharp { public class OpeningRangeBreakoutUniverseAlgorithm : QCAlgorithm { public int MaxPositions = 20; private Universe _universe; [Parameter("universeSize")] private int _universeSize = 1000; [Parameter("atrThreshold")] private decimal _atrThreshold = 0.5m; private int _indicatorPeriod = 14; // days [Parameter("openingRangeMinutes")] private int _openingRangeMinutes = 5; private int _leverage = 4; private Dictionary<Symbol, SymbolData> _symbolDataBySymbol = new(); public override void Initialize() { SetStartDate(2016, 1, 1); SetEndDate(2017, 1, 1); SetCash(10_000_000); Settings.AutomaticIndicatorWarmUp = true; // Add SPY so there is at least 1 asset at minute resolution to step the algorithm along. var spy = AddEquity("SPY").Symbol; // Add a universe of the most liquid US Equities. UniverseSettings.Leverage = _leverage; UniverseSettings.Asynchronous = true; UniverseSettings.Schedule.On(DateRules.MonthStart(spy)); _universe = AddUniverse(fundamentals => fundamentals .Where(f => f.Price > 5 && f.Symbol != spy) .OrderByDescending(f => f.DollarVolume) .Take(_universeSize) .Select(f => f.Symbol) .ToList() ); Schedule.On(DateRules.EveryDay(spy), TimeRules.BeforeMarketClose(spy, 1), () => Liquidate()); // Close all the open positions and cancel standing orders. SetWarmUp(TimeSpan.FromDays(2 * _indicatorPeriod)); } public override void OnSecuritiesChanged(SecurityChanges changes) { // Add indicators for each asset that enters the universe. foreach (var security in changes.AddedSecurities) { _symbolDataBySymbol[security.Symbol] = new SymbolData(this, security, _openingRangeMinutes, _indicatorPeriod); } } public override void OnData(Slice slice) { if (IsWarmingUp || !(Time.Hour == 9 && Time.Minute == 30 + _openingRangeMinutes)) return; // Select the stocks in play. var filtered = ActiveSecurities.Values // Filter 1: Select assets in the unvierse that have a relative volume greater than 100%. .Where(s => s.Price != 0 && _universe.Selected.Contains(s.Symbol)).Select(s => _symbolDataBySymbol[s.Symbol]).Where(s => s.RelativeVolume > 1 && s.ATR > s.Security.Price*0.01m) // Filter 2: Select the top 20 assets with the greatest relative volume. .OrderByDescending(s => s.RelativeVolume).Take(MaxPositions); // Look for trade entries. foreach (var symbolData in filtered) { symbolData.Scan(); } } public override void OnOrderEvent(OrderEvent orderEvent) { if (orderEvent.Status != OrderStatus.Filled) return; _symbolDataBySymbol[orderEvent.Symbol].OnOrderEvent(orderEvent.Ticket); } } class SymbolData { public decimal? RelativeVolume; public TradeBar OpeningBar = new(); public Security Security; private OpeningRangeBreakoutUniverseAlgorithm _algorithm; private decimal _stopLossAtrDistance = 0.1m; // 0.1 => 10% of ATR private decimal _stopLossRiskSize = 0.01m; // 0.01 => Lose 1% of the portfolio if stop loss is hit private IDataConsolidator Consolidator; public AverageTrueRange ATR; private SimpleMovingAverage VolumeSMA; private decimal StopLossPrice; private OrderTicket EntryTicket, StopLossTicket; public SymbolData(OpeningRangeBreakoutUniverseAlgorithm algorithm, Security security, int openingRangeMinutes, int indicatorPeriod) { _algorithm = algorithm; Security = security; Consolidator = algorithm.Consolidate(security.Symbol, TimeSpan.FromMinutes(openingRangeMinutes), ConsolidationHandler); ATR = algorithm.ATR(security.Symbol, indicatorPeriod, resolution: Resolution.Daily); VolumeSMA = new SimpleMovingAverage(indicatorPeriod); } void ConsolidationHandler(TradeBar bar) { if (OpeningBar.Time.Date == bar.Time.Date) return; // Update the asset's indicators and save the day's opening bar. RelativeVolume = VolumeSMA.IsReady && VolumeSMA > 0 ? bar.Volume / VolumeSMA : null; VolumeSMA.Update(bar.EndTime, bar.Volume); OpeningBar = bar; } public void Scan() { // Calculate position sizes so that if you fill an order at the high (low) of the first 5-minute bar // and hit a stop loss based on 10% of the ATR, you only lose x% of portfolio value. if (OpeningBar.Close > OpeningBar.Open) { PlaceTrade(OpeningBar.High, OpeningBar.High - _stopLossAtrDistance * ATR); } else if (OpeningBar.Close < OpeningBar.Open) { PlaceTrade(OpeningBar.Low, OpeningBar.Low + _stopLossAtrDistance * ATR); } } public void PlaceTrade(decimal entryPrice, decimal stopPrice) { var quantity = (int)((_stopLossRiskSize * _algorithm.Portfolio.TotalPortfolioValue / _algorithm.MaxPositions) / (entryPrice - stopPrice)); var quantityLimit = _algorithm.CalculateOrderQuantity(Security.Symbol, 1m/_algorithm.MaxPositions); quantity = (int)(Math.Min(Math.Abs(quantity), quantityLimit) * Math.Sign(quantity)); if (quantity != 0) { StopLossPrice = stopPrice; EntryTicket = _algorithm.StopMarketOrder(Security.Symbol, quantity, entryPrice, $"Entry"); } } public void OnOrderEvent(OrderTicket orderTicket) { // When the entry order is hit, place the exit order: Stop loss based on ATR. if (orderTicket == EntryTicket) { StopLossTicket = _algorithm.StopMarketOrder(Security.Symbol, -EntryTicket.Quantity, StopLossPrice, tag: "ATR Stop"); } } } }