股票与 ETF
账户与持仓
股票与 ETF 统一证券账户的获取方式,账户资金字段(cash、total_value、两融负债),持仓集合与持仓对象的用法,可卖数量 sellable,以及 T+0 / T+1、分红与份额折算时的处理。
股票、ETF 二级交易、ETF 一级申赎和证券两融,都用同一个证券账户。本页讲怎么拿到这个账户,以及资金和持仓字段的准确含义。
一、获取账户
在 initialize 中设置 context.account = context.run_info.stock_account,之后通过 context.stock_account_dict 按编号取账户对象:
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() |
表格,列为 symbol 和 positions;空持仓时可能没有列 |
可以直接遍历 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。
融券的未偿数量在信用快照里查询(见融资融券),不作为普通的负持仓出现。
下面的函数卖出一只证券的全部可卖数量。它只提交一次卖出,是否全部卖出要看成交回报。调仓时先明确自己的管理范围,避免卖掉需要保留的其他持仓或申赎成分券:
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 按具体品种使用 T0 或 T1,不能按「基金」二字统一处理;一级申购到账的份额、二级买入的份额、赎回到账的成分券,可用时点也可能各不相同。卖出时读 sellable;申购交付与赎回资格由对应申请结果确认,sellable 并不代表所有用途的可用数量。
现金分红。 按权益登记日的持仓确定权益,派息到账后体现在现金里;登记后卖出不会撤销已取得的分红权益。策略读取净资产和到账现金即可,不要自己再加一遍收益。
份额拆分或合并。 发生后重新读取 quantity、sellable、avg_price 和 market_value。按固定目标数量调仓的策略也要重新计算目标,否则会把正常的份额变化误当成买卖信号。数量不是整数、存在相关证券冻结、或涉及未结融券负债的拆分场景,不适用本手册的样例;融券跨分红的场景也不要直接套用普通现券策略。
下面是一个持仓观察片段,可以放在已有的 after_trading 中调用,它不改变账户资产:
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}')
