延續昨天Day13的內容,今天會引用Youtuber「neurotrader」的程式碼。他在Automated Head and Shoulders Chart Pattern in Python影片中提到頭肩型態(包含頭肩底與頭肩底)識別。在此也會引用他的程式碼,並嘗試在股價資料中找尋頭肩底型態。
今天所談的是完整的頭肩型態識別演算法,相對來說它也較為複雜。且作者(neurotrader)參照了「Technical Analysis for Algorithmic Pattern Recognition」這本書的規則進行實作,以更為嚴謹的方式進行頭肩型態的識別。
先從引用函式原型(或函式介面)與資料結構說起:
# https://github.com/neurotrader888/TechnicalAnalysisAutomation/blob/main/head_shoulders.py
# 函式原型
def find_hs_patterns(data: np.array, order:int, early_find:bool = False)
# 資料結構
@dataclass
class HSPattern:
# True if inverted, False if not. Inverted is "bullish" according to technical analysis dogma
inverted: bool
# Indices of the parts of the H&S pattern
l_shoulder: int = -1
r_shoulder: int = -1
l_armpit: int = -1
r_armpit: int = -1
head: int = -1
# Price of the parts of the H&S pattern. _p stands for price.
l_shoulder_p: float = -1
r_shoulder_p: float = -1
l_armpit_p: float = -1
r_armpit_p: float = -1
head_p: float = -1
start_i: int = -1
break_i: int = -1
break_p: float = -1
neck_start: float = -1
neck_end: float = -1
# Attributes
neck_slope: float = -1
head_width: float = -1
head_height: float = -1
pattern_r2: float = -1
等一下會引用find_hs_patterns函式。其輸入參數有:
find_hs_patterns函式會回傳兩個HSPattern資料結構(為什麼我說是資料結構而非類別,關鍵在於class HSPattern前面的@dataclass裝飾器),這個資料結構的內容很多,分別說明於下:
當有了上述資訊;下面頭肩型態識別函式的程式碼就容易理解了:
# 來源 : https://github.com/neurotrader888/TechnicalAnalysisAutomation/blob/main/head_shoulders.py
from myutils.HeadShoulders import find_hs_patterns
def FindingHeadShoulderPatterns(prices, order = 1, early_find = False) :
# 價格資料確認與處理
if prices is None and type(prices) is not DataFrame:
return None
in_prices = prices.copy()
if 'Open' not in in_prices.columns or 'High' not in in_prices.columns or 'Low' not in in_prices.columns or 'Close' not in in_prices.columns :
return None
if 'Date' not in in_prices.columns and in_prices.index.dtype == 'datetime64[ns]' :
in_prices.index.name = 'Date'
in_prices = in_prices.reset_index()
if 'Date' not in in_prices.columns :
return None
prices_close=np.array(in_prices['Close'])
# 尋找頭肩型態
hs_patterns, ihs_patterns = find_hs_patterns(prices_close, order=order, early_find=early_find)
# 轉換為輸出格式
patterns = []
for pattern in hs_patterns:
pattern_points = []
# 頭肩型態的左肩
pattern_points.append([pattern.l_shoulder,in_prices.iloc[pattern.l_shoulder]['Date'].strftime("%Y-%m-%d"),pattern.l_shoulder_p])
# 頭肩型態的左腋窩
pattern_points.append([pattern.l_armpit,in_prices.iloc[pattern.l_armpit]['Date'].strftime("%Y-%m-%d"),pattern.l_armpit_p])
# 頭肩型態的頭
pattern_points.append([pattern.head,in_prices.iloc[pattern.head]['Date'].strftime("%Y-%m-%d"),pattern.head_p])
# 頭肩型態的右腋窩
pattern_points.append([pattern.r_armpit,in_prices.iloc[pattern.r_armpit]['Date'].strftime("%Y-%m-%d"),pattern.r_armpit_p])
# 頭肩型態的右肩
pattern_points.append([pattern.r_shoulder,in_prices.iloc[pattern.r_shoulder]['Date'].strftime("%Y-%m-%d"),pattern.r_shoulder_p])
# 頸線開始與結束日期及價格
neckline_start_date = in_prices.iloc[pattern.start_i]['Date'].strftime("%Y-%m-%d")
neckline_start_price = pattern.neck_start
neckline_end_date = in_prices.iloc[pattern.break_i]['Date'].strftime("%Y-%m-%d")
neckline_end_price = pattern.neck_end
neckline_info = {'start_date' : neckline_start_date,'start_price' : neckline_start_price, 'end_date' : neckline_end_date, 'end_price' : neckline_end_price}
# 頭肩型態的頭部資訊
head_info = { 'width': pattern.head_width, 'height': pattern.head_height }
# 頭肩頂
patterns.append({ 'type': 'Top', 'points': pattern_points, 'neckline': neckline_info, 'head': head_info })
for pattern in ihs_patterns:
pattern_points = []
# 頭肩型態的左肩
pattern_points.append([pattern.l_shoulder,in_prices.iloc[pattern.l_shoulder]['Date'].strftime("%Y-%m-%d"),pattern.l_shoulder_p])
# 頭肩型態的左腋窩
pattern_points.append([pattern.l_armpit,in_prices.iloc[pattern.l_armpit]['Date'].strftime("%Y-%m-%d"),pattern.l_armpit_p])
# 頭肩型態的頭
pattern_points.append([pattern.head,in_prices.iloc[pattern.head]['Date'].strftime("%Y-%m-%d"),pattern.head_p])
# 頭肩型態的右腋窩
pattern_points.append([pattern.r_armpit,in_prices.iloc[pattern.r_armpit]['Date'].strftime("%Y-%m-%d"),pattern.r_armpit_p])
# 頭肩型態的右肩
pattern_points.append([pattern.r_shoulder,in_prices.iloc[pattern.r_shoulder]['Date'].strftime("%Y-%m-%d"),pattern.r_shoulder_p])
# 頸線開始與結束日期及價格
neckline_start_date = in_prices.iloc[pattern.start_i]['Date'].strftime("%Y-%m-%d")
neckline_start_price = pattern.neck_start
neckline_end_date = in_prices.iloc[pattern.break_i]['Date'].strftime("%Y-%m-%d")
neckline_end_price = pattern.neck_end
neckline_info = {'start_date' : neckline_start_date,'start_price' : neckline_start_price, 'end_date' : neckline_end_date, 'end_price' : neckline_end_price}
# 頭肩型態的頭部資訊
head_info = { 'width': pattern.head_width, 'height': pattern.head_height }
# 頭肩底
patterns.append({ 'type': 'Bottom', 'points': pattern_points, 'neckline': neckline_info, 'head': head_info })
return patterns
一樣挑重點條列說明:
接下我們就用頭肩型態識別函式來識別型態,程式碼如下所示:
patterns = FindingHeadShoulderPatterns(df_k_line, 10)
print(patterns)
for pattern in patterns :
pattern_points_len = len(np.array(df_k_line['Close']))
pattern_points = np.array([np.nan]*pattern_points_len)
head_shoulder_points = pattern['points']
pattern_points[head_shoulder_points[0][0]] = head_shoulder_points[0][2]
pattern_points[head_shoulder_points[1][0]] = head_shoulder_points[1][2]
pattern_points[head_shoulder_points[2][0]] = head_shoulder_points[2][2]
pattern_points[head_shoulder_points[3][0]] = head_shoulder_points[3][2]
pattern_points[head_shoulder_points[4][0]] = head_shoulder_points[4][2]
head_shoulder_neckline = pattern['neckline']
head_shoulder_head = pattern['head']
# 設定K線格式
mc = mpf.make_marketcolors(up='xkcd:light red', down='xkcd:almost black', inherit=True)
s = mpf.make_mpf_style(base_mpf_style='yahoo', marketcolors=mc)
# 設定頸線
seq_of_seq_of_points=[
[(head_shoulder_neckline['start_date'],head_shoulder_neckline['start_price']),(head_shoulder_neckline['end_date'],head_shoulder_neckline['end_price'])]
]
# 設定轉折點
apds = [
mpf.make_addplot(pattern_points,type='scatter',marker='o',markersize=50,color='xkcd:sky blue')
]
# 繪出K線圖
kwargs = dict(type='candle', style=s, figratio=(19,10), addplot=apds,alines=dict(alines=seq_of_seq_of_points, linewidths=1.2, colors='xkcd:azure', alpha=0.6), datetime_format='%Y-%m-%d')
mpf.plot(df_k_line,**kwargs)
程式執行結果如下:
完整的程式碼請參照「第十四天:頭肩型態識別演算法.ipynb」。
先前討論中有提到「主觀性質客觀化」的方式二(利用演算法以自動化方式來找尋或識別型態)是有限制的,可能人眼能識別到某種型態但演算法卻不能。以下是一個例子,這是由股票老師繪製的頭肩底型態:
實際把資料送進頭肩型態識別函式(也就是前述的FindingHeadShoulderPatterns函式)卻無法識別出來。之後嘗試著調整Day9尋找轉折點函式的參數(mode='open_close',order=10)後,再送入Day13的簡易演算法函式(FindingHeadShoulderBottom函式)卻可以找出型態(但與股票老師繪製的圖形並不一致):
最近看到一本書「約翰墨菲視覺分析: 回歸最單純的圖形解析, 解讀全球市場最深層的故事」把趨勢線與型態這類技術分析工具看做是『視覺分析』,從這個角度來說使用人工智慧(深度學習)技術用來進行型態識別或許是個好主意;也就是說,要探討的是以人類視覺進行分析的工作是否能夠被機器視覺所取代?關於這議題的討論我會放在賽程後半段(Day26)進行,因此型態的課題將會暫時告一段落。明天Day15會進行其他「技術分析」的課題的說明。