using System; using System.Collections.Generic; using System.Windows.Forms; using System.Windows.Forms.DataVisualization.Charting; using System.Xml; using TradeIdeas.TIProData.Configuration; using TradeIdeas.TIProData.Interfaces; using TradeIdeas.XML; /* This is based heavily on MarketMaster. This has a lot more configuration options, but * is harder to use. I considerd some sort of hybrid, a single window that could do both. * See MarketSummaryHistogram for an example. It seemed hard to do, in part because of * when we choose to request config data from the server. That's totally different in the * two windows. So the structure of the code is much different. */ namespace TradeIdeas.TIProGUI { public partial class MarketMaster2 : Form, ISaveLayout { private static readonly double[] ZERO = new double[] { 0 }; private IConnectionMaster _connectionMaster; private Series _upSeries; private Series _downSeries; private Series _plus2StdDevSeries; private Series _plusStdDevSeries; private Series _meanSeries; private Series _minusStdDevSeries; private Series _minus2StdDevSeries; private List _stdSeriesList; public MarketMaster2(IConnectionMaster connectionMaster) { _connectionMaster = connectionMaster; InitializeComponent(); if (!IsHandleCreated) CreateHandle(); WindowIconCache.SetIcon(this); _upSeries = chart1.Series["Up"]; _downSeries = chart1.Series["Down"]; _plus2StdDevSeries = chart1.Series["Plus2StdDev"]; _plusStdDevSeries = chart1.Series["PlusStdDev"]; _meanSeries = chart1.Series["Mean"]; _minusStdDevSeries = chart1.Series["MinusStdDev"]; _minus2StdDevSeries = chart1.Series["Minus2StdDev"]; _stdSeriesList = new List() { _plus2StdDevSeries, _plusStdDevSeries, _meanSeries, _minusStdDevSeries, _minus2StdDevSeries }; for (int i = 0; i < ITEM_COUNT; i++) { _upSeries.Points.Add(new DataPoint(_upSeries)); _downSeries.Points.Add(new DataPoint(_downSeries)); foreach (Series series in _stdSeriesList) series.Points.Add(new DataPoint(series)); } timeTrackBar.Value = 1; UpdateTimeLabel(); ShowHideStdDev(); ClearStandardDeviations(); } private void timeTrackBar_ValueChanged(object sender, EventArgs e) { UpdateTimeLabel(); } private void UpdateTimeLabel() { DateTime time = DateTime.Today; time = time.AddHours(9.5); // Hard coded to NY Time. time = time.AddMinutes(timeTrackBar.Value * 5); // each tick is 5 minutes; timeLabel.Text = time.ToString("h:mm"); } private object _currentEpoch; private int _sentRequestCount; private int _receivedRequestCount; private static readonly TimeSpan OPEN = new TimeSpan(6, 30, 0); private string _currentUpConfig; private string _currentDownConfig; private const int ITEM_COUNT = 29; private void SetStatus(string status) { Text = status; } private void ReportError(string status) { SetStatus(status); MessageBox.Show(status, "Error"); } private static DisplayOnlyField FindSymbol(ConfigurationWindowManager configurationWindowManager) { DisplayOnlyField result = configurationWindowManager.FindDisplayOnlyField("D_Symbol"); System.Diagnostics.Debug.Assert(null != result); return result; } private void loadAllButton_Click(object sender, EventArgs e) { _currentEpoch = new object(); object epoch = _currentEpoch; _configStrings.Clear(); _sentRequestCount = 0; _receivedRequestCount = 0; refreshCurrentButton.Enabled = false; foreach (DataPoint point in _upSeries.Points) point.YValues = ZERO; foreach (DataPoint point in _downSeries.Points) point.YValues = ZERO; ClearStandardDeviations(); chart1.ChartAreas[0].RecalculateAxesScale(); ConfigurationWindowManager configurationWindowManager = new ConfigurationWindowManager(); configurationWindowManager.LoadFromServer(_connectionMaster, ConfigurationType.TopList, delegate(ConfigurationWindowManager unused) { this.InvokeIfRequired(delegate { TopListStrategy upStrategy = configurationWindowManager.CurrentSettings as TopListStrategy; if (null == upStrategy) { ReportError("Unable to use base strategy."); return; } upStrategy.Columns.Clear(); if (accumulatorCheckBox.Checked) { DisplayOnlyField placeholder = FindSymbol(configurationWindowManager); Filter accumulatorFilter = configurationWindowManager.FindFilter(accumulatorTextBox.Text); if (null == accumulatorFilter) { ReportError("Cannot find accumulator filter: \"" + accumulatorTextBox.Text + "\"."); return; } upStrategy.Columns.Add(placeholder); upStrategy.Columns.Add(accumulatorFilter); if (averageWeightCheckBox.Checked) { Filter weightFilter = configurationWindowManager.FindFilter(averageWeightTextBox.Text); if (null == weightFilter) { ReportError("Cannot find weight filter: \"" + averageWeightTextBox.Text + "\"."); return; } upStrategy.Columns.Add(weightFilter); } } upStrategy.History = true; upStrategy.MaxRecords = 1000; if (explicitRedStrategyCheckBox.Checked) { // The user created his own down strategy. // The user now has full control over window specific filters, window name, and sort field. // The pivot field is ignored when building the chart, but still shows up as a column if you double click. configurationWindowManager = new ConfigurationWindowManager(); configurationWindowManager.LoadFromServer(_connectionMaster, ConfigurationType.TopList, delegate(ConfigurationWindowManager unused1) { this.InvokeIfRequired(delegate { TopListStrategy downStrategy = configurationWindowManager.CurrentSettings as TopListStrategy; if (null == downStrategy) { ReportError("Unable to use explicit red strategy."); return; } downStrategy.History = true; downStrategy.MaxRecords = 1000; downStrategy.Columns.Clear(); downStrategy.Columns.AddRange(upStrategy.Columns); RequestAll(upStrategy, downStrategy); }); }, explicitRedStrategyTextBox.Text); } else { // The red strategy is similar to the green strategy, with just a few changes. Filter pivotFilter = configurationWindowManager.FindFilter(pivotFilterTextBox.Text); if (null == pivotFilter) { ReportError("Cannot find pivot filter: \"" + pivotFilterTextBox.Text + "\"."); return; } upStrategy.SortBy = pivotFilter; TopListStrategy downStrategy = new TopListStrategy(upStrategy); upStrategy.MinFilters[pivotFilter] = "1.5"; upStrategy.BiggestOnTop = true; downStrategy.MaxFilters[pivotFilter] = "-1.5"; downStrategy.BiggestOnTop = false; string baseName = upStrategy.WindowName ?? ""; if (baseName != "") baseName += " "; upStrategy.WindowName = baseName + "UP"; downStrategy.WindowName = baseName + "DOWN"; RequestAll(upStrategy, downStrategy); } }); }, baseStrategyTextBox.Text); SetStatus("Loading Config"); } private void RequestAll(TopListStrategy upStrategy, TopListStrategy downStrategy) { TimeSpan time = OPEN.Add(TimeSpan.FromMinutes(timeTrackBar.Value * 5)); DateTime date = DateTime.Today; chart1.ChartAreas[0].AxisX.Enabled = AxisEnabled.True; CustomLabelsCollection labels = chart1.ChartAreas[0].AxisX.CustomLabels; labels.Clear(); for (int i = ITEM_COUNT - 1; i >= 0; i--) { while ((date.DayOfWeek == DayOfWeek.Saturday) || (date.DayOfWeek == DayOfWeek.Sunday)) date = date.AddDays(-1); /* I tried storing the axis labels in the data points and failed. Julio suggested * that I was missing the following code. * Set the following to enable the axis text: * chartArea1.AxisX.IsLabelAutoFit = false; * then the following to change the angle and interval: * chartArea1.AxisX.LabelStyle.Angle = -60; * chartArea1.AxisX.MajorTickMark.Interval = 0D; */ CustomLabel customLabel = new CustomLabel(); customLabel.FromPosition = i + 0.5; customLabel.ToPosition = i + 1.5; customLabel.Text = date.ToString("M/dd/yyyy"); chart1.ChartAreas[0].AxisX.CustomLabels.Add(customLabel); upStrategy.Time = date + time; downStrategy.Time = upStrategy.Time; RequestHistory(upStrategy.MakeConfigString(), _upSeries.Points[i]); RequestHistory(downStrategy.MakeConfigString(), _downSeries.Points[i]); date = date.AddDays(-1); } UpdateRequestCount(); upStrategy.History = false; _currentUpConfig = upStrategy.MakeConfigString(); downStrategy.History = false; _currentDownConfig = downStrategy.MakeConfigString(); } private void UpdateRequestCount() { if (_sentRequestCount > _receivedRequestCount) SetStatus("Loading (" + _receivedRequestCount + " / " + _sentRequestCount + ')'); else { SetStatus("Ready."); refreshCurrentButton.Enabled = true; UpdateStandardDeviations(); } } private void ClearStandardDeviations() { foreach (Series series in _stdSeriesList) foreach (DataPoint point in series.Points) { point.IsEmpty = true; point.YValues = ZERO; } } private static bool IsFinite(double v) { return !(Double.IsInfinity(v) || Double.IsNaN(v)); } private static void SetValue(DataPoint p, double v) { p.IsEmpty = false; p.YValues = new double[] { v }; } private static void ClearValue(DataPoint p) { p.IsEmpty = true; p.YValues = ZERO; } private void UpdateStandardDeviations() { StandardDeviationAccumulator accumulator = new StandardDeviationAccumulator(); for (int i = 0; i < ITEM_COUNT; i++) { double mean = accumulator.GetMean(); double stdDev = accumulator.GetStandardDeviation(); if (IsFinite(mean)) SetValue(_meanSeries.Points[i], mean); else ClearValue(_meanSeries.Points[i]); if (IsFinite(mean) && IsFinite(stdDev)) { SetValue(_plus2StdDevSeries.Points[i], mean + 2 * stdDev); SetValue(_plusStdDevSeries.Points[i], mean + stdDev); SetValue(_minusStdDevSeries.Points[i], mean - stdDev); SetValue(_minus2StdDevSeries.Points[i], mean - 2 * stdDev); } else { ClearValue(_plus2StdDevSeries.Points[i]); ClearValue(_plusStdDevSeries.Points[i]); ClearValue(_minusStdDevSeries.Points[i]); ClearValue(_minus2StdDevSeries.Points[i]); } // Update the statistics *after* drawing the points. So the mean at a point is the // mean of all the points *before* and not including this point. accumulator.Add(_upSeries.Points[i].YValues[0] + _downSeries.Points[i].YValues[0]); } } private Dictionary _configStrings = new Dictionary(); private void RequestHistory(string configString, DataPoint point) { _configStrings[point] = configString; _sentRequestCount++; object originalEpoch = _currentEpoch; MarketSummaryRequest request = new MarketSummaryRequest(configString, _connectionMaster.SendManager); request.ResponseFromServer += delegate(MarketSummaryRequest sender) { this.InvokeIfRequired(delegate { if (originalEpoch != _currentEpoch) return; List result = request.SimpleResult; _receivedRequestCount++; if (null == result) { // Connection error. RequestHistory(configString, point); return; } UpdateRequestCount(); if (result.Count != 1) return; point.IsEmpty = false; point.YValues = new double[] { result[0].Y }; chart1.ChartAreas[0].RecalculateAxesScale(); }); }; request.SendRequest(); } private void refreshCurrentButton_Click(object sender, EventArgs e) { _currentEpoch = new object(); _sentRequestCount = 0; _receivedRequestCount = 0; RequestHistory(_currentUpConfig, _upSeries.Points[ITEM_COUNT - 1]); RequestHistory(_currentDownConfig, _downSeries.Points[ITEM_COUNT - 1]); UpdateRequestCount(); } private void chart1_MouseDoubleClick(object sender, MouseEventArgs e) { HitTestResult hitTestResult = chart1.HitTest(e.X, e.Y); string configString; _configStrings.TryGetValue(hitTestResult.Object, out configString); if (null == configString) return; ConfigurationWindowManager configurationWindowManager = new ConfigurationWindowManager(); configurationWindowManager.LoadFromServer(_connectionMaster, ConfigurationType.TopList, delegate(ConfigurationWindowManager unused) { this.InvokeIfRequired(delegate { TopListStrategy strategy = configurationWindowManager.CurrentSettings as TopListStrategy; if (null == strategy) { // This shouldn't happen at all. If it does, we can at least report something to help // us track it down. SetStatus("Unable to use this strategy."); return; } strategy.Columns.Insert(0, FindSymbol(configurationWindowManager)); Filter pivotFilter = configurationWindowManager.FindFilter(pivotFilterTextBox.Text); if (null != pivotFilter) // This is what's currently in the text box, so it might have changed since we sent the // strategy. Also, this could be a custom filter which is no longer available. strategy.Columns.Add(pivotFilter); Filter accumulatorFilter = configurationWindowManager.FindFilter(accumulatorTextBox.Text); if (null != accumulatorFilter) // This can be null for all the reaons listed above. Also, this might have been turned // off, so the original strategy ignored it. Note: In some cases this will already be // included, but adding it a second time doesn't hurt. strategy.Columns.Add(accumulatorFilter); Filter weightFilter = configurationWindowManager.FindFilter(averageWeightTextBox.Text); if (null != weightFilter) strategy.Columns.Add(weightFilter); configString = strategy.MakeConfigString(); TopListForm form = new TopListForm(_connectionMaster, configString); form.Show(); }); }, configString); } private class StandardDeviationAccumulator { // https://en.wikipedia.org/wiki/Standard_deviation#Rapid_calculation_methods private int _n; private double _sx; private double _sxx; public void Clear() { _n = 0; _sx = 0; _sxx = 0; } public void Add(double x) { _n++; _sx += x; _sxx += x * x; } public double GetMean() { try { return _sx / _n; } catch { // This seems to be unnecessary. C# will throw an exception when an integer or // decimal is divided by 0. But for doubles it retunrs some non-finite value. // http://msdn.microsoft.com/en-us/library/system.dividebyzeroexception.aspx return Double.NaN; } } public double GetStandardDeviation() { try { return Math.Sqrt(_n * _sxx - _sx * _sx) / _n; } catch { return Double.NaN; } } public double GetSampleStandardDeviation() { try { return Math.Sqrt((_n * _sxx - _sx * _sx) / (_n * (_n - 1))); } catch { return Double.NaN; } } } private void accumulatorCheckBox_CheckedChanged(object sender, EventArgs e) { averageWeightCheckBox.Enabled = accumulatorCheckBox.Checked; } private void showStdDevCheckBox_CheckedChanged(object sender, EventArgs e) { ShowHideStdDev(); } private void ShowHideStdDev() { bool visisble = showStdDevCheckBox.Checked; foreach (Series series in _stdSeriesList) series.Enabled = visisble; } /// /// Just a placeholder for now. This allowes us to implement ISaveLayout easily. /// static public WindowIconCache WindowIconCache = new WindowIconCache("MM2"); static public String FORM_TYPE = "MARKET_MASTER_2"; void ISaveLayout.SaveLayout(System.Xml.XmlNode parent) { XmlNode description = LayoutManager.SaveBase(parent, this, FORM_TYPE); XmlNode mm2 = description.NewNode("MM2"); mm2.SetProperty("BAST_STRATEGY", baseStrategyTextBox.Text); mm2.SetProperty("PIVOT_FILTER", pivotFilterTextBox.Text); mm2.SetProperty("ACCUMULATOR_FILTER", accumulatorTextBox.Text); mm2.SetProperty("WEIGHT_FILTER", averageWeightTextBox.Text); mm2.SetProperty("ACCUMULATOR_CHECK", accumulatorCheckBox.Checked); mm2.SetProperty("WEIGHT_CHECK", averageWeightCheckBox.Checked); mm2.SetProperty("SHOW_STD_DEV", showStdDevCheckBox.Checked); mm2.SetProperty("TIME5", timeTrackBar.Value); mm2.SetProperty("EXPLICIT_RED_STRATEGY", explicitRedStrategyTextBox.Text); mm2.SetProperty("RED_STRATEGY_CHECK", explicitRedStrategyCheckBox.Checked); } static public void RegisterLayout() { //LayoutManager.Instance().AddRestoreRule(FORM_TYPE, (RestoreLayout)delegate(XmlNode description, bool ignorePosition, bool cascadePosition) LayoutManager.Instance().AddRestoreRule(FORM_TYPE, (RestoreLayout)delegate (XmlNode description, bool ignorePosition, bool cascadePosition, bool dockPanelMode, string mainDockPanelName, string mainDockPanelTitle, string dockPanelID) { IConnectionMaster connectionMaster = GuiEnvironment.FindConnectionMaster(description.Property("CONNECTION")); if (null == connectionMaster) { // We could report an error here, but it's simpler just to do nothing. Any error message we tried to // report would probably be confusing at best to the user. } else { MarketMaster2 form = new MarketMaster2(connectionMaster); LayoutManager.RestoreBase(description, form, ignorePosition, cascadePosition); XmlNode mm2 = description.Node("MM2"); form.baseStrategyTextBox.Text = mm2.Property("BAST_STRATEGY"); form.pivotFilterTextBox.Text = mm2.Property("PIVOT_FILTER"); form.accumulatorTextBox.Text = mm2.Property("ACCUMULATOR_FILTER"); form.averageWeightTextBox.Text = mm2.Property("WEIGHT_FILTER"); form.accumulatorCheckBox.Checked = mm2.Property("ACCUMULATOR_CHECK", false); form.averageWeightCheckBox.Checked = mm2.Property("WEIGHT_CHECK", false); form.showStdDevCheckBox.Checked = mm2.Property("SHOW_STD_DEV", false); try { form.timeTrackBar.Value = mm2.Property("TIME5", form.timeTrackBar.Value); } catch { } form.explicitRedStrategyTextBox.Text = mm2.Property("EXPLICIT_RED_STRATEGY"); form.explicitRedStrategyCheckBox.Checked = mm2.Property("RED_STRATEGY_CHECK", false); } }); } private bool _pinned = false; public bool Pinned { get { return _pinned; } set { _pinned = value; } } WindowIconCache ISaveLayout.WindowIconCache { get { return WindowIconCache; } } private void explicitRedStrategyCheckBox_CheckedChanged(object sender, EventArgs e) { pivotFilterTextBox.Enabled = !explicitRedStrategyCheckBox.Checked; explicitRedStrategyTextBox.Enabled = explicitRedStrategyCheckBox.Checked; } } }