# # Python Note - 股票分析 ###### tags: `Python`,`Stock` [TOC] ## # **介紹工具** * **1. pandas,用來方便操縱數值表格和分析的軟體庫** * **2. numpy,提供更強大的維度陣列、矩陣運算** *Note:在製造資料的時候偶爾會用到或是陣列、矩陣運算* * **3. matplotlib,將數據可視化非常好用的工具** * **4. openpyxl,方便於 python 在 Excel 進行操作** ## # **安裝** ``` $ python3 -m pip install pandes numpy matplotlib openpyxl pandas_datareader ``` ## # **抓數據** 這裡從 Yahoo 抓台積電、聯發科從 2015 年之後到現在的資料 ### # **程式碼** ```python= import pandas as pd import numpy as np import datetime as dt from datetime import timedelta import matplotlib.pyplot as plt import pandas_datareader.data as web # 2330 台積電, 2454 聯發科 _stocks = ["2330.TW", "2454.TW"] start = dt.datetime(2015,1,1) end = dt.datetime.now() _data = {} for x in _stocks: _data[x] = web.DataReader(x, 'yahoo', start, end) ``` * **這裡用 Pandas 提供的 DataReader,將無須自己利用 API 來接,DataReader 已經接好了。** * **從 Yahoo 抓僅能使用代號** ### # **結果** 另用 print 來簡單來一下,我們抓下來的數據如何: ```python= $ print(stocks) ``` ![](https://i.imgur.com/MfJHoKH.png) ## # **解決中文問題** 在預設的情況下,如果圖表中有中文字會出現錯誤訊息,這一節來講怎麼解決 ### # **錯誤訊息** ```python= RuntimeWarning: Glyph 26376 missing from current font. font.set_text(s, 0.0, flags=flags) ``` ### # **下載字型** 這裡我是直接從 Windows 10 上的 C:\Windows\Fonts 拿 msjhbd.ttc ### # **放置 python 程式的目錄中** ``` $ mkdir -p fonts $ mv ~/msjhbd.ttc fonts/ ``` ### # **增加程式碼** 這邊我是直接新增在 `import matplotlib.pyplot as plt` 下面 ```python= import matplotlib as mpl import matplotlib.pyplot as plt import matplotlib.font_manager as font_manager import os path = os.path.join("fonts/msjhbd.ttc") prop = font_manager.FontProperties(fname=path) plt.rcParams['font.family'] = prop.get_name() plt.rcParams['axes.unicode_minus'] = False ``` * **利用設定字型路徑的方式改掉字型,因為我直接改 matplotlibrc 都沒效果** * **`plt.rcParams['axes.unicode_minus']` 是解決 `-` 可能不正常輸出的問題** ### # **刪掉 cache** ``` $ rm -rf ~/.cache/matplotlib/ ``` ## # **將兩者的數據合併並畫出折線圖** ### # **程式碼** ```python= stocks = pd.concat(list(_data[x] for x in _stocks), axis=1, keys=["TSMC","MTK"]) stocks.plot(kind="line", grid=True, figsize=(10,10)) plt.show() ``` * **這裡使用 `pd.concat` 將兩個 DataReader (其實是得到的資料會變成 DataFrame) 組起來變成一個 DataFrame** * **這裡有兩個重點是,第一個是 `axis=1`,如果是 1 則使用 column 來做連接,如果是 0 則使用 index(rows),預設則為 0** * **第二個為 `keys` 可以將原本使用股票代號變成利用名字,這樣在使用上可以更方便** * **使用 plot 來繪製折線圖,其中 `kind` 是選擇用什麼方式來繪製,`grid` 是讓背景有格狀(axis grid lines)的感覺,`figsize` 是圖片的 size** ### # **結果** ``` $ python3 stock.py ``` ![](https://i.imgur.com/iO1vrhx.png) ## # **針對兩者的收盤價並畫出折線圖** 從上面可以看到,圖會變成非常的凌亂,大致上我們只會想要注意每天的收盤價,接下來會利用收盤價來進行繪製著線圖 ### # **程式碼** ```python= _list = [stocks[('TSMC','Close')], stocks[('MTK','Close')]] _df = pd.concat(_list, axis=1, keys=["TSMC", "MTK"]) _df.plot(kind="line",grid=True, figsize=(10, 10)) plt.show() ``` * **這裡我們將兩個 Close 的結果放進 list 裡面並使用 `pd.concat` 來將兩個和在一起在進行繪製** * **變數前面有加上 `_` 的,通常我只把它拿來用暫時放資料** ### # **結果** ``` $ python3 stock.py ``` ![](https://i.imgur.com/9kjWhBZ.png) ## # **將收盤價以每月為單位取平均值來代表並畫出折線圖** 有時候可能以每天的數據會太細了,所以利用 `resample` 和 `mean` 來取得每周或每月的平均值來當作新的數據並畫出折線圖 *Note:每周為 resample('W')、每月為 resample('M')* *Note:`resample` 也有更多的時間操作,可以參考: [pandas.DataFrame.resample](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.resample.html)* ### # **程式碼** ```python= _list = [stocks[(x,'Close')].resample('M').mean() for x in ['TSMC','MTK']] _df = pd.concat(_list, axis=1, keys=["TSMC", "MTK"]) _df.plot(kind="line",grid=True, figsize=(10, 10)) plt.show() ``` * **大致上跟上一個的程式碼類同,但可以看到 _list 利用 for 的配合來建立 list,這樣就可以少打一次 `resample('M').mean()`,不過這樣的方式在上一個 lab 來說並沒有節省程式碼,所以還是看情況使用** ### # **結果** ``` $ python3 stock.py ``` ![](https://i.imgur.com/swoi9zS.png) ## # **標示聯發科股票價的 10% 到 90% 價格區間** 有時候我們會想算出大概聯發科的幾%到幾%的價格區間並在圖上進行標示,我們可以利用 `quantile()` 來幫忙做到 ### # **算出 10% 及 90% 的價格** #### # **程式碼** ```python= _MTK_upper = stocks[('MTK','Close')].quantile(0.9) _MTK_lower = stocks[('MTK','Close')].quantile(0.1) print("Upper: %s, Lower: %s" % (_MTK_upper, _MTK_lower)) ``` #### # **結果** ``` $ python3 stock.py ``` ![](https://i.imgur.com/PxOVQbQ.png) ### # **繪製聯發科的收盤折線圖並將 % 數價格標上去** #### # **程式碼** ```python= stocks[('MTK','Close')].plot(kind="line", grid=True, figsize=(10, 10)) plt.hlines([_MTK_upper, _MTK_lower], stocks['MTK'].index[0], stocks['MTK'].index[-1]) plt.show() ``` #### # **結果** ``` $ python3 stock.py ``` ![](https://i.imgur.com/gbdBHCk.png) ## # **算出聯發科的日收益率及應用** 日收益率公式: $$ r_t = \frac{P_t - P_{t-1}}{P_{t-1}} $$ 簡單來說就是 *(今天的價格 - 昨天的價格)/昨天的價格* 就是日收益率了 ### # **方法一** #### # **程式碼** ```python= stocks[('MTK','日收益率')] = ((stocks[('MTK','Close')] - stocks[('MTK','Close')].shift(1)) / stocks[('MTK','Close')].shift(1)) print(stocks[('MTK','日收益率')]) ``` #### # **結果** ``` $ python3 stock.py ``` ![](https://i.imgur.com/s62DXRI.png) ### # **方法二** DataFrame 是真的很強大,也可以使用 DataFrame 內建的 `pct_change()` 來做 #### # **程式碼** ```python= stocks[('MTK','日收益率')] = stocks[('MTK','Close')].pct_change() print(stocks[('MTK','日收益率')]) ``` #### # **結果** ``` $ python3 stock.py ``` ![](https://i.imgur.com/kgmzjTk.png) ### # **利用日收益率畫出直方圖** #### # **程式碼** ```python= stocks[('MTK','日收益率')].plot(kind='hist', bins=50, figsize=(10, 10)) plt.show() ``` * **直方圖是用來看數值分散程度的,這樣就可以看大概的日收益率會落在哪裡,`bins` 這個參數可以增加分散數據,數據會更接近於實際,但設定太高的數字也不太好,建議就設定大概想要的分散程度,例如該範例是設定為 40** #### # **結果** ``` $ python3 stock.py ``` ![](https://i.imgur.com/vt1wdbK.png) * **由於聯發科算是穩定的大型股,所以大概上日收益率都在 0 左右** ### # **日收益率的敘述性統計** 利用 `describe()` 可以看到收益率的大概敘述性統計,例如: mean、min、std、max ### # **程式碼** ```python= print(stocks[('MTK','日收益率')].describe()) ``` ### # **結果** ``` $ python3 stock.py ``` ![](https://i.imgur.com/vBlObrO.png) 當然也可以取得特定的資訊,例如我要取得 std(標準差),可以使用 `stocks[('MTK','日收益率')].std()` ## # **算出聯發科的均線** 這一節我們會使用 `rolling()`, rolling 是個很神的 Function,他可以用來處理的時間序列的數據。例如 rolling(3),就會從被選定的地方往前 3 個數字進行累加,如果使用 `stocks[('MTK','Close')].rolling(3).sum()`,2015-01-01 到 2015-01-06 因為往前推不及三天(含年假跟假日沒開盤),所以會出現 NaN,之後就會往前三天累加,就會像: ![](https://i.imgur.com/Dgne5GM.png) ### # **計算 7、14、21 日均線值** #### # **程式碼** ```python= for x in [7, 14, 21]: stocks[('MTK',f'MA{x}')] = stocks[('MTK','Close')].rolling(x).mean() print(stocks[('MTK')]) ``` #### # **結果** ``` $ python3 stock.py ``` ![](https://i.imgur.com/LUIkkFA.png) ### # **繪製均線圖** #### # **程式碼** ```python= stocks['MTK'][['MA7','MA14','MA21']].plot(kind='line', grid=True, figsize=(10, 10)) plt.show() ``` * **這裡就不能使用 `['MTK',('MA7','MA14',MA21)]`的方式,原因是這樣就不會切成多個 list** #### # **結果** ``` $ python3 stock.py ``` ![](https://i.imgur.com/ez2V9jF.png) ## # **取得特定區間的資料** DataFrame 有夠多可以方便好取資料的一些方式 ### # **取得聯發科 2019 的資料** #### # **程式碼** ```python= print(stocks['MTK'].loc['2018']) ``` #### # **結果** ``` $ python3 stock.py ``` ![](https://i.imgur.com/0Lhzd0n.png) ### # **取得聯發科 2021年3月 的資料** #### # **程式碼** ```python= print(stocks['MTK'].loc['2021-3']) ``` #### # **結果** ``` $ python3 stock.py ``` ![](https://i.imgur.com/bYLDkVF.png) ### # **取得聯發科 2020年3月19號 到 2021年5月19號 的資料** #### # **程式碼** ```python= print(stocks['MTK'].loc['2020-03-19':'2021-05-19']) ``` #### # **結果** ``` $ python3 stock.py ``` ![](https://i.imgur.com/0oDYlhW.png) ### # **繪製聯發科的 2020年3月19號 到 2021年5月19號 的收盤和均線圖** #### # **程式碼** ```python= stocks['MTK'].loc['2020-03-19':'2021-05-19',['Close','MA7','MA14','MA21']].plot(kind='line', grid=True, figsize=(10, 10)) plt.show() ``` #### # **結果** ``` $ python3 stock.py ``` ![](https://i.imgur.com/ZpZRkuD.png) ### # **繪製聯發科前 100 天的收盤和均線圖** #### # **程式碼** ```python= stocks['MTK'][['Close','MA7','MA14','MA21']].iloc[-100:].plot(kind='line', grid=True, figsize=(10, 10)) plt.show() ``` #### # **結果** ``` $ python3 stock.py ``` ![](https://i.imgur.com/dj7b7Bc.png) ## # **威廉指數** ### # **介紹** 威廉指數最主要是用來判斷目前市場是超買還是超賣。 定義: * **指標值在 0 ~ -20 範圍表示市場處於超買中** * **指標值在 -80 ~ -100 範圍表示市場處於超賣中** *當然威廉僅只是用來參考,並不代表處於超買就要賣,處於超賣就要買* 公式如下: $$ W\%R = \frac{H_n - C_n}{H_n - L_n} * 100\% * -1 $$ 簡單來說就是 `(最近 N 日內最高價 - 第 N 日收盤價 / 最近 N 日內最高價 - 第 N 日最低價) * 100% * -1` ### # **算出聯發科 2021年11月 到 2021年12月 的 威廉指數 並繪製折線圖** #### # **程式碼** ```python= stocks['MTK','William'] = ((stocks[('MTK','High')].rolling(14).max() - stocks[('MTK','Close')]) \ / (stocks[('MTK','High')].rolling(14).max() - stocks[('MTK','Low')].rolling(14).min()) * -1 * 100) stocks['MTK'].loc['2021-11':'2021-12', 'William'].plot(kind='line', grid=True, figsize=(10, 10)) plt.show() ``` #### # **結果** ``` $ python3 stock.py ``` ![](https://i.imgur.com/NW7CJOI.png)