Skip to content

Instantly share code, notes, and snippets.

@dmitry-a-morozov
Last active December 23, 2015 22:09
Show Gist options
  • Save dmitry-a-morozov/6701202 to your computer and use it in GitHub Desktop.
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|_|).
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