问题描述
解决方案
在PTrade平台中,二级市场买卖ETF基金(像买卖股票一样交易)与买卖股票的操作非常相似,主要使用的API接口也是通用的(如 order 系列函数)。
但是,在价格精度、费率设置以及交易规则(如T+0)上存在一些关键区别。
以下是详细的对比说明及代码示例:
1. 主要区别
| 特性 | 股票 (Stock) | ETF基金 | PTrade注意事项 |
|---|---|---|---|
| 交易接口 | order, order_target 等 |
order, order_target 等 |
接口通用,无需更换。 |
| 价格精度 | 2位小数 (0.01) | 3位小数 (0.001) | 关键点:下单指定 limit_price 时,ETF需保留3位小数,否则可能废单。 |
| 交易费率 | 佣金 + 印花税 | 佣金 (通常无印花税) | 回测时需通过 set_commission(type='ETF') 单独设置。 |
| 最小单位 | 100股 (1手) | 100份 (1手) | 相同,amount 参数需为 100 的整数倍。 |
| T+0制度 | 否 (T+1) | 部分支持 (如跨境ETF、债券ETF、货币ETF等) | 策略逻辑需注意回转交易规则。 |
| 申购赎回 | 无 (仅IPO) | 支持一级市场申购赎回 | 申赎需使用 etf_purchase_redemption 专用接口。 |
2. 策略代码示例
以下是一个完整的PTrade策略示例,展示了如何设置ETF费率以及如何处理ETF特有的3位小数价格精度。
def initialize(context):
# 设置ETF代码,例如:沪深300ETF (510300.SS)
g.security = '510300.SS'
set_universe(g.security)
# 【区别1】设置手续费
# 股票通常有印花税,ETF通常没有印花税且佣金较低
# type='ETF' 指定设置ETF的费率
set_commission(commission_ratio=0.0001, min_commission=5.0, type='ETF')
# 如果同时也交易股票,需要单独设置股票费率
# set_commission(commission_ratio=0.0003, min_commission=5.0, type='STOCK')
def handle_data(context, data):
security = g.security
# 获取当前收盘价
current_price = data[security]['close']
# 获取持仓信息
position = get_position(security)
# --- 买入逻辑 ---
# 假设策略:价格低于某值时买入
# 这里仅作演示,无实际策略意义
if position.amount == 0:
# 【区别2】价格精度处理
# ETF的价格通常精确到小数点后3位
# 如果使用限价单(limit_price),务必使用 round(price, 3)
buy_price = round(current_price * 1.002, 3) # 溢价2‰挂单
log.info("买入ETF: %s, 价格: %s" % (security, buy_price))
order(security, 1000, limit_price=buy_price)
# --- 卖出逻辑 ---
elif position.amount > 0:
# 卖出时同样注意价格精度
sell_price = round(current_price * 0.998, 3) # 折价2‰挂单
log.info("卖出ETF: %s, 价格: %s" % (security, sell_price))
order_target(security, 0, limit_price=sell_price)
def after_trading_end(context, data):
log.info("今日交易结束")
3. 关键操作细节
A. 价格精度 (Price Precision)
文档明确指出:“可转债、ETF、LOF的价格是小数点三位。”
在编写策略时,如果计算出的价格(例如均线价格)包含多位小数,直接传入 order 函数的 limit_price 参数可能会导致柜台拒单或废单。
建议: 始终对ETF价格使用 round(price, 3) 进行处理。
B. 费率设置 (Commission)
在回测中,如果不设置,系统会使用默认值。为了模拟真实环境,建议在 initialize 中明确区分股票和ETF的费率:
# 设置ETF费率 (无印花税)
set_commission(commission_ratio=0.0001, min_commission=5.0, type='ETF')
# 设置股票费率 (默认包含印花税逻辑)
set_commission(commission_ratio=0.0003, min_commission=5.0, type='STOCK')
C. 获取ETF特有数据
如果您需要查询ETF的IOPV(基金份额参考净值)或成分股信息,可以使用以下专用接口(仅限交易模式或特定研究环境):
get_etf_info(etf_code): 获取ETF的IOPV、现金差额等信息。get_etf_stock_list(etf_code): 获取ETF成分股列表。
D. 一级市场申赎 (Purchase & Redemption)
如果您不是在二级市场买卖,而是想进行ETF套利(一级市场申购/赎回),则不能使用 order 函数,必须使用:
# 申购 900000 份
etf_purchase_redemption('510050.SS', 900000)
# 赎回 900000 份
etf_purchase_redemption('510050.SS', -900000)
注意:申赎通常有较高的资金门槛(通常几十万份起),且仅在交易模式下有效。
Q&A
Q: PTrade回测中支持ETF的T+0交易吗?
A: PTrade回测引擎主要基于日线或分钟线撮合。对于支持T+0的ETF(如跨境ETF、债券ETF),在分钟级别回测中,如果您在同一天内先买入后卖出,系统通常允许这种操作(取决于具体的标的属性配置),但在编写逻辑时需自行控制仓位判断。
Q: 如何查询一个标的是否是ETF?
A: 可以通过 get_instrument_type (如果存在) 或者简单地通过代码后缀和板块判断。在PTrade中,通常通过 get_stock_info 查看名称,或者直接根据代码段(如510、159开头)进行逻辑判断。
Q: ETF可以使用市价单吗?
A: 可以。使用 order_market 接口,用法与股票完全一致。例如:order_market('510300.SS', 1000, market_type=4)(最优五档即时成交剩余撤销)。