---
# System prepended metadata

title: Chapter11. AI 客製化投資理財應用實戰
tags: [ChatGPT 開發手冊]

---

# Chapter11. AI 客製化投資理財應用實戰

:+1: 完整程式碼在 https://github.com/iamalex33329/chatgpt-develop-guide-zhtw

## 其他章節

[Chapter1. OpenAI API 入門](https://hackmd.io/@U3f2IzHERbymAst2-lDdjA/S1cNMYi6T)

[Chapter2. 使用 Python 呼叫 API](https://hackmd.io/@U3f2IzHERbymAst2-lDdjA/HyZBg5ia6)

[Chapter3. API 參數解析與錯誤處理](https://hackmd.io/@U3f2IzHERbymAst2-lDdjA/BJWNtsh6p)

[Chapter4. 打造自己的 ChatGPT](https://hackmd.io/@112356044/Hk81U96Tp)

[Chapter5. 突破時空限制 - 整合搜尋功能](https://hackmd.io/@112356044/HkbVM-ApT)

[Chapter6. 讓 AI 幫 AI - 自動串接流程](https://hackmd.io/@112356044/r1Ke-GR6T)

[Chapter7. 網頁版聊天程式與文字生圖 Image API](https://hackmd.io/@112356044/Hyf-AvgAT)

[Chapter8. 設計 LINE AI 聊天機器人](https://hackmd.io/@112356044/r1d6HsgAa)

[Chapter9. 自媒體業者必看！使用 AI 自動生成高品質字幕](https://hackmd.io/@112356044/rJ2T37V0T)

[Chapter10. 把 AI 帶到 Discord](https://hackmd.io/@112356044/Sy_L-B40T)

[Chapter12. 用 LangChain 實作新書宣傳自動小編](https://hackmd.io/@112356044/SybvbdN0p)

## 目錄結構

[TOC]

既然我們已經建構可以搜尋網路結果的機器人，為何不使用機器人來提供股票分析的報告？

這個方法雖然可行，但經作者測試，發現 ChatGPT 常常給出一些簡單並且陽春的答案，又或者是非即時資訊，而我自己測試也有相同的結論。

![Screenshot 2024-03-17 at 7.19.17 PM](https://hackmd.io/_uploads/SyZa-8ERT.png)

若要讓 ChatGPT 變成一位專業的分析師，勢必要增加更多資料以及穩定性。因此作者帶領我們使用爬蟲，抓取

1. 證交所的大盤資料
2. 個股資料
3. 相關新聞

來增加 ChatGPT 能夠分析的面相

另外，其實 Discord BOT 也能接收不同類型的應用程式指令（Interactions API），例如

1. 按鈕指令（buttons）
2. 斜線指令（slash commands）
3. 選單指令（select menus）

## 能抓取證交所資料的 Discord 機器人

[台灣證券交易所（TWSE）](https://www.twse.com.tw/en/)的資料格式較為穩定，並且格式統一，不需要頻繁修改程式碼，因此作為本節資料來源的網站！

![Screenshot 2024-03-17 at 7.31.46 PM](https://hackmd.io/_uploads/B1GhVIEAT.png)
    
example1.py
``` python=
from my_commands.index import index_info
from my_commands.stock_price import stock_price
from my_commands.stock_value import stock_value
from discord import app_commands
from discord.ext import commands

import discord
import apikey


token = apikey.DISCORD_BOT_TOKEN

intents = discord.Intents.default()  # 取得預設的 intent
intents.message_content = True  # 啟用訊息內容

# 建立指令機器人
client = commands.Bot(command_prefix="!", intents=intents)


@client.event
async def on_ready():
    print(f"{client.user} 已登入")
    try:
        synced = await client.tree.sync()
        print(f"{len(synced)}")
    except Exception as e:
        print(e)


# 大盤資訊查詢
@client.tree.command(name="index_info", description="搜尋最新大盤資訊")
async def dc_index(interaction: discord.Interaction):
    index_data = index_info()
    index_block = "```\n" + index_data + "```"
    # 建立內嵌訊息
    embed = discord.Embed(title="大盤指數資訊", description=index_block)
    await interaction.response.send_message(embed=embed)


# 個股股價資料
@client.tree.command(name="stock_price", description="搜尋最近股價資料")
@app_commands.rename(stock_id="股票代碼")
@app_commands.describe(stock_id="輸入要查詢的股票代碼, 如：2330")
async def dc_stock(interaction: discord.Interaction, stock_id: str):
    stock_data = stock_price(stock_id)
    stock_block = "```\n" + stock_data + "```"
    title = f"{stock_id} 各日成交資訊"
    # 建立內嵌訊息
    embed = discord.Embed(title=title, description=stock_block)
    await interaction.response.send_message(embed=embed)


# 個股殖利率、本益比及淨值比資料
@client.tree.command(name="stock_value", description="搜尋本益比、淨值比")
@app_commands.rename(stock_id="股票代碼")
@app_commands.describe(stock_id="輸入要查詢的股票代碼, 如：2330")
async def dc_value(interaction: discord.Interaction, stock_id: str):
    stock_data = stock_value(stock_id)
    stock_block = "```\n" + stock_data + "```"
    title = f"{stock_id} 個股日殖利率、本益比及股價淨值比"
    # 建立內嵌訊息
    embed = discord.Embed(title=title, description=stock_block)
    await interaction.response.send_message(embed=embed)


client.run(token)
```    
    
index.py
``` python=
from tabulate import tabulate

import datetime
import requests


def index_info():
    # 現在時間
    date = datetime.datetime.now().strftime("%Y%m%d")

    # 大盤指數資訊 (指數、漲跌點數)
    url = f'https://www.twse.com.tw/rwd/zh/afterTrading/FMTQIK?date={date}&response=json'
    index_json = requests.get(url).json()

    # 取得本月最新的資料
    data = index_json['data'][-1]
    # header = ["日期", "大盤指數", "Up/Down"]
    header = ["Date", "Market_Index", "Up/Down"]
    filtered_data = [[data[0], data[4], data[5]]]

    # 轉換成 tabulate 的文字表格
    index_data = tabulate(filtered_data, headers=header, tablefmt='ascii')

    return index_data
```
    
stock_price.py
``` python=
from tabulate import tabulate

import datetime
import requests


# 抓取股價資料
def stock_price(stock_index, month_num=2, data_num=10):
    current_date = datetime.datetime.now()
    date_list = []

    date_list = [(datetime.datetime(current_date.year, current_date.month - i, 1)).strftime('%Y%m%d') for i in
                 range(month_num)]
    date_list.reverse()
    all_daily_price_data = []
    # headers = ["日期", "成交股數", "最高價", "最低價","收盤價", "漲跌"]
    headers = ["Date", "Volume", "High", "Low", "Close", "Up/Down"]

    for date in date_list:
        url = f'https://www.twse.com.tw/rwd/zh/afterTrading/STOCK_DAY?date={date}&stockNo={stock_index}'
        try:
            daily_price_json = requests.get(url).json()

            # 提取數據
            daily_price_data = daily_price_json['data']

            # 只保留需要的列, 避免資料過長
            for row in daily_price_data:
                all_daily_price_data.append([row[0], row[1], row[4], row[5], row[6], row[7]])

        except Exception as e:
            print(f"無法取得{date}的資料, 可能資料量不足.")

    # 取得最近數據
    recent_data = all_daily_price_data[-data_num:]

    # 將資料轉換成tabulate表格
    table = tabulate(recent_data, headers=headers, tablefmt='ascii')

    return table
```
    
stock_value.py
``` python=
from tabulate import tabulate

import datetime
import requests


# 抓取基本面資料
def stock_value(stock_index, month_num=2, data_num=10):
    current_date = datetime.datetime.now()

    date_list = [(datetime.datetime(current_date.year, current_date.month - i, 1)).strftime('%Y%m%d') for i in
                 range(month_num)]
    date_list.reverse()
    all_value_data = []
    # headers = ["日期", "殖利率", "本益比","股價淨值比"]
    headers = ["Date", "Dividend", "P/E", "PB_ratio"]

    for date in date_list:
        url = f'https://www.twse.com.tw/rwd/zh/afterTrading/BWIBBU?date={date}&stockNo={stock_index}'
        try:
            daily_value_json = requests.get(url).json()

            # 提取數據
            daily_value_data = daily_value_json['data']

            # 只保留需要的列, 避免資料過長
            for row in daily_value_data:
                all_value_data.append([row[0], row[1], row[3], row[4]])

        except Exception as e:
            print(f"無法取得{date}的資料, 可能資料量不足.")

    # 取得最近數據
    recent_data = all_value_data[-data_num:]

    # 將資料轉換成tabulate表格
    table = tabulate(recent_data, headers=headers, tablefmt='ascii')

    return table
``` 

你可以在聊天框輸入 `/`，會顯示可使用的機器人指令：

![Screenshot 2024-03-17 at 7.27.55 PM](https://hackmd.io/_uploads/ryBTm8VR6.png)

`/index_info`

![Screenshot 2024-03-17 at 7.29.48 PM](https://hackmd.io/_uploads/Sk8VEINAT.png)

`/stock_price <股票代碼>`

![Screenshot 2024-03-17 at 7.30.18 PM](https://hackmd.io/_uploads/H1D8ELE0p.png)

`/stock_value <股票代碼>`

![Screenshot 2024-03-17 at 7.31.08 PM](https://hackmd.io/_uploads/SkvtVIVCT.png)

## StockGPT：專業的證券分析機器人

![Screenshot 2024-03-17 at 7.43.43 PM](https://hackmd.io/_uploads/HJ9dvUVA6.png)

example2.py
``` python=
from my_commands.index import index_info
from my_commands.stock_price import stock_price
from my_commands.stock_news import stock_search
from my_commands.stock_value import stock_value
from my_commands.analysis import stock_analysis
from discord import app_commands
from discord.ext import commands

import discord
import apikey


token = apikey.DISCORD_BOT_TOKEN

intents = discord.Intents.default()  # 取得預設的 intent
intents.message_content = True  # 啟用訊息內容

client = commands.Bot(command_prefix="!", intents=intents)


@client.event
async def on_ready():
    print(f"{client.user} 已登入")
    try:
        synced = await client.tree.sync()
        print(f"{len(synced)}")
    except Exception as e:
        print(e)


# 大盤資訊查詢
@client.tree.command(name="index_info", description="搜尋最新大盤資訊")
async def dc_index(interaction: discord.Interaction):
    index_data = index_info()
    index_block = "```\n" + index_data + "```"
    # 建立內嵌訊息
    embed = discord.Embed(title="大盤指數資訊", description=index_block)
    await interaction.response.send_message(embed=embed)


# 個股股價資料
@client.tree.command(name="stock_price", description="搜尋最近股價資料")
@app_commands.rename(stock_id="股票代碼")
@app_commands.describe(stock_id="輸入要查詢的股票代碼, 如：2330")
async def dc_stock(interaction: discord.Interaction, stock_id: str):
    stock_data = stock_price(stock_id)
    stock_block = "```\n" + stock_data + "```"
    title = f"{stock_id} 各日成交資訊"
    # 建立內嵌訊息
    embed = discord.Embed(title=title, description=stock_block)
    await interaction.response.send_message(embed=embed)


# 個股殖利率、本益比及淨值比資料
@client.tree.command(name="stock_value", description="搜尋本益比、淨值比")
@app_commands.rename(stock_id="股票代碼")
@app_commands.describe(stock_id="輸入要查詢的股票代碼, 如：2330")
async def dc_value(interaction: discord.Interaction, stock_id: str):
    stock_data = stock_value(stock_id)
    stock_block = "```\n" + stock_data + "```"
    title = f"{stock_id} 個股日殖利率、本益比及股價淨值比"
    # 建立內嵌訊息
    embed = discord.Embed(title=title, description=stock_block)
    await interaction.response.send_message(embed=embed)


# 個股新聞資料
@client.tree.command(name="stock_news", description="搜尋最近新聞")
@app_commands.rename(stock_id="股票代碼")
@app_commands.describe(stock_id="輸入要查詢的股票代碼, 如：2330")
async def dc_news(interaction: discord.Interaction, stock_id: str):
    news_data = stock_search(stock_id)
    await interaction.response.send_message(news_data)


# AI 幫你來分析
@client.tree.command(name="stock_gpt", description="讓 AI 來分析")
@app_commands.rename(stock_id="股票代碼")
@app_commands.describe(stock_id="輸入要查詢的股票代碼, 如：2330")
async def dc_ai(interaction: discord.Interaction, stock_id: str):
    # 因為後端程式的執行時間較長, 使用 defer 方法來延遲回應
    await interaction.response.defer()

    gpt_reply = stock_analysis(stock_id)
    await interaction.followup.send(gpt_reply)


client.run(token)
```

analysis.py
``` python=
from index import index_info
from stock_price import stock_price
from stock_value import stock_value
from stock_news import stock_search

import openai
import apikey


openai.api_key = apikey.OPENAI_API_KEY


def get_reply(messages):
    try:
        response = openai.ChatCompletion.create(
            model="gpt-3.5-turbo", messages=messages
        )
        reply = response["choices"][0]["message"]["content"]
    except openai.OpenAIError as err:
        reply = f"發生 {err.error.type} 錯誤\n{err.error.message}"
    return reply


def stock_analysis(stock_index):
    # 大盤資訊
    index_data = index_info()
    # 個股近期股價資訊
    stock_price_data = stock_price(stock_index, month_num=3, data_num=30)
    # 個股基本面
    stock_value_data = stock_value(stock_index, month_num=3, data_num=30)
    # 新聞資訊
    news_data = stock_search(stock_index)

    msg = [
        {
            "role": "system",
            "content": "你現在是一位專業的證券分析師, 你會統整大盤趨勢、近期的股價、基本面、新聞資訊等方面並進行分析, 然後回覆該股票的趨勢分析報告",
        },
        {
            "role": "user",
            "content": f"你現在是一位專業的證券分析師, 你會依據以下資料來進行分析並給出一份完整的分析報告: \n 大盤趨勢資料主要反映整體的市場情況:\n {index_data} \n 個股近期股價資訊: \n {stock_price_data} \n 近期基本面資訊：\n {stock_value_data} 近期新聞資訊: \n {news_data} \n 請給我此股票近期的趨勢報告,請以詳細、嚴謹及專業的角度撰寫此報告, 不要有模糊不定的回答, reply in 繁體中文",
        },
    ]

    reply_data = get_reply(msg)

    return reply_data
```

stock_news.py
``` python=
from googlesearch import search

import datetime
import requests


def stock_search(stock_index):
    # 取得昨天日期
    current_date = datetime.datetime.now()
    current_date = current_date - datetime.timedelta(days=1)
    date = current_date.strftime("%Y%m%d")

    # 利用證交所資料將股票代號轉換成公司名稱
    url = f'https://www.twse.com.tw/rwd/zh/afterTrading/STOCK_DAY?date={date}&stockNo={stock_index}'
    stock_json = requests.get(url).json()

    company_name = stock_json['title'].split()[2]  # 只取得公司名稱
    print(company_name)
    msg = f'{company_name} 市場新聞'

    news_data = ""

    for res in search(msg, advanced=True, num_results=5):
        news_data += f"標題：{res.title}\n摘要：{res.description}\n\n"

    return news_data
```

## 加入按鈕指令來優化使用者體驗

``` python=
from my_commands.stock_index import index_info
from my_commands.stock_price import stock_price
from my_commands.stock_news import stock_search
from my_commands.stock_value import stock_value
from my_commands.analysis import stock_analysis

from discord import app_commands
from discord.ext import commands

import discord
import apikey


token = apikey.DISCORD_BOT_TOKEN

intents = discord.Intents.default()  # 取得預設的 intent
intents.message_content = True  # 啟用訊息內容

client = commands.Bot(command_prefix="!", intents=intents)


@client.event
async def on_ready():
    print(f"{client.user} 已登入")
    try:
        synced = await client.tree.sync()
        print(f"{len(synced)}")
    except Exception as e:
        print(e)


# 大盤資訊查詢
@client.tree.command(name="index_info", description="搜尋最新大盤資訊")
async def dc_index(interaction: discord.Interaction):
    index_data = index_info()
    index_block = "```\n" + index_data + "```"
    # 建立內嵌訊息
    embed = discord.Embed(title="大盤指數資訊/漲跌家數", description=index_block)
    await interaction.response.send_message(embed=embed)


# 個股資訊按鈕類別
class StockButtons(discord.ui.View):
    def __init__(self, stock_id):
        super().__init__(timeout=None)
        self.stock_index = stock_id

    # StockGPT 按鈕
    @discord.ui.button(label="StockGPT", style=discord.ButtonStyle.primary)
    async def stock_gpt(
        self, interaction: discord.Interaction, Button: discord.ui.Button
    ):
        # 因為後端程式的執行時間較長, 使用 defer 方法來延遲回應
        await interaction.response.defer()
        # 機器人正在思考
        async with interaction.channel.typing():
            gpt_reply = stock_analysis(self.stock_index)
        await interaction.followup.send(gpt_reply)

    # 股價按鈕
    @discord.ui.button(label="股價", style=discord.ButtonStyle.primary)
    async def stock_price(
        self, interaction: discord.Interaction, Button: discord.ui.Button
    ):
        await interaction.response.defer()
        stock_data = stock_price(self.stock_index)
        stock_block = "```\n" + stock_data + "```"
        title = f"{self.stock_index} 各日成交資訊"
        # 建立內嵌訊息
        embed = discord.Embed(title=title, description=stock_block)
        await interaction.followup.send(embed=embed)

    # 日殖利率、本益比及淨值比按鈕
    @discord.ui.button(label="日殖利率、本益比及淨值比", style=discord.ButtonStyle.primary)
    async def stock_value(
        self, interaction: discord.Interaction, Button: discord.ui.Button
    ):
        await interaction.response.defer()
        stock_data = stock_value(self.stock_index)
        stock_block = "```\n" + stock_data + "```"
        title = f"{self.stock_index} 個股日殖利率、本益比及股價淨值比"
        # 建立內嵌訊息
        embed = discord.Embed(title=title, description=stock_block)
        await interaction.followup.send(embed=embed)

    # 新聞資訊按鈕
    @discord.ui.button(label="新聞資訊", style=discord.ButtonStyle.primary)
    async def stock_news(
        self, interaction: discord.Interaction, Button: discord.ui.Button
    ):
        await interaction.response.defer()
        news_data = stock_search(self.stock_index)
        await interaction.followup.send(news_data)


# 斜線指令呼叫按鈕選單
@client.tree.command(name="stock_info", description="搜尋個股資訊")
@app_commands.rename(stock_id="股票代碼")
@app_commands.describe(stock_id="輸入要查詢的股票代碼, 如：2330")
async def stock_info(interaction: discord.Interaction, stock_id: str):
    content = f"股票代碼：{stock_id}, 請點擊想要查詢的資訊類型"
    await interaction.response.send_message(
        content=content, view=StockButtons(stock_id)
    )


client.run(token)
```

使用方式為：輸入 `/stock_info` 會跳出按鈕選單，使用者依照需求功能來點選按鈕

![Screenshot 2024-03-17 at 8.05.15 PM](https://hackmd.io/_uploads/SkUY2LNAa.png)

## 結語

由於 ChatGPT 的訓練資料並不是即時最新，因此本章利用程式來**客製化**資料。使我們了解 ChatGPT 的優點就是**資料統整**和**分析**，只要取資料的精華，刪去無用的資訊後，再透過自動化的分析，使 ChatGPT 能夠接觸到更多面向的資料來呈現報告。