股票与 ETF / 下单、撤单与回报

股票与 ETF

下单、撤单与回报

股票与 ETF 二级交易接口:order_shares、order_values、target_stock_group_order、cancel_order 的参数与返回,市价与限价样式,申报数量规则与费用;订单对象、成交对象与订单状态,以及在回报中管理订单和成交去重。

本页讲股票与 ETF 的二级市场买卖:怎么下单、怎么撤单,以及怎么从回报里确认到底成交了多少。ETF 一级申购赎回用另一组接口,见 ETF 申购与赎回

注意

返回订单不等于成交。列表非空也可能是拒单;前置检查没通过时,返回的还可能是空列表或 None。每张订单都要看 statusmessage,实际成交看成交回报。

一、按数量交易

python
order_shares(
    account_id, id_or_ins, quantity, style=None, retry_num=0,
    remark=None, margin_usage="CASH"
)
参数 类型、默认值 用法
account_id 字符串,必填 统一证券账户编号
id_or_ins 字符串,必填 完整股票或 ETF 代码
quantity 整数,必填 正数买入、负数卖出;股票为股、ETF 为份,不传零
style 订单样式,默认 None 不传或 MarketOrderStyle() 为市价;限价见下文
retry_num 整数,默认 0 追单计数参数,普通策略保留默认值;不是「自动重试次数」
remark 字符串,默认 None 自定义备注,会带进订单和成交回报
margin_usage 字符串,默认 "CASH" 普通现金买卖保留默认;两融使用专用方法

可选参数用关键字传入,例如 style=MarketOrderStyle()。返回订单对象列表,可以用 for order in orders or [] 遍历。最终受理数量读 order.quantity,实际成交数量读 trade.volume

二、按金额交易

python
order_values(
    account_id, id_or_ins, amount, style=None, retry_num=0,
    remark=None, margin_usage="CASH"
)

amount本次交易金额,单位元,正数买入、负数卖出;它不是目标持仓市值,也不是目标权重。其余参数和返回与 order_shares 相同。

买入金额用于估算可买数量及费用,受申报数量单位影响,实际成交金额可能小于传入金额,不保证用完;卖出仍受可卖量限制,不会变成融券卖出。交易时间以外调用返回空列表。

三、市价与限价

写法 用法
MarketOrderStyle() 或不传 style 市价,不传价格
LimitOrderStyle(limit_price) 限价;limit_price 为正价格,元/股或元/份

LimitOrderStyle 的完整构造参数是 limit_price, time_condition=TimeConditionType.IOC, gtd_date=None,回测中保留后两个默认值。当前股票下单接口不提供可依赖的跨 Bar 挂单行为,不要靠给 LimitOrderStyle 传有效期来设计挂单策略。价格要符合目标证券的价格档位,不要把 ETF 价格统一截成两位小数。

下面三笔是相互独立的调用示例,按自己的信号选用,不要无条件放进每次行情回调:

python
def buy_stock(context):
    return order_shares(
        context.account, '600000.SH', 100,
        style=MarketOrderStyle(), remark='买入股票',
    )


def buy_etf_with_limit(context, limit_price):
    return order_shares(
        context.account, '510300.SH', 1000,
        style=LimitOrderStyle(limit_price), remark='限价买入ETF',
    )


def buy_etf_by_amount(context):
    return order_values(
        context.account, '159919.SZ', 5000.0,
        style=MarketOrderStyle(), remark='按金额买入ETF',
    )

四、按预算限价买入

下面的辅助函数用于 handle_data,演示沪深普通股票按现金预算限价买入:预算与可用现金取小,按限价加 2% 余量折算股数,再向下取整到 100 股。调用方负责每日去重和订单跟踪。

python
from panda_backtest.api.api import order_shares, LimitOrderStyle


def buy_with_budget(context, symbol, budget, limit_price):
    if not (0 < limit_price < float("inf")):
        raise ValueError("限价必须是有限正数")
    account_id = context.run_info.stock_account
    account = context.stock_account_dict[account_id]
    usable = min(float(budget), max(0.0, account.cash - 10.0))
    quantity = int(usable / (limit_price * 1.02) / 100) * 100
    if quantity < 100:
        return []
    return order_shares(account_id, symbol, quantity,
                        style=LimitOrderStyle(limit_price), remark="预算限价买入")

五、申报数量与费用

数量。 普通沪深股票和本页的 ETF 示例以 100 股/份为数量基数;科创板要结合证券资料里的最小申报量处理。北交所买入通常至少 100 股,个别证券可设置更高的最小申报量,超出部分按 1 股递增,例如最小量为 100 股时可以报 235 股;不足 100 股的卖出须一次卖出全部可卖余额。不要对北交所委托和部分成交一律向下取整到 100 股,也不要把普通 A 股示例中的取整函数直接套到所有证券。

价格。 ETF 价格通常精确到 0.001 元,以订单上的 price_tick 为准。

成交。 限价约束成交价格,但不保证成交。停牌、缺少有效价格、价格超出限制、数量不合法、资金或可卖数量不足,都会让申请无法成交;存在成交量限制时,一笔委托可能只成交一部分。回测用的是历史日线或分钟数据,不是逐笔盘口排队的还原。

费用。 ETF 二级买卖收取成交费用,不计股票卖出印花税;一级申赎另计申购、赎回费用。任务设置的费用和滑点会影响可买数量与收益。二级实际费用读 trade.cost,策略里不要再手工从现金扣费。

先卖后买。 只需按金额调仓时可用 order_values;要严格控制每只 ETF 的份额,就用 order_shares 自己算数量。先卖后买时,要在卖出回报之后重新读取 account.cash,不要把尚未成交的卖出金额提前计入预算。

六、按目标数量调仓

target_stock_group_order(account, symbol_dict, price_type=0)

参数 用法
account 账户编号,不是账户对象
symbol_dict {完整证券代码: 目标持仓数量};目标数量为非负整数股/份
price_type 默认 0,保留默认

返回 None,逐笔委托和成交通过二级回调跟踪。

注意

这个方法按整个账户的目标持仓调仓:字典里没列出的现有持仓也会被卖出,空字典就是对全部股票持仓发出清仓委托(受 T+1、停牌、成交量和价格限制,不保证立即清空)。只管理账户中一部分证券时,改用 order_shares 逐笔下单,不要传不完整的目标字典。

七、撤单

cancel_order(account_id, order_id) 用于二级订单,传订单对象的 order_id(不是 id)。

它不返回撤单是否成功,返回值为 None,需要通过订单回报和 stock_order_cancel 确认。只能撤未结束的部分,已成交的数量不会撤回;不要用它撤 ETF 一级申请。

八、订单对象

order_sharesorder_values 和两融买卖方法返回列表里的元素,以及 on_stock_order_rtnstock_order_cancel 收到的参数,都是订单对象。

属性 类型、单位 含义
order_id 字符串 订单编号
account 字符串 账户编号
order_book_id 字符串 完整证券代码
order_book_name 字符串 证券名称
datetime datetime 委托时点
side 整数 SIDE_BUY=0 买入,SIDE_SELL=1 卖出
price_type 整数 MARKET=1 市价,LIMIT=2 限价
price 数值,元/股或元/份 委托价格或市价参考价格,不代表实际成交价
quantity 整数,股/份 委托数量,取正值;方向看 side
filled_quantity 整数,股/份 本单累计已成交数量
unfilled_quantity 整数,股/份 本单未成交数量;终态后也可能大于零
cur_filled_quantity 整数,股/份 本次成交更新的数量;统计成交优先用成交回报
transaction_cost 数值,元 委托时的预估费用,不作为实际费用累计
status 整数 订单状态,见下表
message 字符串 状态说明或拒绝原因
remark 字符串或 None 下单时传入的备注
stock_type 整数 普通股票 0、ETF 2
settlement 字符串 可卖规则标记,如 T0T1;实际卖出读持仓的 sellable
price_tick 数值,元 该证券最小价格变动单位
asset_source 字符串 二级交易为 SECONDARY
margin_usage 字符串 现金或信用用途,见融资融券
force_liquidation 布尔值 是否为自动强平订单

