接收 FA 组和配置
receiveFA() 是 requestFA() 的返回回调。FA 账户组、账户别名和旧 Profile 都会以 XML 字符串返回,程序需要使用 XML 解析器读取,不应靠字符串截取。
def receiveFA(self, faDataType, cxml): ...| 参数 | 类型 | 中文含义 | 说明 |
|---|---|---|---|
faDataType | int | FA 数据类型 | 与 requestFA(faData) 里的请求类型对应。 |
cxml | str | XML 配置文本 | 可能包含组、账户、别名、默认分配方法;应当用 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 版本返回的节点名可能存在差异。解析失败时,先记录根节点和一级子节点名称,再调整解析路径。生产日志应避免记录完整账户清单。
没有回调时怎么判断
Section titled “没有回调时怎么判断”如果没有进入 receiveFA(),先看 error():
FA_CALLBACK_COUNT=0ERROR=reqId=-1;code=321;msg=FA data operations ignored for non FA customers.中文界面的 TWS 可能在错误文本前加上“确认请求时出错”,但关键判断仍然是 errorCode=321 和 non FA customers。这不是网络断开,也不是 receiveFA() 写错,而是账户类型不支持。确认方式是:
| 检查点 | 结果 |
|---|---|
nextValidId() 是否触发 | 触发说明连接初始化成功。 |
requestFA() 是否已发送 | 已发送说明代码路径正确。 |
是否收到 321 non FA customers | 收到说明账户无 FA 权限。 |
| 是否收到 XML | 没有 XML 就不要继续解析或提交替换。 |
| 做法 | 原因 |
|---|---|
| 保存一份原始 XML | 便于排查 TWS 配置差异,也可作为替换前备份。 |
| 用 XML 解析器读取节点 | 节点顺序和换行不稳定,字符串截取容易出错。 |
| 只在受控后台展示账户号 | FA XML 可能包含多个客户账户,普通日志应脱敏。 |
| 把组名、默认方法、账户数量拆成结构化字段 | 构造 faGroup、核对默认方法和做风控检查时更清楚。 |