Pyfolio 是一個用來評估投資策略表現的 Python 套件, 透過 Jupyter 或 Colab 等 web 介面呈現詳細的分析數據與視覺化圖表, 讓金融投資策略開發者可快速獲得投資組合的風險與績效分析, 例如年化報酬率, 夏普比率, 最大回撤 (MDD) 與波動率等. 除了獨立進行分析, 也可與回測框架 (例如 Zipline) 搭配使用.
Pyfolio 最早是在 2015 年由位於波士頓的對沖基金公司
Quantopian 所開發, 做為其演算法量化交易平台回測引擎 Zipline 的策略績效分析工具. 於 2017 年開放原始碼後受到金融交易社群與學術圈的喜愛. 不過由於 Quantopian 於 2022 年底停止營運, Pyfolio 的開發與維護也隨之停頓, 但 pyfolio 使用者社群仍非常活躍, 其原始碼與教學文件放在 GitHub :
網路教學文章參考 :
1. 安裝 pyfolio 套件 :
Pyfolio 可以直接用 pip 安裝 :
pip install pyfolio
這會從 PyPi 網站下載套件, 參考 :
D:\python\test>pip install pyfolio
Collecting pyfolio
Downloading pyfolio-0.9.2.tar.gz (91 kB)
Preparing metadata (setup.py) ... done
Requirement already satisfied: ipython>=3.2.3 in c:\users\tony1\appdata\roaming\python\python310\site-packages (from pyfolio) (8.15.0)
Requirement already satisfied: matplotlib>=1.4.0 in c:\users\tony1\appdata\roaming\python\python310\site-packages (from pyfolio) (3.7.2)
Requirement already satisfied: numpy>=1.11.1 in c:\users\tony1\appdata\roaming\python\python310\site-packages (from pyfolio) (1.24.3)
Requirement already satisfied: pandas>=0.18.1 in c:\users\tony1\appdata\roaming\python\python310\site-packages (from pyfolio) (2.0.3)
Requirement already satisfied: pytz>=2014.10 in c:\users\tony1\appdata\roaming\python\python310\site-packages (from pyfolio) (2023.3)
Requirement already satisfied: scipy>=0.14.0 in c:\users\tony1\appdata\roaming\python\python310\site-packages (from pyfolio) (1.11.2)
Requirement already satisfied: scikit-learn>=0.16.1 in c:\users\tony1\appdata\roaming\python\python310\site-packages (from pyfolio) (1.3.0)
Requirement already satisfied: seaborn>=0.7.1 in c:\users\tony1\appdata\roaming\python\python310\site-packages (from pyfolio) (0.12.2)
Collecting empyrical>=0.5.0 (from pyfolio)
Downloading empyrical-0.5.5.tar.gz (52 kB)
Preparing metadata (setup.py) ... done
Collecting pandas-datareader>=0.2 (from empyrical>=0.5.0->pyfolio)
Downloading pandas_datareader-0.10.0-py3-none-any.whl.metadata (2.9 kB)
Requirement already satisfied: backcall in c:\users\tony1\appdata\roaming\python\python310\site-packages (from ipython>=3.2.3->pyfolio) (0.2.0)
Requirement already satisfied: decorator in c:\users\tony1\appdata\roaming\python\python310\site-packages (from ipython>=3.2.3->pyfolio) (5.1.1)
Requirement already satisfied: jedi>=0.16 in c:\users\tony1\appdata\local\programs\thonny\lib\site-packages (from ipython>=3.2.3->pyfolio) (0.18.2)
Requirement already satisfied: matplotlib-inline in c:\users\tony1\appdata\roaming\python\python310\site-packages (from ipython>=3.2.3->pyfolio) (0.1.6)
Requirement already satisfied: pickleshare in c:\users\tony1\appdata\roaming\python\python310\site-packages (from ipython>=3.2.3->pyfolio) (0.7.5)
Requirement already satisfied: prompt-toolkit!=3.0.37,<3.1.0,>=3.0.30 in c:\users\tony1\appdata\roaming\python\python310\site-packages (from ipython>=3.2.3->pyfolio) (3.0.39)
Requirement already satisfied: pygments>=2.4.0 in c:\users\tony1\appdata\roaming\python\python310\site-packages (from ipython>=3.2.3->pyfolio) (2.16.1)
Requirement already satisfied: stack-data in c:\users\tony1\appdata\roaming\python\python310\site-packages (from ipython>=3.2.3->pyfolio) (0.6.2)
Requirement already satisfied: traitlets>=5 in c:\users\tony1\appdata\roaming\python\python310\site-packages (from ipython>=3.2.3->pyfolio) (5.9.0)
Requirement already satisfied: exceptiongroup in c:\users\tony1\appdata\roaming\python\python310\site-packages (from ipython>=3.2.3->pyfolio) (1.1.3)
Requirement already satisfied: colorama in c:\users\tony1\appdata\local\programs\thonny\lib\site-packages (from ipython>=3.2.3->pyfolio) (0.4.6)
Requirement already satisfied: contourpy>=1.0.1 in c:\users\tony1\appdata\roaming\python\python310\site-packages (from matplotlib>=1.4.0->pyfolio) (1.1.0)
Requirement already satisfied: cycler>=0.10 in c:\users\tony1\appdata\roaming\python\python310\site-packages (from matplotlib>=1.4.0->pyfolio) (0.11.0)
Requirement already satisfied: fonttools>=4.22.0 in c:\users\tony1\appdata\roaming\python\python310\site-packages (from matplotlib>=1.4.0->pyfolio) (4.42.1)
Requirement already satisfied: kiwisolver>=1.0.1 in c:\users\tony1\appdata\roaming\python\python310\site-packages (from matplotlib>=1.4.0->pyfolio) (1.4.5)
Requirement already satisfied: packaging>=20.0 in c:\users\tony1\appdata\roaming\python\python310\site-packages (from matplotlib>=1.4.0->pyfolio) (23.1)
Requirement already satisfied: pillow>=6.2.0 in c:\users\tony1\appdata\roaming\python\python310\site-packages (from matplotlib>=1.4.0->pyfolio) (9.5.0)
Requirement already satisfied: pyparsing<3.1,>=2.3.1 in c:\users\tony1\appdata\roaming\python\python310\site-packages (from matplotlib>=1.4.0->pyfolio) (3.0.9)
Requirement already satisfied: python-dateutil>=2.7 in c:\users\tony1\appdata\roaming\python\python310\site-packages (from matplotlib>=1.4.0->pyfolio) (2.8.2)
Requirement already satisfied: tzdata>=2022.1 in c:\users\tony1\appdata\roaming\python\python310\site-packages (from pandas>=0.18.1->pyfolio) (2023.3)
Requirement already satisfied: joblib>=1.1.1 in c:\users\tony1\appdata\roaming\python\python310\site-packages (from scikit-learn>=0.16.1->pyfolio) (1.3.2)
Requirement already satisfied: threadpoolctl>=2.0.0 in c:\users\tony1\appdata\roaming\python\python310\site-packages (from scikit-learn>=0.16.1->pyfolio) (3.2.0)
Requirement already satisfied: parso<0.9.0,>=0.8.0 in c:\users\tony1\appdata\local\programs\thonny\lib\site-packages (from jedi>=0.16->ipython>=3.2.3->pyfolio) (0.8.3)
Requirement already satisfied: lxml in c:\users\tony1\appdata\roaming\python\python310\site-packages (from pandas-datareader>=0.2->empyrical>=0.5.0->pyfolio) (4.9.3)
Requirement already satisfied: requests>=2.19.0 in c:\users\tony1\appdata\roaming\python\python310\site-packages (from pandas-datareader>=0.2->empyrical>=0.5.0->pyfolio) (2.31.0)
Requirement already satisfied: wcwidth in c:\users\tony1\appdata\roaming\python\python310\site-packages (from prompt-toolkit!=3.0.37,<3.1.0,>=3.0.30->ipython>=3.2.3->pyfolio) (0.2.6)
Requirement already satisfied: six>=1.5 in c:\users\tony1\appdata\local\programs\thonny\lib\site-packages (from python-dateutil>=2.7->matplotlib>=1.4.0->pyfolio) (1.16.0)
Requirement already satisfied: executing>=1.2.0 in c:\users\tony1\appdata\roaming\python\python310\site-packages (from stack-data->ipython>=3.2.3->pyfolio) (1.2.0)
Requirement already satisfied: asttokens>=2.1.0 in c:\users\tony1\appdata\local\programs\thonny\lib\site-packages (from stack-data->ipython>=3.2.3->pyfolio) (2.2.1)
Requirement already satisfied: pure-eval in c:\users\tony1\appdata\roaming\python\python310\site-packages (from stack-data->ipython>=3.2.3->pyfolio) (0.2.2)
Requirement already satisfied: charset-normalizer<4,>=2 in c:\users\tony1\appdata\roaming\python\python310\site-packages (from requests>=2.19.0->pandas-datareader>=0.2->empyrical>=0.5.0->pyfolio) (3.2.0)
Requirement already satisfied: idna<4,>=2.5 in c:\users\tony1\appdata\roaming\python\python310\site-packages (from requests>=2.19.0->pandas-datareader>=0.2->empyrical>=0.5.0->pyfolio) (3.4)
Requirement already satisfied: urllib3<3,>=1.21.1 in c:\users\tony1\appdata\local\programs\thonny\lib\site-packages (from requests>=2.19.0->pandas-datareader>=0.2->empyrical>=0.5.0->pyfolio) (1.26.19)
Requirement already satisfied: certifi>=2017.4.17 in c:\users\tony1\appdata\roaming\python\python310\site-packages (from requests>=2.19.0->pandas-datareader>=0.2->empyrical>=0.5.0->pyfolio) (2023.7.22)
Downloading pandas_datareader-0.10.0-py3-none-any.whl (109 kB)
Building wheels for collected packages: pyfolio, empyrical
Building wheel for pyfolio (setup.py) ... done
Created wheel for pyfolio: filename=pyfolio-0.9.2-py3-none-any.whl size=88689 sha256=cc4a1c12fb59a822d23492165f319d973d85870c01bda5b80915ced28a199a88
Stored in directory: c:\users\tony1\appdata\local\pip\cache\wheels\71\38\bc\e53700cfd8b0ad6b539d2fbaaf060ed8a299e7622a5b86ef42
Building wheel for empyrical (setup.py) ... done
Created wheel for empyrical: filename=empyrical-0.5.5-py3-none-any.whl size=39779 sha256=7780681258103e51def4b2e78230f7d9e84dddf677cb7e840be6b1abf4701561
Stored in directory: c:\users\tony1\appdata\local\pip\cache\wheels\0e\2e\f2\d6d2d9a1eb8fbbd9949bb5d4c00f753e3b74e5bd7ed10b1d36
Successfully built pyfolio empyrical
Installing collected packages: pandas-datareader, empyrical, pyfolio
Successfully installed empyrical-0.5.5 pandas-datareader-0.10.0 pyfolio-0.9.2
可見 Pyfolio 是在 Pandas, Numpy, Matplotlib + Seaborn 等套件基礎上建構的, 主要依賴 Matplotlib 作為繪圖核心. 匯入 pyfolio 模組檢視版本 (通常取簡名 pf) :
>>> import pyfolio as pf
C:\Users\tony1\AppData\Local\Programs\Thonny\lib\site-packages\pyfolio\pos.py:26: UserWarning: Module "zipline.assets" not found; mutltipliers will not be applied to position notionals.
warnings.warn(
出現這個 warning 的原因是因為 Pyfolio 通常與回測工具 Zipline 搭配使用, 其原始碼中有一部分預設會匯入其處理資產權重的 assets 模組, 因沒有安裝 Zipline 套件所以找不到此模組. 因 Zipline 並非 Pyfolio 必要之相依套件因此可忽略之, 也可以用 warnings 模組的 filterwarnings() 函式將其濾掉 :
>>> import warnings
>>> warnings.filterwarnings('ignore')
再次匯入 pyfolio 就不會有 warning 了 :
>>> import pyfolio as pf
>>> pf.__version__
'0.9.2'
>>> list_members(pf)
APPROX_BDAYS_PER_MONTH <class 'int'>
FACTOR_PARTITIONS <class 'dict'>
FigureCanvasAgg <class 'type'>
FuncFormatter <class 'type'>
MM_DISPLAY_UNIT <class 'float'>
Markdown <class 'type'>
OrderedDict <class 'type'>
STAT_FUNCS_PCT <class 'list'>
axes_style <class 'function'>
capacity <class 'module'>
create_bayesian_tear_sheet <class 'function'>
create_capacity_tear_sheet <class 'function'>
create_full_tear_sheet <class 'function'>
create_interesting_times_tear_sheet <class 'function'>
create_perf_attrib_tear_sheet <class 'function'>
create_position_tear_sheet <class 'function'>
create_returns_tear_sheet <class 'function'>
create_risk_tear_sheet <class 'function'>
create_round_trip_tear_sheet <class 'function'>
create_simple_tear_sheet <class 'function'>
create_txn_tear_sheet <class 'function'>
customize <class 'function'>
datetime <class 'module'>
deprecate <class 'module'>
display <class 'function'>
division <class '__future__._Feature'>
ep <class 'module'>
figure <class 'module'>
gridspec <class 'module'>
have_bayesian <class 'bool'>
interesting_periods <class 'module'>
matplotlib <class 'module'>
np <class 'module'>
patches <class 'module'>
pd <class 'module'>
perf_attrib <class 'module'>
plot_annual_returns <class 'function'>
plot_capacity_sweep <class 'function'>
plot_cones <class 'function'>
plot_daily_turnover_hist <class 'function'>
plot_daily_volume <class 'function'>
plot_drawdown_periods <class 'function'>
plot_drawdown_underwater <class 'function'>
plot_exposures <class 'function'>
plot_gross_leverage <class 'function'>
plot_holdings <class 'function'>
plot_long_short_holdings <class 'function'>
plot_max_median_position_concentration <class 'function'>
plot_monthly_returns_dist <class 'function'>
plot_monthly_returns_heatmap <class 'function'>
plot_monthly_returns_timeseries <class 'function'>
plot_perf_stats <class 'function'>
plot_prob_profit_trade <class 'function'>
plot_return_quantiles <class 'function'>
plot_returns <class 'function'>
plot_rolling_beta <class 'function'>
plot_rolling_returns <class 'function'>
plot_rolling_sharpe <class 'function'>
plot_rolling_volatility <class 'function'>
plot_round_trip_lifetimes <class 'function'>
plot_sector_allocations <class 'function'>
plot_slippage_sensitivity <class 'function'>
plot_slippage_sweep <class 'function'>
plot_turnover <class 'function'>
plot_txn_time_hist <class 'function'>
plotting <class 'module'>
plotting_context <class 'function'>
plt <class 'module'>
pos <class 'module'>
pytz <class 'module'>
risk <class 'module'>
round_trips <class 'module'>
scipy <class 'module'>
show_and_plot_top_positions <class 'function'>
show_perf_stats <class 'function'>
show_profit_attribution <class 'function'>
show_worst_drawdown_periods <class 'function'>
sns <class 'module'>
sp <class 'module'>
tears <class 'module'>
time <class 'builtin_function_or_method'>
timer <class 'function'>
timeseries <class 'module'>
txn <class 'module'>
utils <class 'module'>
warnings <class 'module'>
wraps <class 'function'>
雖然 Pyfolio 提供玲瑯滿目的函式與模組, 但我們通常只會用到它的快捷函式 create_returns_tear_sheet(), 它會自動產生績效分析與風險評估的全部數據與圖表. 此函式必須傳入一個 Series 物件, 且其索引須為日期時間類型, 值通常為代表報酬率 (return) 的小數, 這可以利用 Series 物件的 pct_change() 方法求得.
2. 在 web 介面上執行 pyfolio :
Pyfolio 必須在 web 介面例如 Jupyter Lab, Jupyter Notebook 或 Colab 上執行, 因為它要透過網頁繪製數據分析的圖表, 命令列執行環境無法滿足此要求.
先在本機用 Jupyter Lab 測試, 先在命令列輸入 jupyter lab 啟動 web 介面 :
D:\python\test>jupyter lab
匯入 Pyfolio : import pyfolio as pf
以下以買入 0050 一股持有不賣策略為例測試 pyfolio 用法, 先用 yfinance 下載股票資料 :
import yfinance as yf
df=yf.download('0050.TW')
檢視前後五筆資料 :
df.head()
Open High Low Close Adj Close Volume
Date
2008-01-02 60.009998 60.009998 60.009998 60.009998 42.407536 0
2008-01-03 58.889999 58.889999 58.889999 58.889999 41.616066 0
2008-01-04 59.009998 59.009998 59.009998 59.009998 41.700867 0
2008-01-07 56.389999 56.389999 56.389999 56.389999 39.849377 0
2008-01-08 56.980000 56.980000 56.980000 56.980000 40.266319 0
df.tail()
Open High ... Adj Close Volume
Date ...
2025-01-06 198.649994 202.300003 ... 202.149994 24627793
2025-01-07 204.949997 206.000000 ... 204.399994 15943844
2025-01-08 202.649994 203.350006 ... 201.350006 11855717
2025-01-09 200.199997 201.149994 ... 199.050003 8430655
2025-01-10 198.250000 198.250000 ... 198.250000 0
然後利用此股票資料中的調整後收盤價 Adj Close 計算此策略之報酬率, 如果時間較短用 Close 尚可, 但期間長達 15 年應該用調整後價格較準確. 計算報酬率可以呼叫 Series 物件的 pct_change() 方法達成, 此方法是將今日價減昨日價後再除以昨日價得到今日報酬率 :
returns=df['Adj Close'].pct_change()
print(returns)
可知傳回值為一個以日期時間為索引的 Series 物件, 符合 Pyfolio 的要求, 將此 returns 傳給 pf.create_returns_tear_sheet() 即可得到分析結果 :
pf.create_returns_tear_sheet(returns)
但執行卻出現下列錯誤訊息 :
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
Cell In[23], line 2
1 returns.dropna()
----> 2 pf.create_returns_tear_sheet(returns.dropna())
File ~\AppData\Local\Programs\Thonny\lib\site-packages\pyfolio\plotting.py:52, in customize.<locals>.call_w_context(*args, **kwargs)
50 if set_context:
51 with plotting_context(), axes_style():
---> 52 return func(*args, **kwargs)
53 else:
54 return func(*args, **kwargs)
File ~\AppData\Local\Programs\Thonny\lib\site-packages\pyfolio\tears.py:496, in create_returns_tear_sheet(returns, positions, transactions, live_start_date, cone_std, benchmark_rets, bootstrap, turnover_denom, header_rows, return_fig)
493 if benchmark_rets is not None:
494 returns = utils.clip_returns_to_benchmark(returns, benchmark_rets)
--> 496 plotting.show_perf_stats(returns, benchmark_rets,
497 positions=positions,
498 transactions=transactions,
499 turnover_denom=turnover_denom,
500 bootstrap=bootstrap,
501 live_start_date=live_start_date,
502 header_rows=header_rows)
504 plotting.show_worst_drawdown_periods(returns)
506 vertical_sections = 11
File ~\AppData\Local\Programs\Thonny\lib\site-packages\pyfolio\plotting.py:648, in show_perf_stats(returns, factor_returns, positions, transactions, turnover_denom, live_start_date, bootstrap, header_rows)
645 perf_stats = pd.DataFrame(perf_stats_all, columns=['Backtest'])
647 for column in perf_stats.columns:
--> 648 for stat, value in perf_stats[column].iteritems():
649 if stat in STAT_FUNCS_PCT:
650 perf_stats.loc[stat, column] = str(np.round(value * 100,
651 1)) + '%'
File ~\AppData\Roaming\Python\Python310\site-packages\pandas\core\generic.py:5989, in NDFrame.__getattr__(self, name)
5982 if (
5983 name not in self._internal_names_set
5984 and name not in self._metadata
5985 and name not in self._accessors
5986 and self._info_axis._can_hold_identifiers_and_holds_name(name)
5987 ):
5988 return self[name]
-> 5989 return object.__getattribute__(self, name)
AttributeError: 'Series' object has no attribute 'iteritems'
將錯誤訊息丟給 ChatGPT, 發現原因是與 Pandas 版本不相容, 檢查目前版本是 2.0.3, 而 Pyfolio 因為使用 Pandas v1 版, 所用的 iteritems() 函式已被廢棄導致錯誤, ChatGPT 建議的解決辦法之一是把 Pandas 版本降回 1.2.x 版, 例如 1.2.5 :
pip install pandas==1.2.5
第二個辦法是到 Pyfolio 的安裝路徑下找出 plotting.py 檔, 將第 648 列的 iteritem() 改為 item() 即可, 利用 .__file__ 屬性即可查到此路徑 :
print(pf.__file__)
C:\Users\tony1\AppData\Local\Programs\Thonny\lib\site-packages\pyfolio\__init__.py
改好後再次執行出現另一個錯誤訊息 :
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
Cell In[5], line 1
----> 1 pf.create_returns_tear_sheet(returns)
File ~\AppData\Local\Programs\Thonny\lib\site-packages\pyfolio\plotting.py:52, in customize.<locals>.call_w_context(*args, **kwargs)
50 if set_context:
51 with plotting_context(), axes_style():
---> 52 return func(*args, **kwargs)
53 else:
54 return func(*args, **kwargs)
File ~\AppData\Local\Programs\Thonny\lib\site-packages\pyfolio\tears.py:504, in create_returns_tear_sheet(returns, positions, transactions, live_start_date, cone_std, benchmark_rets, bootstrap, turnover_denom, header_rows, return_fig)
494 returns = utils.clip_returns_to_benchmark(returns, benchmark_rets)
496 plotting.show_perf_stats(returns, benchmark_rets,
497 positions=positions,
498 transactions=transactions,
(...)
501 live_start_date=live_start_date,
502 header_rows=header_rows)
--> 504 plotting.show_worst_drawdown_periods(returns)
506 vertical_sections = 11
508 if live_start_date is not None:
File ~\AppData\Local\Programs\Thonny\lib\site-packages\pyfolio\plotting.py:1664, in show_worst_drawdown_periods(returns, top)
1648 def show_worst_drawdown_periods(returns, top=5):
1649 """
1650 Prints information about the worst drawdown periods.
1651
(...)
1661 Amount of top drawdowns periods to plot (default 5).
1662 """
-> 1664 drawdown_df = timeseries.gen_drawdown_table(returns, top=top)
1665 utils.print_table(
1666 drawdown_df.sort_values('Net drawdown in %', ascending=False),
1667 name='Worst drawdown periods',
1668 float_format='{0:.2f}'.format,
1669 )
File ~\AppData\Local\Programs\Thonny\lib\site-packages\pyfolio\timeseries.py:1008, in gen_drawdown_table(returns, top)
1003 df_drawdowns.loc[i, 'Duration'] = len(pd.date_range(peak,
1004 recovery,
1005 freq='B'))
1006 df_drawdowns.loc[i, 'Peak date'] = (peak.to_pydatetime()
1007 .strftime('%Y-%m-%d'))
-> 1008 df_drawdowns.loc[i, 'Valley date'] = (valley.to_pydatetime()
1009 .strftime('%Y-%m-%d'))
1010 if isinstance(recovery, float):
1011 df_drawdowns.loc[i, 'Recovery date'] = recovery
AttributeError: 'numpy.int64' object has no attribute 'to_pydatetime'
詢問 ChatGPT 回覆錯誤原因同樣是 Pandas 版本不相容所致, 建議修改 Pyfolio 的 timeseries.py 檔, 將其中第 1008~1009 列改為如下寫法 :
df_drawdowns.loc[i, 'Valley date'] = pd.Timestamp(valley).strftime('%Y-%m-%d')
舊版寫法是 :
df_drawdowns.loc[i, 'Valley date'] = (valley.to_pydatetime()
.strftime('%Y-%m-%d'))
改好後重新執行又出現另一個錯誤 :
原因還是 Pandas 版本不相容, 同樣是 timeseries.py 這檔案, 問題出現在 1014~1015 列的 gen_drawdown_table() 函式寫法, 原本寫法是 :
df_drawdowns.loc[i, 'Net drawdown in %'] = (
(df_cum.loc[peak] - df_cum.loc[valley]) / df_cum.loc[peak]) * 100
ChatGPT 建議改為如下 :
try:
df_drawdowns.loc[i, 'Net drawdown in %'] = (
(df_cum.loc[peak] - df_cum.loc[valley]) / df_cum.loc[peak]) * 100
except KeyError:
# 如果索引找不到,設置為 NaN 或其他處理方式
df_drawdowns.loc[i, 'Net drawdown in %'] = float('nan')
改好後就可以順利執行, 結果會產生下面的 13 張分析圖表 :
第一張圖表包含年化報酬率, 累積報酬率, 最大回撤率 (MDD, Maximum Draw Down) 等統計數值之摘要報告 :
MDD 表示資產淨值從最高峰到最低谷的最大跌幅, 是評估投資組合在特定期間內抗風險能力的重要指標, 它反映了投資者在最糟糕情況下可能面臨的損失比例 (最大曝險比率), 是考量曝險能力的重要數據. MDD 的計算通常用資產最大值與最小值之差除以最大值而得, 也可以用累積報酬率的峰值與谷值來算.
第二張圖表顯示歷次回撤的區間與波峰波谷時間 :
第三張圖表 Cumulative returns 是累積報酬率折線圖 :
第四張圖表 Cumulative returns volatility mathed to benchmark 是累積報酬率與基準報酬率 (例如大盤) 的比較, 可看出此策略之投資組合是否跑贏基準報酬率 :
第五張圖表 Cumulative returns on logorithmic scale 則是以對數刻度來顯示累積報酬率與基準報酬率之比較 :
第六張圖表 Returns 是每日報酬率折線圖 :
第七張圖表 Rolling volatility (6-month) 顯示六個月為窗口 (window) 的移動波動率的變化, 以標準差為單位來衡量投資組合報酬率之波動程度 :
第八張圖表 Rolling Sharpe ratio (6-month) 顯示 6 個月為窗口 (window) 的夏普比率變化, 用來衡量報酬率相對於風險的效率. 夏普比的定義是單位風險所能獲得的報酬率, 基本上低於 0.5 的策略不要考慮, 大於 1 就算是很不錯的策略了.
第九張圖表 Top 5 Drawdown periods 顯示最大的前五個回撤區間 :
第十張圖表 Underwater plot 顯示投資組合的回撤深度, 頻率和恢復時間, 讓投資者可觀察策略在不同行情下的表現, 了解該策略是否符合自身的風險承受能力 :
第十張圖表 Monthly returns 依照年月順序 (年份為垂直軸由上而下, 月份為水平軸由左至右) 以不同顏色在熱力圖中顯示每個月的報酬率表現, 正報酬以綠色系, 顏色越深報酬越大; 負報酬以紅色系表示, 越紅越負, 可藉顏色與深淺不同來觀察每年的季節性或趨勢性表現, 找出表現特別好或特別差的月份 :
第十一張圖表 Annual returns 由上而下依照年度以水平長條圖顯示各年度之報酬率 :
第十二張圖表 Distribution of monthly returns 以直方圖呈現投資組合的每月報酬率分佈狀況, 可讓投資者了解報酬率的集中程度, 對稱性, 以及是否存在異常值. 直方圖的橫軸是報酬率的分布範圍, 它將將整個報酬率範圍分為若干個區間 (bins), 例如每 2%. 而縱軸則表示每個報酬率區間發生的次數, 高度越高表示該區間內的報酬率出現得越頻繁 :
第十三張圖表 Return quantiles 呈現投資組合報酬率在不同時間範圍內的分佈情況, 它用類似箱型圖的形式來顯示報酬率在各個時間段的分佈範圍, 橫軸表示日, 周, 月等不同時間範圍; 縱軸表示報酬率, 箱型圖最中間的線代表報酬率之中位數, 箱子的範圍 (上下四分位距) 表示 25% 到 75% 分位數的報酬率範圍 :
投資者可從箱型圖的範圍變化評估策略的穩定性和表現特徵, 若箱型圖的範圍隨著時間窗口增加而變窄, 表示投資組合在更長的時間範圍時表現更加穩定; 如果範圍隨著時間增加而變寬則表示隨著時間窗口拉長, 報酬率可能更加不穩定或波動變大.
我將上面修改好的 plotting.py 與 timeseries.py 這兩個檔案放在 GitHub, 如果 Pandas 版本是 v2 以上的話, 只要將此兩檔案下載後複製到 Pyfolio 安裝目錄下覆蓋舊檔案即可 :
查詢 Pyfolio 安裝路徑的方法 :
import pyfolio as pf
print(pf.__file__)