Skip to content

借助 OpenAI 打造股票分析利器,探索科技股投资之道

作者:老余捞鱼

原创不易,转载请标明出处及原作者。

写在前面的话:本文提供了一个分步指南,指导大家如何利用 OpenAI GPT-4 模型和 雅虎财经 yfinance 库比较不同股票的投资价值,并通过异步请求加快处理速度,最终构建一个基于 Gradio 的 Web UI 应用程序。

在本教程中,我将介绍如何利用 GPT-4 比较潜在的股票购买。它基于 Matt Shumer 使用Claude 进行金融分析的代码。本教程介绍了对该策略的修改,以便与 OpenAI API 配合使用。我们还实现了异步请求,这一改动大大加快了处理速度。我们使用的主要 python 库是:

  • yfinance: Yahoo finance 是一个 python 库,提供线程和 Pythonic 方式从雅虎财经下载市场数据。

首先,为了安装所需的库,我们创建一个 requirements.txt 文件:

yfinance
requests
beautifulsoup4
openai
python-dotenv
gradio

然后,我们就可以创建一个新的 conda 环境并安装库:

conda create --name gptinvestor python=3.10
conda activate gptinvestor
pip install -r requirements.txt

程序包括几个连续的函数调用,首先通过 yfinance 库获取股票代码数据(例如微软的 MSFT)。数据检索完成后,程序会将这些信息转发给 OpenAI API 进行分析。以下是该程序的工作流程概览:


1. 基础代码

我们首先导入所需的 python 库:

from dataclasses import dataclass, field
from typing import List, Dict, Optional
import pandas as pd
import asyncio
import requests
from bs4 import BeautifulSoup
from datetime import datetime, timedelta

from openai import OpenAI, AsyncOpenAI
from dotenv import load_dotenv
import yfinance as yf

接下来,我们创建两个 OpenAI 客户端,一个用于异步调用,另一个用于最后的同步调用,在同步调用中,我们希望 GPT 为我们对股票进行排名。

load_dotenv("env_variables.env")
syncclient = OpenAI()
asyncclient = AsyncOpenAI()

注意:您必须在运行代码的同一目录下创建名为 env_variables.env 的文件,并在其中放入 OpenAI API 密钥:

OPENAI_API_KEY=abcdefghijk12345

load_dotenv(“env_variables.env”) 会将你的 OpenAI API 密钥作为环境变量即时加载。

接下来,我们为每个股票代码定义一个数据类,并将该类命名为 TickerClass。该数据类用于存储每个股票代码(4 个字母的股票表示法)的不同信息。

我们引入了一个名为 TickerClass的数据类,用于保存每个股票代码的不同详细信息,这些代码由四个字母组成(如 MSFT 表示 Microsoft)。该类作为一个结构化容器,用于存储每只股票的相关信息,包括历史数据、资产负债表、分析师评级和价格等。此外,它还包括情感分析、行业分析和最终分析字段,这些字段是根据 OpenAI API 的结果填充的。

@dataclass
class TickerClass:
    name: str
    hist_data: Optional[pd.DataFrame] = field(default=None)
    balance_sheet: Optional[pd.DataFrame] = field(default=None)
    financials: Optional[pd.DataFrame] = field(default=None)
    news: Optional[Dict] = field(default=None)
    analyst_ratings: str = field(default=None)
    price: float = field(default=None)
    ######
    sentiment_analysis: str = field(default=None)
    industry_analysis: str = field(default=None)
    final_analysis: str = field(default=None)

通过采用这种类别,我们可以确保每只股票的所有关键细节和分析见解都被整齐地组织起来,并且易于访问,从而简化了我们在整个程序中处理和检索数据的方式。

接下来,我们定义使用 yfinance 库获取每只股票原始信息的函数:

def get_article_text(url: str) -> str:
    try:
        response = requests.get(url)
        soup = BeautifulSoup(response.content, 'html.parser')
        article_text = ' '.join([p.get_text() for p in soup.find_all('p')])
        return article_text
    except:
        return "Error retrieving article text."

def get_stock_data(ticker: str, years: int=10) -> tuple:
    end_date = datetime.now().date()
    start_date = end_date - timedelta(days=years*365)

    stock = yf.Ticker(ticker)

    # Retrieve historical price data
    hist_data = stock.history(start=start_date, end=end_date)

    # Retrieve balance sheet
    balance_sheet = stock.balance_sheet

    # Retrieve financial statements
    financials = stock.financials

    # Retrieve news articles
    news = stock.news

    return hist_data, balance_sheet, financials, news

def get_analyst_ratings(ticker: str) -> str:
    stock = yf.Ticker(ticker)
    recommendations = stock.get_recommendations()
    if recommendations is None or recommendations.empty:
        return "No analyst ratings available."

    latest_rating = recommendations.iloc[0]


    rating_summary = f"Latest analyst rating for {ticker}:\n {str(latest_rating.to_dict())}"

    return rating_summary

def get_current_price(ticker :str) -> float:
    stock = yf.Ticker(ticker)
    data = stock.history(period='1d')
    return data.iloc[0]['Close']

