AI-股票分析工具开发

一:背景

模型:除了各个大公司会搭建自己的私域模型,并且针对公司的知识做监督学习和强化学习,一般人使用的都是通用模型,需要依赖高质量的prompt的提高模型的准确率,减少幻觉

爱好:个人业余时间有限,又喜欢买股票,一般新知识的应用都会和股票分析工具搭上关系。

 

二:解决方案

提示词 + 模型[通义千问,deepseek,claude] + 人工调试

 

三:prompt提示词

我想写一个Python脚本,用来分析A股和深股的股票,具体需求如下:

1. 尽量使用akshare获取股票数据,如果有其他更好的库也可以
3. 筛选原则
3.1. 按照 "股票编号 股票名称 所属板块 分红率 市值 营收 利润" 找出各个板块的前三个龙头股,然后计算近三年的分红率,比如今年是2025年,计算2022-2024平均分红/当前价格 
3.2. 从上述数据中,再把分红率大于3%的股票 单独组成一份数据
4、脚本添加必要的说明,介绍一下整个脚本的作用,已经如何运行脚本
5、代码中添加必要的打印
6、考虑异常退出,如果脚本出现异常终止的时候,需要输出终止原因
7、接口封装要好,层次清晰
8.获取数据的时候,如果太频繁会接口调用失败,需要考虑每只股票之间随机1~3s获取一次,如果获取失败,最多尝试三次,都失败之后,进行下一个股票的获取

 

四、模型原始产出

import akshare as ak
import pandas as pd
import time
import random
from datetime import datetime
import traceback
import numpy as np

