鉅額交易是警訊?用 TEJ API 分析內部人持股轉讓與股價之變化

本文重點概要

  • 文章難度:★☆☆☆☆
  • 分析內部人以鉅額交易方式轉讓股票對公司股價的影響

前言

在股市風起雲湧的今天,企業股價波動背後的謎團值得深入挖掘。內部人持股轉讓反映公司內部運營、管理層對未來發展的信心,以及對當前經濟環境的評估。透過分析內部人鉅額交易背後的動因,探討股價與內部人持股轉讓之關係。本文將運用TEJ API聚焦於內部人持股轉讓對台灣上市櫃公司股價進行量化分析。

什麼是鉅額交易

依據交易數量與買賣金額,證券交易若符合以下標準,就要申報鉅額交易:

  1. 單一證券:數量達 500 交易單位以上或金額達 1,500 萬元以上。
  2. 股票組合:5 種股票以上且總金額達 1,500 萬元以上。

鉅額交易分為逐筆交易與配對交易兩種,其申報價格範圍與一般交易的漲跌停限制相同。逐筆交易的成交價格會以買賣雙方申報價格的高低與申報時間的先後順序進行搓合;配對交易則是委託人洽特定人以指定的價格與數量對敲。另外,鉅額交易的委託與搓合時間最晚到 17 : 00 為止,為了不讓盤中價格產生大幅波動,因此鉅額交易通常會在盤後執行。

編輯環境與模組需求

本文使用 Windows 11 作業系統以及 VS code 作為編輯器。

資料庫使用

資料導入

取得內部人申報轉讓資料,資料由董監申報轉讓資料庫導入,其中轉讓的方式包含很多種,如:一般交易、洽特定人、信託、贈與、鉅額交易、盤後定價,而我們從中抓取鉅額交易的資料。

import tejapi
import pandas as pd
import matplotlib.pyplot as plt
import plotly.graph_objects as go
from datetime import datetime, timedelta
from dateutil.relativedelta import relativedelta

tejapi.ApiConfig.api_key  = "Your Key"
tejapi.ApiConfig.api_base = "https://api.tej.com.tw"
tejapi.ApiConfig.ignoretz = True

# 取得內部人申報轉讓資料,並篩選出鉅額交易的資料
data_trans = tejapi.get('TWN/ATRANS',        # 上市(櫃)董監申報轉讓資料庫
                        # 證券碼、申報日、股東身份、轉讓方式、申報轉讓股數
                        opts = {'columns':['coid', 'mdate', 'clnm_c', 'tpnm_c', 'tns2']},
                        paginate = True,
                        chinese_column_name = True)
data_trans = data_trans[data_trans['轉讓方式'].str.contains('鉅額')]  # 篩選鉅額交易資料

設置總發行股數函數,資料由上市(櫃)董監全體持股狀況資料庫導入。

# 取得總發行股數函數,將證券碼、日期設為變數
def data_total_stock(coid, date):
# 由於此資料庫將日期設為次月15號更新,本次實作2024/01的資料尚未發布,所以若有2023/12/01之後的事件都以2023/12/01取得總發行股數
    reference_date = datetime.strptime('2023-12-01', '%Y-%m-%d')
    date_object = datetime.strptime(date, '%Y-%m-%d')   # 將日期變數轉為datetime格式
# 判斷日期是否在2023/12/01之後,若在之後以2023/12/01取得總發行股數
    if date_object > reference_date:
        gte = reference_date
        lte = reference_date
# 若在2023/12/01之前,此資料是月頻率的資料,因此若日期不是1號,則取這個月到下個月之間的那個1號,反之則不用改變日期
    else:
        gte = date                    # 設置開始日期
        if date_object.day != 1:
            next_month_date = date_object + relativedelta(months=1)    # 加一個月
        else:
            next_month_date = date_object
        lte = next_month_date.strftime('%Y-%m-%d')         # 設置結束日期
    data_total = tejapi.get('TWN/ABSTN1',                  # 上市(櫃)董監全體持股狀況資料庫   
                            coid = coid,
                            mdate = {'gte':gte, 'lte':lte},
                            opts = {'columns':['coid','fld003']},    # 總發行股數
                            paginate = True,
                            chinese_column_name = True)    
    return data_total

設置股價函數,資料由上市(櫃)未調整股價(日)資料庫導入。

# 取得股價函數,將證券碼、起迄日期設為變數
def data_stock_price(coid, start, end):
    data_coid = tejapi.get('TWN/APRCD',  # 上市(櫃)未調整股價(日)資料庫
                            coid = coid,
                            mdate = {'gte':start, 'lte': end},
                            paginate = True,
                            chinese_column_name = True,
                            opts = {'columns':['coid','mdate','close_d']},
                            )
    return data_coid

資料合併與視覺化分析

考量到不同時間點,企業的總發行股數會有波動,因此設置公司碼、申報日為變數代入總發行股數函數,以得到對應時間點的總發行股數,並計算出申報股數(%)。

# 下面兩個欄位轉成list資料,以便後續帶入函數
coid = list(data_trans['公司碼'])
date = list(data_trans['申報日'])

