다음을 통해 공유


Tutorial: Visualizing Stock Prices Using F# Charts

Applies to: Functional Programming

Authors: Tomas Petricek and Jon Skeet

Referenced Image

Get this book in Print, PDF, ePub and Kindle at manning.com. Use code “MSDN37b” to save 37%.

Summary: This tutorial shows how to download stock prices and visualize them using the FSharpChart library. It includes examples of several standard chart types and explains how to combine multiple charts in a single area.

This topic contains the following sections.

  • Visualizing Data in F#
  • Downloading Stock Data
  • Visualizing Stock Prices
  • Combining Multiple Charts
  • Summary
  • Additional Resources
  • See Also

This article is associated with Real World Functional Programming: With Examples in F# and C# by Tomas Petricek with Jon Skeet from Manning Publications (ISBN 9781933988924, copyright Manning Publications 2009, all rights reserved). No part of these chapters may be reproduced, stored in a retrieval system, or transmitted in any form or by any means—electronic, electrostatic, mechanical, photocopying, recording, or otherwise—without the prior written permission of the publisher, except in the case of brief quotations embodied in critical articles or reviews.

Visualizing Data in F#

F# can be used not only as a language for developing applications or components but also as a tool to explore libraries and work with data interactively. This tutorial focuses on the second scenario. When working with data, it is usually important to understand the structure of the data first (especially if the data source is some unstructured format such as CSV).

Once the dataset is obtained and parsed, the next step is to find the best way to visualize the data. This usually involves some preprocessing of the data (e.g., extract only some values). The data can be also visualized using several chart types with different visual styles.

This tutorial demonstrates the whole process using a sample script that reads stock prices from the Yahoo Finance web site. You will learn how to:

  • Download stock prices from the Yahoo Finance web site and parse the downloaded CSV file into a sequence of F# tuples.

  • Visualize stock price using a basic line chart, using a range chart to display high and low prices per day, and using a candlestick chart that displays opening, closing, high, and low prices for each day.

  • Display prices of two different stocks in a single chart and display multiple prices in separate areas.

The first part of the tutorial creates a function that downloads data from the Yahoo Finance portal. For simplicity, the function is implemented using blocking calls. In a more complex scenario, it would be a good idea to use F# asynchronous workflows to avoid blocking any threads. More information about this approach can be found in Chapter 13 of Real-World Functional Programming.

Downloading Stock Data

The Yahoo Finance portal provides stock prices in an easy to use CSV format. A price history for a given stock (e.g., MSFT) can be downloaded by requesting the contents of a URL like http://ichart.finance.yahoo.com/table.csv?s=MSFT. The contents can be also viewed in web browser, which is a useful method for exploring the data format.

The web site returns a table containing four different prices for each date. The prices are labeled as Open, High, Low, and Close. The first and last values represent the price of stock when the stock exchange opened and closed for that day. The two middle values represent the maximum and minimum price for the given day. For example, a price for a MSFT stock may start at $26.50, then rise up to $26.80, then fall as low as $26.40 and then regain some value to $26.60, which would be the closing price.

The following snippet shows a function getStockPrices that downloads the data. The function takes two arguments. The first one is the name of the stock that we're interested in and the second one is the number of days (counting backwards from the most recent) that we want to download. It returns an array of tuples containing all four prices, with the oldest data at the beginning of the array.

open System
open System.Net

// URL of a service that generates price data
let url = "http://ichart.finance.yahoo.com/table.csv?s="

/// Returns prices (as tuple) of a given stock for a 
/// specified number of days (starting from the most recent)
let getStockPrices stock count =
    // Download the data and split it into lines
    let wc = new WebClient()
    let data = wc.DownloadString(url + stock)
    let dataLines = 
        data.Split([| '\n' |], StringSplitOptions.RemoveEmptyEntries) 

    // Parse lines of the CSV file and take specified
    // number of days using in the oldest to newest order
    seq { for line in dataLines |> Seq.skip 1 do
              let infos = line.Split(',')
              yield float infos.[1], float infos.[2], 
                    float infos.[3], float infos.[4] }
    |> Seq.take count |> Array.ofSeq |> Array.rev

