# 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 能夠接觸到更多面向的資料來呈現報告。