🚀 AI 一键生成 ptrade 策略代码

PTrade 自动化交易:如何实现投资组合的定期再平衡策略?

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

问题描述

请帮我编写一个投资组合再平衡策略,实现每季度自动调仓到目标权重

解决方案

这是一个基于 PTrade 框架的投资组合季度再平衡策略

策略逻辑说明

  1. 目标设定:在 initialize 中设定一个目标股票池及其对应的目标权重(例如:5只股票,每只20%)。
  2. 调仓频率:策略设定为每季度(1月、4月、7月、10月)的第一个交易日进行调仓。
  3. 资金分配:根据当前账户的总资产(portfolio_value)乘以目标权重,计算每只股票的目标持仓市值。
  4. 交易执行
    • 使用 order_target_value 函数,该函数会自动计算买卖数量,将持仓调整至目标金额。
    • 优化逻辑:为了防止资金不足,策略会先卖出(持仓过重的股票),释放资金后再买入(持仓过轻的股票)。

策略代码

def initialize(context):
    """
    初始化函数,设置策略参数
    """
    # 1. 设定目标投资组合:股票代码 -> 目标权重
    # 注意:权重之和建议不超过 1.0 (100%),预留少量现金应对滑点和手续费
    g.target_portfolio = {
        '600519.SS': 0.20,  # 贵州茅台
        '000858.SZ': 0.20,  # 五粮液
        '600036.SS': 0.20,  # 招商银行
        '000001.SZ': 0.20,  # 平安银行
        '601318.SS': 0.19   # 中国平安 (预留1%现金)
    }
    
    # 2. 设置股票池
    g.security_list = list(g.target_portfolio.keys())
    set_universe(g.security_list)
    
    # 3. 设定调仓月份 (1月, 4月, 7月, 10月)
    g.rebalance_months = [1, 4, 7, 10]
    
    # 4. 记录上一次调仓的月份,防止同月重复调仓
    g.last_rebalance_month = 0
    
    # 5. 设置手续费 (可选,模拟真实交易成本)
    set_commission(commission_ratio=0.0003, min_commission=5.0, type='STOCK')

def handle_data(context, data):
    """
    盘中运行函数,每日/每分钟调用
    """
    # 获取当前日期
    current_dt = context.blotter.current_dt
    current_month = current_dt.month
    
    # 判断是否满足调仓条件:
    # 1. 当前月份在设定的调仓月份列表中
    # 2. 当前月份尚未进行过调仓
    if current_month in g.rebalance_months and current_month != g.last_rebalance_month:
        log.info("达到季度调仓时间点: %s-%s,开始执行再平衡..." % (current_dt.year, current_month))
        
        # 执行再平衡逻辑
        rebalance_portfolio(context, data)
        
        # 更新标记,本月不再调仓
        g.last_rebalance_month = current_month

def rebalance_portfolio(context, data):
    """
    执行投资组合再平衡的核心逻辑
    """
    # 获取当前账户总资产 (现金 + 持仓市值)
    total_value = context.portfolio.portfolio_value
    
    # 获取当前所有持仓
    positions = context.portfolio.positions
    
    # 分类待买入和待卖出的股票,以便先卖后买
    sell_list = []
    buy_list = []
    
    # 遍历目标组合中的每只股票
    for stock, weight in g.target_portfolio.items():
        # 计算该股票的目标持仓市值
        target_value = total_value * weight
        
        # 获取当前该股票的持仓市值
        current_value = 0.0
        if stock in positions:
            current_value = positions[stock].price * positions[stock].amount
        
        # 判断是买入还是卖出
        if target_value < current_value:
            # 如果目标市值 < 当前市值,需要卖出
            sell_list.append((stock, target_value))
        elif target_value > current_value:
            # 如果目标市值 > 当前市值,需要买入
            buy_list.append((stock, target_value))
    
    # --- 第一步:执行卖出操作 ---
    # 先卖出权重过高的股票,释放资金,避免买入时资金不足
    for stock, target_val in sell_list:
        # 检查股票是否停牌或数据缺失
        if stock in data and not math.isnan(data[stock]['close']):
            order_target_value(stock, target_val)
            log.info("卖出调仓: %s, 目标市值: %.2f" % (stock, target_val))
    
    # --- 第二步:执行买入操作 ---
    for stock, target_val in buy_list:
        # 检查股票是否停牌或数据缺失
        if stock in data and not math.isnan(data[stock]['close']):
            order_target_value(stock, target_val)
            log.info("买入调仓: %s, 目标市值: %.2f" % (stock, target_val))
            
    # --- 第三步:清理不在目标组合中的持仓 ---
    # 如果账户里有不在 g.target_portfolio 里的股票,将其全部清仓
    for stock in list(positions.keys()):
        if stock not in g.target_portfolio and positions[stock].amount > 0:
            if stock in data and not math.isnan(data[stock]['close']):
                order_target(stock, 0)
                log.info("清仓非目标股票: %s" % stock)

# 引入 math 库用于判断 NaN
import math

代码关键点解析

  1. g.target_portfolio:

    • 这是策略的核心配置。你可以随意修改里面的股票代码和权重。
    • 注意:权重的总和最好略小于 1.0(例如 0.99),因为交易需要手续费,且股价波动可能导致计算出的股数所需的资金微略超过可用现金。
  2. g.last_rebalance_month:

    • 这是一个状态变量。因为 handle_data 每天都会运行,我们需要确保在 1 月份只在第 1 个交易日调仓,而不是 1 月份的每一天都调仓。
  3. 先卖后买机制:

    • rebalance_portfolio 函数中,我们将操作分为 sell_listbuy_list
    • 先执行 order_target_value 减少持仓(卖出),资金回到账户后,再执行增加持仓(买入)。这有效避免了“可用资金不足”的报错。
  4. order_target_value(stock, value):

    • 这是 PTrade 非常方便的一个 API。你不需要计算要买多少股,只需要告诉它“我希望这只股票最终持有多少钱”,系统会自动计算差额进行买卖。
  5. 异常处理:

    • 代码中加入了 if stock in data and not math.isnan(...) 的判断。这是为了防止股票停牌或退市导致没有行情数据时,策略报错停止。

如何使用

  1. 将代码复制到 PTrade 的策略编辑器中。
  2. 修改 initialize 中的 g.target_portfolio 为你想要的股票和权重。
  3. 设置回测时间段(例如过去3年),资金(例如100万),频率选择“日线”。
  4. 运行回测即可看到每季度自动调仓的效果。

Q&A

Q: 如何将调仓频率改为每月一次?
A: 将 initialize 中的 g.rebalance_months 修改为 list(range(1, 13)),即 [1, 2, 3, ..., 12]

Q: 为什么我的回测中有些股票没有成交?
A: 可能是因为该股票在调仓日停牌,或者处于涨跌停状态无法成交。代码中的 check_limit 或简单的 data 检查可以避免报错,但无法强制成交。

Q: order_target_value 会自动处理手数取整吗?
A: 是的,PTrade 的交易函数会自动向下取整到 100 股(1手)的整数倍。不足 100 股的部分资金会保留为现金。