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

## # **解決中文問題**
在預設的情況下,如果圖表中有中文字會出現錯誤訊息,這一節來講怎麼解決
### # **錯誤訊息**
```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
```

## # **針對兩者的收盤價並畫出折線圖**
從上面可以看到,圖會變成非常的凌亂,大致上我們只會想要注意每天的收盤價,接下來會利用收盤價來進行繪製著線圖
### # **程式碼**
```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
```

## # **將收盤價以每月為單位取平均值來代表並畫出折線圖**
有時候可能以每天的數據會太細了,所以利用 `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
```

## # **標示聯發科股票價的 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
```

### # **繪製聯發科的收盤折線圖並將 % 數價格標上去**
#### # **程式碼**
```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
```

## # **算出聯發科的日收益率及應用**
日收益率公式:
$$
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
```

### # **方法二**
DataFrame 是真的很強大,也可以使用 DataFrame 內建的 `pct_change()` 來做
#### # **程式碼**
```python=
stocks[('MTK','日收益率')] = stocks[('MTK','Close')].pct_change()
print(stocks[('MTK','日收益率')])
```
#### # **結果**
```
$ python3 stock.py
```

### # **利用日收益率畫出直方圖**
#### # **程式碼**
```python=
stocks[('MTK','日收益率')].plot(kind='hist', bins=50, figsize=(10, 10))
plt.show()
```
* **直方圖是用來看數值分散程度的,這樣就可以看大概的日收益率會落在哪裡,`bins` 這個參數可以增加分散數據,數據會更接近於實際,但設定太高的數字也不太好,建議就設定大概想要的分散程度,例如該範例是設定為 40**
#### # **結果**
```
$ python3 stock.py
```

* **由於聯發科算是穩定的大型股,所以大概上日收益率都在 0 左右**
### # **日收益率的敘述性統計**
利用 `describe()` 可以看到收益率的大概敘述性統計,例如: mean、min、std、max
### # **程式碼**
```python=
print(stocks[('MTK','日收益率')].describe())
```
### # **結果**
```
$ python3 stock.py
```

當然也可以取得特定的資訊,例如我要取得 std(標準差),可以使用 `stocks[('MTK','日收益率')].std()`
## # **算出聯發科的均線**
這一節我們會使用 `rolling()`, rolling 是個很神的 Function,他可以用來處理的時間序列的數據。例如 rolling(3),就會從被選定的地方往前 3 個數字進行累加,如果使用 `stocks[('MTK','Close')].rolling(3).sum()`,2015-01-01 到 2015-01-06 因為往前推不及三天(含年假跟假日沒開盤),所以會出現 NaN,之後就會往前三天累加,就會像:

### # **計算 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
```

### # **繪製均線圖**
#### # **程式碼**
```python=
stocks['MTK'][['MA7','MA14','MA21']].plot(kind='line', grid=True, figsize=(10, 10))
plt.show()
```
* **這裡就不能使用 `['MTK',('MA7','MA14',MA21)]`的方式,原因是這樣就不會切成多個 list**
#### # **結果**
```
$ python3 stock.py
```

## # **取得特定區間的資料**
DataFrame 有夠多可以方便好取資料的一些方式
### # **取得聯發科 2019 的資料**
#### # **程式碼**
```python=
print(stocks['MTK'].loc['2018'])
```
#### # **結果**
```
$ python3 stock.py
```

### # **取得聯發科 2021年3月 的資料**
#### # **程式碼**
```python=
print(stocks['MTK'].loc['2021-3'])
```
#### # **結果**
```
$ python3 stock.py
```

### # **取得聯發科 2020年3月19號 到 2021年5月19號 的資料**
#### # **程式碼**
```python=
print(stocks['MTK'].loc['2020-03-19':'2021-05-19'])
```
#### # **結果**
```
$ python3 stock.py
```

### # **繪製聯發科的 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
```

### # **繪製聯發科前 100 天的收盤和均線圖**
#### # **程式碼**
```python=
stocks['MTK'][['Close','MA7','MA14','MA21']].iloc[-100:].plot(kind='line', grid=True, figsize=(10, 10))
plt.show()
```
#### # **結果**
```
$ python3 stock.py
```

## # **威廉指數**
### # **介紹**
威廉指數最主要是用來判斷目前市場是超買還是超賣。
定義:
* **指標值在 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
```
