We now have to parse our stock price data downloaded from Yahoo.
We first create a function parsePrice that takes a row from the Yahoo download and parses it into the fields we case about.
let parsePrice (line: string) =
let tokens = line.Split([|','|])
{ Date = DateTime.Parse(tokens.[0]);
Event = StkPrice ({Open = money (Double.Parse(tokens.[1]));
High = money (Double.Parse(tokens.[2]));
Low = money (Double.Parse(tokens.[3]));
Close = money (Double.Parse(tokens.[4]));
Volume = vol (Double.Parse(tokens.[5]))})}
The function line.Split() takes an array of characters to use as delimiters to split a string. What you need to know here is that an array in F# is instantiated with the syntax:
let array = [| some comma separated list |]
The square brackets and pipes confused me at first. So, the snippet: line.Split([|','|]) means split the line just using a comma as a delimiter. The result, in tokens, is a zero-indexed array. We then extract each item and put it into a variable of record type Price. This variable is what is returned by the function
Recall that this is what out type Price looks like:
type Price = { Open: float<dollars>;
High: float<dollars>;
Low:float<dollars>;
Close:float<dollars>;
Volume: float<volume>}
The next function we need is a function that will iterate through each line of our data and apply our parsePrice function to each line.
let rec loadFromLineReader (reader:StringReader) listOfPriceObs parseLineFunc =
match reader.ReadLine () with
| null -> listOfPriceObs
| line -> loadFromLineReader reader (parseLineFunc line::listOfPriceObs) parseLineFunc
This function loadFromLineReader takes as input parameters:
(1) reader: which is of type StringReader;
(2) listofPriceObs: which is of type List; and
(3) parselineFunc: which is a pointer to a function (we will pass it our function ParsePrice from above)
The second statement:
match reader.ReadLine () with
will read a line from the string reader (note that our data does have carriage return characters) and then match it with
(1) null (meaning we are at the end if the data stream). We then return our list of Price observations; or
(2) line (any string). What does it return?
loadFromLineReader reader (parseLineFunc line::listOfPriceObs) parseLineFunc
Well, it calls itself (a recursive call) passing in
(1) the current state of the reader (after the ReadLine).
(2) the expression (parseLineFunc line::listOfPriceObs). The statement apply our parsing function (parsePrice) to the line and returns a type Price. The operator :: takes the new Price and prepends it to the list listOfPriceObs. In F#, the "cons" :: operator prepends items to a list, returning a new list. Note that when this function is first called, we will pass in an empty list the start things out.
(3) our parsePrice function.
Because this function calls itself, it is a recursive function. In order to prevent F# compiler from throwing up a fur ball, we need the key word 'rec' after out let statement. This will tell the compiler to call this function even though it does exist yet.
So how does this all work?
Let's suppose our Yahoo data looks the this:
2009-05-15,25.09,25.21,24.62,24.88,26496700,24.88
2009-05-14,25.42,25.42,24.90,24.98,30646700,24.98
2009-05-13,25.40,25.74,25.12,25.24,22668500,25.24
We make the first call to loadFromLineReader passing in (1) the whole data set; (2) a blank list; and (3) our Parse price function.
The function first reads the first line:
2009-05-15,25.09,25.21,24.62,24.88,26496700,24.88
into the variable named 'line'. It then uses our parsePrice function to split the line into:
Date: 2009-05-15
Event (stkPrice): 25.09
High: 25.21
Low: 24.62
Close: 24.88
Volume: 26496700
These data are loaded into an implicit variable of type Price. This variable is the added to our blank list.
The function then calls itself, passing the state of the reader (now on line 2), the current list (now with one item), and our parsePrice function.
The result of the second iteration is another Price observation prepended (is this even a word?) to our current list. We would then have two items in the list.
The function would then continue to iterate, adding a new price per call, until it reached the end of the reader data, at which time it would return the complete list of price observations.
We need one other function:
let loadFromLineString text listOfPriceObs parseLineFunc =
let reader = new StringReader(text)
reader.ReadLine ()|> ignore // skip header
loadFromLineReader reader listOfPriceObs parseLineFunc
Whose primary purpose is to strip the header row from the Yahoo response text. It then passes the decapitated data to our loadFromLineReader function. The final part of the program is to call the Async.Run function and print some results.
let results = Async.Run(loadPricesAsync aURL parsePrice)
for aObs in results do
let aPrice aObs =
match aObs.Event with
| StkPrice(p) -> toFloat(p.Close)
| Dividend(d) -> toFloat(d)
| _ -> -999.0
printfn "Here's AT&T: %s %10.5f" (aObs.Date.ToShortDateString()) (aPrice(aObs))
()
The 'results' that are returned is a List of Observations. We create a skinny function that looks at the event property of an observation and matches it based on its type. If the event is of type StkPrice it returns a floating point number in the variable aPrice. We then print the value of aPrice to the console.
Ta Da!