策略開發10分鐘上手
1.安裝套件¶
In [22]:
Copied!
!pip install finlab > log.txt
!pip install finlab > log.txt
2.取得資料¶
2-1.時間序列¶
In [ ]:
Copied!
from finlab import data
pb = data.get('price_earning_ratio:股價淨值比')
close = data.get('price:收盤價')
close.iloc[:5,:5]
from finlab import data
pb = data.get('price_earning_ratio:股價淨值比')
close = data.get('price:收盤價')
close.iloc[:5,:5]
輸入成功!
INFO:finlab.data:price_earning_ratio:股價淨值比 -- Daily data usage: 434.0 / 5000 MB INFO:finlab.data:price:收盤價 -- Daily data usage: 455.0 / 5000 MB
Out[ ]:
0015 | 0050 | 0051 | 0052 | 0053 | |
---|---|---|---|---|---|
date | |||||
2007-04-23 | 9.54 | 57.85 | 32.83 | 38.40 | NaN |
2007-04-24 | 9.54 | 58.10 | 32.99 | 38.65 | NaN |
2007-04-25 | 9.52 | 57.60 | 32.80 | 38.59 | NaN |
2007-04-26 | 9.59 | 57.70 | 32.80 | 38.60 | NaN |
2007-04-27 | 9.55 | 57.50 | 32.72 | 38.40 | NaN |
2-2.非時間序列¶
In [ ]:
Copied!
company_basic_info = data.get('company_basic_info')
company_basic_info.head(3)
company_basic_info = data.get('company_basic_info')
company_basic_info.head(3)
Out[ ]:
stock_id | 公司名稱 | 產業類別 | 外國企業註冊地國 | 住址 | 營利事業統一編號 | 董事長 | 總經理 | 發言人 | 發言人職稱 | ... | 公司網址 | 投資人關係聯絡人 | 投資人關係聯絡人職稱 | 投資人關係聯絡電話 | 投資人關係聯絡電子郵件 | 公司網站內利害關係人專區網址 | 市場別 | 上櫃日期 | 興櫃日期 | 公司簡稱 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1101 | 台灣水泥股份有限公司 | 水泥工業 | - | 台北市中山北路2段113號 | 11913502 | 張安平 | 李鐘培 | 黃健強 | 資深副總經理 | ... | http://www.taiwancement.com | 張佳琪 | 主任 | 02-25317099分機20358 | [email protected] | http://www.taiwancement.com/tw/csr/csr5-1.html | sii | nan | nan | 台泥 |
1 | 1102 | 亞洲水泥股份有限公司 | 水泥工業 | - | 台北市大安區敦化南路2段207號30、31樓 | 03244509 | 徐旭東 | 李坤炎 | 周維崑 | 副總經理 | ... | www.acc.com.tw | 陳韋仲 | 投資人關係副經理 | 02 27338000 ext.8336 | [email protected] | http://www.acc.com.tw/tw/stakeholder/stakehold... | sii | nan | nan | 亞泥 |
2 | 1103 | 嘉新水泥股份有限公司 | 水泥工業 | - | 台北市中山北路2段96號 | 11892801 | 張剛綸 | 祁士鉅 | 王立心 | 執行副總經理 | ... | www.chcgroup.com.tw | 王立心 | 執行副總經理 | (02)2551-5211#243 | [email protected] | www.chcgroup.com.tw/index.php?route=system/sys... | sii | nan | nan | 嘉泥 |
3 rows × 42 columns
3.撰寫策略¶
3-1.策略條件¶
價格 > 20 日均線入場, 價格 < 60 日均線出場,最多持有10檔,超過 10 個進場訊號,則以股價淨值比小的股票優先選入。
3-2.hold_until function¶
這大概是所有策略撰寫中,最重要的語法糖,上述語法中 entries 為進場訊號,而 exits 是出場訊號。所以 entries.hold_until(exits) ,就是進場訊號為 True 時,買入並持有該檔股票,直到出場訊號為 True 則賣出。
此函式有很多細部設定,可以讓你最多選擇 N 檔股票做輪動。另外,當超過 N 檔進場訊號發生,也可以按照客制化的排序,選擇優先選入的股票。最後,可以持外設定停損停利,來增加出場的時機點。以下是 hold_until 的參數說明:
- exit (pd.Dataframe): 出場訊號。
- nstocks_limit (int): 持有檔數上限。
- stop_loss (float): 停損基準。範例:0.1,代表成本價下跌 10% 時出場。
- take_profit (float): 停利基準。範例:0.1,代表成本價上漲 10% 時出場。
- trade_at (str): 停損停利參考價。可選 close 或 open。
- rank (pd.Dataframe): 當天進場訊號超過 nstocks_limit 時,以 rank 數字越大的股票優先進場。
In [23]:
Copied!
from finlab import data
from finlab.backtest import sim
close = data.get('price:收盤價')
pb = data.get('price_earning_ratio:股價淨值比')
sma20 = close.average(20)
sma60 = close.average(60)
entries = close > sma20
exits = close < sma60
position = entries.hold_until(exits, nstocks_limit=10, rank=-pb, take_profit=0.4)
backtest_report = sim(position, upload=False)
from finlab import data
from finlab.backtest import sim
close = data.get('price:收盤價')
pb = data.get('price_earning_ratio:股價淨值比')
sma20 = close.average(20)
sma60 = close.average(60)
entries = close > sma20
exits = close < sma60
position = entries.hold_until(exits, nstocks_limit=10, rank=-pb, take_profit=0.4)
backtest_report = sim(position, upload=False)
4.回測¶
4-1.顯示策略回測結果¶
In [24]:
Copied!
from finlab.backtest import sim
backtest_report = sim(position,upload=False)
# 新增對標的指數,你的策略能贏大盤嗎?
backtest_report.benchmark = data.get('benchmark_return:發行量加權股價報酬指數').squeeze()
backtest_report.display()
from finlab.backtest import sim
backtest_report = sim(position,upload=False)
# 新增對標的指數,你的策略能贏大盤嗎?
backtest_report.benchmark = data.get('benchmark_return:發行量加權股價報酬指數').squeeze()
backtest_report.display()
1104 0.1 1402 0.1 1451 0.1 2923 0.1 2929 0.1 3573 0.1 4543 0.1 5438 0.1 6605 0.1 8411 0.1 Name: 2022-07-06 00:00:00, dtype: float64
Timestamp('2022-07-06 00:00:00')
4-2.取得報酬率序列¶
In [ ]:
Copied!
backtest_report.creturn
backtest_report.creturn
Out[ ]:
date 2007-05-07 1.000000 2007-05-08 0.998575 2007-05-09 0.996668 2007-05-10 1.006912 2007-05-11 1.014379 ... 2022-06-30 5.791745 2022-07-01 5.536816 2022-07-04 5.498622 2022-07-05 5.542169 2022-07-06 5.395311 Length: 3742, dtype: float64
4-3.取得近期持有部位與預期換股資訊¶
In [ ]:
Copied!
backtest_report.position_info()
backtest_report.position_info()
Out[ ]:
{'1104 環泥': {'entry_date': '2022-07-06', 'entry_price': 0.0, 'exit_date': '', 'next_weight': 0.1, 'return': 0.0, 'status': '買進', 'weight': 0.0}, '1402 遠東新': {'entry_date': '2022-07-06', 'entry_price': 0.0, 'exit_date': '', 'next_weight': 0.1, 'return': 0.0, 'status': '買進', 'weight': 0.0}, '1451 年興': {'entry_date': '2022-04-22', 'entry_price': 21.0, 'exit_date': '', 'next_weight': 0.1, 'return': 0.07142857142857095, 'status': '買進', 'weight': 0.10060208160861868}, '2201 裕隆': {'entry_date': '2022-06-20', 'entry_price': 49.75, 'exit_date': '2022-07-06', 'next_weight': 0.0, 'return': -0.14572864321608014, 'status': '賣出', 'weight': 0.09758099807682835}, '2474 可成': {'entry_date': '2022-05-09', 'entry_price': 155.5, 'exit_date': '2022-07-06', 'next_weight': 0.0, 'return': 0.012861736334404794, 'status': '賣出', 'weight': 0.09850363328058616}, '2514 龍邦': {'entry_date': '2022-07-01', 'entry_price': 17.3, 'exit_date': '2022-07-06', 'next_weight': 0.0, 'return': -0.023121387283237205, 'status': '賣出', 'weight': 0.09944445830150599}, '2923 鼎固-KY': {'entry_date': '2022-07-01', 'entry_price': 27.5, 'exit_date': '', 'next_weight': 0.1, 'return': -0.040000000000000036, 'status': '買進', 'weight': 0.09772624635925513}, '2929 淘帝-KY': {'entry_date': '2022-05-19', 'entry_price': 12.9, 'exit_date': '', 'next_weight': 0.1, 'return': 0.07364341085271287, 'status': '買進', 'weight': 0.10945090553098917}, '3252 海灣': {'entry_date': '2022-06-20', 'entry_price': 23.1, 'exit_date': '2022-07-06', 'next_weight': 0.0, 'return': -0.17532467532467533, 'status': '賣出', 'weight': 0.09541832353529327}, '3573 穎台 ': {'entry_date': '2016-05-09', 'entry_price': 26.3, 'exit_date': '', 'next_weight': 0.1, 'return': 0.001901140684410274, 'status': '買進', 'weight': 0.1019434426967336}, '4543 萬在': {'entry_date': '2022-07-06', 'entry_price': 0.0, 'exit_date': '', 'next_weight': 0.1, 'return': 0.0, 'status': '買進', 'weight': 0.0}, '5438 東友': {'entry_date': '2022-06-20', 'entry_price': 14.7, 'exit_date': '', 'next_weight': 0.1, 'return': -0.00340136054421758, 'status': '買進', 'weight': 0.10125230071234896}, '6605 帝寶': {'entry_date': '2022-05-12', 'entry_price': 58.8, 'exit_date': '', 'next_weight': 0.1, 'return': 0.16496598639455673, 'status': '買進', 'weight': 0.09807760989784062}, '8411 福貞-KY': {'entry_date': '2022-07-06', 'entry_price': 0.0, 'exit_date': '', 'next_weight': 0.1, 'return': 0.0, 'status': '買進', 'weight': 0.0}, 'next_trading_date': '2022-07-06', 'trade_at': 'close', 'update_date': '2022-07-06'}
In [ ]:
Copied!
trade_record = backtest_report.get_trades()
trade_record.head(5)
trade_record = backtest_report.get_trades()
trade_record.head(5)
Out[ ]:
stock_id | entry_date | exit_date | entry_sig_date | exit_sig_date | position | period | entry_index | exit_index | return | entry_price | exit_price | mae | gmfe | bmfe | mdd | pdays | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
trade_index | |||||||||||||||||
0 | 3297 杭特 | 2007-05-08 | 2007-05-28 | 2007-05-07 | 2007-05-25 | 0.100000 | 14.0 | 10.0 | 24.0 | 0.535874 | 44.60 | 68.50 | 0.000000 | 0.535874 | 0.000000 | -0.044402 | 14.0 |
1 | 3376 新日興 | 2007-05-08 | 2007-06-05 | 2007-05-07 | 2007-06-04 | 0.100000 | 20.0 | 10.0 | 30.0 | -0.022727 | 220.00 | 215.00 | -0.027273 | 0.006818 | 0.006818 | -0.033860 | 2.0 |
2 | 4102 永日 | 2007-05-28 | 2007-06-05 | 2007-05-25 | 2007-06-04 | 0.109485 | 6.0 | 24.0 | 30.0 | -0.046053 | 15.20 | 14.50 | -0.046053 | 0.006579 | 0.006579 | -0.052288 | 2.0 |
3 | 9962 有益 | 2007-05-08 | 2007-06-05 | 2007-05-07 | 2007-06-04 | 0.100000 | 20.0 | 10.0 | 30.0 | -0.065507 | 48.85 | 45.65 | -0.077789 | 0.000000 | 0.000000 | -0.077789 | 0.0 |
4 | 3311 閎暉 | 2007-05-08 | 2007-06-13 | 2007-05-07 | 2007-06-12 | 0.100000 | 26.0 | 10.0 | 36.0 | 0.022508 | 155.50 | 159.00 | -0.025723 | 0.048232 | 0.025723 | -0.055215 | 16.0 |
4-5.交易紀錄應用-繪製報酬率分佈¶
In [27]:
Copied!
import plotly.express as px
trade_record['profit_loss'] = trade_record['return'].apply(lambda s: 'profit' if s > 0 else 'loss')
win_ratio = round(sum(trade_record['profit_loss'] == 'profit') / len(trade_record['profit_loss']) * 100, 2)
return_mean = round(trade_record['return'].mean(), 2)
fig = px.histogram(trade_record, x="return", color="profit_loss", title=f'profit_loss_hist - win_ratio: {win_ratio} %')
fig.add_vline(x=return_mean, line_width=3, line_dash="dash", line_color="green",
annotation_position="top left",
annotation_text=f'avg_return:{return_mean}', row=1, col=1)
import plotly.express as px
trade_record['profit_loss'] = trade_record['return'].apply(lambda s: 'profit' if s > 0 else 'loss')
win_ratio = round(sum(trade_record['profit_loss'] == 'profit') / len(trade_record['profit_loss']) * 100, 2)
return_mean = round(trade_record['return'].mean(), 2)
fig = px.histogram(trade_record, x="return", color="profit_loss", title=f'profit_loss_hist - win_ratio: {win_ratio} %')
fig.add_vline(x=return_mean, line_width=3, line_dash="dash", line_color="green",
annotation_position="top left",
annotation_text=f'avg_return:{return_mean}', row=1, col=1)
4-6.策略流動性風險檢測¶
In [ ]:
Copied!
from finlab.analysis.liquidityAnalysis import LiquidityAnalysis
# 交易紀錄進出場成交張數大於1000張的比例, 成交金額大於1000000元的比例,檢測資金部位胃納量
backtest_report.run_analysis(LiquidityAnalysis(required_volume=100000, required_turnover=1000000))
from finlab.analysis.liquidityAnalysis import LiquidityAnalysis
# 交易紀錄進出場成交張數大於1000張的比例, 成交金額大於1000000元的比例,檢測資金部位胃納量
backtest_report.run_analysis(LiquidityAnalysis(required_volume=100000, required_turnover=1000000))
4-7.取得策略統計指標¶
取得數據如夏普率、索提諾比率、最大回檔、近期報酬率統計...
策略指標解析:
夏普值(sharpe_value)小於零,代表賠錢的意思,以一個簡易的選股策略來說,一般人波段實單操作,可能是0.7,以一個ETF來說,可能 0.9 就已經很不錯了,而以網路上付費選股策略 1.3 可能會是比較理想的數值,當然也有真的很厲害的選股策略,Sharpe可以到 2 或 3。
獲利峰態(kurt)是跟一般的高斯分佈做比較,比高斯分佈更尖 kurt 越高,通常代表策略績效越穩定,而一般股票而言大盤的kurt大約在 1~2 左右,大於 2 基本上算是勉強堪用,5以上就算還不錯。偏態的話也是跟高斯分佈比較,越偏向右邊越小,通常 -0.5 以下算是勉強堪用,-1左右算是還不錯
In [1]:
Copied!
backtest_report.get_stats()
backtest_report.get_stats()
5.上傳 strategy¶
sim 中的 參數 upload 預設為 True,執行完回測後會在平台網頁模顯示選股清單。
複製下列cell的程式碼,貼到 finlab 量化平台,才可設定每日自動更新策略清單。
In [ ]:
Copied!
from finlab import data
from finlab.backtest import sim
close = data.get('price:收盤價')
pb = data.get('price_earning_ratio:股價淨值比')
sma20 = close.average(20)
sma60 = close.average(60)
entries = close > sma20
exits = close < sma60
position = entries.hold_until(exits, nstocks_limit=10, rank=-pb, take_profit=0.4)
sim(position, name='策略教學範例:pb_ma')
from finlab import data
from finlab.backtest import sim
close = data.get('price:收盤價')
pb = data.get('price_earning_ratio:股價淨值比')
sma20 = close.average(20)
sma60 = close.average(60)
entries = close > sma20
exits = close < sma60
position = entries.hold_until(exits, nstocks_limit=10, rank=-pb, take_profit=0.4)
sim(position, name='策略教學範例:pb_ma')