using System; using System.Collections.Generic; using System.Diagnostics; using System.Text; using System.Threading; using System.Web; using System.Xml; using TradeIdeas.MiscSupport; using TradeIdeas.ServerConnection; using TradeIdeas.TIProData.Interfaces; using TradeIdeas.XML; namespace TradeIdeas.TIProData.Configuration { // This gives you all of the data and functionality you'd need to build your own configuration // window. This does not include a GUI, but all of the tools you'd need to build your own GUI. /// /// The functions in this class exist in version 4 of the framework, but not 3.5. /// I recreated them here. /// static public class OldCodeHelper { /// /// Converts an IList to an Array. /// /// Any type. /// The list to copy. /// A new array. static public T[] ToArray(this IList asIList) { T[] result = new T[asIList.Count]; asIList.CopyTo(result, 0); return result; } /// /// Convert a HashSet to an array. /// /// Any type. /// The HashSet to copy. /// A new array. static public T[] ToArray(this HashSet asHashSet) { T[] result = new T[asHashSet.Count]; asHashSet.CopyTo(result, 0); return result; } /// /// Check if the Key is in the HashSet. /// /// Needle /// Haystack /// True if and only if the key is in the set. /// static public bool Contains(this HashSet HashSet, object Key) where T : class { return HashSet.Contains(Key as T); } } /// /// Strategies are normally stored in short strings like O=3_41D_0... (It's hard to add /// an ampersand to these documentation comments!) /// The only approved way to parse a string like that is to send it to the server. The /// result will be a PrepairedStrategy object. You should not keep these very long. /// There's no specific expiration date, but the preferred way to do it is to get a new /// copy from the server every time you open a config window. Some things change more /// often than others. New filters and alerts are typically added once a month or so, but /// there is no specific notification of that event. Symbol lists can be changed several /// times a second, if the user so desired. The client library and the server are both /// robust enough to handle old strategies, even if lots of things have changed. /// /// We make these objects completely available for the main program to alter or create /// from scratch. These object are also used for sending strategies back to the server. /// Note that there is very little error checking in here. Many things will be ignored /// or corrected by the server. /// /// This base class contains the items common to alert windows and top list windows. In /// the web server we had a third type of strategy, an end of day scan. I think that is /// obsolete and will not be moved to this client. /// public class PrepairedStrategy { /// /// This is a name suitable for showing in a list of strategies. It is often the same /// as the window name, but sometimes shorter. /// public string Name = ""; /// /// This is the name which should show up in the title bar of the alert window. /// Others GUIs present it in other ways, but it's almost always available to the /// end user. /// public string WindowName = ""; /// /// This maps filters to their values. Note that the value is a string. It almost /// always should be an a intereger or floating point number, with a period as the /// decimal point. I.e. a number stored using the invariant culture. /// /// Note: TI Pro allows users to enter numbers using the local culture. The /// conversion between the local culture and the invariant culture is done elsewhere. /// /// Note: The values are stored as strings for historical reasons. In addition /// to numbers, there are a few magic values that the server will recognize. These /// are mostly obsolete. Use custom filters instead of these magic codes. /// http://www.trade-ideas.com/FormulaEditor/List.html /// /// Setting a filter value to the empty string is the same as deleting the /// filter from this Dictionary. The cannonical form is to delete the value. /// public Dictionary MinFilters = new Dictionary(); /// /// This maps filters to their values. Note that the value is a string. It almost /// always should be an a intereger or floating point number, with a period as the /// decimal point. I.e. a number stored using the invariant culture. /// /// Note: TI Pro allows users to enter numbers using the local culture. The /// conversion between the local culture and the invariant culture is done elsewhere. /// /// Note: The values are stored as strings for historical reasons. In addition /// to numbers, there are a few magic values that the server will recognize. These /// are mostly obsolete. Use custom filters instead of these magic codes. /// http://www.trade-ideas.com/FormulaEditor/List.html /// /// Setting a filter value to the empty string is the same as deleting the /// filter from this Dictionary. The cannonical form is to delete the value. /// public Dictionary MaxFilters = new Dictionary(); /// /// This is the set of exchanges that the user wants to see. Delete an exchange /// from here to hide all stocks listed on that exchange. /// public HashSet Exchanges = new HashSet(); /// /// There are different ways to use symbol lists, i.e. exclude vs include. /// See the TI Pro GUI for more. /// public SymbolListDisposition SymbolListDisposition; /// /// These are the symbol lists associated with the strategy. This field might or /// might not be used depending on the SymbolListDisposition. /// public HashSet SymbolLists = new HashSet(); /// /// These are the columns we are requesting. This data should appear with each alert /// or top list entry. /// public List Columns = new List(); /// /// This is the only symbol we want to see. This might or might not be used /// depending on the SymbolListDisposition. /// public string SingleSymbol = ""; /// /// This is a string that represents the request. The server usually populates /// this when requests come from the server. This value is *not* automatically /// updated as we change other fields. Use MakeConfigString() instead. /// public string OriginalConfigString = ""; /// /// Many of the sample strategies will include English text describing the strategy. /// This is aimed at the end user. /// public string Description = ""; /// /// This indicates a good icon to display. Common values include "+", "-", "!", /// "*", and ":)". TI Pro uses "!" if this is blank or an unknown value. /// public string Icon = ""; /// /// This is the age of a recently used strategy. Note that this is a static value, /// which is only updated when the server sends us a message. This will be null /// for some strategies. /// /// This field is somewhat redundant. The description will also tell us the age, /// when that information is relevant. TI Pro uses this information to translate /// that message into other languages. /// public int? Age = null; /// /// This generates a new config string based on the current values in this object. /// This string is appropriate to send to the server to request alerts or a top /// list. Note that there are multiple formats for this information. The string /// generated by this function will often be longer than the version given to us /// by the server. Note: It is a bad idea to try to parse this value yourself. /// The only approved way to parse a config string is to call /// ConfigurationWindowManager.LoadFromServer(). /// /// A description of the strategy suitable for sending to the server. public string MakeConfigString() { StringBuilder result = new StringBuilder("form=1"); MakeConfigString(result); return result.ToString(); } /// /// Get the part of the config string associated with the requested columns. /// This is primarialy for internal use. /// /// A fragment of a config string. public string GetColumnConfig() { StringBuilder result = new StringBuilder(); GetColumnConfig(result); return result.ToString(); } /// /// Get the part of the config string associated with the requested columns. /// This is primarialy for internal use. /// /// The results are appended to this. private void GetColumnConfig(StringBuilder result) { int index = 0; foreach (IDisplayColumn column in Columns) { result.Append("&show"); result.Append(index); index++; result.Append('='); result.Append(column.InternalCode); } // Add a blank one at the end. This allows us to append a new set of columns, possibly // overwriting the first set. // For example, if we start with // O=2_41D_0&MinTV=1000000&show0=RV&WN=New+Lows+&show0=Price&show1=TV&show2=RV&show3=RD&show4=Wiggle // (that might come out of the config window the normal way we use it.) // and we add // &show0=Price&show1= // to the end to get // O=2_41D_0&MinTV=1000000&show0=RV&WN=New+Lows+&show0=Price&show1=TV&show2=RV&show3=RD&show4=Wiggle&show0=Price&show1= // the server will interpret that as // O=2_41D_0&MinTV=1000000&show0=RV&WN=New+Lows+&show0=Price&show1= result.Append("&show"); result.Append(index); index++; result.Append('='); } /// /// Build the config string. This is overloaded by the AlertStrategy and TopListStrategy /// classes. This particular function handles the parts of the string common to both /// types of strategies. /// /// The output is appended to this. protected virtual void MakeConfigString(StringBuilder result) { foreach (KeyValuePair pair in MinFilters) if (pair.Value != "") { result.Append('&'); result.Append(pair.Key.MinCode); result.Append('='); result.Append(pair.Value); } foreach (KeyValuePair pair in MaxFilters) if (pair.Value != "") { result.Append('&'); result.Append(pair.Key.MaxCode); result.Append('='); result.Append(pair.Value); } foreach (Exchange exchange in Exchanges) { result.Append('&'); result.Append(exchange.InternalCode); result.Append("=on"); } bool needToSendSymbols = false; switch (SymbolListDisposition) { case SymbolListDisposition.All: // We could say EntireUniverse=1, but the server will automatically figure that out // when we don't send any symbol lists. That option mostly exists for HTML forms // which can send conflicting info. If the server sees EntireUniverse=1 and it // sees symbols lists, it will ignore the symbol lists. needToSendSymbols = false; break; case SymbolListDisposition.OnlyThese: // We could say EntireUniverse= (blank) but that's the default case. needToSendSymbols = true; break; case SymbolListDisposition.ExcludeThese: result.Append("&EntireUniverse=2"); needToSendSymbols = true; break; case SymbolListDisposition.SingleSymbol: result.Append("&EntireUniverse=3"); needToSendSymbols = false; result.Append("&SingleSymbol="); result.Append(Quote(SingleSymbol)); break; } if (needToSendSymbols) foreach (SymbolList symbolList in SymbolLists) { result.Append('&'); result.Append(symbolList.InternalCode); result.Append("=on"); } if (WindowName != "") { result.Append("&WN="); result.Append(Quote(WindowName)); } // Add Column information to the end of the config string GetColumnConfig(result); // Column version so that the server knows not to send default display only fields // the client is requesting exactly what they want. result.Append("&col_ver=1"); } private String Quote(String original) { // UrlPathEncode appears to convert the string to bytes, using UTF-8, then encodes the results. // UrlPathEncode is similar to PHP's rawurlencode, converting space to %20, where UrlEncode // converts space to +. I would prefer the former, but for some reason UrlPathEncode doesn't // quote the ampersand. UrlPathEncode leaves the ampersand alone. return HttpUtility.UrlEncode(original); } /// /// Make a deep copy of the other PrepairedStrategy. /// /// The original we want to copy. protected PrepairedStrategy(PrepairedStrategy from) { Name = from.Name; WindowName = from.WindowName; MinFilters = new Dictionary(from.MinFilters); MaxFilters = new Dictionary(from.MaxFilters); Exchanges = new HashSet(from.Exchanges); SymbolListDisposition = from.SymbolListDisposition; SymbolLists = new HashSet(from.SymbolLists); SingleSymbol = from.SingleSymbol; Columns = new List(from.Columns); OriginalConfigString = from.OriginalConfigString; Description = from.Description; Icon = from.Icon; Age = from.Age; } /// /// Start with a completely empty strategy. Nothing is selected. /// public PrepairedStrategy() { } /// /// Read a strategy from the server. /// /// The data from the server. /// The ConfigurationWindowManager responsible for this request. protected PrepairedStrategy(XmlNode xmlNode, ConfigurationWindowManager configurationWindowManager) { Name = xmlNode.Property("NAME"); OriginalConfigString = xmlNode.Property("SETTINGS"); Icon = xmlNode.Property("ICON"); Description = xmlNode.Property("DESCRIPTION"); XmlNode configNode = xmlNode.Node("CONFIG"); WindowName = configNode.Property("WINDOW_NAME"); XmlNode filtersNode = configNode.Node("WINDOW_SPECIFIC_FILTERS"); foreach (XmlNode filterNode in filtersNode.Enum()) { Filter filter; bool max; configurationWindowManager.FindFilter(filterNode.LocalName, out filter, out max); if (filter != null) { string value = filterNode.Property("VALUE"); if (max) MaxFilters.Add(filter, value); else MinFilters.Add(filter, value); } } XmlNode exchangesNode = configNode.Node("EXCHANGES"); foreach (XmlNode exchangeNode in exchangesNode.Enum()) { Exchange exchange = configurationWindowManager.FindExchange(exchangeNode.LocalName); if (exchange != null) Exchanges.Add(exchange); } XmlNode symbolListsNode = configNode.Node("SYMBOL_LISTS"); bool needToLoadSymbols = false; switch (symbolListsNode.Property("MODE")) { case "single": SingleSymbol = symbolListsNode.Property("SYMBOL"); if (SingleSymbol == "") SymbolListDisposition = SymbolListDisposition.All; else SymbolListDisposition = SymbolListDisposition.SingleSymbol; break; case "only": SymbolListDisposition = SymbolListDisposition.OnlyThese; needToLoadSymbols = true; break; case "exclude": SymbolListDisposition = SymbolListDisposition.ExcludeThese; needToLoadSymbols = true; break; default: SymbolListDisposition = SymbolListDisposition.All; break; } if (needToLoadSymbols) foreach (XmlNode symbolListNode in symbolListsNode) { SymbolList symbolList = configurationWindowManager.FindSymbolList(symbolListNode.LocalName); if (symbolList != null) SymbolLists.Add(symbolList); } XmlNode columnsNode = configNode.Node("COLUMNS"); string clientCookie = configNode.Node("CLIENT_COOKIE").Property("VALUE"); // TODO -- Use this! foreach (XmlNode columnNode in columnsNode.Enum()) { IDisplayColumn column = configurationWindowManager.FindFilter(columnNode.Property("BASE")); if (column != null) { // If we can't find the type in our lists, silently ignore it. That // should never happen, but we are at the mercy of the server. Columns.Add(column); } else { column = configurationWindowManager.FindDisplayOnlyField(columnNode.Property("BASE")); if (column != null) { // If we can't find the type in our lists, silently ignore it. That // should never happen, but we are at the mercy of the server. Columns.Add(column); } } } Age = xmlNode.Property("AGE", -1); if (Age < 0) Age = null; } } /// /// This represents a single strategy for an alert window. /// public class AlertStrategy : PrepairedStrategy { /// /// These are the alerts the user wants to see. /// public HashSet Alerts = new HashSet(); /// /// These are the quality settings for each alert. These should be numbers converted /// to a string using the invariant locale. This is a string for historical reasons. /// The server understands a few magic codes other than numbers. /// /// TI Pro allows users to enter numbers with the local culture settings. Other parts /// of the TI Pro application convert between the numbers that the user sees (i.e. "1,25") /// and the number stored here (i.e. "1.25" for one and a quarter). /// /// These magic codes are obsolete. See /// http://www.trade-ideas.com/FormulaEditor/List.html /// and look for the "quality" field. /// /// These can be missing or set to the empty string if you do not want to use a filter. /// public Dictionary AlertQuality = new Dictionary(); /// /// Create a blank strategy with nothing selected. /// public AlertStrategy() { } /// /// Create a deep copy of the given strategy. /// /// It is safe to copy from a top list strategy to an alert strategy. In that /// case, the missing fields will have their default values, and the extra /// fields will be ignored. /// /// Copy from this. public AlertStrategy(PrepairedStrategy from) : base(from) { AlertStrategy fromAlerts = from as AlertStrategy; if (null != fromAlerts) { Alerts = new HashSet(fromAlerts.Alerts); AlertQuality = new Dictionary(fromAlerts.AlertQuality); } } /// /// Read a strategy from the server. /// /// The description from the server. /// The ConfigurationWindowManager which made this request. internal AlertStrategy(XmlNode xmlNode, ConfigurationWindowManager configurationWindowManager) : base(xmlNode, configurationWindowManager) { XmlNode alertsNode = xmlNode.Node("CONFIG").Node("ALERT_TYPES"); foreach (XmlNode alertNode in alertsNode.Enum()) { Alert alert = configurationWindowManager.FindAlert(alertNode.LocalName); if (alert != null) { // If we can't find the alert type in our lists, silently ignore it. That // should never happen, but we are at the mercy of the server. Alerts.Add(alert); string quality = alertNode.Property("QUALITY", null); if (quality != null) AlertQuality.Add(alert, quality); } } } /// /// Convert the current fields in this object into a complete config string. /// /// This protected function does most of the work. But the user should call /// PrepairedStrategy.MakeConfigString() (with no arguments) to get a result. /// /// protected override void MakeConfigString(StringBuilder result) { foreach (Alert alert in Alerts) { result.Append("&Sh_"); result.Append(alert.InternalCode); result.Append("=on"); string quality; if (AlertQuality.TryGetValue(alert, out quality) && (quality != "")) { result.Append("&Q"); result.Append(alert.InternalCode); result.Append('='); result.Append(quality); } } base.MakeConfigString(result); } } /// /// This represents a single strategy for a top list window. /// public class TopListStrategy : PrepairedStrategy { /// /// Which field are we sorting by? This defaults to null on the client side. If /// the strategy is sent to the server with no value here, the server will provide /// a default. /// public Filter SortBy = null; // Unfortunately there is not a good default for this. /// /// Sort order. /// public bool BiggestOnTop = true; /// /// Default is 100. Current server maximum is 1000. Tells the server to return /// this many records for the top list. /// public int MaxRecords = 100; /// /// Use historical mode or live. Note: History means that the user picks a specific /// time. That is different from delayed data. If the user sets history to false, /// the server will decide wether to use live data or delayed data. Live and delayed /// queries will give a series of responses. Historical queries will only yield one /// response. /// public bool History = false; /// /// This is only meaningful when History is false. By default (when this is false) the /// server will only give us data between the open and the close. If you ask for data /// at other times, you will get a vale from the most recent close. False is a reasonable /// default based on the way most people use market data. /// public bool OutsideMarketHours = false; /// /// This is only meaningful if History is true. Get data from this date and time. /// public DateTime Time; /// /// All defaults. All filters are blank, etc. /// public TopListStrategy() { } /// /// Do a deep copy of the other strategy. Note that the other strategy can be /// a top list or an alert strategy. If the other strategy is an alert strategy, /// the common fields are copied. If a field does not exist in the original, /// the copy gets the default value. If there are extra fields in the original, /// those are ignored. /// /// Make a copy of this. public TopListStrategy(PrepairedStrategy from) : base(from) { TopListStrategy fromTopList = from as TopListStrategy; if (null != fromTopList) { SortBy = fromTopList.SortBy; BiggestOnTop = fromTopList.BiggestOnTop; History = fromTopList.History; Time = fromTopList.Time; OutsideMarketHours = fromTopList.OutsideMarketHours; MaxRecords = fromTopList.MaxRecords; } } /// /// Read a strategy from the server. /// /// The data from the server. /// The ConfigurationWindowManager making the request. internal TopListStrategy(XmlNode xmlNode, ConfigurationWindowManager configurationWindowManager) : base(xmlNode, configurationWindowManager) { XmlNode configNode = xmlNode.Node("CONFIG"); configurationWindowManager.FindFilter(configNode.Property("SORT"), out SortBy, out BiggestOnTop); int timeT = configNode.Property("HISTORY", 0); DateTime? historyTime = null; if (timeT > 0) historyTime = ServerFormats.FromTimeT(timeT); MaxRecords = configNode.Property("COUNT", 100); History = (historyTime != null); if (History) Time = (DateTime)historyTime; else OutsideMarketHours = configNode.Property("OUTSIDE_MARKET_HOURS") == "1"; } /// /// Convert the current fields in this object into a complete config string. /// /// This protected function does most of the work. But the user should call /// PrepairedStrategy.MakeConfigString() (with no arguments) to get a result. /// /// protected override void MakeConfigString(StringBuilder result) { if (null != SortBy) { result.Append("&sort="); result.Append(BiggestOnTop?SortBy.MaxCode:SortBy.MinCode); } if (History) { result.Append("&hist=1&exact_time="); result.Append(ServerFormats.ToTimeT(Time)); } else if (OutsideMarketHours) { result.Append("&omh=1"); } result.Append("&count=" + ServerFormats.ToString(MaxRecords)); base.MakeConfigString(result); } } /// /// How to use symbol lists. See the TI Pro config window to see what this means. /// Other fields might or might not be used based on this value. /// public enum SymbolListDisposition { All, OnlyThese, ExcludeThese, SingleSymbol }; /// /// This represents a symbol list in the context of a config window. This is only /// used to select symbol lists. A completely different object is used to modify /// symbol lists. /// public class SymbolList { private const char PATH_DELIMITER = '|'; /// /// This is a code we need to give back to the server if we want to use this symbol list. /// public readonly string InternalCode; /// /// This is a user friendly description of the symbol list. This is typically created /// by the end user, so it can be anything. /// public readonly string Description; /// /// This allows you to create a symbol list object. It is easier to create the symbol lists /// in advance, and then pick one from the ConfigurationWindowManager object. This constructor /// is used by TI Pro when we create a symbol list directly in the configuration window, rather /// than in the symbol lists window. /// /// See the ListManager class for the easier way to create symbol lists. /// /// A name to show the user. /// A unique number identifying the symbol list on the server. /// 0 (the default) for the current user or the id of another user. public SymbolList(string name, int listId, int owner = 0) { // owner=0 means the current user. I.e. one of his own lists. Anything // else would be for a shared list. Debug.Assert((listId > 0) && (owner >= 0)); InternalCode = "SL_" + owner + "_" + listId; Description = name; } /// /// Read a symbol list description from the server. /// /// The data sent by the server. internal SymbolList(XmlNode node) { InternalCode = node.LocalName; Description = node.Property("NAME"); } /// /// Returns the actual symbol list name. This parses the name from the full folder path. /// For example, a list with the description of "Daily | Doji" returns "Doji". /// public string ListName { get { string[] path = Description.Split(PATH_DELIMITER); return path[path.Length - 1].Trim(); } } /// /// Returns a list with the folder names in order. /// For example, a list with the description "Daily | Candlesticks | Dojis" returns { Daily, Candlesticks } /// public List FolderPath { get { string[] path = Description.Split(PATH_DELIMITER); List folderPath = new List(); for (int i = 0; i < path.Length - 1; i++) { folderPath.Add(path[i].Trim()); } return folderPath; } } /// /// This object uses the standard C# semantics for a read only object, with a slight /// twist. We only look at the internal code, because that's the only part that the /// server cares about. The user friendly description can change at any time, and /// that doesn't really matter. /// /// The right hand side. /// True if the two objects are identical, as far as the server can tell. public bool Equals(SymbolList other) { if ((object)other == null) return false; return (InternalCode == other.InternalCode); } /// /// This object uses the standard C# semantics for a read only object, with a slight /// twist. We only look at the internal code, because that's the only part that the /// server cares about. The user friendly description can change at any time, and /// that doesn't really matter. /// /// The right hand side. /// True if the two objects are identical, as far as the server can tell. public override bool Equals(Object obj) { return Equals(obj as SymbolList); } /// /// This object uses the standard C# semantics for a read only object, with a slight /// twist. We only look at the internal code, because that's the only part that the /// server cares about. The user friendly description can change at any time, and /// that doesn't really matter. /// /// The left hand side. /// The right hande side. /// True if the two objects are identical, as far as the server can tell. public static bool operator ==(SymbolList a, SymbolList b) { // If both are null, or both are same instance, return true. if (System.Object.ReferenceEquals(a, b)) { return true; } // If the first is null, return false. if ((object)a == null) { return false; } // Return true if the fields match: return a.Equals(b); } /// /// This object uses the standard C# semantics for a read only object, with a slight /// twist. We only look at the internal code, because that's the only part that the /// server cares about. The user friendly description can change at any time, and /// that doesn't really matter. /// /// The left hand side. /// The right hand side. /// True if the server would think these objects are different. public static bool operator !=(SymbolList a, SymbolList b) { return !(a == b); } /// /// The hash code is computed in a manner consistent with Equals(). /// /// The hash code. public override int GetHashCode() { return InternalCode.GetHashCode(); } /// /// This is definately aimed at debugging. End users never see the internal id. /// /// public override string ToString() { return InternalCode + ": '" + Description + "'"; } } /// /// The config window includes a tree of recommended strategies. This structure is very /// flexible. The server can generate this in a number of ways. TI personnel can change /// the recommend strategies. And different users are part of differnt groups who might /// have different strategies. The strategies are often layed out different ways for /// different groups. In short, don't assume anything about the layout of these strategies. /// public interface StrategyNode { /// /// A folder has children but does not directly contain a strategy. If folder is /// false, this contains a strategy and no children. /// /// True if this is a folder. bool IsFolder(); /// /// A short name that can be displayed for the user. This might be copied from the /// strategy. /// string Name { get; } /// /// A longer text description that can be displayed for the user. This might be copied /// from the strategy. /// string Description { get; } /// /// An icon copied from the strategy or the special string "folder". /// string Icon { get; } // TODO: Shouldn't "folder" be a constant? /// /// This returns a PrepairedStrategy object, or null if there is none. /// PrepairedStrategy PrepairedStrategy { get; } /// /// This returns an AlertStrategy object. This function will return null if this /// object contains a folder or a top list. /// AlertStrategy AlertStrategy { get; } /// /// This returns a TopListStrategy object. This function will return null if this /// object contains a folder or an alert strategy. /// TopListStrategy TopListStrategy { get; } /// /// This will return a list of StrategyNode objects. This function will return /// null if this object is not a folder. /// IEnumerable Children { get; } /// /// The "start from scratch" node is always specially tagged. /// This is sometimes useful to start a config window, but you /// shouldn't be able to start an alert or top list window /// directly from this strategy. This also applies to the /// current settings. If say you want to change the current /// settings, we disable the okay button until you actually /// change something. /// bool UserMustModify { get; } } /// /// These objects represent the check boxs on the config window that allow you to select what alerts to watch. /// For example Running up would be an Alert. These objects are *not* indivdual messages, like "DELL was running /// up today at 3:20." /// public class Alert { /// /// This is the code we use to find the icon, the on line help, etc. /// public readonly string InternalCode; /// /// This is a user friendly description of the alert. /// public readonly string Description; /// /// If this is true, then the user has the option of setting the quality filter. If this is false, /// you can still try to set the quality filter, but the server will ignore it because it makes no /// sense. /// /// public bool HasFilter() { return FilterDescription != null; } /// /// This is a user friendly description of the alert filter. Typically this is the units for the /// filter, like "Shares" or "$". This is null if there is no filter. /// public readonly string FilterDescription; /// /// Create exactly this alert. /// /// This is the code we use to find the icon, the on line help, etc. /// This is a user friendly description of the alert. /// This is a user friendly description of the alert filter, or null if there is no filter for this alert. internal Alert(string internalCode, string description, string filterDescription) { InternalCode = internalCode; Description = description; if (filterDescription != "") FilterDescription = filterDescription; } /// /// Create an alert based on a message from the server. /// /// The data from the server. internal Alert(XmlNode node) { InternalCode = node.LocalName; Description = node.Property("DESCRIPTION"); FilterDescription = node.Property("QUALITY_NAME"); if (FilterDescription == "") FilterDescription = null; } /// /// We compare the internal codes to see if the two objects are the same. /// (If those are the same and other fields are different, it's probably because the user /// switched to a different language. Having the same internal code means that the /// server will treat these two as identcal.) /// /// The right hand side. /// True if the server would consider these two to be the same. public bool Equals(Alert other) { // The whole point of the internal code is to be unique. The server should never send us two // different definitions with the same internal code. Of course, if it does, we should handle // the case gracefully, but there's no specific meaning to that case. if ((object)other == null) return false; return (InternalCode == other.InternalCode); } /// /// We compare the internal codes to see if the two objects are the same. /// (If those are the same and other fields are different, it's probably because the user /// switched to a different language. Having the same internal code means that the /// server will treat these two as identcal.) /// /// The right hand side. /// True if the server would consider these two to be the same. public override bool Equals(Object obj) { return Equals(obj as Alert); } /// /// We compare the internal codes to see if the two objects are the same. /// (If those are the same and other fields are different, it's probably because the user /// switched to a different language. Having the same internal code means that the /// server will treat these two as identcal.) /// /// Left hand side. /// Right hand side. /// True if the server would consider these two to be the same. public static bool operator ==(Alert a, Alert b) { // If both are null, or both are same instance, return true. if (System.Object.ReferenceEquals(a, b)) { return true; } // If the first is null, return false. if ((object)a == null) { return false; } // Return true if the fields match: return a.Equals(b); } /// /// We compare the internal codes to see if the two objects are the same. /// (If those are the same and other fields are different, it's probably because the user /// switched to a different language. Having the same internal code means that the /// server will treat these two as identcal.) /// /// Left hand side. /// Right hand side. /// True if the server would consider these two to be the different. public static bool operator !=(Alert a, Alert b) { return !(a == b); } /// /// This generates a hash code consistant with our defintion of Equals(). /// /// The hash code. public override int GetHashCode() { return InternalCode.GetHashCode(); } /// /// This is definately aimed at debugging. End users should never see the internal id. /// /// The values of the fields in a format appropriate for a developer. public override string ToString() { StringBuilder result = new StringBuilder(InternalCode); result.Append(": '"); result.Append(Description); if (HasFilter()) { result.Append("' ('"); result.Append(FilterDescription); result.Append("')"); } else { // No quality filter) result.Append('\''); } return result.ToString(); } } /// /// Things that can be displayed as a column. Filters and Display Only Fields. /// public interface IDisplayColumn { /// /// This is the code we send back to the server in some places. e.g. Price, D_Desc. /// string InternalCode { get; } /// /// This is a user friendly description of the column. /// string Description { get; } } /// /// Each of these objects represents a pair of filters, i.e. MinPrice and MaxPrice. /// public class Filter : IDisplayColumn { //private string _internalCode; /// /// This is the code we send back to the server in some places. e.g. Price, D_Desc. /// public string InternalCode { get; private set; } /// /// This is a user friendly description of the column. /// public string Description { get; private set; } /// /// This is a user friendly description of the units for this field. I.e. "$" or "shares". /// public readonly string Units; /// /// Something like "Price ($)" or "Average Volume (Shares)". /// public string LongDescription { get { if ((null == Units) || (Units == "")) return Description; else return Description + " (" + Units + ")"; } } /// /// This is the internal code used when talking about the min filter in this pair. I.e. MinPrice. /// public readonly string MinCode; /// /// This is the internal code used when talking about the max filter in this pair. I.e. MaxPrice. /// public readonly string MaxCode; /// /// Create a filter with exactly these values. /// /// This is the code we send back to the server in some places. /// This is a user friendly description of the filter. /// This is a user friendly description of the units for this field. internal Filter(string internalCode, string description, string units) { Units = units; InternalCode = internalCode; Description = description; MinCode = "Min" + InternalCode; MaxCode = "Max" + InternalCode; } /// /// Create a filter based on a message from the server. /// /// The data from the server. internal Filter(XmlNode node) { InternalCode = node.Property("BASE"); Description = node.Property("DESCRIPTION"); Units = node.Property("UNITS"); MinCode = "Min" + InternalCode; MaxCode = "Max" + InternalCode; // "TYPE" finally died. That was part of the change to use paired filters. } /// /// We compare the internal codes to see if the two objects are the same. /// (If those are the same and other fields are different, it's probably because the user /// switched to a different language. Having the same internal code means that the /// server will treat these two as identcal.) /// /// The right hand side. /// True if the server would consider these two to be the same. public bool Equals(Filter other) { // The whole point of the internal code is to be unique. The server should never send us two // different definitions with the same internal code. Of course, if it does, we should handle // the case gracefully, but there's no specific meaning to that case. if ((object)other == null) return false; return (InternalCode == other.InternalCode); } /// /// We compare the internal codes to see if the two objects are the same. /// (If those are the same and other fields are different, it's probably because the user /// switched to a different language. Having the same internal code means that the /// server will treat these two as identcal.) /// /// The right hand side. /// True if the server would consider these two to be the same. public override bool Equals(Object obj) { return Equals(obj as Filter); } /// /// We compare the internal codes to see if the two objects are the same. /// (If those are the same and other fields are different, it's probably because the user /// switched to a different language. Having the same internal code means that the /// server will treat these two as identcal.) /// /// Left hand side. /// Right hand side. /// True if the server would consider these two to be the same. public static bool operator ==(Filter a, Filter b) { // If both are null, or both are same instance, return true. if (System.Object.ReferenceEquals(a, b)) { return true; } // If the first is null, return false. if ((object)a == null) { return false; } // Return true if the fields match: return a.Equals(b); } /// /// We compare the internal codes to see if the two objects are the same. /// (If those are the same and other fields are different, it's probably because the user /// switched to a different language. Having the same internal code means that the /// server will treat these two as identcal.) /// /// Left hand side. /// Right hand side. /// True if the server would consider these two to be the different. public static bool operator !=(Filter a, Filter b) { return !(a == b); } /// /// This generates a hash code consistant with our defintion of Equals(). /// /// The hash code. public override int GetHashCode() { return InternalCode.GetHashCode(); } /// /// This is definately aimed at debugging. End users should never see the internal id. /// /// The values of the fields in a format appropriate for a developer. public override string ToString() { StringBuilder result = new StringBuilder(InternalCode); result.Append(": '"); result.Append(Description); result.Append("' ('"); result.Append(Units); result.Append("')"); return result.ToString(); } } /// /// These objects represent fields that can be displayed as columns but aren't /// filters. Symbol, Sector, Description, etc... /// public class DisplayOnlyField : IDisplayColumn { /// /// This is the code we send back to the server in some places. e.g. Price, D_Desc. /// public string InternalCode { get; private set; } /// /// This is a user friendly description of the column. /// public string Description { get; private set; } /// /// Create a display only field with exactly these values. /// /// This is the code we send back to the server in some places. /// This is a user friendly description of the field. public DisplayOnlyField(string internalCode, string description) { InternalCode = internalCode; Description = description; } /// /// Create a display field based on a message from the server. /// In some cases we create a column which is completely client side. /// In that case the XML would come from Common.xml or a similar place. /// /// The data from the server. public DisplayOnlyField(XmlNode node) { InternalCode = node.Property("BASE"); Description = node.PropertyForCulture("DESCRIPTION"); } /// /// This is definately aimed at debugging. End users should never see the internal id. /// /// The values of the fields in a format appropriate for a developer. public override string ToString() { StringBuilder result = new StringBuilder(InternalCode); result.Append(": '"); result.Append(Description); result.Append("'"); return result.ToString(); } } /// /// These objects represent exchanges, e.g. NASDAQ, NYSE, XETRA. /// You can filter the stocks based on the exchange on which they are listed. /// public class Exchange { /// /// This is the code we send back to the server in some places. e.g. Price. /// public readonly string InternalCode; /// /// This is a user friendly description of the exchange. /// public readonly string Description; /// /// If this is true, the user cannot see data for stocks listed on this exchange. /// This typically means that the user did not fill out all of his paperwork, /// and we want to bring that to the user's attention. However, there could be /// other reasons for that. Some professional users, in particular, will purposely /// disable an exchange because they pay higher exchange fees than most people. /// public readonly bool Disabled; internal Exchange(string internalCode, string description, bool disabled) { InternalCode = internalCode; Description = description; Disabled = disabled; } internal Exchange(XmlNode node) { InternalCode = node.LocalName; Description = node.Property("DESCRIPTION"); Disabled = node.Property("DISABLED") == "1"; } public bool Equals(Exchange other) { // The whole point of the internal code is to be unique. The server should never send us two // different definitions with the same internal code. Of course, if it does, we should handle // the case gracefully, but there's no specific meaning to that case. if ((object)other == null) return false; return (InternalCode == other.InternalCode); } public override bool Equals(Object obj) { return Equals(obj as Exchange); } public static bool operator ==(Exchange a, Exchange b) { // If both are null, or both are same instance, return true. if (System.Object.ReferenceEquals(a, b)) { return true; } // If the first is null, return false. if ((object)a == null) { return false; } // Return true if the fields match: return a.Equals(b); } public static bool operator !=(Exchange a, Exchange b) { return !(a == b); } public override int GetHashCode() { return InternalCode.GetHashCode(); } /// /// This is definately aimed at debugging. End users should never see the internal id. /// /// The values of the fields in a format appropriate for a developer. public override string ToString() { StringBuilder result = new StringBuilder(InternalCode); result.Append(": '"); result.Append(Description); result.Append('\''); if (Disabled) result.Append(" DISABLED"); return result.ToString(); } } /// /// What type of window are we trying to configure. Currently there are two choices: /// alerts or top list. /// public enum ConfigurationType { Alerts, TopList, MultiStrategy, CustomColumns }; /// /// This class is responsible for talking with the server. Typically each time the user /// opens the config window we will create one of these and send the request. The server /// will give us a list of available options. Some change more often than others. For /// example new alerts or filters are added once or twice a month on average, but new /// symbol lists could be added as often as the user chooses. /// /// In addion to the basic components, like alerts and filters, this contains some /// premade strategies. These can come from a variety of places, such as the user's /// personal history, or a Trade-Ideas employee. /// /// For the most part, all of the functionality of TI Pro's config window appears in /// this class, either directly, or through objects provided by this class. However, /// this does not contain or require any GUI. This could allow the user to manipulate /// a configuration completely programatically. /// public class ConfigurationWindowManager { /// /// Alerts or a top list. /// public ConfigurationType ConfigurationType { get; private set; } /// /// A wrapper around a strategy to make it fit into the tree. /// private class PrepairedStrategyNode : StrategyNode { public bool IsFolder() { return false; } public string Name { get { return _prepairedStrategy.Name; } } public string Description { get { return _prepairedStrategy.Description; } } public string Icon { get { return _prepairedStrategy.Icon; } } public PrepairedStrategy PrepairedStrategy { get { return _prepairedStrategy; } } public AlertStrategy AlertStrategy { get { return _prepairedStrategy as AlertStrategy; } } public TopListStrategy TopListStrategy { get { return _prepairedStrategy as TopListStrategy; } } public PrepairedStrategy _prepairedStrategy; public IEnumerable Children { get { return null; } } public PrepairedStrategyNode(PrepairedStrategy prepairedStrategy) { _prepairedStrategy = prepairedStrategy; } public bool UserMustModify { get; set; } } /// /// A wrapper around a folder to make it fit into the tree. /// private class FolderNode : StrategyNode { public bool IsFolder() { return true; } public string Name { get; set; } public string Description { get; set; } public string Icon { get { return "folder"; } } public PrepairedStrategy PrepairedStrategy { get { return null; } } public AlertStrategy AlertStrategy { get { return null; } } public TopListStrategy TopListStrategy { get { return null; } } public IEnumerable Children { get; set; } public FolderNode(XmlNode xml, ConfigurationWindowManager configurationWindowManager) { Name = xml.Property("NAME"); Description = xml.Property("DESCRIPTION"); List children = new List(); foreach (XmlNode xmlChild in xml.ChildNodes) { StrategyNode strategyChild = configurationWindowManager.GetStrategyNode(xmlChild); if (strategyChild != null) children.Add(strategyChild); } Children = children; } public bool UserMustModify { get { return false; } } } /// /// All alert objects. This lets us look them up by their internal code. /// private Dictionary _alertsById = new Dictionary(); /// /// All filter objects. This lets us look them up by their internal code. /// private Dictionary _filtersById = new Dictionary(); /// /// All display only field objects. This lets us look them up by their internal code. /// private Dictionary _displayOnlyFieldsById = new Dictionary(); /// /// All exchange objects. This lets us look them up by their internal code. /// private Dictionary _exchangesById = new Dictionary(); /// /// All known symbol list objects. This lets us look them up by their internal code. /// These are only the ones sent by the server. Others can be created directly from /// a SymbolList constructor. /// private Dictionary _symbolListsById = new Dictionary(); /// /// The IConnectionMaster which we use to talk with the server. /// private IConnectionMaster _connectionMaster; /// /// The object returns a pointer to itself mostly because there's no reliable way to cancel /// the callback. The listener can keep a copy of the object, compare the callback to its /// own copy, ignore anything that doesn't match, and set it's copy to null when canceling. /// /// The ConfigurationWindowManager which now has data. public delegate void OnLoaded(ConfigurationWindowManager configurationWindowManager); /// /// The callback for when we have data. /// private OnLoaded _onLoaded; /// /// This assures us that we only send one request to the server. /// private bool _loadRequested; private bool _skipStrategies; /// /// Ask the server to give us enough information to populate a typical config window. /// /// You can only call this once. That's to simplify things, espeically in a multithreaded /// environment. /// /// Required to communicate with the server. /// What type of window are we trying to populate. /// The callback for when we have data. /// The current settings in the window. /// This should be null if that's not appropriate. /// For example, a brand new window might not have any current settings. /// The two letter ISO code for the preferred language. /// If this is null (the default) the functin will get this value from Thread.CurrentThread.CurrentUICulture public void LoadFromServer(IConnectionMaster connectionMaster, ConfigurationType configurationType, OnLoaded onLoaded = null, string currentSettings = null, string language = null, bool skipStrategies = false) { lock (this) { Debug.Assert(!_loadRequested); _loadRequested = true; } _connectionMaster = connectionMaster; ConfigurationType = configurationType; _onLoaded = onLoaded; CurrentSettingsString = currentSettings; if (null == language) Language = Thread.CurrentThread.CurrentUICulture.TwoLetterISOLanguageName; else Language = language; _skipStrategies = skipStrategies; LoadFromServerImpl(); } /// /// This is true if the server has given us data. /// This could be used to poll if you don't want to keep track of the callback. /// public bool Loaded { get; private set; } private volatile bool _abandoned; /// /// We don't care about this object any more. This is helpful if there are errors /// so we don't retry forever. As always, because this is a multithreaded object, /// there is no way to gaurentee that you don't get a callback. /// public void Abandon() { _abandoned = true; } /// /// This should be true for all new clients. /// /// The default on the server side is false. An older client won't send this field at all /// so the server will hide any negative symbol lists from the client. Before we added /// this field to the server we found that some clients would crash if they saw a negative /// symbol list. /// /// TODO Make sure the client is smart enough to handle negative symbol lists in the config /// window, then change this to true. /// private const bool ALLOW_NEGATIVE_LIST_IDS = true; /// /// Send the request to the server now. /// This is called when the user requests it, but also when we automatically retry. /// private void LoadFromServerImpl() { Dictionary message; if (ConfigurationType == ConfigurationType.Alerts) message = TalkWithServer.CreateMessage( "command", "edit_config_new", // which_samples still works, but it's depricated. See which_samples and whichSamples in // ConfigWindow.C. The server first looks at the user's white label. If that doesn't // help, then it looks at this value. If that still doesn't help, then is uses a default. // which_samples is the older approach. Our newer white labels use the wl_include field // in the database. E*TRADE (and a few others) still use this field. For some reason // E*TRADE is sending us "E*TRADE preivew" instead of "E*TRADE live". // "which_samples", "E*TRADE live", "config", CurrentSettingsString, "disabled_supported", "1", "paired_filters", "1", "symbol_list_folders", "1", "allow_negative_list_ids", ALLOW_NEGATIVE_LIST_IDS, "language", Language, "skip_strategies", _skipStrategies ? "1" : "0"); else message = TalkWithServer.CreateMessage( "command", "edit_top_list_config", // "which_samples", _whichSamples, "settings", CurrentSettingsString, "symbol_list_folders", "1", "allow_negative_list_ids", ALLOW_NEGATIVE_LIST_IDS, "language", Language); _connectionMaster.SendManager.SendMessage(message, Response); } /* private static string WRITE_SAMPLE_FILE = null; //@"\\vmware-host\Shared Folders\Documents\SampleConfig.txt"; private static string READ_SAMPLE_FILE = @"\\vmware-host\Shared Folders\Documents\SampleConfig.txt"; */ private void Response(byte[] body, object unused) { /* if ((null != WRITE_SAMPLE_FILE) && (null != body)) try { using (FileStream fileStream = new FileStream(WRITE_SAMPLE_FILE, FileMode.Truncate)) fileStream.Write(body, 0, body.Length); } catch (Exception ex) { } if (null != READ_SAMPLE_FILE) try { using (FileStream fileStream = new FileStream(READ_SAMPLE_FILE, FileMode.Open)) using (BinaryReader reader = new BinaryReader(fileStream)) body = reader.ReadBytes((int)fileStream.Length); string viewAsString = Encoding.UTF8.GetString(body); } catch (Exception ex) { } */ bool success = false; try { XmlDocument message = XmlHelper.Get(body); if (message != null) { // If message is null there was a problem. The XML didn't parse or there was // a communication problem. HashSet categories = new HashSet(); HashSet categoriesAlerts = new HashSet(); HashSet categoriesFilters = new HashSet(); _searchTargets = new Dictionary(); _alertsById.Clear(); List alertsInOrder = new List(); XmlNode section = message.Node(0).Node("CONFIG").Node("ALERT_TYPES"); foreach (XmlNode alertNode in section.Enum()) { Alert alert = new Alert(alertNode); alertsInOrder.Add(alert); _alertsById.Add(alert.InternalCode, alert); string flip = alertNode.Property("FLIP", null); if (null != flip) _alertFlip.Add(alert, flip); string keywords = alertNode.Property("KEYWORDS", null); string[] searchWords = BreakSearchStrings(alert.Description, alert.FilterDescription, keywords); AddCategories(searchWords, categories); AddCategories(searchWords, categoriesAlerts); _searchTargets[alert] = searchWords; } AlertsInOrder = alertsInOrder.AsReadOnly(); _filtersById.Clear(); List filtersInOrder = new List(); section = message.Node(0).Node("CONFIG").Node("WINDOW_SPECIFIC_FILTERS"); foreach (XmlNode filterNode in section.Enum()) { Filter filter = new Filter(filterNode); filtersInOrder.Add(filter); _filtersById.Add(filter.InternalCode, filter); string flip = filterNode.Property("FLIP", null); if (null != flip) _filterFlip.Add(filter, flip); string keywords = filterNode.Property("KEYWORDS", null); string[] searchWords = BreakSearchStrings(filter.Description, filter.Units, keywords); AddCategories(searchWords, categories); AddCategories(searchWords, categoriesFilters); _searchTargets[filter] = searchWords; } _displayOnlyFieldsById.Clear(); FiltersInOrder = filtersInOrder.AsReadOnly(); List displayOnlyFieldsInOrder = new List(); section = message.Node(0).Node("CONFIG").Node("DISPLAY_ONLY_FIELDS"); foreach (XmlNode displayonlyFieldNode in section.Enum()) { DisplayOnlyField displayOnlyField = new DisplayOnlyField(displayonlyFieldNode); displayOnlyFieldsInOrder.Add(displayOnlyField); _displayOnlyFieldsById.Add(displayOnlyField.InternalCode, displayOnlyField); string keywords = displayonlyFieldNode.Property("KEYWORDS", null); string[] searchWords = BreakSearchStrings(displayOnlyField.Description, keywords); AddCategories(searchWords, categories); } DisplayOnlyFieldsInOrder = displayOnlyFieldsInOrder.AsReadOnly(); categories.Remove("«ALERT»"); categories.Remove("«FILTER»"); Categories = categories.ToArray(); categoriesAlerts.Remove("«ALERT»"); categoriesAlerts.Remove("«FILTER»"); CategoriesAlerts = categoriesAlerts.ToArray(); categoriesFilters.Remove("«ALERT»"); categoriesFilters.Remove("«FILTER»"); CategoriesFilters = categoriesFilters.ToArray(); Array.Sort(Categories); string[] allCategories = new string[Categories.Length + 2]; allCategories[0] = "«ALERT»"; allCategories[1] = "«FILTER»"; Array.Copy(Categories, 0, allCategories, 2, Categories.Length); Categories = allCategories; Array.Sort(CategoriesAlerts); Array.Sort(CategoriesFilters); _exchangesById.Clear(); List exchangesInOrder = new List(); section = message.Node(0).Node("CONFIG").Node("EXCHANGES"); foreach (XmlNode exchangeNode in section.Enum()) { Exchange exchange = new Exchange(exchangeNode); exchangesInOrder.Add(exchange); //_exchangesById.Add(exchange.Description, exchange); //GetFilter kept returning null... _exchangesById.Add(exchange.InternalCode, exchange); } ExchangesInOrder = exchangesInOrder.AsReadOnly(); _symbolListsById.Clear(); List symbolListsInOrder = new List(); section = message.Node(0).Node("CONFIG").Node("SYMBOL_LISTS"); foreach (XmlNode symbolListNode in section.Enum()) { SymbolList symbolList = new SymbolList(symbolListNode); symbolListsInOrder.Add(symbolList); _symbolListsById.Add(symbolList.InternalCode, symbolList); } // Used to be AsReadOnly() as well, changed to accomodate new lists created inside // the config window. _symbolListsInOrder = symbolListsInOrder; CurrentSettings = null; StrategyTree = GetStrategyNode(message.Node(0).Node("STRATEGIES")); success = true; } } catch (Exception ex) { // We don't expect any exceptions. This is a throw back to the old, old Delphi code. // it could fail for a large number of reasons. Generally, if it got a bad message // from the server, it assumed that HTTP server was acting up, and a PHP script got // cut short. The new servers can't send half a message, so that's not an issue any // more. But we already have the logic for success here, so we might as well keep // the try block. String message = ex.Message; } if (_abandoned) return; if (success) { Loaded = true; if (_onLoaded != null) _onLoaded(this); } else { // The only failures we expect come because the socket got disconnected, or was never // connected. The connection handler stuff should be smart enough to automatically // retry in that case at reasonable intervals. This request should be queued until // we are reconnected. LoadFromServerImpl(); } } private StrategyNode GetStrategyNode(XmlNode xml) { if (xml == null) return null; if ((xml.LocalName == "FOLDER") || (xml.LocalName == "STRATEGIES")) return new FolderNode(xml, this); else if (xml.LocalName == "STRATEGY") { PrepairedStrategy strategy; if (ConfigurationType == ConfigurationType.TopList) strategy = new TopListStrategy(xml, this); else strategy = new AlertStrategy(xml, this); PrepairedStrategyNode node = new PrepairedStrategyNode(strategy); node.UserMustModify = xml.Property("USER_MUST_MODIFY") == "1"; if (xml.Property("CURRENT") == "1") CurrentSettings = strategy; return node; } else return null; } /// /// This is a copy of the current settings, as given to us in the LoadFromServer() request. /// It might be null. /// public string CurrentSettingsString { get; private set; } /// /// This is the language that the user speaks. This is an ISO 2 letter code. See /// LoadFromServer() for more details. /// public string Language { get; private set; } /// /// This is the top of the strategy tree. /// /// This item cannot be used before the strategy is loaded. You can poll the Loaded /// property or listen for the OnLoaded event to know when things are ready. If you /// you access this sooner, you might see null, or an invalid value. Or you might cause /// a threading problem and have undefined results, possibly a bug which doesn't show /// up right away. /// public StrategyNode StrategyTree { get; private set; } /// /// This is a complete list of the alerts which are available. They are listed /// in the order that they normally appear in help and on various windows. /// /// This item cannot be used before the strategy is loaded. You can poll the Loaded /// property or listen for the OnLoaded event to know when things are ready. If you /// you access this sooner, you might see null, or an invalid value. Or you might cause /// a threading problem and have undefined results, possibly a bug which doesn't show /// up right away. /// public IList AlertsInOrder { get; private set; } /// /// This lets you look up an alert using its internal code. If your program wants /// to add new highs without a GUI, it should remember that "NHP" is the internal /// code for new highs, then use that to look up the alert object. /// /// It is possible for alerts to be added or even deleted over time. This could /// return null, even if it worked in the past. /// /// This item cannot be used before the strategy is loaded. You can poll the Loaded /// property or listen for the OnLoaded event to know when things are ready. If you /// you access this sooner, you might see null, or an invalid value. Or you might cause /// a threading problem and have undefined results, possibly a bug which doesn't show /// up right away. /// /// The internal code to look up. /// The matching alert, or null if one cannot be found. public Alert FindAlert(string internalCode) { Alert result; _alertsById.TryGetValue(internalCode, out result); return result; } /// /// This is a complete list of the filters which are available. They are listed /// in the order that they normally appear in help and on various windows. /// /// This item cannot be used before the strategy is loaded. You can poll the Loaded /// property or listen for the OnLoaded event to know when things are ready. If you /// you access this sooner, you might see null, or an invalid value. Or you might cause /// a threading problem and have undefined results, possibly a bug which doesn't show /// up right away. /// public IList FiltersInOrder { get; private set; } /// /// This lets you look up a filter using its internal code. If your program wants /// to manipulate the 3 month average volume filter without a GUI, it should remember /// that "Vol3M" is the internal code for that filter, then use that string to look up /// the filter. /// /// It is possible for filters to be added or even deleted over time. This could /// return null, even if it worked in the past. /// /// This item cannot be used before the strategy is loaded. You can poll the Loaded /// property or listen for the OnLoaded event to know when things are ready. If you /// you access this sooner, you might see null, or an invalid value. Or you might cause /// a threading problem and have undefined results, possibly a bug which doesn't show /// up right away. /// /// The internal code to look up. /// The matching filter, or null if one cannot be found. public Filter FindFilter(string internalCode) { Filter result; _filtersById.TryGetValue(internalCode, out result); return result; } /// /// This lets you look up a filter using its internal code. This uses the long /// version of the filter name, like "MinVol3M" or "MaxVol3M". If you have the /// short form, like "Vol3M", use the other version of FindFilter() to look for /// the filter object. /// /// It is possible for filters to be added or even deleted over time. This could /// return null, even if it worked in the past. /// /// This item cannot be used before the strategy is loaded. You can poll the Loaded /// property or listen for the OnLoaded event to know when things are ready. If you /// you access this sooner, you might see null, or an invalid value. Or you might cause /// a threading problem and have undefined results, possibly a bug which doesn't show /// up right away. /// /// The internal code to look up. /// The matching filter, or null if one cannot be found. /// True if this was a max filter, or false if this was a min filter. public void FindFilter(string longInternalCode, out Filter filter, out bool max) { // Start with some defaults to keep the compiler happy. filter = null; max = false; if (longInternalCode.StartsWith("Max")) max = true; else if (!longInternalCode.StartsWith("Min")) return; filter = FindFilter(longInternalCode.Substring(3)); } /// /// This is a complete list of the display only fields which are available. They are /// listed in the order that they normally appear in help and on various windows. /// /// This item cannot be used before the strategy is loaded. You can poll the Loaded /// property or listen for the OnLoaded event to know when things are ready. If you /// you access this sooner, you might see null, or an invalid value. Or you might cause /// a threading problem and have undefined results, possibly a bug which doesn't show /// up right away. /// public IList DisplayOnlyFieldsInOrder { get; private set; } /// /// This lets you look up a display only field using its internal code. D_Desc, /// D_Symbol, D_Sector, etc... /// /// This item cannot be used before the strategy is loaded. You can poll the Loaded /// property or listen for the OnLoaded event to know when things are ready. If you /// you access this sooner, you might see null, or an invalid value. Or you might cause /// a threading problem and have undefined results, possibly a bug which doesn't show /// up right away. /// /// The internal code to look up. /// The matching display only field, or null if one cannot be found. public DisplayOnlyField FindDisplayOnlyField(string internalCode) { DisplayOnlyField result; _displayOnlyFieldsById.TryGetValue(internalCode, out result); return result; } /// /// This is a complete list of the exchanges which are available. They are listed /// in the order that they normally appear in various windows. /// /// This item cannot be used before the strategy is loaded. You can poll the Loaded /// property or listen for the OnLoaded event to know when things are ready. If you /// you access this sooner, you might see null, or an invalid value. Or you might cause /// a threading problem and have undefined results, possibly a bug which doesn't show /// up right away. /// public IList ExchangesInOrder { get; private set; } /// /// This lets you look up an exchange using its internal code. If your program wants /// to manipulate exchanges without a GUI, it should remember the internal codes for /// each exchange, then use that string to look up the exchange. /// /// It is possible for exchanges to be added or even deleted over time. This could /// return null, even if it worked in the past. /// /// This item cannot be used before the strategy is loaded. You can poll the Loaded /// property or listen for the OnLoaded event to know when things are ready. If you /// you access this sooner, you might see null, or an invalid value. Or you might cause /// a threading problem and have undefined results, possibly a bug which doesn't show /// up right away. /// /// The code to look up. /// The exchange object, or null if none was found. public Exchange FindExchange(string internalCode) { Exchange result; _exchangesById.TryGetValue(internalCode, out result); return result; } private List _symbolListsInOrder; /// /// This returns a list of SymbolList objects. This is returned a prefered order /// that is commonly used in the GUI. /// /// This item cannot be used before the strategy is loaded. You can poll the Loaded /// property or listen for the OnLoaded event to know when things are ready. If you /// you access this sooner, you might see null, or an invalid value. Or you might cause /// a threading problem and have undefined results, possibly a bug which doesn't show /// up right away. /// public IList SymbolListsInOrder { get { return _symbolListsInOrder.AsReadOnly(); } } /// /// This looks up a symbol list by its internal code. The internal /// code is based on the way the symbol list is stored on the server. So /// you can record the internal code if you want persistence between one /// execution of your program and the next. /// /// Symbol lists are often added and deleted over time. This could /// return null, even if it worked in the past. /// /// This item cannot be used before the strategy is loaded. You can poll the Loaded /// property or listen for the OnLoaded event to know when things are ready. If you /// you access this sooner, you might see null, or an invalid value. Or you might cause /// a threading problem and have undefined results, possibly a bug which doesn't show /// up right away. /// /// The code to look up. /// The symbol list, or null if one was not found. public SymbolList FindSymbolList(string internalCode) { SymbolList result; _symbolListsById.TryGetValue(internalCode, out result); return result; } /// /// This inserts a SymbolList into the appropriate position in the various /// lists of SymbolLists maintained by this object. This was added specifically /// to support the addition of newly created symbol lists in the config window /// GUI /// /// The SymbolList to be added public void AddSymbolList(SymbolList list) { // find the position to insert the last symbol list, this is before // any shared lists but after all user created lists int pos = 0; foreach (SymbolList sL in SymbolListsInOrder) { if (sL.InternalCode.Substring(0, 5) != "SL_0_") break; pos++; } _symbolListsById.Add(list.InternalCode, list); _symbolListsInOrder.Insert(pos, list); } /// /// This is the parsed form of the current strategy. /// /// This can be null. Presumably it will be null if and only if we don't send a strategy. /// But it depends on what the server decides to send. /// /// This item cannot be used before the strategy is loaded. You can poll the Loaded /// property or listen for the OnLoaded event to know when things are ready. If you /// you access this sooner, you might see null, or an invalid value. Or you might cause /// a threading problem and have undefined results, possibly a bug which doesn't show /// up right away. /// public PrepairedStrategy CurrentSettings { get; private set; } /// /// This makes a bullish strategy into a bearish strategy. The rules come from the server. /// /// The strategy to flip. /// A newly created strategy object. public AlertStrategy Flip(AlertStrategy original) { // Note: We assume the flip instructions from the server are semetric. If X flips to // Y then Y flips to X. If that's not true we will still act in a rasonable way. In // fact, this function is very robust in the face of bad or unknown flip rules. But the // details are different in the way we handle this for alerts vs. filters. One uses // the rules to push values while the others use the rules to pull values. I think this // is reasonable. AlertStrategy result = new AlertStrategy(original); Flip(original, result); result.Alerts.Clear(); result.AlertQuality.Clear(); foreach (Alert orig in original.Alerts) { Alert final = orig; if (_alertFlip.ContainsKey(orig)) { string flipName = _alertFlip[orig]; if (_alertsById.ContainsKey(flipName)) final = _alertsById[flipName]; } result.Alerts.Add(final); if (original.AlertQuality.ContainsKey(orig)) result.AlertQuality[final] = original.AlertQuality[orig]; } return result; } /// /// This makes a bullish strategy into a bearish strategy. The rules come from the server. /// /// The strategy to flip. /// A newly created strategy object. public TopListStrategy Flip(TopListStrategy original) { TopListStrategy result = new TopListStrategy(original); Flip(original, result); if (null != original.SortBy) { FlipType flipType; DecodeFlipFilter(original.SortBy, out flipType, out result.SortBy); if ((flipType == FlipType.Plus) || (flipType == FlipType.None)) result.BiggestOnTop = original.BiggestOnTop; else result.BiggestOnTop = !original.BiggestOnTop; } return result; } private enum FlipType { Minus, Percent, Decimal, Ratio, Plus, None }; private void DecodeFlipFilter(Filter filter, out FlipType flipType, out Filter partner) { string flip = ""; if (_filterFlip.ContainsKey(filter)) flip = _filterFlip[filter]; char flipTypeChar = '\0'; string partnerName = ""; if (flip.Length > 0) { flipTypeChar = flip[0]; partnerName = flip.Substring(1); } switch (flipTypeChar) { case '-': flipType = FlipType.Minus; break; case '%': flipType = FlipType.Percent; break; case '.': flipType = FlipType.Decimal; break; case '/': flipType = FlipType.Ratio; break; case '+': flipType = FlipType.Plus; break; default: // The empty string explicitly means none. An unknown character is // interpreted as none. flipType = FlipType.None; break; } partner = filter; if (_filtersById.ContainsKey(partnerName)) partner = _filtersById[partnerName]; } private void Flip(PrepairedStrategy original, PrepairedStrategy result) { if (original.WindowName.StartsWith("Flipped: ")) result.WindowName = original.WindowName.Substring("Flipped: ".Length); else result.WindowName = "Flipped: " + original.WindowName; result.MinFilters.Clear(); result.MaxFilters.Clear(); foreach (Filter filter in FiltersInOrder) { FlipType flipType; Filter partner; DecodeFlipFilter(filter, out flipType, out partner); if (flipType == FlipType.None) // If we don't know the flip type, don't do anything. If we look for // the partner, without knowing the rule, we will probably mis interpret // it. It would be better to do nothing. partner = filter; string partnerMin = ""; if (original.MinFilters.ContainsKey(partner)) partnerMin = original.MinFilters[partner]; string partnerMax = ""; if (original.MaxFilters.ContainsKey(partner)) partnerMax = original.MaxFilters[partner]; string min, max; switch (flipType) { case FlipType.Minus: min = ComputeMinusFlip(partnerMax); max = ComputeMinusFlip(partnerMin); break; case FlipType.Percent: min = ComputePercentFlip(partnerMax); max = ComputePercentFlip(partnerMin); break; case FlipType.Decimal: min = ComputeDecimalFlip(partnerMax); max = ComputeDecimalFlip(partnerMin); break; case FlipType.Ratio: min = ComputeReciprocalFlip(partnerMax); max = ComputeReciprocalFlip(partnerMin); break; default: // This is no flip or + flip. Notice that + does not swap the min and max. // We also treat unknowns like a + flip. min = partnerMin; max = partnerMax; break; } // Use [] not Add(). If there is a duplicate [] will take the last one. // Add throws an exception on a duplicate. There shouldn't be a duplicate, // but if there is one we should try to carry on in a reasonable way. if (min != "") { result.MinFilters[filter] = min; } if (max != "") { result.MaxFilters[filter] = max; } } result.Columns = new List(); foreach (IDisplayColumn column in original.Columns) { if (column is Filter) { Filter filter = (Filter)column; FlipType flipType; Filter partner; DecodeFlipFilter(filter, out flipType, out partner); result.Columns.Add(partner); } else { result.Columns.Add(column); } } } private Dictionary _alertFlip = new Dictionary(); private Dictionary _filterFlip = new Dictionary(); private static string ComputeMinusFlip(string partnerValue) { // Result:= - PV // First try to handle value as an integer int asInt; if (ServerFormats.TryParse(partnerValue, out asInt)) { int flipped = -asInt; return ServerFormats.ToString((double)flipped); } // If value is not an integer, try to handle it as a float double asDouble; if (ServerFormats.TryParse(partnerValue, out asDouble)) { double flipped = -asDouble; return ServerFormats.ToString(flipped); } // Partner is probably empty. Or invaid, so we make it empty. return ""; } private static string ComputePercentFlip(string partnerValue) { // Result := 100 - PV // First try to handle value as an integer int asInt; if (ServerFormats.TryParse(partnerValue, out asInt)) { int flipped = (100 - asInt); return ServerFormats.ToString((double)flipped); } // If value is not an integer, try to handle it as a float double asDouble; if (ServerFormats.TryParse(partnerValue, out asDouble)) { double flipped = (100.0 - asDouble); return ServerFormats.ToString(flipped); } // Partner is probably empty. Or invaid, so we make it empty. return ""; } private static string ComputeDecimalFlip(string partnerValue) { // Else Result := 1 - PV int asInt; double asDouble; // First try to handle value as an integer if (ServerFormats.TryParse(partnerValue, out asInt)) { if (asInt == 0) return "0"; } // If value is not an integer, try to handle it as a float else if (ServerFormats.TryParse(partnerValue, out asDouble)) { if (asDouble == 0.0) return "0"; if ((asDouble > 0.0) && (asDouble < 1.0)) { double flipped = (1.0 - asDouble); return ServerFormats.ToString(flipped); } } // Partner is probably empty. Or invaid, so we make it empty. return ""; } private static string ComputeReciprocalFlip(string partnerValue) { // Result := 1 / PV double asDouble; if (ServerFormats.TryParse(partnerValue, out asDouble)) { if (asDouble > 0.00001) { double flipped = (1.0 / asDouble); // TODO: Make the format look more like the Delphi format. // If ExtValue > 0.00001 Then Result := Format('%.5f',[1.0/ExtValue]); // Of course, we have to worry about periods and commas, but we don't // want to use the standard output format of ServerFormats.ToString. return ServerFormats.ToString(flipped); } } // Partner is probably empty. Or invaid, so we make it empty. return ""; } private static bool Match(String[] possibleMatches, String[] patterns) { // Every pattern (what the user typed) must match at least one of // the possible matches(the keywords and categories from the server). foreach (string pattern in patterns) if (!Match(possibleMatches, pattern)) return false; return true; } private static bool Match(String[] possibleMatches, String pattern) { // The pattern (what the user typed) must match at least one of // the possible matches (keywords and categories). foreach (string possibleMatch in possibleMatches) if (possibleMatch.StartsWith(pattern)) // If what the user typed is a prefix of the possible match // we call it a match. return true; return false; } private const char NO_BREAK_SPACE = '\u00A0'; private static void AddInProgress(StringBuilder inProgress, HashSet pieces) { if (inProgress.Length == 0) return; bool isCategory = (inProgress.Length > 1) && (inProgress[0] == '.') && (inProgress[inProgress.Length - 1] == '.'); // The server sends category names like .bid.and.ask. We change it to look like // «bid and ask». Not that those are non-break spaces, not normal spaces. That // looks better, so we can show it directly to a user, but it's still easy for // the code to parse and recognize. // Also, capitolize everything, effectively giving us a case insensitive search. for (int i = 0; i < inProgress.Length; i++) if (isCategory && (inProgress[i] == '.')) inProgress[i] = NO_BREAK_SPACE; else inProgress[i] = Char.ToUpper(inProgress[i]); if (isCategory) { inProgress[0] = '«'; inProgress[inProgress.Length - 1] = '»'; } pieces.Add(inProgress.ToString()); inProgress.Length = 0; } private static void BreakSearchString(string allTogether, HashSet pieces) { StringBuilder inProgress = new StringBuilder(); foreach (char ch in allTogether) { bool breakNow; if (Char.IsLetterOrDigit(ch) || (ch == '.') || (ch == '«') || (ch == '»') || (ch == NO_BREAK_SPACE)) { breakNow = false; inProgress.Append(ch); } else if ((ch == '$') || (ch == '%') || (ch == '/') || (ch == '€')) { breakNow = true; pieces.Add(Char.ToString(ch)); } else { breakNow = true; } if (breakNow) AddInProgress(inProgress, pieces); } AddInProgress(inProgress, pieces); } private static string[] BreakSearchStrings(params string[] strings) { // This very explicitly filters out nulls. This is just a convenience function // one top of BreakSearchString, and it's convient for the rest of the code to // send us data from a lot of things, regardless of wether or not they are empty. HashSet pieces = new HashSet(); foreach (string s in strings) if (null != s) BreakSearchString(s, pieces); return pieces.ToArray(); } private static void AddCategories(String[] keywords, HashSet categories) { foreach (String keyword in keywords) { // Assert keyword.Length > 1 if ((keyword[0] == '«') && (keyword[keyword.Length - 1] == '»')) categories.Add(keyword); } } /* A category is a special type of keyword. * We have one dedicated to Alerts and another to Filters. * 1) A category can (and ususally does) contain internal spaces. * "«bid and ask»" is treated as one word, not three. * 2) Categories/CategoriesAlerts/CategoriesFilters are in a different namespace. * "Bi" is not a prefix of "«bid and ask»", so they do not match. * 3) The user can get a list of categories. This can make a drop- * down list or some check boxes in the GUI. Checkboxes are more * powerful, but we scaled back to the drop down list because it * was easier for users. */ /// /// This is a list of all categories for alerts and filters. /// /// A category is a special type of keyword. /// 1) A category can (and ususally does) contain internal spaces. /// "«bid and ask»" is treated as one word, not three. /// 2) Categories are in a different namespace. /// "Bi" is not a prefix of "«bid and ask»", so they do not match. /// 3) The user can get a list of categories. This can make a drop- /// down list or some check boxes in the GUI. Checkboxes are more /// powerful, but in TI Pro we scaled back to the drop down list /// because it was easier for users. /// /// This item cannot be used before the strategy is loaded. You can poll the Loaded /// property or listen for the OnLoaded event to know when things are ready. If you /// you access this sooner, you might see null, or an invalid value. Or you might cause /// a threading problem and have undefined results, possibly a bug which doesn't show /// up right away. /// public String[] Categories { get; private set; } /// /// This is a list of all categories for alerts. /// /// A category is a special type of keyword. /// 1) A category can (and ususally does) contain internal spaces. /// "«bid and ask»" is treated as one word, not three. /// 2) Categories are in a different namespace. /// "Bi" is not a prefix of "«bid and ask»", so they do not match. /// 3) The user can get a list of categories. This can make a drop- /// down list or some check boxes in the GUI. Checkboxes are more /// powerful, but in TI Pro we scaled back to the drop down list /// because it was easier for users. /// /// This item cannot be used before the strategy is loaded. You can poll the Loaded /// property or listen for the OnLoaded event to know when things are ready. If you /// you access this sooner, you might see null, or an invalid value. Or you might cause /// a threading problem and have undefined results, possibly a bug which doesn't show /// up right away. /// public String[] CategoriesAlerts { get; private set; } /// /// This is a list of all categories for filters. /// /// A category is a special type of keyword. /// 1) A category can (and ususally does) contain internal spaces. /// "«bid and ask»" is treated as one word, not three. /// 2) Categories are in a different namespace. /// "Bi" is not a prefix of "«bid and ask»", so they do not match. /// 3) The user can get a list of categories. This can make a drop- /// down list or some check boxes in the GUI. Checkboxes are more /// powerful, but in TI Pro we scaled back to the drop down list /// because it was easier for users. /// /// This item cannot be used before the strategy is loaded. You can poll the Loaded /// property or listen for the OnLoaded event to know when things are ready. If you /// you access this sooner, you might see null, or an invalid value. Or you might cause /// a threading problem and have undefined results, possibly a bug which doesn't show /// up right away. /// public String[] CategoriesFilters { get; private set; } private Dictionary _searchTargets; /// /// This is how we implement the search tab in the config window in TI Pro. Enter one or /// more strings. If a string has spaces or punctuation in it, we automatically /// split that up. This returns a list of alerts and filters which match all /// of the given strings. So the more the user types, the more restrictive the /// filter. A typical use might be to have a text entry box for the first parameter, /// and a drop down list of categories for the second box. /// /// Any of the inputs can be null. Nulls will be ignored. /// /// A list of things to search for. /// A list of matching alerts and filters. public HashSet Search(params string[] searchFor) { String[] searchStrings = BreakSearchStrings(searchFor); // I'm returning a hash set under the assumption that the caller will already // have a list of alerts and or filters, in a specified order. The result is // only used to hide or show the items. HashSet result = new HashSet(); //There might be cases where the program will reach the foreach //loop before the server brings forth the data to populate _searchTargets. //If this happens, a null reference exception occurs. I've never had this happen //on my computer, however it has happened on someone elses-and the stack trace showed // that the culprit was in this method. try { foreach (KeyValuePair kvp in _searchTargets) { if (Match(kvp.Value, searchStrings)) { result.Add(kvp.Key); } } } catch (NullReferenceException) { } return result; } public ConfigurationWindowManager() { } } }