股票与 ETF / 融资融券

股票与 ETF

融资融券

股票与 ETF 证券两融的回测写法:任务中的两融设置与融资额度,融资买入、融券卖出、卖券还款、买券还券四个信用下单方法,直接还款与还券,信用快照 get_margin_snapshot 的字段,以及自动强平时策略如何处理。

证券两融与普通现金买卖共用同一个证券账户,股票和 ETF 写法一致。信用条件(是否启用、额度、标的、券源、利率与风险阈值)都在启动回测任务前设置,策略里只读取。

一、在任务中启用两融

这些选项在启动任务时设置,策略用 context.run_info.字段名 读取。它们不是放进 STRATEGY_PARAMS 就会生效的账户参数;在策略里给 contextcontext.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 股券源:

json
{
  "securities_margin_enabled": true,
  "margin_credit_limit": 100000,
  "margin_symbols": ["600000.SH"],
  "lending_inventory": {"600000.SH": 1000}
}

初始现金另在任务资金设置里填写,并预留现金担保和费用。

说明

自有资金、融资额度、券源是三项不同的资源。例如初始资金 100 万元、融资额度 100 万元,初始现金仍是 100 万元,融资额度不会增加自有资金;股票和 ETF 的融资买入共同占用这一个额度,不是每只证券各有一份。只开启两融而额度为零时,融资买入会被拒绝,普通现金买卖不受影响。当前没有在策略内动态调额的方法。

二、信用下单方法

使用前确认任务已启用两融,并且目标证券具备该方向所需的资格、额度和券源。

python
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 中调用,只打印真实快照,不改变信用条件:

python
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。不要把同一个信号同时提交成现金买入和融资买入。

python
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_liquidationTruemargin_usage 仍是对应的卖券还款或买券还券。

  • 普通未结束订单可能被撤销;已受理的申赎仍需等待原申请的交收结果。
  • 强平也要实际提交和撮合:部分成交只偿还完成的部分;停牌、缺少成交量或持仓尚不可用时,未偿债务会继续保留。
  • 看到 LIQUIDATION 不代表已经平仓,风险状态变化也不代表还款完成,要重新查询快照中的本金、利息和欠券。

下面的观察片段可以在现有成交回调中调用,不要另外定义同名回调:

python
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 申赎与两融示例