【量化分析】🐲一尾活“隆”的交易策略

TEJ 台灣經濟新報
TEJ-API 金融資料分析
25 min readAug 22, 2023

利用阿隆指標實現自動化交易策略

Photo by R M on Unsplash

本文關鍵字: 阿隆指標、自動化交易、視覺化、回測

本文重點概要:

  • 文章難度:★★★★★
  • 使用阿隆指標進行自動化交易策略
  • 計算投資組合績效表現
  • 視覺化分析結果

前言

震盪技術指標(Oscillator Technical Indicator)是一種用於分析金融市場的技術指標,用於衡量資產價格在一段時間內的超買和超賣情況。震盪指標可以幫助交易者識別市場轉折點或價格趨勢的反轉。

震盪指標的計算通常是將某種價格指標(例如收盤價、開盤價、最高價或最低價)進行處理,使其在某個範圍內波動,有些指標可能有負值。震盪指標通常是以線性的形式展示。
本次將會為各位其中的一種震盪技術指標——阿隆指標。

指標介紹

阿隆指標(Aroon Indicator)是一種用於測量金融市場趨勢強度和方向的技術分析工具。它由 Tushar Chande 於 1995 年開發。該指標由兩條線組成,即Aroon Up 和 Aroon Down。

  • Aroon Up:(窗口天數 − 上一次最高高點距今天數) / 窗口天數 × 100
    該線測量給定時間段內自最高高點以來的周期數。它反映了上漲趨勢的強度。當 Aroon Up 線接近 100 時,表示強勁的上漲趨勢。
  • Aroon Down:(窗口天數 − 上一次最低低點距今天數) / 窗口天數 × 100
    該線測量給定時間段內自最低低點以來的周期數。它反映了下跌趨勢的強度。當 Aroon Down 線接近 100 時,表示強勁的下跌趨勢。

阿隆指標的取值範圍在0到100之間。當 Aroon Up 高於 Aroon Down 時,表明上漲趨勢占優勢;當 Aroon Down 高於 Aroon Up 時,表示下跌趨勢占優勢。交易者和分析師利用這些信息來識別潛在的趨勢變化,因為兩條線的交叉可能預示著市場方向的轉變。

編輯環境與模組需求

本文使用 Mac 作業系統以及 jupyter notebook 作為編輯器。

import pandas as pd 
import re
import numpy as np
import tejapi
import plotly.express as px
import matplotlib.pyplot as plt
import matplotlib.ticker as mticker
from matplotlib.pyplot import MultipleLocator
from sklearn.linear_model import LinearRegression
import datetime

資料庫使用

資料導入

資料期間從 2018–01–01 至 2020–12–31,以電子股:鴻海精密(2317)、仁寶電腦(2324)、國巨(2327)、台積電(2330)、聯強(2347)、宏碁(2353)、鴻準(2354)、(華碩2357)、瑞昱(2379)、廣達(2382)、研華(2395) 作為實例,抓取未調整的開、高、收、低價格資料,並以報酬指數(Y9997)作為大盤進行績效比較。

stock_id = ["Y9997", "2317", "2324", "2327", "2330", "2347", "2353", "2354",
"2357", "2379", "2382", "2395"]
gte, lte = '2018-01-01', '2020-12-31'
stock = tejapi.get('TWN/APRCD',
paginate = True,
coid = stock_id,
mdate = {'gte':gte, 'lte':lte},
opts = {
'columns':[ 'mdate', 'coid', 'open_d', 'high_d', 'low_d', 'close_d', 'volume']
}
)

將 DataFrame 轉置,排除大盤資料,以股票代碼切分資料。

data_pivot = pd.pivot_table(stock[stock.coid != "Y9997"], columns="coid", index="mdate")

交易策略

首先我們需要定義下列參數:

  • principal:本金
  • cash:現金部位持有量
  • order_unit:交易單位數

