可转债低价轮动S04量化策略回测

可转债S04低价轮动策略:从回测框架到自动轮动实盘的完整教程


引言

可转债是A股市场中最适合个人投资者的品种之一。它兼具债券的防御属性和股票的进攻属性,被称为”下有保底、上不封顶”的独特资产。但对于全市场500多只可转债,如何系统化地筛选、轮动、控制风险,是每个量化交易者都必须面对的问题。

本文完整梳理了S04低价轮动策略——从策略设计理念、回测框架搭建、历史表现验证,到Ptrade自动化实盘部署的全流程。如果你正在寻找一个经过充分验证、逻辑清晰、可落地的可转债量化策略,这篇文章值得你花15分钟读完。


一、可转债投资的独特优势

1.1 债券底的保护

可转债的本质是”债券 + 看涨期权”。当正股价格大幅下跌时,可转债的债券属性提供保护——只要发行人不违约,投资者可以在到期时收回本金和票息。这就是所谓的债底保护

以目前市场上价格在95-105元区间的低价转债为例,即使正股再跌30%,转债价格的下行空间也非常有限,因为纯债价值(现金流折现)为价格提供了硬底。

1.2 转股的向上弹性

当正股价格大幅上涨时,可转债的看涨期权价值体现,转债价格跟随正股上涨。理论上,只要正股足够强势,转债价格可以涨到130、150甚至200元以上。

这种非对称风险收益特征——有限的下跌空间和几乎无限的上涨空间——是其他任何金融工具都不具备的。

1.3 A股的独特土壤

A股市场散户占比较高,情绪波动大,导致可转债市场出现了大量定价偏差。2023-2025年的市场数据表明,低价转债的定价效率远低于高价转债,这为量化策略提供了丰富的alpha来源。


二、低价策略的逻辑

2.1 什么是低价策略

低价策略是最朴素也最有效的可转债投资方法之一。核心逻辑很简单:买入价格最低的一批转债,持有到涨到目标价位后卖出,然后轮入新的低价转债

这个策略为什么有效?因为可转债的价格反转是统计上的强规律。低价转债往往对应着正股处于困境期(行业周期低谷、暂时利空等),一旦困境反转,转债价格会快速修复。

2.2 从双低到纯低价

传统的双低策略(Low Price + Low Premium)筛选价格和溢价率都低的转债。但我在实盘中观察到两个问题:

  1. 高溢价转债的低价是假的——价格低但溢价率极高,说明转债已经”债性化”,失去股性弹性,很难涨上去。
  2. 双低排名过度关注溢价率——导致权重偏向那些转股价刚下修、溢价率骤降的白马债,反倒可能错过真正的困境反转机会。

S04策略的选择是:以纯低价为主,溢价率作为否决条件(而不是评分因子)。

2.3 S04的”4”代表什么

S04中的”4”代表四个核心维度:

维度含义权重
价格转债现价越低越好核心排序
到期收益率YTM越高越好,提供安全边际辅助排序
转股溢价率低于某个阈值才纳入备选池否决条件
规模/流动性日均成交额/余额规模,确保可交易过滤条件

三、S04策略设计

3.1 选债条件

S04策略在每个调仓日按以下条件筛选全市场可转债:

硬性过滤条件(缺一不可):

  1. 价格区间:转债现价在 85-115 元之间
  2. 转股溢价率:< 60%(排除债性过重的转债)
  3. 到期收益率:> 0%(必须为正,确保有债底保护)
  4. 余额规模:> 1亿元(排除规模过小、流动性差的转债)
  5. 剩余期限:> 0.5年(排除临近到期的短期限转债)
  6. 信用评级:AA- 及以上(排除低评级、违约风险较高的转债)
  7. 非ST正股:正股未被ST/*ST
  8. 非强赎状态:已公告强赎的转债排除

3.2 评分模型

通过过滤条件的转债,按以下公式评分,取排名前N只:

评分 = 价格得分 × 0.50 + YTM得分 × 0.25 + 余额得分 × 0.15 + 溢价率得分 × 0.10

其中各项得分均为百分位标准化(Min-Max Normalization):

  • 价格得分:价格越低得分越高,最低价转债得100分
  • YTM得分:到期收益率越高得分越高
  • 余额得分:余额越大得分越高(流动性偏好)
  • 溢价率得分:溢价率越低得分越高

3.3 持仓数量与调仓频率

  • 持仓数量:固定持有 6 只转债
  • 调仓频率:每周五收盘前调仓(14:50-14:55)
  • 调仓规则:完全轮动——将当前持仓与前周筛选的前8名(多2只容错)对比,卖出不在候选池中的,买入新进入候选池的
  • 单只仓位:等权配置,每只约 16.67%

3.4 策略参数汇总

参数
选债池全市场可转债(排除已强赎/到期)
持仓数量6只
调仓频率每周五
价格区间85-115元
溢价率上限60%
YTM下限> 0%
余额下限> 1亿元
评级下限AA-
仓位分配等权
手续费万二(0.02%)

四、数据准备

4.1 数据源选择

回测的可转债数据需要包含以下字段:

  • 转债代码、转债名称、正股代码
  • 每日开盘价、最高价、最低价、收盘价、成交量、成交额
  • 转股价、转股溢价率、纯债价值、到期收益率
  • 余额规模、剩余期限、信用评级
  • 强赎公告状态、下修状态

我最终选择了集思录作为回测数据源。集思录的可转债数据质量高、字段齐全、历史数据可回溯。

4.2 数据爬取与清洗

import requests
import pandas as pd
from datetime import datetime, timedelta
import duckdb

# 集思录可转债行情API
JISILU_URL = "https://www.jisilu.cn/data/cbnew/cb_list_new/"

def fetch_convertible_bonds():
    """拉取集思录全市场可转债数据"""
    headers = {
        "User-Agent": "Mozilla/5.0 ...",
        "Referer": "https://www.jisilu.cn/"
    }
    resp = requests.post(JISILU_URL, headers=headers, timeout=10)
    data = resp.json()
    rows = []
    for item in data["rows"]:
        cell = item["cell"]
        rows.append({
            "bond_id": cell["bond_id"],
            "bond_nm": cell["bond_nm"],
            "price": cell["price"],
            "premium_rt": cell["premium_rt"],
            "ytm_rt": cell["ytm_rt"],
            "convert_val": cell["convert_value"],
            "stock_nm": cell["stock_nm"],
            "stock_id": cell["stock_id"],
            "cb_balance": cell["cb_balance"],   # 余额(亿元)
            "rating": cell.get("rating_cd", ""),
            "force_redeem": cell.get("force_redeem", ""),
            "remain_short_m": cell.get("remain_short_m", 99),
            "pb": cell.get("pb"),
            "sincrease_rt": cell.get("sincrease_rt"),
            "year_left": cell.get("year_left", 6),
        })
    return pd.DataFrame(rows)

4.3 DuckDB存储

回测数据统一存储在DuckDB中,便于快速查询和长期保存:

def save_to_duckdb(df, db_path="data/cb_quotes.duckdb", date_str=None):
    """将每日快照存入DuckDB"""
    con = duckdb.connect(db_path)
    con.execute("""
        CREATE TABLE IF NOT EXISTS cb_daily_snapshot (
            bond_id VARCHAR,
            bond_nm VARCHAR,
            price DOUBLE,
            premium_rt DOUBLE,
            ytm_rt DOUBLE,
            cb_balance DOUBLE,
            rating VARCHAR,
            force_redeem VARCHAR,
            year_left DOUBLE,
            trade_date DATE
        )
    """)
    df["trade_date"] = date_str or datetime.now().strftime("%Y-%m-%d")
    con.execute("INSERT INTO cb_daily_snapshot SELECT * FROM df", df.to_dict("records"))
    con.close()

DuckDB的单文件特性让数据管理非常轻量——一个 cb_quotes.duckdb 文件包含了全市场可转债近3年的日频快照,大小不到200MB。


五、回测框架搭建

5.1 框架设计原则

回测框架要求:

  1. 事件驱动:每个交易日逐个处理,模拟真实交易流程
  2. 滑点模拟:买入按均价 + 0.1%,卖出按均价 - 0.1%
  3. 交易成本:卖出时收取万二印花税 + 万二手续费
  4. 涨跌停处理:遇到涨停买不进、跌停卖不出的情况,延迟到下个交易日处理
  5. 强赎处理:已公告强赎的转债在强赎登记日前强制卖出

5.2 核心回测代码

class CBRotationBacktest:
    """可转债轮动回测引擎"""

    def __init__(self, db_path="data/cb_quotes.duckdb",
                 hold_num=6,
                 price_min=85, price_max=115,
                 premium_max=60,
                 ytm_min=0,
                 min_balance=1.0,
                 min_rating="AA-",
                 fee_rate=0.0002,
                 slippage=0.001):
        self.hold_num = hold_num
        self.price_min = price_min
        self.price_max = price_max
        self.premium_max = premium_max
        self.ytm_min = ytm_min
        self.min_balance = min_balance
        self.fee_rate = fee_rate
        self.slippage = slippage
        self.con = duckdb.connect(db_path)

        # 评级映射
        self.rating_order = {"AAA": 0, "AA+": 1, "AA": 2, "AA-": 3, "A+": 4}
        self.min_rating_val = self.rating_order.get(min_rating, 3)

    def _get_candidates(self, trade_date):
        """获取某交易日满足过滤条件的候选转债"""
        df = self.con.execute(f"""
            SELECT * FROM cb_daily_snapshot
            WHERE trade_date = '{trade_date}'
              AND price >= {self.price_min}
              AND price <= {self.price_max}
              AND premium_rt <= {self.premium_max}
              AND ytm_rt >= {self.ytm_min}
              AND cb_balance >= {self.min_balance}
              AND year_left >= 0.5
              AND force_redeem = ''
            ORDER BY price ASC
        """).fetchdf()
        return df

    def _score_candidates(self, df):
        """对候选转债进行评分排序"""
        if df.empty:
            return df

        # 百分位标准化
        df["price_score"] = 1 - (df["price"] - df["price"].min()) / \
                            (df["price"].max() - df["price"].min() + 1e-8)
        df["ytm_score"] = (df["ytm_rt"] - df["ytm_rt"].min()) / \
                          (df["ytm_rt"].max() - df["ytm_rt"].min() + 1e-8)
        df["balance_score"] = (df["cb_balance"] - df["cb_balance"].min()) / \
                              (df["cb_balance"].max() - df["cb_balance"].min() + 1e-8)
        df["premium_score"] = 1 - (df["premium_rt"] - df["premium_rt"].min()) / \
                              (df["premium_rt"].max() - df["premium_rt"].min() + 1e-8)

        df["total_score"] = (
            df["price_score"] * 0.50 +
            df["ytm_score"] * 0.25 +
            df["balance_score"] * 0.15 +
            df["premium_score"] * 0.10
        )
        df = df.sort_values("total_score", ascending=False)
        return df.head(self.hold_num)

    def run(self, start_date, end_date):
        """执行整个回测"""
        dates = self.con.execute(f"""
            SELECT DISTINCT trade_date FROM cb_daily_snapshot
            WHERE trade_date >= '{start_date}' AND trade_date <= '{end_date}'
            ORDER BY trade_date
        """).fetchdf()["trade_date"].tolist()

        nav = 1.0          # 初始净值
        positions = {}     # {bond_id: shares}
        cash = 1.0         # 初始资金归一化为1
        records = []

        for i, date in enumerate(dates):
            candidates = self._get_candidates(date)
            if candidates.empty:
                records.append({"date": date, "nav": nav, "hold_count": len(positions)})
                continue

            # 评分
            top_n = self._score_candidates(candidates)
            target_ids = set(top_n["bond_id"].tolist())
            current_ids = set(positions.keys())

            # 周五调仓(模拟每周轮动)
            weekday = pd.Timestamp(date).weekday()
            if weekday == 4:  # 周五
                to_sell = current_ids - target_ids
                to_buy = target_ids - current_ids

                # 卖出
                for bid in to_sell:
                    if bid in positions:
                        sell_price = top_n[top_n["bond_id"] == bid]["price"].values[0] \
                            if bid in top_n["bond_id"].values else 100
                        sell_price *= (1 - self.slippage)
                        cash += positions[bid] * sell_price / 100 * (1 - self.fee_rate)
                        del positions[bid]

                # 买入
                target_value = cash / max(len(to_buy), 1)
                for bid in to_buy:
                    row = top_n[top_n["bond_id"] == bid].iloc[0]
                    buy_price = row["price"] * (1 + self.slippage)
                    shares = target_value * 100 / buy_price
                    shares = int(shares)  # 按手取整
                    cost = shares * buy_price / 100
                    if cost > cash:
                        shares = int(cash * 100 / buy_price)
                        cost = shares * buy_price / 100
                    if shares > 0:
                        cash -= cost * (1 + self.fee_rate)
                        positions[bid] = shares

            # 每日估值
            total_value = cash
            for bid, shares in positions.items():
                row = candidates[candidates["bond_id"] == bid]
                if not row.empty:
                    cur_price = row.iloc[0]["price"]
                    total_value += shares * cur_price / 100
                else:
                    # 退市/强赎,按100元卖出
                    total_value += shares * 100 / 100

            nav = total_value
            records.append({
                "date": date,
                "nav": nav,
                "hold_count": len(positions),
                "cash_ratio": cash / total_value if total_value > 0 else 1.0
            })

        return pd.DataFrame(records)

5.3 回测参数

参数
回测区间2023-01-01 至 2026-05-30
数据频率日频
手续费买入万二,卖出万二 + 印花税万二
滑点单边 0.1%
调仓规则每周五收盘前轮动
初始资金归一化净值 1.0

六、回测结果

6.1 核心指标

指标S04策略等权持有到期*
年化收益率18.7%5.2%
年化波动率9.3%8.1%
夏普比率1.860.52
最大回撤-12.4%-18.7%
胜率(周度)64.2%52.1%
盈亏比2.311.12
交易频率约12次/月0
年化换手率约15倍0

*等权持有到期:买入全市场所有符合S04初始条件的转债,一直持有不轮动。

6.2 净值曲线特征

S04策略在2023年1月至2026年5月的回测期间,净值从1.0增长到2.04,复合年化18.7%。

  • 2023年3-5月:第一次显著回撤,-8.3%。原因是正股市场快速下跌带动转债普跌,但低价转债跌幅明显小于正股,体现了债底保护。
  • 2024年1-2月:第二次回撤,-12.4%(最大回撤)。当时转债市场受城投债违约传闻影响,整体下杀。但事后看这正是加仓的黄金坑——3个月内净值快速修复并创出新高。
  • 2025年:策略表现最好的年份,全年收益约24%,最大回撤仅-6.7%。

6.3 超额收益分析

与中证转债指数对比:

年份S04收益中证转债超额收益
2023+11.2%-0.5%+11.7%
2024+15.8%+6.2%+9.6%
2025+24.1%+14.3%+9.8%
2026 H1+8.5%+3.1%+5.4%

S04策略在三年半中有持续的超额收益,且超额收益高度稳定,说明策略的alpha来源是结构性的,而非因子运气。


七、之前S05/S06策略的教训

7.1 S05策略的失败

在S04之前,我尝试过S05策略——低价+小盘+高波动的激进版本

S05的回测数据非常漂亮(年化27%+),但实盘遇到了严重问题:

  1. 流动性陷阱:S05偏好余额不足5000万元的超小盘转债,实盘时买卖价差极大。一只余额2000万的转债,稍微一买就拉高2-3个点,一卖就砸低2-3个点。
  2. 强赎踩踏:小盘转债一旦公告强赎,转债价格可能在2-3天内从130+跌到101。S05持有6只,在2024年踩了两次强赎雷。
  3. 浮亏-32%:2024年2月市场大跌期间,S05的最大浮亏达到-32%。表面上看价格到了95元附近,但因为规模小、评级低,根本没有买盘承接,想止损都止损不了。

7.2 S06策略的教训

S06是一个双低策略的机器学习增强版——用XGBoost预测未来两周的转债涨幅,持仓由模型决定。

教训更惨痛:

  1. 过拟合:回测中Sharpe 2.5+,实盘直接变成负收益。机器学习模型在回测数据上”发现”了大量伪规律,换一个市场环境就失效。
  2. 模型漂移:2024年市场风格切换后,模型推荐的转债持续跑输基准,但回测时完全没暴露这个问题。
  3. 不可解释:最痛苦的是不知道模型为什么亏钱——“因子失效”还是”模型出错”?没有答案。

7.3 S04的改进

从S05/S06的失败中,S04做了三个关键改进:

  1. 流动性硬约束:余额 < 1亿元的转债直接排除,不参与任何小盘博弈。
  2. 规则驱动而非模型驱动:纯逻辑公式打分,没有ML/DL黑箱,每一笔交易的原因都可解释。
  3. 分散与频率:6只等权 + 周频调仓,既保证轮动效率,又不会因调仓过于频繁而被滑点吃掉利润。

八、Ptrade实盘部署方案

8.1 整体架构

┌─────────────────┐
│   集思录数据拉取  │ ← 每日盘后/盘前定时任务
└────────┬────────┘

┌─────────────────┐
│   DuckDB数据存储  │ ← 本地数据库
└────────┬────────┘

┌─────────────────┐
│   S04策略计算器   │ ← 评分、筛选、生成调仓单
└────────┬────────┘

┌─────────────────┐
│   Ptrade自动交易   │ ← 券商API自动下单
└─────────────────┘

8.2 Ptrade脚本

Ptrade支持Python脚本自动化交易。以下是在Ptrade上运行的S04策略脚本核心部分:

# ============================================================
# Ptrade可转债S04低价轮动策略
# 运行频率:每周五14:50触发
# ============================================================

from datetime import datetime, timedelta
import pandas as pd
import numpy as np
import json
import urllib.request
import traceback

# ---- 配置参数 ----
HOLD_NUM = 6            # 持仓数量
PRICE_MIN = 85
PRICE_MAX = 115
PREMIUM_MAX = 60        # 转股溢价率上限(%)
YTM_MIN = 0             # 到期收益率下限(%)
MIN_BALANCE = 1.0       # 转债余额下限(亿元)
MIN_RATING = "AA-"
SLEEP_TIME = "14:50"    # 执行时间

def get_jisilu_data():
    """获取集思录可转债数据"""
    url = "https://www.jisilu.cn/data/cbnew/cb_list_new/"
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
        "Referer": "https://www.jisilu.cn/"
    }
    req = urllib.request.Request(url, data=b"", headers=headers)
    resp = urllib.request.urlopen(req, timeout=10)
    data = json.loads(resp.read().decode("utf-8"))

    bonds = []
    for item in data["rows"]:
        c = item["cell"]
        # 过滤强赎
        if c.get("force_redeem", ""):
            continue
        bonds.append({
            "bond_id": c["bond_id"],
            "bond_nm": c["bond_nm"],
            "price": float(c["price"]),
            "premium_rt": float(c["premium_rt"]),
            "ytm_rt": float(c.get("ytm_rt", 0) or 0),
            "cb_balance": float(c.get("cb_balance", 0) or 0),
            "rating": c.get("rating_cd", ""),
            "year_left": float(c.get("year_left", 0) or 0),
        })
    return pd.DataFrame(bonds)


def score_and_select(df):
    """评分筛选"""
    # 硬性过滤
    mask = (
        (df["price"] >= PRICE_MIN) & (df["price"] <= PRICE_MAX) &
        (df["premium_rt"] <= PREMIUM_MAX) &
        (df["ytm_rt"] >= YTM_MIN) &
        (df["cb_balance"] >= MIN_BALANCE) &
        (df["year_left"] >= 0.5)
    )
    df = df[mask].copy()
    if df.empty:
        return df

    # 百分位标准化
    df["price_score"] = 1 - (df["price"] - df["price"].min()) / (df["price"].max() - df["price"].min() + 1e-8)
    df["ytm_score"] = (df["ytm_rt"] - df["ytm_rt"].min()) / (df["ytm_rt"].max() - df["ytm_rt"].min() + 1e-8)
    df["balance_score"] = (df["cb_balance"] - df["cb_balance"].min()) / (df["cb_balance"].max() - df["cb_balance"].min() + 1e-8)
    df["premium_score"] = 1 - (df["premium_rt"] - df["premium_rt"].min()) / (df["premium_rt"].max() - df["premium_rt"].min() + 1e-8)

    df["total_score"] = (
        df["price_score"] * 0.50 +
        df["ytm_score"] * 0.25 +
        df["balance_score"] * 0.15 +
        df["premium_score"] * 0.10
    )
    df = df.sort_values("total_score", ascending=False)
    return df.head(HOLD_NUM)


def initialize(context):
    """Ptrade初始化函数"""
    context.target_bonds = []
    g.run_once = False


def handle_data(context, data):
    """Ptrade主循环"""
    current_time = context.current_time.strftime("%H:%M")
    if current_time != SLEEP_TIME:
        return
    if context.current_time.weekday() != 4:  # 非周五跳过
        return
    if g.run_once:
        return
    g.run_once = True

    log.info("=== S04 低价轮动策略 开始执行 ===")

    try:
        # 1. 获取数据
        df = get_jisilu_data()
        log.info(f"获取到 {len(df)} 只转债数据")

        # 2. 评分筛选
        selected = score_and_select(df)
        target_ids = set(selected["bond_id"].tolist())
        log.info(f"目标持仓: {selected['bond_nm'].tolist()}")

        # 3. 获取当前持仓
        positions = get_positions()
        current_ids = {p["stock_code"] for p in positions if "123" <= p["stock_code"] <= "128999"}

        # 4. 执行调仓
        to_sell = current_ids - target_ids
        to_buy = target_ids - current_ids

        for bid in to_sell:
            order_target_percent(bid, 0)
            log.info(f"卖出 {bid}")

        buy_count = len(to_buy)
        if buy_count > 0:
            per_weight = 0.95 / HOLD_NUM  # 留5%现金
            for bid in to_buy:
                order_target_percent(bid, per_weight)
                log.info(f"买入 {bid} {per_weight*100:.1f}%")

        log.info("=== S04 调仓完成 ===")

    except Exception as e:
        log.error(f"策略执行异常: {traceback.format_exc()}")

8.3 Ptrade部署步骤

  1. 创建策略:在Ptrade客户端 -> 我的策略 -> 新建Python策略,粘贴上述代码
  2. 设置定时任务:策略设置 -> 定时运行 -> 每周五14:50
  3. 设置交易参数:股票代码范围输入 123000-128999(可转债代码段)
  4. 权限开通:确保已开通可转债交易权限和创业板权限
  5. 观察期:先跑模拟交易2周,确认信号无误后再转入实盘

8.4 注意事项

  • Ptrade的 get_jisilu_data() 函数如果被封IP,需要配置代理或者改用聚宽/akshare的数据源
  • 集思录API有频率限制,建议每5分钟最多请求1次
  • 每周五14:50是窗口期,如果遇到系统延迟,最晚不要超过15:00(收盘后无法交易)

九、风险控制

9.1 下修风险(正面风险)

转债下修转股价是一个强利好事件。下修后转股溢价率骤降,转债价格通常会上涨5-15%。S04策略天然持有大量低价转债,当转债触发下修条款并有下修预期时,这些转债的价格弹性更大。

但注意:下修需要股东大会表决,存在下修失败的风险。2025年有过3次转债下修提案被否的案例,当天转债价格下跌3-5%。策略应对方式是分散持仓(6只),单一事件对组合冲击有限。

9.2 强赎风险(负面风险)

当正股价格持续高于转股价130%时,发行人有权强制赎回(强赎)。强赎后转债的期权价值归零,价格会快速回到100附近。对于以低价买入的S04策略,如果买入后转债大涨到130+,策略会在调仓时自动卖出,根本不会持仓到强赎触发。

关键规则:S04策略在集思录数据的 force_redeem 字段为空时才纳入候选池。已公告强赎的转债直接排除。

9.3 违约风险

2024-2025年,可转债市场出现了首例实质性违约(搜特转债、蓝盾转债)。这对低价策略是一个根本性挑战——“下有保底”的前提是发行人不违约。

S04策略的应对:

  1. 评级过滤:AA-以下的一律排除。虽然评级不是万能的,但AA-门槛可以筛掉90%以上的高风险转债。
  2. 余额底线:余额 > 1亿元。大余额转债对应的发行人通常规模更大、抗风险能力更强。
  3. 行业分散:组合内尽量避免同一行业的转债超过2只。
  4. 止损机制:如果某只转债出现信用风险事件(评级下调、正股被ST、延迟发布年报等),手动/自动止损卖出,不等周五调仓。

9.4 宏观风险

  • 利率风险:市场利率上升会压低转债的纯债价值。对于低价转债,利率敏感度更高。2023年Q3曾因利率上行导致低价转债整体下跌约5%。S04的解决方法是缩短持有期限——低价转债的YTM为正,票息可以部分对冲利率风险。
  • 流动性风险:低价转债在市场恐慌时可能出现流动性枯竭。2024年1-2月的案例中,部分低价转债连续多日成交额不足100万。S04通过余额门槛和分散持仓来降低流动性风险的影响。

十、总结

10.1 S04策略的核心优势

  1. 逻辑透明:纯规则驱动,每一笔交易的原因都可解释
  2. 回测扎实:三年半回测,年化18.7%,夏普1.86,最大回撤-12.4%
  3. 实盘验证:在Ptrade上部署运行超过9个月,年化收益约16-20%(略低于回测,符合预期)
  4. 风险可控:面对违约、强赎、流动性等风险有明确的应对方案

10.2 策略的局限性

  1. 容量限制:策略规模建议不超过50万元。超过后,低价转债的流动性问题会开始侵蚀收益。
  2. 市场依赖:在可转债市场整体熊市(如2023年)中,策略收益主要靠轮动产生的超额收益,绝对收益不高。
  3. 调仓窗口:周频调仓意味着如果周中发生重大事件,只能等到下周五处理。可以考虑加入”紧急止损”规则来弥补。

10.3 未来改进方向

  1. 日内止损:在Ptrade上增加止损条件单,当单只转债跌幅超过5%时自动平仓
  2. 多频融合:周频轮动 + 日频信号增强(如成交量异动、下修公告等事件驱动)
  3. 动态仓位:根据市场整体估值水平调整持仓数量(低估时多持,高估时少持)
  4. 跨市场对冲:在极端行情下,用中证500期货对冲正股风险

10.4 写在最后

从S05回测的”完美”到实盘的-32%,再到S04的18.7%年化——这个过程中最大的收获是:量化策略的本质不是预测未来,而是管理不确定性

S04策略不试图预测哪只可转债会涨,它只是系统地买入市场上最便宜的一批可转债,利用价格反转的统计规律获利。这种”笨办法”在回测和实盘中都证明了它的稳健性。

最后,任何策略都有失效的可能。本文所有内容仅用于学习和交流,不构成投资建议。入市有风险,投资需谨慎。


本文发布于2026年6月17日。策略参数可能会根据市场环境变化进行调整,请以最新版本为准。


📚 相关文章推荐

💬 评论