文件
開啟 Studio

開始使用

10 分鐘寫出第一個策略

本頁內容1

這一頁用一個完整的策略,帶你走過 FinLab 策略開發的精華功能:取得資料 → 寫進出場條件 → 回測與分析 → 上傳。照順序執行,後面的程式會用到前面算好的變數。還沒安裝 FinLab 的話,先看快速上手。

  1. 1 取得資料
    data.get('price:收盤價')

    日期 × 股票的一張表

  2. 2 寫條件
    close > close.average(60)

    True=那天要持有

  3. 3 回測
    sim(position, resample='M')

    報酬、回撤、每筆交易

  4. 4 上線
    PortfolioSyncManager

    每天更新、自動下單

這一頁要寫的策略是:價格 > 20 日均線進場,價格 < 60 日均線出場,最多持有 10 檔;超過 10 個進場訊號時,以股價淨值比小的股票優先選入。

匯入套件

from finlab import data
from finlab.backtest import sim

取得資料

data.get() 可以取得各種時間序列資料,回傳的 DataFrame 以日期為索引、股票代號為欄位:

from finlab import data

pb = data.get('price_earning_ratio:股價淨值比')
close = data.get('price:收盤價')
close.iloc[:5, :5]

部分資料(如公司基本資訊)不是時間序列,而是每間公司一列的靜態資料表:

company_basic_info = data.get('company_basic_info')
company_basic_info.head(3)

寫進出場條件

entries 是進場訊號,exits 是出場訊號。entries.hold_until(exits) 的意思是:進場訊號為 True 時買入並持有該檔股票,直到出場訊號為 True 才賣出。這大概是所有策略撰寫中最重要的語法糖。

strategy.py
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)

close > sma20 這種比較會逐格比較兩張表,得到同樣形狀的 True/False 表。下圖以收盤價和 60 日均線為例:

close 收盤價
233023172454
10/279851120
10/2810150118
10/2910449121
10/3010352117
close.average(60) 60 日均線
233023172454
10/2710052115
10/2810051116
10/2910151117
10/3010151118
position 要持有的股票
233023172454
10/27FalseFalseTrue
10/28TrueFalseTrue
10/29TrueFalseTrue
10/30TrueTrueFalse
每一格是一檔股票在一天的值。比較運算一次算完整張表,日期與股票會自動對齊;True 就是那天要持有的股票。數字為示意。

hold_until 可以讓你最多選 N 檔股票做輪動;超過 N 檔進場訊號時,按照自訂的排序選擇優先選入的股票;還能另外設定停損停利,增加出場的時機點。上面的程式設了最多 10 檔、用 -pb 排序(rank 數字越大越優先,加負號就是股價淨值比越小越優先),並在上漲 40% 時停利。

參數型別說明
exitpd.DataFrame出場訊號
nstocks_limitint持有檔數上限
stop_lossfloat停損基準。範例:0.1,代表成本價下跌 10% 時出場
take_profitfloat停利基準。範例:0.1,代表成本價上漲 10% 時出場
trade_atstr停損停利參考價。可選 close 或 open
rankpd.DataFrame當天進場訊號超過 nstocks_limit 時,以 rank 數字越大的股票優先進場

完整參數請看 FinlabDataFrame API。

回測並顯示報告

from finlab.backtest import sim

backtest_report = sim(position, upload=False)

# 台股 Market 已提供加權報酬指數作為預設比較基準
backtest_report.display()

看報酬、持股與交易紀錄

報酬率序列:

backtest_report.creturn

近期持有部位與預期換股資訊:

backtest_report.position_info()

get_trades() 回傳每一筆交易的進出場資訊:

trade_record = backtest_report.get_trades()
trade_record.head(5)
欄位說明
entry_sig_date進場訊號產生日
exit_sig_date出場訊號產生日
entry_date進場日
exit_date出場日
period持有天數
position持有佔比
return報酬率
mdd持有期間最大回撤
mae持有期間最大不利幅度
gmfe持有期間最大有利幅度
bmfemae 發生前的最大有利幅度

用交易紀錄畫出報酬率分佈,可以看出策略的獲利與虧損分佈狀況:

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

檢查流動性風險

LiquidityAnalysis 檢查交易紀錄中,進出場成交張數大於指定門檻的比例,以及成交金額大於指定門檻的比例,用來檢測資金部位的胃納量:

from finlab.analysis.liquidityAnalysis import LiquidityAnalysis

backtest_report.run_analysis(
    LiquidityAnalysis(required_volume=100000, required_turnover=1000000)
)

看統計指標

get_stats() 取得夏普率、索提諾比率、最大回檔、近期報酬率統計等數據:

backtest_report.get_stats()
指標怎麼看
夏普值(Sharpe)小於零代表賠錢。以簡易的選股策略來說,一般人波段實單操作可能是 0.7;以 ETF 來說,0.9 就已經很不錯;網路上的付費選股策略,1.3 可能是比較理想的數值。也有真的很厲害的選股策略,Sharpe 可以到 2 或 3。
獲利峰態(kurt)跟一般的高斯分佈比較,比高斯分佈更尖則 kurt 越高,通常代表策略績效越穩定。一般股票的大盤 kurt 大約在 1~2,大於 2 算是勉強堪用,5 以上就算還不錯。
偏態也是跟高斯分佈比較,越偏向右邊越小,通常 -0.5 以下算是勉強堪用,-1 左右算是還不錯。

上傳策略,每天自動更新

sim 的參數 upload 預設是 None:只有在雲端排程、或在 Studio 網頁手動執行筆記本時才會上傳;其他地方(自己的電腦、Colab)要在平台上顯示選股清單,請明確設定 upload=True。

複製下列程式碼,貼到 FinLab 量化平台,即可設定每日自動更新策略清單:

strategy.py
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')

下一步

↑↓ 選擇 Enter 開啟 Esc 關閉