iT邦幫忙

2023 iThome 鐵人賽

DAY 14
0
AI & Data

用Python程式進行股票技術分析系列 第 14

Day14 頭肩型態識別演算法

  • 分享至 

  • xImage
  •  

延續昨天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函式。其輸入參數有:

  • data:(價格)資料
  • order:同尋找轉折點函式的order參數,請參閱Day9內容
  • early_find:提前尋找模式。在Day12時有提到,當價格突破頸線時才算型態完成(在此也就是early_find為False的情況)。若將early_find設為True時它會在型態尚未完成時提前識別,當然這樣會有過早判斷的風險。

find_hs_patterns函式會回傳兩個HSPattern資料結構(為什麼我說是資料結構而非類別,關鍵在於class HSPattern前面的@dataclass裝飾器),這個資料結構的內容很多,分別說明於下:

  • inverted:為Ture是反轉(也就是頭肩底,國外有時會稱之為反向頭肩型態(inverse head & shoulders pattern)),為Flase則是非反轉(也就是頭肩頂)
  • l_shoulder:左肩位置索引
  • r_shoulder:右肩位置索引
  • l_armpit:左腋窩(以頭肩底來說,就是左側峰位;為左肩之後的轉折高點)位置索引
  • r_armpit:右腋窩(以頭肩底來說,就是右側峰位;為右肩之前的轉折高點)位置索引
  • head:頭部位置索引
  • l_shoulder_p:左肩價格
  • r_shoulder_p:右肩價格
  • l_armpit_p:左腋窩價格
  • r_armpit_p:右腋窩價格
  • head_p:頭部價格
  • start_i:型態開始位置索引(也可以視為頸線開始位置索引)
  • break_i:突破點位置索引(也可以視為頸線結束位置索引)
  • break_p:突破點價格
  • neck_start:頸線開始價格
  • neck_end:頸線結束價格
  • neck_slope:頸線斜率
  • head_width:頭部寬度
  • head_height:頭部高度
  • pattern_r2:這個參數我不知道是什麼?我也沒有使用!

當有了上述資訊;下面頭肩型態識別函式的程式碼就容易理解了:

# 來源 : 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

一樣挑重點條列說明:

  • 頭肩型態識別函式的輸入參數說明於下:
    • prices:價格 (資料型態:DataFrame)
    • order:最小化過濾器的距離參數(資料型態:int)
    • early_find:提前尋找模式(資料型態:bool)
  • 頭肩型態識別函式的回傳說明於下:
    • 識別到的型態(包含:類型(type)、關鍵轉折點(points)、頸線(neckline)、頭部資訊(head))
  • 僅用「收盤價」進行頭肩型態識別

程式實作:識別頭肩底型態

接下我們就用頭肩型態識別函式來識別型態,程式碼如下所示:

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)

程式執行結果如下:
Imgur
完整的程式碼請參照「第十四天:頭肩型態識別演算法.ipynb」。

用演算法識別型態的限制

先前討論中有提到「主觀性質客觀化」的方式二(利用演算法以自動化方式來找尋或識別型態)是有限制的,可能人眼能識別到某種型態但演算法卻不能。以下是一個例子,這是由股票老師繪製的頭肩底型態:
Imgur
實際把資料送進頭肩型態識別函式(也就是前述的FindingHeadShoulderPatterns函式)卻無法識別出來。之後嘗試著調整Day9尋找轉折點函式的參數(mode='open_close',order=10)後,再送入Day13的簡易演算法函式(FindingHeadShoulderBottom函式)卻可以找出型態(但與股票老師繪製的圖形並不一致):
Imgur
最近看到一本書「約翰墨菲視覺分析: 回歸最單純的圖形解析, 解讀全球市場最深層的故事」把趨勢線與型態這類技術分析工具看做是『視覺分析』,從這個角度來說使用人工智慧(深度學習)技術用來進行型態識別或許是個好主意;也就是說,要探討的是以人類視覺進行分析的工作是否能夠被機器視覺所取代?關於這議題的討論我會放在賽程後半段(Day26)進行,因此型態的課題將會暫時告一段落。明天Day15會進行其他「技術分析」的課題的說明。


上一篇
Day13 反轉圖形的底部型態
下一篇
Day15 KD指標
系列文
用Python程式進行股票技術分析30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
Sunny.Cat
iT邦新手 4 級 ‧ 2023-09-15 03:58:38

已拜讀!!!

我要留言

立即登入留言