在金融市場的技術分析領域中,K 線圖(Candlestick Chart)作為最直觀的價格呈現方式,承載著豐富的市場資訊。每一根 K 線不僅記錄開盤價(Open)、最高價(High)、最低價(Low)與收盤價(Close)這四個關鍵價格,更透過其形態反映市場參與者的心理狀態與多空力量對比。對於追求系統化交易的投資人而言,將 K 線形態轉化為可量化、可回測的交易策略,是實現穩定獲利的重要途徑。本文將深入探討如何運用 Python 程式語言建構完整的 K 線圖交易系統,從資料擷取到策略回測,提供實戰導向的技術解決方案。

Python 金融資料處理基礎架構

建立任何量化交易系統的第一步,都必須確保能夠可靠地取得與處理金融資料。Python 生態系統提供豐富的資料處理工具,其中 Pandas 函式庫以其強大的時間序列處理能力,成為金融資料分析的標準選擇。NumPy 則負責底層的數值運算,提供高效能的陣列操作功能。Matplotlib 作為視覺化工具,讓我們能夠將抽象的數據轉化為直觀的圖表。

開始建構交易系統前,需要先建置 Python 開發環境。透過套件管理工具 pip 安裝必要的函式庫,這些工具將成為後續開發的基礎設施。Pandas 處理表格化的金融資料,NumPy 執行快速的數學運算,Matplotlib 繪製價格走勢圖與技術指標,而 TA-Lib 則提供預先實作的技術分析指標函式。

pip install pandas numpy matplotlib ta-lib yfinance MetaTrader5

資料匯入是系統運作的起點。設計一個健壯的資料匯入函式,必須考慮檔案不存在、格式錯誤等異常情況。透過 try-except 結構處理可能的例外狀況,確保程式在遇到問題時能夠優雅地處理而非突然中斷。當資料來源為 CSV 檔案時,Pandas 的 read_csv 函式提供便捷的讀取方式,自動將資料解析為 DataFrame 格式。這種結構化的資料表格,不僅易於操作,更能有效支援後續的技術指標計算與策略回測。

import pandas as pd
import numpy as np

def import_data(filepath):
    """
    匯入 OHLC 金融資料的通用函式
    
    參數:
        filepath: CSV 檔案路徑
        
    回傳:
        DataFrame: 包含 OHLC 資料的 Pandas DataFrame,若失敗則回傳 None
    """
    try:
        data = pd.read_csv(filepath)
        if data.empty:
            print(f"警告:檔案 {filepath} 不包含任何資料")
            return None
        return data
    except FileNotFoundError:
        print(f"錯誤:找不到檔案 {filepath}")
        return None
    except pd.errors.EmptyDataError:
        print(f"錯誤:檔案 {filepath} 為空")
        return None
    except Exception as e:
        print(f"匯入資料時發生未預期錯誤:{str(e)}")
        return None

這個函式實作完善的錯誤處理機制。當檔案路徑不存在時,會捕捉 FileNotFoundError 並提供清晰的錯誤訊息。空檔案的情況由 EmptyDataError 處理,確保後續程式不會因為空資料而出錯。通用的 Exception 捕捉則作為最後一道防線,處理任何未預期的狀況。這種防禦性程式設計的思維,是建構穩定交易系統的關鍵。

@startuml
!define DISABLE_LINK
!define PLANTUML_FORMAT svg
!theme _none_

skinparam dpi auto
skinparam shadowing false
skinparam linetype ortho
skinparam roundcorner 5
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 16
skinparam minClassWidth 100

actor 交易者 as trader
participant "Python 環境" as python
participant "資料來源" as datasource
database "本地資料庫" as localdb
participant "策略引擎" as strategy
participant "回測系統" as backtest

trader -> python: 啟動資料擷取程式
python -> datasource: 請求歷史資料
datasource -> python: 回傳 OHLC 資料
python -> localdb: 儲存資料
trader -> python: 執行策略回測
python -> localdb: 讀取歷史資料
localdb -> strategy: 提供資料
strategy -> backtest: 生成交易訊號
backtest -> trader: 輸出績效報告

@enduml

交易策略回測框架核心函式

量化交易的核心在於能夠系統化地測試策略在歷史資料上的表現。回測框架提供標準化的工具,讓策略開發者能夠專注於交易邏輯的設計,而非重複實作基礎設施。以下介紹構建回測系統所需的核心陣列操作函式,這些函式將頻繁地運用於資料處理與訊號計算過程。

