別人恐懼我進場:安東尼·賣利亞 的反向市場致勝術

安東尼·賣利亞 的反向市場致勝術
Photo by Markus Spiske on Unsplash

前言

在金融市場中,「反向思考(Contrary Thinking)」是一種歷久不衰的智慧策略。它源於一個簡單但深刻的觀察:當大多數人極度樂觀時,市場往往已經過熱;當群眾陷入恐懼時,反而可能是低接良機。然而,反向操作長期以來多停留在諺語層次,缺乏具體、可量化的行動標準,導致實務上難以執行。

安東尼‧賈利亞(Anthony M. Gallea)與威廉‧巴特隆(William Patalon III)於《Contrarian Investing》一書中,首度將反向操作具體化為可執行的量化選股策略。賈利亞是所羅門美邦公司(Salomon Smith Barney)資深投資組合管理董事,帶領逾14人團隊、管理超過六億美元資產;巴特隆則為資深商業記者,曾四度獲得紐約州美聯社頒發之商業報導獎。兩人以豐富的實務與觀察經驗,構建出一套兼具理論深度與實用性的選股方法。

策略邏輯

本策略採用多層次篩選邏輯,結合技術面、籌碼面與基本面條件,從中挑選出具備中期成長潛力的股票。首先,在技術面上,篩選最近一年內收盤價低於一年內最高價的 50% 的股票,作為初步具備反轉動能潛力的候選名單。

接著,在籌碼面條件中,若董事或經理人在最近六個月內持股比例增加至少 5%,或持股比例已達 10% 以上,則視為內部人具備明確信心的標的,亦納入考量;此外,投信若出現明顯加碼行為,也具備相同效力。若籌碼面條件滿足,則進一步檢視該公司是否具備基本面優勢,要求其符合以下四項條件中的至少兩項:

  • 市盈率(PE)低於市場同行平均市盈率的 85%
  • 價格 / 自由現金流量比 < 1 倍
  • 價格 / 每股淨值比 < 0.8 倍
  • 價格 / 每股營收比 < 1 倍

選出標的股票之後, 以等權重的方式去分配總體資金,以達到分散風險的效果,不爾會將資金過度集中於單一股票中。

抓取 TEJ 資料欄位

資料代碼close_dadjfacfld005qfii_pctfd_pctri
科目收盤價調整係數董監持股%外資持股率投信持股率常續性利益
資料代碼sharespercscfocscfir307r19open_d
科目流通在外股數本益比營運現金流投資現金流每股淨值近12月每股營收(元)開盤價

Python 資料整理程式碼展示

import pandas as pd
import numpy as np
import tejapi
import os
import matplotlib.pyplot as plt
import datetime
plt.rcParams['font.family'] = 'Arial'




os.environ['TEJAPI_BASE'] = “your base"
os.environ['TEJAPI_KEY'] = "your tej api key"




from zipline.sources.TEJ_Api_Data import get_universe
import TejToolAPI
from zipline.data.run_ingest import simple_ingest
from zipline.api import set_slippage, set_commission, set_benchmark,  symbol,  record, order_target_percent
from zipline.finance import commission, slippage
from zipline import run_algorithm


start_date = '2015-01-01'
end_date = '2025-06-30'


pool = get_universe(start = start_date,
                   end = end_date,
                   mkt_bd_e = ['TSE', 'OTC', 'TIB'],  # 已上市之股票
                   stktp_e = 'Common Stock')  # 普通股


columns = ['Industry', 'close_d', 'adjfac', 'fld005', 'qfii_pct' , 'fd_pct' , 'ri'  ,
       'shares','per' , 'cscfo' , 'cscfi' , 'r307' , 'r19'  , 'open_d', 'pbr', 'psr_tej']
start_dt = pd.Timestamp(start_date, tz = 'UTC')
end_dt = pd.Timestamp(end_date, tz = "UTC")


data = TejToolAPI.get_history_data(start = start_dt,
                                  end = end_dt,
                                  ticker = pool,
                                  columns = columns,
                                  transfer_to_chinese = True)




data = data.sort_values('日期')


# 依股票代碼進行時間序列運算
data['one_year_max_price'] = data.groupby('股票代碼')['收盤價'] \
   .transform(lambda x: x.rolling(window=252, min_periods=1).max())


data['one_year_price'] = data.groupby('股票代碼')['收盤價'] \
   .transform(lambda x: x.shift(252))


data['insider_holding_change_6m'] = data.groupby('股票代碼')['董監持股%'] \
   .transform(lambda x: x - x.shift(126))


data['foreign_holding_change_6m'] = data.groupby('股票代碼')['外資持股率'] \
   .transform(lambda x: x - x.shift(126))


data['fund_holding_change_6m'] = data.groupby('股票代碼')['投信持股率'] \
   .transform(lambda x: x - x.shift(126))


# 自由現金流與估值比率
data['FCF'] = data['營運產生現金流量_Q'] - data['投資產生現金流量_Q']
data['Price_FCF'] = data['收盤價'] / data['FCF']


# 注意:這裡假設 '股價淨值比' 是 PBR,如有誤請調整
data['Price_pbr'] = data['收盤價'] / data['股價淨值比'] 


# 價格營收比
data['Price_Rev'] = data['收盤價'] / data['股價營收比_TEJ']


# 依產業計算平均本益比
data['industry_pe_avg'] = data.groupby('主產業別_中文')['本益比'] \
   .transform('mean')


data_use = data.copy()

Python 選股邏輯以及回測架構

def compute_growth_strategy(date, data):
   df = data[data['日期'] == pd.to_datetime(date)].reset_index(drop=True)
  
   # 技術面條件:收盤價 > 一年內最高價 50%
   tech_pass = set(df[df['收盤價'] < 0.5 * df['one_year_max_price']]['股票代碼'])




   # 董事經理人條件
   df['insider_6m_ago'] = df.groupby('股票代碼')['董監持股%'].shift(126)
   df['insider_growth'] = df['董監持股%'] - df['insider_6m_ago']
   insider_pass = set(df[(df['insider_growth'] >= 5) | (df['董監持股%'] >= 10)]['股票代碼'])




   # 投信條件(附加條件)
   df['fund_6m_ago'] = df.groupby('股票代碼')['投信持股率'].shift(126)
   fund_pass = set(df[df['投信持股率'] >= df['fund_6m_ago']]['股票代碼'])


   # 基本面條件(如果內部人或投信有持股,才檢查基本面)
   df['cond_A'] = df['本益比'] < 0.85 * df['industry_pe_avg']
   df['cond_B'] = df['Price_FCF'] < 1
   df['cond_C'] = df['Price_pbr'] < 0.8
   df['cond_D'] = df['Price_Rev'] < 1
   df['fundamental_score'] = df[['cond_A', 'cond_B', 'cond_C', 'cond_D']].sum(axis=1)
   df['fundamental_pass'] = df['fundamental_score'] >= 2


   # 只對「內部人 or 投信」有動作的股票檢查基本面
   trigger_set = insider_pass | fund_pass
   fundamental_checked_set = set(df[df['股票代碼'].isin(trigger_set) & df['fundamental_pass']]['股票代碼'])


   # 最終篩選:股價增長 + (內部人或投信) + 基本面
   final_selection = tech_pass & trigger_set & fundamental_checked_set


   print(f"下單日期:{date}, 股價年增長: {len(tech_pass)}, 董監條件: {len(insider_pass)}, 投信條件: {len(fund_pass)}, 觸發基本面: {len(trigger_set)}, 基本面通過: {len(fundamental_checked_set)}, 最終選股: {len(final_selection)}")


   return final_selection


pools = pool + ['IR0001']


start_ingest = start_date.replace('-', '')
end_ingest = end_date.replace('-', '')


print(f'開始匯入回測資料')
simple_ingest(name = 'tquant' , tickers = pools , start_date = start_ingest , end_date = end_ingest)
print(f'結束匯入回測資料')

TQuant Lab 回測邏輯程式碼展示

def initialize(context, re = 120):
   set_slippage(slippage.VolumeShareSlippage(volume_limit=1, price_impact=0.01))
   set_commission(commission.Custom_TW_Commission())
   set_benchmark(symbol('IR0001'))


   context.i = 0
   context.state = False
   context.order_tickers = []
   context.last_tickers = []
   context.rebalance = re




