資料分析學習筆記(七)-- 股價分析
本例子,通過numpy分析股價
csv檔案讀寫
CSV(Comma-Separated Value,逗號分隔值)是一種常見的檔案格式,通常資料庫的轉存檔案就是csv格式,檔案中的各個欄位對應於資料庫表中的列。
這裡有一份csv格式的檔案,本文一該檔案資料為例。
資料結構如下圖
每一條資料對應,第一列為股票程式碼以標識股票(蘋果公司股票程式碼為AAPL),第二列為dd-mm-yyyy格式的日期,第三列為空,隨後各列依次是開盤價、最高價、最低價和收盤價,最後一列為當日的成交量。
我們可以通過numpy的 loadtext()和savetext()函式完成讀寫操作
- 讀
# 從檔案中讀取資料
# 'data.csv':檔名
# delimiter:分隔符
# usecols:所取資料的列下標
# unpack:返回陣列
c,v = np.loadtxt('data.csv', delimiter=',', usecols=(6,7), unpack=True)
# 讀取了收盤價和成交量資料
- 寫
x = y = z = np.arange(0.0,5.0,1.0)
np.savetxt('test.csv', x, delimiter=',') # X 陣列
np.savetxt('test.csv', (x,y,z)) # x,y,z 都是一維陣列
np.savetxt('test.csv' , x, fmt='%1.4e') # 使用指數表示法
成交量加權平均價格
首先讀取成交量資料和收盤價資料
c,v = np.loadtxt('data.csv', delimiter=',', usecols=(6,7), unpack=True)
# 計算成交量的平均價格
vwap = np.average(c, weights=v)
v_mean = np.average(c)
print('算術平均:{},加權平均:{}'.format(v_mean, vwap))
'''算術平均:351.0376666666667,加權平均:350.5895493532009'''
# 時間加權平均價格
t = np.arange(len(c))
twap = np.average(c, weights=t)
print('時間加權平均:{}'.format(twap))
'''時間加權平均:352.4283218390804'''
最大值和最小值
尋找最高價和最低價
# 讀取最高價和最低價
h,l = np.loadtxt('data.csv', delimiter=',', usecols=(4,5), unpack=True)
# 最高價中的最大值
h.max() # 364.9
# 最低價中的最小值
l.min() # 333.53
# 區間差值:np.ptp()
h_ptp = np.ptp(h)
l_ptp = np.ptp(l)
print('最高價的差值為:{:.2f}'.format(h_ptp))
print('最低價的差值為:{:.2f}'.format(l_ptp))
'''最高價的差值為:24.86
最低價的差值為:26.97'''
簡單統計分析
收盤價的統計分析
c = np.loadtxt('data.csv', delimiter=',', usecols=(6,), unpack=True)
print('原始資料:{}'.format(c))
'''
原始資料:[336.1 339.32 345.03 344.32 343.44 346.5 351.88 355.2 358.16 354.54
356.85 359.18 359.9 363.13 358.3 350.56 338.61 342.62 342.88 348.16
353.21 349.31 352.12 359.56 360. 355.36 355.76 352.47 346.67 351.99] '''
# 中位數
print('中位數為:{}'.format(np.median(c)))
'''中位數為:352.055'''
# 驗證
sorted = np.sort(c)
print('排序後的資料為:{}'.format(sorted))
'''
[336.1 338.61 339.32 342.62 342.88 343.44 344.32 345.03 346.5 346.67
348.16 349.31 350.56 351.88 351.99 352.12 352.47 353.21 354.54 355.2
355.36 355.76 356.85 358.16 358.3 359.18 359.56 359.9 360. 363.13]'''
# 可以看出中位數為[351.99 352.12]的平均值
# 方差
print('股價的方差為:{}'.format(np.var(c)))
'''股價的方差為:50.126517888888884'''
股票收益率
- 股票收益率:收盤價的差值/收盤價
# diff()後一項減去前一項的差,數量會少一項,對應的只取到最後一項收盤價
returns = np.diff(c) / c[:-1]
print('收益率為:{}'.format(returns))
'''
[ 0.00958048 0.01682777 -0.00205779 -0.00255576 0.00890985 0.0155267
0.00943503 0.00833333 -0.01010721 0.00651548 0.00652935 0.00200457
0.00897472 -0.01330102 -0.02160201 -0.03408832 0.01184253 0.00075886
0.01539897 0.01450483 -0.01104159 0.00804443 0.02112916 0.00122372
-0.01288889 0.00112562 -0.00924781 -0.0164553 0.01534601]
'''
print('收益率的標準差:{}'.format(np.std(returns)))
'''收益率的標準差:0.012922134436826306'''
# 計算收益率為正的資料
posretindices = np.where(returns>0)
returns_value = returns[posretindices]
print('收益為正的位置為:{},資料為:{}'.format(posretindices, returns_value))
'''
收益為正的位置為:(array([ 0, 1, 4, 5, 6, 7, 9, 10, 11, 12, 16, 17, 18, 19, 21, 22, 23,
25, 28]),),資料為:[0.00958048 0.01682777 0.00890985 0.0155267 0.00943503 0.00833333
0.00651548 0.00652935 0.00200457 0.00897472 0.01184253 0.00075886
0.01539897 0.01450483 0.00804443 0.02112916 0.00122372 0.00112562
0.01534601]
'''
- 收益波動率
收益波動率是對資產收益率不確定性的衡量,用於反映金融資產的風險水平。波動率越高,金融資產價格的波動越劇烈,資產收益率的不確定性就越強;波動率越低,金融資產價格的波動越平緩,資產收益率的確定性就越強
公式:對數收益率的標準差/對數收益率的平均值/交易天數倒數的平方根
# 對數收益率,注意去掉0的資料,因為0沒有對數
logreturns = np.diff(np.log(c))
print('對數收益率為:{}'.format(logreturns))
# 去掉週末天數為252
annual_volatility = np.std(logreturns) / np.mean(logreturns) / np.sqrt(1./252.)
print('年度化收益波動率為:{}'.format(annual_volatility))
'''年度化收益波動率為:129.27478991115132'''
month_volatility = annual_volatility * np.sqrt(1./12.)
print('月度化收益率波動率為:{}'.format(month_volatility))
'''月度化收益率波動率為:37.318417377317765'''
資料彙總
按日期分析
我們按照一週中的星期幾取分析,計算其收盤價,以及均價
首先,我們為了方便處理,需要將資料中的日期格式轉換為資料索引形式
from datetime import datetime
'''
Monday:0
Tuesday:1
Wendesday:2
Thursday:3
Friday:4
Saturday:5
Sunday:6
'''
# 日期轉換函式
def datestr2num(s):
s = s.decode('utf-8') # 將bytes轉為str型別
return datetime.strptime(s, '%d-%m-%Y').date().weekday()
讀取日期和收盤價資料
# converters,將資料對映,這裡是將第1列上的資料通過函式datestr2num進行處理
dates,colse = np.loadtxt('data.csv', delimiter=',', usecols=(1, 6), converters={1: datestr2num}, unpack=True)
print('dates:{}'.format(dates))
'''dates:[4. 0. 1. 2. 3. 4. 0. 1. 2. 3. 4. 0. 1. 2. 3. 4. 1. 2. 3. 4. 0. 1. 2. 3.
4. 0. 1. 2. 3. 4.]'''
# 數字0-4分別對應週一到週五
按日期分析
# 建立5天的陣列
averages = np.zeros(5)
for i in range(5):
# 分別取出星期幾對應的位置
indexs = np.where(dates==i)
# 取出對應的價格
prices = np.take(colse, indices=indexs) # np.take(colse, indices=indexs) 等價於 colse[indexs]
avg = np.mean(prices) # 算出均值
print('Day {} prices:{} Average:{:.2f}'.format(i, prices, avg))
averages[i] = avg # 替換資料
'''
Day 0 prices:[[339.32 351.88 359.18 353.21 355.36]] Average:351.79
Day 1 prices:[[345.03 355.2 359.9 338.61 349.31 355.76]] Average:350.64
Day 2 prices:[[344.32 358.16 363.13 342.62 352.12 352.47]] Average:352.14
Day 3 prices:[[343.44 354.54 358.3 342.88 359.56 346.67]] Average:350.90
Day 4 prices:[[336.1 346.5 356.85 350.56 348.16 360. 351.99]] Average:350.02
'''
順便計算5天中最高和最低均價
print('最高的均值為:{:.2f}, the week {}'.format(np.max(averages), np.argmax(averages)))
print('最低的均值為:{:.2f}, the week {}'.format(np.min(averages), np.argmin(averages)))
'''最高的均值為:352.14, the week 2
最低的均值為:350.02, the week 4
'''
- 周彙總
有時候,我們想要按周進行統計彙總,例如:我想看前三週的,週一的開盤價、週五的收盤價、一週中最高和最低價資訊
首先依舊是獲取到我們所需要到資料
dates, open, high, low, close = np.loadtxt('data.csv', delimiter=',', usecols=(1, 3, 4, 5, 6), converters={1:datestr2num}, unpack=True)
然後建立前三週的索引,分別對應三週中的股票名稱、週一開盤價、一週中最高價、一週中最低價、週五收盤價
# split()可以將陣列均等分割
weeks_indices = np.split(np.arange(0, 15), 3)
print('Weeks indices : {}'.format(weeks_indices))
Weeks indices : [array([0, 1, 2, 3, 4]), array([5, 6, 7, 8, 9]), array([10, 11, 12, 13, 14])]
這裡簡單介紹 apply_along_axis() 函式
numpy.apply_along_axis(func, axis, arr, *args, **kwargs)
必選引數:func,axis,arr。其中func是我們自定義的一個函式,函式func(arr)中的arr是一個數組,函式的主要功能就是對數組裡的每一個元素進行變換,得到目標的結果
axis表示函式func對陣列arr作用的軸,1:橫向,0:豎向
可選引數:*args, **kwargs。都是func()函式額外的引數
返回值:numpy.apply_along_axis()函式返回的是一個根據func()函式以及維度axis運算後得到的的陣列
例子:
def my_func(a):
return (a[0] + a[-1]) * 0.5
b=np.array([[1,2,3,4],[5,6,7,8],[9,10,11,12]])
res = np.apply_along_axis(my_func, 0, b) # 豎向
print(res) # [5. 6. 7. 8.]
res = np.apply_along_axis(my_func, 1, b) # 橫向
print(res) # [ 2.5 6.5 10.5]
回到之前,我們來定一個 summarize() 函式,用來計算周彙總
def summarize(a, o, h, l, c):
monday_open = o[a[0]] # 週一開盤價
week_high = np.max(np.take(h, a) ) # 一週中最高價
week_low = np.min( np.take(l, a) ) # 一週中最低價
friday_close = c[a[-1]] # 週五收盤價
return ("APPL", monday_open, week_high, week_low, friday_close)
# 呼叫apply_along_axis()函式,傳入相關引數
weeksummary = np.apply_along_axis(summarize, 1, weeks_indices, open, high, low, close)
print('Week summary : {}'.format(weeksummary))
'''
Week summary : [['APPL' '344.17' '345.65' '333.53' '343.44']
['APPL' '343.61' '360.0' '343.51' '354.54']
['APPL' '354.75' '364.9' '353.54' '358.3']]
'''
# 將處理後的資料儲存到本地
np.savetxt('weeksummary.csv', weeksummary, delimiter=',', fmt='%s')
在工程目錄下,找到 weeksummary.csv 檔案,開啟
真實波動幅度均值(ATR)
- ATR介紹
1、真實波動幅度均值(ATR)是韋爾斯•王爾德(J. Welles Wilder)提出的,這一指標主要用來衡量證券價格的波動,因此,這一技術指標並不能直接反映價格走向及趨勢穩定性,而只是用來表明價格波動的程度。
2、極端的高ATR或低ATR值可以被看作價格趨勢的反轉或下一個趨勢的開始。較低的ATR表示比較冷清的市場交易氣氛,而較高的ATR則表示比較旺盛的交易氣氛。一段較長時間的低ATR很可能表明市場正在積蓄力量並逐漸開始下一個價格趨勢,而一段非常高的ATR通常是由於短時間內價格的大幅上漲或下跌造成的,通常此數值不可能長期維持在高水平。
3、交易者也可以是使用ATR來設定自己交易的止損和止贏價位。由於ATR計算的是在某一個時間段內貨幣對的波動真實範圍,因此可以把該範圍作為計算止損和止贏的標準。
- TR:真實波動幅度
TR=∣最高價-最低價∣和∣最高價-昨收∣和∣昨收-最低價∣的最大值
ATR=TR的N日簡單移動平均,引數N設定為14日
- 首先我們計算TR
h, l, c = np.loadtxt('data.csv', delimiter=',', usecols=(4,5,6), unpack=True)
# 我們先計算所有資料的TR
truerange = np.maximum(np.fabs(h-l), np.fabs(h-c), np.fabs(c-l))
print('真實波動幅度:{}'.format(truerange))
'''
真實波動幅度:[10.87 5.74 4.67 1.7 5.69 3.19 5.61 3.37 4.13 12. 4.26 2.77
2.42 4.4 3.75 9.98 7.68 6.03 6.78 3.63 3.93 8.04 5.95 3.87
2.54 10.36 5.15 4.16 4.87 7.32]
'''
'''一般來說,真實波動幅度均值(ATR)通常以7或14個時段為基礎進行計算,這個時段可以是一天內的某個時間段,也可以是一天的日價,乃至周價和月價。
第一個ATR通常是前7或14天中每天的TR的簡單算術平均
'''
# 計算出第一個ATR:這裡取週期n為14
n = 14 # 週期為14
ATR0 = np.mean(truerange[:n])
print('首個ATR為:{}'.format(ATR0))
'''首個ATR為:5.058571428571428'''
# 計算剩下天數的 ATR
# 剩下的數量 N
N = len(truerange) - n
print('可計算ATR的天數為:{}'.format(N))
'''可計算ATR的天數為:16'''
# 建立 N 數量的 0 陣列,用來儲存 ATR
atr = np.zeros(N)
# 那麼 atr[0] 就是 ATR0
atr[0] = ATR0
# 剩下的 ATR 步驟如下:
'''
1.將前14天的ATR乘以13
2.將步驟一所得的值加上新一天的TR
3.將步驟二所得值除以14
'''
for i in range(1, N):
atr[i] = (n - 1) * atr[i - 1] + truerange[i]
atr[i] /= n
print('ATR:{}'.format(atr))
'''
ATR:[5.05857143 5.1072449 5.07601312 4.83486933 4.89595009 4.77409651
4.8338039 4.72924648 4.68644316 5.20884008 5.14106579 4.97170394
4.78943938 4.76162228 4.68936354 5.06726615]
'''
- 簡單移動平均線
簡單移動平均線通常用於分析時間序列上的資料,例如我們計算N個交易日股票收盤價的移動平均值
import matplotlib.pyplot as plt
# 讀取資料
c = np.loadtxt('data.csv', delimiter=',', usecols=(6,), unpack=True)
N = 5
# 定義移動視窗和權重
weights = np.ones(N) / N
'''array([ 0.2, 0.2, 0.2, 0.2, 0.2])'''
# 呼叫卷積函式convolve,並從中擷取對稱陣列,其數量和收盤價 len(c)-N+1 一致
sample = np.convolve(weights, c)[N-1:-(N-1)]
# 設定時間作為x軸座標
time = np.arange(N-1, len(c))
# 繪製真實收盤價走勢,顏色為red
plt.plot(time, c[N-1:], linewidth=1.0, color='red')
# 繪製簡單移動平均值走勢,顏色為green
plt.plot(time, sample, linewidth=1.0, color='green')
# show
plt.show()
由上圖所示:紅色線部分是股價的真實收盤走勢,綠色線部分則為簡單移動平均值走勢,其相對真實值延後,相對比較平滑
- 指數移動平均線
相比與簡單移動平均線權重相等,指數移動平均線的權重是呈指數型衰減趨勢的,即與當前時間越久的資料所賦予的權重以指數速度減少
我們通過使用 exp() 函式來獲取指數值, linspace()函式來獲取指定範圍內均勻陣列
# 下面獲取指數移動平均線的權重
N = 5
weights = np.exp(np.linspace(0, 1, N))
# 求出每一份所佔的比重
weights = weights / np.sum(weights)
print('權重為:{}'.format(weights))
'''權重為:[0.11405072 0.14644403 0.18803785 0.24144538 0.31002201]'''
此時,權重不再是0.2了,而是越靠近當前時間權重越大,反之越小
在簡單移動平均線的基礎上,我們再新增一條指數移動均線
exponent = np.convolve(weights, c)[N-1:-(N-1)]
# 繪製指數移動平均值走勢,顏色為blue
plt.plot(time, exponent, linewidth=1.0, color='blue')
plt.show() # show函式移到後面
此圖中的藍色線就是指數移動均線了。
布林帶
布林帶:用來描述股票價格變化的區間
組成:三條線:上軌、中軌、下軌
中軌即簡單移動均線,上軌為:簡單移動均線加上N日兩倍標準差,下軌則為:簡單移動均線減去N日兩倍標準差
首先取出資料,計算好簡單移動均線
N = 5
weights = np.ones(N) / N
c = np.loadtxt('data.csv', delimiter=',', usecols=(6,), unpack=True)
# 簡單移動均線
sample = np.convolve(weights, c)[N-1:-(N-1)]
# 標準差陣列
deviation = []
計算 N 日標準差
for i in range(N - 1, len(c)):
# dev 為 N 日的收盤價,往前推 N 日
dev = c[ i - ( N - 1) : i+1 ]
# 建立 N 日,值為 0 的陣列
averages = np.zeros(N)
# 將簡單移動均線的 i - (N - 1) 分別填充到該陣列中,sample是下標從 0 開始的
averages.fill(sample[i - (N - 1)])
# 計算標準差
dev = dev - averages
dev = dev ** 2
dev = np.sqrt(np.mean(dev))
deviation.append(dev)
# 兩倍的標準差
deviation = 2 * np.array(deviation)
那麼上軌道和下軌道的資料為:
# 上軌
upperBB = sample + deviation
# 下軌
lowerBB = sample - deviation
繪製布林帶
time = np.arange(N-1, len(c))
# 真實收盤價
plt.plot(time, c[N-1:], linewidth=1.0, color='yellow')
# 簡單移動均線
plt.plot(time, sample, linewidth=1.0, color='green')
# 上軌道
plt.plot(time, upperBB, linewidth=2.0, color='red', linestyle='--')
# 下軌道
plt.plot(time, lowerBB, linewidth=2.0, color='green', linestyle='--')
plt.show()
上圖所示,兩條虛線內部即為布林帶,中間綠色實線為簡單移動均線,黃色部分為真實收盤價走勢