🚀 AI 一键生成 joinquant 策略代码

JoinQuant 风险管理:如何根据ATR(真实波幅)动态调整仓位?

joinquant | 发布时间: 2025/12/19
以下内容由 EasyQuant 生成。

问题描述

如何根据股票的波动性(ATR)来决定每次买入多少股?

解决方案

根据股票的波动性(ATR,平均真实波幅)来决定仓位大小,是量化交易中非常经典的资金管理方法(常见于海龟交易法则)。

其核心思想是:波动越大,持仓越小;波动越小,持仓越大。目的是让每一笔交易承担的风险金额是固定的(例如总资金的 1%)。

核心逻辑与公式

  1. 计算 ATR:衡量股票在过去一段时间(如20天)的平均波动幅度。

  2. 确定风险敞口:设定你愿意为这笔交易承担的风险比例(例如总资产的 1%)。

  3. 计算股数
    $$ 买入股数 = \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))
            
    # 这里仅演示买入逻辑,卖出逻辑可根据策略需求自行添加

代码关键点解析

  1. 数据获取 (attribute_history):
    ATR 计算需要最高价(High)、最低价(Low)和收盘价(Close)。我们获取的数据长度比 g.atr_window 稍长一点,是为了防止 talib 在计算初期产生 NaN 值。

  2. 计算 ATR (talib.ATR):
    使用 Python 的 talib 库是计算技术指标最高效的方法。current_atr 代表了该股票近期平均每天可能的波动金额(例如 0.5 元)。

  3. 头寸计算公式:
    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 元。这就实现了风险等价
  4. 取整处理:
    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 的判断。在实盘中,这通常意味着该股票波动率极低,导致计算出的理论仓位非常大(类似于加杠杆),此时受限于本金,只能满仓买入或按最大可用资金买入。