We show how to create a PriceSeries object by downloading prices from Yahoo! Finance while giving the basis for further data sources developments.
PriceSeries.fs
namespace Trading.Data open System type PriceSeries = { Date : DateTime[] Open : float[] High : float[] Low : float[] Close : float[] Volume : float[] } static member create n = { Date = Array.zeroCreate n Open = Array.zeroCreate n High = Array.zeroCreate n Low = Array.zeroCreate n Close = Array.zeroCreate n Volume = Array.zeroCreate n } [<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>] module PriceSeries = let create n = { Date = Array.zeroCreate n Open = Array.zeroCreate n High = Array.zeroCreate n Low = Array.zeroCreate n Close = Array.zeroCreate n Volume = Array.zeroCreate n } let rec private areDatesInvalidAux dates i found = if found then true elif i >= Array.length dates then found else areDatesInvalidAux dates (i+1) (dates.[i] < dates.[i-1]) let checkDates dates = if Array.length dates > 1 && areDatesInvalidAux dates 1 false then invalidArg "dates" "dates are not sorted" let checkSameLength a b = if Array.length a <> Array.length b then invalidArg "input arrays" "input arrays have different lengths" let checkHighLow high low = checkSameLength high low for i in 0 .. high.Length - 1 do if high.[i] < low.[i] then failwithf "high < low at index %d" i let checkHighLowClose high low c = checkSameLength high low checkSameLength high c for i in 0 .. Array.length high - 1 do if c.[i] > high.[i] then failwithf "open > high at index %d" i if low.[i] > c.[i] then failwithf "low > open at index %d" i let checkOpenHighLowClose o high low c = checkSameLength high low checkSameLength high o checkSameLength high c for i in 0 .. Array.length high - 1 do if o.[i] > high.[i] then failwithf "open > high at index %d" i if low.[i] > o.[i] then failwithf "low > open at index %d" i if c.[i] > high.[i] then failwithf "close > high at index %d" i if low.[i] > c.[i] then failwithf "low > close at index %d" i
DataLoader.fs
For the Excel DataLoader, we use the FileHelpers library by Marcos Meli.
namespace Trading.Data open System open System.Net open Microsoft.FSharp.Control.WebExtensions open FileHelpers open FileHelpers.DataLink open Trading.Data type DataLoader = interface abstract AsyncDownload : string -> DateTime -> DateTime -> Async<PriceSeries> end [<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>] module DataLoader = module Excel = [<DelimitedRecord("|")>] type PriceSample () = [<DefaultValue>] val mutable Date : DateTime [<DefaultValue>] val mutable Open : float [<DefaultValue>] val mutable High : float [<DefaultValue>] val mutable Low : float [<DefaultValue>] val mutable Close : float [<DefaultValue>] val mutable Volume : float let create tickerDir ext startRow startColumn = {new DataLoader with override x.AsyncDownload ticker first last = async { let fullPath = System.IO.Path.Combine(tickerDir, ticker + ext) let provider = new ExcelStorage( typeof<PriceSample>, StartRow=startRow, StartColumn=startColumn, FileName=fullPath ) let res = provider.ExtractRecords() |> unbox<PriceSample[]> |> Array.filter (fun sample -> sample.Date >= first && sample.Date <= last) |> Array.sortBy (fun sample -> sample.Date) let series = PriceSeries.create res.Length res |> Array.iteri (fun i sample -> series.Date.[i] <- sample.Date series.Open.[i] <- sample.Open series.High.[i] <- sample.High series.Low.[i] <- sample.Low series.Close.[i] <- sample.Close series.Volume.[i] <- sample.Volume ) PriceSeries.checkDates series.Date PriceSeries.checkOpenHighLowClose series.Open series.High series.Low series.Close return series } } module Yahoo = let createUrl ticker (first:DateTime) (last:DateTime) = sprintf @"http://ichart.finance.yahoo.com/table.csv?s=%s&a=%d&b=%d&c=%d&d=%d&e=%d&f=%d&g=d&ignore=.csv" ticker (first.Month - 1) first.Day first.Year (last.Month - 1) last.Day last.Year let asyncDownload url = async { let client = new WebClient() return! client.AsyncDownloadString(url) } let create () = {new DataLoader with override x.AsyncDownload ticker first last = async { let url = Uri(createUrl ticker first last) let! data = asyncDownload url let lines = data.Split( [|'\n'|]).[1..] let series = PriceSeries.create lines.Length let firstOk = ref -1 let counter = ref 0 for line in 0 .. lines.Length - 1 do let columns = lines.[line].Split([|','|]) if Array.length columns = 7 then let date = DateTime.Parse columns.[0] if date >= first && date <= last then if !firstOk < 0 then firstOk := line incr counter series.Date.[line] <- date series.Open.[line] <- float columns.[1] series.High.[line] <- float columns.[2] series.Low.[line] <- float columns.[3] //adjusted close //normal close would be the 4th column series.Close.[line] <- float columns.[6] series.Volume.[line] <- float columns.[5] //Delete invalid dates if !counter <> lines.Length then let newSeries = PriceSeries.create !counter Array.blit series.Date !firstOk newSeries.Date 0 !counter Array.blit series.Open !firstOk newSeries.Open 0 !counter Array.blit series.High !firstOk newSeries.High 0 !counter Array.blit series.Low !firstOk newSeries.Low 0 !counter Array.blit series.Close !firstOk newSeries.Close 0 !counter Array.blit series.Volume !firstOk newSeries.Volume 0 !counter return newSeries else return series } }