# 日本股票數據 - 特徵工程 組員: 簡廷霖 07155134 林謙穎 07155156 ## 目標 我們將使用機器學習模型的**預測來識別可能上漲或下跌的資產**,因此我們**可以相應地進入市場中性的多頭和空頭頭寸**。該方法類似於”線性模型 – 從風險因素到回報預測”和”工作流程 – 從模型到策略回測”中使用線性回歸的初始交易策略。 ## 需要的東西 環境的套件:arrow_right: [環境套件](https://drive.google.com/file/d/14-X-HPuLkSJim19jHP5RJh5lFU7GYTbU/view?usp=sharing) python版本:arrow_right: 3.8 需要的資料(.csv) :arrow_right:[DATA](https://drive.google.com/drive/folders/1bAZ57tscH5CSQuei5zlpeISF0zY5mE4Z?usp=sharing) ## 用到的套件 ```python= import warnings warnings.filterwarnings('ignore') %matplotlib inline from pathlib import Path import numpy as np import pandas as pd import talib import matplotlib.pyplot as plt import seaborn as sns sns.set_style('white') idx = pd.IndexSlice ``` ## 取得資料(Stooq Japanese Equity data 2014-2019) 我們使用 Stooq 提供的數據為日本股票設計一個策略。Stooq 是一家波蘭數據提供商,目前為各種資產類別、市場和頻率提供有趣的數據集。 雖然數據的來源和質量幾乎沒有透明度,但它具有目前免費的強大優勢。換句話說,我們可以每天、每小時和 5 分鐘的頻率對股票、債券、商品和外匯數據進行試驗,但對結果應該持謹慎態度。 我們使用了 2014-2019 年期間大約 3,000 只日本股票的價格數據。 過去 2 年將作為樣本外測試期,而前幾年將作為我們選擇模型的交叉驗證樣本。 ```python= DATA_DIR = Path('..', 'data') prices = (pd.read_hdf(DATA_DIR / 'assets.h5', 'stooq/jp/tse/stocks/prices') .loc[idx[:, '2014': '2019'], :] .loc[lambda df: ~df.index.duplicated(), :]) prices.info(show_counts=True) before = len(prices.index.unique('ticker').unique()) ``` ### 移除缺失值 本節中的代碼示例請參考 notebook japanese_equity_features。我們刪除連續缺失值超過五個的股票代碼,只保留交易量最大的 1,000 隻股票。 ```python= prices = (prices.unstack('ticker') .sort_index() .ffill(limit=5) .dropna(axis=1) .stack('ticker') .swaplevel()) prices.info(show_counts=True) after = len(prices.index.unique('ticker').unique()) print(f'Before: {before:,.0f} after: {after:,.0f}') ``` ### 保留交易最多的符號 ```python= dv = prices.close.mul(prices.volume) keep = dv.groupby('ticker').median().nlargest(1000).index.tolist() prices = prices.loc[idx[keep, :], :] prices.info(show_counts=True) ``` ## 特徵工程 特徵工程是最大限度地從原始數據中提取特徵以供算法和模型使用。 特徵工程包括:特徵使用方案、特徵獲取方案、特徵處理、特徵監控。 ### 計算週期回報 ```python= intervals = [1, 5, 10, 21, 63] returns = [] by_ticker = prices.groupby(level='ticker').close for t in intervals: returns.append(by_ticker.pct_change(t).to_frame(f'ret_{t}')) returns = pd.concat(returns, axis=1) returns.info(show_counts=True) ``` ### 刪除異常值 ```python= max_ret_by_sym = returns.groupby(level='ticker').max() percentiles = [0.001, .005, .01, .025, .05, .1] percentiles += [1-p for p in percentiles] max_ret_by_sym.describe(percentiles=sorted(percentiles)[6:]) quantiles = max_ret_by_sym.quantile(.95) to_drop = [] for ret, q in quantiles.items(): to_drop.extend(max_ret_by_sym[max_ret_by_sym[ret]>q].index.tolist()) to_drop = pd.Series(to_drop).value_counts() to_drop = to_drop[to_drop > 1].index.tolist() len(to_drop) prices = prices.drop(to_drop, level='ticker') prices.info(show_counts=True) ``` [表格](https://drive.google.com/file/d/1WwYaKG3hkwP47PXrPpkLwoqMyDru5TNs/view?usp=sharing) ### 計算相對回報百分位數 ```python= returns = [] by_sym = prices.groupby(level='ticker').close for t in intervals: ret = by_sym.pct_change(t) rel_perc = (ret.groupby(level='date') .apply(lambda x: pd.qcut(x, q=20, labels=False, duplicates='drop'))) returns.extend([ret.to_frame(f'ret_{t}'), rel_perc.to_frame(f'ret_rel_perc_{t}')]) returns = pd.concat(returns, axis=1) ``` ### 技術指標 #### 百分比價格振盪器 百分比價格振盪器 (PPO):移動平均收斂/發散(MACD) 指標的標準化版本,用於衡量 14 天和 26 天指數移動平均線之間的差異,以捕捉資產動量的差異。 Calculation: PPO Line: {(12-day EMA - 26-day EMA)/26-day EMA} x 100 Signal Line: 9-day EMA of PPO PPO Histogram: PPO - Signal Line ```python= ppo = prices.groupby(level='ticker').close.apply(talib.PPO).to_frame('PPO') ``` #### 歸一化平均真實範圍 標準化平均真實範圍 (NATR):以一種可以跨資產比較的方式衡量價格波動。 Calculation: NATR = ATR(n) / Close * 100 Where: ATR(n) = Average True Range over ‘n’ periods. ```python= natr = prices.groupby(level='ticker', group_keys=False).apply(lambda x: talib.NATR(x.high, x.low, x.close)).to_frame('NATR') ``` #### 相對強弱指標 相對強弱指數 (RSI):另一個流行的動量指標。 Calculation: RSI = 100 – 100/ (1 + RS) RS = Average Gain of n days UP / Average Loss of n days DOWN ```python= rsi = prices.groupby(level='ticker').close.apply(talib.RSI).to_frame('RSI') ``` #### 布林帶 布林帶:移動平均線與移動標準差的比率,用於識別均值回歸的機會。 計算公式 %B = (目前價格 - 下軌) / (上軌 - 下軌Lower Band) 基本原理:這關於價格與上軌和下軌之間的關係。有六個基本關係可以量化。 ```python= def get_bollinger(x): u, m, l = talib.BBANDS(x) return pd.DataFrame({'u': u, 'm': m, 'l': l}) bbands = prices.groupby(level='ticker').close.apply(get_bollinger) ``` ### 結合特點 ```python= data = pd.concat([prices, returns, ppo, natr, rsi, bbands], axis=1) data['bbl'] = data.close.div(data.l) data['bbu'] = data.u.div(data.close) data = data.drop(['u', 'm', 'l'], axis=1) data.bbu.corr(data.bbl, method='spearman') ``` ### 隨機樣本代碼的繪圖指標 ```python= indicators = ['close', 'bbl', 'bbu', 'PPO', 'NATR', 'RSI'] ticker = np.random.choice(data.index.get_level_values('ticker')) (data.loc[idx[ticker, :], indicators].reset_index('ticker', drop=True) .plot(lw=1, subplots=True, figsize=(16, 10), title=indicators, layout=(3, 2), legend=False)) plt.suptitle(ticker, fontsize=14) sns.despine() plt.tight_layout() plt.subplots_adjust(top=.95) data = data.drop(prices.columns, axis=1) ``` [圖表](https://drive.google.com/file/d/1LahI3vz463VyuDTX_v3-0YM3x1xBH8gm/view?usp=sharing) ### 創建時間段指標 ```python= dates = data.index.get_level_values('date') data['weekday'] = dates.weekday data['month'] = dates.month data['year'] = dates.year ``` ## 計算遠期回報 ```python= outcomes = [] by_ticker = data.groupby('ticker') for t in intervals: k = f'fwd_ret_{t:02}' outcomes.append(k) data[k] = by_ticker[f'ret_{t}'].shift(-t) data.info(null_counts=True) data.to_hdf('data.h5', 'stooq/japan/equities') ```