跳转到内容

接收 FA 组和配置

receiveFA()requestFA() 的返回回调。FA 账户组、账户别名和旧 Profile 都会以 XML 字符串返回,程序需要使用 XML 解析器读取,不应靠字符串截取。

def receiveFA(self, faDataType, cxml):
...
参数类型中文含义说明
faDataTypeintFA 数据类型requestFA(faData) 里的请求类型对应。
cxmlstrXML 配置文本可能包含组、账户、别名、默认分配方法;应当用 XML 解析器处理。
import xml.etree.ElementTree as ET
class FAApp(EWrapper, EClient):
def __init__(self):
EClient.__init__(self, self)
self.fa_xml_by_type = {}
def receiveFA(self, faDataType, cxml):
# 先保存原始 XML,便于排查和备份。
self.fa_xml_by_type[faDataType] = cxml
# 再交给 XML 解析函数处理。
parse_fa_xml(faDataType, cxml)
def parse_fa_xml(faDataType, cxml):
root = ET.fromstring(cxml)
print("FA 数据类型:", faDataType)
print("XML 根节点:", root.tag)
for group in root.findall(".//Group"):
name = group.findtext("name")
method = group.findtext("defaultMethod")
accounts = [node.text for node in group.findall(".//String") if node.text]
print("组名:", name, "默认方法:", method, "账户数量:", len(accounts))

这里故意只打印账户数量,不打印完整账户号。生产日志也应避免把客户账户列表直接写进普通日志。

requestFA(3) 请求账户别名。别名常用于界面展示,让操作员看到可读名称,而不是只看账户编号。

def parse_alias_xml(cxml):
root = ET.fromstring(cxml)
aliases = {}
for alias in root.findall(".//AccountAlias"):
account = alias.findtext("account")
name = alias.findtext("alias")
if account and name:
aliases[account] = name
return aliases

不同 TWS 版本返回的节点名可能存在差异。解析失败时,先记录根节点和一级子节点名称,再调整解析路径。生产日志应避免记录完整账户清单。

如果没有进入 receiveFA(),先看 error()

FA_CALLBACK_COUNT=0
ERROR=reqId=-1;code=321;msg=FA data operations ignored for non FA customers.

中文界面的 TWS 可能在错误文本前加上“确认请求时出错”,但关键判断仍然是 errorCode=321non FA customers。这不是网络断开,也不是 receiveFA() 写错,而是账户类型不支持。确认方式是:

检查点结果
nextValidId() 是否触发触发说明连接初始化成功。
requestFA() 是否已发送已发送说明代码路径正确。
是否收到 321 non FA customers收到说明账户无 FA 权限。
是否收到 XML没有 XML 就不要继续解析或提交替换。
做法原因
保存一份原始 XML便于排查 TWS 配置差异,也可作为替换前备份。
用 XML 解析器读取节点节点顺序和换行不稳定,字符串截取容易出错。
只在受控后台展示账户号FA XML 可能包含多个客户账户,普通日志应脱敏。
把组名、默认方法、账户数量拆成结构化字段构造 faGroup、核对默认方法和做风控检查时更清楚。