using System; using System.Linq; using System.Text; using System.Windows.Forms; using System.Xml; using TradeIdeas.XML; using TradeIdeas.MiscSupport; using System.Collections.Generic; using TradeIdeas.ServerConnection; using TradeIdeas.TIProData; using TradeIdeas.TIProData.Interfaces; namespace TradeIdeas.TIProGUI.LimitAlerts { public delegate void LimitAlertMessageReceivedHandler(string type, XmlNode message); public delegate void LimitAlertStatusProcessedHandler(); /// /// Responsible for interpreting messages from the limit alert server and sending them to the appropriate forms in TIPro. /// public class LimitAlertsManager { private IConnectionMaster _connectionMaster; public event LimitAlertMessageReceivedHandler MessageReceived; public event LimitAlertStatusProcessedHandler StatusProcessed; /// /// Responsible for interpreting messages from the limit alert server and sending them to the appropriate forms in TIPro. /// public LimitAlertsManager(IConnectionMaster connectionMaster) { _connectionMaster = connectionMaster; } /// /// Handles message from limit alert server as received from the limit alert server. /// /// Type of message. Can be: alert, update, status, or removed. /// Xml message from server. public void HandleMessage(string type, XmlNode message) { //GuiEnvironment.LogMessage("[LimitAlert-HandleMessage] type=" + type + ", message=" + message.OuterXml); if (type == "alert" || type == "update") { LimitAlert limitAlert = GetLimitAlert(message); if (null != limitAlert) { int index = GuiEnvironment.LimitAlerts.IndexOf(limitAlert); if (index == -1) { if ((GuiEnvironment.LimitAlerts.Count < GuiEnvironment.MaxLimitAlerts)) GuiEnvironment.LimitAlerts.Add(limitAlert); else return; } else GuiEnvironment.LimitAlerts[index] = limitAlert; List limitAlertsForms = Application.OpenForms.OfType().Where(x => !x.IsDisposed).ToList(); if (limitAlertsForms.Count > 0) { foreach (LimitAlertsForm limitAlertsForm in limitAlertsForms) { limitAlertsForm.AddLimitAlert(limitAlert, type == "alert"); } } List chartForms = Application.OpenForms.OfType().Where(x => !x.IsDisposed && x.Symbol == limitAlert.Symbol).ToList(); if (chartForms.Count > 0) { foreach (Charts chart in chartForms) { chart.AddOrUpdateLimitAlertAnnotation(limitAlert); } } } } else if (type == "status") { GuiEnvironment.LoadingLimitAlerts = true; GuiEnvironment.LimitAlerts.Clear(); // We need to clear the oldestLimitAlertForDeletion limit alert by setting Id to an empty string. GuiEnvironment.oldestLimitAlertForDeletion.Id = ""; XmlNode source = message; int alertCount = source.Property("COUNT", 0); if (null != source) GuiEnvironment.LogMessage("[Limit Alert Status Msg] Cleared alerts and alert count from status is " + alertCount + ", outerxml=" + source.OuterXml); else GuiEnvironment.LogMessage("[Limit Alert Status Msg] Cleared alerts and alert count from status is " + alertCount + ", outerxml= null"); for (int i = 1; i <= alertCount; i++) { LimitAlert limitAlert = GetLimitAlert(source, i); if (null != limitAlert && GuiEnvironment.LimitAlerts.Count < GuiEnvironment.MaxLimitAlerts) GuiEnvironment.LimitAlerts.Add(limitAlert); } GuiEnvironment.LoadingLimitAlerts = false; /* List limitAlertsForms = Application.OpenForms.OfType().ToList(); GuiEnvironment.LogMessage("[Limit Alert Status Msg] Found " + limitAlertsForms.Count + " price alert forms in OpenForms"); if (limitAlertsForms.Count > 0) { foreach (LimitAlertsForm limitAlertsForm in limitAlertsForms) { limitAlertsForm.MergeAlerts(); limitAlertsForm.UpdatePandLCharts(); } } */ if (null != StatusProcessed) StatusProcessed(); UpdateLimitAlertAnnotations(); } else if (type == "removed") { LimitAlert limitAlert = GetLimitAlert(message); if (null != limitAlert) { GuiEnvironment.LimitAlerts.Remove(limitAlert); List limitAlertsForms = Application.OpenForms.OfType().Where(x => !x.IsDisposed).ToList(); if (limitAlertsForms.Count > 0) { foreach (LimitAlertsForm limitAlertsForm in limitAlertsForms) { limitAlertsForm.RemoveLimitAlert(limitAlert); } } List chartForms = Application.OpenForms.OfType().Where(x => !x.IsDisposed && x.Symbol == limitAlert.Symbol).ToList(); if (chartForms.Count > 0) { foreach (Charts chart in chartForms) { chart.RemovedLimitAlertAnnotation(limitAlert.Id); } } // We need to clear the oldestLimitAlertForDeletion limit alert if it was removed by setting Id to an empty string. if (GuiEnvironment.oldestLimitAlertForDeletion.Id == limitAlert.Id) GuiEnvironment.oldestLimitAlertForDeletion.Id = ""; } } else if (type == "curve") { // A user was getting an "Object reference not set to an instance of an object." exception. // We need to check if message is not null. string debugView = type; if (null != message) debugView += message.OuterXml; XmlNode source = message; int alertCount = source.Property("COUNT", 0); if (null != source) GuiEnvironment.LogMessage("[Limit Alert Status Msg] Cleared alerts and alert count from status is " + alertCount + ", outerxml=" + source.OuterXml); else GuiEnvironment.LogMessage("[Limit Alert Status Msg] Cleared alerts and alert count from status is " + alertCount + ", outerxml= null"); for (int i = 1; i <= alertCount; i++) { string intradayNumbers = source.Property("INTRADAY_CURVE" + i, ""); string dailyNumbers = source.Property("DAILY_CURVE" + i, ""); string id = source.Property("ID" + i, ""); try { var matches = GuiEnvironment.LimitAlerts.Where(x => x.Id == id); if (matches.Count() == 1) { LimitAlert limitAlert = matches.First(); if (null != limitAlert) { if (intradayNumbers != "") { Thermograph intradayThermograph = RowDataHelper.GetThermograph(intradayNumbers, 1); limitAlert.IntradayPerformance = intradayThermograph; } if (dailyNumbers != "") { Thermograph dailyThermograph = RowDataHelper.GetThermograph(dailyNumbers, 1); limitAlert.DailyPerformance = dailyThermograph; } } } } catch (Exception e) { string debugView1 = e.StackTrace; } } List limitAlertsForms = Application.OpenForms.OfType().Where(x => !x.IsDisposed).ToList(); if (limitAlertsForms.Count > 0) { foreach (LimitAlertsForm limitAlertsForm in limitAlertsForms) { limitAlertsForm.UpdatePandLCharts(); } } } else { // This is for debugging purposing only. // A user was getting an "Object reference not set to an instance of an object." exception. // We need to check if message is not null. string debugView = type; if (null != message) debugView += message.OuterXml; } } /// /// Loops through open charts and looks for limit alerts for each chart symbol /// private void UpdateLimitAlertAnnotations() { List chartForms = Application.OpenForms.OfType().Where(x => !x.IsDisposed).ToList(); if (chartForms.Count > 0) { foreach (Charts chart in chartForms) { try { List limitAlertsForSymbol = GuiEnvironment.LimitAlerts.Where(x => x.Symbol == chart.Symbol).ToList(); foreach (LimitAlert limitAlert in limitAlertsForSymbol) { chart.AddOrUpdateLimitAlertAnnotation(limitAlert); } } catch (Exception e) { string debugView = e.StackTrace; } } } } /// /// Converts XmlNode message from server into a LimitAlert object. /// /// XmlNode message from server /// Optional alert number. To be used when message type = 'status' which includes multiple limit alerts in the XmlNode message. /// A LimitAlert object. private LimitAlert GetLimitAlert(XmlNode source, int? alertNumber = null) { if (null == source) return null; string propertySuffix = ""; if (alertNumber.HasValue) propertySuffix = alertNumber.Value.ToString(); LimitAlert limitAlert = new LimitAlert(); limitAlert.Symbol = source.Property("SYMBOL" + propertySuffix, ""); limitAlert.Id = source.Property("ID" + propertySuffix, ""); limitAlert.ClientId = source.Property("CLIENT_ID" + propertySuffix, ""); limitAlert.Price = source.Property("PRICE" + propertySuffix, 0.00); limitAlert.IsLong = source.Property("IS_LONG" + propertySuffix, 1) == 1; limitAlert.LongAfter = source.Property("LONG_AFTER" + propertySuffix, 1) == 1; limitAlert.AfterHours = source.Property("AFTER_HOURS" + propertySuffix, 1) == 1; int expires = source.Property("EXPIRES" + propertySuffix, -1); if (expires != -1) limitAlert.Expires = ServerFormats.FromTimeT(expires); double invalidPrice = source.Property("INVALID_PRICE" + propertySuffix, -1.0); if (invalidPrice != -1.0) limitAlert.InvalidPrice = invalidPrice; limitAlert.Notes = source.Property("NOTES" + propertySuffix, ""); int triggered = source.Property("TRIGGERED" + propertySuffix, -1); if (triggered != -1) limitAlert.Triggered = ServerFormats.FromTimeT(triggered); limitAlert.Status = source.Property("STATUS" + propertySuffix, "WORKING"); int created = source.Property("CREATED" + propertySuffix, 0); DateTime? createdDateTime = null; if (created > 0) createdDateTime = ServerFormats.FromTimeT(created); if (createdDateTime.HasValue) limitAlert.Created = createdDateTime.Value; int updated = source.Property("UPDATED" + propertySuffix, 0); DateTime? updatedDateTime = null; if (updated > 0) updatedDateTime = ServerFormats.FromTimeT(updated); if (updatedDateTime.HasValue) limitAlert.Updated = updatedDateTime.Value; return limitAlert; } /// /// Converts a LimitAlert object into a TCL prototype appropriate for sending to the limit alert server. /// /// Limit Alert object. /// If this is a create, set this to true to add a client id to avoid duplication. /// TCL prototype string public static string GetPrototype(LimitAlert limitAlert, bool addClientId) { string toReturn = "symbol " + limitAlert.Symbol + " price " + ServerFormats.ToString(limitAlert.Price) + " is_long " + (limitAlert.IsLong ? "1" : "0"); toReturn += " long_after " + (limitAlert.LongAfter ? "1" : "0"); toReturn += " after_hours " + (limitAlert.AfterHours ? "1" : "0"); if (limitAlert.InvalidPrice.HasValue) toReturn += " invalid_price " + ServerFormats.ToString(limitAlert.InvalidPrice.Value); if (limitAlert.Expires.HasValue) toReturn += " expires " + ServerFormats.ToTimeT(limitAlert.Expires.Value); if (limitAlert.Triggered.HasValue) toReturn += " triggered " + ServerFormats.ToTimeT(limitAlert.Triggered.Value); toReturn += " created " + ServerFormats.ToTimeT(limitAlert.Created); toReturn += " updated " + ServerFormats.ToTimeT(limitAlert.Updated); // setting the Id makes the server try to update an existing limit alert if it exists if (null != limitAlert.Id && limitAlert.Id != "") toReturn += " id " + limitAlert.Id; if (limitAlert.Status != "") toReturn += " status " + limitAlert.Status; if (addClientId) toReturn += " client_id " + Guid.NewGuid().ToString(); string escapedNotes = TclStringEscape(limitAlert.Notes); toReturn += " notes {" + escapedNotes + "}"; return toReturn; } /// /// Escapes special characters in a string to be sent to the server in a safe way. /// /// Text to be escaped. /// Escaped text. private static string TclStringEscape(string text) { string escaped = text.Replace("{", "\\{"); escaped = escaped.Replace("}", "\\}"); return escaped; } /// /// Takes LimitAlert object and sends it to the limit alert server to be created. /// /// LimitAlert to be created public void CreateNewLimitAlert(LimitAlert limitAlert) { object[] message = new object[] { "command", GetFlexCommand(), "subcommand", "la_add_alert", "options", LimitAlertsManager.GetPrototype(limitAlert, true) }; SendLimitAlert(message); } private void SendLimitAlert(object[] message) { Dictionary messageToSend = TalkWithServer.CreateMessage(message); _connectionMaster.SendManager.SendMessage(messageToSend, GetLimitAlertCreationResponse, false, message); } private void GetLimitAlertCreationResponse(byte[] body, object message) { if (null == body) { if (message as object[] != null) SendLimitAlert(message as object[]); } else { string debugView = System.Text.Encoding.UTF8.GetString(body); // response to this command is ignored. If successful, the server sends a message of type "update" } } /// /// Send deletion request to the limit alert server. If successful, the server will return a message of type "removed". /// /// ID of limit alert to be removed. public void DeleteAlert(string id) { object[] message = new object[] { "command", GetFlexCommand(), "subcommand", "la_remove_alert", "id", id }; Dictionary messageToSend = TalkWithServer.CreateMessage(message); _connectionMaster.SendManager.SendMessage(messageToSend); } /// /// Tell the limit alert server to start sending limit alert messages to this client. /// public void StartListening() { object[] message = new object[] { "command", GetFlexCommand(), "subcommand", "la_add_listener" }; GuiEnvironment.LogMessage("[Limit Alerts LimitAlertResponse] Start Listening command sent to server"); Dictionary messageToSend = TalkWithServer.CreateMessage(message); _connectionMaster.SendManager.SendMessage(messageToSend, LimitAlertResponse, true); } private void LimitAlertResponse(byte[] body, object clientId) { if (null == body) StartListening(); else { string debugView = System.Text.Encoding.UTF8.GetString(body); //GuiEnvironment.LogMessage("[Limit Alerts LimitAlertResponse] Got limitalertresponse from server: " + debugView); XmlNode message = XmlHelper.Get(body).Node(0); String type = message.Property("type"); if (null != MessageReceived) MessageReceived(type, message); } } /// /// Tell the limit alert server to stop sending limit alert messages to this client. /// public void StopListening() { object[] message = new object[] { "command", GetFlexCommand(), "subcommand", "la_remove_listener" }; Dictionary messageToSend = TalkWithServer.CreateMessage(message); _connectionMaster.SendManager.SendMessage(messageToSend); } public static string GetFlexCommand() { return GuiEnvironment.AppConfig.Node("GUI_LIB").Node("LIMIT_ALERTS").Node("FLEX_COMMAND").Property("VALUE", "limit_alert_command"); } } }