7. 马科维茨资产组合模型+金融研报AI长文本智能体(Qwen-Long)增强方案(理论+Python实战)

目录

    • 0. 承前
    • 1. 深度金融研报准备
    • 2. 核心AI函数代码讲解
      • 2.1 函数概述
      • 2.2 输入参数
      • 2.3 主要流程
      • 2.4 异常处理
      • 2.5 清理工作
      • 2.7 get_ai_weights函数汇总
    • 3. 汇总代码
    • 4. 反思
      • 4.1 不足之处
      • 4.2 提升思路
    • 5. 启后

0. 承前

本篇博文是对前两篇文章,链接:
5. 马科维茨资产组合模型+政策意图AI金融智能体(Qwen-Max)增强方案(理论+Python实战)
6. 马科维茨资产组合模型+政策意图AI金融智能体(DeepSeek-V3)增强方案(理论+Python实战)
的缺点:AI金融智能体所获取信息量(政策意图)过少的改进方案,使用权重计算日期的多篇行业内深度金融研报作为输入信息,为AI金融智能体提供更多更全面的信息支持。

本文与前两篇文章的唯一区别之处在于上文中的get_ai_weights函数,如果需要整体金融工程的思路流程,可以在上文直接跳转,本文中会直接给出修改地方,与能够直接运行的汇总代码。

其中多篇行业内深度金融研报的语义分析,内容整理的实现可参考:
98.1 AI量化开发:长文本AI金融智能体(Qwen-Long)对金融研报大批量处理与智能分析的实战应用

本文与98.1的区别是:本文的行业内深度金融研报信息,会影响资产组合模型的最终权重。

本文主旨:

  • 用文章向大家展示低耦合开发的优点,如本文与上一篇文章的转换只需要重写一个函数即可;
  • 使用实际代码,向大家展示如何向大于语言型传输大批量研报(但单次最好还是10篇以下);

如果想更加全面清晰地了解金融资产组合模型进化论的体系架构,可参考:
0. 金融资产组合模型进化全图鉴

  • 金融工程流程图
AI金融智能体
Qwen-Long
大批量深度行业研报
算法求解
资产组合权重
最大化夏普比优化函数
序列最小二乘规划算法
数据计算
计算预期收益:
Fama-French五因子模型
计算月度对数收益率
计算风险:
协方差矩阵
数据获取
数据预处理
获取行业名单
获取交易数据

1. 深度金融研报准备

以下研报均是使用RPA技术(Robotic Process Automation),获取到20240101日期前的,与本次研究行业(银行)相关的深度金融研报:
在这里插入图片描述
补充:想要找到国内外金融领域的研报,欢迎私信咨询作者。

2. 核心AI函数代码讲解

2.1 函数概述

get_ai_weights函数通过通义千问API分析研报内容,对投资组合权重进行智能调整。

2.2 输入参数

  • character: AI角色设定
  • path: 研报PDF文件目录路径
  • updated_result: 原始投资组合权重
  • api_key: 通义千问API密钥

2.3 主要流程

  • 初始化与路径检查
# 初始化API客户端
client = OpenAI(api_key=api_key, base_url="https://dashscope.aliyuncs.com/compatible-mode/v1")

# 检查并创建目录
abs_path = os.path.abspath(path)
report_dir = Path(abs_path)
  • PDF文件处理
# 获取有效PDF文件
pdf_files = list(report_dir.glob("*.pdf"))
valid_pdf_files = [pdf for pdf in pdf_files if pdf.stat().st_size > 0]

# 上传PDF文件获取file_ids
file_ids = []
for pdf_file in valid_pdf_files:
    file_object = client.files.create(file=pdf_file, purpose="file-extract")
    file_ids.append(file_object.id)
  • AI分析调用
# 构建提示信息
prompt = f"""请分析这些研报,并基于分析结果对以下投资组合进行调整...
            当前投资组合:{json.dumps(updated_result)}"""

# 调用API进行分析
response = client.chat.completions.create(
    model="qwen-long",
    messages=[
        {'role': 'system', 'content': character},
        {'role': 'system', 'content': file_ids_str},
        {'role': 'user', 'content': prompt}
    ]
)
  • 结果处理
# 解析返回结果
content = response.choices[0].message.content.strip()
portfolio_weights = json.loads(content)

# 权重归一化
weights_sum = sum(portfolio_weights.values())
portfolio_weights = {key: round(value/weights_sum, 6) for key, value in portfolio_weights.items()}

2.4 异常处理

  • 目录不存在时返回原始权重
  • PDF文件无效时返回原始权重
  • API调用失败时返回原始权重
  • JSON解析错误时返回原始权重

2.5 清理工作

# 删除已上传的文件
for file_id in file_ids:
    client.files.delete(file_id)

