Last active
December 23, 2015 22:09
-
-
Save dmitry-a-morozov/6701202 to your computer and use it in GitHub Desktop.
Using Single-Case Active Patterns in real application. Parsing CSV files with possible formatting errors and bogus data.
Pay attention to implementation of (|OrderStatusLine|_|), (|AccountPnlLine|_|) and (|PositionPnlLine|_|).
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module Parse = | |
let inline tryParse<'T when ^T : (static member TryParse: string * ^T byref -> bool)> s = | |
let mutable x = Unchecked.defaultof<_> | |
if (^T: (static member TryParse: string * ^T byref -> bool) (s, &x)) then Some x else None | |
let (|Int|_|) = tryParse<int> | |
let (|Int64|_|) = tryParse<int64> | |
let (|Decimal|_|) = tryParse<decimal> | |
let (|DateTime|_|) = tryParse<DateTime> | |
let (|DateTimeExact|_|) (format : string) s = | |
match DateTime.TryParseExact(s, format, null, Globalization.DateTimeStyles.None) with | |
| true, value -> Some value | |
| _ -> None | |
let (|DateTimeOffsetExact|_|) (format : string) s = | |
match DateTimeOffset.TryParseExact(s, format, null, Globalization.DateTimeStyles.None) with | |
| true, value -> Some value | |
| _ -> None | |
[<RequireQualifiedAccess>] | |
module Dict = | |
let inline (|TryFind|_|) key (dict : IDictionary<_, _>) = tryFind key dict | |
let inline (|TryFind2|_|) (key1, key2) (dict : IDictionary<_, _>) = | |
match dict, dict with | |
| TryFind key1 value1, TryFind key2 value2 -> Some(value1, value2) | |
| _ -> None | |
let inline (|All|_|) keys (dict : IDictionary<_, _>) = | |
let rec loop acc = function | |
| [] -> Some acc | |
| head :: tail -> | |
match dict |> tryFind head with | |
| Some value -> loop (value :: acc) tail | |
| None -> None | |
keys |> List.rev |> loop [] | |
module Splunk = | |
[<Literal>] | |
let tagStart = 75 | |
let (|StartWithTag|_|) tag (line : string) = | |
if line.Length > tagStart | |
then | |
let dataWithTagPrefix = line.Substring(tagStart) | |
if dataWithTagPrefix.StartsWith tag | |
then | |
let dataSection = dataWithTagPrefix.Substring(tag.Length) | |
let parsedTail = dict <| seq { | |
for section in dataSection.Split('|') do | |
match section.Split('=') with | |
| [| property; value |] -> yield property, value | |
| _ -> () | |
} | |
Some parsedTail | |
else | |
None | |
else | |
None | |
[<Literal>] | |
let timestampFormat = "yyMMdd-HH:mm:ss.ffffff" | |
open Parse | |
let (|Timestamp|_|) (line : string) = | |
match line.Substring(2, timestampFormat.Length) with | |
| Parse.DateTimeOffsetExact timestampFormat x -> Some x | |
| _ -> None | |
let (|OrderStatusLine|_|) = function | |
| StartWithTag "EXEC-EVENT:" dict & Timestamp timestamp | |
| StartWithTag "REJ-EVENT:" dict & Timestamp timestamp | |
| StartWithTag "ORDER-INIT:" dict & Timestamp timestamp -> | |
match dict with | |
| Dict.All | |
[ "OID"; "ACCOUNT"; "RIC"; "ANC-OID"; "STATE"; "TYPE"; "SIDE"; "QTY"; "PX"; "LAST-QTY"; "LAST-PX"; "CUM-QTY"; "VENUE"; "TXN-TIME" ] | |
[ Int oid; account; ric; Int ancOid; state; typ; side; Int qty; Decimal px; Int lastQty; Decimal lastPx; Int cumQty; venue; DateTimeExact "yyyyMMdd-HH:mm:ss" txnTime ] -> | |
let rejReason = match dict |> Dict.tryFind "REJ-REASON" with |Some r -> r |_ -> "" | |
{ | |
Oid = oid; AncOid = ancOid; Account = account; Ric = ric; State = state; Type = typ; Side = side; Qty = qty; Px = px; LastQty = lastQty; LastPx = lastPx; CumQty = cumQty; Venue = venue; TxnTime = txnTime; Timestamp = timestamp.DateTime; RejReason = rejReason | |
} | |
|> Some | |
| _ -> None | |
| _ -> None | |
type AccountPnlUpdate = | |
| Turnover of decimal | |
| Exposure of decimal | |
| CumPnl of decimal | |
let (|AccountPnlLine|_|) = function | |
| StartWithTag "PNL:" dict & Timestamp timestamp -> | |
dict | |
|> Dict.tryFind "ACCOUNT" | |
|> Option.bind (fun account -> | |
match dict with | |
| Dict.TryFind "TURNOVER" (Decimal value) -> Some(account, Turnover value, timestamp) | |
| Dict.TryFind "EXPOSURE" (Decimal value) -> Some(account, Exposure value, timestamp) | |
| Dict.TryFind "CUM-PNL" (Decimal value) -> Some(account, CumPnl value, timestamp) | |
| _ -> None | |
) | |
| _ -> None | |
let (|PositionPnlLine|_|) = function | |
| StartWithTag "EXEC-EVENT:" | |
(Dict.All | |
["ACCOUNT"; "RIC"; "POS-CUM-PNL"; "POS-EXPOSURE"; "POS-TURNOVER"; "POS-OP-BUY-QTY"; "POS-OP-SELL-QTY"; "POS-TOT-BUY-QTY"; "POS-TOT-SELL-QTY" ] | |
[ account; ric; Decimal cumPnl; Decimal exposure; Decimal turnOver; Int openBuyQty; Int openSellQty; Int totalBuyQty; Int totalSellQty ] | |
) & Timestamp timestamp -> | |
Some { | |
Account = account; Ric = ric; CumPnl = cumPnl; Exposure = exposure; Turnover = turnOver; OpenBuyQty = openBuyQty; | |
OpenSellQty = openSellQty; TotalBuyQty = totalBuyQty; TotalSellQty = totalSellQty; Timestamp = timestamp | |
} | |
| _ -> None | |
//Unit tests | |
module Splunk.Tests | |
open Xunit | |
open Swensen.Unquote.Assertions | |
[<Fact>] | |
let ``Parse position Pnl``() = | |
let input = "I 130604-19:13:11.178192 [ streamer_uat Main.cpp : 139| EXEC-EVENT:OID=6121|EXEC-REQ-ID=60011000000088996|STATE=FILLED|TYPE=LIMIT|SIDE=BUY|TIF=DAY|RIC=BAC.N|QTY=1|PX=14.0000|LAST-QTY=1|LAST-PX=13.3700|CUM-QTY=1|AVG-PX=13.3700|VENUE=XNYS|GWY=MORGAN_STANLEY_GWY|RID=6001|ACCOUNT=ACC-1234-XYZ|TXN-TIME=20130604-19:13:10|EXP-TIME=19700101-00:00:00|CXL-OID=0|ANC-OID=6121|POS-TOT-BUY-QTY=1515|POS-TOT-SELL-QTY=744|POS-AVG-BUY-PX=13.3581|POS-AVG-SELL-PX=13.3479|POS-OP-BUY-QTY=18|POS-OP-SELL-QTY=76|POS-LAST-PNL=-13.37|POS-CUM-PNL=-10306.70|POS-EXPOSURE=10308.27|POS-TURNOVER=33790.00" | |
match input with | |
| Splunk.PositionPnlLine x -> () | |
| _ -> Assert.True(false, "Position was not parsed") | |
[<Fact>] | |
let ``ORDER-INIT``() = | |
let input = "I 130604-19:13:10.926701 [ streamer_uat Main.cpp : 217| ORDER-INIT:OID=6117|STATE=INIT|TYPE=LIMIT|SIDE=BUY|TIF=DAY|RIC=IBM.N|QTY=1|PX=212.0000|LAST-QTY=0|LAST-PX=0.0000|CUM-QTY=0|AVG-PX=0.0000|VENUE=XNYS|GWY=MORGAN_STANLEY_GWY|RID=6001|ACCOUNT=ACC-1234-XYZ|TXN-TIME=20130604-19:13:10|EXP-TIME=19700101-00:00:00|CXL-OID=0|ANC-OID=6117" | |
match input with | |
| Splunk.OrderStatusLine x -> | |
test <@ x.Oid = 6117 @> | |
test <@ x.Ric = "IBM.N" @> | |
test <@ x.Account = "ACC-1234-XYZ" @> | |
| _ -> Assert.True(false, "ORDER-INIT was not parsed") | |
[<Fact>] | |
let ``REJ-EVENT``() = | |
let input = "I 130806-14:46:47.978327 [ streamer Main.cpp : 204| REJ-EVENT:OID=221|EXEC-REQ-ID=50011000000019055|REJ-REASON='gwy: Session level reject, see gwy log for details'|STATE=REJECTED|TYPE=LIMIT|SIDE=SELL|TIF=DAY|RIC=CZ3|QTY=1|PX=458.5000|LAST-QTY=0|LAST-PX=0.0000|CUM-QTY=0|AVG-PX=0.0000|VENUE=XCBT|GWY=DEUTCHE_BANK_GWY|RID=5001|ACCOUNT=SL2|TXN-TIME=20130806-14:46:47|EXP-TIME=19700101-00:00:00|CXL-OID=0|ANC-OID=221|POS-TOT-BUY-QTY=3|POS-TOT-SELL-QTY=1|POS-AVG-BUY-PX=23066.6667|POS-AVG-SELL-PX=22987.5000|POS-OP-BUY-QTY=0|POS-OP-SELL-QTY=0|POS-LAST-PNL=22987.50|POS-CUM-PNL=-46212.50|POS-EXPOSURE=45975.00|POS-TURNOVER=92186.00" | |
match input with | |
| Splunk.OrderStatusLine x -> | |
test <@ x.Oid = 221 @> | |
test <@ x.State = "REJECTED" @> | |
| _ -> Assert.True(false, "REJ-EVENT was not parsed") | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment