逻辑盘管理使用说明 & xs_opend Python API

适用于先胜 OMS 逻辑模拟盘与 xs_opend Python SDK。当前重点版本:xs_opend 1.1.1。交易接口只支持限价单,市价效果请用激进限价模拟。

返回逻辑盘管理

使用说明

建议先点击右上角“下载 AI Agent 文档”。下载后的 Markdown 可以直接喂给 AI agent 阅读。局域网内 OMS 静态页面通常无法被外部 AI agent 访问,下载文档更稳定。

xs_opend 是先胜内部逻辑盘交易 SDK。它封装的是交易接口与逻辑盘账本,不封装富途行情策略接口。

如果策略需要行情、K 线、盘口、实时快照等能力,请继续使用富途官方 OpenD 行情 API。生产上建议:行情由富途 OpenD 提供,交易提交与逻辑账本由 xs_opend 提供,账本状态由常驻 worker 更新。

重要 1.1.1 版本开始,推荐架构是:策略下单,worker 接收富途订单推送并维护账本,OMS 只负责展示与人工操作入口。

一、业务概念

逻辑盘是在一个富途 A 股模拟盘之上拆分出来的内部账本。多个策略可以共用同一个富途模拟账户,但在先胜数据库里分别拥有独立的现金、持仓、订单、成交、盈亏与资金流水。

对象含义说明
真实 OpenD 模拟账户富途侧 A 股模拟盘由 OpenD host、port、可选 acc_id 表示。最终订单提交到这里。
逻辑盘先胜内部子账户每个逻辑盘有唯一 logic_account_id。策略侧必须绑定该 ID 才会进入逻辑账本。
逻辑订单内部订单记录记录 logic_order_idfutu_order_id、状态、成交数量、冻结金额或冻结股数。
逻辑持仓内部持仓记录logic_account_id + code 独立记账。不同逻辑盘买同一只股票不会混账。
账本服务 worker常驻同步进程接收富途订单推送、刷新持仓价格、同步真实盘资产,并做低频兜底同步。

二、新版架构

1. worker 负责账本更新

生产推荐只让 worker 持续维护账本。worker 运行后会做几件事:

worker 处理订单推送时不再额外调用富途订单查询接口。这样可以避免高并发下每个推送都触发远程查询,降低限频风险。

2. 策略程序负责下单和读取本地状态

策略程序通过 xs_place_order() 提交订单,然后通过 xs_get_order_list()xs_get_position_list()xs_get_funds() 读取数据库中的本地账本。

策略程序不应该高频调用 xs_sync() 轮询富途订单。这个接口会触发富途 order_list_query(refresh_cache=True),容易打满富途限频,尤其是多个策略同时运行时。

三、OMS 页面使用说明

1. 线上/测试环境

2. !!!测试库与生产库完全隔离

测试库和生产库不是同一套账本。逻辑盘、订单、持仓、成交、资金流水、worker 状态、收益快照都按数据库隔离,互不共享、互不同步。

  • TEST-... 逻辑盘只应该存在于测试库 xianshengweb_copy_dev。如果用 SDK 默认配置连接生产库,会报 logic account not found
  • LSIM-... 逻辑盘只应该在生产库 xianshengweb_product 运行。不要把生产逻辑盘 ID 拿到测试策略里误跑。
  • SDK 缺省 DSN 指向生产库,所以本机测试逻辑盘时必须显式传入测试库 DSN。
  • worker 也必须连接对应环境的 DSN。测试 worker 连接测试库,生产 worker 连接生产库;否则会出现 worker 在线但 OMS 看不到数据,或者更危险地写错库。
  • OMS 根据 URL 自动选择环境:本机和 192.168.* 是测试库,oms.* 是生产库。页面隐藏环境切换按钮,避免误切库。

排查环境问题时,先确认三件事:OMS 当前 URL、Python SDK 的 dsn、worker 启动命令里的 --dsn 是否指向同一个环境。

3. OpenD 账户

OpenD 账户代表真实富途模拟盘。创建时需要维护账户名称、OpenD host、OpenD port、市场、可选 futu_acc_id。一个 worker 通常只负责同步一个真实盘及其下属逻辑盘。

4. 逻辑盘

逻辑盘是策略运行的最小账本单位。创建时填写名称、初始资金、运行时间 RUN AT、策略注释。策略注释用于复盘,不参与交易逻辑。

5. 账本服务

账本服务页面用于查看 worker 是否在线、最近心跳、订单推送时间、全量同步时间、价格刷新时间、队列深度和处理日志。测试环境下会按浏览器本地用户标识区分不同电脑上的 worker。

四、资金与账本规则

1. 真实盘额度

真实盘已分配 = sum(ACTIVE 逻辑盘 total_deposit)

