// Zanshin Levels for NinjaTrader 8: KING, the walls, the flip and max pain from zanshinflow.com, kept up to date. // Import: copy to Documents\NinjaTrader 8\bin\Custom\Indicators, then press F5 in a NinjaScript Editor window // (New > NinjaScript Editor). Add "ZanshinLevels" to a chart and paste your key from zanshinflow.com/motivewave. #region Using declarations using System; using System.Collections.Generic; using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Globalization; using System.Net; using System.Threading; using System.Windows.Media; using System.Xml.Serialization; using NinjaTrader.Gui; using NinjaTrader.Gui.Tools; using NinjaTrader.NinjaScript.DrawingTools; #endregion namespace NinjaTrader.NinjaScript.Indicators { public class ZanshinLevels : Indicator { private static readonly string[] Names = { "KING", "FLIP", "MP", "CW1", "CW2", "CW3", "PW1", "PW2", "PW3" }; private readonly object gate = new object(); private Dictionary levels = new Dictionary(); private string note = "Zanshin: loading"; private Timer timer; private int busy; protected override void OnStateChange() { if (State == State.SetDefaults) { Name = "ZanshinLevels"; Description = "Zanshin KING, walls, flip and max pain, live."; IsOverlay = true; Calculate = Calculate.OnPriceChange; DisplayInDataBox = false; PaintPriceMarkers = false; IsSuspendedWhileInactive = false; ApiKey = ""; Symbol = "SPX"; FuturesPrices = true; ShowMaxPain = true; CallColor = Brushes.DodgerBlue; PutColor = Brushes.IndianRed; KingColor = Brushes.Gold; FlipColor = Brushes.Gray; } else if (State == State.DataLoaded) { timer = new Timer(_ => Fetch(), null, 0, 30000); } else if (State == State.Terminated) { if (timer != null) { timer.Dispose(); timer = null; } } } // on a timer thread: the chart never waits on the network private void Fetch() { if (Interlocked.Exchange(ref busy, 1) == 1) return; try { ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12; string text; using (var web = new WebClient()) { web.Headers[HttpRequestHeader.Authorization] = "Bearer " + ApiKey.Trim(); text = web.DownloadString("https://www.zanshinflow.com/api/v1/levels?format=lines&sym=" + Uri.EscapeDataString(Symbol.Trim().ToUpperInvariant()) + (FuturesPrices ? "&prices=fut" : "")); } var next = new Dictionary(); string regime = "", stale = "0"; foreach (var line in text.Split('\n')) { var p = line.Trim().Split(','); if (p.Length != 2) continue; double v; if (p[0] == "REGIME") regime = p[1]; else if (p[0] == "STALE") stale = p[1]; else if (Array.IndexOf(Names, p[0]) >= 0 && double.TryParse(p[1], NumberStyles.Float, CultureInfo.InvariantCulture, out v)) next[p[0]] = v; } lock (gate) { levels = next; note = stale == "1" ? "Zanshin: DATA NOT UPDATING" : "Zanshin " + regime.Replace('_', ' '); } } catch (WebException e) { var r = e.Response as HttpWebResponse; lock (gate) note = r != null && r.StatusCode == HttpStatusCode.Forbidden ? "Zanshin: check your key or subscription" : "Zanshin: can't reach the server"; } catch (Exception) { lock (gate) note = "Zanshin: can't read the levels"; } finally { Interlocked.Exchange(ref busy, 0); } try { TriggerCustomEvent(_ => Paint(), null); } catch (Exception) { } } protected override void OnBarUpdate() { Paint(); } private Brush ColourOf(string name) { if (name == "KING") return KingColor; if (name == "FLIP" || name == "MP") return FlipColor; return name.StartsWith("CW") ? CallColor : PutColor; } private void Paint() { if (CurrentBar < 1) return; Dictionary now; string text; lock (gate) { now = levels; text = note; } foreach (var name in Names) { double v; if (!now.TryGetValue(name, out v) || (name == "MP" && !ShowMaxPain)) { RemoveDrawObject("zl" + name); RemoveDrawObject("zt" + name); continue; } var c = ColourOf(name); Draw.HorizontalLine(this, "zl" + name, v, c, name.Length == 3 && name[2] != '1' ? DashStyleHelper.Dash : DashStyleHelper.Solid, name == "KING" ? 2 : 1); Draw.Text(this, "zt" + name, name + " " + v.ToString("0.##", CultureInfo.InvariantCulture), 0, v, c); } Draw.TextFixed(this, "zn", text, TextPosition.TopRight); } [NinjaScriptProperty, Display(Name = "Key", Description = "From zanshinflow.com/motivewave", Order = 1, GroupName = "Zanshin")] public string ApiKey { get; set; } [NinjaScriptProperty, Display(Name = "Symbol", Description = "SPX, SPY, NDX, QQQ, IWM, ES, NQ, RTY, GC or CL", Order = 2, GroupName = "Zanshin")] public string Symbol { get; set; } [NinjaScriptProperty, Display(Name = "In futures prices", Description = "Show SPX / SPY levels in ES prices and NDX / QQQ in NQ", Order = 3, GroupName = "Zanshin")] public bool FuturesPrices { get; set; } [NinjaScriptProperty, Display(Name = "Show max pain", Order = 4, GroupName = "Zanshin")] public bool ShowMaxPain { get; set; } [XmlIgnore, Display(Name = "Call walls", Order = 5, GroupName = "Colours")] public Brush CallColor { get; set; } [Browsable(false)] public string CallColorSerial { get { return Serialize.BrushToString(CallColor); } set { CallColor = Serialize.StringToBrush(value); } } [XmlIgnore, Display(Name = "Put walls", Order = 6, GroupName = "Colours")] public Brush PutColor { get; set; } [Browsable(false)] public string PutColorSerial { get { return Serialize.BrushToString(PutColor); } set { PutColor = Serialize.StringToBrush(value); } } [XmlIgnore, Display(Name = "KING", Order = 7, GroupName = "Colours")] public Brush KingColor { get; set; } [Browsable(false)] public string KingColorSerial { get { return Serialize.BrushToString(KingColor); } set { KingColor = Serialize.StringToBrush(value); } } [XmlIgnore, Display(Name = "Flip and max pain", Order = 8, GroupName = "Colours")] public Brush FlipColor { get; set; } [Browsable(false)] public string FlipColorSerial { get { return Serialize.BrushToString(FlipColor); } set { FlipColor = Serialize.StringToBrush(value); } } } }