由於本次實作包含多個標的,因此我們使用迴圈創建一個 dictionary 以保存下列資訊:

  • position:標的部位持有張數
  • invested_principal:投資金額

aroon_record 這個 list 記錄每支標的的 “代碼”、“日期”、“Aroon-up”、“Aroon-down” 等指數。

接著,為了在最終衡量投資組合的績效,我們建立 daily_stock_value_record daily_cash_value_record 兩個 list 記錄每日標的的 “持有部位數”、“股票價值”、“剩餘現金價值”。

當 Aroon Up 大於 80 且 Aroon Down 小於 45 時,表示目前上漲趨勢高,視為進場的訊號,以隔日開盤價買入一單位。

當 Aroon Up 小於 45 、 Aroon Down 大於 55 且 兩指標相差大於 15 時,視為接下來有可能股價跌落的訊號,以隔日開盤價拋售持有部位。

當 Aroon Up 大於 55 、 Aroon Down 小於 45 且 兩指標相差大於 15 時,以及滿足本金充足、標的投入成本不超過本金的百分之二十時,則繼續加碼一單位。

def Aroon_strategy_multi(data_pivot, principal, cash, order_unit, n):
trade_book = pd.DataFrame()
aroon = pd.DataFrame(columns=["coid", "mdate", "AroonUp", "AroonDown"])

daily_stock_value_record = []
daily_cash_value_record = []

coid_dict = {}
for i in list(data_pivot.high_d.columns):
coid_dict.update({i:{"position":0, "invested_principal":0}})

for ind in range(len(data_pivot.index) - n -1):
for col in data_pivot.high_d.columns:
high_period = data_pivot.high_d[col].iloc[ ind : ind+n].reset_index()
AroonUp = round((high_period.idxmax()[1] + 1)/n*100)

low_period = data_pivot.low_d[col].iloc[ ind : ind+n].reset_index()
AroonDown = round(((low_period.idxmin()[1] + 1)/n*100))


aroon = aroon.append({
"coid":col,
"mdate":data_pivot.index[ind+n],
"AroonUp":AroonUp,
"AroonDown":AroonDown,
}, ignore_index=True)

n_time = data_pivot.index[ind+n+1]
n_open = data_pivot.open_d[col].iloc[ind+n+1]


if coid_dict.get(col).get("position") == 0: #進場條件
if (AroonDown < 45) and (AroonUp > 80):
position = coid_dict.get(col).get("position")

order_time = n_time
order_price = n_open
order_unit = 1
friction_cost = (20 if order_price*1000*0.001425 < 20 else order_price*1000*0.001425)
total_cost = -1 * order_price * 1000 - friction_cost
cash += total_cost

coid_dict.update({col:{"position":position+1, "invested_principal":order_price * 1000,}})

trade_book = pd.concat([trade_book,
pd.DataFrame([col, 'Buy', order_time, 0, total_cost, order_unit, coid_dict.get(col).get("position"), cash, order_price])],
ignore_index = True, axis=1)

elif coid_dict.get(col).get("position") > 0:
if (AroonDown - AroonUp) > 15 and AroonDown > 55 and AroonUp < 45: # 出場條件
order_unit = coid_dict.get(col).get("position")
cover_time = n_time
cover_price = n_open
friction_cost = (20 if cover_price*order_unit*1000*0.001425 < 20 else cover_price*order_unit*1000*0.001425) + cover_price*order_unit*1000*0.003
total_cost = cover_price*order_unit*1000-friction_cost
cash += total_cost

coid_dict.update({col:{"position":0, "invested_principal":0}})

trade_book = pd.concat([trade_book,
pd.DataFrame([col, 'Sell', 0, cover_time, total_cost, -1*order_unit, coid_dict.get(col).get("position"), cash, cover_price])],
ignore_index = True, axis=1)

elif (AroonUp - AroonDown) > 15 and (AroonDown < 45) and AroonUp > 55 and (cash >= n_open*1000) and (coid_dict.get(col).get("invested_principal") <= 0.2 * principal): #加碼條件
order_unit = 1
order_time = n_time
order_price = n_open