2.7 get_ai_weights函数汇总

返回调整后的投资组合权重字典,格式为:

def get_ai_weights(character, path, updated_result, api_key):
    """
    使用AI分析指定路径下的所有PDF报告内容
    
    Args:
        character (str): AI的角色设定
        path (str): 报告所在目录的路径(会被转换为绝对路径)
        updated_result (dict): 用户角色向AI发送的资产组合权重
        api_key (str): DashScope API密钥
        
    Returns:
        dict: AI调整后的投资组合权重
    """
    # 初始化 OpenAI 客户端
    client = OpenAI(
        api_key=api_key,
        base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
    )
    
    # 将路径转换为绝对路径
    abs_path = os.path.abspath(path)
    report_dir = Path(abs_path)
    
    if not report_dir.exists():
        try:
            report_dir.mkdir(parents=True, exist_ok=True)
        except Exception as e:
            raise ValueError(f"创建目录失败: {report_dir}, 错误: {str(e)}")
        return updated_result  # 如果目录不存在,返回原始权重
    
    # 获取所有PDF文件并上传
    file_ids = []
    pdf_files = list(report_dir.glob("*.pdf"))
    
    if not pdf_files:
        return updated_result  # 如果没有PDF文件,返回原始权重
    
    # 检查文件是否可读且非空
    valid_pdf_files = []
    for pdf_file in pdf_files:
        try:
            if pdf_file.stat().st_size > 0:  # 检查文件大小
                valid_pdf_files.append(pdf_file)
        except Exception:
            continue
    
    if not valid_pdf_files:
        return updated_result  # 如果没有有效文件,返回原始权重
    
    # 上传有效的PDF文件
    for pdf_file in valid_pdf_files:
        try:
            file_object = client.files.create(
                file=pdf_file,
                purpose="file-extract"
            )
            file_ids.append(file_object.id)
        except Exception:
            continue
    
    if not file_ids:
        return updated_result  # 如果文件上传失败,返回原始权重
    
    # 构建file_ids字符串
    file_ids_str = ",".join([f"fileid://{file_id}" for file_id in file_ids])
    
    try:
        # 构建更明确的提示信息
        prompt = f"""请分析这些研报,并基于分析结果对以下投资组合进行调整。
                请注意:
                1. 只需返回调整后的权重数据,格式必须与输入完全一致;
                2. 不要添加任何解释或说明;
                3. 确保返回的是有效的JSON格式。

                当前投资组合:
                {json.dumps(updated_result, ensure_ascii=False)}
                """

        # 创建对话完成
        response = client.chat.completions.create(
            model="qwen-long",
            messages=[
                {'role': 'system', 'content': character},
                {'role': 'system', 'content': file_ids_str},
                {'role': 'user', 'content': prompt}
            ],
            stream=False  # 使用非流式返回
        )
        
        # 提取content内容
        content = response.choices[0].message.content.strip()
        
        # 尝试查找和提取JSON内容
        try:
            # 如果返回的不是纯JSON,尝试查找JSON部分
            start_idx = content.find('{')
            end_idx = content.rfind('}') + 1
            if start_idx != -1 and end_idx != 0:
                content = content[start_idx:end_idx]
            
            # 解析JSON
            portfolio_weights = json.loads(content)
            
            # 验证返回的数据包含所有原始股票
            if not all(stock in portfolio_weights for stock in updated_result):
                print("AI返回的数据不完整,使用原始权重")
                return updated_result
            
            # 对AI输出结果进行归一化
            weights_sum = sum(portfolio_weights.values())
            portfolio_weights = {key: value/weights_sum for key, value in portfolio_weights.items()}
            
            # 将字典中的值修改为6位小数
            portfolio_weights = {k: round(v, 6) for k, v in portfolio_weights.items()}
            
            return portfolio_weights
            
        except (json.JSONDecodeError, ValueError) as e:
            print(f"无法解析AI返回的内容: {str(e)}")
            print(f"原始返回内容: {content}")
            return updated_result
        
    except Exception as e:
        error_msg = str(e)
        if "content blank" in error_msg:
            print("文件内容提取失败,使用原始权重")
        else:
            print(f"API调用错误: {error_msg}")
        return updated_result
    
    finally:
        # 清理已上传的文件
        for file_id in file_ids:
            try:
                client.files.delete(file_id)
            except Exception:
                continue

3. 汇总代码

import tushare as ts
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
from scipy.optimize import minimize
import backtrader as bt
import statsmodels.api as sm
import os
import json
import dashscope

# 参数集##############################################################################
ts.set_token('token') # tushare接口
pro = ts.pro_api()
industry = '银行'
end_date = '20240101'
years = 5   # 数据时长
risk_free_rate = 0.03  # 无风险利率参数
top_holdings = 10      # 持仓数量参数
index_code = '000300.SH'  # 市场指数代码参数
api_key='sk-api_key'	# 通义千问API