真实盘可分配 = base_assets
            - sum(所有逻辑盘 total_deposit)
            + sum(DELETED 逻辑盘 released_on_deleted)

逻辑盘运行期间的持仓涨跌不影响真实盘可分配额度。只有删除逻辑盘时,最终释放现金会通过 released_on_deleted 回到真实盘可分配额度。

2. 冻结规则

场景账本动作说明
买入下单冻结现金按限价和保护系数冻结。订单成交、撤单、拒单后释放未使用部分。
卖出下单冻结股票减少 available_qty,增加 frozen_qty。成交或撤单后释放剩余冻结。
全部成交释放订单剩余冻结买入多冻结现金回到可用现金;卖出剩余冻结股数回到可用数量。
撤单/拒单释放未成交部分状态归一为 CANCELLEDREJECTED 后释放。
如果数据库中出现长期停留的 PENDING_SUBMIT 且没有 futu_order_id 的订单,可能会造成可用股数或冻结现金异常。正常路径下 worker 和全量同步会清理终态订单剩余冻结。

3. 持仓字段

字段语义
qty逻辑盘持仓总数量。
available_qty可卖数量,通常等于 qty - frozen_qty。卖单未完成时会降低。
frozen_qty活动卖单冻结数量。
cost_price逻辑账本加权平均成本。
last_priceworker 从富途快照或持仓同步得到的当前价格。
pre_close昨收价,用于计算个股今日涨跌。
market_valueqty * last_price
pl_ratio持仓盈亏,按成本价与现价计算。
day_change_ratio个股今日涨跌,按昨收与现价计算。

五、Python 接入准备

1. 安装 SDK

SDK 以 wheel 包发布。当前建议安装最新 xs_opend-1.1.1-py3-none-any.whl。公司共享目录示例:

python3 -m venv ~/.xs-opend-worker
~/.xs-opend-worker/bin/python -m pip install -U '/Volumes/990PRO_RAID1/pyShares/先胜api/xs_opend-1.1.1-py3-none-any.whl'

不要直接用系统 python3 -m pip install 安装到系统 Python。macOS 新版 Python 可能出现 externally-managed-environment,固定虚拟环境可以避免不同电脑上 python3pip 指向不同解释器。

2. !!!运行环境

场景推荐写法说明
本机直连测试不传 logic_account_id,传 host="127.0.0.1"直接操作当前电脑上的富途模拟盘,不写逻辑盘表。
本机测试逻辑盘传测试库 dsn、测试逻辑盘 ID、host="127.0.0.1"用于本地策略联调,写入测试库。
macmini PyCharm 测试可以传逻辑盘 ID,显式传 host="127.0.0.1"Python 进程在服务器宿主机上,连接宿主机 OpenD。
生产 Docker 策略logic_account_id,通常不传 hostSDK 缺省 host.docker.internal,用于容器访问服务器宿主机 OpenD。

正式策略必须传入 logic_account_id不传逻辑盘 ID 会进入直连模式,绕过先胜逻辑账本,直接操作富途模拟盘。

SDK 缺省 DSN 指向生产库 xianshengweb_product,缺省 host 为 host.docker.internal。测试库请显式传入测试 DSN。

3. 导入方式

from xs_opend import XsOpenDClient, XsRejectError, XsOpenDError

旧源码路径导入如 from xs_OpenD.xs_opend import ... 只适合直接运行源码目录,不适合作为生产策略的标准写法。生产以 wheel 安装后的 from xs_opend import ... 为准。

六、账本服务 worker

1. 启动命令

OMS 的“账本服务”页面可以按当前环境、用户标识、真实盘、同步间隔生成启动命令。命令本质类似:

~/.xs-opend-worker/bin/python -m xs_opend.worker \
  --dsn 'postgresql://xsquant_user:***@192.168.2.1:5432/xianshengweb_product?sslmode=disable&gssencmode=disable&connect_timeout=10&options=-c%20TimeZone%3DAsia%2FShanghai' \
  --env-tag production \
  --worker-id p000-broker-3-worker \
  --client-id p000 \
  --client-label p000 \
  --broker-account-id 3 \
  --full-sync-interval 300 \
  --price-sync-interval 10 \
  --heartbeat-interval 5 \
  --push-queue-maxsize 10000 \
  --snapshot-batch-size 200 \
  --snapshot-batch-sleep 0.35

Docker 部署时请使用 python -m xs_opend.worker。注意不要写成 python -m -m xs_opend.worker

2. 日志事件

