iT邦幫忙

2024 iThome 鐵人賽

DAY 8
0
AI/ ML & Data

打開就會 AI 與數據分析的投資理財術系列 第 8

Day7:利用 Backtrader 進行策略參數優化夏普率細談

  • 分享至 

  • xImage
  •  

在昨天的章節中我們有先窺視了一下策略優化的事,今天,我們將仔細談談這個,我們會學習如何在 Backtrader 中進行多次回測,並找到最佳的策略參數配置,進一步優化我們的策略以提高夏普比率。這將使我們能夠有效地篩選出最適合的參數組合。今日 Colab


一、導入所需的庫

首先,讓我們導入本範例所需的庫:

import backtrader as bt
import backtrader.analyzers as btanalyzers
import pandas as pd
from datetime import datetime
import yfinance as yf  # 若要下載財經數據,可以使用 yfinance

二、建立策略類別並加入參數

我們使用之前介紹過得均線交叉策略為範例。參數最佳化本質為表格化去嘗試每個可能的組合並從中找到最佳組合。為了執行參數最佳化,我們需要在策略類別中定義參數,如下:

params = (
        ('fast_length', 10),
        ('slow_length', 50)
    )

並在指標計算中使用這些參數,像是如下:

ma_fast = bt.ind.SMA(period=self.params.fast_length)
ma_slow = bt.ind.SMA(period=self.params.slow_length)        

1. 定義策略類別

完整定義如下:

class MaCrossStrategy(bt.Strategy):
    params = (
        ('fast_length', 10),
        ('slow_length', 50)
    )
    
    def __init__(self):
        ma_fast = bt.ind.SMA(period=self.params.fast_length)
        ma_slow = bt.ind.SMA(period=self.params.slow_length)
        
        self.crossover = bt.ind.CrossOver(ma_fast, ma_slow)

    def next(self):
        if not self.position:
            if self.crossover > 0:
                self.buy()
        elif self.crossover < 0:
            self.close()

三、設置策略參數範圍並執行優化

1. 使用 optstrategy 方法指定參數範圍

cerebro = bt.Cerebro()

# 使用 yfinance 或 Yahoo Finance 資料
data = bt.feeds.YahooFinanceData(dataname='AAPL', fromdate=datetime(2010, 1, 1), todate=datetime(2020, 1, 1))
cerebro.adddata(data)

# 設定策略參數範圍
strats = cerebro.optstrategy(
    MaCrossStrategy,
    fast_length=range(1, 11),
    slow_length=range(25, 76, 5)
)

# 設定初始資金與分析工具
cerebro.broker.setcash(1000000.0)
cerebro.addsizer(bt.sizers.PercentSizer, percents=10)
cerebro.addanalyzer(btanalyzers.SharpeRatio, _name="sharpe", timeframe=bt.TimeFrame.Days, annualize=True)
cerebro.addanalyzer(btanalyzers.DrawDown, _name="drawdown")
cerebro.addanalyzer(btanalyzers.Returns, _name="returns")

# 執行回測
back = cerebro.run(maxcpus=1)

四、解析優化結果

執行優化後,我們需要將結果解析成方便的格式,以便篩選出最佳的策略參數組合。基本上你將優化過程想像成網格去搜尋最佳組合。

1. 提取參數與指標

# 提取回測結果中需要的參數與分析結果
par_list = []
for run in back:
    for strategy in run:
        sharpe_ratio = strategy.analyzers.sharpe.get_analysis().get('sharperatio')
        returns = strategy.analyzers.returns.get_analysis().get('rnorm100')
        drawdown = strategy.analyzers.drawdown.get_analysis().get('max', {}).get('drawdown')
        
        if sharpe_ratio is not None:
            par_list.append([
                strategy.params.fast_length,
                strategy.params.slow_length,
                returns,
                drawdown,
                sharpe_ratio
            ])

2. 轉換為 DataFrame 並找到最佳參數

# 檢查是否有有效的結果
if not par_list:
    print("無法計算有效的夏普比率,請檢查策略或回測資料。")
else:
    # 將結果轉換成 DataFrame
    par_df = pd.DataFrame(par_list, columns=['fast_length', 'slow_length', 'return', 'drawdown', 'sharpe'])

    print("par_df:")
    print(par_df)
    print("\n")

    # 以夏普比率排序,找到最佳參數組合
    best_result = par_df.sort_values(by='sharpe', ascending=False).iloc[0]
    print(f'最佳參數組合: 快速均線={best_result["fast_length"]}, 慢速均線={best_result["slow_length"]}')
    print(f'對應的夏普比率: {best_result["sharpe"]:.2f}')

可以得到下面結果:
https://ithelp.ithome.com.tw/upload/images/20240922/20120549pSV912D9vd.png
可以看到我們得到的 par_df 本質上就是各種排列組合的結果並且會紀錄其中對應到的觀測指標 (在這裡我們用 Sharpe),由於整張表是 pandas DataFrame 所以舞們輕而易舉的用排序找到最好的 Sharpe 值以及對應的參數~

