//////////////////////////Trade.aspx/////////////////////////////////////////// using System; using System.Collections; using System.ComponentModel; using System.Data; using System.Drawing; using System.Web; using System.Web.SessionState; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.HtmlControls; using COM.TwoForBoth.CalcLib; using SoftwareFX.ChartFX; using SoftwareFX.ChartFX.Annotation; using SoftwareFX.ChartFX.Internet.Server; using SoftwareFX.ChartFX.Internet.Server.GalleryObj; namespace CalcLibTest { /// /// Summary description for History. /// public class History : System.Web.UI.Page { protected System.Web.UI.WebControls.Label lblSymbolNotFound; protected System.Web.UI.WebControls.Button btnOK; protected System.Web.UI.WebControls.TextBox txtSymbol; protected System.Web.UI.WebControls.Label Label1; protected Chart Chart1; protected System.Web.UI.WebControls.Label Label2; protected System.Web.UI.WebControls.Label Label3; protected System.Web.UI.WebControls.TextBox txtStartingBalance; protected System.Web.UI.WebControls.Label lblInvalidValue; protected System.Web.UI.WebControls.DataGrid DataGridResults; private static string tableName = "myTable"; private static string colDate = "Date"; private static string colPosition = "Position"; private static string colSize = "Size"; private static string colPrice = "Price"; private static string colCost = "Cost"; private static string colBalance = "Balance"; private DataTable myDataTable = new DataTable(tableName); private double [] opens; private double [] highs; private double [] lows; private double [] closes; private DateTime [] dates; private DateTime startDate = new DateTime(2002, 1, 2); private DateTime endDate = new DateTime(2002, 12, 31); //Need at least 26 trading days previous to startDate for EMA26 private DateTime calcStartDate = new DateTime(2001, 11, 1); private Signal signal; protected System.Web.UI.WebControls.Label Label4; protected System.Web.UI.WebControls.Label Label5; protected System.Web.UI.WebControls.Label lblPandL; protected System.Web.UI.WebControls.Calendar CalendarStart; protected System.Web.UI.WebControls.Calendar CalendarEnd; private MACD macd; private void Page_Load(object sender, System.EventArgs e) { // Put user code to initialize the page here Chart1.OpenData(SoftwareFX.ChartFX.COD.Values, 6, 0); Chart1.CloseData(SoftwareFX.ChartFX.COD.Values); buildTable(); CalendarStart.TodaysDate = startDate; CalendarEnd.TodaysDate = endDate; } #region Web Form Designer generated code override protected void OnInit(EventArgs e) { // // CODEGEN: This call is required by the ASP.NET Web Form Designer. // InitializeComponent(); base.OnInit(e); } /// /// Required method for Designer support - do not modify /// the contents of this method with the code editor. /// private void InitializeComponent() { this.btnOK.Click += new System.EventHandler(this.btnOK_Click); this.CalendarStart.SelectionChanged += new System.EventHandler(this.CalendarStart_SelectionChanged); this.CalendarEnd.SelectionChanged += new System.EventHandler(this.CalendarEnd_SelectionChanged); this.Load += new System.EventHandler(this.Page_Load); } #endregion private void btnOK_Click(object sender, System.EventArgs e) { double val; double balance; //Initially clear the chart of any extensions Chart1.Extensions.Clear(); //Interested in closes for all trading dates in 2002 startDate = CalendarStart.SelectedDate; endDate = CalendarEnd.SelectedDate; //Need at least 26 trading days previous to startDate for EMA26 calcStartDate = startDate.AddMonths(-2); //Get historical data for plotting HistoryArray ha = new HistoryArray(txtSymbol.Text, startDate, endDate); if (ha.getSize() > 0) { //Get historical data for calculations HistoryArray preArray = new HistoryArray(txtSymbol.Text, calcStartDate, endDate); double [] preCloses = preArray.getCloses(); DateTime [] preDates = preArray.getDates(); lblSymbolNotFound.Visible = false; lblInvalidValue.Visible = false; try { balance = Double.Parse(txtStartingBalance.Text); } catch { lblInvalidValue.Visible = true; balance = 10000.0; } opens = ha.getOpens(); highs = ha.getHighs(); lows = ha.getLows(); closes = ha.getCloses(); dates = ha.getDates(); macd = new MACD(preCloses, preDates); macd.doCalc(); signal = new Signal(preCloses, preDates); signal.doCalc(); //Analyze when to trade using the MACD Analyzer an = new Analyzer(macd, ha); an.doCalc(); int index = an.getOutputPointIndexByDate(startDate); int endIndex = an.getDataSize(); //Create a portfolio and trade based on the MACD rules results returned by tge analyzer Portfolio p = new Portfolio(balance); for (int i = index; i != endIndex; i++) { Data d = an.getOutputPointByIndex(i); if (d != null) { if (d.status == (int)Analyzer.TradeIndicators.Buy || d.status == (int)Analyzer.TradeIndicators.Sell) { p.add(d.date, (int)d.status, d.val); } } } //In case last trade was a buy double finalPrice = ha.getCloseByDate(endDate); p.forceLastSell(endDate, finalPrice); fillTable(p, balance, startDate, endDate, ha.getCloseByDate(endDate)); int nPoints = opens.Length; string title = txtSymbol.Text + " " + startDate.ToShortDateString() + " - " + endDate.ToShortDateString(); Chart1.Titles[0].Text = title; Chart1.AxisX.Title.Text = "Day Number"; Chart1.AxisY.Title.Text = "Price"; //Open the Values channels specifying 5 series //Open, High, Low, Close and MACD and "nPoints" points Chart1.OpenData(SoftwareFX.ChartFX.COD.Values, 6, nPoints); //Set Data DateTime currentDate = new DateTime(2002, 1, 1); for (int j = 0; j != nPoints; j++) { if (ha.hasDate(currentDate)) { Chart1.Value[(int)SoftwareFX.ChartFX.OHLC.LOW, j] = lows[j]; Chart1.Value[(int)SoftwareFX.ChartFX.OHLC.OPEN, j] = opens[j]; Chart1.Value[(int)SoftwareFX.ChartFX.OHLC.CLOSE, j] = closes[j]; Chart1.Value[(int)SoftwareFX.ChartFX.OHLC.HIGH, j] = highs[j]; //fifth series will have the moving avg Data point = macd.getOutputPointByDate(currentDate); if (point != null) { val = point.val; if (val != Data.Undefined && val != Data.Invalid) { Chart1.Value[4, j] = val; } else { Chart1.Value[4, j] = SoftwareFX.ChartFX.Internet.Server.Chart.Hidden; } } else { Chart1.Value[4, j] = SoftwareFX.ChartFX.Internet.Server.Chart.Hidden; } point = signal.getOutputPointByDate(currentDate); if (point != null) { val = point.val; if (val != Data.Undefined && val != Data.Invalid) { Chart1.Value[5, j] = val; } else { Chart1.Value[5, j] = SoftwareFX.ChartFX.Internet.Server.Chart.Hidden; } } else { Chart1.Value[5, j] = SoftwareFX.ChartFX.Internet.Server.Chart.Hidden; } } currentDate = currentDate.AddDays(1); } //Close the Values channel Chart1.CloseData(SoftwareFX.ChartFX.COD.Values); Chart1.RecalcScale(); //Now use the gallery property in the series object to create a line for the moving avg SoftwareFX.ChartFX.Internet.Server.SeriesAttributes series = Chart1.Series[4]; series.Gallery = SoftwareFX.ChartFX.Gallery.Lines; series.MarkerShape = SoftwareFX.ChartFX.MarkerShape.None; SoftwareFX.ChartFX.Internet.Server.SeriesAttributes series2 = Chart1.Series[5]; series2.Gallery = SoftwareFX.ChartFX.Gallery.Lines; series2.MarkerShape = SoftwareFX.ChartFX.MarkerShape.None; Chart1.SerLegBox = true; Chart1.Series[(int)SoftwareFX.ChartFX.OHLC.LOW].Legend = "Low"; Chart1.Series[(int)SoftwareFX.ChartFX.OHLC.OPEN].Legend = "Open"; Chart1.Series[(int)SoftwareFX.ChartFX.OHLC.CLOSE].Legend = "Close"; Chart1.Series[(int)SoftwareFX.ChartFX.OHLC.HIGH].Legend = "High"; Chart1.Series[4].Legend = "MACD"; Chart1.Series[5].Legend = "Signal"; annotateChart(p, ha); Chart1.UserLegendBoxObj.Docked = Docked.Right; UserLegendBoxItem item1 = Chart1.UserLegendBoxObj.Item[0]; item1.Label = "Buy"; item1.Color = Color.Green; item1.MarkerShape = MarkerShape.Circle; item1.BorderEffect = BorderEffect.None; item1.Border.Color = Color.Black; UserLegendBoxItem item2 = Chart1.UserLegendBoxObj.Item[1]; item2.Label = "Sell"; item2.Color = Color.Red; item2.MarkerShape = MarkerShape.Circle; item2.BorderEffect = BorderEffect.None; item2.Border.Color = Color.Black; Chart1.UserLegendBox = true; lblPandL.Visible = true; } else { Chart1.SerLegBox = false; Chart1.OpenData(SoftwareFX.ChartFX.COD.Values, 4, 0); Chart1.ClearData(SoftwareFX.ChartFX.ClearDataFlag.AllData); Chart1.CloseData(SoftwareFX.ChartFX.COD.Values); lblSymbolNotFound.Visible = true; Chart1.UserLegendBox = false; } } /// /// Constructs the table columns /// private void buildTable() { DataColumn myDataColumn; // Date Column myDataColumn = new DataColumn(); myDataColumn.ColumnName = colDate; myDataColumn.AutoIncrement = false; myDataColumn.ReadOnly = true; myDataColumn.Unique = false; // Add the column to the table. myDataTable.Columns.Add(myDataColumn); // Position Column myDataColumn = new DataColumn(); myDataColumn.ColumnName = colPosition; myDataColumn.AutoIncrement = false; myDataColumn.ReadOnly = true; myDataColumn.Unique = false; // Add the column to the table. myDataTable.Columns.Add(myDataColumn); // Size Column myDataColumn = new DataColumn(); myDataColumn.ColumnName = colSize; myDataColumn.AutoIncrement = false; myDataColumn.ReadOnly = true; myDataColumn.Unique = false; // Add the column to the table. myDataTable.Columns.Add(myDataColumn); // Price myDataColumn = new DataColumn(); myDataColumn.ColumnName = colPrice; myDataColumn.AutoIncrement = false; myDataColumn.ReadOnly = true; myDataColumn.Unique = false; // Add the column to the table. myDataTable.Columns.Add(myDataColumn); // Cost Column myDataColumn = new DataColumn(); myDataColumn.ColumnName = colCost; myDataColumn.AutoIncrement = false; myDataColumn.ReadOnly = true; myDataColumn.Unique = false; // Add the column to the table. myDataTable.Columns.Add(myDataColumn); // Balance Column myDataColumn = new DataColumn(); myDataColumn.ColumnName = colBalance; myDataColumn.AutoIncrement = false; myDataColumn.ReadOnly = true; myDataColumn.Unique = false; // Add the column to the table. myDataTable.Columns.Add(myDataColumn); } /// /// Draws red circles for sell points and green circles for buy points /// /// Portfolio object /// HistoryArray object private void annotateChart(Portfolio p, HistoryArray ha) { int priorIndex = -1; AnnotationX annotx = new AnnotationX(); Chart1.Extensions.Add(annotx); int endIndex = p.getSize(); for (int i = 0; i != endIndex; i++) { //Get the trade Trade t = p.getReportLine(i); string priceString = t.PriceString; double price = Double.Parse(priceString); DateTime date = DateTime.Parse(t.DateString); int dateIndex = ha.getIndexFromDate(date); string tradeString = t.PositionString; if (dateIndex != priorIndex) { AnnotationCircle circle = new AnnotationCircle(); annotx.List.Add(circle); // Configure the annotation object //if (t.PositionString.Equals("LONG")) if (t.Signal == (int)Trade.Signals.buy) { circle.Color = System.Drawing.Color.Green; } else { circle.Color = System.Drawing.Color.Red; } circle.Height = 10; circle.Width = 10; circle.Attach(dateIndex, price); circle.Refresh(); priorIndex = dateIndex; } } } /// /// Builds the P&L Report /// /// Portfolio object /// Beginning balance (should really get this from the portfolio object /// Starting period of trading /// Ending period of trading /// Final price for endDate private void fillTable(Portfolio p, double startingBalance, DateTime startDate, DateTime endDate, double endingPrice) { DataRow myDataRow; // Instantiate the DataSet variable. DataSet myDataSet = new DataSet(); // Add the new DataTable to the DataSet. myDataSet.Tables.Add(myDataTable); //First line is starting balance myDataRow = myDataTable.NewRow(); myDataRow[colDate] = startDate.ToShortDateString(); myDataRow[colPosition] = "START"; myDataRow[colSize] = "NA"; myDataRow[colPrice] = "NA"; myDataRow[colCost] = "NA"; myDataRow[colBalance] = String.Format("{0:c}", startingBalance); myDataTable.Rows.Add(myDataRow); // Create new DataRow objects and add them to the DataTable int endIndex = p.getSize(); for (int i = 0; i != endIndex; i++) { //Get the trade Trade t = p.getReportLine(i); myDataRow = myDataTable.NewRow(); myDataRow[colDate] = t.DateString; myDataRow[colPosition] = t.PositionString; myDataRow[colSize] = t.SizeString; myDataRow[colPrice] = t.PriceString; myDataRow[colCost] = t.CostString; myDataRow[colBalance] = t.BalanceString; myDataTable.Rows.Add(myDataRow); } //Ending Balance line myDataRow = myDataTable.NewRow(); myDataRow[colDate] = endDate.ToShortDateString(); myDataRow[colPosition] = "END"; myDataRow[colSize] = "NA"; myDataRow[colPrice] = "NA"; myDataRow[colCost] = "NA"; myDataRow[colBalance] = p.EndingBalanceString; myDataTable.Rows.Add(myDataRow); //Ending Balance line myDataRow = myDataTable.NewRow(); myDataRow[colDate] = endDate.ToShortDateString(); myDataRow[colPosition] = "P&L"; myDataRow[colSize] = "NA"; myDataRow[colPrice] = "NA"; myDataRow[colCost] = "NA"; myDataRow[colBalance] = p.PL; myDataTable.Rows.Add(myDataRow); // Instruct the DataGrid to bind to the DataSet, with the // ParentTable as the topmost DataTable. DataGridResults.DataSource = myDataSet; DataGridResults.DataBind(); } private void CalendarStart_SelectionChanged(object sender, System.EventArgs e) { CalendarStart.TodaysDate = CalendarStart.SelectedDate; } private void CalendarEnd_SelectionChanged(object sender, System.EventArgs e) { CalendarEnd.TodaysDate = CalendarEnd.SelectedDate; } } } ////////////CALCLIB.CS/////////////////////////////////////////////////////// using System; using System.Collections; ///Calc library Copyright Stuart Blavatnik 2003 ///This library calculates the following formulas: /// /// 1) Simple Moving Average (aka SMA) /// 2) Exponential Moving Average (aka EMA) /// 3) Moving average convergence divergence (aka MACD) /// 4) Signal (aka 9 period EMA of MACD) namespace COM.TwoForBoth.CalcLib { /// /// Data represents a data point. The point is made up of a value, /// a date and a status (whether the value was initialized or not) /// public class Data { private double v; //Value private DateTime d; //Date associated with Value private int s; //status of value -- default to Undefined public static int Undefined //Point was not defined yet { get { return -1; } } public static int Initialized //Point was initialized { get { return 1; } } public static double Invalid //Invalid Value { get { return -9999999.9999; } } public double val { get { return v; } set { v = value; status = Initialized; } } public DateTime date { get { return d; } set { d = value; } } public int status { get { return s; } set { s = value; } } //Default Constructor public Data() { v = 0.0; d = new DateTime(1980, 1, 1, 0, 0, 0); s = Undefined; } public Data(double val, DateTime date) { v = val; d = date; s = Initialized; } public Data(double val) { v = val; s = Initialized; } } /// /// Calc is a base class for other calculation formulas /// public abstract class Calc { protected Data [] input; protected Data [] output; public enum status { ok, bad } /// /// Constructor taking values and dates /// /// Values /// Dates public void initValues(double [] values, DateTime [] dates) { input = new Data[values.Length]; output = new Data[values.Length]; for (int i = 0; i < input.Length; i++) { input[i] = new Data(values[i], dates[i]); output[i] = new Data(); output[i].date = dates[i]; } } /// /// Constructor taking only values /// /// Values public void initValues(double [] values) { input = new Data[values.Length]; output = new Data[values.Length]; for (int i = 0; i < output.Length; i++) { input[i] = new Data(values[i]); output[i] = new Data(); } } abstract public int doCalc(); public Data [] getResults() { return output; } /// /// Retrieves a data point from the output array given an index into the output array /// /// Index of array (value between 0 and array.size /// Data object or null public Data getOutputPointByIndex(int index) { Data retval = null; if (index >= 0 && index < output.Length) { retval = output[index]; } return retval; } /// /// Retrieves a data point given a date /// /// DateTime object /// Data object or null public Data getOutputPointByDate(DateTime date) { Data retval = null; for (int i = 0; i < output.Length; i++) { if (output[i].status != Data.Undefined && date.Equals(output[i].date)) { retval = output[i]; break; } } return retval; } /// /// Retrieves the first output point where the data status is not Undefined /// /// Point, or null public Data getFirstDefinedPoint() { Data retval = null; for (int i = 0; i < output.Length; i++) { if (output[i].status != Data.Undefined) { retval = output[i]; break; } } return retval; } /// /// Retrieves the first output point where the data status is not Undefined /// /// Point, or null public int getFirstDefinedPointIndex() { int retval = -1; for (int i = 0; i < output.Length; i++) { if (output[i].status != Data.Undefined) { retval = i; break; } } return retval; } public int getOutputLength() { return output.Length; } public int getInputLength() { return input.Length; } } /// /// Class: SimpleMovingAverage /// /// A simple moving average is formed by finding the average price /// of a security over a set number of periods. Most often, the /// closing price is used to compute the moving average. For /// example: a 5-day moving average would be calculated by adding /// the closing prices for the last 5 days and dividing the total /// by 5. /// example: /// 10 + 11 + 12 + 13 + 14 = 60 /// 60 / 5 = 12 /// /// A moving average moves because as the newest period is added, /// the oldest period is dropped. If the next closing price in the /// average is 15, then this new period would be added and the /// oldest day, which is 10, would be dropped. The new 5-day moving /// average would be calculated as follows: /// /// 11 + 12 + 13 + 14 + 15 = 65 /// 65 / 5 = 13 /// /// Over the last 2 days, the moving average moved from 12 to 13. /// As new days are added, the old days will be subtracted and /// the moving average will continue to move over time. /// public class SimpleMovingAverage : Calc { private int period; //Number of values to take public int Period { get { return period; } } public SimpleMovingAverage(double [] values, DateTime [] dates, int periods) { initValues(values, dates); period = periods; } /// /// Function: doCalc() /// /// Parameters: None /// /// Description: Calculates a simple moving average /// public override int doCalc() { int retval = (int)Calc.status.ok; for (int j = 0; j < input.Length; j++) { if (j >= period - 1 && input[j].val != Data.Invalid) //0 based { output[j].val = getSum(j, period) / period; } else { output[j].val = 0; output[j].status = Data.Undefined; } } return retval; } public Data getOutputPoint(int index) { //Add check here for index return output[index]; } private double getSum(int start, int length) { double retval = 0.0; for (int i = start, j = 0; j < (length); i--, j++) { if (input[i].val != Data.Invalid) { retval += input[i].val; } } return retval; } } /// /// Class: ExponentialMovingAverage /// /// In order to reduce the lag in simple moving averages, technicians /// sometimes use exponential moving averages, or exponentially /// weighted moving averages. Exponential moving averages reduce /// the lag by applying more weight to recent prices relative to /// older prices. The weighting applied to the most recent price /// depends on the length of the moving average. The shorter the /// exponential moving average is, the more weight that will be /// applied to the most recent price. For example: a 10-period /// exponential moving average weighs the most recent price 18.18% /// and a 20-period exponential moving average weighs the most /// recent price 9.52%. The method for calculating the exponential /// moving average is fairly complicated. The important thing to /// remember is that the exponential moving average puts more /// weight on recent prices. As such, it will react quicker to /// recent price changes than a simple moving average. /// /// Exponential Moving Average Calculation /// The formula for an exponential moving average is: /// X = (K x (C - P)) + P /// X = Current EMA /// C = Current Price /// P = Previous period's EMA* /// K = Smoothing constant /// (*A SMA is used for first period's calculation) /// /// The smoothing constant applies the appropriate weighting to the /// most recent price relative to the previous exponential moving /// average. The formula for the smoothing constant is: /// /// K = 2/(1+N) /// N = Number of periods for EMA /// For a 10-period EMA, the smoothing constant would be .1818. /// public class ExponentialMovingAverage : Calc { private int period; //Number of values to take private SimpleMovingAverage sma; private double smoothingConstant; public int Period { get { return period; } } public ExponentialMovingAverage(double [] values, DateTime [] dates, int periods) { initValues(values, dates); period = periods; sma = new SimpleMovingAverage(values, dates, periods); smoothingConstant = calcSmoothingConstant(period); } private double calcSmoothingConstant(int period) { double k = (double) 2 / ( 1 + period); return k; } public override int doCalc() { int retval = (int)Calc.status.ok; // First calculate the SMA for the use for the first point sma.doCalc(); //Get first defined data point //1/21/03 -- now first check the value of sma.getFirstDefinedPoint() Data d = sma.getFirstDefinedPoint(); if (d != null) { double simpleMovingAverage = sma.getFirstDefinedPoint().val; /// X = (K x (C - P)) + P /// X = Current EMA /// C = Current Price /// P = Previous period's EMA* /// K = Smoothing constant /// (*A SMA is used for first period's calculation) bool first = true; for (int j = 0; j < input.Length; j++) { if (j >= period - 1 && input[j].val != Data.Invalid) //0 based { if (first) { output[j].val = simpleMovingAverage; first = false; } else { output[j].val = (smoothingConstant * (input[j].val - output[j - 1].val)) + output[j - 1].val; } } else { output[j].val = 0; output[j].status = Data.Undefined; } } } else { retval = (int)Calc.status.bad; } return retval; } } /// /// Class MACD -- Simply the difference between the 12 and 26 day moving averages /// public class MACD : Calc { private ExponentialMovingAverage EMA12; private ExponentialMovingAverage EMA26; public MACD(double [] values, DateTime [] dates) { initValues(values, dates); EMA12 = new ExponentialMovingAverage(values, dates, 12); EMA26 = new ExponentialMovingAverage(values, dates, 26); EMA12.doCalc(); EMA26.doCalc(); } /// /// Subtracts the values of the 12 DAY EMA from the 26 DAY EMA /// public override int doCalc() { int retval = (int)Calc.status.ok; for (int i = 0; i < input.Length; i++) { //Get the points for the 12 and 26 day EMA's using date from 12 Day Data point = EMA26.getOutputPointByDate(EMA12.getOutputPointByIndex(i).date); if (point != null) { output[i].date = point.date; output[i].val = EMA12.getOutputPointByIndex(i).val - point.val; } else { output[i].status = Data.Undefined; } } return retval; } } /// /// Signal can also be thought of as the 9 period EMA of the MACD /// public class Signal : Calc { private MACD macd; private DateTime[] dates; public Signal(double [] values, DateTime [] dates) { initValues(values, dates); this.dates = dates; macd = new MACD(values, dates); macd.doCalc(); } public override int doCalc() { int retval = (int)Calc.status.ok; Data [] macdResults = macd.getResults(); double [] macdVals = new double[macdResults.Length]; for (int i = 0; i < macdResults.Length; i++) { if (macdResults[i].status != Data.Undefined) { macdVals[i] = macdResults[i].val; } else { macdVals[i] = Data.Invalid; } } ExponentialMovingAverage ema9 = new ExponentialMovingAverage(macdVals, dates, 9); ema9.doCalc(); for (int i = 0; i < ema9.getResults().Length; i++) { Data point = ema9.getOutputPointByIndex(i); if (point != null && point.status != Data.Undefined && point.val != Data.Invalid) { output[i].val = point.val; } } return retval; } } /// /// Represents an array of historical points for a given instrument /// public class HistoryArray { private ArrayList HistoricalDataPoints; private QuotesPlusWrapper qpw; public HistoryArray(string symbol, DateTime from, DateTime to) { DateTime date; double open; double high; double low; double close; HistoricalDataPoints = new ArrayList(); qpw = new QuotesPlusWrapper(symbol); if (qpw.symbolExists()) { int fromIndex = qpw.getDaysBack(from); int toIndex = qpw.getDaysBack(to); if (fromIndex != -1 && toIndex != -1) { for (int i = fromIndex; i <= toIndex; i++) { if ((open = qpw.getOpen(i)) > 0.0) { date = qpw.getDate(i); high = qpw.getHigh(i); low = qpw.getLow(i); close = qpw.getClose(i); HistoricalDataPoint hdp = new HistoricalDataPoint(date, open, high, low, close); HistoricalDataPoints.Add(hdp); } } } } } public int getSize() { return HistoricalDataPoints.Count; } public double [] getOpens() { double [] retval = new double[HistoricalDataPoints.Count]; for (int i = 0; i < HistoricalDataPoints.Count; i++) { HistoricalDataPoint hdp = (HistoricalDataPoint)HistoricalDataPoints[i]; retval[i] = hdp.Open; } return retval; } public double [] getHighs() { double [] retval = new double[HistoricalDataPoints.Count]; for (int i = 0; i < HistoricalDataPoints.Count; i++) { HistoricalDataPoint hdp = (HistoricalDataPoint)HistoricalDataPoints[i]; retval[i] = hdp.High; } return retval; } public double [] getLows() { double [] retval = new double[HistoricalDataPoints.Count]; for (int i = 0; i < HistoricalDataPoints.Count; i++) { HistoricalDataPoint hdp = (HistoricalDataPoint)HistoricalDataPoints[i]; retval[i] = hdp.Low; } return retval; } public double [] getCloses() { double [] retval = new double[HistoricalDataPoints.Count]; for (int i = 0; i < HistoricalDataPoints.Count; i++) { HistoricalDataPoint hdp = (HistoricalDataPoint)HistoricalDataPoints[i]; retval[i] = hdp.Close; } return retval; } public DateTime [] getDates() { DateTime [] retval = new DateTime[HistoricalDataPoints.Count]; for (int i = 0; i < HistoricalDataPoints.Count; i++) { HistoricalDataPoint hdp = (HistoricalDataPoint)HistoricalDataPoints[i]; retval[i] = hdp.Date; } return retval; } public bool hasDate(DateTime date) { bool retval = false; for (int i = 0; i < HistoricalDataPoints.Count; i++) { HistoricalDataPoint hdp = (HistoricalDataPoint)HistoricalDataPoints[i]; if (hdp.Date.CompareTo(date) == 0) { retval = true; break; } } return retval; } public double getCloseByDate(DateTime date) { double retval = Data.Invalid; for (int i = 0; i < HistoricalDataPoints.Count; i++) { HistoricalDataPoint hdp = (HistoricalDataPoint)HistoricalDataPoints[i]; if (hdp.Date.CompareTo(date) == 0) { retval = hdp.Close; break; } } return retval; } public int getIndexFromDate(DateTime date) { int retval = -1; for (int i = 0; i < HistoricalDataPoints.Count; i++) { HistoricalDataPoint hdp = (HistoricalDataPoint)HistoricalDataPoints[i]; if (hdp.Date.CompareTo(date) == 0) { retval = i; break; } } return retval; } } /// /// Represents one point (open, high, low, close and date) /// class HistoricalDataPoint { private double open; private double high; private double low; private double close; private DateTime date; public HistoricalDataPoint(DateTime date, double open, double high, double low, double close) { this.date = date; this.open = open; this.high = high; this.low = low; this.close = close; } 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 DateTime Date { get { return date; } set { date = value; } } } /// /// Wraps the data access functions /// class QuotesPlusWrapper { private QuotesPlus.Price2 qpPrice2 = new QuotesPlus.Price2Class(); public QuotesPlusWrapper(string symbol) { //qpPrice2.RawData = 0; //ID_RAWDATA -- 0 or 1 based on check box //qpPrice2.UseHolidays = 1; //ID_HOL -- 0 or 1 based on check box qpPrice2.Symbol = symbol; } public bool symbolExists() { bool retval = true; object o = qpPrice2.Open(-1); if (o.ToString().Equals("0")) { retval = false; } return retval; } /// /// getDaysBack() retrieves the number of days back in the QP2 database a /// particular date is relative to the 0 point in the database (note QP2 days /// are from 0 to -3000) /// /// QuotePlus Price 2 object /// Date being searched for /// Number of days to go back public int getDaysBack(DateTime date) { object o; int retval = -1; DateTime d; for (int i = 0; i != -3000; i--) { o = qpPrice2.Date(i); try { d = (DateTime)o; if (d.Equals(date)) { retval = i; break; } } catch { } } return retval; } public double getOpen(int daynum) { return Double.Parse(qpPrice2.Open(daynum).ToString()); } public double getHigh(int daynum) { return Double.Parse(qpPrice2.High(daynum).ToString()); } public double getLow(int daynum) { return Double.Parse(qpPrice2.Low(daynum).ToString()); } public double getClose(int daynum) { return Double.Parse(qpPrice2.Close(daynum).ToString()); } public DateTime getDate(int daynum) { return DateTime.Parse(qpPrice2.Date(daynum).ToString()); } } public class Analyzer : Calc { private MACD macd; private HistoryArray ha; //private Data [] output; public enum TradeIndicators { Buy, Sell, Neutral } public Analyzer(MACD macd, HistoryArray ha) { this.macd = macd; this.ha = ha; output = new Data[macd.getOutputLength()]; //Might want to use HA.length here for (int i = 0; i < output.Length; i++) { output[i] = new Data(); } } /// /// Does the MACD analysis /// public override int doCalc() { int retval = (int)Calc.status.ok; int currentIndex = macd.getFirstDefinedPointIndex(); if (currentIndex != -1) { int endIndex = macd.getOutputLength(); double previousValue = 0; //Assume neutral for (int j = 0; j < currentIndex; j++) { Data point = macd.getOutputPointByIndex(j); output[j].date = point.date; output[j].val = Data.Invalid; output[j].status = (int)TradeIndicators.Neutral; } for (int i = currentIndex; i < endIndex; i++) { Data point = macd.getOutputPointByIndex(i); if (point.status != Data.Invalid && point.status != Data.Undefined) { if (point.val < 0.0 && previousValue < 0.0) { output[i].date = point.date; output[i].val = ha.getCloseByDate(point.date); output[i].status = (int)TradeIndicators.Neutral; } else if (point.val < 0.0 && previousValue >= 0.0) { output[i].date = point.date; output[i].val = ha.getCloseByDate(point.date); output[i].status = (int)TradeIndicators.Sell; } else if (point.val > 0.0 && previousValue > 0.0) { output[i].date = point.date; output[i].val = ha.getCloseByDate(point.date); output[i].status = (int)TradeIndicators.Neutral; } else if (point.val > 0.0 && previousValue <= 0.0) { output[i].date = point.date; output[i].val = ha.getCloseByDate(point.date); output[i].status = (int)TradeIndicators.Buy; } previousValue = point.val; } } } else { retval = (int)Calc.status.bad; } return retval; } /// /// Retrieves an index given a date /// /// DateTime object /// Index >= 0 public int getOutputPointIndexByDate(DateTime date) { int retval = -1; for (int i = 0; i < output.Length; i++) { if (output[i].status != Data.Undefined && date.Equals(output[i].date)) { retval = i; break; } } return retval; } public int getDataSize() { return output.Length; } } /// /// Individual trade /// public class Trade { public enum Signals { buy, sell } public enum Positions { buyLong, sellLong, sellShort, coverShort } public Trade(DateTime date, int signal, int position, double size, double price, double balance) { this.date = date; this.signal = signal; this.position = position; this.size = size; this.price = price; this.cost = size * price; this.balance = balance; } private DateTime date; private int signal; private int position; private double size; private double price; //share price private double cost; //transaction cost private double balance; public double Size { get { return size; } } public double Price { get { return price; } } public double Cost { get { return cost; } } public int Signal { get { return signal; } } public int Position { get { return position; } } public String DateString { get { return date.ToShortDateString(); } } public string PositionString { get { if (position == (int)Positions.buyLong) { return "BUY LONG"; } else if (position == (int)Positions.coverShort) { return "COVER SHORT"; } else if (position == (int)Positions.sellLong) { return "SELL LONG"; } else { return "SELL SHORT"; } } } public string SizeString { get { return size.ToString(); } } public string PriceString { get { return String.Format("{0:c}", price.ToString()); } } public string CostString { get { return String.Format("{0:c}", cost); } } public string BalanceString { get { return String.Format("{0:c}", balance); } } } public class Portfolio { private ArrayList trades = new ArrayList(); private double balance; private double currentShares; private double startingBalance; private double endingBalance; private double pl; public Portfolio(double balance) { this.balance = balance; startingBalance = balance; endingBalance = balance; pl = 0.0; currentShares = 0.0; } public void forceLastSell(DateTime date, double price) { double size; if (currentShares > 0.0) { size = currentShares; balance += size * price; //Add the trade trades.Add(new Trade(date, (int)Trade.Signals.sell, (int)Trade.Positions.sellLong, size, price, balance)); currentShares = 0.0; } else if (currentShares < 0.0) { //Get previous size size = ((Trade)trades[trades.Count - 1]).Size; double previousPrice = ((Trade)trades[trades.Count - 1]).Price; //buy back the amount outstanding first //balance += size * price; balance += size * (previousPrice - price); currentShares = 0.0; //Add the trade trades.Add(new Trade(date, (int)Trade.Signals.buy, (int)Trade.Positions.coverShort, size, price, balance)); } endingBalance = balance; } public string EndingBalanceString { get { return String.Format("{0:c}", endingBalance); } } public string PL { get { pl = endingBalance - startingBalance; return String.Format("{0:c}", pl); } } public void add(DateTime date, int signal, double price) { double size; int positionType; if (signal == (int)Trade.Signals.buy) { if (currentShares < 0.0) //means they shorted { size = ((Trade)trades[trades.Count - 1]).Size; double previousPrice = ((Trade)trades[trades.Count - 1]).Price; //buy back the amount outstanding first //balance += size * price; balance += size * (previousPrice - price); currentShares = 0.0; //Add the trade trades.Add(new Trade(date, signal, (int)Trade.Positions.coverShort, size, price, balance)); //Buy more with the current balance size = Math.Floor(balance / price); balance -= size * price; currentShares = size; positionType = (int)Trade.Positions.buyLong; } else //buying shares { size = Math.Floor(balance / price); balance -= size * price; currentShares = size; positionType = (int)Trade.Positions.buyLong; } } else //Sell { //Last value if (currentShares > 0.0) { size = ((Trade)trades[trades.Count - 1]).Size; balance += size * price; currentShares = 0.0; positionType = (int)Trade.Positions.sellLong; trades.Add(new Trade(date, signal, positionType, size, price, balance)); //Now Short size = Math.Floor(balance / price); currentShares = size * -1.0; positionType = (int)Trade.Positions.sellShort; } else //For when the first trade is a sell (aka short) { size = Math.Floor(balance / price); currentShares = size * -1.0; positionType = (int)Trade.Positions.sellShort; } } trades.Add(new Trade(date, signal, positionType, size, price, balance)); } public Trade getReportLine(int index) { Trade retval = null; if (index >= 0 && index < trades.Count) { retval = (Trade)trades[index]; } return retval; } public int getSize() { return trades.Count; } } }