在處理金融時間序列資料時,經常需要增加新的欄位來儲存計算結果。例如計算移動平均線後需要新增欄位儲存均線數值,生成交易訊號後需要欄位標記買賣點。add_column 函式提供動態擴展陣列結構的能力,透過 NumPy 的 column_stack 方法水平合併陣列,新欄位會附加在原有資料的右側。這種設計讓資料處理流程更加靈活,能夠逐步建構複雜的特徵工程管線。

def add_column(array, new_column):
    """
    在 NumPy 陣列右側增加新欄位
    
    參數:
        array: 原始 NumPy 陣列
        new_column: 要增加的新欄位資料(一維陣列)
        
    回傳:
        NumPy array: 增加新欄位後的陣列
    """
    new_column_reshaped = new_column.reshape(-1, 1)
    return np.column_stack((array, new_column_reshaped))

def delete_column(array, column_index):
    """
    從 NumPy 陣列刪除指定欄位
    
    參數:
        array: 原始 NumPy 陣列
        column_index: 要刪除的欄位索引
        
    回傳:
        NumPy array: 刪除欄位後的陣列
    """
    return np.delete(array, column_index, axis=1)

def add_row(array, new_row):
    """
    在 NumPy 陣列底部增加新列
    
    參數:
        array: 原始 NumPy 陣列
        new_row: 要增加的新列資料
        
    回傳:
        NumPy array: 增加新列後的陣列
    """
    return np.vstack((array, new_row))

def delete_row(array, row_index):
    """
    從 NumPy 陣列刪除指定列
    
    參數:
        array: 原始 NumPy 陣列
        row_index: 要刪除的列索引
        
    回傳:
        NumPy array: 刪除列後的陣列
    """
    return np.delete(array, row_index, axis=0)

def round_values(array, decimals=5):
    """
    將陣列數值四捨五入至指定小數位數
    
    參數:
        array: 原始 NumPy 陣列
        decimals: 保留的小數位數,預設為 5
        
    回傳:
        NumPy array: 四捨五入後的陣列
    """
    return np.round(array, decimals)

這些函式構成回測系統的基礎工具箱。delete_column 與 delete_row 函式處理陣列精簡的需求,例如移除計算過程中產生的中間變數,或是刪除資料清洗時發現的異常值。round_values 函式處理浮點數精度問題,在金融交易中特別重要,因為價格通常需要精確到特定小數位數。外匯市場的報價精度到小數點後五位,而股票市場可能只需要兩位小數,這個函式提供彈性的精度控制。

@startuml
!define DISABLE_LINK
!define PLANTUML_FORMAT svg
!theme _none_

skinparam dpi auto
skinparam shadowing false
skinparam linetype ortho
skinparam roundcorner 5
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 16
skinparam minClassWidth 100

package "回測框架核心模組" {
  [資料處理模組] as dataproc
  [訊號生成模組] as signal
  [績效評估模組] as performance
  [風險管理模組] as risk
  [視覺化模組] as visual
}

[原始 OHLC 資料] as rawdata
[技術指標計算] as indicator
[交易訊號] as tradesignal
[部位管理] as position
[績效報告] as report

rawdata --> dataproc
dataproc --> indicator
indicator --> signal
signal --> tradesignal
tradesignal --> position
position --> risk
risk --> performance
performance --> report
report --> visual

note right of signal
  實作各種交易策略
  生成買賣訊號
end note

note right of risk
  設定停損停利
  控制部位大小
end note

@enduml

MetaTrader 5 資料整合實務

MetaTrader 5 (MT5) 作為全球廣泛使用的交易平台,提供豐富的歷史資料與即時報價。透過 Python 的 MetaTrader5 函式庫,我們能夠直接從平台擷取資料進行分析,無需手動匯出與匯入檔案。這種自動化的資料管線大幅提升研究效率,讓策略開發者能夠快速測試想法。

建構 MT5 資料擷取系統的第一步是安裝必要的軟體環境。需要先從官方網站下載 MT5 交易平台,並建立模擬帳戶。模擬帳戶使用虛擬資金,讓開發者能夠在真實市場環境中測試策略而無需承擔實際風險。接著透過 pip 安裝 MetaTrader5 Python 函式庫,這個函式庫提供 Python 與 MT5 平台之間的橋接介面。

pip install MetaTrader5

資料擷取函式的設計必須考慮時區處理、時間範圍設定與錯誤處理。MT5 平台使用 UTC 時區,但在台灣進行分析時,我們可能需要轉換為台北時區(UTC+8)。pytz 函式庫提供完善的時區轉換功能,確保時間戳記的準確性。datetime 模組則用於設定資料擷取的起始與結束時間,支援靈活的歷史資料查詢。

