FIX开发问题整理(Python版),2023-07-01

2023-06-30  本文已影响0人  Mc杰夫

(2023.07.01 Sat @SZ 颖隆大厦)

由JSON生成FIX message

JSON序列作为用户/开发者提供的信息,成为被转化为FIX message的输入。该过程中建议使用Python pydantic model实现数据校验。

基本步骤如下

首先由用户提供基础信息,生成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中的方法和对象,ActionTypeCouponDayCount,是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)等。

上一篇下一篇

猜你喜欢

热点阅读