character = f'''
你是一名专业的金融数据与研报信息分析师,擅长解读金融市场动态和研报信息,并据此调整资产组合的权重分布,以优化投资策略。你的主要任务是对给定的资产组合进行权重调整,确保:
1. 权重之和精确为1;
2. 每个资产调整后的权重只能在原有基础上增减最多10%;
3. 每个资产调整完毕后,如果权重之和不等于1,则归一化使权重之和精确为1;
4. 数据对应的日期是{end_date},在思考过程中,切勿根据该日期之后的信息进行思考。
5. 输出的数据格式需与输入保持一致,仅提供数据而不做额外解释;

当你接收到具体的资产组合及其权重时,请根据最新的研报信息对其进行合理调整。
'''

# 研报路径
path = f'/portfolio_code/reports/{end_date}'

# 参数集##############################################################################

def get_industry_stocks(industry):
    """获取指定行业的股票列表"""
    df = pro.stock_basic(fields=["ts_code", "name", "industry"])
    industry_stocks = df[df["industry"]==industry].copy()
    industry_stocks.sort_values(by='ts_code', inplace=True)
    industry_stocks.reset_index(drop=True, inplace=True)
    return industry_stocks['ts_code'].tolist()

def get_data(code_list, end_date, years):
    """获取指定行业名称的历史收盘价数据"""
    ts_code_list = code_list
    end_date_dt = datetime.strptime(end_date, '%Y%m%d')
    start_date_dt = end_date_dt - timedelta(days=years*365)
    start_date = start_date_dt.strftime('%Y%m%d')

    all_data = []
    for stock in ts_code_list:
        df = pro.daily(ts_code=stock, start_date=start_date, end_date=end_date)
        all_data.append(df)

    combined_df = pd.concat(all_data).sort_values(by=['ts_code', 'trade_date'])
    combined_df.reset_index(drop=True, inplace=True)
    combined_df.rename(columns={'trade_date': 'date'}, inplace=True)

    return combined_df

def get_market_data(index_code='000300.SH', start_date=None, end_date=None):
    """获取市场指数数据用于计算贝塔"""
    df_market = pro.index_daily(ts_code=index_code,
                              start_date=start_date,
                              end_date=end_date,
                              fields=['trade_date', 'close'])
    df_market['date'] = pd.to_datetime(df_market['trade_date'])
    df_market.set_index('date', inplace=True)
    df_market = df_market.sort_index()

    monthly_last_close = df_market['close'].resample('M').last()
    monthly_log_returns = np.log(monthly_last_close).diff().dropna()
    return monthly_log_returns

def get_factor_data(stock_codes, start_date=None, end_date=None):
    """获取指定股票的因子数据(市值和PB)"""
    all_factor_data = []
    for stock in stock_codes:
        try:
            df = pro.daily_basic(
                ts_code=stock,
                start_date=start_date,
                end_date=end_date,
                fields=['ts_code', 'trade_date', 'total_mv', 'pb']
            )
            all_factor_data.append(df)
        except Exception as e:
            print(f"获取股票 {stock} 的因子数据失败: {str(e)}")
            continue

    factor_data = pd.concat(all_factor_data, ignore_index=True)
    factor_data['trade_date'] = pd.to_datetime(factor_data['trade_date'])
    return factor_data

def get_fina_data(stock_codes, start_date=None, end_date=None):
    """获取指定股票的财务指标数据(ROE和资产增长率)"""
    all_fina_data = []
    for stock in stock_codes:
        try:
            df = pro.fina_indicator(
                ts_code=stock,
                start_date=start_date,
                end_date=end_date,
                fields=['ts_code', 'end_date', 'roe_dt', 'assets_yoy', 'update_flag']
            )
            all_fina_data.append(df)
        except Exception as e:
            print(f"获取股票 {stock} 的财务数据失败: {str(e)}")
            continue

    # 合并数据
    fina_data = pd.concat(all_fina_data, ignore_index=True)

    # 处理update_flag,保留最新数据
    fina_data = (fina_data.groupby(['ts_code', 'end_date'])
                         .agg({'roe_dt': 'first',
                              'assets_yoy': 'first',
                              'update_flag': 'max'})
                         .reset_index())

    # 将end_date转换为datetime
    fina_data['end_date'] = pd.to_datetime(fina_data['end_date'])

    # 创建季度到月度的映射
    monthly_data = []
    for _, row in fina_data.iterrows():
        quarter_end = row['end_date']
        if quarter_end.month == 3:  # Q1
            months = [quarter_end + pd.DateOffset(months=i) for i in range(1, 4)]
        elif quarter_end.month == 6:  # Q2
            months = [quarter_end + pd.DateOffset(months=i) for i in range(1, 4)]
        elif quarter_end.month == 9:  # Q3
            months = [quarter_end + pd.DateOffset(months=i) for i in range(1, 4)]
        else:  # Q4
            months = [quarter_end + pd.DateOffset(months=i) for i in range(1, 4)]

        for month in months:
            monthly_data.append({
                'ts_code': row['ts_code'],
                'trade_date': month,
                'roe_dt': row['roe_dt'],
                'assets_yoy': row['assets_yoy']
            })

    monthly_df = pd.DataFrame(monthly_data)
    return monthly_df

def calculate_monthly_log_returns(df):
    """计算每月的对数收益率"""
    df['date'] = pd.to_datetime(df['date'])
    monthly_last_close = df.groupby(['ts_code', pd.Grouper(key='date', freq='M')])['close'].last().unstack(level=-1)
    monthly_log_returns = np.log(monthly_last_close).diff().dropna()
    return monthly_log_returns.T

def calculate_expected_returns(monthly_log_returns):
    """使用Fama-French五因子模型计算各股票的预期收益率"""
    start_date = monthly_log_returns.index.min().strftime('%Y%m%d')
    end_date = monthly_log_returns.index.max().strftime('%Y%m%d')

    # 获取财务数据时,将start_date往前推一个季度,以确保有完整的季度数据
    fina_start_date = (datetime.strptime(start_date, '%Y%m%d') - timedelta(days=90)).strftime('%Y%m%d')

    # 获取市场收益率
    market_returns = get_market_data(index_code, start_date, end_date)

    # 获取股票的市值和PB数据
    stock_data = get_factor_data(
        monthly_log_returns.columns.tolist(),
        start_date,
        end_date
    )

    # 获取财务指标数据,使用提前的start_date
    fina_data = get_fina_data(
        monthly_log_returns.columns.tolist(),
        fina_start_date,
        end_date
    )

    # 确保所有数据的日期对齐
    aligned_dates = monthly_log_returns.index.intersection(market_returns.index)
    market_returns = market_returns[aligned_dates]
    stock_returns = monthly_log_returns.loc[aligned_dates].copy()  # 使用copy()避免SettingWithCopyWarning

    def calculate_size_factor(date):
        date_data = stock_data[stock_data['trade_date'].dt.to_period('M') == date.to_period('M')]
        median_mv = date_data['total_mv'].median()
        small_returns = stock_returns.loc[date, date_data[date_data['total_mv'] <= median_mv]['ts_code']]
        big_returns = stock_returns.loc[date, date_data[date_data['total_mv'] > median_mv]['ts_code']]
        return small_returns.mean() - big_returns.mean()

    def calculate_value_factor(date):
        date_data = stock_data[stock_data['trade_date'].dt.to_period('M') == date.to_period('M')]
        # 创建date_data的副本并计算bm_ratio
        date_data = date_data.copy()
        date_data.loc[:, 'bm_ratio'] = 1 / date_data['pb']

        median_bm = date_data['bm_ratio'].median()
        high_returns = stock_returns.loc[date, date_data[date_data['bm_ratio'] > median_bm]['ts_code']]
        low_returns = stock_returns.loc[date, date_data[date_data['bm_ratio'] <= median_bm]['ts_code']]
        return high_returns.mean() - low_returns.mean()

    def calculate_profitability_factor(date):
        date_data = fina_data[fina_data['trade_date'].dt.to_period('M') == date.to_period('M')]

        median_roe = date_data['roe_dt'].median()
        robust_returns = stock_returns.loc[date, date_data[date_data['roe_dt'] > median_roe]['ts_code']]
        weak_returns = stock_returns.loc[date, date_data[date_data['roe_dt'] <= median_roe]['ts_code']]
        return robust_returns.mean() - weak_returns.mean()

    def calculate_investment_factor(date):
        date_data = fina_data[fina_data['trade_date'].dt.to_period('M') == date.to_period('M')]

        median_growth = date_data['assets_yoy'].median()
        conservative_returns = stock_returns.loc[date, date_data[date_data['assets_yoy'] <= median_growth]['ts_code']]
        aggressive_returns = stock_returns.loc[date, date_data[date_data['assets_yoy'] > median_growth]['ts_code']]
        return conservative_returns.mean() - aggressive_returns.mean()

    # 计算每个月的因子收益
    smb_factor = pd.Series([calculate_size_factor(date) for date in aligned_dates], index=aligned_dates)
    hml_factor = pd.Series([calculate_value_factor(date) for date in aligned_dates], index=aligned_dates)
    rmw_factor = pd.Series([calculate_profitability_factor(date) for date in aligned_dates], index=aligned_dates)
    cma_factor = pd.Series([calculate_investment_factor(date) for date in aligned_dates], index=aligned_dates)

    # 使用OLS回归计算每个股票的因子载荷
    factor_loadings = {}
    for stock in stock_returns.columns:
        X = sm.add_constant(pd.concat([
            market_returns - risk_free_rate,
            smb_factor,
            hml_factor,
            rmw_factor,
            cma_factor
        ], axis=1))
        y = stock_returns[stock] - risk_free_rate

        model = sm.OLS(y, X).fit()
        factor_loadings[stock] = model.params[1:]

    # 计算因子风险溢价
    market_premium = market_returns.mean() - risk_free_rate
    smb_premium = smb_factor.mean()
    hml_premium = hml_factor.mean()
    rmw_premium = rmw_factor.mean()
    cma_premium = cma_factor.mean()

    # 使用FF5模型计算预期收益率
    expected_returns = pd.Series({
        stock: (risk_free_rate +
                loadings.iloc[0] * market_premium +
                loadings.iloc[1] * smb_premium +
                loadings.iloc[2] * hml_premium +
                loadings.iloc[3] * rmw_premium +
                loadings.iloc[4] * cma_premium)
        for stock, loadings in factor_loadings.items()
    })

    return expected_returns

