问题描述
解决方案
根据股票的波动性(ATR,平均真实波幅)来决定仓位大小,是量化交易中非常经典的资金管理方法(常见于海龟交易法则)。
其核心思想是:波动越大,持仓越小;波动越小,持仓越大。目的是让每一笔交易承担的风险金额是固定的(例如总资金的 1%)。
核心逻辑与公式
-
计算 ATR:衡量股票在过去一段时间(如20天)的平均波动幅度。
-
确定风险敞口:设定你愿意为这笔交易承担的风险比例(例如总资产的 1%)。
-
计算股数:
$$ 买入股数 = \frac{账户总资产 \times 单笔风险比例}{ATR} $$解释:如果股价向不利方向波动了 1个 ATR 的幅度,你的亏损刚好等于你设定的风险金额(如总资产的 1%)。
JoinQuant 策略代码实现
下面是一个完整的策略示例。该策略每天计算标的股票的 ATR,并根据账户总权益的 1% 风险来计算应买入的股数。
# -*- coding: utf-8 -*-
import talib
import numpy as np
def initialize(context):
# 设定基准
set_benchmark('000300.XSHG')
# 开启动态复权模式(真实价格)
set_option('use_real_price', True)
# 过滤掉order系列API产生的比error级别低的log
log.set_level('order', 'error')
# --- 策略参数设置 ---
g.security = '000001.XSHE' # 操作标的:平安银行
g.atr_window = 20 # ATR计算周期
g.risk_ratio = 0.01 # 风险比例:每次交易愿意承担总资产的 1% 波动风险
# 每天开盘时运行
run_daily(market_open, time='09:30')
def market_open(context):
security = g.security
# 1. 获取历史数据 (High, Low, Close)
# 获取 ATR 周期 + 1 天的数据,以确保 talib 计算稳定
count = g.atr_window + 5
h_data = attribute_history(security, count, '1d', ['high', 'low', 'close'])
# 提取为 numpy 数组,用于 talib 计算
high_array = h_data['high'].values
low_array = h_data['low'].values
close_array = h_data['close'].values
# 2. 计算 ATR (平均真实波幅)
# talib.ATR 返回的是一个数组,我们取最后一个值作为当前的 ATR
atr_values = talib.ATR(high_array, low_array, close_array, timeperiod=g.atr_window)
current_atr = atr_values[-1]
# 异常处理:如果 ATR 为 NaN 或 0,则不操作
if np.isnan(current_atr) or current_atr == 0:
log.warn("ATR计算异常,跳过本次交易")
return
# 3. 计算基于波动率的头寸大小
# 获取当前账户总资产
total_value = context.portfolio.total_value
# 计算允许承担的风险金额 (例如 10万 * 1% = 1000元)
risk_money = total_value * g.risk_ratio
# 计算理论买入股数 = 风险金额 / ATR
# 含义:如果价格波动 1个 ATR,账户亏损额度刚好是 risk_money
theoretical_shares = risk_money / current_atr
# 向下取整到 100 的倍数 (A股一手为100股)
target_amount = int(theoretical_shares / 100) * 100
# 4. 执行交易逻辑
# 获取当前持仓
current_position = context.portfolio.positions[security].total_amount
# 获取当前股价
current_price = close_array[-1]
# 打印日志方便观察
log.info("-" * 30)
log.info("当前ATR: %.3f" % current_atr)
log.info("账户总值: %.2f, 风险预算: %.2f" % (total_value, risk_money))
log.info("理论股数: %d, 调整后手数: %d" % (theoretical_shares, target_amount))
# 简单的买入逻辑示例:
# 如果当前没有持仓,且计算出的购买数量大于0,则买入
if current_position == 0 and target_amount > 0:
# 检查现金是否足够(虽然按风险计算了股数,但仍需受限于可用现金)
cash = context.portfolio.available_cash
cost = target_amount * current_price
if cash >= cost:
order(security, target_amount)
log.info("买入 %s, 数量: %d" % (security, target_amount))
else:
log.warn("现金不足以买入目标头寸。需: %.2f, 有: %.2f" % (cost, cash))
# 这里仅演示买入逻辑,卖出逻辑可根据策略需求自行添加
代码关键点解析
-
数据获取 (
attribute_history):
ATR 计算需要最高价(High)、最低价(Low)和收盘价(Close)。我们获取的数据长度比g.atr_window稍长一点,是为了防止talib在计算初期产生 NaN 值。 -
计算 ATR (
talib.ATR):
使用 Python 的talib库是计算技术指标最高效的方法。current_atr代表了该股票近期平均每天可能的波动金额(例如 0.5 元)。 -
头寸计算公式:
theoretical_shares = (total_value * g.risk_ratio) / current_atr- 假设总资金 100,000 元,风险度 1%(即愿意承担 1000 元的波动风险)。
- 场景 A(高波动):某妖股 ATR 为 5.0 元。买入股数 = 1000 / 5.0 = 200 股。
- 场景 B(低波动):某银行股 ATR 为 0.5 元。买入股数 = 1000 / 0.5 = 2000 股。
- 结果:无论买哪只股票,如果价格向不利方向波动 1 个 ATR,你的账面亏损都是 1000 元。这就实现了风险等价。
-
取整处理:
A股交易单位是一手(100股),所以使用int(shares / 100) * 100进行向下取整。
Q&A
Q: 为什么要用 ATR 而不是直接用固定股数或固定金额?
A: 固定股数忽略了股价高低的影响;固定金额忽略了股票活跃度的影响。使用 ATR 可以保证你在波动剧烈的股票上仓位较轻,在波动平缓的股票上仓位较重,从而平滑整个账户的风险曲线。
Q: g.risk_ratio 设置多少合适?
A: 通常建议在 0.5% 到 2% 之间。如果设置为 1%,意味着当股票价格波动幅度达到 1个 ATR 时,你的总账户回撤约为 1%。
Q: 如果计算出的股数需要的资金超过了我的可用现金怎么办?
A: 代码中添加了 if cash >= cost 的判断。在实盘中,这通常意味着该股票波动率极低,导致计算出的理论仓位非常大(类似于加杠杆),此时受限于本金,只能满仓买入或按最大可用资金买入。