position = coid_dict.get(col).get("position")

friction_cost = (20 if order_price*1000*0.001425 < 20 else order_price*1000*0.001425)
total_cost = -1 * order_price * 1000 - friction_cost
cash += total_cost

invested_principal = coid_dict.get(col).get("invested_principal")
coid_dict.update({col:{"position":position+1, "invested_principal": invested_principal + order_price*1000}})

trade_book = pd.concat([trade_book,
pd.DataFrame([col, 'Buy', order_time, 0, total_cost, order_unit, coid_dict.get(col).get("position"), cash, order_price])],
ignore_index = True, axis=1)

daily_stock_value_record.append({
"mdate": n_time,
"coid":col,
"position":coid_dict.get(col).get("position"),
"stock_value":coid_dict.get(col).get("position") * data_pivot.close_d[col].iloc[ind+n+1] * 1000,
})
daily_cash_value_record.append(cash)

for col in data_pivot.high_d.columns:# 最後一天平倉

if coid_dict.get(col).get("position") > 0:

high_period = data_pivot.high_d[col].iloc[ -n : -1].reset_index()
AroonUp = round((high_period.idxmax()[1] + 1)/n*100)
low_period = data_pivot.low_d[col].iloc[ -n : -1].reset_index()
AroonDown = round(((low_period.idxmin()[1] + 1)/n*100))

order_unit = coid_dict.get(col).get("position")
cover_price = data_pivot.open_d[col].iloc[-1]
cover_time = data_pivot.index[-1]
friction_cost = (20 if cover_price*order_unit*1000*0.001425 < 20 else cover_price*order_unit*1000*0.001425) + cover_price*order_unit*1000*0.003
cash += cover_price*order_unit*1000-friction_cost

coid_dict.update({col:{"position":0, "invested_principal": 0,}})

trade_book = pd.concat([trade_book,
pd.DataFrame([col, 'Sell',0, cover_time, cover_price*order_unit*1000-friction_cost, -1*order_unit, 0, cash, cover_price])],
ignore_index=True, axis=1)

daily_stock_value_record.append({
"mdate": data_pivot.index[-1]+datetime.timedelta(days = 1),
"coid":col,
"position":coid_dict.get(col).get("position"),
"stock_value":0,
})

daily_cash_value_record.append(cash)
value_book = pd.DataFrame(daily_stock_value_record).set_index("mdate")
value_book = pd.pivot_table(value_book, columns = "coid", index = "mdate")
value_book["cash_value"] = daily_cash_value_record


trade_book = trade_book.T
trade_book.columns = ['coid', 'BuyOrSell', 'BuyTime', 'SellTime', 'CashFlow','TradeUnit', 'HoldingPosition', 'CashValue', 'DealPrice']
trade_book['mdate'] = [trade_book.BuyTime[i] if trade_book.BuyTime[i] != 0 else trade_book.SellTime[i] for i in trade_book.index]
trade_book = trade_book.loc[:, ['coid', 'BuyOrSell', 'DealPrice', 'CashFlow', 'TradeUnit', 'HoldingPosition', 'CashValue' ,'mdate']]

return trade_book, aroon, value_book, order_unit, n)

撰寫好交易策略以及輸出後,接下來就可以將參數輸入函式並執行。

principal = 10e6
cash = principal
order_unit = 0
n = 25

df, aroon, value = Aroon_strategy_multi(data_pivot, principal, cash, order_unit, n)

交易紀錄

函式會輸出三張報表:

  • 交易記錄表:記錄每筆交易的細項,包含交易行為、交易金額、交易單位等。
  • 阿隆指標記錄表:記錄每日Aroon-up、Aroon-down的指標變化情形。
  • 投組價值記錄表:記錄每日投資組合價值與持有現金狀況。
