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

news2025/2/1 11:20:32

目录

    • 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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

安装Maven(安装包+步骤)

1. 安装: 通过网盘分享的文件&#xff1a;apache-maven-3.9.9 链接: https://pan.baidu.com/s/16AE_brICuw6sS0tC6tmE1Q?pwda74r 提取码: a74r --来自百度网盘超级会员v3的分享 2.新建应该系统变量: 3.path中添加bin文件夹路径 4.建议在这里建一个仓库文件夹 博主的: 5.I…

【云安全】云原生-K8S-搭建/安装/部署

一、准备3台虚拟机 务必保证3台是同样的操作系统&#xff01; 1、我这里原有1台centos7&#xff0c;为了节省资源和效率&#xff0c;打算通过“创建链接克隆”2台出来 2、克隆之前&#xff0c;先看一下是否存在k8s相关组件&#xff0c;或者docker相关组件 3、卸载原有的docker …

单细胞-第四节 多样本数据分析,下游画图

文件在单细胞\5_GC_py\1_single_cell\2_plots.Rmd 1.细胞数量条形图 rm(list ls()) library(Seurat) load("seu.obj.Rdata")dat as.data.frame(table(Idents(seu.obj))) dat$label paste(dat$Var1,dat$Freq,sep ":") head(dat) library(ggplot2) lib…

【算法】动态规划专题① ——线性DP python

目录 引入简单实现稍加变形举一反三实战演练总结 引入 楼梯有个台阶&#xff0c;每次可以一步上1阶或2阶。一共有多少种不同的上楼方法&#xff1f; 怎么去思考&#xff1f; 假设就只有1个台阶&#xff0c;走法只有&#xff1a;1 只有2台阶&#xff1a; 11&#xff0c;2 只有3台…

知识管理平台在数字经济时代推动企业智慧决策与知识赋能的路径分析

内容概要 在数字经济时代&#xff0c;知识管理平台被视为企业智慧决策与知识赋能的关键工具。其核心作用在于通过高效地整合、存储和分发企业内部的知识资源&#xff0c;促进信息的透明化与便捷化&#xff0c;使得决策者能够在瞬息万变的市场环境中迅速获取所需信息。这不仅提…

LabVIEW微位移平台位移控制系统

本文介绍了基于LabVIEW的微位移平台位移控制系统的研究。通过设计一个闭环控制系统&#xff0c;针对微位移平台的通信驱动问题进行了解决&#xff0c;并提出了一种LabVIEW的应用方案&#xff0c;用于监控和控制微位移平台的位移&#xff0c;从而提高系统的精度和稳定性。 项目背…

java求职学习day23

