🚀 AI 一键生成 ptrade 策略代码

PTrade 策略进阶:如何利用 pickle 模块实现持仓 N 天后自动卖出?

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

问题描述

如何编写一个持仓满 N 天自动卖出的策略?

解决方案

这是一个非常经典的量化策略需求。在 PTrade 中实现“持仓满 N 天自动卖出”的策略,核心难点在于如何跨交易日记录持仓天数

由于策略在实盘或模拟盘中可能会因为服务器重启而中断,内存中的变量(如 g.count)可能会丢失。因此,我们需要利用 Python 的 pickle 模块将持仓天数保存到文件中,实现数据的持久化

以下是完整的策略代码及详细说明。

策略逻辑说明

  1. 初始化 (initialize): 设定要操作的股票和持仓天数阈值 $N$。尝试读取本地保存的持仓天数文件,如果文件不存在,则初始化一个空字典。
  2. 盘前处理 (before_trading_start): 每天开盘前,将所有在持仓字典中的股票天数加 1。
  3. 盘中处理 (handle_data):
    • 买入逻辑: 如果当前没有持仓,则买入股票,并将该股票记入持仓字典,天数设为 0(或 1,视具体需求而定)。
    • 卖出逻辑: 检查持仓字典,如果某只股票的持仓天数达到 $N$ 天,则执行卖出操作,并从字典中移除该记录。
    • 数据保存: 每次操作后或每日结束时,将最新的持仓字典保存到文件中,防止数据丢失。

PTrade 策略代码实现

import pickle
from collections import defaultdict

def initialize(context):
    """
    初始化函数,设置股票池、全局变量和加载持久化数据
    """
    # 1. 设置要操作的股票
    g.security = '600570.SS'
    set_universe(g.security)
    
    # 2. 设置持仓天数阈值 (例如:持仓满5天卖出)
    g.hold_days_limit = 5
    
    # 3. 获取研究路径,用于存放持久化文件
    # 注意:不要使用 import os,直接使用 PTrade 提供的 get_research_path()
    g.file_path = get_research_path() + 'holding_days_record.pkl'
    
    # 4. 尝试加载之前的持仓天数记录
    try:
        with open(g.file_path, 'rb') as f:
            g.hold_days_dict = pickle.load(f)
            log.info("成功加载持仓记录: %s" % g.hold_days_dict)
    except:
        # 如果文件不存在或读取失败,初始化一个新的字典
        # key为股票代码,value为持仓天数
        g.hold_days_dict = defaultdict(int)
        log.info("未找到持仓记录,初始化为空")

def before_trading_start(context, data):
    """
    盘前处理:每天开盘前,将记录中的持仓天数 +1
    """
    # 遍历字典中的所有股票,天数加1
    # 注意:这里需要判断一下实际持仓,防止字典里有记录但实际仓位已为0的情况(比如手工卖出)
    current_positions = context.portfolio.positions.keys()
    
    # 使用 list() 转换 keys,避免在遍历时修改字典导致报错
    for stock in list(g.hold_days_dict.keys()):
        if stock in current_positions:
            g.hold_days_dict[stock] += 1
            log.info("股票 %s 持仓天数增加至: %d" % (stock, g.hold_days_dict[stock]))
        else:
            # 如果账户里已经没有该股票(可能被手动卖出),则清除记录
            del g.hold_days_dict[stock]
            log.info("股票 %s 已无持仓,清除记录" % stock)

def handle_data(context, data):
    """
    盘中处理:执行买入和基于时间的卖出逻辑
    """
    security = g.security
    
    # --- 卖出逻辑 ---
    # 检查是否满足卖出条件(持仓天数 >= N)
    if security in g.hold_days_dict:
        current_hold_days = g.hold_days_dict[security]
        
        if current_hold_days >= g.hold_days_limit:
            # 卖出该股票所有持仓
            order_target(security, 0)
            log.info("股票 %s 持仓已满 %d 天,执行卖出" % (security, current_hold_days))
            
            # 卖出后,从记录字典中删除
            del g.hold_days_dict[security]
            
            # 保存数据到文件
            save_data()
            return # 卖出后结束本次 handle_data,避免同日买入

    # --- 买入逻辑 (示例) ---
    # 如果当前没有持仓,且不在持仓记录中,则买入
    # 这里仅为示例,实际策略可替换为你的买入信号(如均线金叉等)
    curr_position = get_position(security)
    if curr_position.amount == 0 and security not in g.hold_days_dict:
        # 买入 1000 股
        order(security, 1000)
        
        # 记录初始持仓天数,通常设为 0 或 1
        # 设为 0 表示买入当天不算持有天数,T+1 为第 1 天
        g.hold_days_dict[security] = 0 
        log.info("买入股票 %s,初始化持仓天数为 0" % security)
        
        # 保存数据到文件
        save_data()

def after_trading_end(context, data):
    """
    盘后处理:确保每日收盘后数据被保存
    """
    save_data()
    log.info("盘后数据已保存")

def save_data():
    """
    自定义函数:将 g.hold_days_dict 保存到 pickle 文件
    """
    try:
        with open(g.file_path, 'wb') as f:
            pickle.dump(g.hold_days_dict, f)
    except Exception as e:
        log.error("保存持仓记录失败: %s" % e)

代码关键点解析

  1. pickle 模块的使用

    • PTrade 的策略在服务器重启或隔日运行时,initialize 会重新执行。如果不将变量保存到硬盘文件,g.hold_days_dict 会被重置,导致持仓天数归零。
    • 使用 pickle.dump 保存数据,pickle.load 读取数据。
  2. get_research_path()

    • 这是 PTrade 专用的 API,用于获取策略允许读写文件的目录。严禁使用绝对路径或尝试访问系统其他目录。
  3. 持仓天数的计算逻辑

    • handle_data 买入时,将天数设为 0
    • before_trading_start(每日开盘前)将天数 +1
    • 这意味着:
      • T日买入(Day 0)。
      • T+1日开盘前变为 Day 1。
      • ...
      • T+5日开盘前变为 Day 5 -> 触发 handle_data 中的卖出逻辑。
  4. 异常处理与数据清洗

    • before_trading_start 中增加了一个判断:if stock in current_positions。这是为了防止你在交易终端手动卖出了股票,但策略代码里的字典还记录着它,导致逻辑错乱。

Q&A 常见问题解答

Q: 为什么不能直接用 g.count 累加,而要用文件保存?
A: PTrade 的实盘/模拟盘环境在每天收盘后或服务器维护时可能会重启进程。重启后,内存中的全局变量 g 会被重置为 initialize 中的初始状态。为了保证策略逻辑跨日连续(即“持久化”),必须将关键数据写入文件。

Q: before_trading_starthandle_data 里的逻辑顺序有影响吗?
A: 有影响。通常建议在 before_trading_start 中增加天数。因为该函数在每天 9:00-9:30 之间运行一次,非常适合做“过夜”后的状态更新。如果在 handle_data 中增加天数,需要非常小心控制频率,因为 handle_data 可能会每分钟甚至每 3 秒运行一次。

Q: 如果我想按分钟级别回测,这个策略适用吗?
A: 适用。代码中的 before_trading_start 依然是按天运行的(每个交易日早晨一次)。无论你的回测频率是分钟还是日线,持仓天数的逻辑都是以“自然交易日”为单位增加的,不会受分钟级频率影响。

Q: 如何修改为“持仓满 N 个交易日”而不是自然日?
A: 上述代码实现的正是交易日逻辑。因为 before_trading_start 只在交易日运行。如果中间隔了周末或节假日,函数不会执行,计数器不会增加,完全符合股市的 T+N 交易日概念。