被前置拒绝的订单,部分属性可能保留空值或零值:先读 statusmessage,不要用它的价格计算收益。

九、订单状态

状态常量 数值 含义 是否结束
0 待报
ACTIVE 1 已受理(已报)、待处理
PartTradedQueueing 4 部分成交,余量仍在队列
NoTradeQueueing 6 尚未成交,仍在队列
FILLED 2 全部成交
CANCELLED 3 已撤销剩余数量
PartTradedNotQueueing 5 部分成交,余量结束
NoTradeNotQueueing 7 未成交,订单结束
REJECTED -1 拒绝
8 / 9 未触发 / 已触发 不据此认定完成

状态 5 既有实际成交、又有未成交余量:既不能当成全部成交,也不能当成完全没成交。撤单回调也可能携带拒单状态,所以要同时读 statusmessage

十、成交对象

on_stock_trade_rtn(context, trade) 收到的是成交对象。股票、ETF 和两融买卖都用这组属性:

属性 类型、单位 含义
trade_id 字符串 成交编号,可用于去重
order_id 字符串 对应订单编号
account_id 字符串 账户编号(注意不是 account
contract_code 字符串 完整证券代码
contract_name 字符串 证券名称
business 整数 0 买入,1 卖出
volume 整数,股/份 本笔实际成交数量
price 数值,元/股或元/份 本笔实际成交价
cost 数值,元 本笔实际成交费用
trade_date 字符串 交易日,YYYYMMDD
gmt_create 字符串 自然日,YYYYMMDD,不是完整时间戳
gmt_create_time 字符串 成交时分秒,HHMMSS
order_remark 字符串或 None 原订单备注
stock_type 整数 普通股票 0、ETF 2
settlement 字符串 对应可卖规则标记
asset_source 字符串 二级交易为 SECONDARY
margin_usage 字符串 本笔交易的现金或信用用途
force_liquidation 布尔值 是否来自自动强平
status 整数 该笔成交对应的订单状态

成交对象还提供兼容属性 order_book_id(同 contract_code)、quantity(同 volume)、side(同 business)、transaction_cost(同 cost)。同一份策略里固定用一套名称。

订单和成交对象的字段不能互换,最容易写错的几处:

内容 订单 order 成交 trade
账户 account account_id
证券代码 order_book_id contract_code
方向 side business
数量 quantity 委托数量、filled_quantity 累计成交 volume 本次成交,按 trade_id 去重后累计
价格 price 委托价,不是成交价 price 本次成交价
费用 transaction_cost 委托阶段预估 cost 本次实际费用

十一、在回报中管理状态

  • 订单(账户, 订单编号) 跟踪;成交(账户, 交易日, 订单编号, 成交编号) 去重。
  • 已记录为终态的订单,不因较早到达的活动回报、或下单函数的返回值而重新加入待处理表。
  • 成交统计只累计 trade.volumetrade.cost,不要同时累计订单上的费用。
  • 下单返回后要继续用资金或证券时,重新读取账户;不要把预计的卖出收入或买入数量提前当作已到账资产。
  • 撤单不会抹掉已经发生的成交;部分成交时只按实际成交数量更新统计。
  • 自动强平同样走这些回调,用 force_liquidation 区分。

如果需要按订单号查询本策略提交过的订单,在初始化时建一张自己的订单表,在订单、撤单回调中更新,以 (account, order_id) 为键。一级订单另存一张表,避免编号撞车:

python
def remember_secondary_order(context, order):
    if order.account == context.account:
        context.orders_by_id[(order.account, order.order_id)] = order


def lookup_secondary_order(context, order_id):
    return context.orders_by_id.get((context.account, order_id))

使用前在 initialize 中创建 context.orders_by_id = {},并在二级订单和撤单回调里调用 remember_secondary_order。这两个是示例自定义函数,不是交易 API。表里保存的是订单的当前对象;要保留每次状态变化,需另行记录当次的属性值。

完整示例中的 pending_orders 是另一种用法:只保存待处理订单,进入终态后就移除。完整写法见股票与 ETF 示例