<style>
html, body, .ui-content {
background-color: #333;
color: #ddd;
}
body > .ui-infobar {
display: none;
}
.ui-view-area > .ui-infobar {
display: block;
}
.markdown-body h1,
.markdown-body h2,
.markdown-body h3,
.markdown-body h4,
.markdown-body h5,
.markdown-body h6 {
color: #ddd;
}
.markdown-body h1,
.markdown-body h2 {
border-bottom-color: #ffffff69;
}
.markdown-body h1 .octicon-link,
.markdown-body h2 .octicon-link,
.markdown-body h3 .octicon-link,
.markdown-body h4 .octicon-link,
.markdown-body h5 .octicon-link,
.markdown-body h6 .octicon-link {
color: #fff;
}
.markdown-body img {
background-color: transparent;
}
.ui-toc-dropdown .nav>.active:focus>a, .ui-toc-dropdown .nav>.active:hover>a, .ui-toc-dropdown .nav>.active>a {
color: white;
border-left: 2px solid white;
}
.expand-toggle:hover,
.expand-toggle:focus,
.back-to-top:hover,
.back-to-top:focus,
.go-to-bottom:hover,
.go-to-bottom:focus {
color: white;
}
.ui-toc-dropdown {
background-color: #333;
}
.ui-toc-label.btn {
background-color: #191919;
color: white;
}
.ui-toc-dropdown .nav>li>a:focus,
.ui-toc-dropdown .nav>li>a:hover {
color: white;
border-left: 1px solid white;
}
.markdown-body blockquote {
color: #bcbcbc;
}
.markdown-body table tr {
background-color: #5f5f5f;
}
.markdown-body table tr:nth-child(2n) {
background-color: #4f4f4f;
}
.markdown-body code,
.markdown-body tt {
color: #eee;
background-color: rgba(230, 230, 230, 0.36);
}
a,
.open-files-container li.selected a {
color: #5EB7E0;
}
</style>}
# Python for Finance
###### tags: `pandas` `matplotlib` `numpy`
### 使用Python 繪製K線圖、均線、KD線、成交量圖表。
畫出來大概會像這樣

