using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using TradeIdeas.MarketDataProxy; using TradeIdeas.MarketDataProxy.DataFormats; using MarketDataProxy; using System.IO; using MdsApi; using MdsApiHelper; using System.Threading; namespace TeletraderProxy { public partial class Form1 : Form { TradeIdeas.MarketDataProxy.SymbolListenerThread listen; public TeletraderSymbolListener[] symListen; public IMessage[] requests; public static IMessage[] lostHighsandLows; public delegate void dataCallback(); //executes method to send quote request public static bool recover = false; //used to determine if we're recovering // highs and lows or not. false for no, true for yes. public int lostData; //holds the size of the IMessage lostHighsandLows list; public static int outStandingRequest = 5; //Use outstanding request model, change number if necessary. Neet to keep // the folks at Teletrader happy public static int lostDataTally = outStandingRequest; //tally for the number of elements in the lostHighsandLows list //starts at 5 if we wish number of outstanding requests to be 5 for example //Streamreader object to read the translation file public StreamReader symbolMap; //Streamreader object to read file(boolean) to determine //whether a search for highs and lows will commence public StreamReader recovery; //DateTime object used to compute whether high and low recovery // will take place. if the gui starts at a time that's greater // than "getHiLo", then the high and lows must be recovered using // the quote request public static DateTime getHiLo; //This is the time when the GUI is started up for the trading session public static DateTime startTime; //This is the exit code variable that will be relayed to the // batch file. This code will determine whether the batchfile should keep // attempting to start this application till a connection is reached, or to terminate if // trading session is over. public static int stopProgram; public IMessage result; public static bool statusOK = true; public static string dir = @"..\..\"; static string recFile = "recovery.txt"; static string filePathRec = dir + recFile; //Declare the timer for testing the connection to Teletrader // This timer coordinates the sending of the dummy message. public static System.Threading.Timer testTimer; //For the connection Listener public static ConnectionListenerForm form; //The "rawSymbolList" initially holds the symbols from the file that we wish to observe. //It doesn't comprise all of the symbols in Xetra universe, it is the symbol file produced // from the getsymbollist_cume program using dollar volumes. Hopefully no zero-dollar volume // stocks are included here... // The list "commonSymbol" is an "updated version" of the rawSymbolList and holds // distinct user-friendly symbols...that is, any duplicates have been removed. public static List rawSymbolList = new List(); public static List commonSymbol = new List(); //This will hold our dictionary for symbols read from the translation file.... public static Dictionary translate = new Dictionary(); /*The following is for creation of a logfile should one wish to track down problems.This file will be created in the same folder as the executable...*/ // use the following to name the log file public static DateTime todaysDate = DateTime.Now; public static int year = todaysDate.Year; public static int month = todaysDate.Month; public static int day = todaysDate.Day; public static int hour = todaysDate.Hour; public static int minute = todaysDate.Minute; //set the name of log file and its location public static string logFile = "Log_File" + "_" + year + "_" + month + "_" + day + "_" + hour + "_" + minute + ".txt"; public static string logDirectory = dir + logFile; //Now declare the log file. public static StreamWriter log_file = new StreamWriter( new FileStream(logFile, FileMode.Create, FileAccess.Write)); //other statics related to logging into Teletrader public static int userID; public static int reqID; public static IMDSClient mds; public static OperationStatus cs; //Each ticker symbol read from the text file (and subsequently stored in the commonSymbol container) // This version utilizes the variables in the implemented SymbolListerBase class to take care // of caching of WireL1 and wireTos data for each symbol. As a result, I don't need Lists for // each of the data items for WireL1 and WireTOS //The below BackGroundWorker object "worker" is used for all of the grunt // work of logging into Teletrader and handling the streaming data. This // prevents the GUI from "freezing" BackgroundWorker worker = new BackgroundWorker(); //This is the constructor that is activated when the user uses // the console option. public Form1(string[] commands) { InitializeComponent(); // ConnectionListenerForm.GetInstance().AddConnection("will.trade-ideas.com", 9999); ConnectionListenerForm.GetInstance().AddConnection("127.0.0.1", 9999); MonitorStatusForm.GetInstance().setName(commands[4]); MonitorStatusForm.GetInstance().setDatabase(commands[5], commands[6], commands[7], commands[8]); StartThread(); } public Form1() { InitializeComponent(); // ConnectionListenerForm.GetInstance().AddConnection("will.trade-ideas.com", 9999); ConnectionListenerForm.GetInstance().AddConnection("127.0.0.1", 9999); MonitorStatusForm.GetInstance().setName("Proxy Xetra"); } //Code from Phil private void addLog(string line) { if (messagesTextBox.InvokeRequired) { messagesTextBox.Invoke(new MethodInvoker(delegate() { addLog(line); })); } else { messagesTextBox.AppendText(line); messagesTextBox.AppendText("\r\n"); } } //End of Code from Phil private void btnStartThread_Click(object sender, EventArgs e) { StartThread(); } private void DoBackgroundWork() { worker.WorkerReportsProgress = false; worker.DoWork += startSubscription; worker.WorkerSupportsCancellation = true; worker.RunWorkerAsync(); } private void btnConnections_Click(object sender, EventArgs e) { ConnectionListenerForm form = ConnectionListenerForm.GetInstance(); form.Show(); form.BringToFront(); form.WindowState = FormWindowState.Normal; } private void btnQueueStatus_Click(object sender, EventArgs e) { QueueStatusForm queueForm = QueueStatusForm.GetInstance(); queueForm.Show(); queueForm.BringToFront(); queueForm.WindowState = FormWindowState.Normal; } private void readSymbols() { // This method, readSymbols(), is used to read the user-friendly //symbols from a file, then translate them to the long codes so they //can be processed by the subscription request... // This is always called in a seperate thread, not the GUI thread // Testing file("symbols.txt")..contains the symbols that we want to look at: string ticker = "symbols.txt"; const string fileName = "teletraderTranslationFile.txt"; //Translation file string recFile = "recovery.txt"; // file containing boolean for data recovery after lost connections string filePath = dir + fileName; //teletraderTranslation File string filePath_2 = dir + ticker; // common symbols string filePathRec = dir + recFile; //data recovery /* fetchHiLo() I *only* wish to go through the retrieval process when we're coming up from a lost conenction and *not* when it's started before the beginning of the trading session. Thus the "fetchHiLo method*/ fetchHiLo(); //First read those user-friendly symbols from file: //With our this example, even if somebody furnishes a "naked"list of symbols // that is, symbols without the ".DE" extenstion, it will be added in this //block of code. As a result, we can use two values of the translation table //instead of three... // We will then add the symbols to the list called "rawSymbolist" StreamReader inputCommonSymbols = null; DateTime time1 = DateTime.Now; //StartTime for opening files,removing duplicates.. try { inputCommonSymbols = new StreamReader( new FileStream(filePath_2, FileMode.Open, FileAccess.Read)); while (inputCommonSymbols.Peek() != -1) { string commonElement = inputCommonSymbols.ReadLine(); if (!commonElement.Contains(".")) commonElement = commonElement + ".DE"; rawSymbolList.Add(commonElement); // populate container for user-friendly Symbol } //Now that the text file has been read, let's close it: inputCommonSymbols.Close(); } catch (IOException) { lblMsg.Invoke(new MethodInvoker(delegate() { lblMsg.Text = "I/O error occured while reading text file"; })); log_file.WriteLine("I/O error occured while reading symbols.txt"); log_file.Close(); worker.CancelAsync(); //TODO-need graceful way to shut down thread } //Next, in certain cases a symbol file "might" contain duplicates. If I wish to add, for example, //the symbols generated from the Translation File, there might be some dupes. Although an ugly //internal ID(distinct) can be mapped to *only one* user friendly symbol, different internal ID's can have // that same userFrienly symbol associated with them thus resulting in that same user-friendly symbol // being being seen on the file....so use the LINQ "distinct" to remove dupes...If dupes arent removed, // the list will choke Phil's symbolListener class by throwing a "Duplicate implementation for symbol" //exception. Used a LINQ one-liner: // Get distinct elements and convert into a list again. commonSymbol = rawSymbolList.Distinct().ToList(); //Here, we're opening up the the translation file // Now we populate our dictionary called "translate"... // document StreamReader symbolMap = null; try { symbolMap = new StreamReader( new FileStream(filePath, FileMode.Open, FileAccess.Read)); while (symbolMap.Peek() != -1) { string data = symbolMap.ReadLine(); string[] trans = data.Split(','); translate.Add(trans[0], trans[1]); //trans[0] is the internalID and trans[1] is.DE extension } //Close the symbolMap connection, we're through // populating the Dictionary symbolMap.Close(); } catch (IOException) { lblMsg.Invoke(new MethodInvoker(delegate() { lblMsg.Text = "I/O error occured while reading translation file"; btnStartThread.Enabled = true; btnQueueStatus.Enabled = false; })); log_file.WriteLine("I/O error occured while reading translation file"); log_file.Close(); //Thread.Abort is not the ideal method for // terminating a thread. Unfortuately .Dispose() // does not halt the rest of the startsubscription method. // it will still go on to attempt loading symbols and contacting // teletrader...Ideally would like the current method to just stop without // any other further methods to be executed...This will be a TODO // } DateTime time2 = DateTime.Now; //EndTime for opening files,removing duplicates.. TimeSpan complete = time2 - time1; int dupes = rawSymbolList.Count - commonSymbol.Count; if (lblMsg.InvokeRequired) { lblMsg.Invoke(new MethodInvoker(delegate() { lblMsg.Text = dupes + " dupes removed from file containing " + rawSymbolList.Count + " symbols\n" + commonSymbol.Count + " symbols to sumbit to subscription.\n" + " Total time from opening files to pruning dupes is: " + complete.Milliseconds + " millisec"; })); } } //This method, "startSubscription" is called in the backgroundworker thread private void startSubscription(object sender, DoWorkEventArgs e) { //The below is a counter for the number of times "Sendall" is sent. //for debugging. TradeIdeas.MarketDataProxy.Globals.sendAllCounter = 0; TradeIdeas.MarketDataProxy.Globals.sendFunctionCounter = 0; startTime = DateTime.Now; //..Debug/////////////////////////////////////////////////////////// // Open up the log_file for writing //The below is responsible for the "health check" of the connection to // Teletrader. Set this initially to zero. TradeIdeas.MarketDataProxy.Globals.TeletraderConnectionCheckCounter = 0; BackgroundWorker newWorker = sender as BackgroundWorker; //Make instance of new SymbolListener Thread... //This thread manages all of the symbolListener objects.. listen = new TradeIdeas.MarketDataProxy.SymbolListenerThread(); //Store the "listen" thread in Globals... TradeIdeas.MarketDataProxy.Globals.symbolListenerThread = listen; //Next, read and translate the symbols from the translation text file readSymbols(); // Now prepare the TeletraderListener Objects.. prepareTeletraderListeners(); //Start the SymbolLister Thread listen.StartProcessing(); ////////////////////////////////////////////////////////////////////////////// //Login to Teletrader.... loginToTeletrader(); //Now prepare the request queries... int sizeOfSymbolList = commonSymbol.Count; requests = new IMessage[sizeOfSymbolList]; lostHighsandLows = new IMessage[sizeOfSymbolList]; //recovering lost highs and lows due to dropped connection //load up the IMessage requests and the RequestID list container: for (int i = 0; i < sizeOfSymbolList; i++) { // this respresents the unique ID that will be pulled // from the translation arrays. string unique = null; unique = fetchUniqueSymbol(commonSymbol[i]); //A case can arise when the list of symbols might contain symbols that are not // in the translation table(i.e. translation table might not be up to date-as data // provider can be adding new, or removing symbols periodically ). In this // case, we don't wish to put a "null" entry into a subscription request. The // fetchUniqueSymbol method returns null if a symbol isn't in the translation // dictionary....Hence it will not go to the bundled request... if (unique != null) { requests[i] = RequestFactory.MakeSubscriptionRequest(reqID++, userID, unique, SubscriptionAction.Subscribe) .AddFieldFilter(FID.BestBidDateTime) .AddFieldFilter(FID.BestBid) .AddFieldFilter(FID.BidSize) .AddFieldFilter(FID.BestAsk) .AddFieldFilter(FID.AskSize) .AddFieldFilter(FID.Volume) .AddFieldFilter(FID.LastTradeDateTime) .AddFieldFilter(FID.LastTrade) .AddFieldFilter(FID.Volume) .AddFieldFilter(FID.TotalVolume) .AddFieldFilter(FID.Open) .AddFieldFilter(FID.High) .AddFieldFilter(FID.Low) .AddFieldFilter(FID.TickID) .AddFieldFilter(FID.NetChangeTradingSession); lostHighsandLows[i] = RequestFactory.MakeQuoteRequest(reqID++, userID, unique).AddFieldFilter(FID.Low).AddFieldFilter(FID.High); } } result = null; /////////////////////////////////////////////////////////// // /////////////////////////////////////////////////////////////////// //Send the bundled subscription request... foreach (IMessage rq in requests) mds.Send(rq); ///This one is for the high-low recovery if(recover == true) { recoverData(); } //This one minute timer is designed to check the connection // status TimerCallback callback = new TimerCallback(checkConnection); // Create the 60 second timer testTimer = new System.Threading.Timer(callback, "status", 0, 60000); //This for-loop goes on indefinitely....retrieving each "tick". //Each response is a "tick". Each response can comprise of // more than one message, not necessarily in the order in they // were requested...an incoming message might be a a bidPrice, // askPrice, or bidSize, or whatever...(depending on the filters // that are in place. for (; ; ) { //Messages are coming through cs = mds.Receive(ref result); if (cs != OperationStatus.OK) { if (mds.GetLastNetworkError() == 10054) { Console.WriteLine("Connection Lost"); log_file.WriteLine("Connection Lost: {0}",DateTime.Now.ToString()); statusOK = false; mds.Disconnect(); Console.WriteLine("ReTrying....."); log_file.WriteLine("ReTrying....."); stopProgram = 1; //emit the exit code so the batchfile can pick it up. // If reStart is 1, that alerts the batchfile to keep on // restarting this app till a connection is reached log_file.Close(); System.Environment.ExitCode = stopProgram; Environment.Exit(stopProgram); } else { stopProgram = 1; System.Environment.ExitCode = stopProgram; Console.WriteLine("Problem"); Console.WriteLine("GoodBye....."); log_file.WriteLine("Other Problem encountered.....{0}",DateTime.Now.ToString()); log_file.WriteLine("Other Connection error!"); log_file.WriteLine("Return value: {0}", cs); log_file.WriteLine("Network error code: {0}", mds.GetLastNetworkError()); log_file.WriteLine("Error description: {0}", mds.GetLastErrorDescription()); log_file.Close(); Environment.Exit(stopProgram); } } /*Every 60 seconds we're sending a dummy message which is not a subscription request, hence it has different response items than those coming thru for the subscription request. As a result, we do not want responses to this message to *ever* enter the ProcessSubscriptionResultMessage method as it will ultimately crash the whole program in short order. We're sending this dummy message to ultimately make sure that we still have a connection to Teletrader. If this message cannot be sent, that means a lost connection. Then the approriate actions will be taken by batch file... to restart the connection.The below code snippet is to check each response coming thru and make sure that any time a response from the "dummy request" is received, is skipped over and not allowed to enter ProcessSubscriptionResultMessage(result)*/ SafeMessage dummy = new SafeMessage(result); int testMessage = dummy[MessageFolderName.Header][FID.MessageType].AsInteger(); if ((int)MessageType.LoginCheckResponse == testMessage) { //If we're here, obviously that means that the message made through and // we've got a response. So, we now summon the database of our status.. MonitorStatusForm.GetInstance().touchDatabase(); continue; } ProcessSubscriptionResultMessage(result); //The below if-block is for recovering data //when GUI is fired up "in the middle of the day" if (recover == true) { if (lostDataTally != lostHighsandLows.Length) { mds.Send(lostHighsandLows[lostDataTally]); lostDataTally++; } } } } private bool ProcessSubscriptionResultMessage(IMessage message) { //This is the place that we process each response coming in from Teletrader // the message(s) associated with this response are iterated with the IMessageItemIterator. // //Here is the streaming data.... SafeMessage msg = new SafeMessage(message); //Get internal ID number ...... string returnedUnique = msg[MessageFolderName.SymbolIdentification][FID.UniqueSymbolName].AsString(); //Now, go get the userfriendly symbol that corresponds to that internal id. string returnedSymbol = fetchCommonSymbol(returnedUnique); IMessageItemIterator fit = msg[MessageFolderName.FIDContainer].GetItems(); //entry point into the implemented "TeletraderSymbolListener" TradeIdeas.MarketDataProxy.Globals.symbolListenerThread.NewData(returnedSymbol, fit); return msg[MessageFolderName.Header][FID.Continue].AsBoolean(); } private string fetchCommonSymbol(string input) { // We're retrieving a CommonSymbol from an internalID //The "TryGetValue" is supposed to be more efficient than //"ContainsKey" and we'll never have to worry about friendlySymbol // being null.... //effeciency of tryGetValue: http://dotnetperls.com/trygetvalue string returnValue = null; string friendlySymbol; if (translate.TryGetValue(input, out friendlySymbol)) returnValue = friendlySymbol; return returnValue; } private string fetchUniqueSymbol(string input) { //found a Linq example to retrieve a dictionary key from its value: // http://social.msdn.microsoft.com/Forums/en-US/csharpgeneral/thread/542578c8-a5a7-4092-b6cb-db0fb1cc59ee string uID = (from d in translate where d.Value == input select d).FirstOrDefault().Key; return uID; } private void prepareTeletraderListeners() { //Create Teletrader Listener objects for each symbol // and add the object to the "listen" thread int sizeOfSymbolList = commonSymbol.Count; symListen = new TeletraderSymbolListener[sizeOfSymbolList]; for (int i = 0; i < sizeOfSymbolList; i++) { symListen[i] = new TeletraderSymbolListener(commonSymbol[i]); listen.Add(commonSymbol[i], symListen[i]); } } public static void checkConnection(Object stateInfo) { //The following IMessage is a "dummy message" which will be // sent each 30 seconds. We don't need to know what the response // of this message will be; we just want to know whether this message // can actually be sent. If it can't, then an error will be generated // by the API due to a lost connection. That error will be handled // in the infinite for-loop with the streaming data. This whole app will // shut down and then keep restarting till a connection is made. //First, let's check to see if we're done for the day... checkFinishTime(); IMessage loginCheck = RequestFactory.MakeLoginCheckRequest(reqID++, userID); mds.Send(loginCheck); if (statusOK == true) { //Debug purposes //A Console.WriteLine will show in the Debug mode within the "output" // window within Visual Studio. Console.WriteLine("hello--you're in connectionCheck"); //Increment the counter every time that this method is executed TradeIdeas.MarketDataProxy.Globals.TeletraderConnectionCheckCounter++; } } //BEGIN This one is for data recovery part of a callback public static void recoverData() { //Must use the outstanding request model for (int i = 0; i < outStandingRequest; i++) { mds.Send(lostHighsandLows[i]); } } //END of block for data recovery///// public static void checkFinishTime() { //This method is called from within the CheckConnection to see whether // the time has arrived to shut down the GUI due to the trading day being // over... // Here, we're setting the finished time to be 10 hr 30 min from // start time..and we're assuming that the start-time for this program // will be at 10:30pm for the delayed data. int pmStart = 18; //This represents 6 pm PST int pmFinish = 23; // This represents 11 pm PST int amStart = 0; //represents 12:00 am PST int amFinish = 9; // represents 9:00 am PST DateTime currentTime = DateTime.Now; //check to see whether today's time is between 10:30:00 pm and 11:59:59 pm PST //This is a valid timeblock for Xetra from Sunday through Thursday. DateTime pmBegin = new DateTime(currentTime.Year, currentTime.Month, currentTime.Day, pmStart, 0, 0, 0); DateTime pmEnd = new DateTime(currentTime.Year, currentTime.Month, currentTime.Day, pmFinish, 59,59, 999); //the following are the objects representing the start and finish 12:00 am and 9:00 am PST. For Xetra it is Monday thru Firday DateTime amBegin = new DateTime(currentTime.Year, currentTime.Month, currentTime.Day, amStart, 0, 0, 0); DateTime amEnd = new DateTime(currentTime.Year, currentTime.Month, currentTime.Day, amFinish, 0,0, 0); //is our current time between the start and finish PM hours? if (currentTime >= pmBegin && currentTime <= pmEnd) { //is the current time a friday or saturday?...if so,shut down, otherwise "return" if (currentTime.DayOfWeek == DayOfWeek.Friday || currentTime.DayOfWeek == DayOfWeek.Saturday) { stopProxy(); } return; //keep the proxy running } else if (currentTime >= amBegin && currentTime <= amEnd) { //is current time between the start and finish AM hours? //is the current time a saturday or sunday?...if so,shut down, otherwise "return" if (currentTime.DayOfWeek == DayOfWeek.Saturday || currentTime.DayOfWeek == DayOfWeek.Sunday) { stopProxy(); } return; //keep the proxy running } else //if the current time doesn't meet either of the above constraints...stop proxy { stopProxy(); } } private static void stopProxy() { stopProgram = 0; System.Environment.ExitCode = stopProgram; log_file.WriteLine("Proxy has shut down at end of trade session: {0}", DateTime.Now.ToString()); log_file.Close(); Environment.Exit(stopProgram); } /*Begin Cut and paste from Phils code */ private void onStatusChanged(ConnectionListener source, string status) { addLog("Status: " + status); } private void onConnected(ConnectionListener source) { addLog("Connected!"); } private void onDisconnected(ConnectionListener source) { addLog("Disconnected!"); } private void onSubscription(ConnectionListener source, List data) { if (data == null) // This is *not* the normal way this is used. We never expect to see a null here. addLog("Subscription message error!"); else { addLog("Subcription message:"); foreach (SubscriptionItem i in data) addLog('"' + i.Symbol + "\" (\"" + i.Command + "\")"); addLog("Subcription message ends."); } } private void onPreview(ConnectionListener source, byte[] data) { if (data == null) addLog("Preview message error!"); } ///////////////////End of Cut n Paste from Phil's Code //////////////////// //Cut-N-Paste from Phil's Code (MarketDataProxyTest) private void btnDebug_Click(object sender, EventArgs e) { SymbolListenerThread thread = TradeIdeas.MarketDataProxy.Globals.symbolListenerThread; thread.RequestDebugDump(new SymbolListenerThread.DebugResponse(showDebugResponse)); } private void showDebugResponse(string response) { // We expect to get this callback in the wrong thread. AddLog is smart enough to deal with that. addLog(response); } //End of Cut-N-Paste from Phil's Code private void btnExit_Click(object sender, EventArgs e) { //updateRecovery.Close(); log_file.Close(); this.Close(); } private void StartThread() { lblMsg.Text = "Connecting to Teletrader...."; btnQueueStatus.Enabled = true; btnDebug.Enabled = true; btnConnections.Enabled = true; btnStartThread.Enabled = false; //Start of the worker thread DoBackgroundWork(); } public static void loginToTeletrader() { //Here we shall create a new Login Object to //Login to the data provider Login myLogin = new Login(); //Get the required info to make message // queries. userID = myLogin.RunLogin(); string userName = myLogin.getUserName(); string password = myLogin.getPassword(); reqID = myLogin.getRequestId(); mds = myLogin.getClient(); cs = myLogin.getStatus(); } private void btnMonitor_Click(object sender, EventArgs e) { MonitorStatusForm statusForm = MonitorStatusForm.GetInstance(); statusForm.Show(); statusForm.BringToFront(); statusForm.WindowState = FormWindowState.Normal; } private void fetchHiLo() { /*This method will determine whether we'll go after missing highs and lows due to lost connection. I only wish to fetch missing values when the hour at which the lost connection occurs happens during trading hours. */ TimeZoneInfo timeZoneInfo; DateTime myDateTime; //Set the time zone information to Central European Time timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById("Central European Standard Time"); //Get date and time in Central European Time myDateTime = TimeZoneInfo.ConvertTime(DateTime.Now, timeZoneInfo); DayOfWeek testDate = myDateTime.DayOfWeek; if (testDate == DayOfWeek.Saturday || testDate == DayOfWeek.Sunday) { recover = false; return; } else { //The market is open from 9:OO am to 5:30pm Central European Time. Make // sure that time that this program starts up falls between these two times before retrieving highs and lows. if (myDateTime > myDateTime.Date.AddHours(9) && myDateTime < myDateTime.Date.AddMinutes(1050)) { recover = true; return; } else { recover = false; return; } } } } }