From 9ecc796d36f5f436a1b3cf2ad5f43a4c6b7ad6ba Mon Sep 17 00:00:00 2001 From: aszerW Date: Fri, 8 May 2026 22:52:36 +0800 Subject: [PATCH] =?UTF-8?q?fix(engine):=20=E4=BF=AE=E5=A4=8D=E5=87=80?= =?UTF-8?q?=E5=80=BC=E8=AE=A1=E7=AE=97=E4=B8=ADNaN=E5=80=BC=E5=AF=BC?= =?UTF-8?q?=E8=87=B4=E7=9A=84=E7=BC=BA=E5=A4=B1=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 问题分析: 1. HSTECH.HK数据从2020-08-26才开始,早期数据缺失 2. 原净值计算使用iloc[0]作为基准,若首日价格是NaN则整列净值变成NaN 3. 原日收益率计算中,某品种日收益率NaN会导致mean()返回NaN 4. 导致策略净值62个缺失值(3.53%) 修复内容: 1. strategies/rotation/engine.py - 各品种净值计算 - 使用第一个有效价格作为基准(而非iloc[0]) - 若品种无有效数据则净值列全部为NaN 2. strategies/rotation/engine.py - 策略日收益率计算 - select_num=1时:若日收益率是NaN则返回0.0 - select_num>1时:忽略NaN值计算mean() - 若所有品种日收益率都缺失则返回0.0 修复效果: - 策略净值缺失:从62个(3.53%)降至0个(0%) - HSTECH.HK净值缺失:从100%降至21.49%(377/1754) - 其他品种净值:全部无缺失 说明: - HSTECH.HK净值377个缺失是正常的(数据从2020-08-26才开始) - 早期缺失日期(2019年)非HSTECH持仓期,缺失由其他原因导致 --- strategies/rotation/engine.py | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/strategies/rotation/engine.py b/strategies/rotation/engine.py index fef294d..82144fb 100644 --- a/strategies/rotation/engine.py +++ b/strategies/rotation/engine.py @@ -241,21 +241,29 @@ class RotationStrategy(BacktestStrategy): select_num = self.config["select_num"] trade_cost = self.config["trade_cost"] - # 计算策略日收益率 + # 计算策略日收益率 - 处理NaN值 if select_num == 1: def calc_return(row): signal = row['信号'] if not signal or pd.isna(signal): return 0.0 - return row.get(f"日收益率_{signal}", 0.0) + ret = row.get(f"日收益率_{signal}", 0.0) + # 如果日收益率是NaN,返回0.0 + return ret if pd.notna(ret) else 0.0 result["轮动策略日收益率"] = result.apply(calc_return, axis=1) else: def calc_multi_return(row): codes = [c for c in row["信号"].split(",") if c] # 过滤空字符串 if not codes: return 0.0 - returns = [row.get(f"日收益率_{c}", 0.0) for c in codes] - return np.mean(returns) + # 获取各品种日收益率,忽略NaN值 + returns = [] + for c in codes: + ret = row.get(f"日收益率_{c}", None) + if ret is not None and pd.notna(ret): + returns.append(ret) + # 如果所有品种日收益率都缺失,返回0.0 + return np.mean(returns) if returns else 0.0 result["轮动策略日收益率"] = result.apply(calc_multi_return, axis=1) # 扣除交易成本 @@ -281,10 +289,16 @@ class RotationStrategy(BacktestStrategy): # 计算净值 result["轮动策略净值"] = (1 + result["轮动策略日收益率"]).cumprod() - # 各ETF单独净值 + # 各ETF单独净值 - 使用第一个有效价格作为基准 for code in self.valid_codes: - first_price = result[code].iloc[0] - result[f"净值_{code}"] = result[code] / first_price + # 获取第一个有效价格(非NaN) + valid_prices = result[code][result[code].notna()] + if len(valid_prices) > 0: + first_valid_price = valid_prices.iloc[0] + result[f"净值_{code}"] = result[code] / first_valid_price + else: + # 如果没有有效数据,净值列全部为NaN + result[f"净值_{code}"] = np.nan # 基准净值 # benchmark_data 是 DataFrame,需要提取 close 列