### 使用套件:
* pandas
* numpy
* matplotlib
* pandas_datareader
* pyimgur
* mplfinance
* datetime
* yfinance
### 載入所需模組
```
import yfinance as yf
import datetime as dt
import pandas as pd
from pandas_datareader import data as pdr
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import matplotlib.ticker as mticker
import numpy as np
from mplfinance.original_flavor import candlestick_ohlc
import os
import pyimgur # 可以上傳到imgur
```
## 取得個股歷史數據
使用yahoo finance 提供的 yfinance 來獲取股票歷史紀錄:
```
def chart_plot_upload(stock):
stock = input("Press your stock symbol")
timed = dt.timedelta(120)
start = dt.datetime(2020, 1, 1) - dt.timedelta(days=max(smasUsed))
now = dt.datetime.now()
prices = pdr.get_data_yahoo(stock, start, now) #Fetches stock price data, saves as data frame
```
使用datetime 功能來取得時間參數。
`start = dt.datetime(2020, 1, 1) - dt.timedelta(days=max(smasUsed))`
上面這行程式碼可以確保有足夠的數據繪製MA線。
執行後可以得到下列的表格(DataFrame)。
```
Press your stock symbol: amzn
[*********************100%***********************] 1 of 1 completed
Open High Low Close Adj Close Volume
Date
2020-04-16 2346.000000 2461.000000 2335.000000 2408.189941 2408.189941 12038200
2020-04-17 2372.330078 2400.000000 2316.020020 2375.000000 2375.000000 7930000
2020-04-20 2389.949951 2444.979980 2386.050049 2393.610107 2393.610107 5770700
2020-04-21 2416.610107 2428.310059 2279.659912 2328.120117 2328.120117 7476700
2020-04-22 2369.000000 2394.000000 2351.000000 2363.489990 2363.489990 4218300
... ... ... ... ... ... ...
2020-08-27 3450.050049 3453.000000 3378.000000 3400.000000 3400.000000 4264800
2020-08-28 3423.000000 3433.370117 3386.500000 3401.800049 3401.800049 2897000
2020-08-31 3408.989990 3495.000000 3405.000000 3450.959961 3450.959961 4185900
2020-09-01 3489.580078 3513.870117 3467.000000 3499.120117 3499.120117 3476400
2020-09-02 3547.000000 3552.250000 3486.689941 3531.449951 3531.449951 3914500
[98 rows x 6 columns]
```
## 計算所需的數據
* 移動平均線(MA線)
```
def chart_plot_upload(stock):
...
smaUsed = [5, 10, 20] # 分別計算出5, 10, 20 日均線數值
for x in smaUsed:
prices['SMA_' + str(x)] = prices['Adj Close'].rolling(window = x).mean()
```
執行程式後會看到DataFrame最後多了三個column, SMA_5, SMA_10, SMA_20
```
Press your stock symbol: AMZN
[*********************100%***********************] 1 of 1 completed
Open High Low Close Adj Close Volume SMA_5 SMA_10 SMA_20
Date
2020-04-16 2346.000000 2461.000000 2335.000000 2408.189941 2408.189941 12038200 NaN NaN NaN
2020-04-17 2372.330078 2400.000000 2316.020020 2375.000000 2375.000000 7930000 NaN NaN NaN
2020-04-20 2389.949951 2444.979980 2386.050049 2393.610107 2393.610107 5770700 NaN NaN NaN
2020-04-21 2416.610107 2428.310059 2279.659912 2328.120117 2328.120117 7476700 NaN NaN NaN
2020-04-22 2369.000000 2394.000000 2351.000000 2363.489990 2363.489990 4218300 2373.682031 NaN NaN
... ... ... ... ... ... ... ... ... ...
2020-08-27 3450.050049 3453.000000 3378.000000 3400.000000 3400.000000 4264800 3356.104004 3298.129004 3227.313489
2020-08-28 3423.000000 3433.370117 3386.500000 3401.800049 3401.800049 2897000 3379.520020 3323.507007 3239.169495
2020-08-31 3408.989990 3495.000000 3405.000000 3450.959961 3450.959961 4185900 3408.220020 3350.362012 3256.122998
2020-09-01 3489.580078 3513.870117 3467.000000 3499.120117 3499.120117 3476400 3438.746045 3369.025024 3274.137500
2020-09-02 3547.000000 3552.250000 3486.689941 3531.449951 3531.449951 3914500 3456.666016 3396.122021 3290.458496
```
* 計算KD值
```
def chart_plot_upload(stock):
...
Period = 10
K = 4
D = 4
prices['RolHigh'] = prices['High'].rolling(window = Period).max()
prices['RolLow'] = prices['Low'].rolling(window = Period).min()
prices["stok"] = ((prices["Adj Close"] - prices["RolLow"]) /(prices["RolHigh"] - prices["RolLow"])) * 100
prices["K"] = prices["stok"].rolling(window=K).mean()
prices["D"] = prices["K"].rolling(window=D).mean()
prices = prices[max(smaUsed):] # 將NaN欄位去除
```
這裡是用10,4,4來繪製KD線,可以根據自己交易風格調整數值。
最後使用`prices = prices[max(smaUsed):]`將有Nan的欄位去除,結果如下:
```
Press your stock symbol: amzn
[*********************100%***********************] 1 of 1 completed
Open High Low Close Adj Close Volume SMA_5 SMA_10 SMA_20 Boolinger Band SMA15 STDEV LowerBand UpperBand Date RolHigh RolLow stok K D
Date
2020-05-14 2361.010010 2391.370117 2353.209961 2388.850098 2388.850098 3648100 2380.466016 2354.103027 2367.385522 2365.869352 46.705379 2272.458593 2459.280111 737559.0 2419.669922 2256.379883 81.125717 61.989648 57.167061
2020-05-15 2368.520020 2411.000000 2356.370117 2409.780029 2409.780029 4235000 2386.500000 2366.477026 2369.124524 2365.840023 46.675668 2272.488687 2459.191359 737560.0 2419.669922 2256.379883 93.943358 68.022826 60.394281
2020-05-18 2404.350098 2433.000000 2384.010010 2426.260010 2426.260010 4366600 2389.952002 2377.504028 2370.757019 2369.190690 49.193203 2270.804284 2467.577096 737563.0 2433.000000 2307.129883 94.645282 80.183596 66.499027
2020-05-19 2429.830078 2485.000000 2428.969971 2449.330078 2449.330078 4320500 2408.428027 2390.657031 2376.817517 2378.207357 50.741104 2276.725149 2479.689565 737564.0 2485.000000 2320.000000 78.381866 87.024056 74.305031
2020-05-20 2477.870117 2500.010010 2467.270020 2497.939941 2497.939941 3998100 2434.432031 2405.325024 2383.540015 2386.556022 59.344877 2267.866268 2505.245776 737565.0 2500.010010 2337.800049 98.723834 91.423585 81.663515
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
2020-08-27 3450.050049 3453.000000 3378.000000 3400.000000 3400.000000 4264800 3356.104004 3298.129004 3227.313489 3246.722656 105.446694 3035.829269 3457.616044 737664.0 3453.000000 3120.000000 84.084084 86.316208 85.101758
2020-08-28 3423.000000 3433.370117 3386.500000 3401.800049 3401.800049 2897000 3379.520020 3323.507007 3239.169495 3262.345329 110.120544 3042.104242 3482.586416 737665.0 3453.000000 3154.179932 82.865960 87.959752 86.544865
2020-08-31 3408.989990 3495.000000 3405.000000 3450.959961 3450.959961 4185900 3408.220020 3350.362012 3256.122998 3282.531999 115.324425 3051.883148 3513.180849 737668.0 3495.000000 3205.820068 84.770714 87.184885 87.046295
2020-09-01 3489.580078 3513.870117 3467.000000 3499.120117 3499.120117 3476400 3438.746045 3369.025024 3274.137500 3310.428678 113.604850 3083.218978 3537.638378 737669.0 3513.870117 3238.000000 94.653281 86.593510 87.013589
2020-09-02 3547.000000 3552.250000 3486.689941 3531.449951 3531.449951 3914500 3456.666016 3396.122021 3290.458496 3335.042676 119.070102 3096.902471 3573.182880 737670.0 3552.250000 3238.000000 93.381050 88.917751 87.663974
[78 rows x 19 columns]
```
## 開始繪製圖表
* 準備畫布
因為想畫的圖為蠟燭圖, MA, volumn圖都放在上面的大圖,下面小圖放KD線,因此要將畫布切成兩部分
```
def chart_plot_upload(stock):
...
fig, (axc, axkd) = plt.subplots(2,1, gridspec_kw={'height_ratios': [3, 1]}, sharex=True, figsize=(15,10))
plt.show()
```
axc 是用來畫蠟燭圖, axkd畫kd線,axv用來畫成交量,先用上面的程式碼設定好畫布及subplot。出來的畫布會長這樣