get_stock_data 中,我们检索每只股票的历史价格数据、资产负债表、财务信息和最新消息。在 get_analyst_ratings 函数中,我们接收每只股票的最新分析师评级。分析师评级根据金融分析师的建议对股票进行分类,例如建议 “强力买入”、”买入”、”持有 “等的数量。最后,get_current_price会获取股票的当前价格。

2. OpenAI 调用函数

接下来,我们将定义用于向 OpenAI gpt 模型发送每只股票信息的函数,并将其用于分析。

async def get_sentiment_analysis(ticker: TickerClass) -> None:
    print(f"analyzing sentiment for {ticker.name}")

    news_text = ""
    for article in ticker.news:
        article_text = get_article_text(article['link'])
        timestamp = datetime.fromtimestamp(article['providerPublishTime']).strftime("%Y-%m-%d")
        news_text += f"\n\n---\n\nDate: {timestamp}\nTitle: {article['title']}\nText: {article_text}"

    messages = [
        {
            "role": "system",
            "content": f"You are a sentiment analysis assistant. Analyze the sentiment of the given news articles for {ticker.name} and provide a summary of the overall sentiment and any notable changes over time. Be measured and discerning. You are a skeptical investor."
        },
        {
            "role": "user", 
            "content": f"News articles for {ticker.name}:\n{news_text}\n\n----\n\nProvide a summary of the overall sentiment and any notable changes over time."},
    ]

    
    response = await asyncclient.chat.completions.create(
        temperature = 0.1,
        model="gpt-4-1106-preview",
        messages=messages
    )
    
    ticker.sentiment_analysis = response.choices[0].message.content




async def get_industry_analysis(ticker: TickerClass) -> None:
    print(f"Industry analysis for {ticker.name}")
    stock = yf.Ticker(ticker.name)
    industry = stock.info['industry']
    sector = stock.info['sector']


    messages = [
        {
            "role": "system",
            "content": f"You are an industry analysis assistant. Provide an analysis of the {industry} industry and {sector} sector, including trends, growth prospects, regulatory changes, and competitive landscape. Be measured and discerning. Truly think about the positives and negatives of the stock. Be sure of your analysis. You are a skeptical investor."
        },
        {
            "role": "user", 
            "content": f"Provide an analysis of the {industry} industry and {sector} sector."}
    ]

    
    response = await asyncclient.chat.completions.create(
        temperature = 0.1,
        model="gpt-4-1106-preview",
        messages=messages
    )
    
    ticker.industry_analysis = response.choices[0].message.content

async def get_final_analysis(ticker: TickerClass) -> None:
    print(f"Final analysis for {ticker.name}")
    messages = [
        {
            "role": "system",
            "content": f"You are a financial analyst providing a final investment recommendation for {ticker.name} based on the given data and analyses. Be measured and discerning. Truly think about the positives and negatives of the stock. Be sure of your analysis. You are a skeptical investor."
        },
        {
            "role": "user", 
            "content": f"Ticker: {ticker.name}\n\nSentiment Analysis:\n{ticker.sentiment_analysis}\n\nLatest Analyst Ratings:\n{ticker.analyst_ratings}\n\nIndustry Analysis:\n{ticker.industry_analysis}\n\nBased on the provided data and analyses, please provide a comprehensive investment analysis and recommendation for {ticker.name}. Consider the company's financial strength, growth prospects, competitive position, and potential risks. Provide a clear and concise recommendation on whether to buy, hold, or sell the stock, along with supporting rationale."}
    ]

    
    response = await asyncclient.chat.completions.create(
        temperature = 0.1,
        model="gpt-4-1106-preview",
        messages=messages
    )
    
    ticker.final_analysis = response.choices[0].message.content




def rank_companies(ticker_info_list: List[TickerClass], industry: str) -> str:
    print(f"Ranking ...")
    analysis_text = "\n\n".join(f"Ticker: {ticker.name}\nCurrent Price: {ticker.price}\nAnslysis:\n{ticker.final_analysis}" for ticker in ticker_info_list)
    analysis_text = analysis_text


    messages = [
        {
            "role": "system",
            "content": f"You are a financial analyst providing a ranking of companies in the {industry} industry based on their investment potential. Be discerning and sharp. Truly think about whether a stock is valuable or not. You are a skeptical investor."
        },
        {
            "role": "user", 
            "content": f"Industry: {industry}\n\nCompany Analyses:\n{analysis_text}\n\nBased on the provided analyses, please rank the companies from most attractive to least attractive for investment. Provide a brief rationale for your ranking. In each rationale, include the current price (if available) and a price target."},
    ]

    
    response = syncclient.chat.completions.create(
        temperature = 0.1,
        model="gpt-4-1106-preview",
        messages=messages
    )
    
    return response.choices[0].message.content

在这一部分,我们定义了 4 个不同的函数。第一个函数 get_sentiment_analysis(情感分析)接收我们之前定义的 TickerClass 对象,提取我们之前使用 yfinance 下载的新闻文章,并要求 GPT 对新闻文本进行情感分析。您可以更改系统提示,即消息列表中与系统角色相对应的值。我使用了 Matt Shumer 在他的笔记本中定义的系统提示和用户提示的默认值。您还可以选择使用哪种 GPT 模型来处理请求。我在这里使用的是 gpt-4-1106-preview,但你也可以更改模型并选择你的模型。通过 OpenAI API 获取的可用 GPT 模型列表可在 here 找到。

