Thomas P. Harte ("the Author") is providing this presentation and its contents ("the Content") for educational purposes only at the Open Source Quantitative Finance conference, Chicago, IL (2025-04-12). The Author is not a registered investment advisor and the Author does not purport to offer investment advice nor business advice. The opinions expressed in the Content are solely those of the Author, and do not necessarily represent the opinions of the Author's employer, nor any organization, committee or other group with which the Author is affiliated.
THE AUTHOR SPECIFICALLY DISCLAIMS ANY PERSONAL LIABILITY, LOSS OR RISK INCURRED AS A CONSEQUENCE OF THE USE AND APPLICATION, EITHER DIRECTLY OR INDIRECTLY, OF THE CONTENT. THE AUTHOR SPECIFICALLY DISCLAIMS ANY REPRESENTATION, WHETHER EXPLICIT OR IMPLIED, THAT APPLYING THE CONTENT WILL LEAD TO SIMILAR RESULTS IN A BUSINESS SETTING. THE RESULTS PRESENTED IN THE CONTENT ARE NOT NECESSARILY TYPICAL AND SHOULD NOT DETERMINE EXPECTATIONS OF FINANCIAL OR BUSINESS RESULTS.
Source: https://en.wikipedia.org/wiki/Financial_Information_eXchange
bash$ version=spark-3.5.5-bin-hadoop3-scala2.13
bash$ url=https://dlcdn.apache.org/spark/spark-3.5.5
bash$ curl --compressed $url/$version.tgz > $version && tar xvfpz $version
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 390M 100 390M 0 0 88.5M 0 0:00:04 0:00:04 --:--:-- 89.4M
spark-3.5.5-bin-hadoop3-scala2.13/
spark-3.5.5-bin-hadoop3-scala2.13/jars/
spark-3.5.5-bin-hadoop3-scala2.13/jars/HikariCP-2.5.1.jar
spark-3.5.5-bin-hadoop3-scala2.13/jars/JLargeArrays-1.5.jar
spark-3.5.5-bin-hadoop3-scala2.13/jars/JTransforms-3.1.jar
spark-3.5.5-bin-hadoop3-scala2.13/jars/RoaringBitmap-0.9.45.jar
[snip, snip]
spark-3.5.5-bin-hadoop3-scala2.13/R/lib/SparkR/help/aliases.rds
spark-3.5.5-bin-hadoop3-scala2.13/R/lib/SparkR/html/
spark-3.5.5-bin-hadoop3-scala2.13/R/lib/SparkR/html/00Index.html
spark-3.5.5-bin-hadoop3-scala2.13/R/lib/SparkR/html/R.css
spark-3.5.5-bin-hadoop3-scala2.13/R/lib/SparkR/INDEX
spark-3.5.5-bin-hadoop3-scala2.13/R/lib/sparkr.zip
bash$ $version/bin/spark-shell
Source: FIX Antenna .NET Programmer's Guide
import scala.xml.*
val fixml =
<FIXML>
<Order ID="123456" Side="2" TxnTm="2001-09-11T09:30:47-05:00" Typ="2" Px="93.25" Acct="26522154">
<Hdr Snt="2001-09-11T09:30:47-05:00" PosDup="N" PosRsnd="N" SeqNum="521" SID="AFUNDMGR" TID="ABROKER"/>
<Instrmt Sym="IBM" ID="459200101" Src="1"/>
<OrdQty Qty="1000"/>
</Order>
</FIXML>
import scala.xml.*
val fixml =
<FIXML>
<Order ID="123456" Side="2" TxnTm="2001-09-11T09:30:47-05:00" Typ="2" Px="93.25" Acct="26522154">
<Hdr Snt="2001-09-11T09:30:47-05:00" PosDup="N" PosRsnd="N" SeqNum="521" SID="AFUNDMGR" TID="ABROKER"/>
<Instrmt Sym="IBM" ID="459200101" Src="1"/>
<OrdQty Qty="1000"/>
</Order>
<Order ID="456789" Side="1" TxnTm="2001-09-11T09:30:47-05:00" Typ="2" Px="100.25" Acct="15426522">
<Hdr Snt="2001-09-11T09:30:47-05:00" PosDup="N" PosRsnd="N" SeqNum="522" SID="FUNDMGR-1" TID="BROKERAB"/>
<Instrmt Sym="TSLA" ID="459200101" Src="1"/>
<OrdQty Qty="100"/>
</Order>
</FIXML>
(fixml \ "Order").map(_ \@ "TxnTm")
(fixml \ "Order").map(_ \ "Hdr" \@ "Snt")
res4_0: Seq[String] = List("2001-09-11T09:30:47-05:00", "2001-09-11T09:30:47-05:00") res4_1: Seq[String] = List("2001-09-11T09:30:47-05:00", "2001-09-11T09:30:47-05:00")
import scala.xml.*
val fixml =
<FIXML>
<Order ID="123456" Side="2" TxnTm="2001-09-11T09:30:47-05:00" Typ="2" Px="93.25" Acct="26522154">
<Hdr Snt="2001-09-11T09:30:47-05:00" PosDup="N" PosRsnd="N" SeqNum="521" SID="AFUNDMGR" TID="ABROKER"/>
<Instrmt Sym="IBM" ID="459200101" Src="1"/>
<OrdQty Qty="1000"/>
</Order>
<Order ID="456789" Side="1" TxnTm="2001-09-11T09:30:47-05:00" Typ="2" Px="100.25" Acct="15426522">
<Hdr Snt="2001-09-11T09:30:47-05:00" PosDup="N" PosRsnd="N" SeqNum="522" SID="FUNDMGR-1" TID="BROKERAB"/>
<Instrmt Sym="TSLA" ID="459200101" Src="1"/>
<OrdQty Qty="100"/>
</Order>
</FIXML>
val orders = fixml \ "Order"
val hdr = orders.map(_ \ "Hdr")
orders.map(_ \@ "TxnTm")
hdr.map(_ \@ "Snt")
res6_2: Seq[String] = List("2001-09-11T09:30:47-05:00", "2001-09-11T09:30:47-05:00") res6_3: Seq[String] = List("2001-09-11T09:30:47-05:00", "2001-09-11T09:30:47-05:00")
import scala.xml.*
val fixml =
<FIXML>
<Order ID="123456" Side="2" TxnTm="2001-09-11T09:30:47-05:00" Typ="2" Px="93.25" Acct="26522154">
<Hdr Snt="2001-09-11T09:30:47-05:00" PosDup="N" PosRsnd="N" SeqNum="521" SID="AFUNDMGR" TID="ABROKER"/>
<Instrmt Sym="IBM" ID="459200101" Src="1"/>
<OrdQty Qty="1000"/>
</Order>
<Order ID="456789" Side="1" TxnTm="2001-09-11T09:30:47-05:00" Typ="2" Px="100.25" Acct="15426522">
<Hdr Snt="2001-09-11T09:30:47-05:00" PosDup="N" PosRsnd="N" SeqNum="522" SID="FUNDMGR-1" TID="BROKERAB"/>
<Instrmt Sym="TSLA" ID="459200101" Src="1"/>
<OrdQty Qty="100"/>
</Order>
</FIXML>
for {
order <- fixml \ "Order"
hdr <- order \ "Hdr"
} yield (order \@ "TxnTm", hdr \@ "Snt")
res11: Seq[(String, String)] = List( ("2001-09-11T09:30:47-05:00", "2001-09-11T09:30:47-05:00"), ("2001-09-11T09:30:47-05:00", "2001-09-11T09:30:47-05:00") )
Source: FIX Antenna .NET Programmer's Guide
val fix = """
8=FIX.4.2<SOH>9=153<SOH>35=D<SOH>49=BLP<SOH>56=SCHB<SOH>34=1<SOH>50=30737<SOH>97=Y<SOH>
52=20000809-20:20:50<SOH>11=90001008<SOH>1=10030003<SOH>21=2<SOH>55=TESTA<SOH>54=1<SOH>38=4000<SOH>
40=2<SOH>59=0<SOH>44=30<SOH>47=I<SOH>60=20000809-18:20:32<SOH>10=061<SOH>
""".replace("\n", "").replace("<SOH>", "|")
fix: String = "8=FIX.4.2|9=153|35=D|49=BLP|56=SCHB|34=1|50=30737|97=Y|52=20000809-20:20:50|11=90001008|1=10030003|21=2|55=TESTA|54=1|38=4000|40=2|59=0|44=30|47=I|60=20000809-18:20:32|10=061|"
fix: String = "8=FIX.4.2|9=153|35=D|49=BLP|56=SCHB|34=1|50=30737|97=Y|52=20000809-20:20:50|11=90001008|1=10030003|21=2|55=TESTA|54=1|38=4000|40=2|59=0|44=30|47=I|60=20000809-18:20:32|10=061|"
fix.split('|')
fix.split('|').filter(x => x.startsWith("60=") | x.startsWith("52=")).map(_.substring(3))
res25_1: Array[String] = Array(
"8=FIX.4.2",
"9=153",
"35=D",
"49=BLP",
"56=SCHB",
"34=1",
"50=30737",
"97=Y",
"52=20000809-20:20:50",
"11=90001008",
"1=10030003",
"21=2",
"55=TESTA",
"54=1",
"38=4000",
"40=2",
"59=0",
"44=30",
"47=I",
"60=20000809-18:20:32",
"10=061"
)
res25_2: Array[String] = Array("20000809-20:20:50", "20000809-18:20:32")
Source: quickfix/spec/FIX50SP2.xml
Source: quickfix/spec/FIX50SP2.xml
Source: quickfix/spec/FIX50SP2.xml
Source: quickfix/spec/FIX50SP2.xml
Source: quickfix/spec/FIX50SP2.xml
fields
only)
fix: String = "8=FIX.4.2|9=153|35=D|49=BLP|56=SCHB|34=1|50=30737|97=Y|52=20000809-20:20:50|11=90001008|1=10030003|21=2|55=TESTA|54=1|38=4000|40=2|59=0|44=30|47=I|60=20000809-18:20:32|10=061|"
fix.split('|')
fix.split('|').filter(x => x.startsWith("60=") | x.startsWith("52=")).map(_.substring(3))
res25_1: Array[String] = Array(
"8=FIX.4.2",
"9=153",
"35=D",
"49=BLP",
"56=SCHB",
"34=1",
"50=30737",
"97=Y",
"52=20000809-20:20:50",
"11=90001008",
"1=10030003",
"21=2",
"55=TESTA",
"54=1",
"38=4000",
"40=2",
"59=0",
"44=30",
"47=I",
"60=20000809-18:20:32",
"10=061"
)
res25_2: Array[String] = Array("20000809-20:20:50", "20000809-18:20:32")
Map tags to field
definitions
field
definitionsdef get_xml_specs() = {
Seq(
"FIX40.xml",
"FIX41.xml",
"FIX42.xml",
"FIX43.xml",
"FIX44.xml",
"FIX50.xml",
"FIXT11.xml",
"FIX50SP1.xml",
"FIX50SP2.xml",
)
}
def get_xml_filename(name:String="FIX50SP2.xml") = {
assert(get_xml_specs().filter(_ == name).length != 0)
val folder = os.pwd/"fix-specs"
val filename = folder / name
filename
}
def get_xml(name:String="FIX50SP2.xml") = {
XML.loadFile(get_xml_filename(name).toString)
}
get_xml("FIX42.xml")
res36: Elem = <fix type="FIX" major="4" minor="2" servicepack="0"> <header> <field name="BeginString" required="Y"/> <field name="BodyLength" required="Y"/> <field name="MsgType" required="Y"/> <field name="SenderCompID" required="Y"/> <field name="TargetCompID" required="Y"/> <field name="OnBehalfOfCompID" required="N"/> <field name="DeliverToCompID" required="N"/> <field name="SecureDataLen" required="N"/> <field name="SecureData" required="N"/> <field name="MsgSeqNum" required="Y"/> <field name="SenderSubID" required="N"/> <field name="SenderLocationID" required="N"/> <field name="TargetSubID" required="N"/> <field name="TargetLocationID" required="N"/> <field name="OnBehalfOfSubID" required="N"/> <field name="OnBehalfOfLocationID" required="N"/> <field name="DeliverToSubID" required="N"/> <field name="DeliverToLocationID" required="N"/> <field name="PossDupFlag" required="N"/> <field name="PossResend" required="N"/> <field name="SendingTime" required="Y"/> <field name="OrigSendingTime" required="N"/> <field name="XmlDataLen" required="N"/> <field name="XmlData" required="N"/> <field name="MessageEncoding" required="N"/> <field name="LastMsgSeqNumProcessed" required="N"/> <field name="OnBehalfOfSendingTime" required="N"/> ...
val xml = get_xml("FIX42.xml")
val fields = xml \ "fields" \ "field"
fields: NodeSeq = Seq( <field number="1" name="Account" type="STRING"/>, <field number="2" name="AdvId" type="STRING"/>, <field number="3" name="AdvRefID" type="STRING"/>, <field number="4" name="AdvSide" type="CHAR"> <value enum="B" description="BUY"/> <value enum="S" description="SELL"/> <value enum="T" description="TRADE"/> <value enum="X" description="CROSS"/> </field>, <field number="5" name="AdvTransType" type="STRING"> <value enum="C" description="CANCEL"/> <value enum="N" description="NEW"/> <value enum="R" description="REPLACE"/> </field>, <field number="6" name="AvgPx" type="PRICE"/>, <field number="7" name="BeginSeqNo" type="INT"/>, <field number="8" name="BeginString" type="STRING"/>, <field number="9" name="BodyLength" type="INT"/>, <field number="10" name="CheckSum" type="STRING"/>, <field number="11" name="ClOrdID" type="STRING"/>, <field number="12" name="Commission" type="AMT"/>, <field number="13" name="CommType" type="CHAR"> <value enum="1" description="PER_UNIT"/> <value enum="2" description="PERCENT"/> <value enum="3" description="ABSOLUTE"/> </field>, <field number="14" name="CumQty" type="QTY"/>, <field number="15" name="Currency" type="CURRENCY"/>, ...
val fieldTagMap = (() => {
fields.
map(x => (x \@ "number", x \@ "name", x \@ "type")).
map(x => (x._1.toInt -> (x._1.toInt, x._2, x._3))).
toMap
})()
val fieldNameMap = (() => {
fields.
map(x => (x \@ "number", x \@ "name", x \@ "type")).
map(x => (x._2 -> (x._1.toInt, x._2, x._3))).
toMap
})()
Int
) to fieldfieldTagMap(60)
fieldTagMap(52)
res48: (Int, String, String) = (60, "TransactTime", "UTCTIMESTAMP") res49: (Int, String, String) = (52, "SendingTime", "UTCTIMESTAMP")
String
) to fieldfieldNameMap("TransactTime")
fieldNameMap("SendingTime")
res51: (Int, String, String) = (60, "TransactTime", "UTCTIMESTAMP") res52: (Int, String, String) = (52, "SendingTime", "UTCTIMESTAMP")
val field_cache = scala.collection.mutable.Map[String,Field]()
def get_field(number:String) =
field_cache.getOrElseUpdate(number, Field.apply(number, fields))
case class Field(
number:String,
name:String,
fixType:String,
value:Map[String,String]
)
object Field {
def apply(number:String, fields:NodeSeq):Field = {
val field = fields.filter(x => x \@ "number" == number)
val empty_value = Map[String,String]()
val empty_field = Field(number, "[N/A]", "[N/A]", empty_value)
if (field.length == 0) return(empty_field)
val name = field \@ "name"
val fixType = field \@ "type"
val value = (() => {
val value = field \\ "value"
if (value.length==0) {
empty_value
}
else {
value.map(x => (x \@ "enum", x \@ "description")).toMap
}
}:Map[String,String])()
Field(number, name, fixType, value)
}
}
Field
from the cacheget_field("60")
get_field("52")
res72: Field = Field(number = "60", name = "TransactTime", fixType = "UTCTIMESTAMP", value = Map()) res73: Field = Field(number = "52", name = "SendingTime", fixType = "UTCTIMESTAMP", value = Map())
Field
from the cacheget_field("35")
res74: Field = Field( number = "35", name = "MsgType", fixType = "STRING", value = HashMap( "8" -> "EXECUTION_REPORT", "4" -> "SEQUENCE_RESET", "f" -> "SECURITY_STATUS", "F" -> "ORDER_CANCEL_REQUEST", "5" -> "LOGOUT", "i" -> "MASS_QUOTE", "P" -> "ALLOCATION_INSTRUCTION_ACK", "0" -> "HEARTBEAT", "H" -> "ORDER_STATUS_REQUEST", "7" -> "ADVERTISEMENT", "3" -> "REJECT", "k" -> "BID_REQUEST", "D" -> "NEW_ORDER_SINGLE", "E" -> "NEW_ORDER_LIST", "e" -> "SECURITY_STATUS_REQUEST", "X" -> "MARKET_DATA_INCREMENTAL_REFRESH", "9" -> "ORDER_CANCEL_REJECT", "N" -> "LIST_STATUS", "j" -> "BUSINESS_MESSAGE_REJECT", "T" -> "SETTLEMENT_INSTRUCTIONS", "Y" -> "MARKET_DATA_REQUEST_REJECT", "J" -> "ALLOCATION_INSTRUCTION", "A" -> "LOGON", "a" -> "QUOTE_STATUS_REQUEST", ...
def parse(msg:String, sep:Char='|') = {
val tokens = msg.split(sep)
tokens.map((x) => {
val s = x.split('=')
val tag = s(0); val value = s(1)
val field = get_field(tag)
val description = field.value.getOrElse(value, "")
(tag, field.name, value, description)
})
}
fix: String = "8=FIX.4.2|9=153|35=D|49=BLP|56=SCHB|34=1|50=30737|97=Y|52=20000809-20:20:50|11=90001008|1=10030003|21=2|55=TESTA|54=1|38=4000|40=2|59=0|44=30|47=I|60=20000809-18:20:32|10=061|"
parse(fix)
res79: Array[(String, String, String, String)] = Array( ("8", "BeginString", "FIX.4.2", ""), ("9", "BodyLength", "153", ""), ("35", "MsgType", "D", "NEW_ORDER_SINGLE"), ("49", "SenderCompID", "BLP", ""), ("56", "TargetCompID", "SCHB", ""), ("34", "MsgSeqNum", "1", ""), ("50", "SenderSubID", "30737", ""), ("97", "PossResend", "Y", "YES"), ("52", "SendingTime", "20000809-20:20:50", ""), ("11", "ClOrdID", "90001008", ""), ("1", "Account", "10030003", ""), ("21", "HandlInst", "2", "AUTOMATED_EXECUTION_INTERVENTION_OK"), ("55", "Symbol", "TESTA", ""), ("54", "Side", "1", "BUY"), ("38", "OrderQty", "4000", ""), ("40", "OrdType", "2", "LIMIT"), ("59", "TimeInForce", "0", "DAY"), ("44", "Price", "30", ""), ("47", "Rule80A", "I", "INDIVIDUAL_INVESTOR"), ("60", "TransactTime", "20000809-18:20:32", ""), ("10", "CheckSum", "061", "") )
def showAsDF(msg:String, sep:Char='|'): Unit = {
val p = parse(msg, sep)
val cn = Seq("tag", "field name", "value", "description")
println(f"${cn(0)}%6s${cn(1)}%30s${cn(2)}%40s${cn(3)}%50s")
for (el <- p) println(f"${el(0)}%6s${el(1)}%30s${el(2)}%40s${el(3)}%50s")
}
fix: String = "8=FIX.4.2|9=153|35=D|49=BLP|56=SCHB|34=1|50=30737|97=Y|52=20000809-20:20:50|11=90001008|1=10030003|21=2|55=TESTA|54=1|38=4000|40=2|59=0|44=30|47=I|60=20000809-18:20:32|10=061|"
showAsDF(fix)
tag field name value description 8 BeginString FIX.4.2 9 BodyLength 153 35 MsgType D NEW_ORDER_SINGLE 49 SenderCompID BLP 56 TargetCompID SCHB 34 MsgSeqNum 1 50 SenderSubID 30737 97 PossResend Y YES 52 SendingTime 20000809-20:20:50 11 ClOrdID 90001008 1 Account 10030003 21 HandlInst 2 AUTOMATED_EXECUTION_INTERVENTION_OK 55 Symbol TESTA 54 Side 1 BUY 38 OrderQty 4000 40 OrdType 2 LIMIT 59 TimeInForce 0 DAY 44 Price 30 47 Rule80A I INDIVIDUAL_INVESTOR 60 TransactTime 20000809-18:20:32 10 CheckSum 061
import scala.collection.mutable.LinkedHashMap
def toLinkedHashMap(msg:String, sep:Char='|') = {
val o = LinkedHashMap[String,String]()
parse(msg).map(
x => o.addOne(x(1), if x(3).length > 0 then x(3) else x(2))
)
o
}
fix: String = "8=FIX.4.2|9=153|35=D|49=BLP|56=SCHB|34=1|50=30737|97=Y|52=20000809-20:20:50|11=90001008|1=10030003|21=2|55=TESTA|54=1|38=4000|40=2|59=0|44=30|47=I|60=20000809-18:20:32|10=061|"
toLinkedHashMap(fix)
res89: LinkedHashMap[String, String] = LinkedHashMap( "BeginString" -> "FIX.4.2", "BodyLength" -> "153", "MsgType" -> "NEW_ORDER_SINGLE", "SenderCompID" -> "BLP", "TargetCompID" -> "SCHB", "MsgSeqNum" -> "1", "SenderSubID" -> "30737", "PossResend" -> "YES", "SendingTime" -> "20000809-20:20:50", "ClOrdID" -> "90001008", "Account" -> "10030003", "HandlInst" -> "AUTOMATED_EXECUTION_INTERVENTION_OK", "Symbol" -> "TESTA", "Side" -> "BUY", "OrderQty" -> "4000", "OrdType" -> "LIMIT", "TimeInForce" -> "DAY", "Price" -> "30", "Rule80A" -> "INDIVIDUAL_INVESTOR", "TransactTime" -> "20000809-18:20:32", "CheckSum" -> "061" )
String
fix: String = "8=FIX.4.2|9=153|35=D|49=BLP|56=SCHB|34=1|50=30737|97=Y|52=20000809-20:20:50|11=90001008|1=10030003|21=2|55=TESTA|54=1|38=4000|40=2|59=0|44=30|47=I|60=20000809-18:20:32|10=061|"
(fix.split('|') zip toLinkedHashMap(fix)).foreach { (l, r) => println(f"${l}%20s | \"${r(0)}\" -> \"${r(1)}\"") }
8=FIX.4.2 | "BeginString" -> "FIX.4.2"
9=153 | "BodyLength" -> "153"
35=D | "MsgType" -> "NEW_ORDER_SINGLE"
49=BLP | "SenderCompID" -> "BLP"
56=SCHB | "TargetCompID" -> "SCHB"
34=1 | "MsgSeqNum" -> "1"
50=30737 | "SenderSubID" -> "30737"
97=Y | "PossResend" -> "YES"
52=20000809-20:20:50 | "SendingTime" -> "20000809-20:20:50"
11=90001008 | "ClOrdID" -> "90001008"
1=10030003 | "Account" -> "10030003"
21=2 | "HandlInst" -> "AUTOMATED_EXECUTION_INTERVENTION_OK"
55=TESTA | "Symbol" -> "TESTA"
54=1 | "Side" -> "BUY"
38=4000 | "OrderQty" -> "4000"
40=2 | "OrdType" -> "LIMIT"
59=0 | "TimeInForce" -> "DAY"
44=30 | "Price" -> "30"
47=I | "Rule80A" -> "INDIVIDUAL_INVESTOR"
60=20000809-18:20:32 | "TransactTime" -> "20000809-18:20:32"
10=061 | "CheckSum" -> "061"
8=FIX.4.2 | "BeginString" -> "FIX.4.2"
9=153 | "BodyLength" -> "153"
35=D | "MsgType" -> "NEW_ORDER_SINGLE"
49=BLP | "SenderCompID" -> "BLP"
56=SCHB | "TargetCompID" -> "SCHB"
34=1 | "MsgSeqNum" -> "1"
50=30737 | "SenderSubID" -> "30737"
97=Y | "PossResend" -> "YES"
52=20000809-20:20:50 | "SendingTime" -> "20000809-20:20:50"
11=90001008 | "ClOrdID" -> "90001008"
1=10030003 | "Account" -> "10030003"
21=2 | "HandlInst" -> "AUTOMATED_EXECUTION_INTERVENTION_OK"
55=TESTA | "Symbol" -> "TESTA"
54=1 | "Side" -> "BUY"
38=4000 | "OrderQty" -> "4000"
40=2 | "OrdType" -> "LIMIT"
59=0 | "TimeInForce" -> "DAY"
44=30 | "Price" -> "30"
47=I | "Rule80A" -> "INDIVIDUAL_INVESTOR"
60=20000809-18:20:32 | "TransactTime" -> "20000809-18:20:32"
10=061 | "CheckSum" -> "061"
8=FIX.4.2 | "BeginString" -> "FIX.4.2"
35=D | "MsgType" -> "NEW_ORDER_SINGLE"
49=BLP | "SenderCompID" -> "BLP"
56=SCHB | "TargetCompID" -> "SCHB"
34=1 | "MsgSeqNum" -> "1"
52=20000809-20:20:50 | "SendingTime" -> "20000809-20:20:50"
55=TESTA | "Symbol" -> "TESTA"
54=1 | "Side" -> "BUY"
38=4000 | "OrderQty" -> "4000"
44=30 | "Price" -> "30"
60=20000809-18:20:32 | "TransactTime" -> "20000809-18:20:32"
case class D(
BeginString:String, // 8=FIX.4.2 | "BeginString" -> "FIX.4.2"
MsgType:String, // 35=D | "MsgType" -> "NEW_ORDER_SINGLE"
SenderCompID:String, // 49=BLP | "SenderCompID" -> "BLP"
TargetCompID:String, // 56=SCHB | "TargetCompID" -> "SCHB"
MsgSeqNum:Long, // 34=1 | "MsgSeqNum" -> "1"
SendingTime:String, // 52=20000809-20:20:50 | "SendingTime" -> "20000809-20:20:50"
Symbol:String, // 55=TESTA | "Symbol" -> "TESTA"
Side:String, // 54=1 | "Side" -> "BUY"
OrderQty:Long, // 38=4000 | "OrderQty" -> "4000"
Price:Option[Double], // 44=30 | "Price" -> "30"
TransactTime:Option[String], // 60=20000809-18:20:32 | "TransactTime" -> "20000809-18:20:32"
MISSINGFIELD:Option[String] // <<< NEVER PRESENT >>>
)
case class D(
BeginString:String, // 8=FIX.4.2 | "BeginString" -> "FIX.4.2"
MsgType:String, // 35=D | "MsgType" -> "NEW_ORDER_SINGLE"
SenderCompID:String, // 49=BLP | "SenderCompID" -> "BLP"
TargetCompID:String, // 56=SCHB | "TargetCompID" -> "SCHB"
MsgSeqNum:Long, // 34=1 | "MsgSeqNum" -> "1"
SendingTime:String, // 52=20000809-20:20:50 | "SendingTime" -> "20000809-20:20:50"
Symbol:String, // 55=TESTA | "Symbol" -> "TESTA"
Side:String, // 54=1 | "Side" -> "BUY"
OrderQty:Long, // 38=4000 | "OrderQty" -> "4000"
Price:Option[Double], // 44=30 | "Price" -> "30"
TransactTime:Option[String], // 60=20000809-18:20:32 | "TransactTime" -> "20000809-18:20:32"
MISSINGFIELD:Option[String] // <<< NEVER PRESENT >>>
)
object D {
def apply(msg:LinkedHashMap[String,String]):D = {
D(
msg.getOrElse("BeginString", ""),
msg.getOrElse("MsgType", ""),
msg.getOrElse("SenderCompID", ""),
msg.getOrElse("TargetCompID", ""),
msg.getOrElse("MsgSeqNum", "").toLong, // <- absurd hack! avoids map.get returning an Option[Long]
msg.getOrElse("SendingTime", ""),
msg.getOrElse("Symbol", ""),
msg.getOrElse("Side", ""),
msg.getOrElse("OrderQty", "").toLong, // <- absurd hack! avoids map.get returning an Option[Long]
msg.get("Price").map(_.toDouble),
msg.get("TransactTime").map(_.toString),
msg.get("MISSINGFIELD").map(_.toString),
)
}
}
fix: String = "8=FIX.4.2|9=153|35=D|49=BLP|56=SCHB|34=1|50=30737|97=Y|52=20000809-20:20:50|11=90001008|1=10030003|21=2|55=TESTA|54=1|38=4000|40=2|59=0|44=30|47=I|60=20000809-18:20:32|10=061|"
D(toLinkedHashMap(fix))
res15: D = D(
BeginString = "FIX.4.2",
MsgType = "NEW_ORDER_SINGLE",
SenderCompID = "BLP",
TargetCompID = "SCHB",
MsgSeqNum = 1L,
SendingTime = "20000809-20:20:50",
Symbol = "TESTA",
Side = "BUY",
OrderQty = 4000L,
Price = Some(value = 30.0),
TransactTime = Some(value = "20000809-18:20:32"),
MISSINGFIELD = None
)
case class D1(
BeginString:String, // 8=FIX.4.2 | "BeginString" -> "FIX.4.2"
MsgType:String, // 35=D | "MsgType" -> "NEW_ORDER_SINGLE"
SenderCompID:String, // 49=BLP | "SenderCompID" -> "BLP"
TargetCompID:String, // 56=SCHB | "TargetCompID" -> "SCHB"
MsgSeqNum:Long, // 34=1 | "MsgSeqNum" -> "1"
SendingTime:String, // 52=20000809-20:20:50 | "SendingTime" -> "20000809-20:20:50"
Symbol:String, // 55=TESTA | "Symbol" -> "TESTA"
Side:String, // 54=1 | "Side" -> "BUY"
OrderQty:Long, // 38=4000 | "OrderQty" -> "4000"
Price:Option[Double], // 44=30 | "Price" -> "30"
TransactTime:Option[String], // 60=20000809-18:20:32 | "TransactTime" -> "20000809-18:20:32"
MISSINGFIELD:Option[String] // <<< NEVER PRESENT >>>
)
object D1 {
val classFields = classOf[D1].getDeclaredFields.map(_.getName).toList
def apply(msg:LinkedHashMap[String,String]):D1 = {
val seq = classFields.map(field => msg.getOrElse(field, ""))
Decoder.toProduct[D1](seq)
}
}
fix: String = "8=FIX.4.2|9=153|35=D|49=BLP|56=SCHB|34=1|50=30737|97=Y|52=20000809-20:20:50|11=90001008|1=10030003|21=2|55=TESTA|54=1|38=4000|40=2|59=0|44=30|47=I|60=20000809-18:20:32|10=061|"
D1(toLinkedHashMap(fix))
res1: D1 = D1(
BeginString = "FIX.4.2",
MsgType = "NEW_ORDER_SINGLE",
SenderCompID = "BLP",
TargetCompID = "SCHB",
MsgSeqNum = 1L,
SendingTime = "20000809-20:20:50",
Symbol = "TESTA",
Side = "BUY",
OrderQty = 4000L,
Price = Some(value = 30.0),
TransactTime = Some(value = "20000809-18:20:32"),
MISSINGFIELD = None
)
// Source: based on https://github.com/yummydum/scala3Practice/blob/main/src/main/scala/myutils/utils.scala
import scala.deriving.Mirror
object Decoder {
type Row = Seq[String]
trait FieldDecoder[A]:
def decodeField(a: String): A
trait RowDecoder[A <: Tuple]:
def decodeRow(a: Row): A
given FieldDecoder[Int] with
def decodeField(x: String) = x.toInt
given FieldDecoder[Boolean] with
def decodeField(x: String) = x.toBoolean
given FieldDecoder[Long] with
def decodeField(x: String) = x.toLong
given FieldDecoder[Double] with
def decodeField(x: String) = x.toDouble
given fdod:FieldDecoder[Option[Double]] with
def decodeField(x: String) = if x.length > 0 then Some(x.toDouble) else None
given FieldDecoder[String] with
def decodeField(x: String) = x
given fdos:FieldDecoder[Option[String]] with
def decodeField(x: String) = if x.length > 0 then Some(x) else None
given RowDecoder[EmptyTuple] with
def decodeRow(row: Row) = EmptyTuple
// Type parameter :
// H: FieldDecoder, T <: Tuple: RowDecoder
// Given instance for :
// RowDecoder[H *: T]
given [H: FieldDecoder, T <: Tuple: RowDecoder]: RowDecoder[H *: T] with
def decodeRow(row: Row) = {
summon[FieldDecoder[H]].decodeField(row.head) *: summon[RowDecoder[T]]
.decodeRow(row.tail)
}
def toProduct[P](row: Row)(using p: Mirror.ProductOf[P], d: RowDecoder[p.MirroredElemTypes]): P =
p.fromProduct(d.decodeRow(row))
}
229 lines of code
wc -l functions.sc
229 functions.sc
six
- a Scala package I'm writing
S
cala
Fix