Python投资策略代码故障:Result表未更新,持仓与ptf_val始终为0
投资策略代码异常排查与修复
项目需求
基于Yahoo Finance数据实现投资策略,规则如下:
- 获取指定时间段的日收益率;
- 波动率阈值:取周期前5年日绝对收益率的第一个十分位数(即仅10%的绝对收益率低于该值);
- 第5年之后执行策略:
- 若t-1日收益率绝对值小于阈值,则t日以开盘价买入,更新组合价值(
ptf_val),并打印Opening the position; - 若波动率过高(t-1日收益率绝对值≥阈值),则清空组合(重置
ptf_val为0),10天内不投资,并打印Closing the position;
- 若t-1日收益率绝对值小于阈值,则t日以开盘价买入,更新组合价值(
- 不允许卖空,最终输出策略收益。
当前问题
代码运行后,position始终为0,ptf_val一直为0,完全不符合策略逻辑。
核心问题分析与修复方案
1. 阈值计算逻辑错误
原代码中对降序排列的收益率取90%分位数,与需求的“第一个十分位数”逻辑相反。正确做法是直接对未排序的绝对收益率取10%分位数(即np.percentile(returns, 10))。
2. 初始值与数据填充逻辑错误
ptf_val初始设为NaN,第一次赋值时引用前一天的NaN导致计算失效,需初始化ptf_val的起始值为固定初始资金;- 原循环仅修改部分行的
position和ptf_val,未处理的行被初始的0填充,需遍历所有交易日并通过状态变量维护持仓状态。
3. 仓位与组合价值更新逻辑错误
- 策略禁止卖空,需移除
position = -1的设置,仓位仅设为0(空仓)或1(满仓); - 波动率过高时需连续10天保持空仓,需用状态变量跟踪空仓剩余天数,而非直接跳索引;
- 缺失开仓平仓的打印逻辑,需在持仓状态切换时添加对应打印语句;
- 组合价值更新逻辑错误:买入后需让组合价值随股价波动,而非直接累加开盘价,需明确初始资金并基于收益率计算价值变化。
4. 冗余代码问题
主函数中重复调用calculate_threshold(),可直接使用实例已计算好的self.threshold。
修改后的完整代码
import pandas_datareader.data as data import datetime as dt import yfinance as yf import pandas as pd import numpy as np class InvestissementStrategie: def __init__(self, stock_name, start_date, end_date, initial_capital=10000): self.stock_name = stock_name self.start_date = start_date self.end_date = end_date self.initial_capital = initial_capital self.all_quotes = self.fetch_data() self.threshold = self.calculate_threshold() def fetch_data(self): yf.pdr_override() return data.get_data_yahoo(self.stock_name, start=self.start_date, end=self.end_date) def calculate_threshold(self): # 取前5年交易日数据(每年约252个交易日) five_years_end = 5 * 252 if len(self.all_quotes) < five_years_end: print("数据周期不足5年,无法计算阈值") return None five_years_data = self.all_quotes.iloc[:five_years_end] # 计算日绝对收益率并取10%分位数(第一个十分位数) daily_returns = five_years_data['Adj Close'].pct_change().abs().dropna() threshold = np.percentile(daily_returns, 10) return threshold def apply_strategy(self): if self.threshold is None: return 0 self.all_quotes['returns'] = self.all_quotes['Adj Close'].pct_change() # 初始化列:前5年保持空仓,组合价值为初始资金 self.all_quotes['position'] = 0 self.all_quotes['ptf_val'] = self.initial_capital self.all_quotes['strategy'] = 0.0 self.all_quotes['cumulative'] = 1.0 # 状态变量:跟踪空仓剩余天数 no_trade_days_left = 0 # 从第5年后开始执行策略 start_idx = 5 * 252 for idx in range(start_idx, len(self.all_quotes)): prev_return = self.all_quotes.iloc[idx-1]['returns'] prev_position = self.all_quotes.iloc[idx-1]['position'] prev_ptf_val = self.all_quotes.iloc[idx-1]['ptf_val'] if no_trade_days_left > 0: # 处于空仓冷却期,保持空仓并重置组合价值 self.all_quotes.at[self.all_quotes.index[idx], 'position'] = 0 self.all_quotes.at[self.all_quotes.index[idx], 'ptf_val'] = 0 no_trade_days_left -= 1 if no_trade_days_left == 0: print("Closing the position completed, ready for new trades") else: if abs(prev_return) < self.threshold: # 满足买入条件,切换为满仓 if prev_position == 0: print("Opening the position") self.all_quotes.at[self.all_quotes.index[idx], 'position'] = 1 # 组合价值随当日收益率波动 self.all_quotes.at[self.all_quotes.index[idx], 'ptf_val'] = prev_ptf_val * (1 + self.all_quotes.iloc[idx]['returns']) else: # 波动率过高,切换为空仓并启动10天冷却期 if prev_position == 1: print("Closing the position") self.all_quotes.at[self.all_quotes.index[idx], 'position'] = 0 self.all_quotes.at[self.all_quotes.index[idx], 'ptf_val'] = 0 no_trade_days_left = 9 # 剩余9天冷却(含当日) # 计算策略收益率与累计收益 self.all_quotes['strategy'] = self.all_quotes['returns'] * self.all_quotes['position'] self.all_quotes['cumulative'] = (1 + self.all_quotes['strategy']).cumprod() # 计算第5年之后的策略收益 start_cumulative = self.all_quotes['cumulative'].iloc[start_idx-1] final_cumulative = self.all_quotes['cumulative'].iloc[-1] profit = (final_cumulative / start_cumulative) - 1 print('策略盈亏比 : {:.2f}'.format(profit)) print(self.all_quotes[['position', 'ptf_val', 'strategy', 'cumulative']].tail(20)) return profit if __name__ == "__main__": stock_name = 'AAPL' start_date = dt.datetime(2010, 1, 1) end_date = dt.datetime(2022, 12, 31) strategie = InvestissementStrategie(stock_name, start_date, end_date) profit = strategie.apply_strategy() print("阈值为:", strategie.threshold) print("策略收益率 : {:.2%}".format(profit))
关键修改说明
- 添加
initial_capital参数明确组合起始资金; - 修复阈值计算逻辑,取10%分位数匹配需求;
- 用
no_trade_days_left状态变量跟踪空仓冷却期,正确处理连续10天空仓规则; - 仅在持仓状态切换时打印开仓/平仓语句;
- 修正组合价值更新逻辑,使其随股价波动合理变化;
- 移除冗余的阈值重复计算,直接使用实例属性;
- 严格遵守禁止卖空规则,仓位仅设0或1。
内容的提问来源于stack exchange,提问作者charlene
相关产品推荐
相关产品推荐