The function uses the DownloadString method of a WebClient object to get the contents of a web site as a string. After splitting the table into individual lines, it uses a sequence expression to convert lines into tuples containing four price values. In the sequence expression, we skip the first line (containing column titles).

The caller is only interested in the first count rows, so the function uses pipelining to pass the generated (lazy) sequence to Seq.take. Next, the sequence is converted to an array and the array is reversed to get rows in the correct order (from the oldest to the newest). To use the result as a data source for a chart, it cannot be an arbitrary sequence. Chart Controls require an IEnumerable that supports the Reset method, which isn't supported for sequence expressions created using the seq { … } syntax. A general advice is to convert the data to an array or an F# list.

A simple way to verify that the function works as expected is to try calling it from F# Interactive. This also makes it possible to easily explore the obtained data:

> getStockPrices "MSFT" 4;;
val it : (float * float * float * float) [] =
  [| (26.53, 26.86, 26.43, 26.59); 
     (26.64, 27.06, 26.5, 26.77);
     (26.91, 26.95, 26.5, 26.55); 
     (26.69, 26.86, 26.51, 26.58) |]

The example gets the price information for the MSFT stocks over the last four days. The numbers can be used to estimate the range of the prices (for setting a reasonable minimum and maximum in a chart). The preview is also useful for a better understanding of the structure of the data. The second number is always the highest price and the third is the lowest price, so the prices are stored in the OHLC (Open, High, Low, and Close) format.

The simple function above provides all that is needed to start creating various charts to visualize stock prices. The next section introduces a few simpler chart types.

Visualizing Stock Prices

This section looks at how to visualize the price of an individual stock. It uses a basic line chart and then explores one of the two supported charts that are specifically designed for displaying stock prices—a candlestick chart (the other financial chart is stock chart and it can be used in a similar way). Finally, the section looks at how to display a range (the minimum and maximum daily price) and set some visual properties of a chart.

Displaying the Price as a Line

After downloading the FSharpChart library (links are available at the end of the article), the implementation (in FSharpChart.fsx file) can be copied to the directory where the working script is located and loaded using the #load command.

#load "FSharpChart.fsx"
open System.Drawing
open MSDN.FSharp.Charting

Once the namespace Samples.Charting is opened, the supported chart types can be explored using the FSharpChart type. One of the basic charts is the Line chart:

> [ for o,h,l,c in getStockPrices "MSFT" 3000 do
        yield (o + c) / 2.0 ] |> FSharpChart.Line;;
val it : ChartTypes.LineChart = (Chart)

To display a line chart, the script needs to generate an in-memory collection with data such as list or array (as already noted the sequence needs to support the Reset method). The snippet above uses a list comprehension to create an F# list. The elements of the list are calculated as the average between the opening and closing price of the stock. The list is then sent to a function that creates a chart using the pipelining operator (|>). The sample creates a simple line chart using FSharpChart.Line. Once the code is entered to F# Interactive, a window with a chart like the one in Figure 1 should appear.

To make the chart appear automatically, only the lines that create the chart (from the previous snippet) should be selected and sent to F# Interactive. The library registers a handler that automatically creates a form with the chart when a chart value is printed to the output. The F# Interactive prints just (Chart), but it automatically opens a new form at the same time.

Figure 1. A line chart displaying the price of MSFT stocks

Referenced Image

The code snippet to create a chart in this section was very simple, but it demonstrated how the FSharpChart library works. The easiest way to create a chart is to generate a list of values and then use pipelining. The next section uses more advanced types of charts and also sets a few chart properties.

Displaying the Price Using Candlesticks

The two financial charts supported by Chart Controls are FSharpChart.Candlestick and FSharpChart.Stock. They are both designed to visualize stock prices and take four different values (Open, High, Low and Close prices). There are two ways to provide four different Y values when creating the chart in F#. The functions either take four distinct sequences of numbers or a single sequence of tuples. The following snippet uses the second option:

