讓 AI 計算技術指標及資料視覺化 1. 技術指標公式太複雜?讓 AI 自動化計算 我們剛剛已經學會了如何串接open AI API,並建構簡單的聊天機器人。 在這一節,我們將進一步挖掘AI的潛能。 讓AI自動進行資料處理,並計算任意技術指標。 接著我們會進入到資料視覺化的領域。 這也是在使用Python進行股市分析時的必備技能。 藉由將資料轉換成生動的圖表,能夠幫助我們快速辨認股價的趨勢走向更精準地進行分析。 對於技術面分析來說,技術指標扮演著非常重要的角色。 藉由觀察這些指標,能夠幫助投資人理解市場趨勢市場情緒和動能,但是技術指標有成千上萬種。 甚至有些根本連聽都沒聽過,這些公式的計算方式也很複雜,那有沒有什麼辦法能夠讓我們輕鬆的計算出這些指標呢? 在這一節中,我們會以幾種最常用的技術指標為範例,介紹如何透過串接open AI API的方式。 請AI回傳任意技術指標的程式碼,並自動進行data frame的資料處理。 我認為未來程式開發的樣貌會有所不同。 不再需要從頭手刻程式碼讓AI自動進行數據分析以及資料處理會是一大趨勢。 這也是我想傳達給各位的核心理念之一。 1️⃣ 安裝及匯入套件 !pip install openai !pip install yfinance from openai import OpenAI, OpenAIError # 串接 OpenAI API import yfinance as yf import pandas as pd # 資料處理套件 import datetime as dt # 時間套件 2️⃣ 取得股票資料 # 輸入股票代號 stock_id = "3005.tw" # 抓取半年資料 end = dt.date.today() # 資料結束時間 start = end - dt.timedelta(days=180) # 資料開始時間 df = yf.download(stock_id, start=start, end=end).reset_index() print(df) 在以上程式碼中,我們首先設定要查詢的股票代碼。 以此範例,以我的股票為例,接著我們使用data.today()方法獲取當前的日期作為資料的結束時間,並使用timedelta()函式來計算半年前的日期作為資料的開始時間。 另外,若未使用reset_index所下載的股票資料,會自動將日期設為索引為了讓AI能清楚各欄位名稱,建議加上reset_index來重設索引。 再來,我們一樣設定 API key來作為金鑰,然後執行下一個儲存格。 3️⃣ 輸入 OpenAI API KEY import getpass api_key = getpass.getpass("請輸入金鑰:") client = OpenAI(api_key=api_key) 在第4個儲存格中,我們使用get_reply函式來建構一個基本的gpt3.5模型。 在此先不贅述get_reply,接著另外設定1個ai_helper函式來預先撰寫要輸入到模型的指令。 以下為程式碼詳解。 4️⃣ 創建 GPT 3.5 模型函式 # GPT 3.5 模型 def get_reply(messages): try: response = client.chat.completions.create(model="gpt-3.5-turbo", messages=messages) reply = response.choices[0].message.content except OpenAIError as err: reply = f"發生 {err.type} 錯誤\n{err.message}" return reply # 設定 AI 角色, 使其依據使用者需求進行 df 處理 def ai_helper(df, user_msg): msg = [{ "role": "system", "content": f"As a professional code generation robot, \ I require your assistance in generating Python code \ based on specific user requirements. To proceed, \ I will provide you with a dataframe (df) that follows the \ format {df.columns}. Your task is to carefully analyze the \ user's requirements and generate the Python code \ accordingly.Please note that your response should solely \ consist of the code itself, \ and no additional information should be included." }, { "role": "user", "content": f"The user requirement:{user_msg} \n\ Your task is to develop a Python function named \ 'calculate(df)'. This function should accept a dataframe as \ its parameter. Ensure that you only utilize the columns \ present in the dataset, specifically {df.columns}. \ After processing, the function should return the processed \ dataframe. Your response should strictly contain the Python \ code for the 'calculate(df)' function \ and exclude any unrelated content." }] reply_data = get_reply(msg) return reply_data 讓 AI 自動生成技術指標程式碼 12行。 aI_helper函式,此函式會接受一個dataFrame資料及使用者需求。 這個函式的主要目的是將指令輸入到gpt模型中,從中獲得AI所生成的程式碼,並進行df表格資料的處理。 第14行訊息串列在此,我們預先設定好2個角色,分別是system與user的訊息指令。 第15到26行。 system系統角色訊息。 將AI設定為Python程式碼生成機器人,並提供df.columns(欄位名稱)。 以供AI預先了解我們的df資料格式。 第31到37行。 "user"使用者訊息讓AI根據提供的user_msg即生成名為calculate的Python函數。 Tip: 不是相較於中文,輸入英文指令會較為精準,所生成的程式碼也會比較穩定。 設定好模型後,就能讓AI依據我們的需求來計算技術指標了。 或許可能有人會有疑問,如果要計算技術指標的話,現在已經有像TA-LIP等強大套件了,為什麼還要用AI來計算呢? 原因在於後續我們會讓使用者用白話進行互動。 而使用TA-LIP計算的話,每種技術指標的函式的參數都不同。 使用者需要先了解各個函式的名稱和功能。 AI則可以藉由自然語言來判斷我們的需求生成,更為"客製化"的程式碼。 除此之外,AI還能進行df格式的資料處理,幫我們將日頻資料轉換成月頻, 月頻轉換成年頻或自動進行df的資料合併等複雜功能。 閱讀完本節後,相信各位就會漸漸了解如何讓AI依據我們的需求來進行資料處理。 讓我讓我們一步一步來,先從幾種基本的技術指標開始計算。 5️⃣ 計算移動平均線 code_str = ai_helper(df, "計算8日MA與13日MA ") print(code_str) exec(code_str) new_df = calculate(df) new_df.tail() 在上述程式碼中,首先我們會將使用者需求輸入至ai_helper函式。 透過這一步AI會回傳相應的程序碼。另外,因為gpt模型的回答code__str實際上是一段程式碼的字串。 為了在Python中實際執行這個字串的程式碼,需要使用exec()函式來執行。 程式碼中會創建一個名為calculate的函式。 最後我們就能將原本的df輸入至這個新創的calculate函式中並進行技術指標的運算了!! 6️⃣ 計算 MACD code_str = ai_helper(df, "先計算 EMA 再計算 MACD, 欄位名稱用 MACD Histogram 命名") print(code_str) exec(code_str) new_df = calculate(df) new_df.tail() 7️⃣ 計算 RSI code_str = ai_helper(df, "計算 RSI ") print(code_str) exec(code_str) new_df = calculate(df) new_df.tail() 8️⃣ 計算布林通道 code_str = ai_helper(df, "請計算1.5個標準差的布林通道, 欄位以Upper Band和Lower Band命名") print(code_str) exec(code_str) new_df = calculate(df) new_df.tail() 9️⃣ 能量潮指標 (On-Balance Volumem, OBV) code_str = ai_helper(df, "計算 OBV 指標") print(code_str) exec(code_str) new_df = calculate(df) new_df.tail() 讓 AI 自動統整 Dataframe 🔟 將日頻資料轉換成月頻資料 閱讀到這邊的讀者,應該可以發現這個程序的應用層面非常廣泛,不僅可以計算技術指標,還能要求AI幫我進行資料頻率的轉換。進一步的說,這個程式不僅僅限於股市資料的應用,同樣能對各種df資料進行處理。 例如資料分割統計分析或缺失之處理等等。 甚至只要稍微修改AI_helper函式的指令,還能要求AI同時進行多個df資料處理或合併。 2 資料視覺化 在下面這一節中會進入到股票分析中的另外一個領域,資料視覺化資料視覺化是將原始的表格數據轉換為圖表的過程。 這將更有助於更直觀快速的理解數據的趨勢。在股票分析中,資料視覺化是一個非常關鍵的角色,不論是折線圖柱狀圖K折線圖或其他複雜的圖表,這些都能幫助投資人更清晰的理解市場動態何變化。我將會循序漸進地介紹如何將複雜的數據資料轉換為簡易的圖表。 本節先使用matplotlib套件來繪製出簡易的股價圖,接著透過。 mplifinance的技術分析繪圖套件來繪製較為複雜的K線圖。 下一節,我們將使用功能更強大的matplotlib套件來創建具互動性的圖表。 1️⃣1️⃣ 安裝及匯入套件 import matplotlib.pyplot as plt 畫出簡易股價圖 1️⃣2️⃣ 讓我們再一次檢視資料格式 new_df = new_df.reset_index() # 重設 index new_df['Date'] = pd.to_datetime(new_df['Date']) # 將 Date 轉換為 datetime 類別 new_df.tail() 1️⃣3️⃣ 使用 matplotlib 畫出收盤價的折線圖 # 畫布尺寸大小設定 plt.figure(figsize=(12, 6)) # 設定要繪製的資料欄位 plt.plot(new_df['Close'], label='Close') # 設定 x 軸的時間 num = 10 date = new_df["Date"].dt.strftime('%Y-%m-%d') plt.xticks(date[::len(date)//num].index, date[::len(date)//num], rotation = 45) # 設定圖表的標題,x 軸和 y 軸的標籤 plt.title(f'{stock_id}') # 將股票代號設為圖標 plt.xlabel('Date') # x 軸標籤 plt.ylabel('Price', rotation=0, ha='right') # y 軸標籤 plt.legend(loc='upper left') # 在左上角顯示圖例 plt.grid(True) # 在圖上顯示網格 plt.tight_layout() # 顯示圖表 plt.show() 1️⃣4️⃣ 加入成交量 # 創建兩張子圖 fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 8), gridspec_kw={'height_ratios': [2, 1]}) # 設定 x 軸時間 num = 10 date = new_df["Date"].dt.strftime('%Y-%m-%d') # 繪製收盤價 ax1.plot(new_df['Close'], label='Close') ax1.set_title(f'{stock_id}') ax1.set_ylabel('Price', color='blue', rotation=0, ha='right') ax1.set_xticks(date[::len(date)//num].index) ax1.set_xticklabels(date[::len(date)//num], rotation=45) # 繪製交易量 ax2.bar(new_df.index, new_df['Volume'], color='green') ax2.set_ylabel('Volume', color='green', rotation=0, ha='right') ax2.set_xticks([]) # 不顯示日期標籤 # 若要顯示圖表標籤可以使用以下程式碼 # ax2.set_xticks(date[::len(date)//num].index) # ax2.set_xticklabels(date[::len(date)//num], rotation=45) # 讓子圖填充、對齊 plt.tight_layout() # 顯示圖表 plt.show() 1️⃣5️⃣ 加入技術指標 # 創建三個子圖 fig, (ax1, ax2, ax3) = plt.subplots(3, 1, figsize=(12, 8), gridspec_kw={'height_ratios': [2, 1, 1]}, sharex=True) # 設定 x 軸時間 num = 10 date = new_df["Date"].dt.strftime('%Y-%m-%d') # 繪製收盤價 ax1.plot(new_df['Close'], label='Close') # 加入布林通道 ax1.plot(new_df['Upper Band'], alpha=0.5) # alpha 設定透明度 ax1.plot(new_df['Lower Band'], alpha=0.5) ax1.set_title(f'{stock_id}') ax1.set_ylabel('Price', color='blue', rotation=0, ha='right') ax1.set_xticks(date[::len(date)//num].index) ax1.set_xticklabels(date[::len(date)//num]) # 繪製交易量 ax2.bar(new_df.index, new_df['Volume'], alpha=0.5, color='green') ax2.set_ylabel('Volume', color='green', rotation=0, ha='right') # 繪製技術指標 ax3.bar(new_df.index, new_df['MACD Histogram'], alpha=0.5, color='red') ax3.set_ylabel('MACD', color='red', rotation=0, ha='right') # 調整子圖間的距離 plt.tight_layout() # 顯示圖表 plt.show() 繪製 K 線圖:mplfinance 1️⃣6️⃣ 匯入技術分析繪圖套件 !pip install mplfinance import mplfinance as mpf 1️⃣7️⃣ 選擇資料時間 kplot_df = new_df.set_index('Date') kplot_df = kplot_df['2023-05-18':'2023-12-08'] kplot_df.tail() 1️⃣8️⃣ 用 mplfinance 繪製 K 線圖 mpf.plot(kplot_df, type='candle', title=f'{stock_id}') 1️⃣9️⃣ 加入繪圖設定 # 設置繪圖風格 my_color = mpf.make_marketcolors(up='r', down='g', inherit=True) my_style = mpf.make_mpf_style(base_mpf_style='default', marketcolors=my_color) # 使用 mplfinance 繪製 K 線圖 mpf.plot(kplot_df, type='candle', style=my_style, title=f'{stock_id}') 2️⃣0️⃣ 加入子圖 # 設置繪圖風格 my_color = mpf.make_marketcolors(up='r', down='g', inherit=True) my_style = mpf.make_mpf_style(base_mpf_style='yahoo', marketcolors=my_color) # 交易量和技術指標子圖 ap = [ # 上軌線 mpf.make_addplot(kplot_df['Upper Band'], color='red', alpha=0.5, linestyle='--'), # 下軌線 mpf.make_addplot(kplot_df['Lower Band'], color='red', alpha=0.5, linestyle='--'), # 成交量 mpf.make_addplot(kplot_df['Volume'], panel=1, type='bar', color='g', alpha=0.5, ylabel='Volume'), # MACD mpf.make_addplot(kplot_df['MACD Histogram'], panel=2, type='bar', color='r', alpha=0.5, ylabel='MACD') ] # 使用 mplfinance 繪製 K 線圖 mpf.plot(kplot_df, type='candle', addplot=ap, style=my_style, title=f'{stock_id}') 3 plotly 互動式圖表 2️⃣1️⃣ 檢視資料 bk_df = new_df bk_df.index = bk_df["Date"].dt.strftime('%Y-%m-%d') bk_df.tail() 2️⃣2️⃣ 匯入 plotly 套件 # 創建 K 線圖 fig = go.Figure(data=[go.Candlestick(x=bk_df.index, open=bk_df['Open'], high=bk_df['High'], low=bk_df['Low'], close=bk_df['Close'], increasing_line_color='red', decreasing_line_color='green')]) # 調整寬高 fig.update_layout( height=800, width=1200 ) # 顯示圖表 fig.show() 2️⃣4️⃣ 移除非交易日空值 解決繪圖不連續問題 # 創建 K 線圖 fig = go.Figure(data=[go.Candlestick(x=bk_df.index, open=bk_df['Open'], high=bk_df['High'], low=bk_df['Low'], close=bk_df['Close'], increasing_line_color='red', decreasing_line_color='green')]) # 調整寬高 fig.update_layout( height=800, width=1200 ) # 移除非交易日空值 # 生成該日期範圍內的所有日期 all_dates = pd.date_range(start=bk_df.index.min(), end=bk_df.index.max()) # 找出不在資料中的日期 breaks = all_dates[~all_dates.isin(bk_df.index)] dt_breaks = breaks.tolist() # 轉換成列表 fig.update_xaxes(rangebreaks=[{'values': dt_breaks}]) # 顯示圖表 fig.show() 2️⃣5️⃣ 加入懸停十字軸 # 創建 K 線圖 fig = go.Figure(data=[go.Candlestick(x=bk_df.index, open=bk_df['Open'], high=bk_df['High'], low=bk_df['Low'], close=bk_df['Close'], increasing_line_color='red', decreasing_line_color='green')]) # 圖表更新-加入懸停十字軸 fig.update_xaxes(showspikes=True, spikecolor="gray", spikemode="across") fig.update_yaxes(showspikes=True, spikecolor="gray", spikemode="across") fig.update_layout( height=800, width=1200 ) # 移除非交易日空值 all_dates = pd.date_range(start=bk_df.index.min(), end=bk_df.index.max()) # 生成該日期範圍內的所有日期 breaks = all_dates[~all_dates.isin(bk_df.index)] # 找出不在資料中的日期 dt_breaks = breaks.tolist() # 轉換成列表 fig.update_xaxes(rangebreaks=[{'values': dt_breaks}]) fig.show() 2️⃣6️⃣ 加入技術指標 # 創建 K 線圖 fig = go.Figure(data=[go.Candlestick(x=bk_df.index, open=bk_df['Open'], high=bk_df['High'], low=bk_df['Low'], close=bk_df['Close'], increasing_line_color='red', decreasing_line_color='green', name = "K 線")]) # 布林通道 fig.add_trace(go.Scatter( x=bk_df.index, y=bk_df['Upper Band'],mode='lines', line={'color': 'green','dash': 'dash'},name = "上軌線")) fig.add_trace(go.Scatter( x=bk_df.index, y=bk_df['Lower Band'], mode='lines', line={'color': 'green', 'dash': 'dash'},name = "下軌線")) # 交易量 fig.add_trace(go.Bar( x=bk_df.index, y=bk_df['Volume'], marker={'color': 'green'}, yaxis='y2', name = "交易量")) # MACD fig.add_trace(go.Bar( x=bk_df.index, y=bk_df['MACD Histogram'], marker={'color': 'red'}, yaxis='y3', name = "MACD")) # 加入懸停十字軸 fig.update_xaxes(showspikes=True, spikecolor="gray", spikemode="toaxis") fig.update_yaxes(showspikes=True, spikecolor="gray", spikemode="across") # 更新畫布大小並增加範圍選擇 fig.update_layout( height=800, yaxis={'domain': [0.35, 1]}, yaxis2={'domain': [0.15, 0.3]}, yaxis3={'domain': [0, 0.15]}, title=f"{stock_id}", xaxis={ # 範圍選擇格 'rangeselector': { 'buttons': [ {'count': 1, 'label': '1M', 'step': 'month', 'stepmode': 'backward'}, {'count': 6, 'label': '6M', 'step': 'month', 'stepmode': 'backward'}, {'count': 1, 'label': '1Y', 'step': 'year', 'stepmode': 'backward'}, {'step': 'all'} ] }, # 範圍滑動條 'rangeslider': { 'visible': True, # 滑動條的高度 (設置 0.01 就會變單純的 bar) 'thickness': 0.01, 'bgcolor': "#E4E4E4" # 背景色 }, 'type': 'date' } ) # 移除非交易日空值 all_dates = pd.date_range(start=bk_df.index.min(), end=bk_df.index.max()) # 生成該日期範圍內的所有日期 breaks = all_dates[~all_dates.isin(bk_df.index)] # 找出不在資料中的日期 dt_breaks = breaks.tolist() # 轉換成列表 fig.update_xaxes(rangebreaks=[{'values': dt_breaks}]) fig.show() 2️⃣7️⃣ 寫成函式 # 下載資料並讓 AI 計算指標 def download_stock_data(stock_id, start=None, end=None, indicator='MACD'): stock_id = f"{stock_id}.tw" if not end: end = dt.date.today() if not start: start = end - dt.timedelta(days=365) # 從 yf 下載資料 df = yf.download(stock_id, start=start, end=end).reset_index() # AI 計算技術指標 code_str = ai_helper(df, f"計算{indicator}") print(code_str) # 將 exec 生成的 calculate 設為局部變數 local_vars = {} exec(code_str, globals(), local_vars) calculate = local_vars['calculate'] df = calculate(df) # 資料處理 bk_df = df.reset_index() bk_df.index = bk_df["Date"].dt.strftime('%Y-%m-%d') return bk_df # 繪製圖表函式 def create_stock_figure(stock_id, bk_df): # 創建 K 線圖 fig = go.Figure(data=[go.Candlestick(x=bk_df.index, open=bk_df['Open'], high=bk_df['High'], low=bk_df['Low'], close=bk_df['Close'], increasing_line_color='red', decreasing_line_color='green', name = "K 線")]) # 交易量 fig.add_trace(go.Bar(x=bk_df.index, y=bk_df['Volume'], marker={'color': 'green'}, yaxis='y2', name = "交易量")) # 找出需要繪製的欄位 columns = bk_df.columns exclude_columns = ['index','Date', 'Open', 'High', 'Low', 'Close', 'Adj Close', 'Volume'] remain_columns = [col for col in columns if col not in exclude_columns] min_close = bk_df['Close'].min() - bk_df['Close'].std() max_close = bk_df['Close'].max() + bk_df['Close'].std() # 繪製技術指標 for i in remain_columns: if min_close ###draft_code_symbol_lessthen###= bk_df[i].mean() ###draft_code_symbol_lessthen###= max_close: fig.add_trace(go.Scatter(x=bk_df.index, y=bk_df[i], mode='lines', name=i)) else: fig.add_trace(go.Scatter(x=bk_df.index, y=bk_df[i], mode='lines', yaxis='y3', name=i)) # 加入懸停十字軸 fig.update_xaxes(showspikes=True, spikecolor="gray", spikemode="toaxis") fig.update_yaxes(showspikes=True, spikecolor="gray", spikemode="across") # 更新畫布大小並增加範圍選擇 fig.update_layout( height=800, width=1200, yaxis={'domain': [0.35, 1]}, yaxis2={'domain': [0.15, 0.3]}, # 若要重疊 y1 和 y3, 可以改成 # yaxis3=dict(overlaying='y', side='right') yaxis3={'domain': [0, 0.15]}, title=f"{stock_id}", xaxis={ # 範圍選擇格 'rangeselector': { 'buttons': [ {'count': 1, 'label': '1M', 'step': 'month', 'stepmode': 'backward'}, {'count': 6, 'label': '6M', 'step': 'month', 'stepmode': 'backward'}, {'count': 1, 'label': '1Y', 'step': 'year', 'stepmode': 'backward'}, {'step': 'all'} ] }, # 範圍滑動條 'rangeslider': { 'visible': True, 'thickness': 0.01, # 滑動條的高度 'bgcolor': "#E4E4E4" # 背景色 }, 'type': 'date' } ) # 移除非交易日空值 # 生成該日期範圍內的所有日期 all_dates = pd.date_range(start=bk_df.index.min(), end=bk_df.index.max()) # 找出不在資料中的日期 breaks = all_dates[~all_dates.isin(bk_df.index)] dt_breaks = breaks.tolist() # 轉換成列表格式 fig.update_xaxes(rangebreaks=[{'values': dt_breaks}]) return fig # 主函式 def plotly_stock(stock_id, start=None, end=None, indicator='MACD'): df = download_stock_data(stock_id, start, end, indicator) fig = create_stock_figure(stock_id,df) fig.show() 2️⃣8️⃣ 執行函式 plotly_stock("3005", start='2022-01-01', end= None, indicator='布林通道及MACD')