# 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 常常給出一些簡單並且陽春的答案,又或者是非即時資訊,而我自己測試也有相同的結論。

若要讓 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/)的資料格式較為穩定,並且格式統一,不需要頻繁修改程式碼,因此作為本節資料來源的網站!

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
```
你可以在聊天框輸入 `/`,會顯示可使用的機器人指令:

`/index_info`

`/stock_price <股票代碼>`

`/stock_value <股票代碼>`

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

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` 會跳出按鈕選單,使用者依照需求功能來點選按鈕

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