def main():
    """
    A股/深股龙头股分析脚本
    功能:
    1. 获取所有A股/深股股票数据
    2. 按行业板块筛选市值前三的龙头股
    3. 计算近三年平均分红率
    4. 筛选分红率>3%的优质股票
    
    输出两份报告:
    - 各板块龙头股报告
    - 高分红率股票报告(分红率>3%)
    """
    try:
        print("="*50)
        print("开始执行股票分析脚本")
        print(f"当前时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
        print("="*50)
        
        # 步骤1: 获取股票基本信息
        print("\n[步骤1/4] 获取股票基本信息...")
        stock_basic_df = get_stock_basic_info()
        print(f"获取到 {len(stock_basic_df)} 只股票基本信息")
        
        # 步骤2: 获取财务数据
        print("\n[步骤2/4] 获取财务数据(市值/营收/利润)...")
        financial_df = get_financial_data(stock_basic_df)
        
        # 步骤3: 获取分红数据
        print("\n[步骤3/4] 获取分红数据...")
        dividend_df = get_dividend_data(stock_basic_df)
        
        # 步骤4: 数据整合与分析
        print("\n[步骤4/4] 数据分析与筛选...")
        merged_df = merge_data(stock_basic_df, financial_df, dividend_df)
        
        # 按板块筛选龙头股
        top_stocks = get_industry_top_stocks(merged_df)
        print("\n" + "="*50)
        print(f"各板块龙头股分析完成,共筛选出 {len(top_stocks)} 只龙头股")
        
        # 筛选高分红股票
        high_dividend_stocks = top_stocks[top_stocks['分红率'] > 3]
        print(f"其中高分红率股票(>3%)有 {len(high_dividend_stocks)} 只")
        print("="*50 + "\n")
        
        # 输出结果
        print_top_stocks(top_stocks)
        print_high_dividend_stocks(high_dividend_stocks)
        
        # 保存结果到CSV
        save_results(top_stocks, high_dividend_stocks)
        
    except Exception as e:
        print("\n" + "!"*50)
        print("脚本执行失败!错误信息:")
        print(traceback.format_exc())
        print("!"*50)
        exit(1)

def get_stock_basic_info():
    """获取股票基本信息:代码、名称、所属行业"""
    print("  > 获取A股基本信息...")
    stock_info_a = ak.stock_info_a_code_name()
    print("  > 获取深股基本信息...")
    stock_info_sz = ak.stock_info_sz_name_code(indicator="A股列表")
    
    # 合并沪深两市股票
    df = pd.concat([
        stock_info_a[['code', 'name']],
        stock_info_sz[['A股代码', 'A股简称']].rename(columns={'A股代码': 'code', 'A股简称': 'name'})
    ]).drop_duplicates('code')
    
    print("  > 获取行业分类信息...")
    industry_df = ak.stock_board_industry_name_em()
    industry_map = {}
    for _, row in industry_df.iterrows():
        cons_df = ak.stock_board_industry_cons_em(symbol=row['板块名称'])
        for code in cons_df['代码']:
            industry_map[code] = row['板块名称']
    
    # 添加行业信息
    df['板块'] = df['code'].map(industry_map)
    return df.dropna(subset=['板块'])

def get_financial_data(stock_basic_df):
    """获取财务数据:市值、营收、利润"""
    financial_data = []
    total_stocks = len(stock_basic_df)
    processed = 0
    failed = 0
    
    print(f"  > 开始获取财务数据,共 {total_stocks} 只股票...")
    
    for idx, code in enumerate(stock_basic_df['code']):
        processed += 1
        if processed % 50 == 0:
            print(f"    已处理 {processed}/{total_stocks} 只股票,失败: {failed}")
            
        max_retries = 3
        retry_count = 0
        success = False
        
        while not success and retry_count < max_retries:
            try:
                # 添加随机延迟 (1-3秒)
                delay = random.uniform(1, 3)
                time.sleep(delay)
                
                # 获取最新指标数据
                indicator_df = ak.stock_a_lg_indicator(symbol=code)
                
                if not indicator_df.empty:
                    latest = indicator_df.iloc[0]
                    financial_data.append({
                        'code': code,
                        '市值(亿)': latest['total_mv'] / 1e8,
                        '营收(亿)': latest['or'] / 1e8,
                        '利润(亿)': latest['netprofit'] / 1e8
                    })
                    success = True
                else:
                    # 数据为空,视为失败
                    retry_count += 1
                    if retry_count < max_retries:
                        print(f"    {code} 财务数据为空,重试中... ({retry_count}/{max_retries})")
                        time.sleep(2 ** retry_count)  # 指数退避
            except Exception as e:
                retry_count += 1
                if retry_count < max_retries:
                    print(f"    {code} 财务数据获取失败,重试中... ({retry_count}/{max_retries}) 错误: {str(e)[:50]}")
                    time.sleep(2 ** retry_count)  # 指数退避
                else:
                    print(f"    {code} 财务数据获取失败,跳过该股票")
                    failed += 1
    
    print(f"  > 财务数据获取完成,成功: {len(financial_data)},失败: {failed}")
    return pd.DataFrame(financial_data)

def get_dividend_data(stock_basic_df):
    """获取近三年分红数据并计算分红率"""
    dividend_data = []
    current_year = datetime.now().year
    total_stocks = len(stock_basic_df)
    processed = 0
    failed = 0
    
    print(f"  > 开始获取分红数据,共 {total_stocks} 只股票...")
    
    for idx, code in enumerate(stock_basic_df['code']):
        processed += 1
        if processed % 50 == 0:
            print(f"    已处理 {processed}/{total_stocks} 只股票,失败: {failed}")
            
        max_retries = 3
        retry_count = 0
        success = False
        
        while not success and retry_count < max_retries:
            try:
                # 添加随机延迟 (1-3秒)
                delay = random.uniform(1, 3)
                time.sleep(delay)
                
                # 获取历史分红
                div_df = ak.stock_history_dividend_detail(symbol=code, indicator="分红")
                
                if div_df.empty:
                    # 没有分红数据,视为成功但跳过记录
                    success = True
                    continue
                    
                # 筛选近三年数据
                div_df = div_df[div_df['公告日期'].str[:4].astype(int) >= current_year - 3]
                
                if not div_df.empty:
                    avg_div = div_df['每股股息'].mean()
                    # 获取当前股价
                    quote = ak.stock_zh_a_spot_em(symbol=code)
                    
                    if not quote.empty:
                        current_price = quote['最新价'].values[0]
                        div_rate = (avg_div / current_price * 100) if current_price > 0 else 0
                        dividend_data.append({
                            'code': code,
                            '平均分红': avg_div,
                            '分红率': div_rate
                        })
                        success = True
                    else:
                        # 股价获取失败
                        retry_count += 1
                        if retry_count < max_retries:
                            print(f"    {code} 股价获取失败,重试中... ({retry_count}/{max_retries})")
                            time.sleep(2 ** retry_count)  # 指数退避
                else:
                    # 近三年无分红数据,视为成功但跳过记录
                    success = True
                    
            except Exception as e:
                retry_count += 1
                if retry_count < max_retries:
                    print(f"    {code} 分红数据获取失败,重试中... ({retry_count}/{max_retries}) 错误: {str(e)[:50]}")
                    time.sleep(2 ** retry_count)  # 指数退避
                else:
                    print(f"    {code} 分红数据获取失败,跳过该股票")
                    failed += 1
    
    print(f"  > 分红数据获取完成,成功: {len(dividend_data)},失败: {failed}")
    return pd.DataFrame(dividend_data)

def merge_data(basic_df, financial_df, dividend_df):
    """合并所有数据源"""
    merged = basic_df.merge(financial_df, on='code', how='left')
    merged = merged.merge(dividend_df, on='code', how='left')
    
    # 处理缺失值
    merged['分红率'] = merged['分红率'].fillna(0)
    merged['平均分红'] = merged['平均分红'].fillna(0)
    
    # 过滤无效数据
    merged = merged.dropna(subset=['市值(亿)', '营收(亿)', '利润(亿)'])
    merged = merged[merged['市值(亿)'] > 0]  # 排除市值为0的股票
    
    return merged

def get_industry_top_stocks(df):
    """按行业筛选市值前三的龙头股"""
    top_stocks = []
    
    for industry, group in df.groupby('板块'):
        # 排除无财务数据的股票
        valid_group = group.dropna(subset=['市值(亿)'])
        if len(valid_group) == 0:
            continue
            
        # 按市值降序排序
        sorted_group = valid_group.sort_values('市值(亿)', ascending=False)
        # 取前三名
        top_three = sorted_group.head(3)
        top_stocks.append(top_three)
    
    return pd.concat(top_stocks).sort_values('板块')

def print_top_stocks(df):
    """打印龙头股报告"""
    print("\n" + "="*50)
    print("各板块龙头股报告:")
    print("="*50)
    for industry, group in df.groupby('板块'):
        print(f"\n【{industry}】板块龙头股:")
        for _, row in group.iterrows():
            print(f"  {row['code']} {row['name']} | "
                  f"市值:{row['市值(亿)']:.1f}亿 | "
                  f"分红率:{row['分红率']:.2f}% | "
                  f"营收:{row['营收(亿)']:.1f}亿 | "
                  f"利润:{row['利润(亿)']:.1f}亿")

def print_high_dividend_stocks(df):
    """打印高分红股票报告"""
    if not df.empty:
        print("\n" + "="*50)
        print("高分红率股票(>3%):")
        print("="*50)
        for _, row in df.iterrows():
            print(f"  {row['code']} {row['name']} ({row['板块']}) | "
                  f"分红率:{row['分红率']:.2f}% | "
                  f"市值:{row['市值(亿)']:.1f}亿")
    else:
        print("\n未找到分红率>3%的股票")

def save_results(top_stocks, high_dividend_stocks):
    """保存结果到CSV文件"""
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    top_file = f"龙头股分析_{timestamp}.csv"
    div_file = f"高分红股票_{timestamp}.csv"
    
    # 优化输出格式
    output_columns = ['code', 'name', '板块', '分红率', '市值(亿)', '营收(亿)', '利润(亿)', '平均分红']
    
    top_stocks[output_columns].to_csv(top_file, index=False, float_format='%.2f')
    high_dividend_stocks[output_columns].to_csv(div_file, index=False, float_format='%.2f')
    
    print("\n" + "="*50)
    print(f"结果已保存: {top_file}, {div_file}")
    print("="*50)

if __name__ == "__main__":
    main()

 

五:运行效果

原始运行效果:

错误较多,主要分为以下大类:

1、akshare库接口使用错误,和库变化较快以及不够大众化有关

2、本质上对于数据爬取,都会出现很多失败,需要考虑反爬 

 

人工校验后运行效果:

 

 

六:思考

怎么提高质量?

1、提高提示词质量 --> prompt工程知识有限怎么办? --->模型对promot进行完善

2、细化需求,强调任务

 

 

 

 

开心每一天,写代码的小熊猫~