From d885175945c6de44b6f062a60294f2f5c07bfd19 Mon Sep 17 00:00:00 2001 From: moshferatu Date: Tue, 23 Apr 2024 03:39:09 -0700 Subject: [PATCH] Make the bid / ask levels configurable and resolve issues with previous streams not being cancelled --- indicators/MarketDepth.cs | 101 +++++++++++++++++++++++--------------- 1 file changed, 62 insertions(+), 39 deletions(-) diff --git a/indicators/MarketDepth.cs b/indicators/MarketDepth.cs index eafd304..f7cf5af 100644 --- a/indicators/MarketDepth.cs +++ b/indicators/MarketDepth.cs @@ -7,6 +7,7 @@ using System.ComponentModel.DataAnnotations; using System.Linq; using System.Net.Sockets; using System.Text; +using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Input; @@ -27,12 +28,12 @@ using NinjaTrader.Core; //This namespace holds Indicators in this folder and is required. Do not change it. namespace NinjaTrader.NinjaScript.Indicators { - class MarketDepthProcessor + class MarketDepthStream { - public static ConcurrentDictionary Asks = new ConcurrentDictionary(); - public static ConcurrentDictionary Bids = new ConcurrentDictionary(); + public ConcurrentDictionary Asks { get; private set; } + public ConcurrentDictionary Bids { get; private set; } - static Dictionary MonthCodes = new Dictionary + static readonly Dictionary MonthCodes = new Dictionary { {"01", "F"}, {"02", "G"}, @@ -48,11 +49,11 @@ namespace NinjaTrader.NinjaScript.Indicators {"12", "Z"} }; - public static async Task StreamMarketDepth(Instrument instrument, int maxLevels, string host, int port) + public async Task StreamMarketDepth(Instrument instrument, int maxLevels, string host, int port, CancellationToken cancellationToken) { - Asks.Clear(); - Bids.Clear(); - + Asks = new ConcurrentDictionary(); + Bids = new ConcurrentDictionary(); + string symbol = GetIQFeedSymbol(instrument); using (TcpClient client = new TcpClient(host, port)) @@ -60,7 +61,7 @@ namespace NinjaTrader.NinjaScript.Indicators { StringBuilder data = new StringBuilder(); - while (true) + while (!cancellationToken.IsCancellationRequested) { byte[] buffer = new byte[4096]; int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length); @@ -103,7 +104,7 @@ namespace NinjaTrader.NinjaScript.Indicators } } - static string GetIQFeedSymbol(Instrument instrument) + private string GetIQFeedSymbol(Instrument instrument) { // Example: ES 06-24 string masterInstrument = instrument.MasterInstrument.Name; @@ -117,13 +118,13 @@ namespace NinjaTrader.NinjaScript.Indicators return "@" + masterInstrument + monthCode + year; } - static async Task SendCommand(NetworkStream stream, string command) + private async Task SendCommand(NetworkStream stream, string command) { byte[] commandBytes = Encoding.UTF8.GetBytes(command + "\r\n"); await stream.WriteAsync(commandBytes, 0, commandBytes.Length); } - static void ProcessMarketDepthMessage(string message) + private void ProcessMarketDepthMessage(string message) { string[] fields = message.Split(','); @@ -160,7 +161,7 @@ namespace NinjaTrader.NinjaScript.Indicators } } - static void TrimMarketDepth(int maxLevels) + private void TrimMarketDepth(int maxLevels) { if (maxLevels <= 0) return; @@ -199,6 +200,9 @@ namespace NinjaTrader.NinjaScript.Indicators public class MarketDepth : Indicator { + private MarketDepthStream Stream = new MarketDepthStream(); + private CancellationTokenSource StreamCancellation; + protected override void OnStateChange() { if (State == State.SetDefaults) @@ -216,34 +220,45 @@ namespace NinjaTrader.NinjaScript.Indicators IsSuspendedWhileInactive = false; MaxLevels = 0; MaxWidth = 500; + BidStroke = new Stroke(Brushes.LimeGreen, DashStyleHelper.Solid, 3); + AskStroke = new Stroke(Brushes.Red, DashStyleHelper.Solid, 3); Host = "127.0.0.1"; Port = 9200; } - else if (State == State.Configure) - { - } else if (State == State.DataLoaded) { - Task.Run(() => MarketDepthProcessor.StreamMarketDepth(Instrument, MaxLevels, Host, Port)); + StreamCancellation = new CancellationTokenSource(); + Task.Run(() => Stream.StreamMarketDepth(Instrument, MaxLevels, Host, Port, StreamCancellation.Token)); } else if (State == State.Historical) { SetZOrder(-1); // Display behind bars on chart. } + else if (State == State.Terminated) + { + if (StreamCancellation != null) + { + StreamCancellation.Cancel(); + StreamCancellation.Dispose(); + } + } } protected override void OnRender(ChartControl chartControl, ChartScale chartScale) { base.OnRender(chartControl, chartScale); - if (MarketDepthProcessor.Asks.Count() <= 0 || MarketDepthProcessor.Bids.Count() <= 0) + if (Stream.Asks == null || Stream.Bids == null) + return; + + if (Stream.Asks.Count() <= 0 || Stream.Bids.Count() <= 0) return; // Find the max volume for visually scaling the indicator. - long maxVolume = Math.Max(MarketDepthProcessor.Asks.Values.ToList().DefaultIfEmpty(0).Max(), - MarketDepthProcessor.Bids.Values.ToList().DefaultIfEmpty(0).Max()); + long maxVolume = Math.Max(Stream.Asks.Values.ToList().DefaultIfEmpty(0).Max(), + Stream.Bids.Values.ToList().DefaultIfEmpty(0).Max()); - foreach (var Ask in MarketDepthProcessor.Asks.ToArray()) + foreach (var Ask in Stream.Asks.ToArray()) { float price = Ask.Key; int volume = Ask.Value; @@ -251,10 +266,10 @@ namespace NinjaTrader.NinjaScript.Indicators int length = (int)((double)volume / maxVolume * MaxWidth); int y = chartScale.GetYByValue(price); - DrawHorizontalLine((ChartPanel.X + ChartPanel.W) - length, ChartPanel.X + ChartPanel.W, y, new Stroke(Brushes.Red, DashStyleHelper.Solid, 3)); + DrawHorizontalLine((ChartPanel.X + ChartPanel.W) - length, ChartPanel.X + ChartPanel.W, y, AskStroke); } - foreach (var Bid in MarketDepthProcessor.Bids.ToArray()) + foreach (var Bid in Stream.Bids.ToArray()) { float price = Bid.Key; int volume = Bid.Value; @@ -262,7 +277,7 @@ namespace NinjaTrader.NinjaScript.Indicators int length = (int)((double)volume / maxVolume * MaxWidth); int y = chartScale.GetYByValue(price); - DrawHorizontalLine((ChartPanel.X + ChartPanel.W) - length, ChartPanel.X + ChartPanel.W, y, new Stroke(Brushes.LimeGreen, DashStyleHelper.Solid, 3)); + DrawHorizontalLine((ChartPanel.X + ChartPanel.W) - length, ChartPanel.X + ChartPanel.W, y, BidStroke); } } @@ -294,11 +309,19 @@ namespace NinjaTrader.NinjaScript.Indicators { get; set; } [NinjaScriptProperty] - [Display(Name = "IQFeed Host", Description = "The host on which the IQFeed connection is running", Order = 3, GroupName = "Market Depth")] + [Display(Name = "Bid Level", Description = "Color and style of bid levels", Order = 3, GroupName = "Market Depth")] + public Stroke BidStroke { get; set; } + + [NinjaScriptProperty] + [Display(Name = "Ask Level", Description = "Color and style of ask levels", Order = 4, GroupName = "Market Depth")] + public Stroke AskStroke { get; set; } + + [NinjaScriptProperty] + [Display(Name = "IQFeed Host", Description = "The host on which the IQFeed connection is running", Order = 5, GroupName = "Market Depth")] public string Host { get; set; } [NinjaScriptProperty] - [Display(Name = "IQFeed Port", Description = "The port on which the IQFeed connection is listening", Order = 4, GroupName = "Market Depth")] + [Display(Name = "IQFeed Port", Description = "The port on which the IQFeed connection is listening", Order = 6, GroupName = "Market Depth")] public int Port { get; set; } } } @@ -310,18 +333,18 @@ namespace NinjaTrader.NinjaScript.Indicators public partial class Indicator : NinjaTrader.Gui.NinjaScript.IndicatorRenderBase { private MarketDepth[] cacheMarketDepth; - public MarketDepth MarketDepth(int maxLevels, int maxWidth, string host, int port) + public MarketDepth MarketDepth(int maxLevels, int maxWidth, Stroke bidStroke, Stroke askStroke, string host, int port) { - return MarketDepth(Input, maxLevels, maxWidth, host, port); + return MarketDepth(Input, maxLevels, maxWidth, bidStroke, askStroke, host, port); } - public MarketDepth MarketDepth(ISeries input, int maxLevels, int maxWidth, string host, int port) + public MarketDepth MarketDepth(ISeries input, int maxLevels, int maxWidth, Stroke bidStroke, Stroke askStroke, string host, int port) { if (cacheMarketDepth != null) for (int idx = 0; idx < cacheMarketDepth.Length; idx++) - if (cacheMarketDepth[idx] != null && cacheMarketDepth[idx].MaxLevels == maxLevels && cacheMarketDepth[idx].MaxWidth == maxWidth && cacheMarketDepth[idx].Host == host && cacheMarketDepth[idx].Port == port && cacheMarketDepth[idx].EqualsInput(input)) + if (cacheMarketDepth[idx] != null && cacheMarketDepth[idx].MaxLevels == maxLevels && cacheMarketDepth[idx].MaxWidth == maxWidth && cacheMarketDepth[idx].BidStroke == bidStroke && cacheMarketDepth[idx].AskStroke == askStroke && cacheMarketDepth[idx].Host == host && cacheMarketDepth[idx].Port == port && cacheMarketDepth[idx].EqualsInput(input)) return cacheMarketDepth[idx]; - return CacheIndicator(new MarketDepth(){ MaxLevels = maxLevels, MaxWidth = maxWidth, Host = host, Port = port }, input, ref cacheMarketDepth); + return CacheIndicator(new MarketDepth(){ MaxLevels = maxLevels, MaxWidth = maxWidth, BidStroke = bidStroke, AskStroke = askStroke, Host = host, Port = port }, input, ref cacheMarketDepth); } } } @@ -330,14 +353,14 @@ namespace NinjaTrader.NinjaScript.MarketAnalyzerColumns { public partial class MarketAnalyzerColumn : MarketAnalyzerColumnBase { - public Indicators.MarketDepth MarketDepth(int maxLevels, int maxWidth, string host, int port) + public Indicators.MarketDepth MarketDepth(int maxLevels, int maxWidth, Stroke bidStroke, Stroke askStroke, string host, int port) { - return indicator.MarketDepth(Input, maxLevels, maxWidth, host, port); + return indicator.MarketDepth(Input, maxLevels, maxWidth, bidStroke, askStroke, host, port); } - public Indicators.MarketDepth MarketDepth(ISeries input , int maxLevels, int maxWidth, string host, int port) + public Indicators.MarketDepth MarketDepth(ISeries input , int maxLevels, int maxWidth, Stroke bidStroke, Stroke askStroke, string host, int port) { - return indicator.MarketDepth(input, maxLevels, maxWidth, host, port); + return indicator.MarketDepth(input, maxLevels, maxWidth, bidStroke, askStroke, host, port); } } } @@ -346,14 +369,14 @@ namespace NinjaTrader.NinjaScript.Strategies { public partial class Strategy : NinjaTrader.Gui.NinjaScript.StrategyRenderBase { - public Indicators.MarketDepth MarketDepth(int maxLevels, int maxWidth, string host, int port) + public Indicators.MarketDepth MarketDepth(int maxLevels, int maxWidth, Stroke bidStroke, Stroke askStroke, string host, int port) { - return indicator.MarketDepth(Input, maxLevels, maxWidth, host, port); + return indicator.MarketDepth(Input, maxLevels, maxWidth, bidStroke, askStroke, host, port); } - public Indicators.MarketDepth MarketDepth(ISeries input , int maxLevels, int maxWidth, string host, int port) + public Indicators.MarketDepth MarketDepth(ISeries input , int maxLevels, int maxWidth, Stroke bidStroke, Stroke askStroke, string host, int port) { - return indicator.MarketDepth(input, maxLevels, maxWidth, host, port); + return indicator.MarketDepth(input, maxLevels, maxWidth, bidStroke, askStroke, host, port); } } }