466 lines
14 KiB
C#
466 lines
14 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Text;
|
|
using System.Linq;
|
|
using MarketData.Utils;
|
|
using MarketData.Numerical;
|
|
using MarketData.DataAccess;
|
|
|
|
namespace MarketData.MarketDataModel
|
|
{
|
|
public class PriceComparerDesc : IComparer<Price>
|
|
{
|
|
public int Compare(Price p1, Price p2)
|
|
{
|
|
if (p1.Date < p2.Date) return -1;
|
|
else if (p1.Date > p2.Date) return 1;
|
|
return 0;
|
|
}
|
|
}
|
|
// Throughout the application it is assumed that these collections, when populated, be be in descending date order.
|
|
public class Prices : List<Price>
|
|
{
|
|
public Prices()
|
|
{
|
|
}
|
|
public Prices(Price[] prices)
|
|
{
|
|
foreach (Price price in prices) Add(price);
|
|
}
|
|
public Prices(List<Price> prices)
|
|
{
|
|
foreach(Price price in prices)Add(price);
|
|
}
|
|
public Prices(String strCSV,String symbol)
|
|
{
|
|
String[] csvLines = strCSV.Split('\n');
|
|
Clear();
|
|
for (int index = 1; index < csvLines.Length; index++)
|
|
{
|
|
if (csvLines[index].Length < 1) continue;
|
|
String[] lineItems = csvLines[index].Split(',');
|
|
Price price = new Price();
|
|
String[] dateParts = lineItems[0].Split('-');
|
|
try { price.Date = new DateTime(int.Parse(dateParts[0]), int.Parse(dateParts[1]), int.Parse(dateParts[2])); }
|
|
catch (Exception /*exception*/)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("'{0}' does not contain a date", lineItems[0]));
|
|
continue;
|
|
}
|
|
price.Symbol = symbol;
|
|
price.Open = Double.Parse(lineItems[1]);
|
|
price.High = Double.Parse(lineItems[2]);
|
|
price.Low = Double.Parse(lineItems[3]);
|
|
price.Close = Double.Parse(lineItems[4]);
|
|
price.Volume = Int64.Parse(lineItems[5]);
|
|
if(lineItems.Length>6)price.AdjClose=Double.Parse(lineItems[6]);
|
|
Add(price);
|
|
}
|
|
}
|
|
// Assumes that the prices are stored lowest date first
|
|
public double MaxDrawdown()
|
|
{
|
|
return Numerics.MaxDrawdown(GetPrices());
|
|
}
|
|
public double MaxUpside()
|
|
{
|
|
return Numerics.MaxUpside(GetPrices());
|
|
}
|
|
public PricesByDate GetPricesByDate()
|
|
{
|
|
PricesByDate pricesByDate = new PricesByDate();
|
|
for (int index = 0; index < Count; index++) pricesByDate.Add(this[index].Date, this[index]);
|
|
return pricesByDate;
|
|
}
|
|
public DateTime MaxDate()
|
|
{
|
|
return this.Max(x=>x.Date);
|
|
}
|
|
public DateTime MinDate()
|
|
{
|
|
return this.Min(x=>x.Date);
|
|
}
|
|
public Prices Top(int count)
|
|
{
|
|
Prices prices = new Prices();
|
|
for (int index = 0; index < count && index<Count; index++)
|
|
{
|
|
prices.Add(this[index]);
|
|
}
|
|
return prices;
|
|
}
|
|
public Prices Bottom(int count)
|
|
{
|
|
Prices prices = new Prices();
|
|
for (int index = Count-1; index>=0 && prices.Count<count; index--)
|
|
{
|
|
prices.Add(this[index]);
|
|
}
|
|
return prices;
|
|
}
|
|
public double Volatility()
|
|
{
|
|
float[] pricesAr = GetPrices();
|
|
return Numerics.StdDev(ref pricesAr);
|
|
}
|
|
public double Min()
|
|
{
|
|
float[] pricesAr = GetPrices();
|
|
return Numerics.Min(ref pricesAr);
|
|
}
|
|
public double MinLow()
|
|
{
|
|
float[] pricesAr = GetPricesLow();
|
|
return Numerics.Min(ref pricesAr);
|
|
}
|
|
public double Max()
|
|
{
|
|
float[] pricesAr = GetPrices();
|
|
return Numerics.Max(ref pricesAr);
|
|
}
|
|
public double Mean()
|
|
{
|
|
float[] pricesAr = GetPrices();
|
|
return Numerics.Mean(ref pricesAr);
|
|
}
|
|
public double[] GetLeastSquaresFit()
|
|
{
|
|
double[] pricesArray = new double[Count];
|
|
for (int index = 0; index < Count; index++)
|
|
{
|
|
pricesArray[index] = (float)this[index].Close;
|
|
}
|
|
LeastSquaresResult leastSquaresResult=Numerics.LeastSquares(pricesArray);
|
|
return leastSquaresResult.LeastSquares;
|
|
}
|
|
public float[] GetPrices()
|
|
{
|
|
float[] pricesArray = new float[Count];
|
|
for (int index = 0; index < Count; index++)
|
|
{
|
|
pricesArray[index] = (float)this[index].Close;
|
|
}
|
|
return pricesArray;
|
|
}
|
|
public float[] GetPricesLow()
|
|
{
|
|
float[] pricesArray = new float[Count];
|
|
for (int index = 0; index < Count; index++)
|
|
{
|
|
pricesArray[index] = (float)this[index].Low;
|
|
}
|
|
return pricesArray;
|
|
}
|
|
public float[] GetVolume()
|
|
{
|
|
float[] volumeArray=new float[Count];
|
|
for(int index=0;index<Count;index++)
|
|
{
|
|
volumeArray[index]=(float)this[index].Volume;
|
|
}
|
|
return volumeArray;
|
|
}
|
|
public float[] GetPricesHigh()
|
|
{
|
|
float[] pricesArray = new float[Count];
|
|
for (int index = 0; index < Count; index++)
|
|
{
|
|
pricesArray[index] = (float)this[index].High;
|
|
}
|
|
return pricesArray;
|
|
}
|
|
public float[] GetPrices(int startIndex, int count)
|
|
{
|
|
if (startIndex + count > Count) return null;
|
|
float[] pricesArray=new float[count];
|
|
for (int index = startIndex,arrayIndex=0; index < startIndex + count; index++,arrayIndex++)
|
|
{
|
|
pricesArray[arrayIndex] = (float)this[index].Close;
|
|
}
|
|
return pricesArray;
|
|
}
|
|
public float[] GetPricesHigh(int startIndex, int count)
|
|
{
|
|
if (startIndex + count > Count) return null;
|
|
float[] pricesArray = new float[count];
|
|
for (int index = startIndex, arrayIndex = 0; index < startIndex + count; index++, arrayIndex++)
|
|
{
|
|
pricesArray[arrayIndex] = (float)this[index].High;
|
|
}
|
|
return pricesArray;
|
|
}
|
|
public float[] GetPricesLow(int startIndex, int count)
|
|
{
|
|
if (startIndex + count > Count) return null;
|
|
float[] pricesArray = new float[count];
|
|
for (int index = startIndex, arrayIndex = 0; index < startIndex + count; index++, arrayIndex++)
|
|
{
|
|
pricesArray[arrayIndex] = (float)this[index].Low;
|
|
}
|
|
return pricesArray;
|
|
}
|
|
public float[] GetReturns()
|
|
{
|
|
if(Count==0||1==Count)return null;
|
|
float[] returns = new float[Count - 1];
|
|
for (int index = 0; index < Count - 1; index++)
|
|
{
|
|
Price currentPrice = this[index];
|
|
Price prevPrice = this[index + 1];
|
|
if (0.00 == prevPrice.Close) returns[index] = 0.00F;
|
|
else returns[index] = (float)((currentPrice.Close - prevPrice.Close) / Math.Abs(prevPrice.Close));
|
|
}
|
|
return returns;
|
|
}
|
|
public double GetReturn1D()
|
|
{
|
|
if(Count<2)return double.NaN;
|
|
Prices pricesForReturn1D=new Prices(this.Take(2).ToList());
|
|
return pricesForReturn1D.GetCumulativeReturn();
|
|
}
|
|
public float[] GetReturns(int dayCount)
|
|
{
|
|
if(Count-dayCount<=0)return new float[0];
|
|
float[] returns = new float[Count - dayCount];
|
|
for (int index = 0; index < Count - dayCount; index++)
|
|
{
|
|
Price currentPrice = this[index];
|
|
Price prevPrice = this[index + dayCount];
|
|
if (0.00 == prevPrice.Close) returns[index] = 0.00F;
|
|
else returns[index] = (float)((currentPrice.Close - prevPrice.Close) / Math.Abs(prevPrice.Close));
|
|
}
|
|
return returns;
|
|
}
|
|
public double GetCumulativeReturn()
|
|
{
|
|
float[] returns=GetReturns();
|
|
if(null==returns)return double.NaN;
|
|
double itemReturn=0.00;
|
|
for(int index=0;index<returns.Length;index++)itemReturn+=(double)returns[index];
|
|
return itemReturn;
|
|
}
|
|
public double GetCumulativeReturn(int dayCount)
|
|
{
|
|
float[] returns=GetReturns(dayCount);
|
|
double itemReturn=0.00;
|
|
for(int index=0;index<returns.Length;index++)itemReturn+=(double)returns[index];
|
|
return itemReturn;
|
|
}
|
|
public double[] GetReturnsAsDoubleArray()
|
|
{
|
|
double[] returns = new double[Count - 1];
|
|
for (int index = 0; index < Count - 1; index++)
|
|
{
|
|
Price currentPrice = this[index];
|
|
Price prevPrice = this[index + 1];
|
|
if (0.00 == prevPrice.Close) returns[index] = 0.00;
|
|
else returns[index] = (double)((currentPrice.Close - prevPrice.Close) / Math.Abs(prevPrice.Close));
|
|
}
|
|
return returns;
|
|
}
|
|
public double[] GetReturnsAsDoubleArray(int dayCount)
|
|
{
|
|
if (0 == Count) return null;
|
|
double[] returns = new double[Count - dayCount];
|
|
for (int index = 0; index < Count - dayCount; index++)
|
|
{
|
|
Price currentPrice = this[index];
|
|
Price prevPrice = this[index + dayCount];
|
|
if (0.00 == prevPrice.Close) returns[index] = 0.00F;
|
|
else returns[index] = ((currentPrice.Close - prevPrice.Close) / Math.Abs(prevPrice.Close));
|
|
}
|
|
return returns;
|
|
}
|
|
// *********************************
|
|
public static Prices GetMonthlyPrices(String symbol, DateTime asof, int months = 36)
|
|
{
|
|
DateGenerator dateGenerator = new DateGenerator();
|
|
Prices prices = new Prices();
|
|
DateTime startDate = dateGenerator.GetCurrMonthStart(asof);
|
|
DateTime minPricingDate = PricingDA.GetEarliestDate(symbol);
|
|
Dictionary<DateTime, Price> symbolPricesByDate = new Dictionary<DateTime, Price>();
|
|
List<DateTime> historicalDates = new List<DateTime>();
|
|
while (historicalDates.Count < (months + 5)) // pad the months by 5
|
|
{
|
|
historicalDates.Add(startDate);
|
|
startDate = dateGenerator.GetPrevMonthStart(startDate);
|
|
}
|
|
DateTime requestStartDate = dateGenerator.DaysAddActual(asof, 5); // advance 5 days to provide an error margin for holidays
|
|
Prices symbolPrices = PricingDA.GetPrices(symbol, requestStartDate, historicalDates[historicalDates.Count - 1]);
|
|
foreach (Price price in symbolPrices) symbolPricesByDate.Add(price.Date, price);
|
|
startDate = dateGenerator.GetCurrMonthStart(asof);
|
|
if(startDate>asof)startDate = dateGenerator.GetPrevMonthStart(asof); // if start date winds up > asof on account of a weekend or holiday then fall back a further month
|
|
while (prices.Count < (months + 1))
|
|
{
|
|
Price price = GetPrice(symbol, startDate, symbolPricesByDate);
|
|
if(null == price)return null;
|
|
prices.Add(price);
|
|
startDate = dateGenerator.GetPrevMonthStart(startDate);
|
|
if (startDate < minPricingDate) break;
|
|
}
|
|
return prices;
|
|
}
|
|
private static Price GetPrice(String symbol,DateTime requestedDate, Dictionary<DateTime, Price> symbolPricesByDate)
|
|
{
|
|
int maxAdvanceDays = 5;
|
|
Price symbolPrice = null;
|
|
for (int advanceDays = 0; advanceDays < maxAdvanceDays; advanceDays++)
|
|
{
|
|
if (!symbolPricesByDate.ContainsKey(requestedDate)) { requestedDate = requestedDate.AddDays(1); continue; }
|
|
symbolPrice = symbolPricesByDate[requestedDate];
|
|
}
|
|
return symbolPrice;
|
|
}
|
|
}
|
|
// ***************************************************************************************************************************************************************************
|
|
public class Price
|
|
{
|
|
public enum PriceSource{Other=0,BigCharts=1,Yahoo=2,Fidelity=3,Google=4,BarChart=5,Robinhood=6};
|
|
private String symbol;
|
|
private DateTime date;
|
|
private double open;
|
|
private double high;
|
|
private double low;
|
|
private double close;
|
|
private long volume;
|
|
private double adjClose;
|
|
private double prevClose; // !!IMPORTANT we don't store this nor do we consider this when evaluating a valid price.
|
|
private PriceSource source;
|
|
public Price()
|
|
{
|
|
}
|
|
public Price(Price price)
|
|
{
|
|
this.Symbol=price.Symbol;
|
|
this.Date=price.Date;
|
|
this.Open=price.Open;
|
|
this.High=price.High;
|
|
this.Low=price.Low;
|
|
this.Close=price.Close;
|
|
this.Volume=price.Volume;
|
|
this.AdjClose=price.AdjClose;
|
|
this.PrevClose=price.PrevClose;
|
|
this.Source=price.Source;
|
|
}
|
|
public Price Clone()
|
|
{
|
|
Price clonePrice=new Price();
|
|
clonePrice.Symbol=Symbol;
|
|
clonePrice.Date=Date;
|
|
clonePrice.Open=Open;
|
|
clonePrice.High=High;
|
|
clonePrice.Low=Low;
|
|
clonePrice.Close=Close;
|
|
clonePrice.Volume=Volume;
|
|
clonePrice.AdjClose=AdjClose;
|
|
clonePrice.PrevClose=PrevClose;
|
|
clonePrice.Source=Source;
|
|
return clonePrice;
|
|
}
|
|
public PriceSource Source
|
|
{
|
|
get{return source;}
|
|
set{source=value;}
|
|
}
|
|
public String SourceAsString()
|
|
{
|
|
switch(Source)
|
|
{
|
|
case PriceSource.Other :
|
|
return "Other";
|
|
case PriceSource.BigCharts :
|
|
return "BigCharts";
|
|
case PriceSource.Yahoo :
|
|
return "Yahoo";
|
|
case PriceSource.Fidelity :
|
|
return "Fidelity";
|
|
case PriceSource.Google :
|
|
return "Google";
|
|
case PriceSource.BarChart :
|
|
return "BarChart";
|
|
case PriceSource.Robinhood :
|
|
return "Robinhood";
|
|
default :
|
|
return Constants.CONST_QUESTION;
|
|
}
|
|
}
|
|
public String Symbol
|
|
{
|
|
get { return symbol; }
|
|
set { symbol = value; }
|
|
}
|
|
public DateTime Date
|
|
{
|
|
get { return date; }
|
|
set { date = value; }
|
|
}
|
|
public double Open
|
|
{
|
|
get { return open; }
|
|
set { open = value; }
|
|
}
|
|
public double High
|
|
{
|
|
get { return high; }
|
|
set { high = value; }
|
|
}
|
|
public double Low
|
|
{
|
|
get { return low; }
|
|
set { low = value; }
|
|
}
|
|
public double Close
|
|
{
|
|
get { return close; }
|
|
set { close = value; }
|
|
}
|
|
public long Volume
|
|
{
|
|
get { return volume; }
|
|
set { volume = value; }
|
|
}
|
|
public double AdjClose
|
|
{
|
|
get { return adjClose; }
|
|
set { adjClose = value; }
|
|
}
|
|
public double PrevClose
|
|
{
|
|
get { return prevClose; }
|
|
set { prevClose = value; }
|
|
}
|
|
public bool IsValid
|
|
{
|
|
get
|
|
{
|
|
if(null==symbol)return false;
|
|
if(Utility.IsEpoch(date))return false;
|
|
if(double.IsNaN(open))return false;
|
|
if(double.IsNaN(high))return false;
|
|
if(double.IsNaN(low))return false;
|
|
if(double.IsNaN(close))return false;
|
|
if(double.IsNaN(adjClose))return false;
|
|
return true;
|
|
}
|
|
}
|
|
public static String Header
|
|
{
|
|
get { return "Symbol,Date,Open,High,Low,Close,Volume,Adj Close,Source"; } // ,M12,M26,MACD,Signal,Histogram
|
|
}
|
|
public override String ToString()
|
|
{
|
|
StringBuilder sb = new StringBuilder();
|
|
sb.Append(symbol).Append(",");
|
|
sb.Append(Utility.DateTimeToStringMMSDDSYYYY(Date)).Append(",");
|
|
sb.Append(String.Format("{0:0.00}", Open)).Append(",");
|
|
sb.Append(String.Format("{0:0.00}", High)).Append(",");
|
|
sb.Append(String.Format("{0:0.00}", Low)).Append(",");
|
|
sb.Append(String.Format("{0:0.00}", Close)).Append(",");
|
|
sb.Append(Volume).Append(",");
|
|
sb.Append(String.Format("{0:0.00}", AdjClose)).Append(",");
|
|
sb.Append(SourceAsString());
|
|
return sb.ToString();
|
|
}
|
|
}
|
|
} |