1. 程式人生 > >資料分析學習筆記(七)-- 股價分析

資料分析學習筆記(七)-- 股價分析

本例子,通過numpy分析股價

csv檔案讀寫

CSV(Comma-Separated Value,逗號分隔值)是一種常見的檔案格式,通常資料庫的轉存檔案就是csv格式,檔案中的各個欄位對應於資料庫表中的列。

這裡有一份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 檔案,開啟

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

布林帶

上圖所示,兩條虛線內部即為布林帶,中間綠色實線為簡單移動均線,黃色部分為真實收盤價走勢