* 畫蠟燭圖, MA, Volume
使用 mplfinance.original_flavor 的 candlestick_ohlc 模組,畫圖之前先把所需要的參數放到ohlc的list內
```
from mplfinance.original_flavor import candlestick_ohlc
def chart_plot_upload(stock):
...
ohlc = []
for i in prices.index:
append_me = prices['Date'][i], prices['Open'][i], prices['High'][i], prices["Low"][i], prices["Adj Close"][i], prices["Volume"][i]
ohlc.append(append_me)
```
然後指定用axc的subplot畫,並接著執行plt.show()看一下長什麼德性
```
def chart_plot_upload(stock):
...
candlestick_ohlc(axc, ohlc, width=.5, colorup='g', colordown='r', alpha=0.75)
plt.show()
```
輸入標的: amzn
```
Press your stock symbol: amzn
```
沒意外應該就這樣

成功後開始畫移動平均線
```
def chart_plot_upload(stock):
...
for x in smaUsed:
axc.plot(prices['SMA_' + str(x)], label = 'SMA_' + str(x))
```

最後畫第一個subplot的最後一部分,成交量 (volume)
```
def chart_plot_upload(stock):
...
axv = axc.twinx()
axv.plot(prices['Volume'], color = 'Navy')
```
因為想要讓成交量以及蠟燭圖共用x軸,但y軸要分開放在圖表的右側。所以使用
`axv = axc.twinx()`
來設置雙座標軸,然後用新設置的axv繪製圖表,結果如下:

由上圖可以看到成功地畫出圖表了,也在圖的右側有屬於成交量本身的刻度,但太醜了,所以藉由把成交量刻度拉大來壓縮成交量的圖表:
```
def chart_plot_upload(stock):
...
axv.set_ylim(0, 5 * max(prices['Volume']))
axv.set_ylabel("Volume (K)") #順手加個座標軸標籤
```

最後再用fill_between功能來把volumn到底線的部分填滿,並用set_major_formatter來設定座標軸,讓他數字不要那麼大,讀起來比較有fu
```
def chart_plot_upload(stock):
...
axv.fill_between(prices['Date'],prices['Volume'], facecolor='Navy', alpha = 0.25)
axv.yaxis.set_major_formatter(mticker.FuncFormatter(lambda x, p: format(int(x/1000), ',')))
```
再微調一下座標軸以及顯示每條線的名字(legend)
```
def chart_plot_upload(stock):
...
axc.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d')) #change x axis back to datestamps
axc.xaxis.set_major_locator(mticker.MaxNLocator(8)) #add more x axis labels
axc.legend()
```
如下

* 畫KD線
因為數據都已經算出來了,只要用axkd這個subplot來把它畫在下半部就行:
```
def chart_plot_upload(stock):
...
axkd.plot(prices['K'], label = 'K')
axkd.plot(prices['D'], label = 'D')
axkd.axhline(y = 80, xmin = 0, xmax = 1, color='gray',linestyle = '--') #加橫線在80的位置
axkd.axhline(y = 20, xmin = 0, xmax = 1, color='gray',linestyle = '--') #加橫線在80的位置
axkd.legend(loc='center left', bbox_to_anchor=(1, 0.85)) #KD線的標籤,覺得礙眼把它移到圖表外
axkd.tick_params(axis='x', rotation=45) #rotate dates for readability
axc.set_title(stock) # 最上面新增了股票代號
```
最後執行就會變成一開始看到的那種風格了
PS. 因為一開始在設定畫布的時候有給了`sharex=True` 所以x的座標軸刻度只會顯示在最下面的那張圖。

## 後記
如果在最一開始執行 `plt.xkcd()`就可以使用matplotlib內建的卡通圖表風格。

還有別的, 例如
```
plt.style.use("fivethirtyeight")
```

更多風格可以自己上網找
就這樣