事件含义
PUSH_CONNECT已连接 OpenD 订单推送。
ORDER_PUSH收到订单推送。日志展示富途原始状态 raw 和先胜归一状态 normalized
FULL_SYNC_ACCOUNT单个逻辑盘兜底同步完成。
FULL_SYNC全量同步汇总。若没有活动订单,真实盘订单查询次数可以是 0,这是正常的。
PRICE_SYNC持仓快照刷新完成,更新现价、昨收、个股今日涨跌。
BROKER_ASSET_SYNC真实盘当前资产、基准资产、已分配、可分配刷新完成。

3. 限频与缓存

七、Client 初始化

client = XsOpenDClient(
    dsn=None,
    logic_account_id=None,
    host="host.docker.internal",
    port=11111,
    futu_acc_id=None,
    dry_run=False,
    broker=None,
    config_path=None,
    auto_sync=False,
)

1. 参数说明

参数默认值说明
dsnconfig 缺省数据库连接串。测试环境必须显式传测试库 DSN,生产通常使用缺省配置。
logic_account_idNone传入后进入逻辑盘模式;不传则直连富途模拟盘。
hosthost.docker.internalOpenD host。生产 Docker 通常不传,本机测试传 127.0.0.1
port11111OpenD port。
futu_acc_idNone富途交易账户 ID。多数 A 股模拟盘可为空。
dry_runFalse仅测试账本流程,不真实调用富途。
auto_syncFalse是否在每次 API 前主动调用 xs_sync()。新版架构不建议开启,常规同步交给 worker。

2. 运行模式

模式触发条件行为
逻辑盘模式传入 logic_account_id写入先胜逻辑盘订单、持仓、资金表,并提交富途模拟盘订单。
直连模式不传 logic_account_id直接调用富途模拟盘,不写先胜逻辑盘表。

八、Python API 详解

1. xs_place_order(...) 下单

业务含义:提交逻辑盘订单。逻辑盘模式下,SDK 会先做内部校验和冻结,再调用富途 OpenD 下限价单。

order = client.xs_place_order(
    code="SZ.000001",
    qty=300,
    price=11.00,
    trd_side="BUY",
    order_type="LIMIT",
    remark="strategy_alpha_buy_001",
)
参数说明
code股票代码,例如 SH.600000SZ.000001
qty下单数量。A 股通常应为 100 股整数倍,策略侧自行保证。
price限价价格。必填。
trd_sideBUYSELL
order_type当前只支持 LIMIT。传 MARKET 会被拒绝。
remark策略备注。富途订单 remark 会写入 logic_order_id,便于推送回查。

市价效果:不要使用市价单。买入可用接近涨停价的限价单模拟,卖出可用接近跌停价的限价单模拟。这样更安全,也更容易复盘。

返回字段:logic_order_idfutu_order_idstatuscodeqtypricetrd_sidefrozen_cashfrozen_qtycreated_at 等。

异常:现金不足、持仓不足、逻辑盘不存在、非 LIMIT 单等会抛出 XsRejectError

2. xs_cancel_order(order_id) 撤单

逻辑盘模式下传 logic_order_id,直连模式下传富途订单号。

client.xs_cancel_order("XSO-7DF1C6ACF1664095A765")

撤单成功或远端已撤单后,订单状态归一为 CANCELLED,并释放剩余冻结现金或冻结股数。终态订单重复撤单会返回当前状态,不应造成二次释放。

3. xs_get_funds() 查询资金

funds = client.xs_get_funds()
字段含义
cash_balance可用现金。
frozen_cash买单冻结现金。
position_value逻辑持仓市值。
total_assetscash_balance + frozen_cash + position_value
total_deposit累计入金。
realized_pnl已实现盈亏。

逻辑盘模式下该接口读取先胜数据库,不主动访问富途,除非创建 client 时设置了 auto_sync=True

4. xs_get_position_list() 查询持仓

positions = client.xs_get_position_list()
字段含义
code股票代码。
stock_name股票名称。
qty持仓总数量。
available_qty可卖数量,活动卖单会降低这个值。
frozen_qty卖单冻结数量。
cost_price加权平均成本。
last_price现价。
pre_close昨收。
market_value市值。
pl_ratio账户持仓盈亏比例。
day_change_ratio个股今日涨跌。

返回结果会过滤 qty=0frozen_qty=0 的空持仓。

5. xs_get_order_list() 查询订单

orders = client.xs_get_order_list()

这是策略读取订单状态的首选接口。逻辑盘模式下它读取本地数据库,不访问富途。worker 会通过订单推送持续更新数据库。

状态说明
PENDING_SUBMIT本地已创建,正在或等待提交富途。
SUBMITTED富途已接收,未成交或未完全成交。
PART_FILLED部分成交。
FILLED全部成交。
CANCELLED已撤单。
REJECTED下单失败、富途拒绝、异常失败。

6. xs_get_deal_list() 查询成交

