using System; using System.Collections.Generic; using System.Linq; using System.Drawing; using System.Globalization; using System.Xml; using TradeIdeas.XML; using System.IO; namespace TradeIdeas.MiscSupport { public class Bar { private DateTime _date; public DateTime Date { get => _date; set { _date = value; DisplayDate = _date.ToString(_dateTimeFormat); } } private string _dateTimeFormat = "MMM d, yyyy"; public string DateTimeFormat { get => _dateTimeFormat; set { _dateTimeFormat = value; DisplayDate = Date.ToString(_dateTimeFormat); } } /// /// Suitable for display for the user. /// public string DisplayDate { get; set; } /// /// Suitable for adding directly to the chart. Order is high, low, close, open /// public double[] Values; public int Volume; public Bar(DateTime date, double high, double low, double open, double close, int volume = 0) { Date = date; Values = new[] { high, low, close, open }; Volume = volume; } public Bar(XmlNode definition) { Values = new[] { 0.00, 0.00, 0.00, 0.00 }; Restore(definition); } public double High { get => Values[0]; set => Values[0] = value; } public double Low { get => Values[1]; set => Values[1] = value; } public double Close { get => Values[2]; set => Values[2] = value; } public double Open { get => Values[3]; set => Values[3] = value; } public Color Color() { return Color(System.Drawing.Color.Green, System.Drawing.Color.Red, System.Drawing.Color.Black); } public Color Color(Color up, Color down, Color flat) { double open = Open; double close = Close; if (close > open) return up; else if (close < open) return down; else return flat; } public void Restore(XmlNode definition) { string dateTimeString = definition.Property("DATETIME"); if (dateTimeString != "") { if (DateTime.TryParse(dateTimeString, out var dt)) Date = dt; } Open = definition.Property("OPEN", 0.00); High = definition.Property("HIGH", 0.00); Low = definition.Property("LOW", 0.00); Close = definition.Property("CLOSE", 0.00); } public void Save(XmlNode parent) { parent.SetProperty("DATETIME", Date); parent.SetProperty("OPEN", Open); parent.SetProperty("HIGH", High); parent.SetProperty("LOW", Low); parent.SetProperty("CLOSE", Close); } } public delegate void InstrumentDataReadyHandler(Instrument instrument); public class Instrument { public event InstrumentDataReadyHandler InstrumentDataReady; public string Symbol { get; } public string SecType { get; } public string Description { get; } private decimal _last; /// /// Last Price for this instrument /// public decimal Last { get => _last; set { _last = value; InstrumentDataReady?.Invoke(this); } } /// /// Last time a quote was updated for this instrument /// public DateTime QuoteUpdated { get; set; } public decimal Bid { get; set; } public decimal Ask { get; set; } public int AskSize { get; set; } public int BidSize { get; set; } public decimal YesterdayClose { get; set; } public decimal ChangeLastSixty { get; set; } public decimal ChangeLastFifteen { get; set; } public decimal ChangeLastFive { get; set; } /// /// Smart stop as retrieved by an alert or a TopListRequest. /// public double? SmartStop { get; set; } = null; /// /// Minimum price increment for this security. /// public double? MinMove { get; set; } /// /// Market rule id that identifies the price increment rules for this Instrument. /// public int? MarketRuleId { get; set; } public List Bars { get; } = new List(); /// /// Day 0 is the first day of the consolidation. /// FirstDay will be negative. It is the first day for which we can provide data. /// public int FirstDay => 0; /// /// This is the last day for which we have data. /// This should be positive, and therefore part of the consolidation pattern. /// public int LastDay => Bars.Count + FirstDay - 1; /// /// This describes the day. /// If we don't have information about this day, we try to extrapolate based on the /// first or last day that we do know about. /// /// Which day to find. 0 is the first day of the consolidation. /// public Bar GetDay(int day) { return GetFromZero(day - FirstDay); } /// /// This describes the day. /// If we don't have information about this day, we try to extrapolate based on the /// first or last day that we do know about. /// /// Which day to find. 0 is the first day of history. /// public Bar GetFromZero(int index) { if (index < 0) { var result = Bars[0]; result.DisplayDate = ""; return result; } if (index >= Bars.Count) { var result = Bars[Bars.Count - 1]; result.DisplayDate = ""; return result; } return Bars[index]; } public Instrument(string symbol, string secType = "STK") { Symbol = symbol; SecType = secType; } public Instrument(string symbol, string description, Bar[] data) { Symbol = symbol; Description = description; Bars = new List(data); } public Instrument(string symbol, string description, string historicalData) { Symbol = symbol; Description = description; ParseHistoricalData(historicalData); } public override bool Equals(object obj) { // If parameter cannot be cast to Point return false. if (!(obj is Instrument c)) return false; // Return true if the fields match: return (Symbol == c.Symbol && SecType == c.SecType); } public bool Equals(Instrument c) { // If parameter is null return false: if (c == null) return false; // Return true if the fields match: return (Symbol == c.Symbol && SecType == c.SecType); } public override int GetHashCode() { // ReSharper disable once NonReadonlyMemberInGetHashCode return Symbol.GetHashCode(); } public double? SimpleMovingAverage(int periods, DateTime? referenceDate = null) { if (Bars.Count < periods) return null; if (null == referenceDate) return Bars.Skip(Bars.Count - periods).Average(x => x.Close); else { var appropriateBars = Bars.Where(x => x.Date <= referenceDate).ToList(); if (appropriateBars.Count < periods) return null; else return appropriateBars.Skip(appropriateBars.Count - periods).Average(x => x.Close); } } private void ParseHistoricalData(string historicalData) { Bars.Clear(); using (var sr = new StringReader(historicalData)) { string line; while ((line = sr.ReadLine()) != null) { string[] fields = line.Split('\t'); if (!DateTime.TryParseExact(fields[0], "MMM d, yyyy", CultureInfo.InvariantCulture, DateTimeStyles.None, out var date)) continue; if (!ServerFormats.TryParse(fields[1], out double open)) continue; if (!ServerFormats.TryParse(fields[2], out double high)) continue; if (!ServerFormats.TryParse(fields[3], out double low)) continue; if (!ServerFormats.TryParse(fields[4], out double close)) continue; string volumeString = fields[5].Replace(",", ""); if (!ServerFormats.TryParse(volumeString, out int volume)) continue; var bar = new Bar(date, high, low, open, close, volume); Bars.Insert(0, bar); } } } public override string ToString() { return Symbol; } } }