FIX开发问题整理(Python版),2023-07-01
(2023.07.01 Sat @SZ 颖隆大厦)
由JSON生成FIX message
JSON序列作为用户/开发者提供的信息,成为被转化为FIX message的输入。该过程中建议使用Python pydantic model实现数据校验。
基本步骤如下
- 提供就基础信息,生成JSON序列
- 通过pydantic model对JSON序列校验
- 将校验过的JSON序列复制给
quickfix
中对应的字段
首先由用户提供基础信息,生成JSON序列,该案例中 仅列出部分信息
userinfo =
{
"body": {
"deliver_to_location_id": "XX",
"sending_time": "20220122-08:18:07.578",
"cl_ord_id": "TXXX001",
"side": "B",
"symbol": "HKD",
"order_qty": 9981,
"currency": "EUR",
"ord_type": "1",
"transact_time": "20230315-13:36:42.113",
"trade_date": "20220122",
"security_id_source": "A",
"settl_date": "20220826",
"num_days_interest": 31,
"security_type": "CHEQUE",
"product": 4,
"maturity_date": "20220926",
"action_type": "N",
"coupon_day_count": 7,
"text": "this is a test text",
}
}
定义pydantic model,包含FIX中的必要字段。
import datetime
from typing import Union, Optional
from pydantic import BaseModel
class SomeModel(BaseModel):
cl_ord_id: str # 11
currency: str # 15
msg_seq_num: int = 1 # 34
order_qty: Union[float, int] # 38
ord_type: str # 40
sender_comp_id: str = "Placeholder" # by default, set to Placeholder# 49
sending_time: str = datetime.datetime.now().isoformat().replace("-", "").replace("T", "-")[:21] # set to now string by default, 52
side: str # 54
symbol: str # 55
transact_time: str # 60
trade_date: str # 75
deliver_to_location_id: str = "FX" # 145
security_id_source: str # 22
num_days_interest: int # 157, int
security_type: str # 167
product: int # 460
maturity_date: str # 541
action_type: str # 6812
coupon_day_count: int # 1950
text: str = "" # 58
将输入的JSON序列转换为SomeModel
对象。
user_info = SomeModel(**userinfo["body"])
此时查看 user_info
对象,返回如下
>> user_info
SomeModel(cl_ord_id='TXXX001', currency='EUR', msg_seq_num=1, order_qty=9981.0, ord_type='1', sender_comp_id='Placeholder', sending_time='20220122-08:18:07.578', side='B', symbol='HKD', transact_time='20230315-13:36:42.113', trade_date='20220122', deliver_to_location_id='XX', security_id_source='A', num_days_interest=31, security_type='CHEQUE', product=4, maturity_date='20220926', action_type='N', coupon_day_count=7, text='this is a test text')
考虑到quickfix
中的字段命名采用Camel case,仲需要将JSON中 的snake case字段名做变换。
name_mapping = {key: "".join(t.capitalize() for t in key.split("_"))
for key in SomeModel.__fields__.keys()}
接下来将用户输入的信息传递给FIX对象,采用Python中的quickfix
工具包生成FIX message。赋值方式:给quickfix
中的对应字段的对象赋值,并将该对象传递给quickfix.Message
方法。
import quickfix as fix
message = fix.Message()
# header initialisation
header = message.getHeader()
header.setField(fix.BeginString(fix.BeginString_FIX44))
header.setField(fix.MsgType(fix.MsgType_NewOrderSingle)) # 39 = D
for u in userinfo.dict().keys():
if u == "side" or "time" in u.lower():
continue
print(u)
try:
tmpcmd = f"fix.{name_mapping[u]}(userinfo.{u})"
print(tmpcmd)
tmp = eval(tmpcmd)
message.setField(tmp)
except:
print(f"key = {u}")
str(message).replace("\x01", "|")
输出结果如下:
cl_ord_id
fix.ClOrdID(userinfo.cl_ord_id)
currency
fix.Currency(userinfo.currency)
msg_seq_num
fix.MsgSeqNum(userinfo.msg_seq_num)
order_qty
fix.OrderQty(userinfo.order_qty)
ord_type
fix.OrdType(userinfo.ord_type)
sender_comp_id
fix.SenderCompID(userinfo.sender_comp_id)
sending_time
fix.SendingTime(userinfo.sending_time)
-------ERROR------
symbol
fix.Symbol(userinfo.symbol)
-------ERROR------
transact_time
fix.TransactTime(userinfo.transact_time)
-------ERROR------
trade_date
fix.TradeDate(userinfo.trade_date)
deliver_to_location_id
fix.DeliverToLocationID(userinfo.deliver_to_location_id)
security_id_source
fix.SecurityIDSource(userinfo.security_id_source)
num_days_interest
fix.NumDaysInterest(userinfo.num_days_interest)
security_type
fix.SecurityType(userinfo.security_type)
product
fix.Product(userinfo.product)
maturity_date
fix.MaturityDate(userinfo.maturity_date)
action_type
fix.ActionType(userinfo.action_type)
-------ERROR------
coupon_day_count
fix.CouponDayCount(userinfo.coupon_day_count)
-------ERROR------
text
fix.Text(userinfo.text)
'8=FIX.4.4|9=140|35=D|11=TXXX001|15=EUR|22=A|34=1|38=9981|40=1|
49=Placeholder|58=this is a test text|75=20220122|145=XX|
157=31|167=CHEQUE|460=4|541=20220926|10=005|'
(2023.07.02 Sun @SZ 颖隆大厦)
上面的案例只是给出了最简形式的实现 。从运行结果的log中发现有若干error,包括所有和timestamp
有关的对象无法赋值,quickfix
不包含的方法/对象无法赋值和quickfix.Symbol
无法赋值。
下面的代码是对上面代码的细化,针对不同情况使用不同的处理方式。
首先定义不包含在quickfix
中的方法和对象,ActionType
和CouponDayCount
,是Bloomberg FX系统中使用的对象。
class ActionType(fix.StringField):
number = 6812
required = False
messages = []
groups = []
def __init__(self, value=None):
if value is None:
fix.StringField.__init__(self, self.number)
else:
fix.StringField.__init__(self, self.number, value)
class CouponDayCount(fix.IntField):
number = 1950
required = False
messages = []
groups = []
def __init__(self, value=None):
if value is None:
fix.IntField.__init__(self, self.number)
else:
fix.IntField.__init__(self, self.number, value)
for u in userinfo.dict().keys():
# for different objects in FIX, diff methods are used to process data
if name_mapping[u] not in dir(fix):
# user-defined object
user_defined_key = name_mapping[u]
print(f"user defined key: {user_defined_key}")
class_type = eval(f"{user_defined_key}.__base__.__name__")
if class_type.lower().startswith("string"):
tmp = eval(f"{user_defined_key}('{userinfo.dict()[u]}')")
elif class_type.lower().startswith("int"):
tmp = eval(f"{user_defined_key}({userinfo.dict()[u]})")
message.setField(tmp)
continue
tmp_key = eval(f"fix.{name_mapping[u]}")
print(f"type(tmp_key) = {type(tmp_key)}")
if isinstance(tmp_key, str):
# string-type object
print(1)
continue
tmp_type = tmp_key.__base__.__name__
if tmp_type in ("UtcTimeStampField"):
# timestamp object
tmp_key = eval(f"fix.{name_mapping[u]}()")
tmp_value = getattr(userinfo, f"{u}") # eval(f"userinfo.{u}")
tmp_key.setString(tmp_value)
header.setField(tmp_key)
elif tmp_type in ("StringField", "IntField"):
# string/int object
tmpcmd = f"fix.{name_mapping[u]}(userinfo.{u})"
print(tmpcmd)
tmp = eval(tmpcmd)
message.setField(tmp)
elif tmp_type in ("CharField"):
# char, side
if u == "side":
side_mapping = {"B": fix.Side_BUY, "S": fix.Side_SELL}
tmp = eval(f"fix.Side('{side_mapping[userinfo.dict()[u]]}')")
message.setField(tmp)
运行结果
>> str(message).replace("\x01", "|")
'8=FIX.4.4|9=209|35=D|52=20220122-08:18:07.578|60=20230315-13:36:42.113|
11=TXXX001|15=EUR|22=A|34=1|38=9981|40=1|49=Placeholder|54=1|
58=this is a test text|75=20220122|145=XX|157=31|167=CHEQUE|460=4|
541=20220926|1950=7|6812=N|10=042|'
对于quickfix中没有的对象,根据对象类型由quickfix.StringField
等类继承从而实现定义,对于timestamp类型对象,将该对象赋值给header,而对于side
对象,则加入判断并加入引号实现赋值。
在FIX message中加入repeated group
生成FIX message过程中不时需要加入group,比如party/counterparty group, note group(Bloomberg-specific)等。