From c6738f315332e020fe003b7a3d54739ac1ea76bc Mon Sep 17 00:00:00 2001 From: moshferatu Date: Wed, 27 Sep 2023 09:00:40 -0700 Subject: [PATCH] Add Order Flow Oscillator indicator --- indicators/OrderFlowOscillator.cs | 208 ++++++++++++++++++++++++++++++ 1 file changed, 208 insertions(+) create mode 100644 indicators/OrderFlowOscillator.cs diff --git a/indicators/OrderFlowOscillator.cs b/indicators/OrderFlowOscillator.cs new file mode 100644 index 0000000..6a16878 --- /dev/null +++ b/indicators/OrderFlowOscillator.cs @@ -0,0 +1,208 @@ +#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.DrawingTools; +#endregion + +//This namespace holds Indicators in this folder and is required. Do not change it. +namespace NinjaTrader.NinjaScript.Indicators +{ + [CategoryOrder("Order Flow Oscillator", 1)] + [CategoryOrder("Plots", 2)] + public class OrderFlowOscillator : Indicator + { + private double TradesAtBid = 0; + private double TradesAtAsk = 0; + + private Series AverageGain; + private Series AverageLoss; + + protected override void OnStateChange() + { + if (State == State.SetDefaults) + { + Description = @"Credit to @jamestsliu for the idea, though the result is different."; + Name = "Order Flow Oscillator"; + Calculate = Calculate.OnEachTick; + IsOverlay = false; + DisplayInDataBox = true; + DrawOnPricePanel = true; + DrawHorizontalGridLines = true; + DrawVerticalGridLines = true; + PaintPriceMarkers = true; + ScaleJustification = ScaleJustification.Right; + IsSuspendedWhileInactive = false; + Period = 14; + UpperLevel = 70.0; + LowerLevel = 30.0; + UpperLevelStroke = new Stroke(Brushes.Red, 3); + LowerLevelStroke = new Stroke(Brushes.LimeGreen, 3); + AddPlot(new Stroke(Brushes.Yellow, 2), PlotStyle.Line, "Order Flow Oscillator"); + } + else if (State == State.Configure) + { + AddLine(UpperLevelStroke, UpperLevel, "Upper Level"); + AddLine(LowerLevelStroke, LowerLevel, "Lower Level"); + } + else if (State == State.DataLoaded) + { + AverageGain = new Series(this); + AverageLoss = new Series(this); + } + } + + protected override void OnBarUpdate() + { + if (CurrentBar < 2) + return; + + // Order Flow Imbalance + double OFI = TradesAtAsk - TradesAtBid; + + if (CurrentBar < Period) + { + AverageGain[0] = (AverageGain[1] * (CurrentBar - 1) + (OFI > 0 ? OFI : 0)) / CurrentBar; + AverageLoss[0] = (AverageLoss[1] * (CurrentBar - 1) + (OFI < 0 ? -OFI : 0)) / CurrentBar; + } + else + { + AverageGain[0] = ((AverageGain[1] * (Period - 1)) + (OFI > 0 ? OFI : 0)) / Period; + AverageLoss[0] = ((AverageLoss[1] * (Period - 1)) + (OFI < 0 ? -OFI : 0)) / Period; + } + + // Relative Strength + double RS = AverageLoss.Equals(0) ? 0 : AverageGain[0] / AverageLoss[0]; + double OFI_RSI = 100 - (100 / (1 + RS)); + + Oscillator[0] = OFI_RSI; + } + + protected override void OnMarketData(MarketDataEventArgs marketDataUpdate) + { + if (IsFirstTickOfBar) + { + TradesAtBid = 0; + TradesAtAsk = 0; + } + + if (marketDataUpdate.MarketDataType == MarketDataType.Last) + { + if (marketDataUpdate.Price <= marketDataUpdate.Bid) + TradesAtBid += 1; + else if (marketDataUpdate.Price >= marketDataUpdate.Ask) + TradesAtAsk += 1; + } + } + + public override string DisplayName + { + get { return Name; } + } + + #region Properties + [Browsable(false)] + [XmlIgnore] + public Series Oscillator + { + get { return Values[0]; } + } + + [NinjaScriptProperty] + [Range(1, int.MaxValue)] + [Display(Name = "Period", Order = 1, GroupName = "Order Flow Oscillator")] + public int Period { get; set; } + + [NinjaScriptProperty] + [Range(double.MinValue, double.MaxValue)] + [Display(Name = "Upper Level", Order = 2, GroupName = "Plots")] + public double UpperLevel { get; set; } + + [NinjaScriptProperty] + [Display(Name = "Upper Level Style", Order = 3, GroupName = "Plots")] + public Stroke UpperLevelStroke { get; set; } + + [NinjaScriptProperty] + [Range(double.MinValue, double.MaxValue)] + [Display(Name = "Lower Level", Order = 4, GroupName = "Plots")] + public double LowerLevel { get; set; } + + [NinjaScriptProperty] + [Display(Name = "Lower Level Style", Order = 5, GroupName = "Plots")] + public Stroke LowerLevelStroke { get; set; } + #endregion + } +} + +#region NinjaScript generated code. Neither change nor remove. + +namespace NinjaTrader.NinjaScript.Indicators +{ + public partial class Indicator : NinjaTrader.Gui.NinjaScript.IndicatorRenderBase + { + private OrderFlowOscillator[] cacheOrderFlowOscillator; + public OrderFlowOscillator OrderFlowOscillator(int period, double upperLevel, Stroke upperLevelStroke, double lowerLevel, Stroke lowerLevelStroke) + { + return OrderFlowOscillator(Input, period, upperLevel, upperLevelStroke, lowerLevel, lowerLevelStroke); + } + + public OrderFlowOscillator OrderFlowOscillator(ISeries input, int period, double upperLevel, Stroke upperLevelStroke, double lowerLevel, Stroke lowerLevelStroke) + { + if (cacheOrderFlowOscillator != null) + for (int idx = 0; idx < cacheOrderFlowOscillator.Length; idx++) + if (cacheOrderFlowOscillator[idx] != null && cacheOrderFlowOscillator[idx].Period == period && cacheOrderFlowOscillator[idx].UpperLevel == upperLevel && cacheOrderFlowOscillator[idx].UpperLevelStroke == upperLevelStroke && cacheOrderFlowOscillator[idx].LowerLevel == lowerLevel && cacheOrderFlowOscillator[idx].LowerLevelStroke == lowerLevelStroke && cacheOrderFlowOscillator[idx].EqualsInput(input)) + return cacheOrderFlowOscillator[idx]; + return CacheIndicator(new OrderFlowOscillator(){ Period = period, UpperLevel = upperLevel, UpperLevelStroke = upperLevelStroke, LowerLevel = lowerLevel, LowerLevelStroke = lowerLevelStroke }, input, ref cacheOrderFlowOscillator); + } + } +} + +namespace NinjaTrader.NinjaScript.MarketAnalyzerColumns +{ + public partial class MarketAnalyzerColumn : MarketAnalyzerColumnBase + { + public Indicators.OrderFlowOscillator OrderFlowOscillator(int period, double upperLevel, Stroke upperLevelStroke, double lowerLevel, Stroke lowerLevelStroke) + { + return indicator.OrderFlowOscillator(Input, period, upperLevel, upperLevelStroke, lowerLevel, lowerLevelStroke); + } + + public Indicators.OrderFlowOscillator OrderFlowOscillator(ISeries input , int period, double upperLevel, Stroke upperLevelStroke, double lowerLevel, Stroke lowerLevelStroke) + { + return indicator.OrderFlowOscillator(input, period, upperLevel, upperLevelStroke, lowerLevel, lowerLevelStroke); + } + } +} + +namespace NinjaTrader.NinjaScript.Strategies +{ + public partial class Strategy : NinjaTrader.Gui.NinjaScript.StrategyRenderBase + { + public Indicators.OrderFlowOscillator OrderFlowOscillator(int period, double upperLevel, Stroke upperLevelStroke, double lowerLevel, Stroke lowerLevelStroke) + { + return indicator.OrderFlowOscillator(Input, period, upperLevel, upperLevelStroke, lowerLevel, lowerLevelStroke); + } + + public Indicators.OrderFlowOscillator OrderFlowOscillator(ISeries input , int period, double upperLevel, Stroke upperLevelStroke, double lowerLevel, Stroke lowerLevelStroke) + { + return indicator.OrderFlowOscillator(input, period, upperLevel, upperLevelStroke, lowerLevel, lowerLevelStroke); + } + } +} + +#endregion