五、結果分析與應用

通過將回測結果存儲在 DataFrame 中,我們可以方便地對其進行篩選和排序,找到最適合的策略參數組合。這樣,我們就能夠有效地優化策略並提高夏普比率。另外我們也提供動量策略的優化在 Appendix 章節


總結與作業

今天我們學習了如何利用 Backtrader 進行策略參數的優化,並且成功找到了最佳的策略參數組合,進一步提高了策略的夏普比率。

今日作業:

  1. 嘗試對不同的技術指標(如 RSIMACD)進行策略優化,可以參考 Appendix 完整程式做修改。
  2. 比較不同策略的夏普比率,找出最優的策略。
  3. 使用 DataFrame 對結果進行可視化分析,進一步優化你的投資策略。

透過這些練習,你將能夠更熟練地進行策略優化,並能夠在真實市場中應用所學的量化投資技巧。

Appendix:動量策略 (Momentum Strategy) 的夏普比率優化實驗

在這個附錄中,我們將進行動量策略的參數優化,並尋找出可以提高夏普比率的最佳參數組合。我們將設定動量策略的參數範圍,並使用 Backtrader 進行優化,尋找出最佳的 momentum_period 參數以提高夏普比率。。

完整程式

class MomentumStrategy(bt.Strategy):
    params = (('momentum_period', 10),)

    def __init__(self):
        # 定義動量指標
        self.momentum = bt.indicators.Momentum(self.data.close, period=self.params.momentum_period)
        self.daily_values = []  # 用於儲存每日資產價值

    def next(self):
        if not self.position:  # 如果沒有持倉
            if self.momentum[0] > 0:  # 動量指標大於0,表示上升趨勢
                self.buy()
        elif self.momentum[0] <= 0:  # 動量指標小於等於0,表示下降趨勢
            self.sell()
        
        # 記錄每日的資產價值
        self.daily_values.append(self.broker.getvalue())

    def stop(self):
        # 在每次策略完成時,將結果存儲到策略屬性中
        self.returns = pd.Series(self.daily_values).pct_change().dropna()
        annual_return = self.returns.mean() * 252
        annual_volatility = self.returns.std() * np.sqrt(252)
        risk_free_rate = 0.01  # 假設無風險利率為1%
        self.sharpe_ratio = (annual_return - risk_free_rate) / annual_volatility

        # 計算最大回撤
        cumulative_returns = (1 + self.returns).cumprod()
        drawdown = cumulative_returns / cumulative_returns.cummax() - 1
        self.max_drawdown = drawdown.min()

cerebro = bt.Cerebro()

# 使用 yfinance 下載 AAPL 的數據
data = yf.download('AAPL', start='2020-01-01', end='2021-01-01')

# 將數據轉換為 backtrader 可以使用的格式
data_bt = bt.feeds.PandasData(dataname=data)

cerebro.adddata(data_bt)

# 設定策略參數範圍
strats = cerebro.optstrategy(
    MomentumStrategy,
    momentum_period=range(5, 31, 5)  # 設定動量週期從5到30,每5為一個間隔
)

# 設定初始資金與分析工具
cerebro.broker.setcash(1000000.0)
cerebro.addsizer(bt.sizers.PercentSizer, percents=10)
cerebro.addanalyzer(btanalyzers.SharpeRatio, _name="sharpe", timeframe=bt.TimeFrame.Days, annualize=True)
cerebro.addanalyzer(btanalyzers.DrawDown, _name="drawdown")
cerebro.addanalyzer(btanalyzers.Returns, _name="returns")

# 執行回測
back = cerebro.run(maxcpus=1)

# 提取回測結果中需要的參數與分析結果
par_list = []
for run in back:
    for strategy in run:
        sharpe_ratio = strategy.analyzers.sharpe.get_analysis().get('sharperatio')
        returns = strategy.analyzers.returns.get_analysis().get('rnorm100')
        drawdown = strategy.analyzers.drawdown.get_analysis().get('max', {}).get('drawdown')
        
        if sharpe_ratio is not None:
            par_list.append([
                strategy.params.momentum_period,
                returns,
                drawdown,
                sharpe_ratio
            ])

# 檢查是否有有效的結果
if not par_list:
    print("無法計算有效的夏普比率,請檢查策略或回測資料。")
else:
    # 將結果轉換成 DataFrame
    par_df = pd.DataFrame(par_list, columns=['momentum_period', 'return', 'drawdown', 'sharpe'])

    # 以夏普比率排序,找到最佳參數組合
    best_result = par_df.sort_values(by='sharpe', ascending=False).iloc[0]
    print(f'最佳參數組合: 動量週期={best_result["momentum_period"]}')
    print(f'對應的夏普比率: {best_result["sharpe"]:.2f}')

上一篇
Day6:動量策略、均值回歸策略與技術指標策略開發
下一篇
Day8:風險管理基礎與風險調整後的收益
系列文
打開就會 AI 與數據分析的投資理財術30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言