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;
}
}
}