结合CTA策略开发的实际场景,用Python举例说明凯利公式的不同应用方式

核心前提:

  • CTA策略已存在: 我们假设你已经有了一个(或多个)CTA策略,并通过历史回测得到了一系列的交易记录(盈利或亏损)。
  • 数据准备: 关键是获取可靠的 p (胜率) 和 b (赔率/盈亏比)。

场景一:基于固定历史数据的基本分数凯利

这是最基础的应用。策略回测完成后,基于整个回测期的表现计算一次凯利比例,并使用一个固定的折扣因子(分数凯利)。

Python 示例:

import numpy as np
import pandas as pd

# 假设这是你的策略回测交易记录 (PnL: Profit and Loss)
# trade_results = [-100, 250, -50, 300, -120, 400, -80] # 示例数据
# 更真实的数据可能来自 Pandas DataFrame
trades_df = pd.DataFrame({
    'pnl': [-100, 250, -50, 300, -120, 400, -80, 150, -60, 220]
})

def calculate_kelly_basic(trade_pnl_series, kelly_fraction=0.5):
    """
    基于历史交易盈亏计算基础凯利比例 (带折扣因子)

    Args:
        trade_pnl_series (pd.Series): 包含单笔交易盈亏的 Pandas Series
        kelly_fraction (float): 凯利折扣因子 (0 < k <= 1), 推荐远小于1

    Returns:
        float: 推荐的资金使用比例 (分数凯利 f)
               返回 -1 表示无法计算或期望为负
    """
    n_trades = len(trade_pnl_series)
    if n_trades == 0:
        print("没有交易记录,无法计算凯利比例。")
        return -1

    wins = trade_pnl_series[trade_pnl_series > 0]
    losses = trade_pnl_series[trade_pnl_series <= 0] # 包含0 PnL视为亏损或不计

    n_wins = len(wins)
    n_losses = n_trades - n_wins

    if n_losses == 0: # 胜率100% (理论情况)
        p = 1.0
        # 无法计算赔率b,可以认为风险极小,但 Full Kelly 仍有风险
        # 实际中,可以设定一个最大允许比例或基于波动性决定
        print("警告: 胜率100%,无法计算传统赔率。返回设定的最大允许比例或分数。")
        return kelly_fraction # 或者一个预设的上限,如 0.1

    if n_wins == 0: # 胜率0%
         print("胜率为0,不建议投入资金。")
         return 0.0

    # 计算胜率 p
    p = n_wins / n_trades

    # 计算平均盈利和平均亏损
    avg_win = wins.mean()
    avg_loss = abs(losses.mean()) # 注意取绝对值

    if avg_loss == 0: # 防止除以零
        print("警告: 平均亏损为0,无法计算赔率。")
        # 类似于胜率100%的情况处理
        return kelly_fraction

    # 计算赔率 b
    b = avg_win / avg_loss

    # 计算 Full Kelly f*
    q = 1 - p
    f_star = p - q / b

    if f_star <= 0:
        print(f"计算得到的凯利比例 f* = {f_star:.4f} <= 0。策略期望为负或零,不建议投入资金。")
        return 0.0
    else:
        # 应用分数凯利
        f = kelly_fraction * f_star
        print(f"胜率 p: {p:.4f}")
        print(f"赔率 b: {b:.4f}")
        print(f"全凯利 f*: {f_star:.4f}")
        print(f"使用分数 k = {kelly_fraction}, 最终凯利比例 f: {f:.4f}")
        return f

# --- 使用示例 ---
account_equity = 1_000_000 # 假设账户总权益
kelly_k = 0.2 # 使用 20% 的凯利比例 (比较保守)

# 计算凯利比例 f
kelly_f = calculate_kelly_basic(trades_df['pnl'], kelly_fraction=kelly_k)

if kelly_f > 0:
    # 这个 f 代表你这次交易愿意承担风险的资金占总权益的比例
    # 这里的 "风险" 通常指预期亏损 (基于平均亏损或止损点)
    risk_capital_per_trade = account_equity * kelly_f
    print(f"\n根据凯利公式 (k={kelly_k}), 单次交易目标风险资金: {risk_capital_per_trade:,.2f}")
    # 后续需要结合波动率或固定止损来计算具体头寸大小 (见场景三)
else:
    print("\n凯利计算结果不建议投入资金。")

说明:

  1. 输入: 函数需要一个包含历史交易盈亏记录的Pandas Series。
  2. 计算: 分别计算胜率 p 和 赔率 b
  3. Full Kelly: 计算 f* = p - (1-p)/b
  4. 检查: 如果 f* <= 0,说明策略期望值为负或零,不应投入资金。
  5. 分数凯利:f* 乘以一个小于1的 kelly_fraction (即 k),得到最终的头寸比例 f
  6. 输出: 返回计算出的分数凯利比例 f。这个 f 代表的是风险预算比例

