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