交易記錄表
阿隆指標記錄表
投組價值記錄表

績效計算

本次實作將計算投資組合的 Alpha 與 Beta 來衡量其績效,因此我們需要先計算投資組合與大盤的每日報酬率,並將計算結果分別儲存至資料表中。

value["total_value"] = value.apply(lambda x : x.sum(), axis = 1)
value["daily_return"] = value["total_value"].pct_change(periods=1)
value["market_return"] = list(stock[stock.coid == "Y9997"]["close_d"].pct_change(periods = 1)[25:])
value
計算每日報酬率

計算好上述資訊後,我們使用 sklearn 來擬合數據,以投資組合每日報酬率大盤每日報酬率進行回歸分析。

X = np.array(value["daily_return"].iloc[1:]).reshape(-1, 1)
y = np.array(value["market_return"].iloc[1:])
regressor = LinearRegression()
regressor.fit(X, y)
w_0 = regressor.intercept_
w_1 = regressor.coef_

print('alpha : ', w_0)
print('beta : ', w_1)
投資組合回測期間平均 Alpha 與 Beta

我們設定窗口期為 60 天,每日滾動計算 Alpha 與 Beta ,再將數據以視覺化圖表來進行分析。

window = 60
alpha = []
beta = []
mdate = []
for i in range(len(value) - window - 1):
X = np.array(value["daily_return"].iloc[i+1 : i+1+window]).reshape(-1, 1)
y = np.array(value["market_return"].iloc[i+1 : i+1+window])
regressor = LinearRegression()
regressor.fit(X, y)
w_0 = regressor.intercept_
w_1 = regressor.coef_
alpha.append(round(w_0, 5))
beta.append(w_1)
mdate.append(value.index[i+1+window])
fig, ax1 = plt.subplots(figsize=[16, 9], constrained_layout=True)
ax1.plot(mdate, alpha, label = "Alpha")
ax1_2 = ax1.twinx()
ax1_2.plot(mdate, beta, label = "Beta", color = "orange")

Alpha_lines, Alpha_labels = ax1.get_legend_handles_labels()
Beta_lines, Beta_labels = ax1_2.get_legend_handles_labels()
ax1.legend(Alpha_lines + Beta_lines,
Alpha_labels + Beta_labels, loc='upper right')

ax1.set_xlabel('mdate')
ax1.set_ylabel('Alpha')
ax1_2.set_ylabel('Beta')
Alpha & Bate 滾動變化圖

接著,我們再近一步繪製出個標的與大盤的走勢比較圖,標明買進點、賣出點,以及在回測期間 Aroon 指摽的變化情形。

def make_plot(stock_df, aroon_dict, record_df, coid):
# stock["mdate"] = stock["mdate"].apply(lambda x:x.strftime('%Y-%m-%d'))
mdate = stock[stock.coid == "Y9997"].mdate

benchmark = stock[stock.coid == "Y9997"].close_d

AroonUp = aroon[aroon.coid == coid].AroonUp
AroonDown = aroon[aroon.coid == coid].AroonDown
aroon_date = aroon[aroon.coid == coid].mdate

fig, axes = plt.subplots(2,1, figsize=[16, 9], constrained_layout=True)

ax1 = axes[0]
stock[stock.coid == "Y9997"].set_index("mdate").close_d.plot(ax = ax1, label = "market return")
ax1_2 = ax1.twinx()
stock[stock.coid == coid].set_index("mdate").close_d.plot(ax = ax1_2, label=f'{coid}_close', color = "lime")
stock[stock.coid == coid].set_index("mdate").open_d.plot(ax = ax1_2, label=f'{coid}_open', color = "deeppink", alpha = 0.5)
ax1_2.scatter(df[df.coid == coid].mdate, df[df.coid == coid].DealPrice, label = "BuyOrSell", color = ["orange" if i == "Buy" else "purple" for i in df[df.coid == coid].BuyOrSell])

