# 專案概述 預測個股未來是否為當沖股 * 專案目標:將當沖熱門股的數據包裝成產品,販賣給客戶 * 例如:券商可透過當沖熱門股預測結果,告訴散戶,吸引散戶當沖 * 專案任務:開發一個模型來預測個股未來幾天是否會成為當沖熱門股 * 額外可以知道,那些因子會影響成為當沖熱門股 * 專案受眾: * 主要:法人(券商等) * 次要:當沖投資者等 ## 模型概述 * 輸入資料:個股歷史交易數據、技術指標、基本面資料等。 * 輸出結果:個股是否為當沖股的標籤。 ### 功能模組 ``` 資料收集與清洗模組 特徵工程模組 預測模型模組 可視化與報表模組 ``` ### 技術架構 開發語言:Python 主要工具與框架: 資料處理:Pandas、NumPy 機器學習:隨機森林 # 模型製作 開發一個模型來預測個股未來1天是否會成為當沖熱門股 ## 當沖股定義與判斷方法 當日的當沖成交量排名前 **n** 名的個股被視為當沖股。其中,**n** 的取值由「Knee Point」方法決定。 ![某日當沖成交量及keen point圖](https://hackmd.io/_uploads/BkjCGucrJg.png) ### Knee Point 的定義 Knee Point 是在一組數據中,曲率最大的位置,表示數據趨勢發生顯著變化的點。 在本專案中,透過將每個交易日中所有個股的當沖成交量由高到低排序,找出當沖成交量「爆發上升」的臨界點(Knee Point),作為 **n** 的值。 ![類膝點示意圖](https://hackmd.io/_uploads/B1i0pw5B1e.png) ### 演算法來源 我們採用了開源工具 [kneed](https://github.com/arvkevi/kneed) 來實現 Knee Point 的判定。該工具提供了可靠的曲率計算方法,適合用於大規模數據的分析。 ### 應用步驟 1. 收集當日所有個股的當沖成交量。 2. 按照當沖成交量進行降序排序。 3. 利用 Knee Point 演算法,找出曲率最大的位置,並確定排名值 **n**。 4. 將排名前 **n** 的個股標記為當沖股。 ## 資料準備 ``` 數據來源:歷史交易數據(如開高低收成交量、當沖比例)、市場指數、基本面數據。 數據格式:CSV、JSON、API 返回格式。 資料處理過程:缺失值處理、異常值檢測、標準化。 ``` ### **數據來源** 透過WCFAdox,取自CMoney資料庫的歷史資料 - **歷史交易數據**:包含每日交易的開高低收、成交量、當沖成交量與非當沖成交量。 - **基本面數據**:如本益比、成交量變動百分比、產業分類等。 - **技術指標數據**:如各種技術分析指標及衍生指標。 - **特徵工程**:基於歷史資料生成可能有用的特徵 時間長度: 2023/01~2024/10 | 資料表 | 欄位 | | ----- | --- | | 上市櫃公司基本資料 | 年度,股票代號,股票名稱,產業名稱,產業指數名稱,指數彙編分類 | | 日收盤表排行 | 日期,股票代號,開盤價,最高價,最低價,收盤價,漲跌,漲幅(%),振幅(%),漲跌停,成交量,成交量變動(%),成交筆數,總市值(億),本益比,股價淨值比,週轉率(%) | | 日當日沖銷交易 | 日期,股票代號,當沖成交量,非當沖成交量 | | 日融資券排行 | 日期,股票代號,資買,資賣,資現償,資餘,資增減,資限,券買,券賣,券賣金額(千),券現償,券餘,券增減,資券相抵,券資比,資使用率,券使用率,當沖比率,融資維持率(%),整體維持率(%) | | 日報酬率比較表 | 日期,股票代號,還原收盤價,日報酬率(%),週報酬率(%),月報酬率(%),開盤價(相對大盤),最高價(相對大盤),最低價(相對大盤),收盤價(相對大盤),漲跌(相對大盤),漲幅%(相對大盤),與大盤比日報酬率(%),與大盤比週報酬率(%),與大盤比月報酬率(%),殖利率(%) | | 日常用技術指標表 | 日期,股票代號,K(9),D(9),RSI(5),RSI(10),DIF,MACD,DIF-MACD,W%R(5),W%R(10),+DI(14),-DI(14),ADX(14),週K(9),週D(9),週RSI(10),週DIF,週MACD,週DIF-週MACD,週+DI(14),週-DI(14),週ADX(14),Alpha(250D),Beta係數(21D),Beta係數(65D),乖離率(20日),乖離率(60日),相對強弱比(日),相對強弱比(週),近一月歷史波動率(%),EWMA波動率(%) | | 日常用技術指標表Ⅱ | 日期,股票代號,保力加通道–頂部(20),保力加通道–均線(20),保力加通道–底部(20),SAR,TR(1),ADXR(14),+DM(14),-DM(14),週TR(14),週ADXR(14),週+DM(14),週-DM(14),紅三兵,黑三兵,一星二陽,一星二陰,孤島晨星,孤島夜星,長紅吞噬,長黑吞噬,低檔長紅,高檔長黑,低檔槌子,高檔吊人 | | 特徵工程(股票類型) | 台灣50, 台灣100, 上市櫃 | | 特徵工程(股票產業類型) | 產業名稱`One-Hot encoder`, 指數彙編分類`One-Hot encoder` | | 特徵工程(時間特徵) | 年,月,月中日,年中日,星期,年週,季度,月初,月底,月中,季初月,季末月,年初月,年末月,month_sin,month_cos,weekday_sin,weekday_cos,monthday_sin,monthday_cos,yearday_sin,yearday_cos,週一,週五 | | 特徵工程(滯後特徵) | 當沖成交量_lag_{1~5}, 當沖股(膝點)_lag_{1~5}, rolling_mean_{3, 5, 10, 15, 20} | | 特徵工程(當沖相關) | 當沖成交比例, 當沖成交比例(市場) | | 特徵工程(膝點) | 目標, 膝點標註 | | | | ### **數據處理過程** #### 1. 資料合併與處理 * 利用 `pd.merge` 合併不同數據來源,通過日期、股票代號等關鍵字段進行匹配 #### 2. 數據型態處理 - 日期欄位轉換為 `datetime` 格式並排序 - 類別型特徵(如股票代號、產業名稱)轉換為類別型數據以節省記憶體 #### 3. 缺失值處理 - 特定欄位如本益比、成交量變動百分比補 `0` - 剩餘含有缺失值的行直接移除,確保數據完整性 - 移除約xxx筆資料,占總資料xxxx% #### 4. 特徵工程 加入適合解釋當沖股的特徵 1. **新增特徵** - **當沖成交比例**:計算當沖成交量占個股總成交量的比例 - **當沖成交比例(市場)**:計算個股當沖成交量占當日市場總成交量的比例 - **市場總當沖成交量**:按日期計算每日市場總當沖成交量 - **時間特徵**:新增時間相關特徵,包括季度、週數、月初標記等。 - **類別型特徵 One-Hot 編碼**:對產業名稱及指數分類進行 One-Hot 編碼。 2. **膝點檢測** - 按每日個股當沖成交量排序,==標準化==後使用 [kneed](https://github.com/arvkevi/kneed) 演算法計算膝點 - 膝點之前的個股標記為「當沖股」 3. **滯後與滑動特徵** - 當沖成交量、當沖股(膝點):新增 1-5 天的滯後特徵 - 收盤價:計算 3、5、10、15、20 天的移動平均成交量 4. **設定目標值** - 滯前一天的膝點標註作為預測目標。 #### 5. 清理數據 - 移除因滯後特徵等原因產生的缺失值行,生成最終訓練數據集。 ## 模型設計 模型選型:隨機森林 模型評估指標: 準確率、召回率、F1 分數、AUC。 ### 模型驗證 測試數據:劃分訓練集與測試集,進行交叉驗證。 效能測試:模型推斷速度、系統響應時間。 固定移動視窗,訓練資料使用60天,並測試5天效果最好 | 訓練集天數 | 測試集天數 | 平均 F1 scores | F1標準差 | |---|---|---|---| | 60 | 5 | 0.6357 | 0.04945 | 以測試集60天,測試集5天效果通常最好 表示 資料時間用太長反而會有噪音出現 > 符合常理,當沖股通常只是短期,非長期 :::info 故模型可只使用`60`天訓練,並每`5`個交易日更新 但當然,每天更新模型的效果一定最好 ::: :::spoiler 其餘測試天數組合 結果 | 訓練集天數 | 測試集天數 | 平均 F1 scores | |---|---|---| | 20 | 10 | 0.6176 | | 20 | 5 | 0.6209 | | 30 | 10 | 0.6203 | | 30 | 5 | 0.6239 | | 60 | 20 | 0.6254 | | 60 | 10 | 0.6270 | | 60 | 5 | 0.6310 | | 90 | 10 | 0.6281 | | 90 | 5 | 0.6298 | | 訓練集天數 | 測試集天數 | 平均 F1 scores | F1標準差 | |---|---|---|---| | 60 | 5 | 0.6310 | 0.05504 | | 60 | 3 | 0.6275 | 0.06808 | | 60 | 1 | 0.6299 | 0.09436 | | 70 | 5 | 0.6280 | 0.05865 | | 70 | 3 | 0.6302 | 0.06430 | | 70 | 1 | 0.6294 | 0.09582 | | 80 | 5 | 0.6287 | 0.05979 | | 80 | 3 | 0.6301 | 0.06650 | | 80 | 1 | 0.6311 | 0.09659 | | 90 | 5 | 0.6298 | 0.06028 | | 90 | 3 | 0.6292 | 0.06999 | | 90 | 1 | 0.6340 | 0.09538 | ::: # 結果 ## 當沖股特性 - 通常是成交量大、周轉率高 - 股價變動大的 - 短期 ## 當沖股預測結果分析 ![20250110特徵重要程度](https://hackmd.io/_uploads/HyG-bS0Ikl.png) 總共253個特徵,特徵重要性和為100%,前20個特徵占**72%**,前30個特徵占**81%** | 特徵名稱 | 特徵重要性 | |---|---| | 當沖成交比例(市場) | 7.59% | | 當沖成交量 | 7.23% | | 平均3日當沖成交量 (rolling_mean_3) | 6.22% | | 成交量 | 5.87% | | 膝點標註 | 4.97% | | 平均5日當沖成交量 (rolling_mean_5) | 4.89% | | 資買 | 4.36% | | 平均10日當沖成交量 (rolling_mean_10) | 4.13% | | 成交筆數 | 3.95% | | 當沖成交量_lag_1 | 2.93% | | 資賣 | 2.85% | | 當沖成交量_lag_2 | 2.75% | | 非當沖成交量 | 2.71% | | 當沖股(膝點)_lag_1 | 2.43% | | 券賣 | 2.15% | | 當沖股(膝點)_lag_2 | 1.81% | | 當沖成交量_lag_4 | 1.55% | | 當沖成交量_lag_3 | 1.36% | | 資券相抵 | 1.35% | | 券餘 | 1.32% | 以其中一天(20250108),預測隔天(20250109)其預測值 ![20250110機率分布](https://hackmd.io/_uploads/B1GWZHA8kg.png) | 股票代號 | 股票名稱 | 被當沖概率 | 指數彙編分類 | 上市櫃 | |---|---|---|---|---| | 4931 | 新盛力 | 94.43% | 電子中游-NB與手機零組件 | 0 | | 6558 | 興能高 | 90.72% | 電子中游-NB與手機零組件 | 1 | | 3645 | 達邁 | 86.17% | 電子上游-PCB-製造 | 1 | | 3450 | 聯鈞 | 83.88% | 電子上游-IC-封測 | 1 | | 2374 | 佳能 | 83.55% | 電子下游-數位相機 | 1 | | 4979 | 華星光 | 80.83% | 電子中游-通訊設備 | 0 | | 3078 | 僑威 | 80.46% | 電子中游-電源供應器 | 0 | | 6191 | 精成科 | 79.05% | 電子上游-PCB-製造 | 1 | | 3211 | 順達 | 78.69% | 電子中游-NB與手機零組件 | 0 | | 3013 | 晟銘電 | 77.11% | 電子中游-機殼 | 1 | | 4991 | 環宇-KY | 75.31% | 電子上游-IC-其他 | 0 | | 2498 | 宏達電 | 74.64% | 電子下游-手機製造 | 1 | | 6215 | 和椿 | 74.53% | 軟體-系統整合 | 1 | | 2618 | 長榮航 | 69.58% | 傳產-航運 | 1 | | 1524 | 耿鼎 | 68.61% | 傳產-汽車零組件 | 1 | | 6125 | 廣運 | 68.58% | 電子中游-儀器設備工程 | 0 | | 4977 | 眾達-KY | 68.06% | 電子中游-網通 | 1 | | 3706 | 神達 | 67.75% | 電子中游-EMS | 1 | | 2609 | 陽明 | 64.92% | 傳產-航運 | 1 | | 3323 | 加百裕 | 64.70% | 電子中游-NB與手機零組件 | 0 | ## 覆蓋率 若以今日當沖成交量排名前30名,預測隔日也為前30名的當沖股,覆蓋率平均60% ![猜隔天當沖_覆蓋率20241209_20250108](https://hackmd.io/_uploads/Syx97xKPvJx.png) 若以模型採取機率排名前30當作隔日預測,平均覆蓋率約85.8%(最高100%,最低63.33%) ![當沖_覆蓋率20250117](https://hackmd.io/_uploads/rJXpJtwDJg.png) 化成每日的覆蓋率圖如下: (https://hackmd.io/_uploads/rkgZgYDD1x.png) ![當沖20250117](https://hackmd.io/_uploads/S1XaytvDyx.png) 下半年模型平均預測較準確(尤其是7、8月) 上半年則相對較差(尤其是2月連假後) --- 單一個月的結果如下: ![當沖_覆蓋率20241209_20250108] # 完整程式碼 ```python= # 套件 import numpy as np import pandas as pd from WCFAdox import PCAX # CMoney套件(獲取資料用) import matplotlib import matplotlib.pyplot as plt from datetime import datetime from kneed import KneeLocator # 膝點 from sklearn.metrics import roc_curve, auc , f1_score from sklearn.metrics import confusion_matrix, classification_report from sklearn.preprocessing import OneHotEncoder import logging import warnings # 處理警告訊息 warnings.simplefilter(action='ignore', category=FutureWarning) # category類型 # Windows 處理中文字體 # # 設定字體預設是"serif", "sans-serif", "cursive", "fantasy", "monospace" # plt.rcParams['font.family'] = 'sans-serif' # # 設定MPL的font.sans-serif # plt.rcParams['font.sans-serif']=['Microsoft YaHei', 'DejaVu Sans', 'Bitstream Vera Sans', 'Computer Modern Sans Serif', 'Lucida Grande', 'Verdana', 'Geneva', 'Lucid', 'Arial', 'Helvetica', 'Avant Garde', 'sans-serif'] #用来正常显示中文标签 # plt.rcParams['axes.unicode_minus'] = False #用来正常显示负号 # 取得資料 #設定連線主機IP並產生物件; 請依實際狀況調整http或https PX=PCAX("http://192.168.10.30") PX._returnAdoxVer() #上市個股2年基本資料 tse_info_data=PX.Pal_Data("上市櫃公司基本資料","Y","2023","2024", colist="年度,股票代號,股票名稱,產業名稱,產業指數名稱,指數彙編分類", ps="<CM代號,1>") #上櫃個股2年基本資料 otc_info_data=PX.Pal_Data("上市櫃公司基本資料","Y","2023","2024", colist="年度,股票代號,股票名稱,產業名稱,產業指數名稱,指數彙編分類", ps="<CM代號,2>") #上市個股2年開高低收 tse_price_data=PX.Pal_Data("日收盤表排行","D","20230101","20241031", colist="日期,股票代號,開盤價,最高價,最低價,收盤價,漲跌,[漲幅(%)],[振幅(%)],漲跌停,成交量,[成交量變動(%)],成交筆數,[總市值(億)],本益比,股價淨值比,[週轉率(%)]", ps="<CM代號,1>") #上櫃個股2年開高低收 otc_price_data=PX.Pal_Data("日收盤表排行","D","20230101","20241031", colist="日期,股票代號,開盤價,最高價,最低價,收盤價,漲跌,[漲幅(%)],[振幅(%)],漲跌停,成交量,[成交量變動(%)],成交筆數,[總市值(億)],本益比,股價淨值比,[週轉率(%)]", ps="<CM代號,2>") #上市個股2年沖銷交易 tse_target_data=PX.Pal_Data("日當日沖銷交易","D","20230101","20241031", colist="日期,股票代號,當沖成交量,非當沖成交量", ps="<CM代號,1>") #上櫃個股2年沖銷交易 otc_target_data=PX.Pal_Data("日當日沖銷交易","D","20230101","20241031", colist="日期,股票代號,當沖成交量,非當沖成交量", ps="<CM代號,2>") #上市個股2年日融資券排行 tse_marginTrad_data=PX.Pal_Data("日融資券排行","D","20230101","20241031", colist="日期,股票代號,資買,資賣,資現償,資餘,資增減,資限,券買,券賣,[券賣金額(千)],券現償,券餘,券增減,資券相抵,券資比,資使用率,券使用率,當沖比率,[融資維持率(%)],[整體維持率(%)]", ps="<CM代號,1>") #上市個股2年日報酬率比較表 tse_return_data=PX.Pal_Data("日報酬率比較表","D","20230101","20241031", colist="日期,股票代號,還原收盤價,[日報酬率(%)],[週報酬率(%)],[月報酬率(%)],[開盤價(相對大盤)],[最高價(相對大盤)],[最低價(相對大盤)],[收盤價(相對大盤)],[漲跌(相對大盤)],[漲幅%(相對大盤)],[與大盤比日報酬率(%)],[與大盤比週報酬率(%)],[與大盤比月報酬率(%)],[殖利率(%)]", ps="<CM代號,1>") #上市個股2年日常用技術指標表 tse_techIndex_data=PX.Pal_Data("日常用技術指標表","D","20230101","20241031", colist="日期,股票代號,[K(9)],[D(9)],[RSI(5)],[RSI(10)],[DIF],[MACD],[DIF-MACD],[W%R(5)],[W%R(10)],[+DI(14)],[-DI(14)],[ADX(14)],[週K(9)],[週D(9)],[週RSI(10)],[週DIF],[週MACD],[週DIF-週MACD],[週+DI(14)],[週-DI(14)],[週ADX(14)],[Alpha(250D)],[Beta係數(21D)],[Beta係數(65D)],[乖離率(20日)],[乖離率(60日)],[相對強弱比(日)],[相對強弱比(週)],[近一月歷史波動率(%)],[EWMA波動率(%)]", ps="<CM代號,1>") #上市個股2年日常用技術指標表Ⅱ tse_techIndex2_data=PX.Pal_Data("日常用技術指標表Ⅱ","D","20230101","20241031", colist="日期,股票代號,[保力加通道–頂部(20)],[保力加通道–均線(20)],[保力加通道–底部(20)],[SAR],[TR(1)],[ADXR(14)],[+DM(14)],[-DM(14)],[週TR(14)],[週ADXR(14)],[週+DM(14)],[週-DM(14)],紅三兵,黑三兵,一星二陽,一星二陰,孤島晨星,孤島夜星,長紅吞噬,長黑吞噬,低檔長紅,高檔長黑,低檔槌子,高檔吊人", ps="<CM代號,1>") #上櫃個股2年日融資券排行 otc_marginTrad_data=PX.Pal_Data("日融資券排行","D","20230101","20241031", colist="日期,股票代號,資買,資賣,資現償,資餘,資增減,資限,券買,券賣,[券賣金額(千)],券現償,券餘,券增減,資券相抵,券資比,資使用率,券使用率,當沖比率,[融資維持率(%)],[整體維持率(%)]", ps="<CM代號,2>") #上櫃個股2年日報酬率比較表 otc_return_data=PX.Pal_Data("日報酬率比較表","D","20230101","20241031", colist="日期,股票代號,還原收盤價,[日報酬率(%)],[週報酬率(%)],[月報酬率(%)],[開盤價(相對大盤)],[最高價(相對大盤)],[最低價(相對大盤)],[收盤價(相對大盤)],[漲跌(相對大盤)],[漲幅%(相對大盤)],[與大盤比日報酬率(%)],[與大盤比週報酬率(%)],[與大盤比月報酬率(%)],[殖利率(%)]", ps="<CM代號,2>") #上櫃個股2年日常用技術指標表 otc_techIndex_data=PX.Pal_Data("日常用技術指標表","D","20230101","20241031", colist="日期,股票代號,[K(9)],[D(9)],[RSI(5)],[RSI(10)],[DIF],[MACD],[DIF-MACD],[W%R(5)],[W%R(10)],[+DI(14)],[-DI(14)],[ADX(14)],[週K(9)],[週D(9)],[週RSI(10)],[週DIF],[週MACD],[週DIF-週MACD],[週+DI(14)],[週-DI(14)],[週ADX(14)],[Alpha(250D)],[Beta係數(21D)],[Beta係數(65D)],[乖離率(20日)],[乖離率(60日)],[相對強弱比(日)],[相對強弱比(週)],[近一月歷史波動率(%)],[EWMA波動率(%)]", ps="<CM代號,2>") #上櫃個股2年日常用技術指標表Ⅱ otc_techIndex2_data=PX.Pal_Data("日常用技術指標表Ⅱ","D","20230101","20241031", colist="日期,股票代號,[保力加通道–頂部(20)],[保力加通道–均線(20)],[保力加通道–底部(20)],[SAR],[TR(1)],[ADXR(14)],[+DM(14)],[-DM(14)],[週TR(14)],[週ADXR(14)],[週+DM(14)],[週-DM(14)],紅三兵,黑三兵,一星二陽,一星二陰,孤島晨星,孤島夜星,長紅吞噬,長黑吞噬,低檔長紅,高檔長黑,低檔槌子,高檔吊人", ps="<CM代號,2>") #上市台灣50個股2年開高低收 tse_t50_data=PX.Pal_Data("日收盤表排行","D","20230101","20241031", colist="日期,股票代號", ps="<CM特殊,1>") #上市台灣100個股2年開高低收(不含台灣50) tse_t100_data=PX.Pal_Data("日收盤表排行","D","20230101","20241031", colist="日期,股票代號", ps="<CM特殊,3>") # 合併資料 tse_t50_data["台灣50"] = 1 tse_t100_data["台灣100"] = 1 tse_info_data["上市櫃"] = 1 tse_price_data["上市櫃"] = 1 tse_target_data["上市櫃"] = 1 tse_marginTrad_data["上市櫃"] = 1 tse_return_data["上市櫃"] = 1 tse_techIndex_data["上市櫃"] = 1 tse_techIndex2_data["上市櫃"] = 1 otc_info_data["上市櫃"] = 0 otc_price_data["上市櫃"] = 0 otc_target_data["上市櫃"] = 0 otc_marginTrad_data["上市櫃"] = 0 otc_return_data["上市櫃"] = 0 otc_techIndex_data["上市櫃"] = 0 otc_techIndex2_data["上市櫃"] = 0 info_data=pd.concat([tse_info_data,otc_info_data]) price_data=pd.concat([tse_price_data,otc_price_data]) target_data=pd.concat([tse_target_data,otc_target_data]) marginTrad_data = pd.concat([tse_marginTrad_data,otc_marginTrad_data]) return_data = pd.concat([tse_return_data,otc_return_data]) techIndex_data = pd.concat([tse_techIndex_data,otc_techIndex_data]) techIndex2_data = pd.concat([tse_techIndex2_data,otc_techIndex2_data]) merged_df = pd.merge(price_data, tse_t50_data, on=["日期", "股票代號"], how="left") # 將 '台灣50' 的缺失值補 0 merged_df['台灣50'] = merged_df['台灣50'].fillna(0) merged_df = pd.merge(merged_df, tse_t100_data, on=["日期", "股票代號"], how="left") # 將 '台灣100' 的缺失值補 0 merged_df['台灣100'] = merged_df['台灣100'].fillna(0) merged_df = pd.merge(merged_df, target_data, on=["日期", "股票代號", "上市櫃"], how="inner") merged_df = pd.merge(merged_df, marginTrad_data, on=["日期", "股票代號", "上市櫃"], how="inner") merged_df = pd.merge(merged_df, return_data, on=["日期", "股票代號", "上市櫃"], how="inner") merged_df = pd.merge(merged_df, techIndex_data, on=["日期", "股票代號", "上市櫃"], how="inner") merged_df = pd.merge(merged_df, techIndex2_data, on=["日期", "股票代號", "上市櫃"], how="inner") # 處理月資料併入日資料 # 提取日資料的年度 merged_df['年度'] = merged_df['日期'].str[:4].astype(object) # 根據年度合併 merged_df = pd.merge(merged_df, info_data, on=['年度', '股票代號', '上市櫃'], how='left').drop(columns="年度") # 刪除變數釋放記憶體 # del tse_t50_data, tse_t100_data, tse_info_data, tse_price_data, tse_target_data, tse_marginTrad_data, tse_return_data, tse_techIndex_data, tse_techIndex2_data # del otc_info_data, otc_price_data, otc_target_data, otc_marginTrad_data, otc_return_data, otc_techIndex_data, otc_techIndex2_data # 處理資料型態 # 設定索引為日期,確保是時間序列 merged_df['日期'] = pd.to_datetime(merged_df['日期']) merged_df.sort_values(by=['日期', '股票代號'], ascending=[True, True], inplace=True) # 將"股票代號"欄位轉換為類別型態 merged_df[['股票代號', '股票名稱', '產業名稱', '產業指數名稱', '指數彙編分類']] = merged_df[['股票代號', '股票名稱', '產業名稱', '產業指數名稱', '指數彙編分類']].astype('category') numeric_cols = merged_df.select_dtypes(include=['object']).columns merged_df[numeric_cols] = merged_df[numeric_cols].apply(pd.to_numeric, errors='coerce') merged_df.info() # 處理殘缺值 # Nan補0 merged_df["本益比"] = merged_df["本益比"].fillna(0) merged_df["成交量變動(%)"] = merged_df["成交量變動(%)"].fillna(0) # 刪除Nan的列資料 data_dropna_df = merged_df.dropna() # 特徵工程-新欄位 data_dropna_df["當沖成交比例"] = data_dropna_df["當沖成交量"] / (data_dropna_df["當沖成交量"] + data_dropna_df["非當沖成交量"]) data_dropna_df["當沖成交比例"] = data_dropna_df["當沖成交比例"].fillna(0) # 計算每日市場總當沖成交量 market_daily_volume = data_dropna_df.groupby('日期')['當沖成交量'].sum().reset_index() market_daily_volume = market_daily_volume.rename(columns={'當沖成交量': '市場總當沖成交量'}) # 將市場總當沖成交量合併到原始資料中 data_dropna_df = data_dropna_df.merge(market_daily_volume, on='日期', how='left') # 計算正確的當沖成交比例 data_dropna_df["當沖成交比例(市場)"] = data_dropna_df["當沖成交量"] / data_dropna_df["市場總當沖成交量"] def add_time_features(df, date_column='日期'): """ 為資料集增加時間相關特徵 Parameters: ----------- df : pandas.DataFrame 含有日期欄位的資料框 date_column : str 日期欄位的名稱 Returns: -------- pandas.DataFrame 增加時間特徵後的資料框 """ # 確保日期欄位為datetime格式 df = df.copy() if not pd.api.types.is_datetime64_any_dtype(df[date_column]): df[date_column] = pd.to_datetime(df[date_column]) # 基本時間特徵 df['年'] = df[date_column].dt.year df['月'] = df[date_column].dt.month df['月中日'] = df[date_column].dt.day # 月中的第幾天(1-31) df['年中日'] = df[date_column].dt.dayofyear # 年中的第幾天(1-366) df['星期'] = df[date_column].dt.dayofweek + 1 # 1-7,其中1代表星期一 df['年週'] = df[date_column].dt.isocalendar().week # 年中的第幾週(1-52) df['季度'] = df[date_column].dt.quarter # 年中的第幾季(1-4) # 月初/月中/月底標記 df['月初'] = (df['月中日'] <= 5).astype(int) df['月底'] = (df['月中日'] >= 25).astype(int) df['月中'] = ((df['月中日'] > 5) & (df['月中日'] < 25)).astype(int) # 季初/季末月標記 df['季初月'] = df['月'].isin([1, 4, 7, 10]).astype(int) df['季末月'] = df['月'].isin([3, 6, 9, 12]).astype(int) # 年初/年末月標記 df['年初月'] = (df['月'] <= 3).astype(int) df['年末月'] = (df['月'] >= 10).astype(int) # 周期性特徵 - 使用三角函數 # 月份的周期性(12個月) df['month_sin'] = np.sin(2 * np.pi * df['月']/12) df['month_cos'] = np.cos(2 * np.pi * df['月']/12) # 星期的周期性(5個工作日) df['weekday_sin'] = np.sin(2 * np.pi * (df['星期']-1)/5) df['weekday_cos'] = np.cos(2 * np.pi * (df['星期']-1)/5) # 月中日的周期性 df['monthday_sin'] = np.sin(2 * np.pi * df['月中日']/31) df['monthday_cos'] = np.cos(2 * np.pi * df['月中日']/31) # 年中日的周期性 df['yearday_sin'] = np.sin(2 * np.pi * df['年中日']/365) df['yearday_cos'] = np.cos(2 * np.pi * df['年中日']/365) # 是否為假日前後的交易日 # 注意:這裡的實現比較簡單,可能需要根據實際的假日日曆進行調整 df['週一'] = (df['星期'] == 1).astype(int) # 假日後 df['週五'] = (df['星期'] == 5).astype(int) # 假日前 return df # 使用: data_dropna_df = add_time_features(data_dropna_df) # 創建 One-Hot Encoder encoder = OneHotEncoder(sparse_output=False) # 對類別型特徵進行編碼 encoded_features = encoder.fit_transform(data_dropna_df[['產業名稱', '指數彙編分類']]) # 獲取 One-Hot 編碼後的欄位名稱 encoded_feature_names = encoder.get_feature_names_out(['產業名稱', '指數彙編分類']) # 建立編碼後的 DataFrame,並將欄位名稱設置為對應名稱 encoded_df = pd.DataFrame(encoded_features, columns=encoded_feature_names, index=data_dropna_df.index) # 合併原始資料(去掉類別型欄位)和編碼後的特徵 df_encoded = pd.concat([data_dropna_df.drop(columns=['股票名稱', '產業名稱', '產業指數名稱', '指數彙編分類']), encoded_df], axis=1) # 計算膝點 # 需要按日期分組計算每日的膝點 # 新增膝點標註欄位 (0 或 1) df_encoded['膝點標註'] = 0 # 儲存每日膝點詳細資訊的 DataFrame knee_points_df = pd.DataFrame(columns=['日期', '膝點排序後索引', '膝點排序後數值']) for date in df_encoded['日期'].unique(): # 篩選當天的資料 daily_data = df_encoded[df_encoded['日期'] == date] # 進行膝點檢測 volume = daily_data["當沖成交量"].values sorted_indices = np.argsort(volume)[::-1] # 由大到小排序 sorted_volume = volume[sorted_indices] # 標準化處理 x = np.arange(len(sorted_volume)) normalized_x = (x - x.min()) / (x.max() - x.min()) normalized_y = (sorted_volume - sorted_volume.min()) / (sorted_volume.max() - sorted_volume.min()) # 使用 Kneedle 找膝點 knee_locator = KneeLocator(normalized_x, normalized_y, curve="convex", direction="decreasing") turning_point = int(round(knee_locator.knee * (len(sorted_volume) - 1))) # 膝點的索引 turning_point_value = sorted_volume[turning_point] # 膝點對應的數值 # 紀錄每日膝點 knee_points_df = pd.concat([knee_points_df, pd.DataFrame({ '日期': [date], '膝點排序後索引': [turning_point], '膝點排序後數值': [turning_point_value] })], ignore_index=True) # 更新膝點標註 # 找出膝點及之前的位置對應到原始 DataFrame 的索引 indices_to_update = daily_data.iloc[sorted_indices[:turning_point]].index # 找到原始索引 df_encoded.loc[indices_to_update, '膝點標註'] = 1 # 標註膝點及其之前的位置為 1 # 滯後特徵 for lag in range(1, 6): # 添加 1 到 5 天的滯後特徵 df_encoded[f'當沖成交量_lag_{lag}'] = df_encoded.groupby('股票代號')['當沖成交量'].shift(lag) for lag in range(1, 6): # 添加 1 到 5 天的滯後特徵 df_encoded[f'當沖股(膝點)_lag_{lag}'] = df_encoded.groupby('股票代號')['膝點標註'].shift(lag) for lag in [3, 5, 10, 15, 20]: # 滑動窗口特徵(移動平均值)添加 3~20 天 df_encoded[f'rolling_mean_{lag}'] = df_encoded.groupby('股票代號')['當沖成交量'].transform(lambda x: x.rolling(window=lag).mean()) df_encoded['目標'] = df_encoded.groupby('股票代號')['膝點標註'].shift(-1) # 移除因為滯後產生的Nan row data_df = df_encoded.dropna() ```