import datetime
import pytz
import pandas as pd
import MetaTrader5 as mt5
import numpy as np

def initialize_mt5():
    """
    初始化 MetaTrader 5 連線
    
    回傳:
        bool: 初始化是否成功
    """
    if not mt5.initialize():
        print(f"MT5 初始化失敗,錯誤代碼:{mt5.last_error()}")
        return False
    print("MT5 初始化成功")
    return True

def get_quotes(time_frame, year=2020, month=1, day=1, asset="EURUSD"):
    """
    從 MetaTrader 5 擷取歷史報價資料
    
    參數:
        time_frame: 時間框架(例如 mt5.TIMEFRAME_H1 代表小時線)
        year: 起始年份
        month: 起始月份
        day: 起始日期
        asset: 交易標的代碼
        
    回傳:
        DataFrame: 包含 OHLC 資料的 Pandas DataFrame
    """
    if not initialize_mt5():
        return None
    
    timezone = pytz.timezone("Asia/Taipei")
    time_from = datetime.datetime(year, month, day, tzinfo=timezone)
    time_to = datetime.datetime.now(timezone)
    
    rates = mt5.copy_rates_range(asset, time_frame, time_from, time_to)
    
    if rates is None or len(rates) == 0:
        print(f"無法取得 {asset} 的資料,請確認交易標的代碼是否正確")
        mt5.shutdown()
        return None
    
    rates_frame = pd.DataFrame(rates)
    rates_frame['time'] = pd.to_datetime(rates_frame['time'], unit='s')
    
    mt5.shutdown()
    return rates_frame

def mass_import(asset_index, time_frame_str, assets_list):
    """
    批次匯入指定交易標的的歷史資料
    
    參數:
        asset_index: 資產在清單中的索引
        time_frame_str: 時間框架字串('H1', 'D1' 等)
        assets_list: 交易標的清單
        
    回傳:
        NumPy array: OHLC 價格資料陣列
    """
    time_frame_mapping = {
        'M15': mt5.TIMEFRAME_M15,
        'M30': mt5.TIMEFRAME_M30,
        'H1': mt5.TIMEFRAME_H1,
        'H4': mt5.TIMEFRAME_H4,
        'D1': mt5.TIMEFRAME_D1,
        'W1': mt5.TIMEFRAME_W1
    }
    
    if time_frame_str not in time_frame_mapping:
        print(f"不支援的時間框架:{time_frame_str}")
        return None
    
    time_frame = time_frame_mapping[time_frame_str]
    asset = assets_list[asset_index]
    
    data = get_quotes(time_frame, 2020, 1, 1, asset=asset)
    
    if data is None or data.empty:
        return None
    
    ohlc_data = data[['open', 'high', 'low', 'close']].values
    ohlc_data = np.round(ohlc_data, 5)
    
    return ohlc_data

這個資料擷取系統提供模組化的架構。initialize_mt5 函式處理 MT5 連線的建立,並提供清晰的錯誤訊息幫助診斷連線問題。get_quotes 函式實作核心的資料查詢邏輯,支援彈性的時間範圍與交易標的設定。mass_import 函式則提供批次處理能力,透過索引值與時間框架字串,能夠快速擷取多個交易標的的資料。時間框架映射字典將人類可讀的字串(‘H1’, ‘D1’)轉換為 MT5 API 所需的常數,提升程式碼的可讀性。

@startuml
!define DISABLE_LINK
!define PLANTUML_FORMAT svg
!theme _none_

skinparam dpi auto
skinparam shadowing false
skinparam linetype ortho
skinparam roundcorner 5
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 16
skinparam minClassWidth 100

participant "Python 程式" as python
participant "MT5 API" as mt5api
participant "MT5 伺服器" as mt5server
database "歷史資料庫" as histdb

python -> mt5api: initialize()
mt5api -> mt5server: 建立連線
mt5server -> mt5api: 連線成功
mt5api -> python: 回傳成功狀態

python -> mt5api: copy_rates_range(symbol, timeframe, from, to)
mt5api -> mt5server: 請求歷史資料
mt5server -> histdb: 查詢資料
histdb -> mt5server: 回傳資料
mt5server -> mt5api: 傳送資料
mt5api -> python: 回傳 OHLC 陣列

python -> mt5api: shutdown()
mt5api -> mt5server: 關閉連線

@enduml

趨勢追蹤策略:雙重麻煩形態

