市場恐慌還是機會?從融資維持率看穿轉折訊號

融資維持率
Photo by rc.xyz NFT gallery on Unsplash

本文重點摘要

  • 文章難度:★★☆☆☆
  • 融資維持率進出場策略。
  • 透過 TQuant Lab 回測,觀察此策略因子績效。

前言

融資維持率(Maintenance Margin Ratio)是市場上用來衡量融資賬戶風險狀況的重要指標,可以用來判斷投資者是否接近券商發出追繳通知的門檻。因為投資人在進行融資交易時,券商通常會設定「融資維持率」作為追繳保證金(Margin Call)的標準。目的是券商借款給投資者時,為了減少行情波動大的時候,投資者無法還款的風險。當股價下跌時,融資維持率會降低。市場普遍認為融資維持率落在 130%~160% 較為安全;若低於130%,券商會給予投資人T+2的時間來補繳保證金、提供擔保品或減少融資部位,否則有權強制賣出持股(斷頭)。然而,實務上經常出現維持率降至 80%~90% 的情形,甚至更低,主要原因在於融資投資人多為散戶,追高買進後遇股價急跌,未及時補繳,導致系統性賣壓醞釀。值得注意的是,TEJ 提供的融資維持率是針對每檔股票的所有融資戶進行統計,反映的是「整體平均帳戶」的風險狀況。

如何計算融資維持率

融資維持率 = 名目股價 / (每股融資成本 x 融資乘數) x 100%

融資乘數 : 上市公司60%、上櫃公司50%

例子:

若投資者以每股100元買進股票一張(1,000股),融資成數為60%,則 :

原融資維持率 = 100 / 100 x 60% = 166%

當股價下跌至78元時 :

融資維持率 = 78 / 100 x 60% = 130%

當股價進一步跌至60元:

融資維持率 = 60 / 100 x 60% = 100%

此時,投資人若未補繳保證金,則處於高風險狀態,可能遭券商斷頭。以下為實際市場上的低維持率個股範例:

  • 麗台(2465),2025/05/15的融資維持率為 90.19%
  • 太極(4934),融資維持率 80.72%
融資維持率
融資維持率

為何出現極低維持率 ?

  • 高檔融資進場:散戶常在股價創高時追高,導致平均融資成本偏高。
  • 股價急跌:如 AI、航運、重電類股修正時,導致帳戶價值迅速縮水。
  • 未及時補繳保證金:部分投資人選擇觀望不補繳,或無能力補繳。
  • 市場氣氛恐慌:引發連鎖斷頭效應,造成融資餘額與維持率惡化。

時事案例

2025年4月初,台股遭遇重大震盪,單日下跌2,065點,跌幅達 9.6%,創下台股史上第三大單日跌點,市場恐慌情緒急遽升溫。根據鉅亨網報導,當日融資維持率下降至133.8%,低於130%的個股數量高達589檔,形成連環追繳潮,券商紛紛發出追繳通知。此次事件顯示出當融資維持率普遍低落,市場便潛藏大規模斷頭壓力,進而導致價格進一步下跌,形成惡性循環。此外,融資餘額在短期內並未快速下降,顯示許多投資人仍選擇抱股觀望,進一步拉長市場的下跌修正時間。以熱門 AI 類股為例:

  • 緯創(3231):2024 年底因生成式 AI 題材大漲後,融資買盤大量湧入。2025年 Q1 開始回檔,TEJ 融資維持率曾下探至 91%,代表絕大多數融資戶處於浮虧狀態。
  • 技嘉(2376):同樣因 AI 題材而吸引高檔融資,維持率跌破 100%,引發多日追繳賣壓。

這些個股後續即使有短線反彈,仍因籌碼凌亂與浮虧戶未清洗完全,表現明顯弱於大盤。此類案例證明:維持率是反映市場潛藏風險的重要指標之一

實驗架構設計與回測流程

融資維持率除了是帳戶風險的衡量標準,也能作為投資選股與避險的指標。本研究構建以下策略並進行回測:

1. 實驗設計目標:

檢驗個股在「TEJ融資維持率異常偏低」情境下,是否存在具統計顯著性的短期報酬反轉行為。

探討將融資維持率與技術面、籌碼面濾網結合後是否能提升策略表現。

2. 進場條件設定:

測試期為2020~2025。

當日 TEJ 融資維持率 < 過去10日移動平均值(即進入異常低檔狀態)。 (Margin_Maintenance_Ratio < mmr_10)

且同時滿足以下三項濾網: 

  • 當日成交量 < 過去10日平均量:代表籌碼面已經趨於穩定,融資散戶已出場 (volume < vol_10)
  • 當日為紅K(日收盤價 > 開盤價):視為反轉指標出現的時機 (close > open)
  • 當日融資餘額 > 前5日平均融資餘額 × 0.95:如果維持率很低、但融資餘額變動幅度不大,代表剩餘融資戶仍撐著,反彈機率較高。 (Margin_Balance_Vol > mbv_5)

 

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)



將資料匯入回測系統

建立 CustomDataset  函式 & 建立 Pipeline 函式

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 函式

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),
    )




    

接著建立handle_data,依據每日的 signal 資訊進行買進、持有與平倉操作,並納入停利、停損與最大持有天數的限制。


這段程式碼為回測系統的主控流程,用來根據每日盤中訊號決定何時買進、續抱、或出場。

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)

利用 Pyfolio 進行績效評估(無止盈、無止損

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.041.12
卡瑪比率0.651.14
期間最大回撤–28.47%-16.68%

策略績效比較說明

在最大回撤方面,策略的跌幅控制優於大盤,說明該策略能有效降低極端市場波動對資金部位的衝擊,強化資本保全效果。

融資維持率投資策略的年化報酬率略高於大盤,代表該策略在整體報酬上具備一定超額表現,能為投資人帶來更佳的收益水準。

累積報酬方面,策略的長期績效優於大盤,顯示出良好的資產增值能力與投資穩定性。

從年化波動度來看,該策略的波動性低於大盤,意味著在承擔較低風險的情況下仍能提供不錯的報酬,有助於提高投資效率。

夏普值表現較高,代表該策略在風險調整後的報酬優於大盤,顯示其在不同市場環境下都能維持穩定的報酬品質。

卡瑪比率明顯優於大盤,顯示該策略在面對大幅回檔時具備更強的風險承受能力,適合風險意識較高的投資者。

結論

本策略以融資維持率作為核心指標,試圖在市場過度悲觀時捕捉反彈機會。策略的表現顯示出顯著的超額回報,在長期和短期的績效上均優於大盤。年化報酬率與累積回報均顯示出該策略在多變市場中的穩定增長潛力。

儘管策略的波動度較高,但其在風險調整後的表現,無論是夏普值還是卡瑪比率,都顯示出策略具備較大程度的報酬穩定性。尤其在最大回撤的控制上,策略顯示出顯著優於大盤的抗跌能力,證明其能有效減少市場劇烈波動對投資組合的衝擊。

總體來說,本策略成功運用融資維持率指標進行市場情緒判斷,並在技術性反彈中有效擷取超額報酬。該策略適合那些尋求在波動市場中穩定增長的投資者,並具備強大的風險管理能力,有望在未來繼續超越市場表現。

歡迎投資朋友參考,之後也會持續介紹TEJ資料庫來建構各式指標,並回測指標績效,所以歡迎對各種交易回測有興趣的讀者,選購TQuant Lab的相關方案,用高品質的資料庫,建構出適合自己的交易策略。

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


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


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


GitHub 原始碼

點擊前往 Github

延伸閱讀

相關連結

返回總覽頁
Processing...