Finrodchen
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note No publishing access yet

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.

      Your account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

      Your team account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

      Explore these features while you wait
      Complete general settings
      Bookmark and like published notes
      Write a few more notes
      Complete general settings
      Write a few more notes
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights New
    • Engagement control
    • Make a copy
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Make a copy Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note No publishing access yet

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.

    Your account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

    Your team account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

    Explore these features while you wait
    Complete general settings
    Bookmark and like published notes
    Write a few more notes
    Complete general settings
    Write a few more notes
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    22
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    # 股票交易策略回測─Python筆記 ###### tags: `Python` `Coding` ## 前情提要 之前我們建立了自動抓取股票歷史交易數據的程式,藉由這個程式我們能自由擷取想研究的股票的歷史資料。而這些資料除了可以用來繪製線圖以外,最常被使用的就是「交易策略回測」了。利用過往的成交資料,套入自訂的交易策略並模擬交易,由程式中設定的條件交由電腦幫我們下單,藉這樣的回測可以驗證你的交易策略是否有效。 首先我們先複習一下抓取歷史股價資料的程式,原先我使用的是`twstock`模組,但考慮到抓取美股資料的需求,因此將資料來源換成`yfinance`也就是從Yahoo財經抓取資料,並存成CSV,供接下來的程式使用。 ```python= import yfinance as yf import pandas as pd from pandas_datareader import data from datetime import datetime yf.pdr_override() #以pandasreader常用的格式覆寫 target_stock = 'TSLA' #股票代號變數 start_date = datetime(2010, 1, 1) end_date = datetime(2020, 6, 30) #設定資料起訖日期 df = data.get_data_yahoo([target_stock], start_date, end_date) #將資料放到Dataframe裡面 filename = f'./data/{target_stock}.csv' #以股票名稱命名檔案,放在data資料夾下面 df.to_csv(filename) #將df轉成CSV保存 ``` 執行這個程式就可以得到特斯拉自2010/01/01~2020/06/30的歷史股價資料了(這邊因為特斯拉是2010/06/29才開始交易,所以最舊一筆資料會是2010/06/29): ``` Open High Low Close Adj Close Volume Date 2010-06-29 19.000000 25.000000 17.540001 23.889999 23.889999 18766300 2010-06-30 25.790001 30.420000 23.299999 23.830000 23.830000 17187100 2010-07-01 25.000000 25.920000 20.270000 21.959999 21.959999 8218800 2010-07-02 23.000000 23.100000 18.709999 19.200001 19.200001 5139800 2010-07-06 20.000000 20.000000 15.830000 16.110001 16.110001 6866900 ... ... ... ... ... ... ... 2020-06-23 998.880005 1012.000000 994.010010 1001.780029 1001.780029 6365300 2020-06-24 994.109985 1000.880005 953.140015 960.849976 960.849976 10959600 2020-06-25 954.270020 985.979980 937.150024 985.979980 985.979980 9254500 2020-06-26 994.780029 995.000000 954.869995 959.739990 959.739990 8854900 2020-06-29 969.010010 1010.000000 948.520020 1009.349976 1009.349976 9026400 [2518 rows x 6 columns] ``` ## 使用模組 ![](https://i.imgur.com/10jgVt3.png) 回測的程式最主要的模組就是`backtesting.py`,這個模組對於新手來說非常簡單易用,而且除了輸出回測交易結果之外,backtesting還會生成一個可互動的線圖網頁,讓用戶能回顧整個交易歷程,相當方便。廢話不多說,開始寫程式吧! ## 引入模組 ```python= from backtesting import Backtest, Strategy #引入回測和交易策略功能 from backtesting.lib import crossover #從lib子模組引入判斷均線交會功能 from backtesting.test import SMA #從test子模組引入繪製均線功能 import pandas as pd #引入pandas讀取股價歷史資料CSV檔 ``` ## 定義交易策略 交易策略有很多個面向,這邊我先示範最基本的均線(Moving Average)判斷,設定5日均線(周線)和20日均線(月線),當周線往上走超過月線時,表示股票進入上漲的趨勢,這時就選擇買進。而當周線又再度與月線交叉時,表示股票開始下跌,則選擇賣出。這樣一來一往,雖然不會買在起漲點,但也不會被殺在谷底,藉此維持一定水準的報酬。 情況|動作 ---|--- 周線向上突破月線,開始上漲|買入股票 周現向下跌破月線,開始下跌|賣出股票 ```python= class SmaCross(Strategy): #交易策略命名為SmaClass,使用backtesting.py的Strategy功能 n1 = 5 #設定第一條均線日數為5日(周線) n2 = 20 #設定第二條均線日數為20日(月線),這邊的日數可自由調整 def init(self): self.sma1 = self.I(SMA, self.data.Close, self.n1) #定義第一條均線為sma1,使用backtesting.py的SMA功能算繪 self.sma2 = self.I(SMA, self.data.Close, self.n2) #定義第二條均線為sma2,使用backtesting.py的SMA功能算繪 def next(self): if crossover(self.sma1, self.sma2): #如果周線衝上月線,表示近期是上漲的,則買入 self.buy() elif crossover(self.sma2, self.sma1): #如果周線再與月線交叉,表示開始下跌了,則賣出 self.sell() ``` ## 導入要回測的股票標的 完成策略的定義之後,就可以導入我們目標的股票,並用pandas將其格式化。 ```python= stock = "TSLA" #設定要測試的股票標的名稱 df = pd.read_csv(f"./data/{stock}.csv", index_col=0) #pandas讀取資料,並將第1欄作為索引欄 df = df.interpolate() #CSV檔案中若有缺漏,會使用內插法自動補值,不一定需要的功能 df.index = pd.to_datetime(df.index) #將索引欄資料轉換成pandas的時間格式,backtesting才有辦法排序 ``` ## 回測功能 回測功能的程式非常簡單,可以想像backtesting模組內建一個回測大腦,我們只要將設定好的策略及股價資訊提供給它,再設定好我們回測程式起始手上的現金與交易的手續費比例,backtesting就能幫我們完成其它複雜的運算了。 ```python= test = Backtest(df, SmaCross, cash=10000, commission=.002) # 指定回測程式為test,在Backtest函數中依序放入(資料來源、策略、現金、手續費) result = test.run() #執行回測程式並存到result中 ``` ## 輸出結果 回測程式已經完成,接下來要輸出結果,在`backtesting.py`模組中提供兩種結果呈現方式,第一種是單純輸出純文字資料,內容包含交易次數、時間、報酬率、最終資產等等;另一種則是以網頁的方式呈現,是可互動的線圖,使用者可以看到每次買進和賣出的時間點以及資產的累積狀況。 ```python= print(result) # 直接print文字結果 test.plot(filename=f"./backtest_result/{stock}.html") #將線圖網頁依照指定檔名保存 ``` ## 程式執行結果 如果我們執行以上程式碼,便可以看到我們以這個均線交叉策略購買特斯拉的股票,自2010/06/29到2020/06/29的回測結果了。 ``` Start 2010-06-29 00:00:00 起始時間 End 2020-06-29 00:00:00 結束時間 Duration 3653 days 00:00:00 經過天數 Exposure [%] 96.5782 投資比率 Equity Final [$] 51692.9 最終資產 Equity Peak [$] 52497 最高資產 Return [%] 416.929 報酬率 Buy & Hold Return [%] 4124.99 買入持有報酬率 Max. Drawdown [%] -80.6771 最大交易回落 Avg. Drawdown [%] -15.2517 平均交易回落 Max. Drawdown Duration 1037 days 00:00:00 最長交易回落期間 Avg. Drawdown Duration 120 days 00:00:00 平均交易回落期間 # Trades 149 交易次數 Win Rate [%] 38.255 勝率 Best Trade [%] 353.968 最好交易報酬率 Worst Trade [%] -17.9826 最差交易報酬率 Avg. Trade [%] 2.81325 平均交易報酬率 Max. Trade Duration 197 days 00:00:00 最長交易間隔 Avg. Trade Duration 24 days 00:00:00 平均交易間隔 Expectancy [%] 11.1337 期望值 SQN 0.708816 系統品質指標 Sharpe Ratio 0.0875347 夏普比率 Sortino Ratio 0.672486 索丁諾比率 Calmar Ratio 0.0348704 卡瑪比率 _strategy SmaCross 使用策略名稱 ``` 而[線圖網頁部分則是這樣](https://i-stock.neocities.org/TSLA.html),可互動的介面非常方便。 ## 運用在台股 這邊以台積電做範例,要修改的地方有下面幾項: 1) 擷取歷史資料的程式中`target_stock`變數要指定為`'2330.TW'` (Line 8) 3) 回測程式中的`stock`變數也要指定為`'2330.TW'` (Line 22) 4) 回測程式中的手續費要改為`.004`,以平均0.04%交易費用計算 (Line 29) 接著執行程式我們就能得到均線交叉策略購買台積電的回測結果以及[線圖](https://i-stock.neocities.org/2330.TW.html)了(是賠錢的嗚嗚嗚)。 ``` Start 2010-01-04 00:00:00 End 2020-06-29 00:00:00 Duration 3829 days 00:00:00 Exposure [%] 97.8062 Equity Final [$] 3128.54 Equity Peak [$] 10518.2 Return [%] -68.7146 Buy & Hold Return [%] 380.74 Max. Drawdown [%] -79.3499 Avg. Drawdown [%] -32.6934 Max. Drawdown Duration 3449 days 00:00:00 Avg. Drawdown Duration 1255 days 00:00:00 # Trades 162 Win Rate [%] 33.3333 Best Trade [%] 22.9392 Worst Trade [%] -10.8921 Avg. Trade [%] -0.629596 Max. Trade Duration 98 days 00:00:00 Avg. Trade Duration 24 days 00:00:00 Expectancy [%] 3.53282 SQN -2.42403 Sharpe Ratio -0.13655 Sortino Ratio -0.302971 Calmar Ratio -0.00793443 _strategy SmaCross ``` 下一篇文章我們就要進階運用`backtesting.py`模組裡面的功能,優化我們的交易策略,看看能不能把台積電轉回賺錢的狀態XDDD。 ## 完整程式碼 ```python= from backtesting import Backtest, Strategy #引入回測和交易策略功能 from backtesting.lib import crossover #引入判斷均線交會功能 from backtesting.test import SMA #引入繪製均線功能 import pandas as pd #引入pandas讀取股價歷史資料CSV檔 class SmaCross(Strategy): #交易策略命名為SmaClass,使用backtesting.py的Strategy功能 n1 = 5 #設定第一條均線日數為5日(周線) n2 = 20 #設定第二條均線日數為20日(月線),這邊的日數可自由調整 def init(self): self.sma1 = self.I(SMA, self.data.Close, self.n1) #定義第一條均線為sma1,使用backtesting.py的SMA功能算繪 self.sma2 = self.I(SMA, self.data.Close, self.n2) #定義第二條均線為sma2,使用backtesting.py的SMA功能算繪 def next(self): if crossover(self.sma1, self.sma2): #如果周線衝上月線,表示近期是上漲的,則買入 self.buy() elif crossover(self.sma2, self.sma1): #如果周線再與月線交叉,表示開始下跌了,則賣出 self.sell() stock = "TSLA" #設定要測試的股票標的名稱 df = pd.read_csv(f"data/{stock}.csv", index_col=0) #pandas讀取資料,並將第1欄作為索引欄 df = df.interpolate() #CSV檔案中若有缺漏,會使用內插法自動補值,不一定需要的功能 df.index = pd.to_datetime(df.index) #將索引欄資料轉換成pandas的時間格式,backtesting才有辦法排序 test = Backtest(df, SmaCross, cash=10000, commission=.002) # 指定回測程式為test,在Backtest函數中依序放入(資料來源、策略、現金、手續費) result = test.run() #執行回測程式並存到result中 print(result) # 直接print文字結果 test.plot(filename=f"./backtest_result/{stock}.html") #將線圖網頁依照指定檔名保存 ```

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password
    or
    Sign in via Facebook Sign in via X(Twitter) Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    By signing in, you agree to our terms of service.

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully