Continuously graph stock prices in C#

[graph stock prices]

The example Get stock prices from the internet in C# explains how to get stock prices from the Yahoo! finance web site. This example uses similar techniques to query the site periodically and then graph stock prices.

This example is kind of long so only the key pieces are described here. See the code for other details.

The program stores stock price data in a List<List<PointF>> named Prices.

// The price data.
private List<List<PointF>> Prices = null;

Each of the entries in Prices is a List<PointF> representing one stock’s prices. The X coordinate represents the elapsed time since the program started tracking values. The example’s Timer has Interval = 60000 so the program gets the stocks’ current prices every 60 seconds. Each point’s Y value represents the stock’s price.

When the program’s Timer fires, the code gets the current prices and adds them to the Prices list. If the list contains more than MaxNumPrices values (60 in the example) for each stock, the program removes the oldest price for each stock and decrements the prices’ X values so the results start at the left edge of the graph’s drawing area.

The following code shows the Timer object’s Tick event handler and the ShowLatestPrices method that it calls. ShowLatestPrices coordinates all of the work.

// Get the latest prices and then redraw the graph.
private void tmrCheckPrices_Tick(object sender, EventArgs e)
{
    ShowLatestPrices();
}

// Get the latest prices and then redraw the graph.
private void ShowLatestPrices()
{
    // Get the latest prices.
    List<float> current_prices = GetStockPrices(Symbols);

    // Add these prices to the old ones.
    for (int i = 0; i < Prices.Count; i++)
    {
        Prices[i].Add(new PointF(Prices[i].Count,
            current_prices[i]));
        Console.Write(current_prices[i].ToString() + " ");
    }
    Console.WriteLine("");  // Debugging.

    // If we have more than MaxNumPrices values, remove the oldest.
    if (Prices[0].Count > MaxNumPrices)
    {
        List<List<PointF>> new_price_lists =
            new List<List<PointF>>();
        foreach (List<PointF> old_price_list in Prices)
        {
            // Copy all but the first point into the new list,
            // decrementing X.
            List<PointF> new_price_list = new List<PointF>();
            for (int i = 1; i < old_price_list.Count; i++)
            {
                new_price_list.Add(new PointF(
                    old_price_list[i].X - 1,
                    old_price_list[i].Y));
            }
            new_price_lists.Add(new_price_list);
        }

        // Save the new lists.
        Prices = new_price_lists;
    }

    // Redraw the graph.
    picGraph.Refresh();
}

ShowLatestPrices calls GetStockPrices (described shortly) to get the current prices for the stocks. It adds the new prices to the previous ones. Then if the program has too many prices, the method removes the oldest values and updates the remaining values’ X coordinates. The method finishes by making the program’s PictureBox refresh itself to redraw the graph.

The following code shows the GetStockPrices method.

// Get the latest stock prices.
private List<float> GetStockPrices(List<string> symbols)
{
    // Build the URL.
    string symbol_text = string.Join("+", symbols.ToArray());
    string url =
        "http://download.finance.yahoo.com/d/quotes.csv?s=" +
        symbol_text + "&f=sl1d1t1c1";

    // Get the stock data.
    try
    {
        // Get the web response.
        string result = GetWebResponse(url);

        // Pull out the current prices.
        string[] lines = result.Split(
            new char[] { '\r', '\n' },
            StringSplitOptions.RemoveEmptyEntries);

        List<float> prices = new List<float>();
        foreach (string line in lines)
            prices.Add(float.Parse(line.Split(',')[1]));

        return prices;
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message, "Read Error",
            MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
    }

    // If we get this far, we failed.
    return null;
}

GetStockPrices builds a URL of the correct form. It then calls the GetWebResponse method described in the earlier example. It places the results in a List<float> and returns the list.

The most interesting remaining piece of code is the DrawGraph method shown in the following code.