def calculate_covariance_matrix(monthly_log_returns):
    """计算收益率协方差矩阵"""
    return monthly_log_returns.cov()

def portfolio_performance(weights, mean_returns, cov_matrix):
    """计算投资组合的表现"""
    returns = np.sum(mean_returns * weights)
    std_dev = np.sqrt(np.dot(weights.T, np.dot(cov_matrix, weights)))
    return returns, std_dev

def negative_sharpe_ratio(weights, mean_returns, cov_matrix, risk_free_rate):
    """计算负夏普比率"""
    p_ret, p_std = portfolio_performance(weights, mean_returns, cov_matrix)
    sharpe_ratio = (p_ret - risk_free_rate) / p_std
    return -sharpe_ratio

def max_sharpe_ratio(mean_returns, cov_matrix, risk_free_rate):
    """计算最大夏普比率的投资组合权重"""
    num_assets = len(mean_returns)
    args = (mean_returns, cov_matrix, risk_free_rate)
    constraints = ({'type': 'eq', 'fun': lambda x: np.sum(x) - 1})
    bounds = tuple((0, 1) for asset in range(num_assets))
    result = minimize(negative_sharpe_ratio, num_assets*[1./num_assets], args=args,
                      method='SLSQP', bounds=bounds, constraints=constraints)
    return result.x

def calculate_top_holdings_weights(optimal_weights, monthly_log_returns_columns, top_n):
  """计算前N大持仓的权重占比"""
  result_dict = {asset: weight for asset, weight in zip(monthly_log_returns_columns, optimal_weights)}
  top_n_holdings = sorted(result_dict.items(), key=lambda item: item[1], reverse=True)[:top_n]
  top_n_sum = sum(value for _, value in top_n_holdings)
  updated_result = {key: value / top_n_sum for key, value in top_n_holdings}
  return updated_result

def get_ai_weights(character, path, updated_result, api_key):
    """
    使用AI分析指定路径下的所有PDF报告内容
    
    Args:
        character (str): AI的角色设定
        path (str): 报告所在目录的路径(会被转换为绝对路径)
        updated_result (dict): 用户角色向AI发送的资产组合权重
        api_key (str): DashScope API密钥
        
    Returns:
        dict: AI调整后的投资组合权重
    """
    # 初始化 OpenAI 客户端
    client = OpenAI(
        api_key=api_key,
        base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
    )
    
    # 将路径转换为绝对路径
    abs_path = os.path.abspath(path)
    report_dir = Path(abs_path)
    
    if not report_dir.exists():
        try:
            report_dir.mkdir(parents=True, exist_ok=True)
        except Exception as e:
            raise ValueError(f"创建目录失败: {report_dir}, 错误: {str(e)}")
        return updated_result  # 如果目录不存在,返回原始权重
    
    # 获取所有PDF文件并上传
    file_ids = []
    pdf_files = list(report_dir.glob("*.pdf"))
    
    if not pdf_files:
        return updated_result  # 如果没有PDF文件,返回原始权重
    
    # 检查文件是否可读且非空
    valid_pdf_files = []
    for pdf_file in pdf_files:
        try:
            if pdf_file.stat().st_size > 0:  # 检查文件大小
                valid_pdf_files.append(pdf_file)
        except Exception:
            continue
    
    if not valid_pdf_files:
        return updated_result  # 如果没有有效文件,返回原始权重
    
    # 上传有效的PDF文件
    for pdf_file in valid_pdf_files:
        try:
            file_object = client.files.create(
                file=pdf_file,
                purpose="file-extract"
            )
            file_ids.append(file_object.id)
        except Exception:
            continue
    
    if not file_ids:
        return updated_result  # 如果文件上传失败,返回原始权重
    
    # 构建file_ids字符串
    file_ids_str = ",".join([f"fileid://{file_id}" for file_id in file_ids])
    
    try:
        # 构建更明确的提示信息
        prompt = f"""请分析这些研报,并基于分析结果对以下投资组合进行调整。
                请注意:
                1. 只需返回调整后的权重数据,格式必须与输入完全一致;
                2. 不要添加任何解释或说明;
                3. 确保返回的是有效的JSON格式。

                当前投资组合:
                {json.dumps(updated_result, ensure_ascii=False)}
                """

        # 创建对话完成
        response = client.chat.completions.create(
            model="qwen-long",
            messages=[
                {'role': 'system', 'content': character},
                {'role': 'system', 'content': file_ids_str},
                {'role': 'user', 'content': prompt}
            ],
            stream=False  # 使用非流式返回
        )
        
        # 提取content内容
        content = response.choices[0].message.content.strip()
        
        # 尝试查找和提取JSON内容
        try:
            # 如果返回的不是纯JSON,尝试查找JSON部分
            start_idx = content.find('{')
            end_idx = content.rfind('}') + 1
            if start_idx != -1 and end_idx != 0:
                content = content[start_idx:end_idx]
            
            # 解析JSON
            portfolio_weights = json.loads(content)
            
            # 验证返回的数据包含所有原始股票
            if not all(stock in portfolio_weights for stock in updated_result):
                print("AI返回的数据不完整,使用原始权重")
                return updated_result
            
            # 对AI输出结果进行归一化
            weights_sum = sum(portfolio_weights.values())
            portfolio_weights = {key: value/weights_sum for key, value in portfolio_weights.items()}
            
            # 将字典中的值修改为6位小数
            portfolio_weights = {k: round(v, 6) for k, v in portfolio_weights.items()}
            
            return portfolio_weights
            
        except (json.JSONDecodeError, ValueError) as e:
            print(f"无法解析AI返回的内容: {str(e)}")
            print(f"原始返回内容: {content}")
            return updated_result
        
    except Exception as e:
        error_msg = str(e)
        if "content blank" in error_msg:
            print("文件内容提取失败,使用原始权重")
        else:
            print(f"API调用错误: {error_msg}")
        return updated_result
    
    finally:
        # 清理已上传的文件
        for file_id in file_ids:
            try:
                client.files.delete(file_id)
            except Exception:
                continue

def main():
	# 获取数据
	code_list = get_industry_stocks(industry)
	df = get_data(code_list, end_date, years)
	
	# 计算每月的对数收益率
	monthly_log_returns = calculate_monthly_log_returns(df)
	
	# 使用FF5模型计算预期收益率
	mean_returns = calculate_expected_returns(monthly_log_returns)
	
	# 计算收益率协方差矩阵
	cov_matrix = calculate_covariance_matrix(monthly_log_returns)
	
	# 优化权重
	optimal_weights = max_sharpe_ratio(mean_returns, cov_matrix, risk_free_rate)
	
	# 计算前N大持仓权重
	updated_result = calculate_top_holdings_weights(
	    optimal_weights,
	    monthly_log_returns.columns,
	    top_holdings
	)
	
	# 计算AI调仓后的持仓权重
	updated_result = get_ai_weights(character, path, updated_result, api_key)
	
	# 打印更新后的资产占比
	print(f"\n{end_date}最优资产前{top_holdings}占比:")
	print(updated_result)

if __name__ == "__main__":
	main()

注意:tushare接口、通义千问API需要自行申请。

运行结果:

金融研报AI长文本智能体(Qwen-Long)体调仓后权重:

{'601398.SH': 0.188942, '601328.SH': 0.166264, '600919.SH': 0.118216, '600036.SH': 0.103397, '601169.SH': 0.090905, 
'600016.SH': 0.085375, '601166.SH': 0.083199, '601288.SH': 0.068825, '600908.SH': 0.049392, '600926.SH': 0.045485}

与前两篇Qwen-Max、DeepSeek-V3进行对比:

股票代码股票占比(Qwen-Max)股票占比(DeepSeek-V3)股票占比(Qwen-Long)
601398.SH0.1630250.1665250.188942
601328.SH0.1616230.1651650.166264
600919.SH0.1292520.1340110.118216
600036.SH0.1073720.1129550.103397
601169.SH0.0957640.0921570.090905
600016.SH0.0900460.0866630.085375
601166.SH0.0876060.0843160.083199
601288.SH0.0653230.0628680.068825
600908.SH0.0555410.0534540.049392
600926.SH0.0444490.0418850.045485

