Table of Contents
融資維持率(Maintenance Margin Ratio)是市場上用來衡量融資賬戶風險狀況的重要指標,可以用來判斷投資者是否接近券商發出追繳通知的門檻。因為投資人在進行融資交易時,券商通常會設定「融資維持率」作為追繳保證金(Margin Call)的標準。目的是券商借款給投資者時,為了減少行情波動大的時候,投資者無法還款的風險。當股價下跌時,融資維持率會降低。市場普遍認為融資維持率落在 130%~160% 較為安全;若低於130%,券商會給予投資人T+2的時間來補繳保證金、提供擔保品或減少融資部位,否則有權強制賣出持股(斷頭)。然而,實務上經常出現維持率降至 80%~90% 的情形,甚至更低,主要原因在於融資投資人多為散戶,追高買進後遇股價急跌,未及時補繳,導致系統性賣壓醞釀。值得注意的是,TEJ 提供的融資維持率是針對每檔股票的所有融資戶進行統計,反映的是「整體平均帳戶」的風險狀況。
融資乘數 : 上市公司60%、上櫃公司50%
若投資者以每股100元買進股票一張(1,000股),融資成數為60%,則 :
原融資維持率 = 100 / 100 x 60% = 166%
當股價下跌至78元時 :
融資維持率 = 78 / 100 x 60% = 130%
當股價進一步跌至60元:
融資維持率 = 60 / 100 x 60% = 100%
此時,投資人若未補繳保證金,則處於高風險狀態,可能遭券商斷頭。以下為實際市場上的低維持率個股範例:
2025年4月初,台股遭遇重大震盪,單日下跌2,065點,跌幅達 9.6%,創下台股史上第三大單日跌點,市場恐慌情緒急遽升溫。根據鉅亨網報導,當日融資維持率下降至133.8%,低於130%的個股數量高達589檔,形成連環追繳潮,券商紛紛發出追繳通知。此次事件顯示出當融資維持率普遍低落,市場便潛藏大規模斷頭壓力,進而導致價格進一步下跌,形成惡性循環。此外,融資餘額在短期內並未快速下降,顯示許多投資人仍選擇抱股觀望,進一步拉長市場的下跌修正時間。以熱門 AI 類股為例:
這些個股後續即使有短線反彈,仍因籌碼凌亂與浮虧戶未清洗完全,表現明顯弱於大盤。此類案例證明:維持率是反映市場潛藏風險的重要指標之一。
融資維持率除了是帳戶風險的衡量標準,也能作為投資選股與避險的指標。本研究構建以下策略並進行回測:
1. 實驗設計目標:
檢驗個股在「TEJ融資維持率異常偏低」情境下,是否存在具統計顯著性的短期報酬反轉行為。
探討將融資維持率與技術面、籌碼面濾網結合後是否能提升策略表現。
2. 進場條件設定:
測試期為2020~2025。
當日 TEJ 融資維持率 < 過去10日移動平均值(即進入異常低檔狀態)。 (Margin_Maintenance_Ratio < mmr_10)
且同時滿足以下三項濾網:
3. 操作與風控規則:
符合上述條件後,於「紅K當日」開盤價買進。
持有15個交易日或達停損/停利即出場:
停利 +40%
停損 -10%
若在15個交易日內有買進訊號,則以最新訊號當日重新判斷出場條件、舉例來說,在2025/05/05時有了買進訊號,在2025/05/13(15個交易日內)又有一次買進訊號,則以2025/05/13重新判斷一次出場條件。
4. 回測與績效評估方法:
回測區間:2020~2025,每日逐步滾動檢查是否觸發進場條件。
評估指標:平均報酬率、勝率、最大回落、夏普值等。
結果與同期間隨機進場比較,檢驗策略信號是否具備優勢。
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_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
透過 TejToolAPI 獲取資料
pool = get_universe(start = '2018-01-01',
end = '2025-05-21',
mkt_bd_e = ['TSE'], # 已上市股票
stktp_e = 'Common Stock'
)
columns = ['open_d', 'close_d', 'vol', 'long_t', 'lmr']
start_dt = pd.Timestamp('2018-12-01', tz = 'UTC')
end_dt = pd.Timestamp('2025-05-21', tz = "UTC")
data = TejToolAPI.get_history_data(start = start_dt,
end = end_dt,
ticker = pool,
columns = columns,
transfer_to_chinese = True)
data
將data計算並進行篩選,篩選出融資維持率跌幅前十的股票
df = data.copy()
# K線判斷
df['K線'] = df.apply(lambda row: 'Red' if row['收盤價'] > row['開盤價']
else ('Green' if row['收盤價'] < row['開盤價'] else '十字'), axis=1)
# 計算過去10天平均成交量(分股票)
df['過去10天平均成交額'] = df.groupby('股票代碼')['成交金額_元'].transform(lambda x: x.rolling(window=10).mean())
# 成交量標準判斷
df['成交額標準'] = df.apply(lambda row: 'T' if row['成交金額_元'] > row['過去10天平均成交額'] else 'F', axis=1)
# 計算過去5天融資餘額平均(分股票)
df['過去5天融資餘額平均'] = df.groupby('股票代碼')['融資餘額'].transform(lambda x: x.rolling(window=5).mean())
# 融資餘額標準判斷
df['融資餘額標準'] = df.apply(lambda row: 'T' if row['融資餘額'] > row['過去5天融資餘額平均'] * 0.95 else 'F', axis=1)
# 計算過去10天融資維持率平均(分股票)
df['過去10天融資維持率平均'] = df.groupby('股票代碼')['融資維持率'].transform(lambda x: x.rolling(window=10).mean())
# 融資維持率標準判斷(當日維持率是否低於過去10天平均)
df['融資維持率標準'] = df.apply(lambda row: 'T' if row['融資維持率'] < row['過去10天融資維持率平均'] else 'F', axis=1)
# 計算融資維持率相對過去10天移動平均的跌幅百分比
df['融資維持率跌幅百分比'] = (df['融資維持率'] - df['過去10天融資維持率平均']) / df['過去10天融資維持率平均'] * 100
# 對每天的資料,挑出跌幅最大的前10檔標記 'long',其餘標記 'dont'
def label_top10(group):
# 依跌幅百分比由小到大排序(跌越多越前面)
group = group.sort_values('融資維持率跌幅百分比')
group['持倉標記'] = 'dont'
# 標記前10檔
group.iloc[:10, group.columns.get_loc('持倉標記')] = 'long'
return group
df = df.groupby('日期').apply(label_top10)
將資料匯入回測系統
from zipline.data import bundles
# 讀取 Zipline bundle
bundle_name = 'tquant'
bundle = bundles.load(bundle_name)
# 取得 Zipline 的 SID
sids = bundle.asset_finder.equities_sids
assets = bundle.asset_finder.retrieve_all(sids)
# 建立 股票代碼 → SID 的對應表
symbol_mapping_sid = {i.symbol: i.sid for i in assets}
# 將 股票代碼 轉換為 SID
df_long = df_long.reset_index()
df_long['SID'] = df_long['股票代碼'].map(symbol_mapping_sid)
# 刪除無法對應的股票
df = df_long.dropna(subset=['SID']).copy()
df['SID'] = df['SID'].astype(int)
# 重新設索引 (`日期`, `SID`)
df = df.set_index(['日期', 'SID']).sort_index()
從技術指標中找出符合特定條件的股票。它整合了三個條件:K線是紅K、成交量達標、融資餘額達標。透過 Zipline 的管線工具(Pipeline),將這些條件轉換為可以運算的布林值(True/False),然後找出同時滿足三個條件的股票。
整體流程包含:先整理資料格式,再定義條件組合,最後在設定的時間範圍內執行篩選,輸出每一天有哪些股票符合這些訊號。這可以當作選股策略的初步篩選工具。
from zipline.pipeline import Pipeline
from zipline.pipeline.data import EquityPricing
from zipline.pipeline.loaders.frame import DataFrameLoader
from zipline.pipeline.domain import TW_EQUITIES
from zipline.pipeline.engine import SimplePipelineEngine
from zipline.pipeline.data import Column, DataSet
transform_data = df.unstack('SID')
fixed_transform_data = transform_data.copy()
if fixed_transform_data.index.tz is not None:
fixed_transform_data.index = fixed_transform_data.index.tz_convert('UTC')
else:
fixed_transform_data.index = fixed_transform_data.index.tz_localize('UTC')
# 定義自訂數據集,將字符串類型轉換為布林型 (bool)
class CustomDataset(DataSet):
KLine = Column(dtype='bool', missing_value=False) # K線轉換為bool型別
VolumeStandard = Column(dtype='bool', missing_value=False) # 成交量標準轉換為bool型別
FinancingBalanceStandard = Column(dtype='bool', missing_value=False) # 融資餘額標準轉換為bool型別
domain = TW_EQUITIES
# 建立 DataFrameLoader
Custom_loader = {
CustomDataset.KLine: DataFrameLoader(CustomDataset.KLine, fixed_transform_data['K線'] == 'Red'), # 轉為布林型
CustomDataset.VolumeStandard: DataFrameLoader(CustomDataset.VolumeStandard, fixed_transform_data['成交量標準'] == 'T'), # 轉為布林型
CustomDataset.FinancingBalanceStandard: DataFrameLoader(CustomDataset.FinancingBalanceStandard, fixed_transform_data['融資餘額標準'] == 'T') # 轉為布林型
}
def choose_loader(column):
if column.name in EquityPricing._column_names:
return pricing_loader
elif column.name in CustomDataset._column_names:
return Custom_loader[column]
else:
raise Exception('Column not available')
# Pipeline 執行引擎
engine = SimplePipelineEngine(get_loader=choose_loader,
asset_finder=bundle.asset_finder,
default_domain=TW_EQUITIES)
# 計算訊號的函數
def compute_signals():
# 篩選出符合條件的信號
k_line_red = CustomDataset.KLine.latest # K線為Red (已經是布林型)
volume_standard_t = CustomDataset.VolumeStandard.latest # 成交量標準為T (已經是布林型)
financing_balance_standard_t = CustomDataset.FinancingBalanceStandard.latest # 融資餘額標準為T (已經是布林型)
# 合併所有條件,符合的結果為True
combined_signals = k_line_red & volume_standard_t & financing_balance_standard_t
# 只取出符合條件的股票訊號
return Pipeline(columns={
'signals': combined_signals # 這裡存放符合條件的訊號
})
# 設定日期範圍
start_dt = pd.Timestamp('2018-12-01', tz='UTC')
end_dt = pd.Timestamp('2025-04-21', tz='UTC')
# 執行 Pipeline
pipeline_result = engine.run_pipeline(compute_signals(), start_dt, end_dt)
initialize()
函式用於定義交易開始前的每日交易環境,與此例中我們設置:
def initialize(context):
"""環境初始化,設置交易參數。"""
context.holdings = {} # 記錄持倉 { asset: { entry_dt, entry_price } }
context.stop_loss_pct = 0.10 # 停損百分比
context.take_profit_pct = 0.40 # 停利百分比
context.rebalance_period = 15 # 持倉期數
# 設置手續費、滑點、基準、管道
set_slippage(slippage.TW_Slippage(volume_limit=1.0))
set_commission(commission.Custom_TW_Commission())
set_benchmark(symbol('IR0001'))
attach_pipeline(compute_signals(), 'mystrats') # 設置信號管道
# 每日開盤後 5 分鐘執行 handle_data
schedule_function(
handle_data,
date_rules.every_day(),
#time_rules.market_open(minutes=5),
)
# 每日收盤後檢查是否需要平倉
schedule_function(
rebalance,
date_rules.every_day(),
#time_rules.market_close(minutes=1),
)
這段程式碼為回測系統的主控流程,用來根據每日盤中訊號決定何時買進、續抱、或出場。
def handle_data(context, data):
"""根據當日信號執行下單操作。"""
out = pipeline_output('mystrats')
if out.empty:
return
signals = out['signals']
for asset, signal in signals.items():
if signal: # 若信號為 True
cash = context.portfolio.cash
if cash > 0:
price = data.history(asset, 'close', 2, '1d').iloc[1] # 取得昨日開盤價
order_value = cash * 0.1 # 使用可用現金的 10% 進行下單
shares = int(order_value // price)
if shares > 0:
order(asset, shares) # 下單
context.holdings[asset] = {
'entry_dt': get_datetime(),
'entry_price': price,
}
print(f"Buy {asset} x{shares} @ {price:.2f} on {get_datetime().date()} (10% cash)")
def rebalance(context, data):
"""檢查並平倉,包含停損、停利或達到持倉期。"""
assets_to_exit = [] # 用來存儲需要平倉的資產
# 遍歷持倉中的每檔資產
for asset, info in context.holdings.items():
price = data.current(asset, 'close')
entry_price = info['entry_price']
# 停損條件
if price <= entry_price * (1 - context.stop_loss_pct):
assets_to_exit.append(asset)
print(f"Sell {asset} for stop loss @ {price:.2f} on {get_datetime().date()}")
continue
# 停利條件
elif price >= entry_price * (1 + context.take_profit_pct):
assets_to_exit.append(asset)
print(f"Sell {asset} for take profit @ {price:.2f} on {get_datetime().date()}")
continue
# 超過持倉期則平倉
elif days_held(context, info['entry_dt']) >= context.rebalance_period:
assets_to_exit.append(asset)
print(f"Sell {asset} for rebalance @ {price:.2f} on {get_datetime().date()}")
# 在遍歷完後平倉所有需要平倉的資產
for asset in assets_to_exit:
order_target_percent(asset, 0)
context.holdings.pop(asset, None)
def days_held(context, entry_dt):
"""計算從 entry_dt 到今天的交易日數(不含 entry 當天)。"""
cal = get_calendar('TEJ')
today = get_datetime().normalize()
sessions = cal.sessions_in_range(entry_dt.normalize(), today)
return len(sessions) - 1
使用 run_algorithm() 來執行上述設定的動能策略,資料集使用 tquant,初始資金設定為 1,000,000 元。執行過程中,輸出的 results 包含每日績效和交易明細。
在進行繪圖時,為了避免字體錯誤,先進行字體設定:
from zipline import run_algorithm
from zipline.utils.calendar_utils import get_calendar
# Setup for running the algorithm
capital_base = 1e6
start = '2020-08-01' # Example start date
end = '2025-04-21' # Example end date
# Convert to pandas Timestamp
start_dt = pd.Timestamp(start, tz='UTC')
end_dt = pd.Timestamp(end, tz="UTC")
# Running the backtest
results = run_algorithm(start=start_dt,
end=end_dt,
initialize=initialize,
handle_data=handle_data,
capital_base=capital_base,
data_frequency='daily',
analyze=analyze,
bundle=bundle_name, # Replace with your bundle name
trading_calendar=get_calendar('TEJ'),
custom_loader=Custom_loader)
from pyfolio.utils import extract_rets_pos_txn_from_zipline
import pyfolio as pf
# 從 results 資料表中取出 returns, positions & transactions
returns, positions, transactions = extract_rets_pos_txn_from_zipline(results)
benchmark_rets = results.benchmark_return # 取出 benchmark 的報酬率# 繪製 Pyfolio 中提供的所有圖表
pf.tears.create_full_tear_sheet(returns=returns,
positions=positions,
transactions=transactions,
benchmark_rets=benchmark_rets
)
績效指標 / 策略 | 大盤(Benchmark) | 融資維持率投資策略 |
年化報酬率 | 18.39% | 19.04% |
累積報酬率 | 110.51% | 115.6% |
年化波動度 | 17.67% | 16.897% |
夏普值 | 1.04 | 1.12 |
卡瑪比率 | 0.65 | 1.14 |
期間最大回撤 | –28.47% | -16.68% |
策略績效比較說明
在最大回撤方面,策略的跌幅控制優於大盤,說明該策略能有效降低極端市場波動對資金部位的衝擊,強化資本保全效果。
融資維持率投資策略的年化報酬率略高於大盤,代表該策略在整體報酬上具備一定超額表現,能為投資人帶來更佳的收益水準。
累積報酬方面,策略的長期績效優於大盤,顯示出良好的資產增值能力與投資穩定性。
從年化波動度來看,該策略的波動性低於大盤,意味著在承擔較低風險的情況下仍能提供不錯的報酬,有助於提高投資效率。
夏普值表現較高,代表該策略在風險調整後的報酬優於大盤,顯示其在不同市場環境下都能維持穩定的報酬品質。
卡瑪比率明顯優於大盤,顯示該策略在面對大幅回檔時具備更強的風險承受能力,適合風險意識較高的投資者。
本策略以融資維持率作為核心指標,試圖在市場過度悲觀時捕捉反彈機會。策略的表現顯示出顯著的超額回報,在長期和短期的績效上均優於大盤。年化報酬率與累積回報均顯示出該策略在多變市場中的穩定增長潛力。
儘管策略的波動度較高,但其在風險調整後的表現,無論是夏普值還是卡瑪比率,都顯示出策略具備較大程度的報酬穩定性。尤其在最大回撤的控制上,策略顯示出顯著優於大盤的抗跌能力,證明其能有效減少市場劇烈波動對投資組合的衝擊。
總體來說,本策略成功運用融資維持率指標進行市場情緒判斷,並在技術性反彈中有效擷取超額報酬。該策略適合那些尋求在波動市場中穩定增長的投資者,並具備強大的風險管理能力,有望在未來繼續超越市場表現。
歡迎投資朋友參考,之後也會持續介紹TEJ資料庫來建構各式指標,並回測指標績效,所以歡迎對各種交易回測有興趣的讀者,選購TQuant Lab的相關方案,用高品質的資料庫,建構出適合自己的交易策略。
溫馨提醒,本次分析僅供參考,不代表任何商品或投資上建議。