// Draw the graph.
private void picGraph_Paint(object sender, PaintEventArgs e)
{
    DrawGraph(e.Graphics);
}

// Draw the graph.
private void DrawGraph(Graphics gr)
{
    gr.Clear(Color.White);
    gr.SmoothingMode = SmoothingMode.AntiAlias;
    if (Prices == null) return;
    if (Prices[0].Count < 2) return;

    // Find the maximum price.
    const float min_price = 0;
    float max_price = 50;     // Use at least 50.
    if (Prices[0].Count > 0)
    {
        foreach (List<PointF> pts in Prices)
        {
            float maxy = pts.Max(pt => pt.Y);
            if (max_price < maxy) max_price = maxy;
        }
    }
    max_price *= 1.1f;

    // Scale.
    RectangleF rect = new RectangleF(
        0, min_price, MaxNumPrices, max_price);
    int wid = picGraph.ClientSize.Width;
    int hgt = picGraph.ClientSize.Height;
    PointF[] points = 
    { 
        new PointF(0, hgt), 
        new PointF(wid, hgt), 
        new PointF(0, 0) 
    };
    gr.Transform = new Matrix(rect, points);

    // Draw the grid lines.
    Matrix inverse = gr.Transform.Clone();
    inverse.Invert();
    using (StringFormat string_format = new StringFormat())
    {
        using (Pen thin_pen = new Pen(Color.Gray, 0))
        {
            for (int y = 0; y <= max_price; y += 10)
            {
                gr.DrawLine(thin_pen, 0, y, MaxNumPrices, y);
            }
            for (int x = 0; x < MaxNumPrices; x++)
            {
                gr.DrawLine(thin_pen, x, min_price,
                    x, min_price + 2);
            }
        }
    }
    
    // Draw the graphs.
    Color[] colors = { Color.Black, Color.Red, Color.Green,
        Color.Blue, Color.Orange, Color.Purple };
    for (int i = 0; i < Prices.Count; i++)
    {
        Color clr = colors[i % colors.Length];
        using (Pen thin_pen = new Pen(clr, 0))
        {
            // Plot the prices.
            gr.DrawLines(thin_pen, Prices[i].ToArray());

            // Draw the symbol's name.
            DrawSymbolName(gr, Symbols[i], Prices[i][0].Y, clr);
        }
    }
}

DrawGraph first finds the largest price for any stock. (It uses LINQ to find the largest price in each stock's price list.) Next the method sets the Graphics object's transformation to map the area 0 ≤= X ≤ MaxNumPrices, 0 ≤ Y ≤ max_price onto the drawing area. It then draws lines on the graph's background to show prices of 0, 10, 20, and so forth.

DrawGraph then loops through the lists drawing each stock's prices and calling DrawSymbolName to draw each stock's symbol near the beginning of its curve. You might like to draw the symbol where the first point is. Unfortunately if you do that, the text is transformed just as the curves are so it appears stretched and upside down.

The DrawSymbolName method shown in the following code draws the text unstretched and right side up.

// Draw the text at the specified location.
private void DrawSymbolName(Graphics gr, string txt,
    float y, Color clr)
{
    // See where the point is in PictureBox coordinates.
    Matrix old_transformation = gr.Transform;
    PointF[] pt = { new PointF(0, 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))
        {
            gr.DrawString(txt, small_font, br, 0, pt[0].Y);
        }
    }

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

DrawSymbolName saves the Graphics object's current transformation so it can restore it later. It uses the transformation to transform the point where the text should be drawn so it knows that location in the PictureBox control's normal coordinates (where (0, 0) is in the upper left corner and distances are measured in pixels). The code then resets the transformation so it can work in the PictureBox control's normal coordinates. Finally the method draws the text at the transformed location.


Download Example   Follow me on Twitter   RSS feed   Donate




This entry was posted in algorithms, finance, graphics, internet, mathematics, multimedia, web and tagged , , , , , , , , , , , , , , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *