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;
}
}
}