You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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;
  • 不允许卖空,最终输出策略收益。

当前问题

代码运行后,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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 18:49:55