From 89042c2e5b54408b392b766f3cb68f78230e7c26 Mon Sep 17 00:00:00 2001 From: moshferatu Date: Thu, 9 Mar 2023 13:00:02 -0800 Subject: [PATCH] Add Opening Range Breakout strategy --- strategies/OpeningRangeBreakout.cs | 144 +++++++++++++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 strategies/OpeningRangeBreakout.cs diff --git a/strategies/OpeningRangeBreakout.cs b/strategies/OpeningRangeBreakout.cs new file mode 100644 index 0000000..3fd24be --- /dev/null +++ b/strategies/OpeningRangeBreakout.cs @@ -0,0 +1,144 @@ +#region Using declarations +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Input; +using System.Windows.Media; +using System.Xml.Serialization; +using NinjaTrader.Cbi; +using NinjaTrader.Gui; +using NinjaTrader.Gui.Chart; +using NinjaTrader.Gui.SuperDom; +using NinjaTrader.Gui.Tools; +using NinjaTrader.Data; +using NinjaTrader.NinjaScript; +using NinjaTrader.Core.FloatingPoint; +using NinjaTrader.NinjaScript.Indicators; +using NinjaTrader.NinjaScript.DrawingTools; +#endregion + +//This namespace holds Strategies in this folder and is required. Do not change it. +namespace NinjaTrader.NinjaScript.Strategies +{ + public class OpeningRangeBreakout : Strategy + { + private OpeningRange OR; + private PivotHighLow Pivots; + + private DateTime TradeDate = DateTime.MinValue; + + protected override void OnStateChange() + { + if (State == State.SetDefaults) + { + Description = @"Opening range breakout strategy"; + Name = "OpeningRangeBreakout"; + Calculate = Calculate.OnPriceChange; + EntriesPerDirection = 2; + EntryHandling = EntryHandling.AllEntries; + IsExitOnSessionCloseStrategy = true; + ExitOnSessionCloseSeconds = 30; + IsFillLimitOnTouch = false; + MaximumBarsLookBack = MaximumBarsLookBack.TwoHundredFiftySix; + OrderFillResolution = OrderFillResolution.Standard; + Slippage = 0; + StartBehavior = StartBehavior.WaitUntilFlat; + TimeInForce = TimeInForce.Gtc; + TraceOrders = false; + RealtimeErrorHandling = RealtimeErrorHandling.StopCancelClose; + StopTargetHandling = StopTargetHandling.PerEntryExecution; + BarsRequiredToTrade = 20; + // Disable this property for performance gains in Strategy Analyzer optimizations + // See the Help Guide for additional information + IsInstantiatedOnEachOptimizationIteration = true; + } + else if (State == State.Configure) + { + AddDataSeries(BarsPeriodType.Tick, 1); + AddDataSeries(Instrument.FullName, new BarsPeriod + { + BarsPeriodType = BarsPeriodType.Minute, + Value = 30 + }, "US Equities RTH"); + } + else if (State == State.DataLoaded) + { + OR = OpeningRange( + 30, + OpeningRangeBarType.Minutes, + new Stroke(Brushes.Yellow, DashStyleHelper.Solid, 3), + new Stroke(Brushes.Yellow, DashStyleHelper.Solid, 3), + new Stroke(Brushes.Gray, DashStyleHelper.Dot, 1) + ); + Pivots = PivotHighLow(10, 10, 3, + new Stroke(Brushes.LimeGreen, DashStyleHelper.Solid, 3), + new Stroke(Brushes.Red, DashStyleHelper.Solid, 3)); + } + } + + protected override void OnBarUpdate() + { + if (CurrentBar <= BarsRequiredToTrade) + return; + + // Ignore tick bars. They are used only for entering trades. + // Also ignore data series used for opening range calculation. + if (BarsInProgress == 1 || BarsInProgress == 2) + return; + + if (Position.MarketPosition != MarketPosition.Flat) + { + if (Position.MarketPosition == MarketPosition.Long) + { + SetStopLoss("Long", CalculationMode.Price, Math.Max(OR.ORM[0], Pivots.PivotLow[0]), false); + SetStopLoss("LongRunner", CalculationMode.Price, Math.Max(OR.ORM[0], Pivots.PivotLow[0]), false); + } + else + { + SetStopLoss("Short", CalculationMode.Price, Math.Min(OR.ORM[0], Pivots.PivotHigh[0]), false); + SetStopLoss("ShortRunner", CalculationMode.Price, Math.Min(OR.ORM[0], Pivots.PivotHigh[0]), false); + } + return; + } + + if (TradeDate == Time[0].Date) + return; + + if (!IsFirstTickOfBar) + return; + + // TODO: Change opening range end time to be timezone-agnostic. + if (ToTime(Time[0]) <= 70000) + return; + + if (Close[1] > OR.ORH[0]) + { + SetStopLoss("Long", CalculationMode.Price, Math.Max(OR.ORM[0], Pivots.PivotLow[0]), false); + SetProfitTarget("Long", CalculationMode.Ticks, (OR.ORH[0] - OR.ORM[0]) / TickSize * 1.5); + EnterLong(1, DefaultQuantity, "Long"); + + SetStopLoss("LongRunner", CalculationMode.Price, OR.ORM[0], false); + EnterLong(1, DefaultQuantity, "LongRunner"); + + TradeDate = Time[0].Date; + } + + if (Close[1] < OR.ORL[0]) + { + SetStopLoss("Short", CalculationMode.Price, Math.Min(OR.ORM[0], Pivots.PivotHigh[0]), false); + SetProfitTarget("Short", CalculationMode.Ticks, (OR.ORM[0] - OR.ORL[0]) / TickSize * 1.5); + EnterShort(1, DefaultQuantity, "Short"); + + SetStopLoss("ShortRunner", CalculationMode.Price, OR.ORM[0], false); + EnterShort(1, DefaultQuantity, "ShortRunner"); + + TradeDate = Time[0].Date; + } + } + } +}