using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; using System.Drawing.Drawing2D; using System.Diagnostics; using TradeIdeas.TIProData; using TradeIdeas.MiscSupport; namespace TradeIdeas.TIProGUI.RealTimeStockRace { public partial class RaceLaneControl : UserControl { private RaceCarControl _raceCarControl; private string _symbol; private LaneSizeMode _laneSize = LaneSizeMode.Small; private int _highestRaceCarLeft = 0; private int _lastRaceCarLeft = 0; private int _lastLaneWidth = 0; private double _startBasedOn; private string _units; private bool _showComplimentUnits; private double _basedOn; private bool _live; private bool _displayDeltaValue; private double? _startBasedOnCompliment; private double? _basedOnCompliment; private bool _biggestLoserWins; private string _basedOnCode; private string _basedOnComplimentCode; private Color? _borderColor = null; private int _alpha = 255; private bool _moveLaneEffect = true; private int _currentTop; private int _newTop; private int _incrementTop; private bool _enableFastUpdates = true; private bool _locked = false; private Font _orignalFont; private const int MAX_ALPHA = 255; private const int MIN_ALPHA = 64; private const int ALPHA_STEP_DOWN = 10; public RaceLaneControl(RaceCarControl raceCarControl, string symbol, LaneSizeMode laneSize, double startBasedOn, string basedOnCode, string units, bool showComplimentUnits, double? startBasedOnCompliment = null, string basedOnComplimentCode = null, bool biggestLoserWins = false, bool enableFastUpdates = true) { _raceCarControl = raceCarControl; _symbol = symbol; _laneSize = laneSize; _startBasedOn = startBasedOn; _basedOnCode = basedOnCode; _startBasedOnCompliment = startBasedOnCompliment; _basedOnComplimentCode = basedOnComplimentCode; _units = units; _showComplimentUnits = showComplimentUnits; _biggestLoserWins = biggestLoserWins; _enableFastUpdates = enableFastUpdates; InitializeComponent(); _orignalFont = this.Font; SetLaneSize(); _lastLaneWidth = this.Width; this.Controls.Add(_raceCarControl); } /// /// Race Lane Symbol. /// public string Symbol { get { return _symbol; } } /// /// Lane Size. /// public LaneSizeMode LaneSize { get { return _laneSize; } set { _laneSize = value; SetLaneSize(); } } /// /// Set lane size based on LaneSizeMode. /// private void SetLaneSize() { this.Font = RealTimeStockRaceForm.CalculateLaneFontSize(_orignalFont, _laneSize); this.Height = RealTimeStockRaceForm.CalculateLaneRowSize(_laneSize); // Reload Company Logo _raceCarControl.LoadCompanyLogo(_symbol); } /// /// Race Lane Start Based On value. /// public double StartBasedOn { get { return _startBasedOn; } } /// /// Race Lane Locked. If Locked, Lane will alway stay in race. /// public bool Locked { get { return _locked; } set { _locked = value; if (_raceCarControl != null && _locked) _raceCarControl.LabelBackColor = Color.FromArgb(68, 68, 68); } } public void MoveRaceCar(double percentComplete, double totalPercentComplete, double basedOn, double highestBasedOn, bool live, bool displayDeltaValue, Color? symbolTextColor, int symbolWidth, double? basedOnCompliment = null) { // If fast updates are enabled, recalculate the percent complete. if (_enableFastUpdates && highestBasedOn != 0) { try { double closeFactor; if (displayDeltaValue) { if ((highestBasedOn < 0 && !_biggestLoserWins) || (highestBasedOn > 0 && _biggestLoserWins)) closeFactor = highestBasedOn / (_basedOn - _startBasedOn); else closeFactor = (_basedOn - _startBasedOn) / highestBasedOn; } else { if ((highestBasedOn < 0 && !_biggestLoserWins) || (highestBasedOn > 0 && _biggestLoserWins)) closeFactor = highestBasedOn / _basedOn; else closeFactor = _basedOn / highestBasedOn; } if (!double.IsNaN(closeFactor) && !double.IsInfinity(closeFactor) && !double.IsPositiveInfinity(closeFactor) && !double.IsNegativeInfinity(closeFactor)) { percentComplete = Convert.ToInt32(Convert.ToDouble(totalPercentComplete) * closeFactor); if (percentComplete > 100) percentComplete = 100; else if (percentComplete < 0) percentComplete = 0; Debug.WriteLine($"RTSR: RaceLaneControl.MoveRaceCar Symbol: {_symbol} Percent Complete set to {percentComplete}"); } } catch (Exception ex) { Debug.WriteLine($"RTSR: RaceLaneControl.MoveRaceCar EXCEPTION Symbol: {_symbol}, highestBasedOn: {highestBasedOn}, _basedOn: {_basedOn}, Message: {ex.ToString()}"); } } // Only update the values when fast updates are not enabled. if (!_enableFastUpdates) { _basedOn = basedOn; _basedOnCompliment = basedOnCompliment; } _live = live; _displayDeltaValue = displayDeltaValue; // Debug info //Debug.WriteLine($"RTSR: ({Text}) RaceLaneControl.MoveRaceCar symbol: {_symbol}"); //Debug.WriteLine($"RTSR: ({Text}) RaceLaneControl.MoveRaceCar percentComplete: {percentComplete}"); //Debug.WriteLine($"RTSR: ({Text}) RaceLaneControl.MoveRaceCar displayDeltaValue: {_displayDeltaValue}"); //Debug.WriteLine($"RTSR: ({Text}) RaceLaneControl.MoveRaceCar basedOn: {_basedOn}"); //Debug.WriteLine($"RTSR: ({Text}) RaceLaneControl.MoveRaceCar startBasedOn: {_startBasedOn}"); //Debug.WriteLine($"RTSR: ({Text}) RaceLaneControl.MoveRaceCar basedOnCompliment: {_basedOnCompliment}"); //Debug.WriteLine($"RTSR: ({Text}) RaceLaneControl.MoveRaceCar startBasedOnCompliment: {_startBasedOnCompliment}"); // Set symbol text color and symbol width. if (symbolTextColor != null) _raceCarControl.SymbolTextColor = (Color)symbolTextColor; _raceCarControl.SymbolWidth = symbolWidth; // Calculate new position of race car and move it. if (percentComplete > 0) { int availableWidth = this.Width - _raceCarControl.Width; int newLeft = Convert.ToInt32(availableWidth * (percentComplete / 100.0)); _raceCarControl.Left = newLeft; } else _raceCarControl.Left = 0; // Store highest left position of car. _highestRaceCarLeft = Math.Max(_raceCarControl.Left, _highestRaceCarLeft); // See if the car moved backwards. if (_live && _highestRaceCarLeft > _raceCarControl.Left && _lastRaceCarLeft > _raceCarControl.Left) // { // Start alpha value count down at max value. _alpha = MAX_ALPHA; timFadeOut.Stop(); timFadeOut.Start(); } _lastRaceCarLeft = _raceCarControl.Left; this.Invalidate(); } /// /// Update the Price and Day Change amounts. /// /// Price /// Change from Close Dollars /// Change from Close Percent /// Indicator Time public void UpdatePriceDayChange(double price, double changeFromCloseDollars, double changeFromClosePercent, DateTime? indicatorTime) { // Update Price and Indicator time. _raceCarControl.Price = price; _raceCarControl.IndicatorTime = indicatorTime; // Update Day Change. _raceCarControl.DayChange = $"{changeFromCloseDollars.ToString("N2")} ({changeFromClosePercent.ToString("N2")}%)"; } public void SetBorderColor(Color? borderColor) { _borderColor = borderColor; this.Invalidate(); } public void SetTop(int newTop, bool moveInstantly) { if (_moveLaneEffect && !moveInstantly) { // Sometimes lanes don't alway finish moving to their new top position. // Check to see if the last top setting is the same as the new one // but the lane didn't get completely moved. if (newTop == _newTop && this.Top != newTop) { // Just move it to the new top position instantly. this.Top = newTop; } else { // When move lane effect is enabled, move the lane in a timer. _newTop = newTop; _currentTop = this.Top; _incrementTop = (_newTop - _currentTop) / 10; moveTimer.Start(); } } else { this.Top = newTop; } } protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); // Draw lines on race lane. using (Pen pen = new Pen(Color.White) { Width = 2 }) { e.Graphics.DrawLine(pen, Convert.ToSingle(this.Width * 0.25), 0, Convert.ToSingle(this.Width * 0.25), this.Height); e.Graphics.DrawLine(pen, Convert.ToSingle(this.Width * 0.50), 0, Convert.ToSingle(this.Width * 0.50), this.Height); e.Graphics.DrawLine(pen, Convert.ToSingle(this.Width * 0.75), 0, Convert.ToSingle(this.Width * 0.75), this.Height); } if (_raceCarControl.Left > e.ClipRectangle.Left) { // Fill color behind race car. Rectangle behindCarRect = new Rectangle() { Location = new Point(e.ClipRectangle.Left, _raceCarControl.Top), Size = new Size(_raceCarControl.Left - e.ClipRectangle.Left, _raceCarControl.Height) }; if (_biggestLoserWins) { // Biggest loser red gradient behind race car. using (LinearGradientBrush linearGradientBrush = new LinearGradientBrush(new Point(0, 0), new Point(behindCarRect.Width, 0), Color.FromArgb(246, 170, 148), Color.FromArgb(174, 0, 18))) { e.Graphics.FillRectangle(linearGradientBrush, behindCarRect); } } else { // Normal green gradient behind race car. using (LinearGradientBrush linearGradientBrush = new LinearGradientBrush(new Point(0, 0), new Point(behindCarRect.Width, 0), Color.GreenYellow, Color.DarkGreen)) { e.Graphics.FillRectangle(linearGradientBrush, behindCarRect); } } // Show Based On value. SizeF fontSize = e.Graphics.MeasureString("X", new Font(this.Font, FontStyle.Bold)); int basedOnTop = Convert.ToInt32((Convert.ToSingle(this.Height) - fontSize.Height) / 2); double displayValue; if (_displayDeltaValue) displayValue = _basedOn - _startBasedOn; else displayValue = _basedOn; e.Graphics.DrawString((_units == "$" ? _units : "") + (CheckMBT(displayValue) != "" ? CheckMBT(displayValue) : (displayValue).ToString("N")) + (_units == "%" ? _units : ""), new Font(this.Font, FontStyle.Bold), Brushes.Black, new Point(behindCarRect.Location.X + 2, behindCarRect.Y + basedOnTop)); // 12)); // Show Based On Compliment value. if (_basedOnCompliment != null && _startBasedOnCompliment != null) { // Get length of based on string. SizeF basedOnSize = e.Graphics.MeasureString((_units == "$" ? _units : "") + (CheckMBT(displayValue) != "" ? CheckMBT(displayValue) : (displayValue).ToString("N")) + (_units == "%" ? _units : ""), new Font(this.Font, FontStyle.Bold)); if (_displayDeltaValue) displayValue = (double)_basedOnCompliment - (double)_startBasedOnCompliment; else displayValue = (double)_basedOnCompliment; // Get length of based on compliment string. SizeF basedOnComplimentSize = e.Graphics.MeasureString((_showComplimentUnits && _units == "%" ? "$" : "") + (CheckMBT(displayValue) != "" ? CheckMBT(displayValue) : (displayValue).ToString("N")) + (_showComplimentUnits && _units == "$" ? "%" : ""), new Font(this.Font, FontStyle.Bold)); // Check to make sure there is enough room for the based on compliment. if (basedOnSize.Width + basedOnComplimentSize.Width + 5 <= _raceCarControl.Left) { if (_biggestLoserWins) { // Biggest loser red compliment behind race car. e.Graphics.DrawString((_showComplimentUnits && _units == "%" ? "$" : "") + (CheckMBT(displayValue) != "" ? CheckMBT(displayValue) : (displayValue).ToString("N")) + (_showComplimentUnits && _units == "$" ? "%" : ""), new Font(this.Font, FontStyle.Bold), new SolidBrush(Color.FromArgb(246, 170, 148)), new Point(_raceCarControl.Left - Convert.ToInt32(basedOnComplimentSize.Width) - 3, behindCarRect.Y + basedOnTop)); // 12)); } else { // Normal green compliment behind race car. e.Graphics.DrawString((_showComplimentUnits && _units == "%" ? "$" : "") + (CheckMBT(displayValue) != "" ? CheckMBT(displayValue) : (displayValue).ToString("N")) + (_showComplimentUnits && _units == "$" ? "%" : ""), new Font(this.Font, FontStyle.Bold), Brushes.GreenYellow, new Point(_raceCarControl.Left - Convert.ToInt32(basedOnComplimentSize.Width) - 3, behindCarRect.Y + basedOnTop)); // 12)); } } } } if (_live && _highestRaceCarLeft > _raceCarControl.Left) // { // Car moved backwards. // Fill in color after race car from previous position. Rectangle beforeCarRect = new Rectangle() { Location = new Point(_raceCarControl.Right, _raceCarControl.Top), Size = new Size(_highestRaceCarLeft - _raceCarControl.Left, _raceCarControl.Height) }; using (LinearGradientBrush linearGradientBrush = new LinearGradientBrush(beforeCarRect.Location, new Point(beforeCarRect.X + beforeCarRect.Width, beforeCarRect.Bottom), Color.FromArgb(_alpha, Color.Red), Color.FromArgb(_alpha, RealTimeStockRaceForm.DarkModeTrackColor))) //Color.FromArgb(_alpha, Color.DarkGray))) { e.Graphics.FillRectangle(linearGradientBrush, beforeCarRect); } } // _borderColor = Color.DarkRed; // For testing. if (_borderColor != null) { // Add border color to control. This is done when a lane is moved or added. if (e.ClipRectangle.Y == 0) { using (Pen pen = new Pen((Color)_borderColor)) { Rectangle entireLaneRect = new Rectangle() { Location = new Point(e.ClipRectangle.Left, e.ClipRectangle.Top), Size = new Size(this.Size.Width - 3, this.Size.Height - 1) }; e.Graphics.DrawRectangle(pen, entireLaneRect); } } } } private void timFadeOut_Tick(object sender, EventArgs e) { _alpha -= ALPHA_STEP_DOWN; if (_alpha <= MIN_ALPHA) { _alpha = MIN_ALPHA; timFadeOut.Stop(); } this.Invalidate(); } public static string CheckMBT(double value) //check if number is billion,trillions-abbreviate if conditions met { string retVal = ""; double result; double test = Math.Abs(value); if (test >= 1000 && test < 1000000) { result = value / 1.0e+3; retVal = result.ToString("N02") + "K"; } if (test >= 1000000 && test < 1000000000) { result = value / 1.0e+6; retVal = result.ToString("N02") + "M"; } if (test >= 1000000000 && test < 1000000000000) { result = value / 1.0e+9; retVal = result.ToString("N02") + "B"; } else if (test >= 1000000000000) { result = value / 1.0e+12; retVal = result.ToString("N02") + "T"; } return retVal; } private void RaceLaneControl_Resize(object sender, EventArgs e) { if (_lastLaneWidth != this.Width && _highestRaceCarLeft > 0) // && _highestRaceCarLeft != _raceCarControl.Left) { // Lane has changed width, so recalculate the highest race car left position. _highestRaceCarLeft = Convert.ToInt32(Math.Floor((double)_highestRaceCarLeft * ((double)this.Width / (double)_lastLaneWidth))); // Move position of car control. //_raceCarControl.Left = Convert.ToInt32((double)_raceCarControl.Left * ((double)this.Width / (double)_lastLaneWidth)); //_lastRaceCarLeft = _raceCarControl.Left; } _live = false; _lastLaneWidth = this.Width; } private void moveTimer_Tick(object sender, EventArgs e) { // Stop the timer. moveTimer.Stop(); // Move lane to new increment top position. int calcNewTop = this.Top += _incrementTop; if (_incrementTop > 0 && calcNewTop >= _newTop) calcNewTop = _newTop; else if (_incrementTop < 0 && calcNewTop <= _newTop) calcNewTop = _newTop; this.Top = calcNewTop; // Check to see if we made it to the new top position. If not, start the timer again. if (this.Top == _newTop) this.Invalidate(); // Force repaint. else moveTimer.Start(); } /// /// Use this to cancel the top list request for the symbol data. /// private TopListRequest.Token _symbolRequestToken = null; private SymbolRequest _symbolListener = null; /// /// Starts the top list data feed for the current symbol. Cancels the previous request if there is one. /// public void StartTopListDataFeed() { if (null != _symbolRequestToken) { _symbolRequestToken.Cancel(); _symbolRequestToken = null; } if (null != _symbolListener) { _symbolListener.SymbolReceived -= Listener_SymbolReceived; _symbolListener = null; } _symbolListener = new SymbolRequest(_symbol); _symbolListener.SymbolReceived += Listener_SymbolReceived; TopListRequest dataRequest = new TopListRequest(); dataRequest.SingleSymbol = _symbol; dataRequest.Streaming = true; dataRequest.OutsideMarketHours = true; // Add Symbol. dataRequest.AddExtraColumn("D_Symbol"); // Add Company Name. dataRequest.AddExtraColumn("D_Name"); // Add Price. dataRequest.AddExtraColumn("Price"); // Add Change from Close Dollars if needed. if (_basedOnCode != "FCD" && _basedOnComplimentCode != "FCD") dataRequest.AddExtraColumn("FCD"); // Add Change from Close Percent if needed. if (_basedOnCode != "FCP" && _basedOnComplimentCode != "FCP") dataRequest.AddExtraColumn("FCP"); // Add based on code. if (!string.IsNullOrEmpty(_basedOnCode)) dataRequest.AddExtraColumn(_basedOnCode); // Add based on compliment code. if (!string.IsNullOrEmpty(_basedOnComplimentCode)) dataRequest.AddExtraColumn(_basedOnComplimentCode); _symbolRequestToken = dataRequest.Send(ConnectionMaster.First.SendManager, _symbolListener, _symbol); } public void StopTopListDataFeed() { if (null != _symbolRequestToken) { _symbolRequestToken.Cancel(); _symbolRequestToken = null; } if (null != _symbolListener) { _symbolListener.SymbolReceived -= Listener_SymbolReceived; _symbolListener = null; } } private void Listener_SymbolReceived(string symbol, RowData rowData, DateTime? start, DateTime? end) { if (symbol == _symbol && _enableFastUpdates) { this.InvokeIfRequired(delegate { // Update the data for this race symbol and lane. if (!string.IsNullOrEmpty(_basedOnCode)) { // Update based on value. object basedOnObj; rowData.Data.TryGetValue(RealTimeStockRaceForm.ConvertFieldToCode(_basedOnCode), out basedOnObj); if (basedOnObj != null) { //double basedOn = Convert.ToDouble(basedOnObj.ToString(), System.Globalization.CultureInfo.InvariantCulture); double d; if (ServerFormats.TryParse(basedOnObj.ToString(), out d)) { _basedOn = d; // basedOn; UpdateHistoricalBasedOnValue(_symbol, RealTimeStockRaceForm.ConvertFieldToCode(_basedOnCode), _basedOn); //, rowData); Debug.WriteLine($"RTSR: RaceLaneControl.Listener_SymbolReceived Symbol: {_symbol} Based On Value updated to {_basedOn}"); } } } if (!string.IsNullOrEmpty(_basedOnComplimentCode)) { // Update based on compliment value. object basedOnComplimentObj; rowData.Data.TryGetValue(RealTimeStockRaceForm.ConvertFieldToCode(_basedOnComplimentCode), out basedOnComplimentObj); if (basedOnComplimentObj != null) { //double basedOnCompliment = Convert.ToDouble(basedOnComplimentObj.ToString(), System.Globalization.CultureInfo.InvariantCulture); double d; if (ServerFormats.TryParse(basedOnComplimentObj.ToString(), out d)) { _basedOnCompliment = d; // basedOnCompliment; UpdateHistoricalBasedOnComplimentValue(_symbol, RealTimeStockRaceForm.ConvertFieldToCode(_basedOnComplimentCode), (double)_basedOnCompliment); Debug.WriteLine($"RTSR: RaceLaneControl.Listener_SymbolReceived Symbol: {_symbol} Based On Compliment Value updated to {_basedOnCompliment}"); } } } // Update Price and Indicator time. _raceCarControl.Price = rowData.GetPrice(); _raceCarControl.IndicatorTime = GuiEnvironment.GetNowInLocalTimeZone(); // Update Day Change. double changeFromCloseDollars = rowData.GetAsDouble("c_FCD", 0); double changeFromClosePercent = rowData.GetAsDouble("c_FCP", 0); _raceCarControl.DayChange = $"{changeFromCloseDollars.ToString("N2")} ({changeFromClosePercent.ToString("N2")}%)"; this.Invalidate(); // Force repaint. }); } } private void UpdateHistoricalBasedOnValue(string symbol, string basedOnCode, double basedOnValue) //, RowData newRowData) { RealTimeStockRaceControl realTimeStockRaceControl = this.Parent as RealTimeStockRaceControl; if (realTimeStockRaceControl != null) { RealTimeStockRaceForm realTimeStockRaceForm = realTimeStockRaceControl.Parent as RealTimeStockRaceForm; if (realTimeStockRaceForm != null) { realTimeStockRaceForm.UpdateHistoricalBasedOnValue(symbol, basedOnCode, basedOnValue); //, newRowData); } } } private void UpdateHistoricalBasedOnComplimentValue(string symbol, string basedOnComplimentCode, double basedOnComplimentValue) { RealTimeStockRaceControl realTimeStockRaceControl = this.Parent as RealTimeStockRaceControl; if (realTimeStockRaceControl != null) { RealTimeStockRaceForm realTimeStockRaceForm = realTimeStockRaceControl.Parent as RealTimeStockRaceForm; if (realTimeStockRaceForm != null) { realTimeStockRaceForm.UpdateHistoricalBasedOnComplimentValue(symbol, basedOnComplimentCode, basedOnComplimentValue); } } } private delegate void SymbolReceivedHandler(string symbol, RowData rowData, DateTime? start, DateTime? end); private class SymbolRequest : TopListRequest.Listener { public event SymbolReceivedHandler SymbolReceived; public string Symbol { get; set; } public SymbolRequest(string symbol) { Symbol = symbol; } public void OnDisconnect(TopListRequest.Token token) { } public void OnMetaData(TopListRequest.Token token) { } public void OnRowData(List rows, DateTime? start, DateTime? end, TopListRequest.Token token) { if (rows.Count > 0) { RowData rowData = rows[0]; if (null != SymbolReceived) SymbolReceived(Symbol, rowData, start, end); } } } } }