---
# System prepended metadata

title: '# Python Note - 股票分析'
tags: [Python, Stock]

---

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