MySQL 单表 & 约束 & 事务 1. DQL操作单表 1.1 创建数据库,复制表 1) 创建一个新的数据库 db2 CREATE DATABASE db2 CHARACTER SET utf8; 2) 将 db1 数据库中的 emp 表 复制到当前 db2 数据库 1.2 排序 通过 ORDER BY 子句 , 可以将查询出的结果进行排序 ( 排序只…

22.Word:小张-经费联审核结算单❗【16】

目录 NO1.2 NO3.4​ NO5.6.7 NO8邮件合并 MS搜狗输入法 NO1.2 用ms打开文件&#xff0c;而不是wps❗不然后面都没分布局→页面设置→页面大小→页面方向→上下左右&#xff1a;页边距→页码范围&#xff1a;多页&#xff1a;拼页光标处于→布局→分隔符&#xff1a;分节符…

MYSQL 商城系统设计 商品数据表的设计 商品 商品类别 商品选项卡 多表查询

介绍 在开发商品模块时&#xff0c;通常使用分表的方式进行查询以及关联。在通过表连接的方式进行查询。每个商品都有不同的分类&#xff0c;每个不同分类下面都有商品规格可以选择&#xff0c;每个商品分类对应商品规格都有自己的价格和库存。在实际的开发中应该给这些表进行…

python3+TensorFlow 2.x(二) 回归模型

目录 回归算法 1、线性回归 (Linear Regression) 一元线性回归举例 2、非线性回归 3、回归分类 回归算法 回归算法用于预测连续的数值输出。回归分析的目标是建立一个模型&#xff0c;以便根据输入特征预测目标变量&#xff0c;在使用 TensorFlow 2.x 实现线性回归模型时&…

Android --- CameraX讲解

预备知识 surface surfaceView SurfaceHolder surface 是什么&#xff1f; 一句话来说&#xff1a; surface是一块用于填充图像数据的内存。 surfaceView 是什么&#xff1f; 它是一个显示surface 的View。 在app中仍在 ViewHierachy 中&#xff0c;但在wms 中可以理解为…

CentOS7非root用户离线安装Docker及常见问题总结、各种操作系统docker桌面程序下载地址

环境说明 1、安装用户有sudo权限 2、本文讲docker组件安装&#xff0c;不是桌面程序安装 3、本文讲离线安装&#xff0c;不是在线安装 4、目标机器是内网机器&#xff0c;与外部网络不连通 下载 1、下载离线安装包&#xff0c;并上传到$HOME/basic-tool 目录 下载地址&am…

笔记本搭配显示器

笔记本&#xff1a;2022款拯救者Y9000P&#xff0c;显卡RTX3060&#xff0c;分辨率2560*1600&#xff0c;刷新率&#xff1a;165Hz&#xff0c;无DP1.4口 显示器&#xff1a;2024款R27Q&#xff0c;27存&#xff0c;分辨率2560*1600&#xff0c;刷新率&#xff1a;165Hz &…

DeepSeek R1 linux云部署

云平台&#xff1a;AutoDL 模型加载工具&#xff1a;Ollama 参考&#xff1a;https://github.com/ollama/ollama/blob/main/docs/linux.md 下载Ollama 服务器上下载ollama比较慢&#xff0c;因此我使用浏览器先下载到本地电脑上。 https://ollama.com/download/ollama-linux…

UE5.3 C++ CDO的初步理解

一.UObject UObject是所有对象的基类&#xff0c;往上还有UObjectBaseUtility。 注释&#xff1a;所有虚幻引擎对象的基类。对象的类型由基于 UClass 类来定义。 这为创建和使用UObject的对象提供了 函数&#xff0c;并且提供了应在子类中重写的虚函数。 /** * The base cla…

在排序数组中查找元素的第一个和最后一个位置(力扣)

一.题目介绍 二.题目解析 使用二分进行查找 2.1处理边界情况 如果数组为空&#xff0c;直接返回 [-1, -1]&#xff0c;因为无法找到目标值。 int[] ret new int[2]; ret[0] ret[1] -1; if (nums.length 0) return ret; 2.2查找左端点&#xff08;目标值开始位置&#…

Native Memory Tracking 与 RSS的差异问题

一 问题现象 前一段时间用nmt查看jvm进程的栈区占用的内存大小。测试代码如下 public class ThreadOOM {public static void main(String[] args) {int i 1;while (i < 3000) {Thread thread new TestThread();thread.start();System.out.println("thread : "…

知识库管理如何推动企业数字化转型与创新发展的深层次探索

内容概要 在当今数字化转型的大背景下&#xff0c;知识库管理日益显现出其作为企业创新发展的核心驱动力的潜力。这种管理方式不仅仅是对信息的存储与检索&#xff0c;更是一种赋能&#xff0c;以提升决策效率和员工创造力。企业能够通过系统地整合和管理各类知识资源&#xf…

《DeepSeek 网页/API 性能异常(DeepSeek Web/API Degraded Performance):网络安全日志》

DeepSeek 网页/API 性能异常&#xff08;DeepSeek Web/API Degraded Performance&#xff09;订阅 已识别 - 已识别问题&#xff0c;并且正在实施修复。 1月 29&#xff0c; 2025 - 20&#xff1a;57 CST 更新 - 我们将继续监控任何其他问题。 1月 28&#xff0c; 2025 - 22&am…

DeepSeek-R1本地部署笔记

文章目录 效果概要下载 ollama终端下载模型【可选】浏览器插件 UIQ: 内存占用高&#xff0c;显存占用不高&#xff0c;正常吗 效果 我的配置如下 E5 2666 V3 AMD 590Gme 可以说是慢的一批了&#xff0c;内存和显卡都太垃圾了&#xff0c;回去用我的新设备再试试 概要 安装…