Qwen-Long的股票权重配置是基于对大量研究报告(研报)的深入分析和综合考量的结果。这种策略不仅反映了模型在处理复杂信息和识别市场趋势方面的强大能力,还体现了其对宏观经济环境、行业动态以及公司特定风险因素的全面理解。

其他尝试:

  • 向AI提供研报文件的同时,提供更多的因子数据
  • 使用RAG技术,让AI更加全面了解研报文件内容

4. 反思

4.1 不足之处

  1. 研报文件获取:获取研报文件方案仍为半手动
  2. AI逻辑缜密度:AI可能未能完全按照提示词工程执行

4.2 提升思路

  1. 更换提示词工程
  2. 工作流接入金融工程内部,实现真正全自动

5. 启后

  • 优化,:,可参考下一篇文章:
    pass

  • 量化回测实现,可参考下一篇文章:
    pass

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/962841.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Linux网络 HTTP cookie 与 session

Cookie 定义与功能&#xff1a;Cookie是服务器发送到用户浏览器并保存在本地的一小块数据&#xff0c;它会在浏览器下次向同一服务器再发起请求时被携带并发送到服务器上。通常&#xff0c;它用于告知服务端两个请求是否来自同一浏览器&#xff0c;如保持用户的登录状态、记录…

BW AO/工作簿权限配置

场景&#xff1a; 按事业部配置工作簿权限&#xff1b; 1、创建用户 事务码&#xff1a;SU01&#xff0c;用户主数据的维护&#xff0c;可以创建、修改、删除、锁定、解锁、修改密码等 用户设置详情页 2、创建权限角色 用户的权限菜单是通过权限角色分配来实现的 2.1、自定…

Python之Excel操作 - 写入数据

我们将使用 openpyxl 库&#xff0c;它是一个功能强大且易于使用的库&#xff0c;专门用于处理 Excel 文件。 1. 安装 openpyxl 首先&#xff0c;你需要安装 openpyxl 库。你可以使用 pip 命令进行安装&#xff1a; pip install openpyxl创建一个文件 example.xlsx&#xff…

【后端开发】字节跳动青训营之性能分析工具pprof

性能分析工具pprof 一、测试程序介绍二、pprof工具安装与使用2.1 pprof工具安装2.2 pprof工具使用 资料链接&#xff1a; 项目代码链接实验指南pprof使用指南 一、测试程序介绍 package mainimport ("log""net/http"_ "net/http/pprof" // 自…

2025开源DouyinLiveRecorder全平台直播间录制工具整合包,多直播同时录制、教学直播录制、教学视频推送、简单易用不占内存

一、DouyinLiveRecorder软件介绍&#xff08;文末提供下载&#xff09; 官方地址&#xff1a;GitHub - ihmily/DouyinLiveRecorder 本文信息来源于作者GitHub地址 一款简易的可循环值守的直播录制工具&#xff0c;基于FFmpeg实现多平台直播源录制&#xff0c;支持自定义配置录制…

大数据学习之SCALA分布式语言三

7.集合类 111.可变set一 112.可变set二 113.不可变MAP集合一 114.不可变MAP集合二 115.不可变MAP集合三 116.可变map一 package com . itbaizhan . chapter07 //TODO 2. 使用 mutable.Map 前导入如下包 import scala . collection . mutable // 可变 Map 集合 object Ma…

RAG是否被取代(缓存增强生成-CAG)吗?

引言&#xff1a; 本文深入研究一种名为缓存增强生成&#xff08;CAG&#xff09;的新技术如何工作并减少/消除检索增强生成&#xff08;RAG&#xff09;弱点和瓶颈。 LLMs 可以根据输入给他的信息给出对应的输出&#xff0c;但是这样的工作方式很快就不能满足应用的需要: 因…

使用 Tauri 2 + Next.js 开发跨平台桌面应用实践:Singbox GUI 实践

Singbox GUI 实践 最近用 Tauri Next.js 做了个项目 - Singbox GUI&#xff0c;是个给 sing-box 用的图形界面工具。支持 Windows、Linux 和 macOS。作为第一次接触这两个框架的新手&#xff0c;感觉收获还蛮多的&#xff0c;今天来分享下开发过程中的一些经验~ 为啥要做这个…

三甲医院大型生信服务器多配置方案剖析与应用(2024版)

一、引言 1.1 研究背景与意义 在当今数智化时代&#xff0c;生物信息学作为一门融合生物学、计算机科学和信息技术的交叉学科&#xff0c;在三甲医院的科研和临床应用中占据着举足轻重的地位。随着高通量测序技术、医学影像技术等的飞速发展&#xff0c;生物医学数据呈爆发式…

