股票与 ETF / 账户与持仓

股票与 ETF

账户与持仓

股票与 ETF 统一证券账户的获取方式,账户资金字段(cash、total_value、两融负债),持仓集合与持仓对象的用法,可卖数量 sellable,以及 T+0 / T+1、分红与份额折算时的处理。

股票、ETF 二级交易、ETF 一级申赎和证券两融,都用同一个证券账户。本页讲怎么拿到这个账户,以及资金和持仓字段的准确含义。

一、获取账户

initialize 中设置 context.account = context.run_info.stock_account,之后通过 context.stock_account_dict 按编号取账户对象:

python
def get_strategy_account(context):
    account = context.stock_account_dict.get(context.account)
    if account is None:
        raise ValueError(f'股票与ETF统一账户不存在: {context.account}')
    return account

context.stock_account_dict.get(account_id) 未找到时返回 None。不要用字典里的第一个账号代替指定账号。

二、账户字段

属性 类型、单位 含义
cash 数值,元 当前可用资金,已扣除冻结部分;也不等于授信额度
frozen_cash 数值,元 当前冻结资金
market_value 数值,元 当前多头证券持仓市值
total_value 数值,元 账户净资产,不是收益金额;信用账户已计入负债影响
transaction_cost 数值,元 累计已计入的费用;启用两融时也包括已计提的利息费用
add_profit 数值,比例 相对初始资金的净资产变化比例,0.1 表示 10%
positions 持仓集合 读取方法见下一节
margin_enabled 布尔值 是否启用证券两融
financing_balance 数值,元 未偿融资本金
securities_lending_balance 整数,股/份 未归还融券数量合计,不是金额
margin_interest 数值,元 当前未偿融资利息与融券费用
maintenance_ratio_value 数值,比例 当前维持担保比例;无负债时可能为正无穷

两个容易算错的地方:

  • 预算直接读 cash,不要再减一次 frozen_cash。股票和 ETF 共享这笔资金,每笔下单前重新读取,并预留费用及价格变化的余量。
  • 组合估值用 total_value。存在在途收付、分红应收或信用负债时,不能用现金与持仓的简单相加代替它。

三、持仓集合

写法 返回
account.positions.get(symbol) 正持仓对象;不存在时为 None,可传第二个参数作默认值
account.positions[symbol] 持仓对象;无该证券持仓时返回数量为 0 的对象
account.positions.keys() 当前数量大于零的证券代码列表
account.positions.items() 可遍历的 (证券代码, 持仓对象)
account.positions.values() 可遍历的持仓对象
symbol in account.positions 是否有该证券的正持仓
len(account.positions) 正持仓证券数量
account.positions.to_dataframe() 表格,列为 symbolpositions;空持仓时可能没有列

可以直接遍历 account.positions 得到证券代码,但不要向集合赋值。持仓对象用属性读取,写 position.quantity,不写 position['quantity']

四、持仓字段

属性 类型、单位 含义
order_book_id 字符串 完整证券代码
quantity 整数,股/份 当前总持仓数量
sellable 整数,股/份 当前可卖数量,已考虑可卖日期和冻结
avg_price 数值,元/股或元/份 持仓成本价,交易费用另记
market_value 数值,元 当前持仓市值
pnl 数值,元 当前持仓浮动盈亏,不含历史已实现收益
value_percent 数值,比例 持仓市值占账户净资产的比例
stock_type 整数 普通股票为 0,ETF 为 2
提示

卖出一律以 sellable 为准,不要用 quantity,也不要自己推算 T+0 / T+1,更不要把当天买入的数量加进 sellable

融券的未偿数量在信用快照里查询(见融资融券),不作为普通的负持仓出现。

下面的函数卖出一只证券的全部可卖数量。它只提交一次卖出,是否全部卖出要看成交回报。调仓时先明确自己的管理范围,避免卖掉需要保留的其他持仓或申赎成分券:

python
def sell_managed_position(context, symbol):
    account = get_strategy_account(context)
    position = account.positions.get(symbol)
    if position is None or position.sellable <= 0:
        return []
    return order_shares(
        context.account, symbol, -int(position.sellable),
        style=MarketOrderStyle(),
    )

五、T+0、T+1、分红与份额调整

可卖规则。 普通国内股票按 T+1 处理。ETF 按具体品种使用 T0T1,不能按「基金」二字统一处理;一级申购到账的份额、二级买入的份额、赎回到账的成分券,可用时点也可能各不相同。卖出时读 sellable;申购交付与赎回资格由对应申请结果确认,sellable 并不代表所有用途的可用数量。

现金分红。 按权益登记日的持仓确定权益,派息到账后体现在现金里;登记后卖出不会撤销已取得的分红权益。策略读取净资产和到账现金即可,不要自己再加一遍收益。

份额拆分或合并。 发生后重新读取 quantitysellableavg_pricemarket_value。按固定目标数量调仓的策略也要重新计算目标,否则会把正常的份额变化误当成买卖信号。数量不是整数、存在相关证券冻结、或涉及未结融券负债的拆分场景,不适用本手册的样例;融券跨分红的场景也不要直接套用普通现券策略。

下面是一个持仓观察片段,可以放在已有的 after_trading 中调用,它不改变账户资产:

python
def print_etf_holding(context, symbol):
    account = get_strategy_account(context)
    position = account.positions.get(symbol)
    print(f'[账户] 现金={account.cash} 净资产={account.total_value}')
    if position is not None:
        print(f'[ETF持仓] {symbol} 数量={position.quantity} 可卖={position.sellable} '
              f'成本={position.avg_price} 市值={position.market_value}')