股票与 ETF
融资融券
股票与 ETF 证券两融的回测写法:任务中的两融设置与融资额度,融资买入、融券卖出、卖券还款、买券还券四个信用下单方法,直接还款与还券,信用快照 get_margin_snapshot 的字段,以及自动强平时策略如何处理。
证券两融与普通现金买卖共用同一个证券账户,股票和 ETF 写法一致。信用条件(是否启用、额度、标的、券源、利率与风险阈值)都在启动回测任务前设置,策略里只读取。
一、在任务中启用两融
这些选项在启动任务时设置,策略用 context.run_info.字段名 读取。它们不是放进 STRATEGY_PARAMS 就会生效的账户参数;在策略里给 context 或 context.run_info 赋值,也不会增加资金、额度或券源。
| 字段 | 默认值 | 含义 |
|---|---|---|
securities_margin_enabled |
False |
开启证券两融 |
margin_credit_limit |
0 |
融资本金总额度,元,股票与 ETF 共用;为 0 时不能新增融资 |
margin_symbols |
[] |
两融标的范围;空列表不代表全市场开放,建议明确列出 |
lending_inventory |
{} |
各证券预设可借数量,如 {"510300.SH": 10000},单位股/份 |
financing_margin_ratio |
0.5 |
融资现金担保比例 |
lending_margin_ratio |
1.0 |
融券现金担保比例 |
financing_rate |
0.08 |
融资年利率,表示 8% |
securities_lending_rate |
0.08 |
融券年费率,表示 8% |
warning_ratio |
1.40 |
预警阈值,表示 140% |
maintenance_ratio |
1.30 |
追保阈值,表示 130% |
liquidation_ratio |
1.10 |
自动强平触发阈值,表示 110% |
margin_auto_liquidation |
True |
是否自动提交强平委托 |
etf_symbols |
[] |
任务准备的 ETF 列表,不是自动买入指令 |
etf_purchase_fee_rate |
0.0005 |
一级申购费率 0.05%;清单提供有效费率时以清单为准 |
etf_redemption_fee_rate |
0.0005 |
一级赎回费率 0.05%;清单提供有效费率时以清单为准 |
strict_account_check |
False |
严格账户核对;不改变策略的下单方法 |
启用两融时,两个担保比例须大于零,风险阈值按 liquidation_ratio ≤ maintenance_ratio ≤ warning_ratio 设置;布尔选项填 True / False,不是字符串。融资和融券费用按经过的自然日计提,跨周末持有也会增加负债。
例如,下面这段证券配置启用两融、授信 10 万元,允许 600000.SH 做两融并提供 1000 股券源:
{
"securities_margin_enabled": true,
"margin_credit_limit": 100000,
"margin_symbols": ["600000.SH"],
"lending_inventory": {"600000.SH": 1000}
}
初始现金另在任务资金设置里填写,并预留现金担保和费用。
自有资金、融资额度、券源是三项不同的资源。例如初始资金 100 万元、融资额度 100 万元,初始现金仍是 100 万元,融资额度不会增加自有资金;股票和 ETF 的融资买入共同占用这一个额度,不是每只证券各有一份。只开启两融而额度为零时,融资买入会被拒绝,普通现金买卖不受影响。当前没有在策略内动态调额的方法。
二、信用下单方法
使用前确认任务已启用两融,并且目标证券具备该方向所需的资格、额度和券源。
financing_buy(
account_id, id_or_ins, quantity, style=None, retry_num=0,
remark=None
)
securities_lending_sell(
account_id, id_or_ins, quantity, style=None, retry_num=0,
remark=None
)
sell_repayment(
account_id, id_or_ins, quantity, style=None, retry_num=0,
remark=None
)
buy_to_cover(
account_id, id_or_ins, quantity, style=None, retry_num=0,
remark=None
)
| 方法 | 用途 | 回报中的 margin_usage |
|---|---|---|
financing_buy |
融资买入,成交后产生融资负债 | FINANCING_BUY |
securities_lending_sell |
融券卖出,需要券源,成交后产生欠券 | SECURITIES_LENDING |
sell_repayment |
卖券还款,受可卖数量限制,卖出收入用于偿还 | SELL_REPAYMENT |
buy_to_cover |
买券还券,受欠券数量限制,成交减少欠券 | BUY_TO_COVER |
四个方法的 quantity 都传正整数(股票为股、ETF 为份),方向由方法本身决定。其余参数与 order_shares 相同;都返回二级订单列表,沿用普通股票的订单、成交和撤单回调(见下单、撤单与回报)。
- 普通
order_shares默认用途为CASH。不要用普通的负数量卖单代替融券,也不要把普通卖出等同于卖券还款。 - 买券还券不等于增加普通持仓;融券卖出所得的受限资金不能当普通买入现金用。
- 有融资负债的标的,通过普通卖出取得的净收入也可能用于偿还该标的负债。操作后重新查询负债,不要按成交金额自行假定已经还清。
额度的口径。 margin_credit_limit 限制的是未偿融资本金,不是融资与融券合并后的负债上限。融券另受每只证券的可借数量限制,不能只凭融资额度判断能否融券。额度充足也不代表一定能下单,还要满足现金担保、费用和标的条件。
三、直接还款与还券
| 方法 | 参数 | 返回 |
|---|---|---|
repay_financing(account_id, amount) |
正的还款金额,元 | 实际偿还金额,元 |
return_securities(account_id, symbol, quantity) |
完整证券代码、正整数可交付股数/份额 | 实际归还数量,整数 |
直接还款先还利息、再还本金,实际还款不超过剩余负债;只有归还本金的部分会恢复融资额度,支付利息不恢复。直接还券需要相应的可交付现券,并支付对应融券费用。资金、可交付证券或可偿还负债不足时,可能抛出 ValueError。
这两种操作属于账户操作,不返回订单列表,也不产生二级成交回报。检查返回值后重新查询信用快照,不要再手工扣减账户资金或负债。
四、信用快照
get_margin_snapshot(account_id) 返回字典;没有信用账本时可能为空。先检查是否为空以及 snapshot.get('enabled'),再读下列键:
| 键 | 类型、单位 | 含义 |
|---|---|---|
enabled |
布尔值 | 两融是否启用 |
financing_balance |
数值,元 | 未偿融资本金 |
financing_interest |
数值,元 | 未偿融资利息 |
lending_interest |
数值,元 | 未偿融券费用 |
used_credit |
数值,元 | 已用融资额度,不是可用额度;不含未成交委托的预占 |
restricted_cash |
数值,元 | 融券卖出受限资金,不是可自由使用的现金 |
collateral |
数值,元 | 融资与融券现金担保合计 |
short_market_value |
数值,元 | 未归还融券的当前市值 |
total_debt |
数值,元 | 融资本金、融资利息、融券费用及融券市值合计 |
financing_by_symbol |
字典 | 证券代码 → 未偿融资本金,元 |
lending |
字典 | 证券代码 → 融券记录,字段见下表 |
risk_state |
字符串 | NORMAL 正常、WARNING 预警、MARGIN_CALL 追保、LIQUIDATION 强平 |
snapshot['lending'].get(symbol) 返回该证券的融券记录,没有时为 None:
| 融券记录键 | 类型、单位 | 含义 |
|---|---|---|
symbol |
字符串 | 完整证券代码 |
quantity |
整数,股/份 | 尚未归还数量 |
avg_price |
数值,元/股或元/份 | 融券卖出加权均价 |
accrued_fee |
数值,元 | 该证券未偿融券费用 |
restricted_cash |
数值,元 | 该证券融券卖出受限资金 |
collateral |
数值,元 | 该证券现金担保 |
last_price |
数值,元/股或元/份 | 最新估值价格 |
可用融资额度怎么算。 口径是「总额度 − 未偿融资本金 − 未成交融资委托的预占」。融资委托受理后预占额度,成交后按实际融资本金占用,撤单或部分成交后的撤余释放未成交部分。快照不返回总额度、预占额度或 available_credit,所以不能直接用总额度减 used_credit 当作准确的可用额度,也不要在策略里另记一套余额去代替实际下单结果。used_credit 也不是当天的融资成交额,归还本金后它会减少。
其他读法。 总融资额度读 context.run_info.margin_credit_limit;当前维持担保比例读 account.maintenance_ratio_value,配置的追保阈值读 context.run_info.maintenance_ratio,两者不能混用。account.securities_lending_balance 是欠券数量汇总、不是金额,按证券查询用 snapshot['lending']。快照不提供 lending_inventory。
下面的函数可以在 after_trading 中调用,只打印真实快照,不改变信用条件:
from panda_backtest.api.api import get_margin_snapshot
def print_credit_status(context):
account_id = context.run_info.stock_account
snapshot = get_margin_snapshot(account_id)
if not snapshot or not snapshot.get("enabled", False):
print("信用业务未启用")
return
account = context.stock_account_dict[account_id]
print("授信", context.run_info.margin_credit_limit,
"已用", snapshot["used_credit"],
"融资本金", snapshot["financing_balance"],
"融资利息", snapshot["financing_interest"],
"欠券", snapshot["lending"],
"维持担保比值", account.maintenance_ratio_value,
"风险状态", snapshot["risk_state"])
五、融资买入片段
这个片段复用完整示例里的 pending_orders、_track_order 和二级回调:在初始化完成这些状态后调用 init_credit_tracking(context),再把需要的买入信号改为 submit_financing_once。不要把同一个信号同时提交成现金买入和融资买入。
from panda_backtest.api.api import *
def init_credit_tracking(context):
snapshot = get_margin_snapshot(context.account)
if not snapshot or not snapshot.get('enabled'):
raise ValueError('当前任务未启用证券两融')
context.credit_attempts = set()
def submit_financing_once(context, symbol, quantity):
if (not isinstance(quantity, int) or isinstance(quantity, bool)
or quantity <= 0):
raise ValueError('融资买入数量必须为正整数')
intent = (str(context.trade_date), symbol, 'FINANCING_BUY')
if intent in context.credit_attempts or context.pending_orders:
return []
snapshot = get_margin_snapshot(context.account)
if not snapshot or not snapshot.get('enabled'):
raise ValueError('当前任务未启用证券两融')
if snapshot.get('risk_state') != 'NORMAL':
print('[提示] 当前信用风险状态不允许本示例新增融资')
return []
context.credit_attempts.add(intent)
orders = financing_buy(
context.account, symbol, quantity,
style=MarketOrderStyle(), remark='策略融资买入',
)
for order in orders or []:
_track_order(context, order)
return orders
def print_credit_status(context):
snapshot = get_margin_snapshot(context.account)
if not snapshot or not snapshot.get('enabled'):
raise ValueError('当前任务未启用证券两融')
print(
f"[信用] {context.trade_date} 账户={context.account} "
f"融资总额度={context.run_info.margin_credit_limit} "
f"已用额度={snapshot['used_credit']} "
f"融资本金={snapshot['financing_balance']} "
f"总负债={snapshot['total_debt']} 状态={snapshot['risk_state']}"
)
本例只在 NORMAL 状态下新增融资,且有待处理二级订单时不继续提交,这是示例自己的交易限制。后续还款、还券按持仓和信用快照中的实际余额安排。
六、自动强平
风险状态从 get_margin_snapshot(account)['risk_state'] 读取。进入 LIQUIDATION 后,停止新增信用仓位,继续记录后续的订单和成交回报。自动强平的订单、成交 force_liquidation 为 True,margin_usage 仍是对应的卖券还款或买券还券。
- 普通未结束订单可能被撤销;已受理的申赎仍需等待原申请的交收结果。
- 强平也要实际提交和撮合:部分成交只偿还完成的部分;停牌、缺少成交量或持仓尚不可用时,未偿债务会继续保留。
- 看到
LIQUIDATION不代表已经平仓,风险状态变化也不代表还款完成,要重新查询快照中的本金、利息和欠券。
下面的观察片段可以在现有成交回调中调用,不要另外定义同名回调:
def print_credit_trade(context, trade):
if trade.account_id != context.account:
return
if trade.force_liquidation:
print(f'[强平成交] {trade.contract_code} 数量={trade.volume} 价格={trade.price}')
snapshot = get_margin_snapshot(context.account)
if snapshot and snapshot.get('enabled'):
print(f'[负债] 本金={snapshot["financing_balance"]} '
f'总负债={snapshot["total_debt"]} 状态={snapshot["risk_state"]}')
融资、融券与两种偿还方式的完整往返策略,见 ETF 申赎与两融示例。
