using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text.RegularExpressions; using System.Threading; using System.Windows.Forms; using System.Xml; using TradeIdeas.MiscSupport; using TradeIdeas.ServerConnection; using TradeIdeas.TIProData; using TradeIdeas.TIProData.Configuration; using TradeIdeas.TIProData.Interfaces; using TradeIdeas.XML; namespace TradeIdeas.TIProGUI.LimitAlerts { public partial class LimitAlertsGrid : UserControl { private readonly IConnectionMaster _connectionMaster; /// /// Main binding list that is used by dataGridView1. /// private BindingList _rowDatas = new BindingList(); /// /// All limit alerts whether they match the current filter setting or not. /// private BindingListWithRemoving _limitAlerts = new BindingListWithRemoving(); /// /// All limit alerts that match the current filter setting. /// private BindingList _filteredLimitAlerts = new BindingList(); /// /// Maps a limit alert to its rowdata object contained in _rowDatas. Many operations require finding the limit alert's rowdata object to refresh it /// this dictionary makes finding it really fast. /// private Dictionary _limitAlertToRowData = new Dictionary(); // add parameter for form type - RVH20210602 //List _columns = RowDataHelper.GetColumnInfoFromClass(typeof(LimitAlert)); List _columns = RowDataHelper.GetColumnInfoFromClass(typeof(LimitAlert), FORM_TYPE); private Dictionary _dataGridViewColumns = new Dictionary(); private Dictionary _dataGridViewColumnsNames = new Dictionary(); private Dictionary _columnInfosMap = new Dictionary(); private System.Windows.Forms.Timer _refreshTimer = new System.Windows.Forms.Timer(); // new variable for symbol linking channel - RVH20210927 private string _linkChannel = SymbolLinkChannels.DefaultLinkChannel; private DataCell.SymbolPlusLayoutType _symbolPlusLayout = DataCell.SymbolPlusLayoutType.Off; private bool _showSymbolPlus = false; public DataCell.SymbolPlusLayoutType SymbolPlusLayout { get { return _symbolPlusLayout; } set { _symbolPlusLayout = value; updateSymbolPlusMenuItem(); uncheckCurrentSymbolPlusItem(); var correspondingOption = _toolStripMenuItemSymbolPlus.DropDownItems.Cast().FirstOrDefault(item => (DataCell.SymbolPlusLayoutType)item.Tag == _symbolPlusLayout); if (correspondingOption != null) correspondingOption.PerformClick(); } } public LimitAlertsGrid(IConnectionMaster connectionMaster) { _connectionMaster = connectionMaster; InitializeComponent(); GuiEnvironment.QuoteFeedManager.QuoteFeedDataReceived += QuoteFeedManager_QuoteFeedDataReceived; InstallColumns(); // check to see if auto-size columns is set - RVH20210720 if (GuiEnvironment.autosizeColumns) dataGridView1.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill; ExtensionMethods.DoubleBufferedDatagrid(dataGridView1, true); dataGridView1.DataSource = _rowDatas; dataGridView1.CellDoubleClick += dataGridView1_CellDoubleClick; dataGridView1.CellMouseEnter += dataGridView1_CellMouseEnter; dataGridView1.CellMouseLeave += dataGridView1_CellMouseLeave; dataGridView1.Click += dataGridView1_Click; dataGridView1.CellMouseDown += dataGridView1_CellMouseDown; BuildContextMenu(); updateSymbolPlusMenuItem(); chooseRowSelect(); _refreshTimer = new System.Windows.Forms.Timer(); _refreshTimer.Interval = 1000; _refreshTimer.Tick += _refreshTimer_Tick; _refreshTimer.Start(); } void _refreshTimer_Tick(object sender, EventArgs e) { this.BeginInvokeIfRequired(delegate () { RowDataHelper.DoSorting(dataGridView1, _rowDatas, _sortByColumn, _sortType); }); } public void SetSelectedRow() { this.InvokeIfRequired(delegate () { if (dataGridView1.Rows.Count > 0 && dataGridView1.Columns.Count > 0) { dataGridView1.ClearSelection(); dataGridView1.Rows[0].Cells[0].Selected = true; dataGridView1.FirstDisplayedScrollingRowIndex = 0; } }); } /// /// Updates any gradients that should be updated on a periodic basis. /// public void UpdateGradients() { UpdatePandLGradient("DollarsSinceTriggered"); List> cellsToRefresh = GetCellsToRefresh(); foreach (Tuple cell in cellsToRefresh.ToList()) { dataGridView1.InvalidateCell(cell.Item1, cell.Item2); } } private List> GetCellsToRefresh() { List> toReturn = new List>(); List updateColumns = GetUpdateColumns(); foreach (RowData rowData in _rowDatas.ToList()) { int rowIndex = _rowDatas.IndexOf(rowData); foreach (int columnIndex in updateColumns.ToList()) { toReturn.Add(Tuple.Create(columnIndex, rowIndex)); } } return toReturn; } private List GetUpdateColumns() { List toReturn = new List(); foreach (ColumnInfo columnInfo in _columns.ToList()) { if (columnInfo.RealTimeUpdates) { if (_columnInfosMap.ContainsKey(columnInfo)) { DataGridViewColumn column = _columnInfosMap[columnInfo]; toReturn.Add(column.Index); } } } return toReturn; } private void UpdatePandLGradient(string columnCode) { ColumnInfo columnInfo = null; foreach (Column column in dataGridView1.Columns.OfType().ToList()) { if (null != column.ColumnInfo && column.ColumnInfo.WireName == columnCode) { columnInfo = column.ColumnInfo; break; } } if (null != columnInfo) { double averageProfit = 0; List rowDatas = _rowDatas.ToList(); var positiveRows = rowDatas.Where(x => x.GetAsDouble(columnCode, 0.0) > 0); if (positiveRows.Count() > 0) averageProfit = positiveRows.Average(x => x.GetAsDouble(columnCode, 0.0)); double averageLoss = 0; var negativeRows = rowDatas.Where(x => x.GetAsDouble(columnCode, 0.0) < 0); if (negativeRows.Count() > 0) averageLoss = Math.Abs(negativeRows.Average(x => x.GetAsDouble(columnCode, 0.0))); double max = Math.Max(averageProfit, averageLoss); if (max > 0) { columnInfo.GradientInfo = new GradientInfo(); columnInfo.GradientInfo.Add(max, Color.Green); columnInfo.GradientInfo.Add(0, Color.Black); columnInfo.GradientInfo.Add(-max, Color.Red); } } } void QuoteFeedManager_QuoteFeedDataReceived(string symbol, RowData data) { this.BeginInvokeIfRequired(delegate () { RefreshData(symbol); }); } public void setContextMenuFont(Font font) { dataGridView1.ContextMenuStrip.Font = font; } public DataGridView getDataGridView() { return dataGridView1; } private const string FORM_TYPE = "LIMIT_ALERTS"; static private bool IsDuplicate(LimitAlert limitAlert) { if (GuiEnvironment.LimitAlerts.Count > 0) { if (GuiEnvironment.LimitAlerts.Count(x => x.Symbol == limitAlert.Symbol && x.Price == limitAlert.Price && x.IsLong == limitAlert.IsLong && x.LongAfter == limitAlert.LongAfter && x.AfterHours == limitAlert.AfterHours) > 0) return true; } return false; } static public void RegisterLayout() { // change parameters to accommodate dock panel changes - RVH20210402 //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 (!connectionMaster.LoginManager.IsDemo) { int restoredAlerts = 0; int duplicateAlerts = 0; int notLoadedAlerts = 0; string notLoadedMessage = ""; string marketPremium = GuiEnvironment.MarketingLink; foreach (XmlNode limitAlertNode in description.ChildNodes) { LimitAlert limitAlert = new LimitAlert(); limitAlert.Restore(limitAlertNode); bool duplicate = IsDuplicate(limitAlert); if (duplicate) duplicateAlerts++; else { if ((GuiEnvironment.LimitAlerts.Count + restoredAlerts) < GuiEnvironment.MaxLimitAlerts) { restoredAlerts++; GuiEnvironment.LimitAlertsMessageManager.CreateNewLimitAlert(limitAlert); } else notLoadedAlerts++; } } if (notLoadedAlerts == 1) notLoadedMessage = Environment.NewLine + "Skipped " + notLoadedAlerts + " alert due to exceeding the maximun number of price alerts of " + GuiEnvironment.MaxLimitAlerts + "."; else if (notLoadedAlerts > 0) notLoadedMessage = Environment.NewLine + "Skipped " + notLoadedAlerts + " alerts due to exceeding the maximun number of price alerts of " + GuiEnvironment.MaxLimitAlerts + "."; if (GuiEnvironment.MaxLimitAlerts == GuiEnvironment.STDLIMTALERTS && notLoadedAlerts > 0) { UpgradeMessageBox.Show("Created " + restoredAlerts + " new alerts. Found " + duplicateAlerts + " duplicates." + notLoadedMessage, "Copied Price Alerts", "Acccept", "Cancel"); } else MessageBox.Show("Created " + restoredAlerts + " new alerts. Found " + duplicateAlerts + " duplicates." + notLoadedMessage, "Copied Price Alerts"); } else { UpgradeMessageBox.Show(Environment.NewLine + Environment.NewLine + "You need a subscription to use Price Alerts. Click link to learn more.", "Need Subscription", "OK", ""); } }); } void dataGridView1_CellMouseDown(object sender, DataGridViewCellMouseEventArgs e) { if (e.RowIndex != -1) { if (e.Button == System.Windows.Forms.MouseButtons.Right) { // if multiple rows are already selected, then don't change the selection. // if one or zero rows is selected, then change selected row to the one you right clicked on. if (!MultipleRowsSelected()) { dataGridView1.ClearSelection(); dataGridView1.Rows[e.RowIndex].Cells[e.ColumnIndex].Selected = true; SetMenuItemStatus(); } // See if this column is Symbol Plus. _showSymbolPlus = false; if (e.ColumnIndex >= 0) { Column column = dataGridView1.Columns[e.ColumnIndex] as Column; if (column != null && column.ColumnInfo != null && column.ColumnInfo.Format == "symbolplus") _showSymbolPlus = true; } } } } /// /// This function returns selected rows whether SelectionMode is FullRowSelect or CellSelect. /// It has been modified to improve performance. /// /// List of selected rows if any. private HashSet GetSelectedRows() { //DateTime start = DateTime.Now; HashSet selectedRows = new HashSet(); // In the event the user selects all the rows we can improve the datagrid performance // by not using the dataGridView1.SelectedCells collection. // https://msdn.microsoft.com/en-us/library/system.windows.forms.datagridview.areallcellsselected.aspx if (dataGridView1.AreAllCellsSelected(true)) { foreach (DataGridViewRow row in dataGridView1.Rows) { selectedRows.Add(row); } } else { foreach (DataGridViewCell selectedCell in dataGridView1.SelectedCells) { DataGridViewRow row = selectedCell.OwningRow; if (!selectedRows.Contains(row)) selectedRows.Add(row); } } //DateTime finish = DateTime.Now; //System.Diagnostics.Debug.WriteLine("GetSelectedRows() took " + (finish-start).TotalMilliseconds + "ms to find " + selectedRows.Count + " of " + dataGridView1.Rows.Count); return selectedRows; } private bool MultipleRowsSelected() { int rowCount = GetSelectedRows().Count; //System.Diagnostics.Debug.WriteLine("MultipleRowsSelected(), dataGridView1.SelectedCells.Count=" + dataGridView1.SelectedCells.Count + ", rowCount=" + rowCount); return rowCount > 1; } private bool SingleRowSelected() { return GetSelectedRows().Count == 1; } void dataGridView1_Click(object sender, EventArgs e) { if (!_hoveringOnHeader && null == _currentLimitAlert) dataGridView1.ClearSelection(); } void dataGridView1_CellMouseLeave(object sender, DataGridViewCellEventArgs e) { _hoveringOnHeader = false; _currentLimitAlert = null; } private bool _hoveringOnHeader = false; private LimitAlert _currentLimitAlert = null; void dataGridView1_CellMouseEnter(object sender, DataGridViewCellEventArgs e) { _hoveringOnHeader = e.RowIndex == -1; if (e.RowIndex != -1) { try { RowData rowData = dataGridView1.Rows[e.RowIndex].DataBoundItem as RowData; if (null != rowData) { LimitAlert limitAlert = rowData.Data["object"] as LimitAlert; if (null != limitAlert) _currentLimitAlert = limitAlert; } } catch (Exception exc) { string debugView = exc.ToString(); } } } public void ClearAlerts() { this.InvokeIfRequired(delegate () { _rowDatas.Clear(); _limitAlerts.Clear(); _limitAlertToRowData.Clear(); }); } private ToolStripMenuItem _toolStripMenuItemDeleteAlerts = new ToolStripMenuItem(); private ToolStripMenuItem _toolStripMenuItemCreateAlert = new ToolStripMenuItem(); private ToolStripMenuItem _toolStripMenuItemEditAlert = new ToolStripMenuItem(); private ToolStripMenuItem _toolStripMenuItemSaveAlerts = new ToolStripMenuItem(); private ToolStripMenuItem _toolStripMenuItemActions = new ToolStripMenuItem(); private ToolStripMenuItem _toolStripMenuItemStockTwitsShare = new ToolStripMenuItem(); private ToolStripMenuItem _toolStripMenuItemPinned = new ToolStripMenuItem(); private ToolStripMenuItem _toolStripMenuItemDuplicate = new ToolStripMenuItem(); private ToolStripMenuItem _toolStripMenuItemSaveToCloud = new ToolStripMenuItem(); private ToolStripMenuItem _toolStripMenuItemSetAsDefault = new ToolStripMenuItem(); private ToolStripMenuItem _toolStripMenuItemColumns = new ToolStripMenuItem(); private ToolStripMenuItem _toolStripMenuItemShowFilter = new ToolStripMenuItem(); private ToolStripMenuItem _toolStripMenuItemShowChartIntraday = new ToolStripMenuItem(); private ToolStripMenuItem _toolStripMenuItemShowChartDaily = new ToolStripMenuItem(); // new menu item for symbol linking - RVH20210927 private ToolStripMenuItem _toolStripMenuItemSymbolLinking = new ToolStripMenuItem(); // New menu item for symbol plus setting. private ToolStripMenuItem _toolStripMenuItemSymbolPlus = new ToolStripMenuItem(); private void updateSymbolPlusMenuItem() { if (dataGridView1.ContextMenuStrip != null) { _toolStripMenuItemSymbolPlus.Checked = _symbolPlusLayout != DataCell.SymbolPlusLayoutType.Off; } } private void uncheckCurrentSymbolPlusItem() { var currentSelectedOption = _toolStripMenuItemSymbolPlus.DropDownItems.Cast().FirstOrDefault(item => item.Checked); if (currentSelectedOption != null) currentSelectedOption.Checked = false; } private void addSymbolPlusMenuOptions() { ToolStripMenuItem toolStripMenuItem1 = new ToolStripMenuItem("Normal"); toolStripMenuItem1.Tag = DataCell.SymbolPlusLayoutType.Normal; toolStripMenuItem1.Click += delegate { if (!toolStripMenuItem1.Checked) { DataGridViewHelper.SetSymbolPlusLayout(dataGridView1, DataCell.SymbolPlusLayoutType.Normal); _symbolPlusLayout = DataCell.SymbolPlusLayoutType.Normal; uncheckCurrentSymbolPlusItem(); toolStripMenuItem1.Checked = true; } else { DataGridViewHelper.SetSymbolPlusLayout(dataGridView1, DataCell.SymbolPlusLayoutType.Off); _symbolPlusLayout = DataCell.SymbolPlusLayoutType.Off; uncheckCurrentSymbolPlusItem(); } DataGridViewHelper.SetRowHeight(dataGridView1, _symbolPlusLayout); UpdateSymbolPlus(); updateSymbolPlusMenuItem(); }; ToolStripMenuItem toolStripMenuItem2 = new ToolStripMenuItem("Logo Only"); toolStripMenuItem2.Tag = DataCell.SymbolPlusLayoutType.LogoOnly; toolStripMenuItem2.Click += delegate { if (!toolStripMenuItem2.Checked) { DataGridViewHelper.SetSymbolPlusLayout(dataGridView1, DataCell.SymbolPlusLayoutType.LogoOnly); _symbolPlusLayout = DataCell.SymbolPlusLayoutType.LogoOnly; uncheckCurrentSymbolPlusItem(); toolStripMenuItem2.Checked = true; } else { DataGridViewHelper.SetSymbolPlusLayout(dataGridView1, DataCell.SymbolPlusLayoutType.Off); _symbolPlusLayout = DataCell.SymbolPlusLayoutType.Off; uncheckCurrentSymbolPlusItem(); } DataGridViewHelper.SetRowHeight(dataGridView1, _symbolPlusLayout); UpdateSymbolPlus(); updateSymbolPlusMenuItem(); }; ToolStripMenuItem toolStripMenuItem3 = new ToolStripMenuItem("No Logo"); toolStripMenuItem3.Tag = DataCell.SymbolPlusLayoutType.NoLogo; toolStripMenuItem3.Click += delegate { if (!toolStripMenuItem3.Checked) { DataGridViewHelper.SetSymbolPlusLayout(dataGridView1, DataCell.SymbolPlusLayoutType.NoLogo); _symbolPlusLayout = DataCell.SymbolPlusLayoutType.NoLogo; uncheckCurrentSymbolPlusItem(); toolStripMenuItem3.Checked = true; } else { DataGridViewHelper.SetSymbolPlusLayout(dataGridView1, DataCell.SymbolPlusLayoutType.Off); _symbolPlusLayout = DataCell.SymbolPlusLayoutType.Off; uncheckCurrentSymbolPlusItem(); } DataGridViewHelper.SetRowHeight(dataGridView1, _symbolPlusLayout); UpdateSymbolPlus(); updateSymbolPlusMenuItem(); }; ToolStripMenuItem toolStripMenuItem4 = new ToolStripMenuItem("Symbol/Logo"); toolStripMenuItem4.Tag = DataCell.SymbolPlusLayoutType.SymbolLogo; toolStripMenuItem4.Click += delegate { if (!toolStripMenuItem4.Checked) { DataGridViewHelper.SetSymbolPlusLayout(dataGridView1, DataCell.SymbolPlusLayoutType.SymbolLogo); _symbolPlusLayout = DataCell.SymbolPlusLayoutType.SymbolLogo; uncheckCurrentSymbolPlusItem(); toolStripMenuItem4.Checked = true; } else { DataGridViewHelper.SetSymbolPlusLayout(dataGridView1, DataCell.SymbolPlusLayoutType.Off); _symbolPlusLayout = DataCell.SymbolPlusLayoutType.Off; uncheckCurrentSymbolPlusItem(); } DataGridViewHelper.SetRowHeight(dataGridView1, _symbolPlusLayout); UpdateSymbolPlus(); updateSymbolPlusMenuItem(); }; _toolStripMenuItemSymbolPlus.DropDownItems.Add(toolStripMenuItem1); _toolStripMenuItemSymbolPlus.DropDownItems.Add(toolStripMenuItem2); _toolStripMenuItemSymbolPlus.DropDownItems.Add(toolStripMenuItem3); _toolStripMenuItemSymbolPlus.DropDownItems.Add(toolStripMenuItem4); } private void BuildContextMenu() { dataGridView1.ContextMenuStrip = new System.Windows.Forms.ContextMenuStrip(); _toolStripMenuItemSymbolPlus.Text = "Symbol Plus"; addSymbolPlusMenuOptions(); _toolStripMenuItemSymbolPlus.Click += delegate { if (_toolStripMenuItemSymbolPlus.Checked) { var selectedOption = _toolStripMenuItemSymbolPlus.DropDownItems.Cast().ToList().FirstOrDefault(item => item.Checked); if (selectedOption != null) selectedOption.PerformClick(); } else { var normalOption = _toolStripMenuItemSymbolPlus.DropDownItems.Cast().ToList().FirstOrDefault(item => item.Text == "Normal"); if (normalOption != null) normalOption.PerformClick(); } }; dataGridView1.ContextMenuStrip.Items.Add(_toolStripMenuItemSymbolPlus); _toolStripMenuItemCreateAlert.Text = "Create New Price Alert..."; _toolStripMenuItemCreateAlert.Click += _toolStripMenuItemCreateAlert_Click; dataGridView1.ContextMenuStrip.Items.Add(_toolStripMenuItemCreateAlert); _toolStripMenuItemEditAlert.Text = "Edit Price Alert..."; _toolStripMenuItemEditAlert.Click += _toolStripMenuItemEditAlert_Click; dataGridView1.ContextMenuStrip.Items.Add(_toolStripMenuItemEditAlert); _toolStripMenuItemDeleteAlerts.Text = "Delete Price Alert..."; _toolStripMenuItemDeleteAlerts.Click += _toolStripMenuItemDeleteAlerts_Click; dataGridView1.ContextMenuStrip.Items.Add(_toolStripMenuItemDeleteAlerts); dataGridView1.ContextMenuStrip.Items.Add(new ToolStripSeparator()); _toolStripMenuItemSaveAlerts.Text = "Share Price Alert..."; _toolStripMenuItemSaveAlerts.Click += _toolStripMenuItemSaveAlerts_Click; dataGridView1.ContextMenuStrip.Items.Add(_toolStripMenuItemSaveAlerts); _toolStripMenuItemStockTwitsShare.Text = "Post Price Alert to StockTwits..."; _toolStripMenuItemStockTwitsShare.Click += _toolStripMenuItemStockTwitsShare_Click; dataGridView1.ContextMenuStrip.Items.Add(_toolStripMenuItemStockTwitsShare); dataGridView1.ContextMenuStrip.Items.Add(new ToolStripSeparator()); _toolStripMenuItemActions.Text = "Actions..."; _toolStripMenuItemActions.Click += _toolStripMenuItemActions_Click; dataGridView1.ContextMenuStrip.Items.Add(_toolStripMenuItemActions); _toolStripMenuItemColumns.Text = "Columns..."; _toolStripMenuItemColumns.Click += _toolStripMenuItemColumns_Click; dataGridView1.ContextMenuStrip.Items.Add(_toolStripMenuItemColumns); _toolStripMenuItemPinned.Text = "Pinned"; _toolStripMenuItemPinned.CheckOnClick = true; _toolStripMenuItemPinned.Checked = _pinned; _toolStripMenuItemPinned.Click += _toolStripMenuItemPinned_Click; dataGridView1.ContextMenuStrip.Items.Add(_toolStripMenuItemPinned); _toolStripMenuItemShowFilter.Text = "Show Filter"; _toolStripMenuItemShowFilter.CheckOnClick = true; _toolStripMenuItemShowFilter.Checked = _showFilter; _toolStripMenuItemShowFilter.Click += _toolStripMenuItemShowFilter_Click; dataGridView1.ContextMenuStrip.Items.Add(_toolStripMenuItemShowFilter); _toolStripMenuItemShowChartIntraday.Text = "Show Intraday P&&L"; _toolStripMenuItemShowChartIntraday.CheckOnClick = true; _toolStripMenuItemShowChartIntraday.Checked = _showChartIntraday; _toolStripMenuItemShowChartIntraday.Click += _toolStripMenuItemShowChartIntraday_Click; dataGridView1.ContextMenuStrip.Items.Add(_toolStripMenuItemShowChartIntraday); _toolStripMenuItemShowChartDaily.Text = "Show Daily P&&L"; _toolStripMenuItemShowChartDaily.CheckOnClick = true; _toolStripMenuItemShowChartDaily.Checked = _showChartDaily; _toolStripMenuItemShowChartDaily.Click += _toolStripMenuItemShowChartDaily_Click; dataGridView1.ContextMenuStrip.Items.Add(_toolStripMenuItemShowChartDaily); // new menu item for symbol linking - RVH20210927 _toolStripMenuItemSymbolLinking.Text = "Symbol Linking"; //_toolStripMenuItemSymbolLinking.DropDownOpening += _toolStripMenuItemSymbolLinking_DropDownOpening; // update symbol linking channels when the context menu is opening dataGridView1.ContextMenuStrip.Opening += _toolStripMenuItemSymbolLinking_DropDownOpening; dataGridView1.ContextMenuStrip.Items.Add(_toolStripMenuItemSymbolLinking); dataGridView1.ContextMenuStrip.Items.Add(new ToolStripSeparator()); _toolStripMenuItemDuplicate.Text = "Duplicate"; _toolStripMenuItemDuplicate.Click += _toolStripMenuItemDuplicate_Click; dataGridView1.ContextMenuStrip.Items.Add(_toolStripMenuItemDuplicate); _toolStripMenuItemSaveToCloud.Text = "Save to Cloud..."; _toolStripMenuItemSaveToCloud.Click += _toolStripMenuItemSaveToCloud_Click; dataGridView1.ContextMenuStrip.Items.Add(_toolStripMenuItemSaveToCloud); _toolStripMenuItemSetAsDefault.Text = "Save As Default"; _toolStripMenuItemSetAsDefault.CheckOnClick = true; _toolStripMenuItemSetAsDefault.Click += _toolStripMenuItemSetAsDefault_Click; dataGridView1.ContextMenuStrip.Items.Add(_toolStripMenuItemSetAsDefault); dataGridView1.ContextMenuStrip.Opening += ContextMenuStrip_Opening; SetMenuItemStatus(); } private void _toolStripMenuItemSymbolLinking_DropDownOpening(object sender, EventArgs e) { _toolStripMenuItemSymbolLinking.DropDown.Items.Clear(); //Detect if the current window is in floating mode var floatingMode = string.IsNullOrEmpty(GuiEnvironment.GetDockWindowID(this.ParentForm)); foreach (KeyValuePair channel in SymbolLinkChannels.Channels) { if (channel.Key == SymbolLinkChannels.DockWindowChannel && floatingMode) continue; ToolStripMenuItem menuItem = new ToolStripMenuItem(channel.Key, null, delegate { SetLinkChannel(channel.Key); //foreach (ToolStripMenuItem toolStripMenuItem in _toolStripMenuItemSymbolLinking.DropDownItems) //{ // if (toolStripMenuItem.Text == _linkChannel) // toolStripMenuItem.Checked = true; // else // toolStripMenuItem.Checked = false; //} }); menuItem.BackColor = channel.Value; Color foreColor = GradientInfo.AltColor(channel.Value); menuItem.ForeColor = foreColor; menuItem.Checked = _linkChannel == channel.Key; _toolStripMenuItemSymbolLinking.DropDown.Items.Add(menuItem); } } public void UpdateSymbolPlus(bool restoringLayout = false, int symbolPlusWidth = -1) { var column = dataGridView1.Columns.Cast().FirstOrDefault(gridColumn => gridColumn.ColumnInfo.Format == "symbolplus"); if (column != null) { var adjustedColumn = DataCell.GetColumn(column.ColumnInfo, _connectionMaster, this, symbolPlusLayoutType: _symbolPlusLayout); dataGridView1.Columns[column.Index].MinimumWidth = adjustedColumn.MinimumWidth; if (!restoringLayout) dataGridView1.Columns[column.Index].Width = adjustedColumn.MinimumWidth; else { if (symbolPlusWidth > -1) dataGridView1.Columns[column.Index].Width = symbolPlusWidth; } } } //private void _toolStripMenuItemSymbolLinking_DropDownOpening(object sender, EventArgs e) //{ // _toolStripMenuItemSymbolLinking.DropDown.Items.Clear(); // foreach (KeyValuePair channel in SymbolLinkChannels.Channels) // { // ToolStripMenuItem menuItem = new ToolStripMenuItem(channel.Key, null, delegate // { // SetLinkChannel(channel.Key); // }); // menuItem.BackColor = channel.Value; // menuItem.Checked = _linkChannel == channel.Key; // _toolStripMenuItemSymbolLinking.DropDown.Items.Add(menuItem); // } //} void _toolStripMenuItemShowChartDaily_Click(object sender, EventArgs e) { LimitAlertsForm parent = GetParent(); if (null != parent) { parent.ShowHideDailyChart(_toolStripMenuItemShowChartDaily.Checked); _showChartDaily = _toolStripMenuItemShowChartDaily.Checked; } } void _toolStripMenuItemShowChartIntraday_Click(object sender, EventArgs e) { LimitAlertsForm parent = GetParent(); if (null != parent) { parent.ShowHideIntradayChart(_toolStripMenuItemShowChartIntraday.Checked); _showChartIntraday = _toolStripMenuItemShowChartIntraday.Checked; } } void _toolStripMenuItemSaveToCloud_Click(object sender, EventArgs e) { LimitAlertsForm parent = GetParent(); if (null != parent) parent.SaveToCloud(); } void _toolStripMenuItemDuplicate_Click(object sender, EventArgs e) { LimitAlertsForm parent = GetParent(); if (null != parent) parent.Duplicate(); } void _toolStripMenuItemShowFilter_Click(object sender, EventArgs e) { LimitAlertsForm parent = GetParent(); if (null != parent) { parent.ShowHideFilterPanel(_toolStripMenuItemShowFilter.Checked); _showFilter = _toolStripMenuItemShowFilter.Checked; } } void _toolStripMenuItemColumns_Click(object sender, EventArgs e) { ConfigWindowWrapper configWindowWrapper = new TraditionalConfigWindowWrapper(ConfigurationType.CustomColumns, _connectionMaster); configWindowWrapper.VisibleCustomColumns = RowDataHelper.GetVisibleColumns(_dataGridViewColumns); configWindowWrapper.AllCustomColumns = RowDataHelper.GetAllColumns(_dataGridViewColumns); configWindowWrapper.ShowIt(); // TODO the following defaultColumns should be customized for limit alerts. string[] defaultColumns = new string[] { "Entry Time", "L/S", "Symbol", "Entry Price", "Exit Price", "Profit", "Strategy" }; if (!configWindowWrapper.CanceledAIColumnConfig) RowDataHelper.ApplyVisibleColumns(defaultColumns, _columns, configWindowWrapper.VisibleCustomColumns, _dataGridViewColumnsNames); } private const string _defaultFileName = "DEFAULT_LIMITALERTS.WTI"; void _toolStripMenuItemSetAsDefault_Click(object sender, EventArgs e) { string programDirFileName = LayoutManager.Instance().Directory + "\\" + _defaultFileName; LimitAlertsForm parent = GetParent(); if (null != parent) LayoutManager.Instance().SaveOne(parent, programDirFileName); } void _toolStripMenuItemPinned_Click(object sender, EventArgs e) { _pinned = _toolStripMenuItemPinned.Checked; } void _toolStripMenuItemStockTwitsShare_Click(object sender, EventArgs e) { if (dataGridView1.SelectedCells.Count == 1) { RowData rowData = dataGridView1.SelectedCells[0].OwningRow.DataBoundItem as RowData; if (null != rowData) { LimitAlert limitAlert = rowData.GetAsObject("object") as LimitAlert; if (null != limitAlert) SendToStockTwits(limitAlert); } } } private string _lastTiniedCloudLink = ""; private string _lastCloudLink = ""; private LimitAlert _lastSendingLimitAlert = null; private void SendToStockTwits(LimitAlert limitAlert) { _lastSendingLimitAlert = limitAlert; List saveAlerts = new List(); saveAlerts.Add(limitAlert); _lastCloudLink = SaveLimitAlerts(saveAlerts, false); _lastTiniedCloudLink = ""; new Thread(GetTinyUrl).Start(); } private void StartStockTwitsForm() { LimitAlert limitAlert = _lastSendingLimitAlert; RowData rowData = new RowData(); rowData.Data.Add("SYMBOL", limitAlert.Symbol); string limitAlertAsString = "alert when price" + (limitAlert.IsLong ? " rises to " : " falls to ") + "$" + limitAlert.Price.ToString("N2"); if (limitAlert.InvalidPrice.HasValue) limitAlertAsString += " unless it reaches $" + limitAlert.InvalidPrice.Value.ToString("N2") + " first"; if (_lastTiniedCloudLink != "") limitAlertAsString += " " + _lastTiniedCloudLink; SendToForm sendToForm = new SendToForm(AlertForm.SocialNetwork.STWITS, rowData, "", "", limitAlertAsString); sendToForm.Show(); LimitAlertsForm parentForm = GetParent(); if (null != parentForm) { GuiEnvironment.SetWindowOpeningPosition(sendToForm, parentForm); } } private void GetTinyUrl() { _lastTiniedCloudLink = GuiEnvironment.makeTinyURL(_lastCloudLink); if (_lastTiniedCloudLink != "") this.BeginInvokeIfRequired(StartStockTwitsForm); } void ContextMenuStrip_Opening(object sender, CancelEventArgs e) { SetMenuItemStatus(); // Show or hide the Symbol Plus menu item. _toolStripMenuItemSymbolPlus.Visible = _showSymbolPlus; if (null != GuiEnvironment.RobotStrategyMenuCode) { string symbol = ""; if (SingleRowSelected()) { RowData rowData = dataGridView1.SelectedCells[0].OwningRow.DataBoundItem as RowData; if (null != rowData) { LimitAlert limitAlert = rowData.GetAsObject("object") as LimitAlert; if (null != limitAlert) symbol = limitAlert.Symbol; GuiEnvironment.RobotStrategyMenuCode(dataGridView1.ContextMenuStrip, symbol, "PRICE_ALERTS", rowData); } } else GuiEnvironment.RobotStrategyMenuCode(dataGridView1.ContextMenuStrip, "", "PRICE_ALERTS", null); } } void _toolStripMenuItemActions_Click(object sender, EventArgs e) { LimitAlertsForm parentForm = GetParent(); if (null != parentForm && null != parentForm.Actions) { // Hide show alert checkbox in the actions form since this feature is handled in this class. parentForm.Actions.HideShowAlert = true; parentForm.Actions.ShowDialog(); _toolStripMenuItemActions.Checked = parentForm.Actions.InUse(); } } void _toolStripMenuItemEditAlert_Click(object sender, EventArgs e) { if (SingleRowSelected()) { RowData rowData = dataGridView1.SelectedCells[0].OwningRow.DataBoundItem as RowData; if (null != rowData) { LimitAlert limitAlert = rowData.GetAsObject("object") as LimitAlert; if (null != limitAlert) { LimitAlertCreateForm editForm = new LimitAlertCreateForm(limitAlert.Symbol); editForm.WindowName = "Edit Price Alert"; editForm.Price = limitAlert.Price; editForm.Notes = limitAlert.Notes; editForm.InvalidPrice = limitAlert.InvalidPrice; editForm.Expires = limitAlert.Expires; editForm.Creation = limitAlert.Created; editForm.Updated = limitAlert.Updated; editForm.IsLong = limitAlert.IsLong; editForm.LongAfter = limitAlert.LongAfter; editForm.AfterHours = limitAlert.AfterHours; editForm.Id = limitAlert.Id; DialogResult result = editForm.ShowDialog(); if (result == DialogResult.OK) GuiEnvironment.LimitAlertsMessageManager.CreateNewLimitAlert(editForm.LimitAlert); } } } } void _toolStripMenuItemSaveAlerts_Click(object sender, EventArgs e) { HashSet selectedRows = GetSelectedRows(); if (selectedRows.Count > 0) { List toSave = new List(); foreach (DataGridViewRow row in selectedRows.ToList()) { RowData rowData = row.DataBoundItem as RowData; if (null != rowData) { LimitAlert limitAlert = rowData.GetAsObject("object") as LimitAlert; if (null != limitAlert) toSave.Add(limitAlert); } } SaveLimitAlerts(toSave); selectedRows.Clear(); } } /// /// Saves limit alerts to the cloud and brings up cloud link to share. /// /// List of LimitAlert objects to share. private string SaveLimitAlerts(List limitAlerts, bool bringUpCollaborateWindow = true) { XmlDocument xmlDoc = new XmlDocument(); XmlNode layoutNode = xmlDoc.CreateElement("LAYOUT"); xmlDoc.AppendChild(layoutNode); XmlNode topNode = layoutNode.NewNode("LIMIT_ALERTS"); topNode.SetProperty("FORM_TYPE", FORM_TYPE); string description = ""; List allSymbols = limitAlerts.Select(x => x.Symbol).ToList(); int uniqueSymbols = allSymbols.Distinct().Count(); string shortDescription = "Price Alerts - " + limitAlerts.Count; if (limitAlerts.Count == 1) shortDescription += " alert at $" + limitAlerts[0].Price.ToString("N2"); else shortDescription += " alerts"; shortDescription += " for "; if (uniqueSymbols == 1) shortDescription += allSymbols[0]; else shortDescription += uniqueSymbols + " symbols"; foreach (LimitAlert limitAlert in limitAlerts) { XmlNode limitAlertNode = topNode.NewNode("LIMIT_ALERT"); description += limitAlert.Symbol + " at $" + limitAlert.Price + "\n"; limitAlert.Save(limitAlertNode); } string layout = xmlDoc.OuterXml; if (System.Text.Encoding.UTF8.GetByteCount(layout) > 500000) MessageBox.Show("Unable to save these price alerts to the cloud. It is too big.", "Save or Share to Cloud"); else { Dictionary messageToServer = TalkWithServer.CreateMessage( "command", "save_to_cloud", "layout", layout, "icon", FORM_TYPE, "window_count", 0, "clear_previous_layout", "0", "short_description", shortDescription, "long_description", description); using (SaveToCloudResponse responseCloud = new SaveToCloudResponse(messageToServer, _connectionMaster.SendManager)) { responseCloud.StartPosition = FormStartPosition.CenterParent; responseCloud.ShowDialog(); string code = responseCloud._code; if (code != null && bringUpCollaborateWindow) { try { Clipboard.SetText(code); } catch { } CollaborateForm form = new CollaborateForm(); form.ConfigString = code; form.Text = "Share this Link"; form.SetCloudOptions(); form.ShowDialog(); form.Dispose(); } else return code; } } return ""; } void _toolStripMenuItemCreateAlert_Click(object sender, EventArgs e) { // If the user is at the max limit alert count then we will give them the option to delete the oldest // price alert and then proceeed to create the new price alert. if (GuiEnvironment.LimitAlerts.Count >= GuiEnvironment.MaxLimitAlerts) { GuiEnvironment.UpdateOldestLimitAlertForDeletion(); if (GuiEnvironment.MaxLimitAlerts == GuiEnvironment.STDLIMTALERTS) { DialogResult result1 = UpgradeMessageBox.Show("You have reached the maximum number of price alerts allowed of " + GuiEnvironment.MaxLimitAlerts + "." + Environment.NewLine + Environment.NewLine + "Click OK to delete the oldest Price Alert and continue to create the new price alert.", "Confirm Price Alert", "OK", "Cancel"); if (result1 == System.Windows.Forms.DialogResult.OK) { // Delete oldest GuiEnvironment.LimitAlertsMessageManager.DeleteAlert(GuiEnvironment.oldestLimitAlertForDeletion.Id); } else return; } else { DialogResult result1 = MessageBox.Show("You have reached the maximum number of price alerts allowed of " + GuiEnvironment.MaxLimitAlerts + "." + " Click OK to delete the oldest Price Alert and continue to create the new price alert.", "Confirm Price Alert", MessageBoxButtons.OKCancel); if (result1 == System.Windows.Forms.DialogResult.OK) { // Delete oldest limit alert GuiEnvironment.LimitAlertsMessageManager.DeleteAlert(GuiEnvironment.oldestLimitAlertForDeletion.Id); } else return; } } LimitAlertCreateForm limitAlertForm = new LimitAlertCreateForm("SPY"); limitAlertForm.Price = 200.00; limitAlertForm.IsLong = true; limitAlertForm.LongAfter = true; limitAlertForm.AfterHours = GuiEnvironment.LimitAlertAfterHours; DialogResult result = limitAlertForm.ShowDialog(); if (result == System.Windows.Forms.DialogResult.OK) { GuiEnvironment.LimitAlertAfterHours = limitAlertForm.AfterHours; GuiEnvironment.LimitAlertsMessageManager.CreateNewLimitAlert(limitAlertForm.LimitAlert); } } void _toolStripMenuItemDeleteAlerts_Click(object sender, EventArgs e) { HashSet selectedRows = GetSelectedRows(); if (selectedRows.Count > 0) { DialogResult result = MessageBox.Show("Delete " + selectedRows.Count + " selected price alerts?", "Confirm deletion", MessageBoxButtons.OKCancel); if (result == DialogResult.OK) { foreach (DataGridViewRow row in selectedRows.ToList()) { RowData rowData = row.DataBoundItem as RowData; if (null != rowData) { LimitAlert limitAlert = rowData.GetAsObject("object") as LimitAlert; if (null != limitAlert) GuiEnvironment.LimitAlertsMessageManager.DeleteAlert(limitAlert.Id); } } } SetSelectedRow(); } } /// /// Most recently retrieved filter values for symbols. /// private Dictionary> _currentValues = new Dictionary>(); private Dictionary GetValueStore(string symbol) { Dictionary toReturn = new Dictionary(); if (_currentValues.ContainsKey(symbol)) toReturn = _currentValues[symbol]; else _currentValues.Add(symbol, toReturn); return toReturn; } private void RefreshData(string symbol) { bool limitAlertUpdated = false; foreach (LimitAlert limitAlert in _limitAlerts.ToList()) { if (limitAlert.Symbol == symbol) { limitAlertUpdated = true; RowData currentValues = GuiEnvironment.QuoteFeedManager.GetCurrentValues(symbol); if (null != currentValues) { limitAlert.CompanyName = currentValues.GetCompanyName(); limitAlert.ChangeFromTheClose = currentValues.GetAsDouble("c_FCD", null); limitAlert.ChangeFromTheClosePercent = currentValues.GetAsDouble("c_FCP", null); limitAlert.RelativeVolume = currentValues.GetAsDouble("c_RV", null); limitAlert.Volatility = currentValues.GetAsDouble("c_VWV", null); limitAlert.TodayVolume = currentValues.GetAsInt("c_TV", null); limitAlert.CurrentPrice = currentValues.GetPrice(); AddLimitAlert(limitAlert); } } } if (limitAlertUpdated) UpdateColumnGradients(); } private void UpdateColumnGradients() { this.InvokeIfRequired(delegate () { UpdateColumnGradient("DistanceFromAlert"); UpdateColumnGradient("PercentFromAlert"); UpdateColumnGradient("VolatilityBarsFromAlert"); }); } private void UpdateColumnGradient(string columnCode) { ColumnInfo columnInfo = null; foreach (Column column in dataGridView1.Columns.OfType()) { if (null != column.ColumnInfo && column.ColumnInfo.WireName == columnCode) { columnInfo = column.ColumnInfo; break; } } if (null != columnInfo) { double averageProfit = 0; List rowDatas = _rowDatas.ToList(); var positiveRows = rowDatas.Where(x => x.GetAsDouble(columnCode, 0.0) > 0); if (positiveRows.Count() > 0) averageProfit = positiveRows.Average(x => x.GetAsDouble(columnCode, 0.0)); double averageLoss = 0; var negativeRows = rowDatas.Where(x => x.GetAsDouble(columnCode, 0.0) < 0); if (negativeRows.Count() > 0) averageLoss = Math.Abs(negativeRows.Average(x => x.GetAsDouble(columnCode, 0.0))); double max = Math.Max(averageProfit, averageLoss); if (max > 0) { columnInfo.GradientInfo = new GradientInfo(); columnInfo.GradientInfo.Add(max, Color.Green); columnInfo.GradientInfo.Add(0, Color.Black); columnInfo.GradientInfo.Add(-max, Color.Red); } } } /// /// Validate that the price alert is valid for the current market hours and the alert configuration /// /// /// private bool IsAlertValidAfterHours(LimitAlert limitAlert) { var now = ServerFormats.Now; var marketOpenLocal = GuiEnvironment.GetMarketOpenLocalTime(); var marketCloseLocal = GuiEnvironment.GetMarketCloseLocalTime(); var isMarketOpen = now > marketOpenLocal && now < marketCloseLocal; var result = isMarketOpen || (!isMarketOpen && limitAlert.AfterHours); return result; } public void AddLimitAlert(LimitAlert limitAlert, bool bringToFront = false) { // Enable"real time" updates in limit alerts. GuiEnvironment.QuoteFeedManager.AddQuoteFeed(limitAlert.Symbol); try { // Make a copy of the _limitAlerts to avert "Collection was modified enumeration operation may not execute" exceptions. // _limitAlerts is being modified List limitAlertList = _limitAlerts.ToList(); bool matched = limitAlertList.Where(x => x.Id == limitAlert.Id).Count() > 0; if (matched) { LimitAlert destination = limitAlertList.Where(x => x.Id == limitAlert.Id).First(); int index = _limitAlerts.IndexOf(destination); CopyRealTimeData(destination, limitAlert); _limitAlerts[index] = limitAlert; if (_limitAlertToRowData.ContainsKey(limitAlert)) _limitAlertToRowData[limitAlert].Data = limitAlert.ToRowData().Data; else _limitAlertToRowData.Add(limitAlert, limitAlert.ToRowData()); } else { if (_limitAlerts.Count <= GuiEnvironment.MaxLimitAlerts) _limitAlerts.Add(limitAlert); else System.Diagnostics.Debug.WriteLine("Skipped adding a price alert due to max price alert reached."); } limitAlertList.Clear(); } catch (Exception e) { string debugView = e.ToString(); } bool valid = IsAlertValidAfterHours(limitAlert); if (bringToFront && LimitAlertMatchesFilter(limitAlert) && valid) { try { Parent.BringToFront(); // Perfrom actions LimitAlertsForm parentForm = GetParent(); if (null != parentForm) { List actionSymbolList = new List(); actionSymbolList.Add(parentForm.Actions.GetTextToSpeak(GetAlertTemplate(), GetSubstitutionRules(limitAlert))); // Turn off ShowWindow in the action method since it is handled above. // Also exception occurs in the actions method when ShowWindow is set to true. parentForm.Actions.ShowWindow = false; parentForm.Actions.Perform(actionSymbolList); } // Sort before highlighting limit alert this.BeginInvokeIfRequired(delegate () { try { RowDataHelper.DoSorting(dataGridView1, _rowDatas, _sortByColumn, _sortType); RowDataHelper.FixCellHeaders(dataGridView1, _columns, _sortByColumn); HighlightLimitAlert(limitAlert); } catch (Exception e) { string debugView = e.ToString(); } }); } catch (Exception exception) { string debugView = exception.ToString(); } } } /// /// Returns the substitution rules for the speech alert template for this window type /// /// /// RowData object that contains the row information /// /// private Dictionary GetSubstitutionRules(LimitAlert alert) { var rules = new Dictionary(); rules.Add("SYMBOL", alert.Symbol); var direction = alert.IsLong ? "below" : "above"; var description = $"Price {alert.Price:0.00} has been reach from {direction}"; rules.Add("DESCRIPTION", description); var parentName = GetParent()?.Text; if (parentName != null) rules.Add("WINDOW_NAME", parentName); rules.Add("PRICE", alert.Price.ToString()); //pending review to detrmine what field to use as DESCRIPTION return rules; } /// /// Returns the speech alerts template for this window /// /// private string GetAlertTemplate() { return !string.IsNullOrEmpty(GuiEnvironment.PriceAlertAlertTemplate) ? GuiEnvironment.PriceAlertAlertTemplate : GuiEnvironment.DefaultAlertTemplate; } private void HighlightLimitAlert(LimitAlert limitAlert) { if (_limitAlertToRowData.ContainsKey(limitAlert)) { RowHighlighter rowHighlighter = new RowHighlighter(dataGridView1, _limitAlertToRowData[limitAlert], Color.Yellow, 20000, 1000); rowHighlighter.Start(); } } /* var matchingAlerts = _limitAlerts.Where(x => x.Id == limitAlert.Id); if (matchingAlerts.Count() > 0) { LimitAlert existingLimitAlert = matchingAlerts.First(); int limitIndex = _limitAlerts.IndexOf(limitAlert); if (limitIndex != -1) _limitAlerts[limitIndex] = limitAlert; if (_limitAlertToRowData.ContainsKey(existingLimitAlert)) { RowData rowData = _limitAlertToRowData[existingLimitAlert]; CopyRealTimeData(existingLimitAlert, limitAlert); this.InvokeIfRequired(delegate() { string previousStatus = rowData.GetAsString("Status", ""); rowData.Data = limitAlert.ToRowData().Data; int index = _rowDatas.IndexOf(rowData); if (index != -1) { if (LimitAlertMatchesFilter(limitAlert)) { dataGridView1.InvalidateRow(index); RowDataHelper.DoSorting(dataGridView1, _rowDatas, _sortByColumn, _sortType); if (previousStatus != "TRIGGERED" && limitAlert.Status == "TRIGGERED") { RowHighlighter rowHighlighter = new RowHighlighter(dataGridView1, rowData, Color.Yellow, 20000, 250); rowHighlighter.Start(); } } else { if (_filteredLimitAlerts.Contains(limitAlert)) _filteredLimitAlerts.Remove(limitAlert); _rowDatas.Remove(rowData); } } else if (LimitAlertMatchesFilter(limitAlert)) { if (!_filteredLimitAlerts.Contains(limitAlert)) _filteredLimitAlerts.Add(limitAlert); RowDataHelper.AddRowData(_rowDatas, rowData, _localSortEnabled, _sortByColumn, _sortType); } LimitAlertsForm parent = GetParent(); if (bringToFront && null != parent && !parent.Disposing && LimitAlertMatchesFilter(limitAlert)) parent.BringToFront(); }); } } else { _limitAlerts.Add(limitAlert); RowData rowData = limitAlert.ToRowData(); if (!_limitAlertToRowData.ContainsKey(limitAlert)) _limitAlertToRowData.Add(limitAlert, rowData); if (LimitAlertMatchesFilter(limitAlert)) { this.InvokeIfRequired(delegate() { RowDataHelper.AddRowData(_rowDatas, rowData, _localSortEnabled, _sortByColumn, _sortType); RefreshData(limitAlert.Symbol); LimitAlertsForm parent = GetParent(); if (bringToFront && null != parent && !parent.Disposing) parent.BringToFront(); }); } else if (_rowDatas.Contains(rowData)) { this.InvokeIfRequired(delegate() { _rowDatas.Remove(rowData); }); if (_filteredLimitAlerts.Contains(limitAlert)) _filteredLimitAlerts.Remove(limitAlert); } } if (bringToFront && LimitAlertMatchesFilter(limitAlert)) { List symbols = new List(); symbols.Add(limitAlert.Symbol); LimitAlertsForm parentForm = GetParent(); if (null != parentForm) parentForm.Actions.Perform(symbols); } } */ private void CopyRealTimeData(LimitAlert source, LimitAlert destination) { destination.RelativeVolume = source.RelativeVolume; destination.CurrentPrice = source.CurrentPrice; destination.CompanyName = source.CompanyName; destination.ChangeFromTheClose = source.ChangeFromTheClose; destination.ChangeFromTheClosePercent = source.ChangeFromTheClosePercent; destination.Volatility = source.Volatility; destination.TodayVolume = source.TodayVolume; } private void InstallColumns() { RowDataHelper.SetGridDefaults(dataGridView1); dataGridView1.MultiSelect = true; _columnInfosMap.Clear(); foreach (ColumnInfo columnInfo in _columns) { columnInfo.UseEmptyColor = false; DataGridViewColumn column = DataCell.GetColumn(columnInfo, _connectionMaster, this, null, false, this.CreateGraphics(), Font); RowDataHelper.SetHeaderCellDefaults(column.HeaderCell); if (columnInfo.InternalCode == "Strategy") //"Strategy" is the internal code of the strategy Name column { column.DefaultCellStyle.WrapMode = DataGridViewTriState.True; } else { column.DefaultCellStyle.WrapMode = DataGridViewTriState.False; } column.Visible = false; _dataGridViewColumns.Add(columnInfo, column); _dataGridViewColumnsNames.Add(columnInfo, column); dataGridView1.Columns.Add(column); _columnInfosMap.Add(columnInfo, column); } /* ColumnInfo currentPriceColumnInfo = new ColumnInfo(); currentPriceColumnInfo.Format = "2"; currentPriceColumnInfo.Description = "Current Price"; currentPriceColumnInfo.InternalCode = "CurrentPrice"; currentPriceColumnInfo.WireName = "CurrentPrice"; currentPriceColumnInfo.TextHeader = true; currentPriceColumnInfo.SizeHint = "XXXXX.XX"; DataGridViewColumn currentPriceColumn = DataCell.GetColumn(currentPriceColumnInfo, _connectionMaster, this, null, false, this.CreateGraphics(), Font); RowDataHelper.SetHeaderCellDefaults(currentPriceColumn.HeaderCell); _dataGridViewColumns.Add(currentPriceColumnInfo, currentPriceColumn); _dataGridViewColumnsNames.Add(currentPriceColumnInfo.Description, currentPriceColumn); dataGridView1.Columns.Add(currentPriceColumn); _columnInfosMap.Add(currentPriceColumnInfo, currentPriceColumn); ColumnInfo companyNameColumnInfo = new ColumnInfo(); companyNameColumnInfo.Format = ""; companyNameColumnInfo.Description = "Company Name"; companyNameColumnInfo.InternalCode = "CompanyName"; companyNameColumnInfo.WireName = "CompanyName"; companyNameColumnInfo.TextHeader = true; companyNameColumnInfo.SizeHint = "XXXXXXXXXXXXXXXXXXXX"; DataGridViewColumn companyNameColumn = DataCell.GetColumn(companyNameColumnInfo, _connectionMaster, this, null, false, this.CreateGraphics(), Font); RowDataHelper.SetHeaderCellDefaults(companyNameColumn.HeaderCell); _dataGridViewColumns.Add(companyNameColumnInfo, companyNameColumn); _dataGridViewColumnsNames.Add(companyNameColumnInfo.Description, companyNameColumn); dataGridView1.Columns.Add(companyNameColumn); _columnInfosMap.Add(companyNameColumnInfo, companyNameColumn); */ } private void dataGridView1_SelectionChanged(object sender, EventArgs e) { SetMenuItemStatus(); } private void SetMenuItemStatus() { this.BeginInvokeIfRequired(delegate () { HashSet selectedRows = GetSelectedRows(); _toolStripMenuItemDeleteAlerts.Enabled = _toolStripMenuItemSaveAlerts.Enabled = _toolStripMenuItemStockTwitsShare.Enabled = selectedRows.Count > 0; _toolStripMenuItemEditAlert.Enabled = selectedRows.Count == 1; if (selectedRows.Count > 1) { _toolStripMenuItemDeleteAlerts.Text = "Delete " + selectedRows.Count + " Selected Price Alerts..."; _toolStripMenuItemSaveAlerts.Text = "Share " + selectedRows.Count + " Selected Price Alerts..."; _toolStripMenuItemStockTwitsShare.Enabled = false; } else if (selectedRows.Count == 1) { RowData selectedRowData = dataGridView1.SelectedCells[0].OwningRow.DataBoundItem as RowData; if (null != selectedRowData) { LimitAlert limitAlert = selectedRowData.GetAsObject("object") as LimitAlert; if (null != limitAlert) { _toolStripMenuItemDeleteAlerts.Text = "Delete Price Alert for " + limitAlert.Symbol + " at " + limitAlert.Price + "..."; _toolStripMenuItemSaveAlerts.Text = "Share Price Alert for " + limitAlert.Symbol + " at " + limitAlert.Price + "..."; } } } else { _toolStripMenuItemDeleteAlerts.Text = "Delete Price Alerts..."; _toolStripMenuItemSaveAlerts.Text = "Share Price Alerts..."; } LimitAlertsForm parentForm = GetParent(); if (null != parentForm) { _toolStripMenuItemActions.Enabled = true; _toolStripMenuItemActions.Checked = parentForm.Actions.InUse(); _toolStripMenuItemShowChartDaily.Checked = ShowChartDaily; _toolStripMenuItemShowChartIntraday.Checked = ShowChartIntraday; } else _toolStripMenuItemActions.Enabled = _toolStripMenuItemActions.Checked = false; }); } private LimitAlertsForm GetParent() { LimitAlertsForm parentForm = null; if (null != Parent && null != Parent.Parent && Parent.Parent as LimitAlertsForm != null) parentForm = Parent.Parent as LimitAlertsForm; return parentForm; } internal void RemoveLimitAlert(LimitAlert limitAlert) { GuiEnvironment.LogMessage("[RemoveLimitAlert] Removing alert: " + limitAlert.ToFriendlyString()); if (_limitAlerts.Contains(limitAlert)) _limitAlerts.Remove(limitAlert); RemoveQuoteFeedIfNecessary(limitAlert.Symbol); } private void RemoveQuoteFeedIfNecessary(string symbol) { // TODO currently we use the QuoteFeedManager to get data. Currently QuoteFeedManager has // no way to unsubscribe. } private void SendToExternalLinking(int index) { RowData rowData = _rowDatas[index]; if (null != rowData && rowData.Data.ContainsKey("object")) { LimitAlert limitAlert = rowData.Data["object"] as LimitAlert; if (null != limitAlert) { string symbol = limitAlert.Symbol; if (symbol != "") { RowData linkRowData = new RowData(); if (limitAlert.Triggered.HasValue) linkRowData.Data.Add("TIME", ServerFormats.ToTimeT(limitAlert.Triggered.Value)); else linkRowData.Data.Add("TIME", ServerFormats.ToTimeT(limitAlert.Created)); linkRowData.Data.Add("SYMBOL", symbol); linkRowData.Data.Add("ENTRY_PRICE", limitAlert.Price); if (limitAlert.InvalidPrice.HasValue) linkRowData.Data.Add("STOP_PRICE", limitAlert.InvalidPrice); if (symbol != "") { RecordExternalLinkUseCase(linkRowData); // add symbol linking channel - RVH20210927 GuiEnvironment.sendSymbolToExternalConnector(symbol, "", "", null, linkRowData, linkChannel: _linkChannel, dockWindowID: GuiEnvironment.GetDockWindowID(this.ParentForm)); //GuiEnvironment.sendSymbolToExternalConnector(symbol, "", "", null, linkRowData); } } } } } /// /// Set the symbol link channel /// /// The link channel public void SetLinkChannel(string linkChannel) { _linkChannel = linkChannel; foreach (ToolStripMenuItem toolStripMenuItem in _toolStripMenuItemSymbolLinking.DropDownItems) { if (toolStripMenuItem.Text == _linkChannel) toolStripMenuItem.Checked = true; else toolStripMenuItem.Checked = false; } // change window icon TableLayoutPanel tableLayoutPanel = this.Parent as TableLayoutPanel; if (tableLayoutPanel != null) { LimitAlertsForm limitAlertsForm = tableLayoutPanel.Parent as LimitAlertsForm; if (limitAlertsForm != null) limitAlertsForm.SetLinkChannel(linkChannel); } } /// /// Get the symbol link channel /// /// public string GetLinkChannel() { return _linkChannel; } private void RecordExternalLinkUseCase(RowData rowData) { GuiEnvironment.RecordExternalLinkingUseCase(rowData, "LA", _connectionMaster.SendManager); } void dataGridView1_CellDoubleClick(object sender, DataGridViewCellEventArgs e) { if (e.RowIndex == -1) { Column column = (Column)dataGridView1.Columns[e.ColumnIndex]; if (null != column && null != column.ColumnInfo) { GuiEnvironment.RecordUseCase("LA.Grid.Sort." + column.ColumnInfo.InternalCode, _connectionMaster.SendManager); // Set sortType to descending by default when we pick a new column to sort. if ((_sortByColumn == null) || (_sortByColumn != column.ColumnInfo)) _localSortEnabled = false; _sortByColumn = column.ColumnInfo; //_importSortBy = _sortByColumn.InternalCode; if (!_localSortEnabled) _sortType = SortType.Descending; else { if (_sortType == SortType.Ascending) _sortType = SortType.Descending; else _sortType = SortType.Ascending; } _localSortEnabled = true; RowDataHelper.DoSorting(dataGridView1, _rowDatas, _sortByColumn, _sortType); RowDataHelper.FixCellHeaders(dataGridView1, _columns, _sortByColumn); } } else if (!GuiEnvironment.IntegrationSupportSingleClick) SendToExternalLinking(e.RowIndex); } private ColumnInfo _sortByColumn; public ColumnInfo getSortByColumn() { return _sortByColumn; } private string _importSortBy; public string ImportSortBy { get { return _importSortBy; } set { _importSortBy = value; } } public ColumnInfo SortByColumn { get { return _sortByColumn; } set { _sortByColumn = value; } } private bool _localSortEnabled = false; public bool LocalSortEnabled { get { return _localSortEnabled; } set { _localSortEnabled = value; } } private SortType _sortType = SortType.Descending; public SortType LocalSortType { get { return _sortType; } set { _sortType = value; } } public List getColumnInfoList() { return _columns; } private void dataGridView1_CellClick(object sender, DataGridViewCellEventArgs e) { if (e.RowIndex != -1 && GuiEnvironment.IntegrationSupportSingleClick) SendToExternalLinking(e.RowIndex); } private bool _pinned = false; public bool Pinned { get { return _pinned; } set { _pinned = value; _toolStripMenuItemPinned.Checked = _pinned; } } private List _selectedStatuses = new List(); public List SelectedStatuses { get { return _selectedStatuses; } set { _selectedStatuses = value; } } /// /// After this is run the _filteredLimitAlerts list is populated with limit alerts that satisfy the current filter setting. /// private void FilterLimitAlerts() { int added = 0; foreach (LimitAlert limitAlert in _limitAlerts.ToList()) { if (LimitAlertMatchesFilter(limitAlert)) { int index = _filteredLimitAlerts.IndexOf(limitAlert); if (index == -1) { added++; _filteredLimitAlerts.Add(limitAlert); } else _filteredLimitAlerts[index] = limitAlert; } } int removed = 0; foreach (LimitAlert limitAlert in _filteredLimitAlerts.ToList()) { if (!LimitAlertMatchesFilter(limitAlert)) { removed++; _filteredLimitAlerts.Remove(limitAlert); } } //GuiEnvironment.LogMessage("[FilterLimitAlerts] Total satisfying filter: " + _filteredLimitAlerts.Count + ", added=" + added + ", removed=" + removed); } /// /// Returns true if the passed limit alert satisfies the current filter setting. /// /// Limit Alert to test filtering for. /// public bool LimitAlertMatchesFilter(LimitAlert limitAlert) { if (null == limitAlert) return false; LimitAlertStatus status = LimitAlertStatus.WORKING; bool foundStatus = false; if (Enum.TryParse(limitAlert.Status, out status)) foundStatus = true; bool statusFilterExcluded = foundStatus && _selectedStatuses.Count > 0 && (!_selectedStatuses.Contains(status)); return !statusFilterExcluded; } private bool _dataBindingInProgress = false; public void DoDataBinding() { this.InvokeIfRequired(delegate () { try { if (!_dataBindingInProgress) { _refreshTimer.Stop(); _dataBindingInProgress = true; FilterLimitAlerts(); SuspendLayout(); _rowDatas.Clear(); _limitAlertToRowData.Clear(); _limitAlerts.ListChanged -= _limitAlerts_ListChanged; _limitAlerts.BeforeRemove -= _limitAlerts_BeforeRemove; foreach (LimitAlert limitAlert in _limitAlerts.ToList()) { RowData rowData = limitAlert.ToRowData(); if (_filteredLimitAlerts.Contains(limitAlert)) _rowDatas.Add(rowData); if (_limitAlertToRowData.ContainsKey(limitAlert)) _limitAlertToRowData[limitAlert] = rowData; else _limitAlertToRowData.Add(limitAlert, rowData); } RowDataHelper.DoSorting(dataGridView1, _rowDatas, _sortByColumn, _sortType); dataGridView1.DataSource = _rowDatas; _limitAlerts.ListChanged += _limitAlerts_ListChanged; _limitAlerts.BeforeRemove += _limitAlerts_BeforeRemove; dataGridView1.ClearSelection(); SetSelectedRow(); if (GlobalDataSettings.GetDataGridColumnSetting(FORM_TYPE, "Symbol").Format == "symbolplus") { // Symbol Plus format is specified. So set the symbol plus column format. DataGridViewHelper.SetSymbolPlusLayout(dataGridView1, _symbolPlusLayout); DataGridViewHelper.SetRowHeight(dataGridView1, _symbolPlusLayout); } ResumeLayout(); _dataBindingInProgress = false; _refreshTimer.Start(); } } catch (Exception e) { string debugView = e.StackTrace; _dataBindingInProgress = false; if (_refreshTimer.Enabled == false) _refreshTimer.Start(); } }); } void _limitAlerts_BeforeRemove(object sender, ListChangedEventArgs e) { try { LimitAlert limitAlert = _limitAlerts[e.NewIndex]; if (null != limitAlert) { GuiEnvironment.LogMessage("[LimitAlerts-BeforeRemove] removing " + limitAlert.ToFriendlyString()); if (_limitAlerts.Count > 0) { if (_limitAlertToRowData.ContainsKey(limitAlert)) { RowData rowData = _limitAlertToRowData[limitAlert]; _limitAlertToRowData.Remove(limitAlert); if (_rowDatas.Contains(rowData)) { this.InvokeIfRequired(delegate () { //Remove the link between the grid and the data before removing the row from the datasource //to avoid the issue https://trade-ideas.atlassian.net/browse/PRO-554 //See https://stackoverflow.com/questions/1630200/net-datagridview-index-0-does-not-have-a-value for more details. dataGridView1.DataSource = null; _rowDatas.Remove(rowData); dataGridView1.DataSource = _rowDatas; }); } } } if (_filteredLimitAlerts.Contains(limitAlert)) _filteredLimitAlerts.Remove(limitAlert); } else { GuiEnvironment.LogMessage("[LimitAlerts-BeforeRemove] limit alert is null"); } } catch (Exception exc) { string debugView = exc.StackTrace; } } void _limitAlerts_ListChanged(object sender, ListChangedEventArgs e) { try { if (e.ListChangedType == ListChangedType.ItemAdded && _limitAlerts.Count > 0) { LimitAlert limitAlert = _limitAlerts[e.NewIndex]; //GuiEnvironment.LogMessage("[LimitAlerts-ListChanged-ItemAdded] Adding " + limitAlert.ToFriendlyString()); if (LimitAlertMatchesFilter(limitAlert)) { RowData rowData = limitAlert.ToRowData(); if (!_filteredLimitAlerts.Contains(limitAlert)) _filteredLimitAlerts.Add(limitAlert); if (_limitAlertToRowData.ContainsKey(limitAlert)) _limitAlertToRowData[limitAlert].Data = rowData.Data; else { GuiEnvironment.LogMessage("[ListChanged] Adding limit alert for " + limitAlert.Symbol + ", id: " + limitAlert.Id); _limitAlertToRowData.Add(limitAlert, rowData); this.InvokeIfRequired(delegate () { RowDataHelper.AddRowData(_rowDatas, rowData, _localSortEnabled, _sortByColumn, _sortType); }); } } if (GlobalDataSettings.GetDataGridColumnSetting(FORM_TYPE, "Symbol").Format == "symbolplus") { // Symbol Plus format is specified. So reset the symbol plus column format. DataGridViewHelper.SetSymbolPlusLayout(dataGridView1, _symbolPlusLayout); } } else if (e.ListChangedType == ListChangedType.ItemChanged && _limitAlerts.Count > 0 && e.NewIndex < _limitAlerts.Count && e.NewIndex >= 0) { LimitAlert limitAlert = _limitAlerts[e.NewIndex]; //System.Diagnostics.Debug.WriteLine("[LimitAlerts-ListChanged-ItemChanged] Changing " + limitAlert.ToFriendlyString()); //SendMessage("LimitAlert item changed: " + limitAlert.Symbol + ", limitAlertrowdatas: " + _limitAlertToRowData.Keys.Count + ", containsthislimitAlert: " + _limitAlertToRowData.ContainsKey(limitAlert)); if (_limitAlertToRowData.ContainsKey(limitAlert)) { RowData rowData = _limitAlertToRowData[limitAlert]; int index = _rowDatas.IndexOf(rowData); if (index != -1 && dataGridView1.Rows.Count > index) { _rowDatas[index].Data = limitAlert.ToRowData().Data; dataGridView1.InvalidateRow(index); } if (LimitAlertMatchesFilter(limitAlert)) { if (!_filteredLimitAlerts.Contains(limitAlert)) { _filteredLimitAlerts.Add(limitAlert); this.InvokeIfRequired(delegate () { GuiEnvironment.LogMessage("[ListChanged] ItemChanged " + rowData.ToString()); RowDataHelper.AddRowData(_rowDatas, rowData, _localSortEnabled, _sortByColumn, _sortType); }); } } else { if (_rowDatas.Contains(rowData)) { this.InvokeIfRequired(delegate () { _rowDatas.Remove(rowData); }); } if (_filteredLimitAlerts.Contains(limitAlert)) _filteredLimitAlerts.Remove(limitAlert); } } } } catch (Exception exc) { string debugView = exc.StackTrace; } } private bool _showChartDaily = false; public bool ShowChartDaily { get { return _showChartDaily; } set { _showChartDaily = value; _toolStripMenuItemShowChartDaily.Checked = _showChartDaily; } } private bool _showChartIntraday = false; public bool ShowChartIntraday { get { return _showChartIntraday; } set { _showChartIntraday = value; _toolStripMenuItemShowChartIntraday.Checked = _showChartIntraday; } } private bool _showFilter = false; public bool ShowFilter { get { return _showFilter; } set { _showFilter = value; _toolStripMenuItemShowFilter.Checked = _showFilter; } } internal void chooseRowSelect() { if (GuiEnvironment.HighlightGridRow) dataGridView1.SelectionMode = DataGridViewSelectionMode.FullRowSelect; else dataGridView1.SelectionMode = DataGridViewSelectionMode.CellSelect; } /// /// Sums up the TotalSinceTriggered column from all filtered alerts /// /// internal double GetTotalSinceTriggered() { FilterLimitAlerts(); double toReturn = 0; foreach (LimitAlert limitAlert in _filteredLimitAlerts.ToList()) { if (null != limitAlert) { if (limitAlert.DollarsSinceTriggered.HasValue) toReturn += limitAlert.DollarsSinceTriggered.Value; } } return toReturn; } public bool MergingAlerts = false; /// /// MergeAlerts will merge existing price alerts in this window with the master list of price alerts in GuiEnvironment.LimitAlerts. /// Normally this is done on a reconnection after a disconnect or when switching connections. /// internal void MergeAlerts() { this.BeginInvokeIfRequired(delegate () { try { MergingAlerts = true; GuiEnvironment.LogMessage("[MergeAlerts] Merging alerts. _limitAlerts: " + _limitAlerts.Count + ", _filtered: " + _filteredLimitAlerts.Count + ", _rowDatas: " + _rowDatas.Count); Dictionary existingAlertIds = new Dictionary(); foreach (LimitAlert limitAlert in GuiEnvironment.LimitAlerts.ToList()) { AddLimitAlert(limitAlert, false); existingAlertIds.Add(limitAlert.Id, limitAlert.Id); } foreach (LimitAlert limitAlert in _limitAlerts.ToList()) { if (!existingAlertIds.ContainsKey(limitAlert.Id)) { this.InvokeIfRequired(delegate () { _limitAlerts.Remove(limitAlert); }); } } Dictionary existingRowDataIds = new Dictionary(); foreach (RowData rowData in _rowDatas.ToList()) { string id = rowData.GetAsString("Id", ""); bool containsIdAlready = existingRowDataIds.ContainsKey(id); if (!existingAlertIds.ContainsKey(id) || containsIdAlready) { GuiEnvironment.LogMessage("[MergeAlerts] removing rowData since id: " + id + " not found in existing limit alerts"); this.InvokeIfRequired(delegate () { _rowDatas.Remove(rowData); }); } else existingRowDataIds.Add(id, id); } GuiEnvironment.LogMessage("[MergeAlerts] Merging alerts complete. _limitAlerts: " + _limitAlerts.Count + ", _filtered: " + _filteredLimitAlerts.Count + ", _rowDatas: " + _rowDatas.Count); SetSelectedRow(); MergingAlerts = false; } catch (Exception e) { string debugView = e.StackTrace; MergingAlerts = false; } }); } } }