可转债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)筛选价格和溢价率都低的转债。但我在实盘中观察到两个问题:
- 高溢价转债的低价是假的——价格低但溢价率极高,说明转债已经”债性化”,失去股性弹性,很难涨上去。
- 双低排名过度关注溢价率——导致权重偏向那些转股价刚下修、溢价率骤降的白马债,反倒可能错过真正的困境反转机会。
S04策略的选择是:以纯低价为主,溢价率作为否决条件(而不是评分因子)。
2.3 S04的”4”代表什么
S04中的”4”代表四个核心维度:
| 维度 | 含义 | 权重 |
|---|---|---|
| 价格 | 转债现价越低越好 | 核心排序 |
| 到期收益率 | YTM越高越好,提供安全边际 | 辅助排序 |
| 转股溢价率 | 低于某个阈值才纳入备选池 | 否决条件 |
| 规模/流动性 | 日均成交额/余额规模,确保可交易 | 过滤条件 |
三、S04策略设计
3.1 选债条件
S04策略在每个调仓日按以下条件筛选全市场可转债:
硬性过滤条件(缺一不可):
- 价格区间:转债现价在 85-115 元之间
- 转股溢价率:< 60%(排除债性过重的转债)
- 到期收益率:> 0%(必须为正,确保有债底保护)
- 余额规模:> 1亿元(排除规模过小、流动性差的转债)
- 剩余期限:> 0.5年(排除临近到期的短期限转债)
- 信用评级:AA- 及以上(排除低评级、违约风险较高的转债)
- 非ST正股:正股未被ST/*ST
- 非强赎状态:已公告强赎的转债排除
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 框架设计原则
回测框架要求:
- 事件驱动:每个交易日逐个处理,模拟真实交易流程
- 滑点模拟:买入按均价 + 0.1%,卖出按均价 - 0.1%
- 交易成本:卖出时收取万二印花税 + 万二手续费
- 涨跌停处理:遇到涨停买不进、跌停卖不出的情况,延迟到下个交易日处理
- 强赎处理:已公告强赎的转债在强赎登记日前强制卖出
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.86 | 0.52 |
| 最大回撤 | -12.4% | -18.7% |
| 胜率(周度) | 64.2% | 52.1% |
| 盈亏比 | 2.31 | 1.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%+),但实盘遇到了严重问题:
- 流动性陷阱:S05偏好余额不足5000万元的超小盘转债,实盘时买卖价差极大。一只余额2000万的转债,稍微一买就拉高2-3个点,一卖就砸低2-3个点。
- 强赎踩踏:小盘转债一旦公告强赎,转债价格可能在2-3天内从130+跌到101。S05持有6只,在2024年踩了两次强赎雷。
- 浮亏-32%:2024年2月市场大跌期间,S05的最大浮亏达到-32%。表面上看价格到了95元附近,但因为规模小、评级低,根本没有买盘承接,想止损都止损不了。
7.2 S06策略的教训
S06是一个双低策略的机器学习增强版——用XGBoost预测未来两周的转债涨幅,持仓由模型决定。
教训更惨痛:
- 过拟合:回测中Sharpe 2.5+,实盘直接变成负收益。机器学习模型在回测数据上”发现”了大量伪规律,换一个市场环境就失效。
- 模型漂移:2024年市场风格切换后,模型推荐的转债持续跑输基准,但回测时完全没暴露这个问题。
- 不可解释:最痛苦的是不知道模型为什么亏钱——“因子失效”还是”模型出错”?没有答案。
7.3 S04的改进
从S05/S06的失败中,S04做了三个关键改进:
- 流动性硬约束:余额 < 1亿元的转债直接排除,不参与任何小盘博弈。
- 规则驱动而非模型驱动:纯逻辑公式打分,没有ML/DL黑箱,每一笔交易的原因都可解释。
- 分散与频率: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部署步骤
- 创建策略:在Ptrade客户端 -> 我的策略 -> 新建Python策略,粘贴上述代码
- 设置定时任务:策略设置 -> 定时运行 -> 每周五14:50
- 设置交易参数:股票代码范围输入
123000-128999(可转债代码段) - 权限开通:确保已开通可转债交易权限和创业板权限
- 观察期:先跑模拟交易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策略的应对:
- 评级过滤:AA-以下的一律排除。虽然评级不是万能的,但AA-门槛可以筛掉90%以上的高风险转债。
- 余额底线:余额 > 1亿元。大余额转债对应的发行人通常规模更大、抗风险能力更强。
- 行业分散:组合内尽量避免同一行业的转债超过2只。
- 止损机制:如果某只转债出现信用风险事件(评级下调、正股被ST、延迟发布年报等),手动/自动止损卖出,不等周五调仓。
9.4 宏观风险
- 利率风险:市场利率上升会压低转债的纯债价值。对于低价转债,利率敏感度更高。2023年Q3曾因利率上行导致低价转债整体下跌约5%。S04的解决方法是缩短持有期限——低价转债的YTM为正,票息可以部分对冲利率风险。
- 流动性风险:低价转债在市场恐慌时可能出现流动性枯竭。2024年1-2月的案例中,部分低价转债连续多日成交额不足100万。S04通过余额门槛和分散持仓来降低流动性风险的影响。
十、总结
10.1 S04策略的核心优势
- 逻辑透明:纯规则驱动,每一笔交易的原因都可解释
- 回测扎实:三年半回测,年化18.7%,夏普1.86,最大回撤-12.4%
- 实盘验证:在Ptrade上部署运行超过9个月,年化收益约16-20%(略低于回测,符合预期)
- 风险可控:面对违约、强赎、流动性等风险有明确的应对方案
10.2 策略的局限性
- 容量限制:策略规模建议不超过50万元。超过后,低价转债的流动性问题会开始侵蚀收益。
- 市场依赖:在可转债市场整体熊市(如2023年)中,策略收益主要靠轮动产生的超额收益,绝对收益不高。
- 调仓窗口:周频调仓意味着如果周中发生重大事件,只能等到下周五处理。可以考虑加入”紧急止损”规则来弥补。
10.3 未来改进方向
- 日内止损:在Ptrade上增加止损条件单,当单只转债跌幅超过5%时自动平仓
- 多频融合:周频轮动 + 日频信号增强(如成交量异动、下修公告等事件驱动)
- 动态仓位:根据市场整体估值水平调整持仓数量(低估时多持,高估时少持)
- 跨市场对冲:在极端行情下,用中证500期货对冲正股风险
10.4 写在最后
从S05回测的”完美”到实盘的-32%,再到S04的18.7%年化——这个过程中最大的收获是:量化策略的本质不是预测未来,而是管理不确定性。
S04策略不试图预测哪只可转债会涨,它只是系统地买入市场上最便宜的一批可转债,利用价格反转的统计规律获利。这种”笨办法”在回测和实盘中都证明了它的稳健性。
最后,任何策略都有失效的可能。本文所有内容仅用于学习和交流,不构成投资建议。入市有风险,投资需谨慎。
本文发布于2026年6月17日。策略参数可能会根据市场环境变化进行调整,请以最新版本为准。
📚 相关文章推荐
- ETF轮动策略实战:30只选池动量评分+可投资性双因子动态轮动 — 同属轮动策略,动量轮动思路可与低价轮动互为参照
- 布林带均值回归策略:Python回测A股量化,7笔交易85.7%胜率实战 — 均值回归思路相通,都依赖价格反转的统计规律
- DuckDB搭建A股量化数据库:4589只股票本地数据库实战教程 — 988只可转债数据已入库,可直接套用本文轮动策略