Graph historical Dow Jones Industrial Average values in C#

Datetime:2016-08-23 01:35:10          Topic: C#           Share

This program graphs historical prices for the Dow Jones Industrial Average. I got the data from this URL:

ichart.finance.yahoo.com/table.csv?s=^DJI&a=0&b=1&c=1990&d=0&e=1&f=2020&g=d&ignore=.csv

The fields in the URL are:

  • s=^DJI – Get Down Jones Industrial data
  • a=0 – The start month number is 0 (January)
  • b=1 – The start day number is 1 (the 1st of the month)
  • c=1990 – The start year is 1990
  • d=0 – The end month number is 0 (January)
  • e=1 – The end day number is 1 (the 1st of the month)
  • f=2020 – The end year is 2020 (the actual data stops after the current date)
  • ignore=.csv – Return a downloaded CSV (comma-separated value) file

The results are included in the example in the file DjiPrices.csv. At design time, I added the file to the project and set its “Copy to output Directory” property to “Copy if newer” so the file is included in the output directory with the executable program so it’s easy for the program to find.

The program stores price data in the following structure.

// Structure to hold price data.
private struct PriceData
{
    public DateTime Date;
    public float Price;
    public PriceData(DateTime new_Date, float new_Price)
    {
        Date = new_Date;
        Price = new_Price;
    }
};

This structure simply holds a date and the corresponding closing price. You could easily add other fields such as opening price and share volume if you wanted to graph that data. The structure defines a constructor to make initializing a new PriceData structure easy.

The following code shows how the program loads the price data.

// Get the historical prices.
private List<PriceData> GetDjiPrices()
{
    // Get the data by lines.
    string[] lines = File.ReadAllLines("DjiPrices.csv");

    // See which header fields contains Date and Adj Close.
    string[] fields = lines[0].Split(',');
    int date_field = -1, close_field = -1;
    for (int i = 0; i < fields.Length; i++)
    {
        if (fields[i].ToLower() == "adj close")
            close_field = i;
        else if (fields[i].ToLower() == "date")
            date_field = i;
    }

    // Process the lines, skipping the header.
    List<PriceData> price_data = new List<PriceData>();
    for (int i = 1; i < lines.Length; i++)
    {
        fields = lines[i].Split(',');
        price_data.Add(new PriceData(
            DateTime.Parse(fields[date_field]),
            float.Parse(fields[close_field])));
    }

    // Reverse so the data is in historical order.
    price_data.Reverse();
    return price_data;
}

This code uses File.ReadAllLines to read the lines in the data file into an array of strings. It reads through the header line to find the columns that contain the date and the adjusted closing price. (They should be the first and last columns.)

The code then loops through the remaining lines in the file. For each line, the program creates a PriceData structure to hold that day’s closing Dow Jones price and adds it to a list.

The following code shows how the program draws the price data.

// Draw the graph.
private void DrawGraph(List price_data)
{
    // Make the bitmap.
    Bitmap bm = new Bitmap(
        picGraph.ClientSize.Width,
        picGraph.ClientSize.Height);
    using (Graphics gr = Graphics.FromImage(bm))
    {
        gr.Clear(Color.White);
        gr.SmoothingMode = SmoothingMode.AntiAlias;

        // Get the largest price.
        var max_query = from PriceData data
            in price_data select data.Price;
        float max_price = max_query.Max() + 500;

        // Scale and translate the graph.
        float scale_x =
            picGraph.ClientSize.Width / (float)price_data.Count;
        float scale_y =
            -picGraph.ClientSize.Height / max_price;
        gr.ScaleTransform(scale_x, scale_y);
        gr.TranslateTransform(
            0,
            picGraph.ClientSize.Height,
            System.Drawing.Drawing2D.MatrixOrder.Append);

        using (Pen thin_pen = new Pen(Color.Gray, 0))
        {
            // Draw the horizontal grid lines.
            for (int y = 0; y <= max_price; y += 1000)
            {
                // Draw the line.
                gr.DrawLine(thin_pen, 0, y, price_data.Count, y);

                // Draw the value.
                if (y > 0)
                    DrawTextAt(gr, y.ToString("C"), 10, y,
                        Color.Blue, StringAlignment.Near,
                        StringAlignment.Far);
            }

            // Draw the vertical grid lines.
            using (StringFormat string_format = new StringFormat())
            {
                string_format.Alignment =
                    StringAlignment.Center;
                string_format.LineAlignment =
                    StringAlignment.Center;
                int last_year = 0;
                for (int i = 0; i < price_data.Count; i++)
                {
                    // See if this is the start of a new year.
                    if (price_data[i].Date.Year > last_year)
                    {
                        last_year = price_data[i].Date.Year;

                        // Draw a line for the year.
                        gr.DrawLine(thin_pen, i, 0, i, 750);

                        // Draw the year number.
                        DrawTextAt(gr, last_year.ToString(), i, 0,
                            Color.Blue, StringAlignment.Center,
                            StringAlignment.Far);
                    }
                }
            }
        }

        // Draw the prices. Make the data points.
        PointF[] points = new PointF[price_data.Count];
        for (int i = 0; i < price_data.Count; i++)
        {
            points[i] = new PointF(i, price_data[i].Price);
        }

        // Draw the points.
        using (Pen thin_pen = new Pen(Color.Black, 0))
        {
            gr.DrawLines(thin_pen, points);
        }
    }

    // Display the result.
    picGraph.Image = bm;
}

The code starts by creating a bitmap to hold the result. It uses a LINQ query to find the largest price so it can scale the graph to fit nicely. It then gives the Graphics object scale and translation transformations to make future drawing on the object fit the bitmap.

Next the program draws horizontal lines representing prices that are multiples of $1,000. For each price, it calls the DrawTextAt method (described shortly) to display the price on the graph.

The code them draws vertical grid lines showing where each year starts. To do that, it loops through the data comparing each PriceData structure's year to the previous one's. If the year has changed, the program draws a small vertical line and displays the year.

Finally the code loops through the data creating a Point for each value. It then draws lines connecting the points.

The following code shows the DrawTextAt method, which draws untransformed text at a specific point on the graph.

// Draw the text at the specified location.
private void DrawTextAt(Graphics gr, string txt, float x, float y,
    Color clr, StringAlignment alignment,
    StringAlignment line_alignment)
{
    // See where the point is in PictureBox coordinates.
    Matrix old_transformation = gr.Transform;
    PointF[] pt = { new PointF(x, y) };
    gr.Transform.TransformPoints(pt);

    // Reset the transformation.
    gr.ResetTransform();

    // Draw the text.
    using (Font small_font = new Font("Arial", 8))
    {
        using (SolidBrush br = new SolidBrush(clr))
        {
            using (StringFormat string_format = new StringFormat())
            {
                string_format.Alignment = alignment;
                string_format.LineAlignment = line_alignment;
                gr.DrawString(txt, small_font, br,
                    pt[0].X, pt[0].Y, string_format);
            }
        }
    }

    // Restore the original transformation.
    gr.Transform = old_transformation;
}

The trick here is that the point where the text should be drawn is in the transformed graph coordinate system but the text should not be transformed so it doesn't become stretched or flipped upside down. The DrawTextAt method first saves the Graphics object's Transform property that represents the current transformation. It then applies the current transformation to the point (x, y) where the text should appear to see where on the graph that point is.

Next the code resets the Graphics object's transformation to remove it. It then draws the text at the transformed location. Finally the code restores the transformation it saved earlier so future drawing is transformed properly.





About List