趨勢追蹤策略的核心理念是順勢而為,在上升趨勢中尋找買點,在下降趨勢中尋找賣點。雙重麻煩形態(Double Trouble)是一種經典的趨勢延續形態,通常出現在強勁趨勢的短暫回檔後。這個形態由兩根相鄰的 K 線組成,第一根為長實體的陽線,顯示多頭力量強勁。第二根為較短的陽線,收盤價略低於前一根,暗示上漲動能暫時減弱但趨勢尚未反轉。

結合技術指標能夠提升形態識別的準確度。相對強弱指標(Relative Strength Index, RSI)衡量價格動能的強弱,數值範圍在 0 到 100 之間。當 RSI 超過 70 時,市場被視為超買,價格可能面臨回檔壓力。將雙重麻煩形態與 RSI 超買訊號結合,能夠捕捉趨勢轉折的時機,在上漲過度後尋找賣出機會。

import talib as ta
import yfinance as yf
import pandas as pd
import numpy as np

def download_stock_data(symbol, start_date, end_date):
    """
    從 Yahoo Finance 下載股票資料
    
    參數:
        symbol: 股票代碼
        start_date: 起始日期
        end_date: 結束日期
        
    回傳:
        DataFrame: 包含 OHLC 資料的 DataFrame
    """
    try:
        data = yf.download(symbol, start=start_date, end=end_date, progress=False)
        if data.empty:
            print(f"無法取得 {symbol} 的資料")
            return None
        return data
    except Exception as e:
        print(f"下載資料時發生錯誤:{str(e)}")
        return None

def identify_double_trouble(data):
    """
    識別雙重麻煩 K 線形態
    
    參數:
        data: 包含 OHLC 資料的 DataFrame
        
    回傳:
        Series: 形態識別結果(1 代表出現形態,0 代表沒有)
    """
    signals = pd.Series(0, index=data.index)
    
    for i in range(1, len(data)):
        prev_candle = data.iloc[i-1]
        curr_candle = data.iloc[i]
        
        prev_body = abs(prev_candle['Close'] - prev_candle['Open'])
        curr_body = abs(curr_candle['Close'] - curr_candle['Open'])
        
        is_prev_bullish = prev_candle['Close'] > prev_candle['Open']
        is_curr_bullish = curr_candle['Close'] > curr_candle['Open']
        
        is_prev_long = prev_body > (prev_candle['High'] - prev_candle['Low']) * 0.6
        is_curr_shorter = curr_body < prev_body * 0.8
        is_curr_lower_close = curr_candle['Close'] < prev_candle['Close']
        
        if (is_prev_bullish and is_curr_bullish and 
            is_prev_long and is_curr_shorter and is_curr_lower_close):
            signals.iloc[i] = 1
    
    return signals

def calculate_rsi(data, period=14):
    """
    計算 RSI 指標
    
    參數:
        data: 包含收盤價的 DataFrame
        period: RSI 計算週期
        
    回傳:
        Series: RSI 數值
    """
    return ta.RSI(data['Close'], timeperiod=period)

def generate_double_trouble_signals(data, rsi_overbought=70):
    """
    生成雙重麻煩策略的交易訊號
    
    參數:
        data: 包含 OHLC 資料的 DataFrame
        rsi_overbought: RSI 超買門檻
        
    回傳:
        DataFrame: 增加訊號欄位的 DataFrame
    """
    data = data.copy()
    data['RSI'] = calculate_rsi(data)
    data['DoubleTrouble'] = identify_double_trouble(data)
    
    data['Signal'] = 0
    data.loc[(data['DoubleTrouble'] == 1) & (data['RSI'] > rsi_overbought), 'Signal'] = -1
    
    return data

這個策略實作展示完整的訊號生成流程。download_stock_data 函式使用 yfinance 函式庫從 Yahoo Finance 下載股票資料,提供便捷的資料來源。identify_double_trouble 函式實作形態識別邏輯,透過比較相鄰 K 線的實體大小與收盤價位置判斷形態出現。calculate_rsi 函式使用 TA-Lib 函式庫計算 RSI 指標,這個業界標準函式庫提供高效且準確的技術指標計算。generate_double_trouble_signals 函式整合所有元件,當雙重麻煩形態出現且 RSI 超過超買門檻時,生成賣出訊號(-1)。

逆勢操作策略:十字星反轉形態