# 設置總發行股數的dataframe
data_total = pd.DataFrame()

for x, y in zip(coid, date):
    data_stock = data_total_stock(x, str(y)[0:10]) # 將證券碼、日期轉成字串取年月日帶入函數
    if data_stock.size == 0:         # 若沒有資料不處理
        pass    
    else:
        data_total = pd.concat([data_total, data_stock], ignore_index=True)\
        
data_trans = data_trans[data_trans['公司碼'] != '5261']   # 申報轉讓資料刪除上方沒有資料的證券碼(經查詢已下櫃)
data_trans.reset_index(inplace=True)
data_trans = data_trans.drop(columns='None')

# 將總發行股數併入申報轉讓資料,並新增欄位申報股數(%)
data_outstanding = [outstanding for outstanding in data_total['總股數']]
data_trans['總股數'] = data_outstanding
stock_pct = [(j/i)*100 for i, j in zip(data_trans['總股數'], data_trans['申報轉讓股數'])]
data_trans['申報股數(%)'] = stock_pct

提取每支股票申報比例最高的資料

# 取出每支股票申報股數(%)最高的資料
max_percentage_coid = data_trans.groupby('公司碼')['申報股數(%)'].idxmax()
final_data = data_trans.loc[max_percentage_coid]
內部人持股轉讓
每支股票申報股數(%)最高的資料

提取證券代碼與設置時間區間

綜合以上數據與相關新聞,本文歸納出以下四種結果,並取 3036、6452、3017、2311、2597、2723 為例:

  1. 股價隨之暴跌,以文曄 ( 3036 ) 與康友-KY ( 6452 ) 為例:
  • 文曄(3036) (2023/11/10)
    • 文曄自 2023/9/14 宣布收購加拿大半導體通路商 Future Electronics 的全部股權後,股價自 72.1 元一路漲至 2023/11/08 的 147 元,其最大股東大聯大 ( 3702 ) 便於 2023/11/10 轉讓 4 萬張文曄股票,市場投資人也隨之出脫手中持股,使股價大跌。
內部人持股轉讓
  • 康友-KY(6452) (2018/10/16)
    • 康友-KY ( 6452 ) 股價於 2018/10/22 創 538 元歷史新高後,因市場傳出董座賣股獲利,使股價連 3 天跌停。事後康友董事長雖聲明轉讓 1300 張股票是因合作夥伴想增持康友持股,也無法挽回投資人的信心,康友股價也於 1 個月內跌到僅剩 125 元。
內部人持股轉讓
  1. 股價稍有波動或基本不受轉讓消息影響,以奇鋐 ( 3017 ) 與日月光 ( 2311 ) 為例:
  • 奇鋐(3017) (2021/03/23) → 奇鋐
    • 奇鋐於 2021/3/23 宣布大股東日商古河電工將轉讓奇鋐股票 9300 張,不過於同日發布的財報也顯示奇鋐受惠於產品版圖優化,使年度營收、毛利與稅後淨利皆創新高,使股價較不受轉讓消息影響。
內部人持股轉讓
  • 日月光(2311) (2018/02/14) → 日月光
    • 日月光副董張洪本因個人信託規劃,轉讓其持有的 118222 張股票,並同時透過張洪本 100 % 持有的卓越資本獲利有限公司,自集中市場透過鉅額交易方式,依市場價格買入相同股數的日月光股票。因張洪本無實質釋出日月光股份,使日月光股價不至於受轉讓消息影響。
內部人持股轉讓
  1. 股價微微上漲,以潤弘 ( 2597 ) 為例:
  • 潤弘(2597) (2019/10/23) → 潤弘
    • 潤泰集團為簡化集團組織架構,清算子公司潤泰資源整合,並透過潤泰新、潤泰全與集團相關投資公司於集中市場買回潤泰資源所持有之潤弘 72881 張股票。
內部人持股轉讓
  1. 股價下跌後回穩,以美食-KY ( 2723 ) 為例:
  • 美食-KY(2723) (2023/03/24) → 美食-KY
    • 美食-KY 於 2023/3/23 召開法說會釋出利多消息後,董事長配偶卻於隔日 ( 24日 )股價大漲 5 %之際轉讓 3463 張持股,使下一交易日 ( 27日 ) 亮燈跌停。隨後美食-KY 說明此轉讓理由是「引進國際長期投資人」,股價也有逐漸回穩的趨勢。
內部人持股轉讓
內部人持股轉讓

觀察各股申報日與下一個工作日之股價與報酬率變化

我們想知道各股申報轉讓後股價與報酬率的變化,算出申報日的下一個工作日,來觀察是正向變動還是負向變動的多,又或者是股價不受影響。

# 找出申報日的下一個工作日,以利進行申報轉讓前後之對比
next_workdays = []
for c in range (len(all_data)):
    next_work_day = all_data['申報日'][c] + timedelta(days=1)
    next_working = str(next_work_day)[0:10]
    ROC_data = data_stock_price(all_data['公司碼'][c], str(all_data['申報日'][c])[0:10], next_working)
    while True:
        if len(ROC_data) < 2: # 如果資料少於兩筆代表目前只抓到申報日這天的股價資料,所以日期再繼續往後抓
            next_work_day += timedelta(days=1)
            next_working = str(next_work_day)[0:10]
            ROC_data = data_stock_price(all_data['公司碼'][c], str(all_data['申報日'][c])[0:10], next_working)
        else:
            break
    next_workdays.append(next_working)

all_data['下一個工作日'] = next_workdays

找到下一個工作日後,設置日報酬率函數,資料由上市(櫃)股價報酬(日)-報酬率資料庫導入。

# 取得日報酬率函數,將證券碼、起迄日期設為變數
def data_rate_of_return(coid, start, end):
    data_return = tejapi.get('TWN/APRCD2',                  # 上市(櫃)股價報酬(日)-報酬率資料庫   
                            coid = coid,
                            mdate = {'gte':start, 'lte':end},
                            opts = {'columns':['coid','mdate','zclose','roia']},    
                            # 證券碼、日期、收盤價、日報酬率%
                            paginate = True,
                            chinese_column_name = True)
    return data_return

將申報日、下一個工作日與證券代碼代入日報酬率函數

# 將申報日、下一個工作日與證券代碼代入日報酬率函數
ROC_df = pd.DataFrame(columns=['證券代碼', '年月日', '未調整收盤價(元)', '日報酬率 %'])
for count in range (len(all_data)):
    ROC_data = data_rate_of_return(all_data['公司碼'][count], 
                                    str(all_data['申報日'][count])[0:10], 
                                    str(all_data['下一個工作日'][count])[0:10])
    ROC_df = pd.concat([ROC_df, ROC_data], ignore_index=True)
內部人持股轉讓

統計結果與內部人持股轉讓分析

計算日報酬率的數量,大於 0 歸納在positive、小於 0 歸納在negative、等於 0 歸納在zero

# 計算正向、反向及無變動的數量
positive = 0
negative = 0
zero = 0
for i in range(1, len(ROC_df), 2):
    rate = ROC_df['日報酬率 %'][i]
    if rate == 0:
        zero += 1
    elif rate > 0:
        positive += 1
    elif rate < 0:
        negative += 1

total = zero+positive+negative # 計算資料筆數

# 利用plotly套件畫出圓餅圖
labels = ['Zero', 'Positive', 'Negative']
values = [zero, positive, negative]
colors = ['gold', 'mediumturquoise', 'darkorange']

fig = go.Figure(data=[go.Pie(labels=labels, values=values)])
fig.update_traces(hoverinfo='label+percent+value',
                    textinfo='percent+value+label',
                    textfont_size=15,
                    marker=dict(colors=colors, line=dict(color='#000000', width=1.5)))

title_text = ("內部人持股轉讓後,下一個工作日股市變動情形圓餅圖,總資料筆數為{}筆" .format(total))  
fig.update_layout(title_text=title_text)
fig.show()
內部人持股轉讓
內部人持股轉讓後,下一個工作日股市報酬率變動情形圓餅圖,總資料筆數為488筆
其中負向變動佔 49.2% 為最多,達到將近一半的比例;正向變動佔 42.8% 為次多;無變動佔約 8%

上圖呈現的是申報日後的下一個工作日之收盤價的變動率比例圖,可以看到還是以負向變動居多,佔比將近 50%,可以直覺看出投資人在面對不確定性時第一時間較傾向將資金撤出。不過若一間公司的基本面體質優良,或是鉅額交易並沒有稀釋到股份,正向變動或者無變動也都有發生的可能。

結論

從數據上來看,鉅額交易對股價產生的影響確實是一個值得關注的議題。我們發現內部人持股轉讓後,股價短期變動主要呈現負向趨勢,顯示市場對於這類交易存在著一定程度的擔憂與不確定性。

然而,鉅額交易並不一定對股價造成負面的影響。透過上述的分析,我們可以看到股東轉讓股票可能是因為個人的理財或信託規劃,其並不會造成企業股份的變動。另外,若公司的財報亮眼,或鉅額交易僅是企業內部的股權重新分配,股價就不會因此而下跌。儘管如此,投資人也應密切關注投資的企業對於鉅額交易的相關說明,以了解其轉讓股票的目的,若企業無法明確說明轉讓原因,加上公司營運績效不佳,或市場普遍瀰漫利空的氛圍,使股價接連下跌,即需要考慮降低持股比列或出脫股票。

本次實作透過 TEJ API 實現內部人持股轉讓分析,僅用 TEJ API 所提供的有效資料和視覺化的套件,就可以做出這樣的結果並實作自身所想像的各類型之視覺化分析,省時又省力的情況下,也能提供投資人更多元的評估指標。選購 TEJ E-Shop 的相關方案,用高品質的資料庫,建構出適合自己的分析應用或交易策略。

溫馨提醒,本次策略僅供參考,不代表任何商品或投資上的建議

Github 原始碼

內部人持股轉讓與股價變化研究

延伸閱讀

相關連結

返回總覽頁
Procesing