Files
ARM64/eNavigator/MarketDataLib/MarketDataModel/Prices.cs
2025-04-03 17:28:36 -04:00

448 lines
14 KiB
C#
Executable File

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 PricesByDate : Dictionary<DateTime, Price>
{
private DateTime maxDate=Utility.Epoch;
private DateTime minDate=Utility.Epoch;
public PricesByDate()
{
}
public new void Add(DateTime key,Price price)
{
base.Add(key,price);
if(key>maxDate)maxDate=key;
else if(Utility.IsEpoch(minDate))minDate=key;
else if(key<minDate)minDate=key;
}
public DateTime MaxDate
{
get{return maxDate;}
}
public DateTime MinDate
{
get{return minDate;}
}
}
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);
}
}
// This Beta differs wildly with Yahoo Finance. Yahoo finance uses 36 buckets of monthly returns which was simulated when building this Investopedia-based routine.
// I have yet to discover the cause of the difference but I leave this routine in place as a matter of further study and to make comparisons.
// I tested the bucket methodology against just using a stream of returns and they produce the same result so this routine simply uses a stream of returns.
// At any rate, the beta produced by this method does not match to Yahoo finance.
//public double Beta(Prices bmkPrices)
//{
// double beta=double.NaN;
// if(Count!=bmkPrices.Count)return beta;
// float[] cumReturnsPricesArray=GetReturns();
// float[] cumReturnsPricesBenchmark=bmkPrices.GetReturns();
// beta=Numerics.Beta(ref cumReturnsPricesArray,ref cumReturnsPricesBenchmark);
// return beta*10;
//}
// 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 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 double[] GetVolume()
{
double[] volumeArray = new double[Count];
for (int index = 0; index < Count; index++)
{
volumeArray[index] = (double)this[index].Volume;
}
return volumeArray;
}
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[] 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 + 1))
// {
// 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);
// 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
{
private String symbol;
private DateTime date;
private double open;
private double high;
private double low;
private double close;
private long volume;
private double adjClose;
public Price()
{
}
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;
return clonePrice;
}
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 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 "Date,Symbol,Open,High,Low,Close,Volume,Adj Close"; } // ,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));
return sb.ToString();
}
}
}