逆勢操作策略採取與趨勢相反的立場,在價格過度上漲時賣出,在過度下跌時買入。這種策略的核心在於識別趨勢轉折點,捕捉價格回歸平均的機會。十字星(Doji)是最經典的反轉形態之一,其特徵是開盤價與收盤價幾乎相等,形成十字形或加號形的 K 線。這種形態暗示多空雙方力量均衡,原有趨勢可能失去動能。

當十字星出現在極端價格區域時,反轉的可能性大幅提升。RSI 指標除衡量超買外,也能識別超賣狀態。當 RSI 低於 30 時,市場被視為超賣,價格可能面臨反彈。結合十字星形態與 RSI 超賣訊號,能夠提高反轉交易的勝率,在市場恐慌性拋售後尋找買入時機。

def identify_doji(data, threshold=0.1):
    """
    識別十字星 K 線形態
    
    參數:
        data: 包含 OHLC 資料的 DataFrame
        threshold: 實體與影線比例門檻,預設 0.1
        
    回傳:
        Series: 形態識別結果(1 代表出現形態,0 代表沒有)
    """
    signals = pd.Series(0, index=data.index)
    
    for i in range(len(data)):
        candle = data.iloc[i]
        
        body = abs(candle['Close'] - candle['Open'])
        range_size = candle['High'] - candle['Low']
        
        if range_size == 0:
            continue
        
        body_ratio = body / range_size
        
        if body_ratio < threshold:
            signals.iloc[i] = 1
    
    return signals

def generate_doji_reversal_signals(data, rsi_oversold=30):
    """
    生成十字星反轉策略的交易訊號
    
    參數:
        data: 包含 OHLC 資料的 DataFrame
        rsi_oversold: RSI 超賣門檻
        
    回傳:
        DataFrame: 增加訊號欄位的 DataFrame
    """
    data = data.copy()
    data['RSI'] = calculate_rsi(data)
    data['Doji'] = identify_doji(data)
    
    data['Signal'] = 0
    data.loc[(data['Doji'] == 1) & (data['RSI'] < rsi_oversold), 'Signal'] = 1
    
    return data

十字星識別函式計算 K 線實體與整體振幅的比例。當實體佔振幅的比例小於門檻值(預設 10%)時,判定為十字星形態。這個彈性的門檻設計允許策略開發者根據不同市場特性調整識別標準。某些市場的 K 線波動較大,可能需要放寬門檻;而波動較小的市場則需要更嚴格的標準。generate_doji_reversal_signals 函式整合形態識別與 RSI 指標,當十字星出現且 RSI 低於超賣門檻時,生成買入訊號(1)。

@startuml
!define DISABLE_LINK
!define PLANTUML_FORMAT svg
!theme _none_

skinparam dpi auto
skinparam shadowing false
skinparam linetype ortho
skinparam roundcorner 5
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 16
skinparam minClassWidth 100

state "交易訊號決策流程" as decision {
  state "分析 K 線形態" as pattern
  state "計算技術指標" as indicator
  state "評估市場狀態" as market
  state "生成交易訊號" as signal
  
  [*] --> pattern
  pattern --> indicator
  indicator --> market
  market --> signal: 條件符合
  market --> [*]: 條件不符合
  signal --> [*]
}

note right of pattern
  識別雙重麻煩
  識別十字星
  識別其他形態
end note

note right of indicator
  計算 RSI
  計算移動平均
  計算其他指標
end note

note right of market
  判斷超買超賣
  判斷趨勢方向
  評估風險水平
end note

@enduml

Alpha 策略:多時間框架動能分析

Alpha 策略採用多時間框架分析方法,透過比較不同週期的價格關係識別動能變化。這種策略的優勢在於能夠過濾短期雜訊,專注於具有持續性的價格走勢。策略邏輯結合價格相對位置與動能方向,創造具有明確規則的交易系統。

做多條件要求當前最低價低於 5 個週期前與 13 個週期前的最低價,顯示短期出現回檔。但同時當前最低價必須高於 21 個週期前的最低價,確保中長期趨勢仍然向上。這種多層次的價格比較能夠識別上升趨勢中的短暫回檔機會。最後要求當前收盤價高於 3 個週期前的收盤價,確認動能開始恢復向上。

做空條件採用對稱的邏輯。當前最高價高於 5 個週期前與 13 個週期前的最高價,顯示短期出現反彈。但當前最高價低於 21 個週期前的最高價,確保中長期趨勢仍然向下。當前收盤價低於 3 個週期前的收盤價,確認動能繼續向下。這種設計能夠在下降趨勢中的短暫反彈後尋找賣出機會。