benchmark_lines, benchmark_labels = ax1.get_legend_handles_labels()
target_lines, target_labels = ax1_2.get_legend_handles_labels()

ax1.legend(benchmark_lines + target_lines,
benchmark_labels + target_labels, loc='upper right')
ax1.set_xlabel('mdate')
ax1.set_ylabel('index')
ax1_2.set_ylabel(f'price')
ax1.set_title(f"{coid}_Aroon")

ax2 = axes[1]
aroon[aroon.coid == coid].set_index("mdate").AroonUp.plot(ax = ax2, label = "AroonUp", color = "red")
ax2_2 = ax2.twinx()
aroon[aroon.coid == coid].set_index("mdate").AroonDown.plot(ax = ax2_2, label = "AroonDown", color = "green")

up_lines, up_labels = ax2.get_legend_handles_labels()
down_lines, down_labels = ax2_2.get_legend_handles_labels()

ax2.legend(down_lines + down_lines,
up_labels + down_labels, loc='upper right')
ax2.set_xlabel('mdate')
ax2.set_ylabel('Aroon_indicator')

fig.tight_layout()

plt.show()

定義完繪圖函式後,我們可以透過簡單的迴圈實現各標的的圖表繪製。
請注意由於最後一項標的代碼為大盤代碼 ”Y9997”,因此迴圈僅可取至倒數第二項。

for coid in stock.coid.unique()[:-1]:
make_plot(stock, aroon, value, coid)
2330 視覺化圖表

我們以 2330 作為圖表範例,在第一張圖表中,藍線為大盤收盤指數、綠線表示標的收盤價、粉線表示標的開盤價、黃點為買進點、紫點為賣出點;而第二張圖表中,紅線為 Aroon-up、綠線為 Aroon-down。

與大盤績效與市場大盤比較

最後我們簡單計算投資組合與大盤的總績效,可以發現使用 Aroon 指標交易策略的投資組合總績效勝過大盤約 17%。

print(f'大盤總績效:{stock[stock.coid == "Y9997"].close_d.iloc[-1]/stock[stock.coid == "Y9997"].close_d.iloc[0] -1}')
print(f'投資組合總績效:{value["total_value"].iloc[-1]/value["total_value"].iloc[0] -1}')

結語

透過 Alpha & Bate 滾動變化圖 可以發現,整體來說,交易策略的 Alpha 與 Beta 的波動幅度都是相對平緩的,代表投資策略相對保守;而從各標的的視覺化報表中可以清楚觀察到每支標的各自的買賣點,分析在整個投資組合中,哪些標的才是獲利的主要關鍵。由此延伸,使用者可近一步撰寫搭配的選股策略,實現從選股、下單到績效計算的自動化一條 “隆” 服務。

溫馨提醒,本次策略與標的僅供參考,不代表任何商品或投資上的建議。之後也會介紹使用TEJ資料庫來建構各式指標,並回測指標績效,所以歡迎對各種交易回測有興趣的讀者,選購TEJ E-Shop的相關方案,用高品質的資料庫,建構出適合自己的交易策略。

原始碼

延伸閱讀

相關連結

給我們鼓勵
之後會持續分享更多財金資料庫的應用
如果你的覺得今天的文章不錯,可以幫我們在下面的 掌聲 icon 點 1下
如果覺得超讚,可以按住 掌聲 icon 不放直到 50 下
有任何想法歡迎點選 留言 icon和我們討論

--

--

TEJ 台灣經濟新報
TEJ-API 金融資料分析

TEJ 為台灣本土第一大財經資訊公司,成立於 1990 年,提供金融市場基本分析所需資訊,以及信用風險、法遵科技、資產評價、量化分析及 ESG 等解決方案及顧問服務。鑒於財務金融領域日趨多元與複雜,TEJ 結合實務與學術界的精英人才,致力於開發機器學習、人工智慧 AI 及自然語言處理 NLP 等新技術,持續提供創新服務