分钟回测中,ETF 份额拆分后卖出,资产价格异常
由bq3m81rk创建,最终由bq3m81rk 被浏览 1 用户
我们在 M.bigtrader.v35 的分钟回测中,发现 ETF 发生份额拆分后,策略层 context.get_position() 读取到的持仓字段疑似没有同步更新。
具体表现是:
1、拆分后的卖出日前,current_qty / cost_price / last_price / market_value 仍然显示拆分前口径;
2、但实际卖出结果又像按拆分后价格成交,导致账户资产异常下降。
3、我们已经用单标的最小复现脚本验证,行情表里的 adjust_factor 是正常跳变的。
请大神帮忙确认:v35 中 ETF 拆分后,策略层可见的持仓对象字段是否存在同步问题,或者是否有官方推荐的正确处理范式。
复现案例可用 159363.SZ,2025-07-18 买入,2025-07-22 卖出前打印 position,再执行卖出。
\
"""BigQuant v35 ETF 拆分后卖出异常最小复现脚本。
用途:
1. 在拆分前买入指定 ETF;
2. 持有跨过拆分生效日;
3. 在拆分后固定时间卖出前,打印策略层看到的持仓字段;
4. 卖出后打印账户现金,便于和预期结果对比。
当前默认案例:
- 159363.SZ
- 2025-07-18 买入
- 2025-07-22 卖出
预期正常现象:
- 拆分后,current_qty 应翻倍;
- cost_price 应减半;
- last_price 应变为拆分后价格;
- market_value 应与拆分前同口径连续。
当前异常现象(历史复现):
- 卖出前 position 里的 qty / cost_price / last_price / market_value 仍是拆分前口径;
- 卖出后账户现金/净值像按拆分后价格成交,导致资产近乎减半。
"""
from bigmodule import M
from bigquant import bigtrader
TARGET_SYMBOL = "159363.SZ"
START_DATE = "2025-07-18"
END_DATE = "2025-07-22"
BUY_TIME = "13:12"
SELL_TIME = "13:10"
BENCHMARK = "510300.SH"
CAPITAL_BASE = 200000
def _time_key(dt_value):
return dt_value.strftime("%H:%M")
def _position_snapshot(position):
if position is None:
return None
result = {}
# 这里只保留和“拆分后仓位是否同步更新”直接相关的字段,
# 方便客服对照看:
# 1. 数量是否翻倍;
# 2. 成本是否减半;
# 3. 最新价是否切到拆分后价格;
# 4. 市值是否保持连续。
for field in (
"current_qty",
"amount",
"avail_qty",
"cost_price",
"cost_basis",
"last_price",
"market_value",
):
value = getattr(position, field, None)
if value is not None:
result[field] = value
return result
def initialize(context):
context.buy_done = False
context.sell_done = False
context.after_sell_logged = False
# 手续费尽量简化,避免把问题混到交易成本里。
context.set_commission(
bigtrader.PerOrder(
buy_cost=0.0001,
sell_cost=0.0001,
min_cost=5,
tax_ratio=0,
)
)
def before_trading_start(context, data):
# 分钟级回测里,先订阅标的分钟线,确保后续能正常下单和读分钟价格。
context.subscribe_bar([TARGET_SYMBOL], "1m")
def handle_data(context, data):
now = data.current_dt
today = now.strftime("%Y-%m-%d")
tkey = _time_key(now)
# 第一步:在拆分前买入并满仓持有。
# 这里不做任何选股、止损、换仓,只保留最小交易动作。
if not context.buy_done and today == START_DATE and tkey >= BUY_TIME:
result = context.order_target_percent(TARGET_SYMBOL, 1.0)
context.buy_done = True
print(
f"PROBE_BUY date={today} time={tkey} symbol={TARGET_SYMBOL} result={result}",
flush=True,
)
return
position = context.get_position(TARGET_SYMBOL)
# 第二步:在拆分后的卖出日前,先打印一次策略层看到的持仓对象。
# 如果引擎处理正确,这里应该已经看到:
# - current_qty 翻倍
# - cost_price 减半
# - last_price 变成拆分后价格
# 这个日志就是拿来和预期做直接对照的。
if not context.sell_done and today == END_DATE and tkey >= SELL_TIME and position is not None:
print(
"PROBE_BEFORE_SELL "
f"date={today} time={tkey} symbol={TARGET_SYMBOL} "
f"cash={getattr(context.portfolio, 'cash', None)} "
f"available_cash={getattr(context.portfolio, 'available_cash', None)} "
f"position={_position_snapshot(position)}",
flush=True,
)
result = context.order_target_percent(TARGET_SYMBOL, 0.0)
context.sell_done = True
print(
f"PROBE_SELL date={today} time={tkey} symbol={TARGET_SYMBOL} result={result}",
flush=True,
)
return
# 第三步:卖出后只打印一次现金结果。
# 正常情况下,卖出后的现金应与拆分前总资产连续;
# 如果这里明显减半,而卖出前 position 又还是拆分前口径,
# 就能说明“策略层持仓字段”和“实际卖出执行结果”不一致。
if context.sell_done is True and not context.after_sell_logged and today == END_DATE and tkey >= "13:12":
print(
"PROBE_AFTER_SELL "
f"date={today} time={tkey} symbol={TARGET_SYMBOL} "
f"cash={getattr(context.portfolio, 'cash', None)} "
f"available_cash={getattr(context.portfolio, 'available_cash', None)}",
flush=True,
)
context.after_sell_logged = True
m5 = M.bigtrader.v35(
# 只放一个标的,避免其他持仓干扰复现结果。
data={"instruments": [TARGET_SYMBOL]},
start_date=START_DATE,
end_date=END_DATE,
initialize=initialize,
before_trading_start=before_trading_start,
handle_data=handle_data,
capital_base=CAPITAL_BASE,
frequency="1m",
product_type="股票",
rebalance_period_type="交易日",
rebalance_period_days="1",
order_price_field_buy="close",
order_price_field_sell="close",
benchmark=BENCHMARK,
volume_limit=0,
plot_charts=True,
)
\