def generate_alpha_signals(data):
    """
    生成 Alpha 策略的交易訊號
    
    參數:
        data: 包含 OHLC 資料的 DataFrame
        
    回傳:
        DataFrame: 增加訊號欄位的 DataFrame
    """
    data = data.copy()
    data['Buy'] = 0
    data['Sell'] = 0
    
    for i in range(21, len(data) - 1):
        low_curr = data['Low'].iloc[i]
        low_5 = data['Low'].iloc[i - 5]
        low_13 = data['Low'].iloc[i - 13]
        low_21 = data['Low'].iloc[i - 21]
        close_curr = data['Close'].iloc[i]
        close_3 = data['Close'].iloc[i - 3]
        
        high_curr = data['High'].iloc[i]
        high_5 = data['High'].iloc[i - 5]
        high_13 = data['High'].iloc[i - 13]
        high_21 = data['High'].iloc[i - 21]
        
        buy_condition = (low_curr < low_5 and 
                        low_curr < low_13 and 
                        low_curr > low_21 and 
                        close_curr > close_3)
        
        sell_condition = (high_curr > high_5 and 
                         high_curr > high_13 and 
                         high_curr < high_21 and 
                         close_curr < close_3)
        
        if buy_condition and data['Buy'].iloc[i] == 0:
            data.loc[data.index[i + 1], 'Buy'] = 1
        
        if sell_condition and data['Sell'].iloc[i] == 0:
            data.loc[data.index[i + 1], 'Sell'] = -1
    
    data['Signal'] = data['Buy'] + data['Sell']
    
    return data

這個實作展示訊號生成的完整邏輯。迴圈從索引 21 開始,確保有足夠的歷史資料計算條件。對於每個 K 線,函式提取當前與歷史的價格資料,然後評估做多與做空條件。當條件滿足時,訊號標記在下一個索引位置,這符合實際交易中無法在當前 K 線收盤時立即執行的限制。訊號會在下一根 K 線開盤時執行,這是更符合實務的設計。

風險管理與交易執行

再優秀的交易策略,若缺乏完善的風險管理機制,最終都可能導致災難性的損失。風險管理的核心在於控制單筆交易的風險暴露,設定明確的停損點與獲利目標。停損點(Stop Loss)是預先設定的價格水平,當市場走勢不利時自動平倉,限制損失在可承受範圍內。獲利目標(Take Profit)則在價格達到預期水平時鎖定利潤,避免貪婪導致已實現的獲利回吐。

部位大小(Position Sizing)決定每筆交易投入的資金比例。固定比例法是最簡單的方法,例如每筆交易投入帳戶資金的 2%。這種方法能夠在連續虧損時自動減少交易規模,保護資本。風險調整法則根據停損距離動態調整部位大小,當停損較遠時減少部位,停損較近時增加部位,確保每筆交易的風險金額保持一致。

def calculate_position_size(account_balance, risk_percentage, entry_price, stop_loss_price):
    """
    計算部位大小
    
    參數:
        account_balance: 帳戶餘額
        risk_percentage: 風險比例(例如 0.02 代表 2%)
        entry_price: 進場價格
        stop_loss_price: 停損價格
        
    回傳:
        float: 建議的部位大小
    """
    risk_amount = account_balance * risk_percentage
    risk_per_unit = abs(entry_price - stop_loss_price)
    
    if risk_per_unit == 0:
        return 0
    
    position_size = risk_amount / risk_per_unit
    
    return position_size

def apply_risk_management(data, initial_balance, risk_per_trade, stop_loss_pct, take_profit_pct):
    """
    應用風險管理規則到交易訊號
    
    參數:
        data: 包含交易訊號的 DataFrame
        initial_balance: 初始資金
        risk_per_trade: 每筆交易風險比例
        stop_loss_pct: 停損百分比
        take_profit_pct: 獲利目標百分比
        
    回傳:
        DataFrame: 包含風險管理資訊的 DataFrame
    """
    data = data.copy()
    data['Position'] = 0
    data['StopLoss'] = 0
    data['TakeProfit'] = 0
    data['PositionSize'] = 0
    
    current_balance = initial_balance
    
    for i in range(len(data)):
        if data['Signal'].iloc[i] == 1:
            entry_price = data['Close'].iloc[i]
            stop_loss = entry_price * (1 - stop_loss_pct)
            take_profit = entry_price * (1 + take_profit_pct)
            
            position_size = calculate_position_size(
                current_balance, 
                risk_per_trade, 
                entry_price, 
                stop_loss
            )
            
            data.loc[data.index[i], 'Position'] = 1
            data.loc[data.index[i], 'StopLoss'] = stop_loss
            data.loc[data.index[i], 'TakeProfit'] = take_profit
            data.loc[data.index[i], 'PositionSize'] = position_size
        
        elif data['Signal'].iloc[i] == -1:
            entry_price = data['Close'].iloc[i]
            stop_loss = entry_price * (1 + stop_loss_pct)
            take_profit = entry_price * (1 - take_profit_pct)
            
            position_size = calculate_position_size(
                current_balance, 
                risk_per_trade, 
                entry_price, 
                stop_loss
            )
            
            data.loc[data.index[i], 'Position'] = -1
            data.loc[data.index[i], 'StopLoss'] = stop_loss
            data.loc[data.index[i], 'TakeProfit'] = take_profit
            data.loc[data.index[i], 'PositionSize'] = position_size
    
    return data

