🚀 AI 一键生成 ptrade 策略代码

PTrade 平台如何实现股票投资组合的等权重自动化再平衡?

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

问题描述

如何编写一个策略,让我持有的多只股票每个月自动调整一次仓位,使得每只股票的市值都相等?

解决方案

这是一个非常经典的**定期等权重再平衡(Equal Weight Rebalancing)**策略。

策略逻辑分析

  1. 选股(Universe):在 initialize 中设定你想要持有的股票列表。
  2. 择时(Timing):我们需要判断“每个月调整一次”的时间点。通常的做法是在每个月的第一个交易日进行调仓。
    • 判断方法:获取当前日期和上一个交易日的日期,如果两者的月份不同,说明今天是一个新月份的开始。
  3. 交易(Trading)
    • 计算账户总资产(现金 + 持仓市值)。
    • 计算每只股票的目标持仓市值 = 总资产 / 股票数量。
    • 使用 order_target_value 函数,将每只股票的持仓调整到目标市值。

策略代码实现

以下是完整的 PTrade 策略代码。为了保证交易成功率,代码中增加了“先卖后买”的逻辑,防止因可用资金不足导致买入失败。

def initialize(context):
    """
    初始化函数,设置股票池和定时任务
    """
    # 1. 设置我们要持有的股票列表(示例为几只蓝筹股,可根据需求修改)
    g.security = ['600519.SS', '000858.SZ', '601318.SS', '600036.SS']
    
    # 2. 设置股票池
    set_universe(g.security)
    
    # 3. 设置手续费(可选,这里设为万分之三)
    set_commission(commission_ratio=0.0003, min_commission=5.0, type="STOCK")
    
    # 4. 每天上午 10:00 检查是否需要调仓
    # 选择 10:00 是为了避开开盘剧烈波动,也可以设为 '14:50' 尾盘调仓
    run_daily(context, monthly_rebalance_check, time='10:00')

def monthly_rebalance_check(context):
    """
    每日检查函数:判断今天是否是本月第一个交易日
    """
    # 获取当前回测/交易日期的 date 对象
    current_date = context.blotter.current_dt.date()
    
    # 获取上一个交易日的 date 对象
    # get_trading_day(-1) 返回的是 datetime.date 对象
    prev_trading_day = get_trading_day(-1)
    
    # 如果当前日期的月份 与 上一交易日的月份 不同,说明跨月了,今天是本月第一天
    if current_date.month != prev_trading_day.month:
        log.info("检测到月份变化,开始执行月度等权重调仓...")
        rebalance(context)

def rebalance(context):
    """
    调仓核心逻辑:将所有股票调整为等市值
    """
    # 获取股票数量
    stock_count = len(g.security)
    if stock_count == 0:
        return
    
    # 获取账户总资产 (现金 + 持仓市值)
    total_value = context.portfolio.portfolio_value
    
    # 计算每只股票的目标市值
    target_value = total_value / stock_count
    
    log.info("当前账户总资产: %.2f, 单只股票目标市值: %.2f" % (total_value, target_value))
    
    # --- 交易执行部分 ---
    
    # 为了防止资金不足,建议分两步走:先卖出超出比例的,再买入不足比例的
    
    # 第一轮:卖出操作
    # 遍历股票池,如果当前持仓市值 > 目标市值,则卖出差额
    for stock in g.security:
        position = get_position(stock)
        # 计算当前该股持仓市值 (价格 * 数量)
        current_stock_value = position.last_sale_price * position.amount
        
        # 如果当前持仓过重,先卖出
        if current_stock_value > target_value:
            order_target_value(stock, target_value)
    
    # 第二轮:买入操作
    # 遍历股票池,如果当前持仓市值 < 目标市值,则买入
    for stock in g.security:
        position = get_position(stock)
        current_stock_value = position.last_sale_price * position.amount
        
        # 如果当前持仓不足(或没持仓),买入
        if current_stock_value < target_value:
            order_target_value(stock, target_value)

def handle_data(context, data):
    """
    盘中运行函数,本策略主要逻辑在 run_daily 中,此处留空即可
    """
    pass

代码关键点解析

  1. run_daily(..., time='10:00'):

    • 我们使用定时任务来触发检查,而不是在 handle_data 里每分钟都检查。这样效率更高,且逻辑更清晰。
    • 时间设为 10:00 是为了等待开盘行情稳定。如果你希望以收盘价为准,可以改为 14:50
  2. 月份判断逻辑:

    • current_date.month != prev_trading_day.month:这是判断“月初”最简单有效的方法。只要今天和上一个交易日不在同一个月,今天必然是该月的第一天。
  3. order_target_value(stock, target_value):

    • 这是 PTrade 非常强大的一个函数。你不需要计算要买卖多少股,只需要告诉它“我希望这只股票最终持有多少钱”,系统会自动计算差额并下单。
  4. 先卖后买:

    • rebalance 函数中,我写了两个循环。第一个循环先处理需要减仓的股票,释放出现金;第二个循环处理需要加仓的股票。这样可以避免因为满仓状态下,先发出买单导致“资金不足”的废单。

常见问题 (Q&A)

Q1: 如果我想每季度调整一次怎么办?
A: 可以修改 monthly_rebalance_check 中的判断逻辑。除了判断月份不同外,还可以判断当前月份是否是季度的第一个月(1月, 4月, 7月, 10月)。
例如:

if current_date.month != prev_trading_day.month and current_date.month in [1, 4, 7, 10]:
    rebalance(context)

Q2: 为什么不直接在 handle_data 里写逻辑?
A: handle_data 的运行频率取决于回测设置(分钟级或日级)。如果在分钟级回测中直接写日期判断,会导致该月第一天的每一分钟都触发调仓,造成重复交易和巨额手续费。使用 run_daily 可以确保每天只运行一次检查。

Q3: order_target_value 会自动处理整手(100股)吗?
A: 是的。PTrade 的交易函数会自动向下取整到 100 股(股票交易规则)。因此,实际持仓市值可能会略小于计算出的 target_value,剩下的零头资金会保留在现金账户中。

Q4: 如果某只股票停牌了怎么办?
A: 如果股票停牌,order_target_value 下单会失败(或者无法成交)。策略中的 try...except 机制(虽然示例代码为了简洁未加,但 PTrade 内部有容错)通常会跳过该股票。在下个月调仓时,如果复牌了,会再次尝试调整。