using System; using System.Collections.Generic; using System.Xml; using TradeIdeas.MiscSupport; using TradeIdeas.ServerConnection; using TradeIdeas.TIProData.Interfaces; using TradeIdeas.XML; namespace TradeIdeas.TIProData { /// /// Unknown means there was some sort of error. Like this alert type does not exist, or /// we haven't received the information on it. All known alerts fall into exactly one /// of the other three categories. Neutral is the default; if you are looking at the raw /// data and you don't see this field, the value is Neutral. /// public enum AlertColorType { Unknown, Bullish, Bearish, Neutral }; /// /// This is a notification when the server sends us data to populate the object. /// /// The object which now has data. public delegate void GeneralInfoReceived(GeneralInfo sender); /// /// This class provides a lot of general information about alerts and filters. This is /// always updated from the server, so we don't need to release a new client each time /// an alert or filter is added. /// /// This class is completely thread safe. You can access it from any thread. You will /// receive responses (if requested) in some arbitrary thread. /// public class GeneralInfo { /// /// This is the event-driven way to know when data is available. /// This event can occur in any thread. And you can change this event in any thread. /// /// public event GeneralInfoReceived Received; /// /// This means that we have received at least one message. This is just a curiosity. It /// does not guarantee that we are up to date. It does not guarantee that we've received /// a response to every request. And there is no need to wait for this. The other methods /// in this class will return a reasonable default if we have no data. /// public bool DataReady { get; private set; } /// /// The last time we got a response from the server, it was a good response. False if the /// last one was an error. /// public bool LastResponseGood { get; private set; } /// /// This is the amount of time since the last good response. It will be a very large value /// if we've never had a good response. /// public TimeSpan LastGoodResponseAge { get { if (null == _lastGoodResponseTime) return TimeSpan.MaxValue; return DateTime.Now - _lastGoodResponseTime.Value; } } /// /// This is the time of the last good response, or null if there has never been a good /// response. We keep this private to avoid a few silly issues, like is this the normal /// DateTime.Now (it is!) or is this the special time that we adjust for the requested /// timezone? /// private DateTime? _lastGoodResponseTime; /// /// Call this at least once to get data. Call it again if you wish. This NOT will automatically /// retry. You might want to call it periodically just to check for updates. /// The old TI Pro code made a similar request about once every 8 hours. /// /// A pointer to (the relevant part of) the server connection. public void RequestNow(ISendManager sendManager) { sendManager.SendMessage(TalkWithServer.CreateMessage("command", "general_info"), Response); } private int _debugCountBad; private int _debugCountGood; private const int ODDSMAKER_DAYS_DEFAULT = 30; private volatile int _oddsmakerDays = ODDSMAKER_DAYS_DEFAULT; private DateTime? _oldestAlert = null; private void Response(byte[] body, object clientId) { if (null == body) { _debugCountBad++; LastResponseGood = false; return; } _debugCountGood++; XmlDocument wholeMessage = XmlHelper.Get(body); XmlNode alertsTypes = wholeMessage.Node(0).Node("ALERT_TYPES"); Dictionary colors = new Dictionary(); Dictionary alertInfo = new Dictionary(); List alertsInOrder = new List(); foreach (XmlNode alertNode in alertsTypes.Enum()) { string internalCode = alertNode.SafeName(); alertsInOrder.Add(internalCode); alertInfo[internalCode] = alertNode; switch (alertNode.Property("DIRECTION")) { case "+": colors[internalCode] = AlertColorType.Bullish; break; case "-": colors[internalCode] = AlertColorType.Bearish; break; default: colors[internalCode] = AlertColorType.Neutral; break; } } _colors = colors; _alertInfo = alertInfo; _alertsInOrder = alertsInOrder.AsReadOnly(); XmlNode exchanges = wholeMessage.Node(0).Node("EXCHANGES"); Dictionary exchangeInfo = new Dictionary(); foreach (XmlNode exchangeNode in exchanges.Enum()) { string internalCode = exchangeNode.Property("CODE"); exchangeInfo[internalCode] = exchangeNode; } _exchangeInfo = exchangeInfo; XmlNode oddsMakerNode = wholeMessage.Node(0).Node("OM_DAY_COUNT").Node("COUNT"); int oldestAlertTimeT = wholeMessage.Node(0).Node("HISTORY").Property("OLDEST_ALERT", 0); if (oldestAlertTimeT > 0) _oldestAlert = ServerFormats.FromTimeT((long)oldestAlertTimeT); _oddsmakerDays = XmlHelper.Property(oddsMakerNode, "DAYS", ODDSMAKER_DAYS_DEFAULT); DataReady = true; LastResponseGood = true; _lastGoodResponseTime = DateTime.Now; GeneralInfoReceived callback = Received; if (null != callback) try { callback(this); } catch { } } private volatile Dictionary _colors = new Dictionary(); /// /// This says if an alert is bullish, bearish, or neutral. Typically the GUI will use this to set /// the color of the alert. /// /// The same codes that we use for help, icons, etc. I.e. "NHP" for new high price. /// A description of the alert, or AlertColorType.Unknown if we don't know about this alert type. public AlertColorType GetAlertColor(string internalCode) { Dictionary colors = _colors; if (null == colors) return AlertColorType.Unknown; if (!colors.ContainsKey(internalCode)) return AlertColorType.Unknown; return colors[internalCode]; } private volatile Dictionary _alertInfo = new Dictionary(); /// /// Look up the user friendly name of the alert. /// /// The same codes that we use for help, icons, etc. I.e. "NHP" for new high price. /// A user friendly name of the alert, or "" if we don't know. Never returns null. public string GetAlertName(string internalCode) { Dictionary alertInfo = _alertInfo; if (null == alertInfo) return ""; if (!alertInfo.ContainsKey(internalCode)) return ""; return alertInfo[internalCode].PropertyForCulture("DESCRIPTION"); } /// /// Look up information about the alert specific filter for this alert type. /// /// The same codes that we use for help, icons, etc. I.e. "NHP" for new high price. /// A user friendly description of this filter, or "" if there is no filter. Never null. public string GetAlertFilter(string internalCode) { Dictionary alertInfo = _alertInfo; if (null == alertInfo) return ""; if (!alertInfo.ContainsKey(internalCode)) return ""; return alertInfo[internalCode].PropertyForCulture("QUALITY_NAME"); } /// /// Look up information on how to display the quality column for this alert type. /// /// The same codes that we use for help, icons, etc. I.e. "NHP" for new high price. /// A code suitable for Delphi's format command. "" means to display nothing. Never null. public string GetQualtyFormat(string internalCode) { Dictionary alertInfo = _alertInfo; if (null == alertInfo) return ""; if (!alertInfo.ContainsKey(internalCode)) return ""; XmlNode node = alertInfo[internalCode]; return node.PropertyForCulture("QUALITY_FORMAT"); } private volatile IList _alertsInOrder; /// /// Get a list of all alert types. The order is the same as is used in the help document, the config /// window, etc. /// /// A list of internal codes for each alert type. Null if we don't have that information. The returned object is read only. public IList GetAlertsInOrder() { return _alertsInOrder; } private volatile Dictionary _exchangeInfo = new Dictionary(); /// /// Looks up the user friendly name of an exchange based on the internal code and the current language. /// /// The normal internal code like "NYSE", "NASD", "$NDX", etc. /// The value to return if we can't find anything better. If you leave this as null, the default will be the internal code. /// The user friendly name. If we can't find a better answer, we return the defaultValue public string GetExchangeName(string internalCode, string defaultValue = null) { if (null == defaultValue) defaultValue = internalCode; Dictionary exchangeInfo = _exchangeInfo; if (null == exchangeInfo) return defaultValue; if (!exchangeInfo.ContainsKey(internalCode)) return defaultValue; return exchangeInfo[internalCode].PropertyForCulture("DESCRIPTION", defaultValue); } /// /// Looks up the user friendly short name of an exchange based on the internal code and the current language. /// /// The normal internal code like "NYSE", "NASD", "$NDX", etc. /// The value to return if we can't find anything better. If you leave this as null, the default will be the internal code. /// The user friendly short name. If we can't find a better answer, we return the defaultValue public string GetShortExchangeName(string internalCode, string defaultValue = null) { if (null == defaultValue) defaultValue = internalCode; Dictionary exchangeInfo = _exchangeInfo; if (null == exchangeInfo) return defaultValue; if (!exchangeInfo.ContainsKey(internalCode)) return defaultValue; string result; // Return short exchange name as Brad requested in Jira PRO-44. switch (internalCode) { case "NYSE": case "ARCA": case "AMEX": case "PINK": result = internalCode; break; case "SMAL": result = "SMALL"; break; case "CAT": result = "TSX"; break; case "OQB": result = "OTCQB"; break; case "OQX": result = "OTCQX"; break; default: result = exchangeInfo[internalCode].PropertyForCulture("DESCRIPTION", defaultValue); break; } return result; } /// /// The maximum number of days for an OddsMaker query. This is the /// number of trading days, not calendar days. So 63 is approximately 3 /// months, not 2. See also the related GetOldestAlert function. /// /// public int GetOddsMakerDays() { return _oddsmakerDays; } /// /// Gets the date of the oldest alert available for history and OddsMaker functions. /// See also the related GetOddsMakerDays function. /// /// public DateTime? GetOldestAlert() { return _oldestAlert; } } }