[ for o,h,l,c in getStockPrices "MSFT" 60 do
    yield h, l, o, c ]
|> FSharpChart.Candlestick
|> FSharpChart.WithArea.AxisY(Minimum = 25.0, Maximum = 30.0)

The candlestick chart expects the prices in the HLOC format. The snippet uses a simple sequence expression that rearranges elements of the tuple. Then, it calls the function to create a chart as in the previous snippet. Finally, it also sets the range of the Y-axis, resulting in the chart shown in Figure 2. The maximum and minimum values are based on the data displayed in the previous F# Interactive output. At the time of this writing, 25.00 to 30.00 was appropriate, but note that the chart displays data over the last two months, so the limits may need to be changed.

The range specification is interesting. It is done by appending another call to the pipeline. It takes a chart and modifies it by setting some additional properties. Various options that can be configured can be explored by typing FSharpChart.With… and then looking at the available members. The WithArea property groups configuration related to the chart area such as background style (see Style member) and axes. The AxisY method has several named parameters for configuring axis properties. Note that the parameters are shown in the parameter info tool tip in the IntelliSense.

Figure 2. A candlestick chart with explicitly specified range for the Y-axis

Referenced Image

The FSharpChart library knows which charts require which number of Y values, so it isn't possible to accidentally provide fewer than four values when creating the candlestick chart. The next section uses a range chart that takes two values.

Displaying the Price Range

This example also works with the prices of MSFT stocks over the last 60 days. It creates a chart showing the range between the minimum and maximum prices per day. This can be done using the FSharpChart.Range, which takes a sequence of two-element tuples. Alternatively, there is also an overload that takes two separate sequences. As in the previous snippet, the next example sets the range of the Y-axis, but it also configures the look of the data series:

[ for o, h, l, c in getStockPrices "MSFT" 60 -> l, h ]
|> FSharpChart.Range
|> FSharpChart.WithArea.AxisY(Minimum = 25.0, Maximum = 30.0)
|> FSharpChart.WithSeries.Style
     ( Color = Color.FromArgb(32, 135, 206, 250), 
       BorderColor = Color.SkyBlue, BorderWidth = 1)

The first part of the snippet is almost the same as in the previous examples. The only difference is that it uses a more compact version of sequence expression that uses the [ for .. in .. -> .. ] syntax. This can be used if the expression simply calculates a single new value for each element from the source. It is not possible to use, for example, if ... then to implement filtering.

The rest of the snippet configures the look of the chart. The setting of the y-axis range is the same as before. The last line uses WithSeries to specify the style of the data series (the shape that represents the data). A range chart can have different border and fill colors to get a partially transparent area with solid borders. The result is shown in Figure 3.

Figure 3. A range chart showing the minimum and maximum daily price

Referenced Image

The charts that were created so far displayed only a single data series (all examples used MSFT stock price). How to create a chart that compares prices of several different stocks? The upcoming section looks at various ways of showing multiple stocks in a single chart.

Combining Multiple Charts

There are two ways of showing multiple data sets on a single form. If the data sets have a similar range of Y values, they can be shown in a single chart area. This way, you get a single chart showing different values (using different colors). If the ranges differ, it is possible to create a chart with multiple y-axes. Another interesting option that works for data that are not directly related is to create a single form that contains separate chart areas (each with one or more datasets).

The next section starts with the first option. It demonstrates how to display multiple stocks with similar price ranges in a single chart area.

Displaying Multiple Data Series

Multiple data series can be combined in a single chart using the FSharpChart.Combine method. The method takes a sequence containing other charts (for example created using FSharpChart.Line) and creates a single chart that displays all of them in a single chart area. The following snippet first declares a function createPriceLine that creates a line chart for a given stock and sets the color of the line. Then, it combines two such charts into a single one:

