組員:
林謙穎 07155156
我們將使用機器學習模型的預測來識別可能上漲或下跌的資產,因此我們可以相應地進入市場中性的多頭和空頭頭寸。該方法類似於”線性模型 – 從風險因素到回報預測”和”工作流程 – 從模型到策略回測”中使用線性回歸的初始交易策略。
環境的套件
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提供的數據為日本股票設計一個策略。Stooq 是一家波蘭數據提供商,目前為各種資產類別、市場和頻率提供有趣的數據集。
雖然數據的來源和質量幾乎沒有透明度,但它具有目前免費的強大優勢。換句話說,我們可以每天、每小時和 5 分鐘的頻率對股票、債券、商品和外匯數據進行試驗,但對結果應該持謹慎態度。
我們使用了 2014-2019 年期間大約 3,000 只日本股票的價格數據。
過去 2 年將作為樣本外測試期,而前幾年將作為我們選擇模型的交叉驗證樣本。
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())
我們刪除連續缺失值超過五個的股票代碼,只保留交易量最大的 1,000 隻股票。
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}')
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)
特徵工程是最大限度地從原始數據中提取特徵以供算法和模型使用。
特徵工程包括:特徵使用方案、特徵獲取方案、特徵處理、特徵監控。
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)
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)
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)
從TA-Lib的158個技術指標找比較有興趣的出來分析
百分比價格振盪器 (PPO):移動平均收斂/發散(MACD) 指標的標準化版本,用於衡量 14 天和 26 天指數移動平均線之間的差異,以捕捉資產動量的差異。
Calculation:
PPO Line: {(14-day EMA - 26-day EMA)/26-day EMA} x 100
Signal Line: 9-day EMA of PPO
PPO Histogram: PPO - Signal Line
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.
natr = prices.groupby(level='ticker', group_keys=False).apply(lambda x: talib.NATR(x.high, x.low, x.close)).to_frame('NATR')
相對強弱指數 (RSI):另一個流行的動量指標。
用來測量價格動向的快慢(速度)和變化(幅度)。以RSI之高低來決定買賣時機是根據漲久必跌,跌久必漲之原則。
Calculation:
RSI = 100 – 100/ (1 + RS)
RS = Average Gain of n days UP / Average Loss of n days DOWN
rsi = prices.groupby(level='ticker').close.apply(talib.RSI).to_frame('RSI')
布林帶:移動平均線與移動標準差的比率,用於識別均值回歸的機會。
計算公式
%B = (目前價格 - 下軌) / (上軌 - 下軌Lower Band)
基本原理:這關於價格與上軌和下軌之間的關係。有六個基本關係可以量化。
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)
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')
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)
CLOSE:收盤價持續遞增。
BBL:在2016年中百分比中震盪較大。其餘還算穩定。
PPO:百分比中震盪較穩定。
NATR:有幾個區間百分比中波動較大。其餘波動較小。
RSI:測量結果,當n=14時,指數最具代表性。他指出當某證券的RSI升至70時,代表該證券已被超買(Overbought),投資者應考慮出售該證券。相反,當證券RSI跌至30時,代表證券被超賣。
dates = data.index.get_level_values('date')
data['weekday'] = dates.weekday
data['month'] = dates.month
data['year'] = dates.year
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')
=print(data)
data.iloc[?]