#region Using declarations using System; using System.Collections.Generic; using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Net.Sockets; using System.Net; 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.Data; using NinjaTrader.NinjaScript; using NinjaTrader.Core.FloatingPoint; using NinjaTrader.NinjaScript.DrawingTools; using System.Globalization; using System.Reflection; #endregion //This namespace holds Indicators in this folder and is required. Do not change it. namespace NinjaTrader.NinjaScript.Indicators { public class EncogIndicator : Indicator { public const int TIMEOUT = 20000; private enum IndicatorState { /// /// The indicator has not yet connected. /// Uninitialized, /// /// The indicator has connected and is ready for use. /// Also indicates that the indicator is not blocking /// and waiting for a bar. /// Ready, /// /// The indicator has sent a bar, and is now waiting on /// a response from Encog. /// SentBar, /// /// An error has occured. The indicator is useless at /// this point and will perform no further action. /// Error } private Socket _sock; /// /// The source data requested, for example HIGH, LOW, etc, as well as /// 3rd party indicators. /// private IList _sourceData; /// /// Are we in blocking mode? If we are just downloading data, then probably not /// in blocking mode. If we are going to actually display indicator data then we /// are in blocking mode. Blocking mode requres a "bar" response from Encog for /// each bar. Non-blocking mode can simply "stream". /// private bool _blockingMode = false; /// /// The state of the indicator. /// private IndicatorState _indicatorState = IndicatorState.Uninitialized; /// /// The output data from Encog to diplay as the indicator. /// private double[] _indicatorData = new double[8]; /// /// Error text that should be displayed if we are in an error state. /// private string _errorText; /// /// Do not change this. The "wire protocol" uses USA format numbers. /// This does not affect display. /// private readonly CultureInfo _cultureUSA = new CultureInfo("en-US"); protected override void OnStateChange() { if (State == State.SetDefaults) { Description = @"Enter the description for your new custom Indicator here."; Name = "EncogIndicator"; Calculate = Calculate.OnBarClose; IsOverlay = false; DisplayInDataBox = true; DrawOnPricePanel = true; DrawHorizontalGridLines = true; DrawVerticalGridLines = true; PaintPriceMarkers = true; ScaleJustification = NinjaTrader.Gui.Chart.ScaleJustification.Right; //Disable this property if your indicator requires custom values that cumulate with each new market data event. //See Help Guide for additional information. IsSuspendedWhileInactive = true; AddPlot(new Stroke(Brushes.Orange), PlotStyle.Line, "Plot1"); AddPlot(new Stroke(Brushes.Red), PlotStyle.Line, "Plot2"); AddPlot(new Stroke(Brushes.Green), PlotStyle.Line, "Plot3"); AddPlot(new Stroke(Brushes.Orange), PlotStyle.Bar, "Bar1"); AddPlot(new Stroke(Brushes.Red), PlotStyle.Bar, "Bar2"); AddPlot(new Stroke(Brushes.Green), PlotStyle.Bar, "Bar3"); AddPlot(new Stroke(Brushes.Firebrick), PlotStyle.TriangleDown, "IndSell"); AddPlot(new Stroke(Brushes.Green), PlotStyle.TriangleUp, "IndBuy"); AddLine(new Stroke(Brushes.DarkOliveGreen),0, "Osc1"); AddLine(new Stroke(Brushes.Khaki), 0, "Osc2"); AddLine(new Stroke(Brushes.CadetBlue), 0, "Osc3"); Console.WriteLine("Init"); Host = "localhost"; Port = 5128; } else if (State == State.Configure) { } else if(State == State.Terminated) { if (_sock != null) { Log("Encog: OnTermination called, shutting down connection.", LogLevel.Information); PerformGoodBye(); } } } protected override void OnMarketData(MarketDataEventArgs marketDataUpdate) { } protected override void OnMarketDepth(MarketDepthEventArgs marketDepthUpdate) { } protected override void OnBarUpdate() { if (CurrentBar < 1) return; // Try to connect, if we are not yet connected. // We do this here so that we do not connect everytime the indicator is instanciated. // Indicators are often instanciated several times before they are actually used. if (_indicatorState == IndicatorState.Uninitialized) { OpenConnection(); } // Are we in an error state? If so, display and exit. if (_indicatorState == IndicatorState.Error) { DrawError(); return; } // If we are actually connected to a socket, then communicate with it. if (_sock != null) { StringBuilder line = new StringBuilder(); long when = Time[0].Second + Time[0].Minute * 100l + Time[0].Hour * 10000l + Time[0].Day * 1000000l + Time[0].Month * 100000000l + Time[0].Year * 10000000000l; line.Append("\"" + PACKET_BAR + "\","); line.Append(when); line.Append(",\""); line.Append(this.Instrument.FullName); line.Append("\""); foreach (string name in _sourceData) { ISeries source; int totalBars = 1; string name2 = name; ParseArraySize(name, ref totalBars, ref name2); if (string.Compare(name2, "HIGH", true) == 0) { source = High; } else if (string.Compare(name2, "LOW", true) == 0) { source = Low; } else if (string.Compare(name2, "OPEN", true) == 0) { source = Open; } else if (string.Compare(name2, "CLOSE", true) == 0) { source = Close; } else if (string.Compare(name2, "VOL", true) == 0) { source = Volume; } else if (string.Compare(name2, "THIS", true) == 0) { source = Values[0]; } else { source = Eval(name2); if (source == null) { return; } } // now copy needed data var cnt = CurrentBar + 1; for (int i = 0; i < totalBars; i++) { line.Append(","); if (i >= cnt) { line.Append("?"); } else { //line.Append(Convert.ToString(source[i])); line.Append(Convert.ToString(source[i], _cultureUSA)); } } } Send(line.ToString()); // if we are expecting data back from the socket, then wait for it. if (_blockingMode) { // we are now waiting for a bar _indicatorState = IndicatorState.SentBar; while (_indicatorState != IndicatorState.Error && _indicatorState != IndicatorState.Ready) { WaitForPacket(); } // we got a bar message, then display it if (_indicatorState == IndicatorState.Ready) { if (!double.IsNaN(_indicatorData[0])) { Plot1[0] = _indicatorData[0]; } if (!double.IsNaN(_indicatorData[1])) { Plot2[0] = _indicatorData[1]; } if (!double.IsNaN(_indicatorData[2])) { Plot3[0] = _indicatorData[2]; } if (!double.IsNaN(_indicatorData[3])) { Bar1[0] = _indicatorData[3]; } if (!double.IsNaN(_indicatorData[4])) { Bar2[0] = _indicatorData[4]; } if (!double.IsNaN(_indicatorData[5])) { Bar3[0] = _indicatorData[5]; } if (!double.IsNaN(_indicatorData[6])) { IndSell[0] = _indicatorData[6]; } if (!double.IsNaN(_indicatorData[7])) { IndBuy[0] = _indicatorData[7]; } } } else { var hold = this.DrawOnPricePanel; DrawOnPricePanel = false; Draw.TextFixed(this,"general msg", "This indicator only sends data, so there is no display.", TextPosition.Center); DrawOnPricePanel = hold; } } } /// /// Send data to the remote socket, if we are connected. /// If we are not connected, ignore. Data is sent in ASCII. /// /// The data to send to the remote. protected void Send(String str) { if( _sock!=null ) { byte[] msg = Encoding.ASCII.GetBytes(str+"\n"); _sock.Send(msg); } } /// /// Open a connection to Encog. Also send the HELLO packet. /// protected void OpenConnection() { try { IPAddress[] ips = Dns.GetHostAddresses(Host); IPAddress targetIP = null; // first we need to resolve the host name to an IP address foreach( IPAddress ip in ips ) { if( ip.AddressFamily == AddressFamily.InterNetwork ) { targetIP = ip; break; } } // if successful, then connect to the remote and send the HELLO packet. if( targetIP!=null ) { IPEndPoint endPoint = new IPEndPoint(targetIP,Port); _sock = new Socket(targetIP.AddressFamily, SocketType.Stream, ProtocolType.Tcp); _sock.Connect(endPoint); Send("\""+PACKET_HELLO+"\",\"NinjaTrader 8\",\""+ "EMA" + "\""); } else { // report failure and exit PerformError("Failed to resolve: " + Host); return; } // make sure the socket will timeout _sock.ReceiveTimeout = TIMEOUT; _indicatorState = IndicatorState.Uninitialized; while( _indicatorState == IndicatorState.Uninitialized ) { WaitForPacket(); } } catch (Exception ex) { _sock = null; PerformError(ex.Message); } } /// /// Parse a line of CSV. Lines can have quote escaped text. /// A quote inside a string should be HTML escaped (i.e. ") /// Only " is supported, and it must be lower case. /// /// Ideally, I would like to use HttpUtility, however, in .Net /// 3.5, used by NT, this requires an additonal reference, /// which is an extra step in the setup process. /// /// The line to parse. /// A list of values. protected IList ParseCSV(string line) { var quote = false; var temp = new StringBuilder(); var result = new List(); foreach(char ch in line) { if( ch=='\"' ) { quote = !quote; } else if( ch==',' && !quote ) { result.Add(temp.ToString().Replace(""","\"")); temp.Length = 0; quote = false; } else { temp.Append(ch); } } result.Add(temp.ToString().Replace(""","\"")); return result; } /// /// Parse an array size, for example "avg[5]". Return both the /// number, and the name before the brakets. /// /// The string to parse. /// The index parsed. /// The name parsed. private void ParseArraySize(string str, ref int index, ref string name) { var idx = str.IndexOf('['); if( idx==-1 ) { return; } var idx2 = str.IndexOf(']',idx); if( idx2==-1 ) { return; } var s = str.Substring(idx+1,idx2-idx-1); index = int.Parse(s); if( index<1 ) { index = 1; } name = str.Substring(0,idx).Trim(); } /// /// The HELLO packet, sent from the client to the server to provide version information. /// public const string PACKET_HELLO = "HELLO"; /// /// The GOODBYE packet, sent from the client to the server to end communication. /// public const string PACKET_GOODBYE = "GOODBYE"; /// /// The SIGNALS packet, sent from the client to the server to specify requested data. /// public const string PACKET_SIGNALS = "SIGNALS"; /// /// The INIT packet, sent from the server to the client to provide config information. /// public const string PACKET_INIT = "INIT"; /// /// The BAR packet, sent from the client to the server at the end of each BAR. /// public const string PACKET_BAR = "BAR"; /// /// The IND packet, sent from the server to the clinet, in response to a BAR. /// public const string PACKET_IND = "IND"; /// /// The ERROR packet, used to move to an error state. /// public const String PACKET_ERROR = "ERROR"; /// /// The WARNING packet, used to log a warning. /// public const String PACKET_WARNING = "WARNING"; /// /// Wait for a packet. Timeout if necessary. /// public void WaitForPacket() { var line = new StringBuilder(); var buffer = new byte[1024]; var charBuffer = new char[1024]; var actualSize = 0; // if there was an error, nothing to wait for. if( this._indicatorState == IndicatorState.Error ) { return; } // attempt to get a packet try { actualSize = _sock.Receive(buffer); } catch(SocketException ex) { PerformError("Socket Error: " + ex.Message); return; } // If we got a packet, then process it. if( actualSize>0 ) { // packets are in ASCII ASCIIEncoding ascii = new ASCIIEncoding(); ascii.GetChars(buffer,0,actualSize,charBuffer,0); // Break up the packets, they are ASCII lines. for(int i=0;i0 ) { GotPacket(line.ToString()); line.Length = 0; } } } } } /// /// Handle an error. Display the error to the user, log it, /// and set the state to error. No further processing on this /// indicator will occur. /// /// The error text. protected void PerformError(string whatError) { try { _indicatorState = IndicatorState.Error; _errorText = whatError; Log("Encog Error: " + whatError ,LogLevel.Error); Log("Encog: Shutting down socket", LogLevel.Information); Send("\""+PACKET_GOODBYE+"\""); _sock.Close(); _sock = null; } finally { DrawError(); } } /// /// Tell the remote that we are about to disconnect. /// protected void PerformGoodBye() { try { Log("Encog: Shutting down socket", LogLevel.Information); Send("\""+PACKET_GOODBYE+"\""); _sock.Close(); _sock = null; } finally { _indicatorState = IndicatorState.Uninitialized; } } /// /// Process a packet. /// /// The packet line. protected void GotPacket(String line) { var list = ParseCSV(line); var temp = new List(); list[0] = list[0].ToUpper(); // a SIGNALS packet tells us what indicators and fund data the remote wants. if( string.Compare(list[0],PACKET_SIGNALS,true)==0 ) { _sourceData = new List(); for(int i=1;i /// Fill the parameters of a 3rd party indicator. /// /// The name of the indicator( i.e. "MACD(12,26,9)") /// The parameter list. /// The objects protected object[] FillParams(MethodInfo targetMethod, IList paramList) { ParameterInfo[] pi = targetMethod.GetParameters(); object[] result = new Object[paramList.Count]; // loop over the parameters and create objects of the correct type. for(int i=0;i /// Evaluate a custom indicator. These indicators must be in the form: /// /// INDICATOR.VALUE[BARS_NEEDED] /// /// For example, /// /// MACD(12,26,9).Avg[1] /// /// This would request the current BAR of the Avg value of the MACD indicator. /// If the indicator has only one value, then the following format is used. /// /// EMA(14)[1] /// /// This would request the current bar of EMA, with a period of 14. /// /// The indicator string. /// protected ISeries Eval(string str) { IList indicatorParams = new List(); int index2; try { // first extract the indicator name var index = str.IndexOf('('); if( index == -1 ) { index = str.Length; } var indicatorName = str.Substring(0,index).Trim(); // now extract params index = str.IndexOf('(',index); if( index!=-1 ) { index2 = str.IndexOf(')'); if( index2!=-1) { var s = str.Substring(index+1,index2-index-1).Trim(); indicatorParams = ParseCSV(s); } else { PerformError("Invalid custom indicator: " + str); return null; } index = index2+1; } // find the indicator method MethodInfo[] methods = GetType().GetMethods(); MethodInfo targetMethod = null; foreach( MethodInfo m in methods ) { if( m.Name.CompareTo(indicatorName)==0 && m.GetParameters().Length==indicatorParams.Count) { targetMethod = m; } } // determine if there is a property name string propertyName; var p = str.IndexOf('.',index); if( p!=-1 ) { index = p+1; index2 = str.IndexOf('[',index); if( index2==-1 ) { propertyName = str.Substring(index); } else { propertyName = str.Substring(index,index2-index); } index = index2; } else { propertyName = "Value"; } propertyName = propertyName.Trim(); // execute indicator var rawParams = FillParams(targetMethod,indicatorParams); object rtn = targetMethod.Invoke(this,rawParams); if( rtn==null ) { PerformError("Custom indicator returned null: " + str); return null; } var pi = rtn.GetType().GetProperty(propertyName); if( pi==null ) { PerformError("Custom indicator property not found: " + str); return null; } ISeries ds = (ISeries)pi.GetValue(rtn,null); return ds; } catch(Exception ex) { if( ex.InnerException==null ) { PerformError("Eval Error: " + ex.Message); } else { PerformError("Eval Error: " + ex.InnerException.Message); } } return null; } void DrawError() { if (this.ChartControl != null && _errorText != null) { bool hold = this.DrawOnPricePanel; this.DrawOnPricePanel = false; Draw.TextFixed(this, "error msg", "ERROR:" + _errorText, TextPosition.Center); this.DrawOnPricePanel = hold; } } public string Host{get;set;} public int Port{get;set;} [Browsable(false)] [XmlIgnore] public Series Plot1 { get { return Values[0]; } } [Browsable(false)] [XmlIgnore] public Series Plot2 { get { return Values[1]; } } [Browsable(false)] [XmlIgnore] public Series Plot3 { get { return Values[2]; } } [Browsable(false)] [XmlIgnore] public Series Bar1 { get { return Values[3]; } } [Browsable(false)] [XmlIgnore] public Series Bar2 { get { return Values[4]; } } [Browsable(false)] [XmlIgnore] public Series Bar3 { get { return Values[5]; } } [Browsable(false)] [XmlIgnore] public Series IndSell { get { return Values[6]; } } [Browsable(false)] [XmlIgnore] public Series IndBuy { get { return Values[7]; } } } } #region NinjaScript generated code. Neither change nor remove. namespace NinjaTrader.NinjaScript.Indicators { public partial class Indicator : NinjaTrader.Gui.NinjaScript.IndicatorRenderBase { private EncogIndicator[] cacheEncogIndicator; public EncogIndicator EncogIndicator() { return EncogIndicator(Input); } public EncogIndicator EncogIndicator(ISeries input) { if (cacheEncogIndicator != null) for (int idx = 0; idx < cacheEncogIndicator.Length; idx++) if (cacheEncogIndicator[idx] != null && cacheEncogIndicator[idx].EqualsInput(input)) return cacheEncogIndicator[idx]; return CacheIndicator(new EncogIndicator(), input, ref cacheEncogIndicator); } } } namespace NinjaTrader.NinjaScript.MarketAnalyzerColumns { public partial class MarketAnalyzerColumn : MarketAnalyzerColumnBase { public Indicators.EncogIndicator EncogIndicator() { return indicator.EncogIndicator(Input); } public Indicators.EncogIndicator EncogIndicator(ISeries input ) { return indicator.EncogIndicator(input); } } } namespace NinjaTrader.NinjaScript.Strategies { public partial class Strategy : NinjaTrader.Gui.NinjaScript.StrategyRenderBase { public Indicators.EncogIndicator EncogIndicator() { return indicator.EncogIndicator(Input); } public Indicators.EncogIndicator EncogIndicator(ISeries input ) { return indicator.EncogIndicator(input); } } } #endregion