let createPriceLine stock color =
  FSharpChart.Line
    ( [ for o,h,l,c in getStockPrices stock 60 -> o ], Name = stock)
  |> FSharpChart.WithSeries.Style(Color = color, BorderWidth = 2)

FSharpChart.Combine
  [ createPriceLine "MSFT" Color.SkyBlue
    createPriceLine "YHOO" Color.Red ]
|> FSharpChart.WithArea.AxisY(Minimum = 15.0, Maximum = 30.0)

The snippet uses a compact sequence expression to get the opening price of the stock. When creating the chart, it also specifies a value for the Name property, which makes it possible to add labels to the chart (adding a legend is discussed in the next section). The color of a line chart is specified using the WithSeries property in the same way as in the earlier examples.

After creating the function, the snippet constructs a list containing multiple line charts and combines them into a single chart using FSharpChart.Combine. Note that the sample did not use pipelining to call the Combine method in this example. This is intentional because F#'s type inference can infer the type of the list containing individual charts only if it follows the Combine call.

The style of the combined chart can be configured using the same operations as before. The snippet above uses just WithArea to specify the range of the y-axis. The resulting chart is shown in Figure 4.

Figure 4. Showing MSFT and YHOO stock prices in a single chart

Referenced Image

The chart looks readable because the color of MSFT stocks is set to blue and YHOO stocks are shown in red. Nevertheless, when combining multiple series in a single chart, it is always a good idea to add a legend to make the chart more readable.

Adding a Legend and Visual Style

This section makes the chart look a little nicer. It adds a legend to the top of the chart and also changes the default black grid lines in the background. In some situations, the FSharpChart library uses types from the underlying Chart Controls library directly. Specifying the grid style is one of them. To do that, it is necessary to create an instance of the Grid object from the System.Windows.Forms.DataVisualization.Charting namespace. The object has several properties that can be set during initialization:

open System.Windows.Forms.DataVisualization.Charting

let dashGrid = 
    Grid( LineColor = Color.Gainsboro, 
          LineDashStyle = ChartDashStyle.Dash )

FSharpChart.Combine
  [ createPriceLine "MSFT" Color.SkyBlue
    createPriceLine "YHOO" Color.Red ]
|> FSharpChart.WithArea.AxisY
    ( Minimum = 15.0, Maximum = 30.0, MajorGrid = dashGrid ) 
|> FSharpChart.WithArea.AxisX(MajorGrid = dashGrid)
|> FSharpChart.WithMargin(0.0f, 10.0f, 2.0f, 0.0f)
|> FSharpChart.WithLegend
    ( InsideArea = false, Font = new Font("Arial", 8.0f), 
      Alignment = StringAlignment.Center, Docking = Docking.Top)

When creating the Grid object, F# doesn't show a tool tip with the parameter info. However, the syntax sets the ordinary mutable properties (LineColor and LineDashStyle) of the created Chart instance. The easiest way to get a completion hint and browse the properties is to type (Grid()) followed by a dot at the end.

The chart is created in a similar way as in the previous section. It adds the MajorGrid parameter when setting properties of AxisY and it adds one more line using WithArea to set the grid of AxisX too. After setting these properties, the snippet specifies the margins of the chart area to be 10% from the top and 2% from the left of the form to get some free space for the legend and axes. This is done using the WithMargin member. Finally, the snippet adds a legend using the WithLegend method. The arguments specify the location of the legend (center top), the fact that it is located outside of the chart area, as well as the font. The resulting chart is shown in Figure 5.

Figure 5 A chart with a legend and gray dotted grid lines

Referenced Image

The chart looks reasonable because the prices of the stocks are quite close. When combining stocks with very different price ranges (or when presenting different views of data), it may be more appropriate to show them using separate charts.

Displaying Multiple Charts

The previous section combined multiple charts in a single area using FSharpChart.Combine. Displaying multiple charts on a single form is done in a very similar way. The methods FSharpChart.Rows and FSharpChart.Columns can be used to split the chart horizontally and vertically. It is also possible to nest the two calls (for example, to create a 2 × 2 table) and also to use FSharpChart.Combine to show a multiple series in a single column.