def handle_data_1(context, data):
   # 避免前視偏誤,在篩選股票下一交易日下單
   if context.state == True:
       for i in context.last_tickers:
           if i not in context.order_tickers:
               order_target_percent(symbol(i), 0)


       for i in context.order_tickers:
           order_target_percent(symbol(i), 1.0 / len(context.order_tickers))


       print(f"下單日期:{data.current_dt.date()}, 擇股股票數量:{len(context.order_tickers)}, Leverage: {context.account.leverage}")
       context.last_tickers = context.order_tickers.copy()
       context.state = False


   backtest_date = data.current_dt.date()


   if context.i % context.rebalance == 0:
       context.state = True
       context.order_tickers = compute_growth_strategy(date = backtest_date, data = data_use)


   record(tickers = context.order_tickers)
   record(Leverage = context.account.leverage)
  
   if context.account.leverage > 1.2:
       print(f'{data.current_dt.date()}: Over Leverage, Leverage: {round(context.account.leverage, 2)}')
       for i in context.order_tickers:
           order_target_percent(symbol(i), 1 / len(context.order_tickers))


   context.i += 1


def analyze(context, perf):
 print(perf.columns)
 plt.style.use('ggplot')


 # 第一張圖:策略績效與報酬
 fig1, axes1 = plt.subplots(nrows=2, ncols=1, figsize=(18, 10), sharex=False)


 axes1[0].plot(perf.index, perf['algorithm_period_return'], label='Strategy')
 axes1[0].plot(perf.index, perf['benchmark_period_return'], label='Benchmark')
 axes1[0].bar(perf.index, perf['algorithm_period_return'] - perf['benchmark_period_return'],
             label='Excess return', color='g', alpha=0.4)
 axes1[0].set_title("Backtest Results")
 axes1[0].legend()


 axes1[1].plot(perf.index, perf['benchmark_volatility'], label='Benchmark Volatility')
 axes1[1].plot(perf.index, perf['algo_volatility'], label='Strategy Volatility')
 axes1[1].set_title("Voloatility")
 axes1[1].legend()
 plt.tight_layout()
 plt.show()


results = run_algorithm(
           start = pd.Timestamp('2018-01-01', tz = 'utc'),
           end = pd.Timestamp(end_date, tz = 'utc'),
           initialize = initialize,
           handle_data = handle_data_1,
           analyze = analyze,
           bundle = 'tquant',
           capital_base = 1e5)
安東尼·賣利亞 的反向市場致勝術
績效指標 / 策略大盤(Benchmark)本投資策略
年化報酬率13.91%14.53%
累積報酬率156.29%166.60%
年化波動度17.54%23.93%
夏普值0.830.69
卡瑪比率0.510.33
期間最大回撤-27.37%-43.47%

從回測結果來看,本投資策略在 年化報酬率(14.53%)與累積報酬率(166.6%) 上略優於大盤(13.91% / 156.29%),但整體風險調整後的表現並不理想。策略的 年化波動度高達 23.93%,遠高於大盤的 17.54%,導致 夏普值僅為 0.69,低於大盤的 0.83,代表單位風險所帶來的報酬反而較差。此外,最大回撤高達 -43.47%,相較於大盤的 -27.37%,顯示該策略在市場波動時缺乏足夠的防禦能力。

策略的 卡瑪比率為 0.33,也低於大盤的 0.51,意味著其承受每一單位最大損失所換得的報酬相對偏低,這點在圖中「Excess return」長期為負的部分亦可見端倪,特別是在 2021~2024 年間策略表現明顯落後。

完整程式碼連結


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

【TQuant Lab 回測系統】解決你的量化金融痛點

全方位提供交易回測所需工具

延伸閱讀

從景氣燈號到資產輪動:一套避開熊市的量化策略

麥克.墨菲高科技股投資風險評估法則

查爾士.布蘭帝 價值型選股法則:打造安全邊際的投資組合

相關連結

返回總覽頁
Processing...