這個風險管理系統整合部位大小計算與停損停利設定。calculate_position_size 函式實作風險調整法,根據停損距離與帳戶風險容忍度計算適當的部位大小。apply_risk_management 函式遍歷所有交易訊號,為每個進場點設定對應的風險管理參數。做多時停損設在進場價下方,獲利目標設在上方;做空時則相反。這些參數將用於後續的回測與交易執行。

策略績效評估框架

評估交易策略的績效不能只看總獲利,必須考慮風險調整後的報酬、最大回撤、勝率等多維度指標。夏普比率(Sharpe Ratio)衡量每單位風險所獲得的超額報酬,是最常用的風險調整績效指標。最大回撤(Maximum Drawdown)反映策略歷史上最嚴重的損失幅度,幫助投資人評估最壞情況下的風險。勝率(Win Rate)是獲利交易次數佔總交易次數的比例,但高勝率不一定代表高獲利,還需要考慮平均獲利與平均損失的比例。

def calculate_performance_metrics(data):
    """
    計算策略績效指標
    
    參數:
        data: 包含交易結果的 DataFrame
        
    回傳:
        dict: 績效指標字典
    """
    data = data.copy()
    data['Returns'] = data['Close'].pct_change()
    data['StrategyReturns'] = data['Returns'] * data['Position'].shift(1)
    
    total_return = (1 + data['StrategyReturns']).prod() - 1
    
    annual_return = (1 + total_return) ** (252 / len(data)) - 1
    
    returns_std = data['StrategyReturns'].std()
    sharpe_ratio = (annual_return - 0.02) / (returns_std * np.sqrt(252)) if returns_std != 0 else 0
    
    cumulative_returns = (1 + data['StrategyReturns']).cumprod()
    running_max = cumulative_returns.expanding().max()
    drawdown = (cumulative_returns - running_max) / running_max
    max_drawdown = drawdown.min()
    
    winning_trades = data[data['StrategyReturns'] > 0]['StrategyReturns']
    losing_trades = data[data['StrategyReturns'] < 0]['StrategyReturns']
    
    win_rate = len(winning_trades) / (len(winning_trades) + len(losing_trades)) if (len(winning_trades) + len(losing_trades)) > 0 else 0
    
    avg_win = winning_trades.mean() if len(winning_trades) > 0 else 0
    avg_loss = abs(losing_trades.mean()) if len(losing_trades) > 0 else 0
    profit_factor = (avg_win * len(winning_trades)) / (avg_loss * len(losing_trades)) if len(losing_trades) > 0 and avg_loss != 0 else 0
    
    metrics = {
        '總報酬率': f'{total_return * 100:.2f}%',
        '年化報酬率': f'{annual_return * 100:.2f}%',
        '夏普比率': f'{sharpe_ratio:.2f}',
        '最大回撤': f'{max_drawdown * 100:.2f}%',
        '勝率': f'{win_rate * 100:.2f}%',
        '獲利因子': f'{profit_factor:.2f}',
        '平均獲利': f'{avg_win * 100:.2f}%',
        '平均虧損': f'{avg_loss * 100:.2f}%'
    }
    
    return metrics

這個績效評估函式計算全方位的策略指標。首先計算策略報酬,透過將市場報酬乘以部位方向(1 代表做多,-1 代表做空,0 代表空手)。累積報酬展示資金曲線的成長軌跡。年化報酬將總報酬標準化為年度基礎,便於與其他投資機會比較。夏普比率使用無風險利率(假設 2%)計算超額報酬,然後除以報酬波動度。獲利因子是總獲利除以總虧損,大於 1 表示策略整體獲利,數值越高表示獲利能力越強。