场景二:动态凯利 - 使用滚动窗口估计参数

考虑到市场条件会变化,使用固定历史数据可能不准确。采用滚动窗口来动态估计 pb 更能适应近期市场。

Python 示例:

import pandas as pd

# 假设 trades_df 包含时间戳和 pnl
# trades_df = pd.DataFrame({'timestamp': pd.to_datetime([...]), 'pnl': [...]})
# trades_df = trades_df.set_index('timestamp') # 假设时间为索引

# 为了演示,我们继续用之前的 trades_df,并假设它是按时间顺序的
trades_df['timestamp'] = pd.to_datetime(pd.date_range(start='2023-01-01', periods=len(trades_df), freq='D'))
trades_df = trades_df.set_index('timestamp')

def calculate_kelly_rolling(trade_pnl_series, window_size, kelly_fraction=0.5):
    """
    使用滚动窗口动态计算凯利比例

    Args:
        trade_pnl_series (pd.Series): 时间索引的交易盈亏 Series
        window_size (int): 滚动窗口大小 (交易次数)
        kelly_fraction (float): 凯利折扣因子

    Returns:
        pd.Series: 每个时间点计算出的分数凯利比例 f
    """
    # 定义一个函数,用于在滚动窗口上计算凯利比例
    def rolling_kelly_func(window_pnl):
        # 复用之前的基础凯利计算逻辑 (稍作修改以适应滚动应用)
        n_trades = len(window_pnl)
        if n_trades < window_size / 2: # 窗口内数据太少,不可靠
             return np.nan # 或返回 0

        wins = window_pnl[window_pnl > 0]
        losses = window_pnl[window_pnl <= 0]
        n_wins = len(wins)
        n_losses = n_trades - n_wins

        if n_losses == 0 or n_wins == 0 or losses.mean() == 0:
            # 边界情况,返回0或nan表示无法计算或不交易
             return np.nan # 或 0.0

        p = n_wins / n_trades
        avg_win = wins.mean()
        avg_loss = abs(losses.mean())
        b = avg_win / avg_loss
        q = 1 - p
        f_star = p - q / b

        if f_star <= 0:
            return 0.0
        else:
            return kelly_fraction * f_star

    # 应用滚动计算
    # min_periods 确保窗口内有足够的数据点才开始计算
    rolling_kelly_f = trade_pnl_series.rolling(window=window_size, min_periods=max(10, window_size // 2)).apply(rolling_kelly_func, raw=True)

    return rolling_kelly_f

# --- 使用示例 ---
rolling_window = 50 # 使用过去50笔交易计算
kelly_k = 0.2

# 假设我们有更长的历史数据用于滚动计算
# 这里用 trades_df 只是演示,实际需要更多数据
if len(trades_df) >= rolling_window:
    dynamic_kelly_f = calculate_kelly_rolling(trades_df['pnl'], window_size=rolling_window, kelly_fraction=kelly_k)
    print("\n动态凯利比例 (滚动窗口):")
    print(dynamic_kelly_f.tail()) # 显示最后几个计算结果

    # 在实际交易中,你会使用最新的 dynamic_kelly_f 值来决定下一笔交易的风险预算
    latest_f = dynamic_kelly_f.iloc[-1] if not pd.isna(dynamic_kelly_f.iloc[-1]) else 0
    print(f"\n最新的动态凯利比例 f: {latest_f:.4f}")

    if latest_f > 0:
         risk_capital_per_trade_dynamic = account_equity * latest_f
         print(f"根据最新的动态凯利 (k={kelly_k}, window={rolling_window}), 单次交易目标风险资金: {risk_capital_per_trade_dynamic:,.2f}")
    else:
         print("最新的动态凯利计算结果不建议投入资金。")

else:
    print(f"\n交易数据不足 {rolling_window} 条,无法进行滚动计算。")

说明:

  1. 滚动窗口: 使用 Pandas 的 rolling() 方法,指定一个窗口大小(例如过去50笔交易)。
  2. 应用函数: 对每个滚动窗口应用我们定义的 rolling_kelly_func 函数来计算该窗口内的 p, b, 和 f
  3. 动态比例: 这样会得到一个随时间变化的凯利比例序列。在进行下一笔交易时,使用最新的有效凯利比例。
  4. min_periods: 确保窗口内至少有一定数量的交易才开始计算,避免早期数据不足导致结果不稳定。

场景三:凯利结合波动率调整 (实际头寸计算)

这是CTA中非常常见和实用的方法。凯利公式确定总风险预算(占总权益的百分比 f,然后根据标的资产的波动率来计算具体的头寸规模(合约数量),使得单次交易的最大预期损失(通常基于ATR止损)大致等于这个风险预算。

Python 示例:

import math

def calculate_position_size_kelly_vol(account_equity, kelly_f, atr, point_value, atr_stop_multiplier=2.0):
    """
    结合凯利比例和ATR波动率计算头寸规模 (期货合约数量)

    Args:
        account_equity (float): 当前账户总权益
        kelly_f (float): 由凯利公式计算出的风险预算比例 (分数凯利 f)
        atr (float): 标的资产的当前 ATR (Average True Range) 值
        point_value (float): 每张合约的价格乘数 (例如,IF沪深300是 300)
        atr_stop_multiplier (float): 使用几倍ATR作为止损距离

    Returns:
        int: 建议的交易合约数量 (向下取整)
    """
    if kelly_f <= 0:
        print("凯利比例为0或负数,不开仓。")
        return 0
    if atr <= 0 or point_value <= 0:
         print("ATR或Point Value无效,无法计算头寸。")
         return 0

    # 1. 计算单次交易的目标风险金额
    target_risk_amount = account_equity * kelly_f

    # 2. 计算单张合约的风险金额 (基于ATR止损)
    #   假设止损设置在入场点 +/- atr_stop_multiplier * ATR 处
    risk_per_contract = atr * atr_stop_multiplier * point_value

    if risk_per_contract == 0:
        print("单张合约风险为0 (ATR可能为0),无法计算头寸。")
        return 0

    # 3. 计算理论头寸数量
    theoretical_position_size = target_risk_amount / risk_per_contract

    # 4. 向下取整得到实际可交易的合约数量
    position_size = math.floor(theoretical_position_size)

    print(f"账户权益: {account_equity:,.2f}")
    print(f"凯利风险预算比例 f: {kelly_f:.4f}")
    print(f"单次交易目标风险金额: {target_risk_amount:,.2f}")
    print(f"ATR: {atr:.2f}, ATR止损倍数: {atr_stop_multiplier}, 合约乘数: {point_value}")
    print(f"单张合约风险 (基于ATR止损): {risk_per_contract:,.2f}")
    print(f"理论头寸数量: {theoretical_position_size:.4f}")
    print(f"实际头寸数量 (向下取整): {position_size}")

    return position_size

# --- 使用示例 ---
account_equity = 1_000_000
# 假设从场景一或场景二得到 kelly_f
# kelly_f = calculate_kelly_basic(trades_df['pnl'], kelly_fraction=0.2)
# 或者使用最新的动态凯利比例
latest_f = 0.08 # 假设最新的动态凯利 f 为 0.08 (8%)

# 假设交易 沪深300股指期货 (IF)
current_atr_IF = 35.5  # 当前IF合约的ATR值 (示例)
point_value_IF = 300   # IF合约的乘数

# 计算应交易的合约数量
contracts_to_trade = calculate_position_size_kelly_vol(
    account_equity,
    latest_f,
    current_atr_IF,
    point_value_IF,
    atr_stop_multiplier=2.0 # 使用2倍ATR止损
)

print(f"\n建议交易 IF 合约数量: {contracts_to_trade}")

说明:

  1. 风险预算: 首先,根据凯利比例 f 和总权益 account_equity 计算出这次交易愿意承担的最大风险金额 target_risk_amount
  2. 单合约风险: 然后,基于当前市场的波动率(常用ATR)和预设的止损逻辑(例如离入场价 atr_stop_multiplier 倍ATR处止损),计算出交易一张合约会带来的预期风险 risk_per_contract
  3. 头寸计算: 用总风险预算除以单张合约的风险,得到理论上可以持有的合约数量。
  4. 取整: 因为合约不能分割,所以向下取整得到最终的整数合约数量。

场景四:多策略/多市场组合(简化视角)

当CTA系统包含多个子策略或交易多个不相关(或低相关)的市场时,直接应用单一凯利比例可能不是最优的。理论上需要使用更复杂的组合凯利公式(涉及协方差矩阵)。实践中,常用简化方法:

  • 方法A:独立计算,风险预算分配:
    1. 为每个子策略/市场独立计算其凯利比例 f_i (可以使用场景一或二)。
    2. 根据某种规则(如等权重、基于夏普比率加权、基于 f_i 大小加权)为每个子策略/市场分配总风险预算的一部分。
    3. 在每个子策略/市场内部,使用分配到的风险预算和其自身的波动率(场景三)来计算头寸。
  • 方法B:风险平价思想:
    1. 设定一个总的投资组合风险目标(如目标年化波动率或最大回撤)。
    2. 估计每个策略/市场的预期波动率和它们之间的相关性。
    3. 分配风险预算,使得每个策略/市场对总组合的边际风险贡献大致相等(或符合预设比例)。这通常需要优化算法。

Python 示例 (方法A - 简化演示):

# 假设有两个策略/市场的数据
trades_pnl_strategy1 = pd.Series([-50, 150, -30, 200, -80])
trades_pnl_strategy2 = pd.Series([80, -40, 120, -60, 100, -50])

atr_market1 = 15
point_value1 = 100
atr_market2 = 0.0050
point_value2 = 100000 # 假设是外汇

account_equity = 1_000_000
kelly_k = 0.2 # 全局凯利分数

# 1. 分别计算每个策略的凯利 f
f1 = calculate_kelly_basic(trades_pnl_strategy1, kelly_fraction=kelly_k)
f2 = calculate_kelly_basic(trades_pnl_strategy2, kelly_fraction=kelly_k)

print(f"\n策略1 凯利 f1: {f1:.4f}")
print(f"策略2 凯利 f2: {f2:.4f}")

# 2. 分配总风险预算 (简化:假设等权重分配总凯利预算)
# 注意:更合理的方式是直接用 f1, f2 作为各自的风险预算比例
# 这里演示另一种思路:先算一个组合的平均或总凯利预算,再分配
# total_portfolio_f = (f1 + f2) / 2 # 极其简化的假设,忽略相关性等

# 更常见的做法: 直接将 f1, f2 视为各自的风险预算比例,但要确保总风险可控
# 例如,可以对 f1, f2 再做调整,使得总风险不超过某个上限
# 假设我们直接使用计算出的 f1, f2 (如果它们>0)

if f1 > 0:
    print("\n--- 计算策略1头寸 ---")
    # 假设策略1使用全部权益进行计算(实践中可能有更复杂的资本分配)
    pos1 = calculate_position_size_kelly_vol(account_equity, f1, atr_market1, point_value1)
    print(f"建议策略1头寸: {pos1}")
else:
    print("\n策略1不建议开仓")

if f2 > 0:
    print("\n--- 计算策略2头寸 ---")
    pos2 = calculate_position_size_kelly_vol(account_equity, f2, atr_market2, point_value2)
    print(f"建议策略2头寸: {pos2}")
else:
    print("\n策略2不建议开仓")

# 重要提示:这种独立计算未考虑组合效应和相关性!
# 实际组合管理需要更复杂的模型,如均值-方差优化、风险平价等。
# 并且,需要监控组合的总风险敞口,确保 (pos1 * risk_per_contract1 + pos2 * risk_per_contract2) / account_equity
# 不超过预设的总风险上限。可能需要按比例缩减 pos1 和 pos2。

说明:

  1. 独立计算: 分别为每个策略/市场计算凯利比例 f
  2. 独立应用: 将计算出的 f(假设为正)和对应市场的波动率信息(ATR, Point Value)代入场景三的函数,计算各自的头寸。
  3. 简化与风险: 这是极度简化的方法。它忽略了策略/市场间的相关性。如果两个策略高度正相关,这样做可能会导致总风险远超预期。实际应用中,必须考虑组合层面的风险控制,可能需要按比例缩减所有头寸以满足整体风险约束。

总结与注意事项:

  • 分数凯利是必须的 (k < 1): 不要使用完整凯利。k 的选择依赖于风险偏好、参数估计的信心和回撤承受能力,通常从很小的值开始(如0.1-0.3)。
  • 参数估计是关键: pb 的准确性直接影响结果。使用滚动窗口、对参数进行保守调整(打折)是常见的做法。
  • 波动率结合: 将凯利(风险预算%)与波动率(计算具体头寸)结合是CTA中非常实用的风险管理技术。
  • 组合复杂性: 多策略/多市场的凯利应用需要考虑相关性,通常需要更高级的投资组合优化技术。
  • 不仅仅是凯利: 头寸管理只是风险管理的一部分。止损策略、整体组合风险监控、流动性管理等同样重要。
  • 回测验证: 任何凯利应用方法都需要在历史数据上进行严格的回测,特别关注最大回撤、夏普比率等指标,并在样本外数据上进行验证。

这些例子提供了一个从基础到进阶应用凯利公式的框架。在实际开发中,你需要根据策略的具体特点、数据的质量和自身的风险管理要求来选择和调整最适合的方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值