Every day, the European Central Bank publishes “official” reference FX (foreign exchange) rates where currencies are quoted against the Euro (expressed as EUR/XXX), or 1 EUR = xxx XXX.
The exchange rates of the last thirty sessions are available in xml format which we shall try to parse.
In order to do so, we use F# and active patterns, based on Don Syme’s draft paper.
Note : the ampersand operator ( & ) is used in active patterns to combine two patterns. That is : p1 & p2 returns true is both patterns are matched. They are the equivalent of the ( && ) operator for boolean tests.
/* The basic structure of the xml node we are interested in looks like : <Cube> <Cube time="YYYY-MM-DD"> <Cube currency="..." rate="..." /> <Cube currency="..." rate="..." /> <Cube currency="..." rate="..." /> </Cube> </Cube> */ open System open System.Net open System.Text open System.Xml //FxRate of (currency * official rate) type t = FxRate of string * float let (|Elem|_|) name (inp:XmlNode) = if inp.Name = name then Some inp else None //The root cube has no attributes let (|NoAttr|_|) (inp:XmlNode) = if inp.Attributes.Count = 0 then Some inp else None let (|RootCube|_|) inp = match inp with | (Elem "Cube" (NoAttr c)) -> Some c | _ -> None //But its children have... let (|Attributes|) (inp:XmlNode) = inp.Attributes let (|Attr|) attr (inp: XmlAttributeCollection) = match inp.GetNamedItem(attr) with | null -> failwithf "%s not found" attr | node -> node.Value //We want to convert attributes to the types we expect in programming let (|Float|) (s:string) = float s let (|Timestamp|) (s:string) = DateTime.Parse s let rec (|FxCube|_|) = function | Elem "Cube" (Attributes (Attr "currency" currency & Attr "rate" (Float rate))) -> Some <| FxRate (currency, rate) | _ -> None and (|FxCubes|) (inp:XmlNode) = [ for x in inp.ChildNodes do match x with | FxCube c -> yield c | _ -> () ] let rec (|DateCube|_|) = function | Elem "Cube" (Attributes (Attr "time" (Timestamp dt)) & FxCubes xs) -> Some (dt, xs) | _ -> None and (|DateCubes|) (inp:XmlNode) = [ for x in inp.ChildNodes do match x with DateCube c -> yield c | _ -> () ] let parse inp = match inp with | RootCube (DateCubes elems) -> elems | _ -> failwith "not an interesting cube"
Once we have defined the helpers, we download the xml file from the ECB, and we parse it. We can then whatever we like with the data, such as printing it (a mighty interesting application indeed…)
let url = @"http://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist-90d.xml" let extractCube (root:XmlElement) = let rootCube = ref <| Unchecked.defaultof<XmlNode> for node in root.ChildNodes do match node with | RootCube c -> rootCube := c | _ -> () !rootCube let rootCube = async { use webClient = new WebClient(Encoding=Encoding.UTF8) let uri = Uri(url) let! xml = webClient.AsyncDownloadString(uri) let doc = new XmlDocument() doc.LoadXml(xml) return extractCube doc.DocumentElement } |> Async.RunSynchronously let ecbFxRates = parse rootCube for (dt, fxRates) in ecbFxRates do printfn "On date %A :" dt for FxRate(currency, rate) in fxRates do printfn " EUR/%s @ %0.4f" currency rate