@startuml
!define DISABLE_LINK
!define PLANTUML_FORMAT svg
!theme _none_

skinparam dpi auto
skinparam shadowing false
skinparam linetype ortho
skinparam roundcorner 5
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 16
skinparam minClassWidth 100

package "績效評估系統" {
  [報酬計算] as return_calc
  [風險指標] as risk_metric
  [交易統計] as trade_stat
  [視覺化呈現] as visual_report
}

[歷史資料] --> [報酬計算]
[交易訊號] --> [報酬計算]
[報酬計算] --> [風險指標]
[報酬計算] --> [交易統計]
[風險指標] --> [視覺化呈現]
[交易統計] --> [視覺化呈現]

note right of return_calc
  總報酬率
  年化報酬率
  累積報酬曲線
end note

note right of risk_metric
  夏普比率
  最大回撤
  報酬波動度
end note

note right of trade_stat
  勝率
  獲利因子
  平均獲損比
end note

@enduml

交易心理與紀律執行

技術面的完善策略只是成功交易的一半,另一半取決於交易者的心理素質與紀律執行。市場波動經常觸發人性中的貪婪與恐懼,導致偏離原定計劃的衝動決策。貪婪使交易者在獲利時不願止盈,期待更大的利潤,結果往往是利潤回吐甚至轉為虧損。恐懼則在虧損時放大,導致過早停損或報復性交易,打亂原有的風險管理計劃。

程式化交易的優勢在於能夠消除情緒干擾,嚴格按照預設規則執行。一旦策略通過嚴謹的回測驗證,就應該信任系統並堅持執行,避免因為短期績效波動而頻繁調整。交易日誌是培養紀律的有效工具,記錄每筆交易的理由、進出場時機、情緒狀態與結果反思。透過定期檢視日誌,能夠發現重複出現的錯誤模式,持續改進交易流程。

設定合理的績效預期同樣重要。專業交易者年化報酬率能夠穩定維持在 20-30% 已經是優秀表現,不應該期待短期內實現財富自由的暴利。接受虧損是交易的必然部分,沒有任何策略能夠百戰百勝。關鍵是確保獲利交易的規模大於虧損交易,透過正期望值的策略長期累積優勢。當面對連續虧損時,更需要保持冷靜,檢視是否因為市場環境改變而需要暫停交易,而非盲目增加部位試圖快速回本。

持續優化與適應市場

金融市場不斷演化,昨天有效的策略可能在今天失效。策略優化是持續進行的過程,需要定期檢視績效並根據市場變化調整參數。但優化必須謹慎,避免過度配適(Overfitting)的陷阱。過度配適是指策略過度針對歷史資料調整,在回測中表現優異但在實盤交易中失效。

樣本外測試(Out-of-Sample Testing)是避免過度配適的有效方法。將歷史資料分為訓練集與測試集,在訓練集上優化參數,然後在測試集上驗證績效。如果測試集績效大幅低於訓練集,可能存在過度配適問題。前進分析(Walk-Forward Analysis)則模擬實際交易情境,在滾動的時間視窗中訓練與測試策略,更真實地反映策略的適應性。

市場制度(Market Regime)的變化會影響策略績效。趨勢市與盤整市需要不同的交易方法,高波動與低波動環境也需要調整風險參數。建立市場狀態分類系統,根據當前市場特徵選擇適當的策略,能夠提升整體績效的穩定性。例如在高波動期間降低槓桿,在低波動期間增加部位,動態調整風險暴露。

邁向專業交易之路

掌握 K 線圖交易策略的程式化實作,只是量化交易旅程的起點。從資料處理到訊號生成,從風險管理到績效評估,每個環節都需要深入理解與實務經驗。本文提供完整的技術框架與程式碼範例,讓讀者能夠快速建構自己的交易系統。但真正的專業能力,需要透過持續學習、大量回測與實盤驗證逐步培養。

建議初學者從簡單策略開始,先熟悉完整的開發流程,再逐步增加複雜度。紮實的程式設計能力、統計學知識與金融市場理解是成功的三大支柱。參與交易社群交流經驗,閱讀經典著作汲取智慧,在模擬環境中反覆測試策略,都是成長的必經之路。

記住,量化交易不是聖杯,無法保證穩賺不賠。但透過系統化的方法、嚴謹的風險管理與持續的學習精進,能夠在市場中建立長期優勢。保持謙卑的態度面對市場,用科學的方法驗證假設,以紀律的執行貫徹策略,這是邁向專業交易者的正確道路。