deals = client.xs_get_deal_list()

逻辑盘模式下读取先胜数据库中的成交流水。worker 根据富途订单推送中的累计成交数量和成交均价生成增量成交,避免重复入账。

7. xs_sync() 主动同步

result = client.xs_sync()

定位:低频兜底或人工排查接口,不是策略轮询接口。

它会读取本地活动订单。只有存在活动订单时,才按真实盘调用富途 order_list_query(refresh_cache=True) 并同步状态。无活动订单时,只做本地终态订单冻结清理和空持仓清理。

不要在策略里每 0.4 秒或每 1 秒调用 xs_sync() 等订单结果。这会快速触发富途订单查询限频,严重时会让策略重复下单。

8. xs_sync_position_prices_from_broker() 同步持仓现价

client.xs_sync_position_prices_from_broker()

该接口从富途持仓列表读取价格并更新逻辑持仓。新版生产架构中,常规价格刷新由 worker 的 PRICE_SYNC 完成,策略通常不需要主动调用。

九、推荐代码

1. 本机测试逻辑盘

from xs_opend import XsOpenDClient

TEST_DSN = (
    "postgresql://xsquant_user:xianshengQuant2025@192.168.2.1:5432/"
    "xianshengweb_copy_dev?sslmode=disable&gssencmode=disable&connect_timeout=10"
)

with XsOpenDClient(
    dsn=TEST_DSN,
    logic_account_id="TEST-20260609-0017",
    host="127.0.0.1",
    port=11111,
) as client:
    print(client.xs_get_funds())
    print(client.xs_get_position_list())

2. 生产策略下单

from xs_opend import XsOpenDClient, XsRejectError

LOGIC_ID = "LSIM-20260610-0018"

try:
    with XsOpenDClient(logic_account_id=LOGIC_ID) as client:
        order = client.xs_place_order(
            code="SZ.002910",
            qty=500,
            price=9.20,
            trd_side="BUY",
            order_type="LIMIT",
            remark="morning_signal",
        )
        print("下单成功:", order["logic_order_id"], order.get("futu_order_id"))
except XsRejectError as exc:
    print("逻辑盘拒单:", exc)

3. 等待订单结果的正确方式

下单后用本地订单表判断状态,等待 worker 推送更新。不要在循环里调用 xs_sync()

import time
from xs_opend import XsOpenDClient

TERMINAL = {"FILLED", "CANCELLED", "REJECTED"}

with XsOpenDClient(logic_account_id="LSIM-20260610-0018") as client:
    order = client.xs_place_order(
        code="SZ.002910",
        qty=500,
        price=9.20,
        trd_side="BUY",
        order_type="LIMIT",
        remark="wait_local_order_status",
    )
    logic_order_id = order["logic_order_id"]

    for _ in range(120):
        orders = client.xs_get_order_list()
        current = next((o for o in orders if o["logic_order_id"] == logic_order_id), None)
        if current:
            print(current["status"], current.get("dealt_qty"), current.get("dealt_avg_price"))
            if current["status"] in TERMINAL:
                break
        time.sleep(0.5)

4. 反例:高频 xs_sync

# 不推荐:这会不断调用富途 order_list_query,容易限频
while True:
    client.xs_sync()
    time.sleep(0.4)

十、常见问题

1. 不传 logic_account_id 会怎样?

进入直连模式,直接操作富途模拟盘,不写先胜逻辑盘表。正式策略不要这样运行。

2. 为什么 logic account not found

常见原因是环境库不一致。例如策略默认连接生产库,但传入的是测试库里的 TEST-... 逻辑盘。测试逻辑盘请显式传测试 DSN。

3. 为什么 worker 日志里“真实盘订单查询 0 次”?

如果当前没有活动订单,worker 全量同步不会查富途订单。这是为了减少限频消耗,是正常行为。

4. 为什么成交后可用数量比持仓数量少?

通常是存在活动卖单冻结了股票。查看订单列表中是否有 SUBMITTEDPART_FILLED 的卖单。若存在异常长期 PENDING_SUBMIT 且没有富途订单号,需要人工排查或修复。

5. 富途模拟盘手续费怎么处理?

富途模拟盘 API 不提供稳定的订单费用明细。逻辑盘当前主要基于成交价和成交数量维护账本;如需更真实成本,应在先胜内部增加可配置费率模型。

6. 可以用市价单吗?

不可以。xs_place_order() 当前只允许 LIMIT。需要快速成交时,请用激进限价模拟。

7. OMS 会自己更新账本吗?

新版架构中,OMS 主要展示数据库状态。账本更新主要由 worker 完成。OMS 的刷新按钮用于重新读取展示数据,不应该承担核心账本维护职责。