下一个函数 get_industry_analysis 要求 GPT 对股票所属行业的增长前景、监管变化和竞争格局进行总体分析。下一个函数 get_final_analysis 要求 GPT 使用之前进行的所有分析和信息对股票进行最终分析。最后,rank_companies 函数接收一个 TickerClass 对象列表(每只股票一个),并根据之前所做的分析对股票进行排名。

注:本节中定义的所有函数调用(不包括 rank_companies)都是异步编写的,以加快处理速度。利用异步函数调用可显著提高流程效率。当需要比较多只股票时,这种加速效果尤为明显。例如,假设您要比较 AAPL、MSFT、AMZN 和 NVDA。为了说明同步和异步方法之间的对比,请考虑以下情况。

同步方法:

  • 对于 AAPL,调用 get_sentiment_analysis,等待结果。
  • 收到情感分析结果后,调用 AAPL 的 get_industry_analysis,再次等待结果。
  • 获得行业分析后,调用 get_final_analysis for AAPL 。
  • 对每只股票(MSFT、AMZN、NVDA)依次重复上述步骤。
  • 最后,调用 rank_companies 对股票进行比较排名。

异步方法:

  • 同时对所有股票(AAPL,MSFT,AMZN,NVDA)启动 get_sentiment_analysis:通过一次性发送所有请求并等待其集体结果,我们大大缩短了等待时间。这种效率源于 OpenAI API 同时处理多个请求的能力,与按顺序提交相比,减少了整体处理时间。
  • 同样,同时为每个库存请求 get_industry_analysis,可大大减少等待时间。
  • 然后,对所有股票并行调用 get_final_analysis,等待汇总结果。
  • 完成所有分析后,rank_companies 会综合所有受评估股票的最终排名。

如需全面了解 Python 中的 Asyncio,可阅读本文

最后,我们编写一个函数来完成上述所有步骤并得到结果(您需要将股票所属的行业作为行业参数):

async def get_openai_verdict_2(tickers, industry):
    ticker_info_list = []
    for ticker in tickers:
        temp_ticker_info = TickerClass(name = ticker)
        temp_ticker_info.hist_data, temp_ticker_info.balance_sheet, temp_ticker_info.financials, temp_ticker_info.news = get_stock_data(ticker, years=1)

        temp_ticker_info.analyst_ratings = get_analyst_ratings(ticker)
        temp_ticker_info.price = get_current_price(ticker)
        ticker_info_list.append(temp_ticker_info)

    tasks1 = [get_sentiment_analysis(ticker_object) for ticker_object in ticker_info_list]
    await asyncio.gather(*tasks1)

    tasks2 = [get_industry_analysis(ticker_object) for ticker_object in ticker_info_list]
    await asyncio.gather(*tasks2)

    tasks3 = [get_final_analysis(ticker_object) for ticker_object in ticker_info_list]
    await asyncio.gather(*tasks3)

    final_ranking = rank_companies(ticker_info_list, industry)
    return final_ranking

tickers = ['AAPL', 'MSFT', 'AMZN', 'NVDA']
final_rankings = await get_openai_verdict_2(tickers=tickers, industry="tech")
print(final_ranking)

以下是我最终收到的 AAPL、MSFT、AMZN 和 NVDA 的截图。因为太长了,我把它裁剪了一下,但你应该已经明白了:):

3. 网络用户界面(Gradio)


为了给我们的应用程序创建一个简单的用户界面,我们使用了 Gradio。Gradio 是一个很棒的库,使用它可以在几分钟内为你的数据科学项目轻松创建用户界面。你可以在我的仓库中获取 Gradio UI 代码:

GitHub —alexyu2013/gpt-investor

4. 观点回顾

  1. 利用 GPT-4 和 yfinance 进行股票投资分析可以提供深入的市场洞察。GPT-4 的强大分析能力可以帮助投资者从大量数据中提取有用的信息,包括情感分析、行业趋势和竞争格局。
  2. 异步请求可显著提高数据处理的效率。通过同时处理多个股票的分析请求,可以减少总体的等待时间,特别是在处理大量股票时。
  3. 构建用户友好的 Web UI 对于普及投资工具至关重要。Gradio 库提供了一个简单快速的方式来创建用户界面,使得非技术用户也能够轻松使用这些复杂的数据分析工具。
  4. 分析师评级和股票价格是投资决策的重要参考。文章中提到的分析师评级和实时股票价格信息对于形成投资决策提供了关键的指导。
  5. OpenAI API 的可访问性和灵活性为金融分析提供了新的可能性。通过 OpenAI API,用户可以选择不同的 GPT 模型来处理数据,并根据自己的需求定制分析流程。


文内容仅仅是技术探讨和学习,并不构成任何投资建议。

转发请注明原作者和出处。

Published inAI&Invest专栏

Be First to Comment

    发表回复