This section shows a relatively simple example. It creates a form that shows the prices of three stocks (MSFT, GOOG, and YHOO) in three separate rows. To keep the code simpler, the following helper function creates a single chart, configures its look, and sets the range of the y-axis:

let createPriceChart stock color (min, max) =
  createPriceLine stock color
  |> FSharpChart.WithArea.AxisX(MajorGrid = dashGrid)
  |> FSharpChart.WithArea.AxisY
       ( Minimum = min, Maximum = max, MajorGrid = dashGrid ) 

A more sophisticated example would calculate an appropriate Y-axis range from the data. To keep the example simple, the next snippet contains hard-coded ranges (that may need to be adjusted to match the current stock prices). The following snippet calls the createPriceChart function three times and combines the created charts using FSharpChart.Rows:

FSharpChart.Rows
  [ createPriceChart "MSFT" Color.SkyBlue (26.0, 29.0)
    createPriceChart "GOOG" Color.OliveDrab (560.0, 650.0)
    createPriceChart "YHOO" Color.Red (15.0, 18.0) ]
|> FSharpChart.WithMargin(0.0f, 8.0f, 2.0f, 0.0f)
|> FSharpChart.WithLegend
    ( InsideArea = false, Font = new Font("Arial", 8.0f), 
      Alignment = StringAlignment.Center, Docking = Docking.Top)

The result of combining multiple charts as separate rows again is a new chart. This means that it is possible to use the usual methods for configuring charts to specify the margins of the chart and to add a legend. The F# Charting library is designed to be composable, so the same approach works at any level. Figure 6 shows the result of entering the last snippet in F# Interactive.

Figure 6. Comparing three stock prices using separate chart areas

Referenced Image

As the figure shows, the price of individual stocks is very different. When visualized using three separate rows, it is easy to compare the prices and to analyze the trends. For example, some trends apply to the entire IT industry and some are specific for each company.

Summary

This tutorial discussed how to use the FSharpChart library to analyze and visualize financial data interactively. It used a simple synchronous function to download the prices from the Yahoo Finance portal and then explored various ways to visualize the price. The tutorial also demonstrated how to change the visual properties of the chart (such as the color and the grid style).

The charts that were used include a basic line chart (to display the overall price), a range chart (to display the range between the minimum and maximum prices per day) and a candlestick chart (to display opening, closing, high, and low prices). In order to be able to easily compare the stock prices, the tutorial discussed ways of combining multiple data sources in a single chart. The FSharpChart.Combine function creates a chart showing multiple data series in a single area and FSharpChart.Rows combines separate charts in a single form.

Additional Resources

This tutorial looked at examples of using FSharpChart library to visualize financial data. The following two articles provide more examples and discuss basic features of the library:

An alternative to using the FSharpChart library is to work directly with the underlying Microsoft Chart Controls library. This is mainly useful when creating standalone applications. It is also possible to create charts using Excel, gnuplot, or other third-party libraries. For more information see:

To download the code snippets shown in this article, go to https://code.msdn.microsoft.com/Chapter-6-Visualizing-Data-c68a2296

See Also

This article is based on Real World Functional Programming: With Examples in F# and C#. Book chapters related to the content of this article are:

  • Book Chapter 4: “Exploring F# and .NET libraries by example” demonstrates how to create a simple charting application from scratch. This chapter is useful for learning F# programming when focusing on working with data.

  • Book Chapter 12: “Sequence expressions and alternative workflows” explains how to work with in-memory data sets in F# using sequence expressions and higher-order functions.

  • Book Chapter 13: “Asynchronous and data-driven programming” shows how to use asynchronous workflows to obtain data from the internet, how to convert data to a structured format, and how to chart it using Excel.

The following MSDN documents are related to the topic of this article:

Previous article: Overview: Getting Started with the FSharpChart Library

Next article: Tutorial: Charting with Excel from F#