2025_2_1 C语言中关于字符串

1.字符串 C语言中的字符串都是字符数组&#xff0c;以空字符 ‘\0’结尾。 创建一个字符数组必须以空字符结尾&#xff0c;不然会访问非法区域&#xff0c;直到找到\0为止 char c[] {a, b, c, \0};长度为n的字符串字面值&#xff0c;会存储在虚拟内存中的只读数据段中&#…

Redis篇 Redis如何清理过期的key以及对应的解决方法

Redis设置Key过期时间 在 Redis 中&#xff0c;可以通过特定的命令为 Key 设置过期时间&#xff0c;使得 Key 在一定时间后自动删除&#xff0c;这对于管理缓存、验证码等临时数据非常有用。 解决方法 1. Redis过期删除策略 1.1 如何实现过期策略 对一个 key 设置了过期时间…

java练习(1)

两数之和&#xff08;题目来自力扣&#xff09; 给定一个整数数组 nums 和一个整数目标值 target&#xff0c;请你在该数组中找出 和为目标值 target 的那 两个 整数&#xff0c;并返回它们的数组下标。 你可以假设每种输入只会对应一个答案&#xff0c;并且你不能使用两次相…

Python3 OS模块中的文件/目录方法说明十四

一. 简介 前面文章简单学习了 Python3 中 OS模块中的文件/目录的部分函数。 本文继续来学习 OS 模块中文件、目录的操作方法&#xff1a;os.statvfs() 方法&#xff0c;os.symlink() 方法。 二. Python3 OS模块中的文件/目录方法 1. os.statvfs() 方法 os.statvfs() 方法用…

从理论到实践:Linux 进程替换与 exec 系列函数

个人主页&#xff1a;chian-ocean 文章专栏-Linux 前言&#xff1a; 在Linux中&#xff0c;进程替换&#xff08;Process Substitution&#xff09;是一个非常强大的特性&#xff0c;它允许将一个进程的输出直接当作一个文件来处理。这种技术通常用于Shell脚本和命令行操作中…

ZZNUOJ(C/C++)基础练习1041——1050(详解版)

1041 : 数列求和2 题目描述 输入一个整数n&#xff0c;输出数列1-1/31/5-……前n项的和。 输入 输入只有一个整数n。 输出 结果保留2为小数,单独占一行。 样例输入 3 样例输出 0.87注意sum 1相当于sumsum1 注意sum * 1相当于sumsum*1 C语言版 #include<stdio.h> // 包含…

2021 年 6 月大学英语四级考试真题(第 2 套)——纯享题目版

&#x1f3e0;个人主页&#xff1a;fo安方的博客✨ &#x1f482;个人简历&#xff1a;大家好&#xff0c;我是fo安方&#xff0c;目前中南大学MBA在读&#xff0c;也考取过HCIE Cloud Computing、CCIE Security、PMP、CISP、RHCE、CCNP RS、PEST 3等证书。&#x1f433; &…

【Block总结】CPCA,通道优先卷积注意力|即插即用

论文信息 标题: Channel Prior Convolutional Attention for Medical Image Segmentation 论文链接: arxiv.org 代码链接: GitHub 创新点 本文提出了一种新的通道优先卷积注意力&#xff08;CPCA&#xff09;机制&#xff0c;旨在解决医学图像分割中存在的低对比度和显著…

grpc 和 http 的区别---二进制vsJSON编码

gRPC 和 HTTP 是两种广泛使用的通信协议&#xff0c;各自适用于不同的场景。以下是它们的详细对比与优势分析&#xff1a; 一、核心特性对比 特性gRPCHTTP协议基础基于 HTTP/2基于 HTTP/1.1 或 HTTP/2数据格式默认使用 Protobuf&#xff08;二进制&#xff09;通常使用 JSON/…

Qt常用控件 输入类控件

文章目录 1.QLineEdit1.1 常用属性1.2 常用信号1.3 例子1&#xff0c;录入用户信息1.4 例子2&#xff0c;正则验证手机号1.5 例子3&#xff0c;验证输入的密码1.6 例子4&#xff0c;显示密码 2. QTextEdit2.1 常用属性2.2 常用信号2.3 例子1&#xff0c;获取输入框的内容2.4 例…

[b01lers2020]Life on Mars1

打开题目页面如下 看了旁边的链接&#xff0c;也没有什么注入点&#xff0c;是正常的科普 利用burp suite抓包&#xff0c;发现传参 访问一下 http://5edaec92-dd87-4fec-b0e3-501ff24d3650.node5.buuoj.cn:81/query?searchtharsis_rise 接下来进行sql注入 方法一&#xf…