---
# System prepended metadata

title: 日本股票數據 - 特徵工程

---

# 日本股票數據 - 特徵工程

組員:
簡廷霖 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')
```