1. 程式人生 > >Python股市資料分析教程——學會它,或可以實現半“智慧”炒股 (Part 1)

Python股市資料分析教程——學會它,或可以實現半“智慧”炒股 (Part 1)

以下為譯文

本篇文章是"Python股市資料分析"兩部曲中的第一部分(第二部分的文章在這裡),內容基於我在猶他州立大學MATH 3900 (Data Mining)課程上的一次講座。在這些文章中,我將介紹一些關於金融資料分析的基礎知識,例如,使用pandas獲取雅虎財經上的資料,股票資料視覺化,移動均線,開發一種均線交叉策略,回溯檢驗以及基準測試。第二篇文章會介紹一些實踐中可能出現的問題,而本篇文章著重討論移動平均線。

注意:本篇文章所涉及的看法、意見等一般性資訊僅為作者個人觀點。本文的任何內容都不應被視為金融投資方面的建議。此外,在此提供的所有程式碼均無法提供任何保證。選擇使用這些程式碼的個人需自行承擔風險。

引言

高等數學與統計學已在金融領域應用了一段時間。 在20世紀80年代以前,銀行業和金融界以"枯燥乏味"而聞名;投資銀行與商業銀行不同,銀行的主要職責在於處理"簡單的"(至少與今天相比)金融商品,如貸款。里根政府的放松管制,再加上一大批數學天才,將整個行業從"枯燥的"銀行業務轉變成了今天這個樣子,而且,從那時起,金融便融入了其他自然學科,激勵著數學領域的研究與發展。比如,近期數學領域最大的成就之一,便是Black-Scholes公式的推導,這一成果可用於股票期權(一種賦予持有人以特定價格向期權發行商購買或出售股票權利的合約)的定價。可以說,在一定程度上,包括 Black-Scholes公式在內的糟糕的統計學模型導致了2008年金融危機的爆發

近幾年來,為了在買賣金融資產的過程中賺取利潤,電腦科學也同高等數學一起,參與到了金融與貿易領域的變革當中。最近幾年,計算機主導著貿易的進行;演算法相比人類能夠更快速地做出交易決策(如此之迅速,以至於在設計系統時,光的傳導速度成為了約束)。此外,機器學習與資料探勘技術在金融領域越來越受歡迎,而且以後也可能會繼續這樣下去。實際上,大部分的演算法交易都屬於高頻交易(HFT)。儘管演算法的表現可能超過人類,但是這項技術並不成熟,應用的領域又充滿著高風險與動盪。高頻交易導致了2010年2013年市場的閃電崩盤,其中,2013年的崩盤是由一條美聯社被黑客偽造的關於白宮受到攻擊的推文所引發的。

然而,本篇文章並不會討論如何使用糟糕的數學模型和交易演算法使股市崩盤。相反,我打算向大家介紹一些用於處理和分析股市資料的Python工具。我還將討論移動均線、如何使用移動均線來構建交易策略、如何在進入倉位時制定退出策略以及如何使用回溯檢驗評估交易策略等方面的內容。

宣告:這不是關於金融投資的建議!!!而且,我從未從事過交易員等工作(許多這方面的知識我都是在鹽湖城社群學院中一門為期一學期的股市交易課程中接觸到的)!這些只是單純的入門級知識,並不足以讀者在股市中進行實際的交易操作。股市有風險,入市需謹慎!

獲取並可視化股票資料

使用pandas從雅虎財經中獲取資料

在我們處理股票資料之前,我們首先需要通過一些可行的途徑獲取它們。股票資料可以從雅虎財經谷歌財經或者其他資料來源中獲得,而pandas可以輕鬆訪問雅虎財經、谷歌財經以及其他來源中的資料。在本篇文章中,我們從雅虎財經獲取股票資料。

以下程式碼演示了直接建立一個包含股票資訊的DataFrame物件的過程。(你可以在這裡瞭解更多關於遠端資料訪問的資訊。)

import pandas as pd
import pandas.io.data as web
import datetime

start = datetime.datetime(2016,1,1)
end = datetime.date.today()

apple = web.DataReader("AAPL", "yahoo", start, end)

type(apple)
C:\Anaconda3\lib\site-packages\pandas\io\data.py:35: FutureWarning: 
The pandas.io.data module is moved to a separate package (pandas-datareader) and will be removed from pandas in a future version.
After installing the pandas-datareader package (https://github.com/pydata/pandas-datareader), you can change the import ``from pandas.io import data, wb`` to ``from pandas_datareader import data, wb``.
  FutureWarning)

pandas.core.frame.DataFrame
apple.head()
bdd6427669202df573a3041c5a4c1fe372b3a5bd

讓我們簡單介紹一下。開盤價是指股票在交易日開市時的股價(並不一定是前一交易日的收盤價格),最高價是指在交易日當天股價的最高價格,最低價是指在交易日當天股價的最低價格,收盤價是指股票在交易日收盤時的股價。交易量表示被交易股票的數量。調整收盤價是根據公司行為調整後的股票收盤價格。儘管我們認為大多數股票的價格是由交易員設定的,但是股票分割(公司將當前的一張股票拆分成價值一半的兩張股票)和派付股息(為每份股份支付公司紅利)仍然會影響到股票的價格,這些情況我們都應該考慮進來。

股票資料視覺化

既然我們現在有了股票資料,我們可以通過視覺化的形式展示它。我首先演示如何使用matplotlib來視覺化股票資料。注意,名為apple的DataFrame物件有一個很方便的方法plot(),這個函式使建立圖表更加容易。

import matplotlib.pyplot as plt

%matplotlib inline

%pylab inline
pylab.rcParams['figure.figsize'] = (15, 9)  

apple["Adj Close"].plot(grid = True) 
Populating the interactive namespace from numpy and matplotlib
8728601e9377c83107c46360846e0f29744c03c0

折線圖是很不錯,但是每個日期都至少包含四個變數(開盤價、最高價、最低價、收盤價),我們希望有一些視覺化的方法能夠同時展示這四個變數,而不是簡單地畫四條折線。金融資料通常以日本蠟燭圖(即K線圖)的形式繪製,這種圖表最早在18世紀由日本米市商人命名。matplotlib可以繪製這樣的圖表,但操作起來比較複雜。

我實現了一個函式,你可以更容易地在pandas資料框架中建立蠟燭圖,並使用它繪製我們的股票資料。(程式碼基於這個例子,你可以在這裡找到相關函式的文件)

from matplotlib.dates import DateFormatter, WeekdayLocator,\
    DayLocator, MONDAY
from matplotlib.finance import candlestick_ohlc

def pandas_candlestick_ohlc(dat, stick = "day", otherseries = None):
    mondays = WeekdayLocator(MONDAY)        
    alldays = DayLocator()     
    dayFormatter = DateFormatter('%d')

    transdat = dat.loc[:,["Open", "High", "Low", "Close"]]
    if (type(stick) == str):
        if stick == "day":
            plotdat = transdat
            stick = 1 
        elif stick in ["week", "month", "year"]:
            if stick == "week":
                transdat["week"] = pd.to_datetime(transdat.index).map(lambda x: x.isocalendar()[1]) 
            elif stick == "month":
                transdat["month"] = pd.to_datetime(transdat.index).map(lambda x: x.month) 
            transdat["year"] = pd.to_datetime(transdat.index).map(lambda x: x.isocalendar()[0]) 
            grouped = transdat.groupby(list(set(["year",stick]))) 
            plotdat = pd.DataFrame({"Open": [], "High": [], "Low": [], "Close": []})

            for name, group in grouped:
                plotdat = plotdat.append(pd.DataFrame({"Open": group.iloc[0,0],
                                            "High": max(group.High),
                                            "Low": min(group.Low),
                                            "Close": group.iloc[-1,3]},
                                           index = [group.index[0]]))
            if stick == "week": stick = 5
            elif stick == "month": stick = 30
            elif stick == "year": stick = 365

    elif (type(stick) == int and stick >= 1):
        transdat["stick"] = [np.floor(i / stick) for i in range(len(transdat.index))]
        grouped = transdat.groupby("stick")
        plotdat = pd.DataFrame({"Open": [], "High": [], "Low": [], "Close": []}) 
        for name, group in grouped:
            plotdat = plotdat.append(pd.DataFrame({"Open": group.iloc[0,0],
                                        "High": max(group.High),
                                        "Low": min(group.Low),
                                        "Close": group.iloc[-1,3]},
                                       index = [group.index[0]]))

    else:
        raise ValueError('Valid inputs to argument "stick" include the strings "day", "week", "month", "year", or a positive integer')

    fig, ax = plt.subplots()
    fig.subplots_adjust(bottom=0.2)
    if plotdat.index[-1] - plotdat.index[0] < pd.Timedelta('730 days'):
        weekFormatter = DateFormatter('%b %d')
        ax.xaxis.set_major_locator(mondays)
        ax.xaxis.set_minor_locator(alldays)
    else:
        weekFormatter = DateFormatter('%b %d, %Y')
    ax.xaxis.set_major_formatter(weekFormatter)

    ax.grid(True)

    candlestick_ohlc(ax, list(zip(list(date2num(plotdat.index.tolist())), plotdat["Open"].tolist(), plotdat["High"].tolist(),
                      plotdat["Low"].tolist(), plotdat["Close"].tolist())),
                      colorup = "black", colordown = "red", width = stick * .4)

    if otherseries != None:
        if type(otherseries) != list:
            otherseries = [otherseries]
        dat.loc[:,otherseries].plot(ax = ax, lw = 1.3, grid = True)

    ax.xaxis_date()
    ax.autoscale_view()
    plt.setp(plt.gca().get_xticklabels(), rotation=45, horizontalalignment='right')

    plt.show()

pandas_candlestick_ohlc(apple)
39fa3f3c7223d2abed09b861c96d0f88915a8700

在蠟燭圖中,黑色蠟燭表示交易日當天收盤價高於開盤價(盈利),而紅色蠟燭表示交易日當天開盤價高於收盤價(虧損)。燭芯表示最高價與最低價,蠟燭體則表示開盤價與收盤價(顏色用來區分哪一側為開盤價,哪一側為收盤價)。蠟燭圖在金融領域很受歡迎,根據圖表中蠟燭的形狀、顏色以及位置,技術分析中的一些策略可以使用它來制定交易策略。但在這裡我不會介紹有關這類策略的內容。

我們可能希望在同一張圖表中繪製多個金融商品的資料;我們可能想要對比股票,將它們與市場進行比較,或者看看其他證券,比如交易所交易基金(ETFs)。之後,我們可能還想看看如何根據一些指標,如移動均線,來繪製金融商品。對於這種情況,你最好使用折線圖而不是蠟燭圖。(如何將多個蠟燭圖相互疊加在一起而不使圖表混亂?)

在下面的程式碼中,我獲取了一些其他科技公司的股票資料,並把它們的調整收盤價格繪製在了一起。

microsoft = web.DataReader("MSFT", "yahoo", start, end)
google = web.DataReader("GOOG", "yahoo", start, end)

stocks = pd.DataFrame({"AAPL": apple["Adj Close"],
                      "MSFT": microsoft["Adj Close"],
                      "GOOG": google["Adj Close"]})

stocks.head()
9b29b6861bdeeea12662adc740b0c4658503e84f
stocks.plot(grid = True)
26c6f7bdff2370caf9c75b03f738401fabe53be8

這張圖有什麼問題?儘管絕對價格很重要(昂貴的股票很難購買,這不僅影響著這類股票的價格波動,也影響著你交易這類股票的能力),但是在交易過程中,相比絕對價格,我們更加關心資產的相對變化。谷歌的股票比蘋果和微軟的股票貴得多,這種差異使得蘋果和微軟股票的波動看起來比實際情況小得多。

一種解決方案是在繪製圖表時使用兩種不同的尺度;一種尺度用於蘋果和微軟的股票,另一種尺度用於谷歌股票。

stocks.plot(secondary_y = ["AAPL", "MSFT"], grid = True)
938968bcdd14ef18606013c73bdca34b78035743

然而,一個"更好的"解決方案是,僅在圖表中繪製我們真正想要的資訊:股票的回報。這就需要我們根據需求將資料轉換成更有用的形式。這裡有幾種我們可以應用的轉換。

一種方式是考慮股票自利息週期開始以來的回報。換句話說,我們繪製:

cb4b43ed555b9dd98caffdf1eadb2757792ea777

正如我下面演示的這樣,這意味著轉換stocks物件中的資料。

stock_return = stocks.apply(lambda x: x / x[0])
stock_return.head()
390a68467b8a3784f7e6615e8bbc5bedbf60f445
stock_return.plot(grid = True).axhline(y = 1, color = "black", lw = 2)
4cf6edd1ca18c4aebb67ab94b15f02b00eb2cb87

這樣的圖表就更有用了。現在,我們可以看到每隻股票在週期開始以來的盈利。而且,我們還能發現這些股票密切相關;它們通常朝同一個方向發展,在其他的圖表中很難發現這樣的事實。

除此之外,我們還可以繪製每隻股票在每一個交易日的變化。比如,我們可以通過比較第t天與第t+1天的價格來繪製股票增長的百分比,公式如下:

237c691507a947824291f6c77e56ccc60f8398c7

但是這種變化也可以通過如下公式定義:

19d06ddeab8e92628ffcb4196cf47c53d8af3998

這些公式多少有些不同,可能會分析出不同的結論,但是還有另外一種對股票增長建模的方法:對數差值。

f3c22b9cf8fd99609c34f20ad9237fa1dacbbaa1

(這裡的log為自然對數,我們的定義並不關心使用的是第t天與第t-1天的對數差值還是第t+1天與第t天的對數差值。)使用對數差值的好處在於,這種差值可以理解為股價的百分比變化,且不依賴於計算過程中分數的分母。

我們可以通過如下方式獲取並繪製stocks物件中資料的對數差值:

import numpy as np

stock_change = stocks.apply(lambda x: np.log(x) - np.log(x.shift(1)))
stock_change.head()
7695425d5fa139f09de9b1a07036888fd0517d0d
stock_change.plot(grid = True).axhline(y = 0, color = "black", lw = 2)
f7a9e86ec17b40b17bc5887afa3ef9c8645e64fe

你傾向於哪一種轉換?關注股票以往的盈利情況會使得證券的整體趨勢更加明顯。但是,在對股票的行為模式建模時,更先進的方法實際考慮的是交易日間股價的變化。因此,我們不應該忽略這部分的資訊。

移動均線

圖表是很非常有用的。實際上,一些交易員做出的策略幾乎完全基於圖表(他們屬於"技術人員",因為基於在圖表中查詢模式的交易策略是被稱為技術分析的貿易規則的一部分)。現在,讓我們考慮如何才能找到股票的趨勢。

對於序列xt以及時刻t,q天均線表示過去q天股價的均值:也就是說,如果MAtq表示t時刻的q天均線,那麼:

6a17c5a6ac91cbddd3b9aff86e7c5ab14f33250b

移動均線平滑了資料序列,並有助於識別股市的發展趨勢。q值越大,移動均線就越難反映序列xt中的短期波動。這裡的想法是,移動均線過程能夠從"噪聲"中識別股市的發展趨勢。短期均線具有較小的q值,比較緊密地跟隨股票的趨勢發展,而長期均線的q值較大,進而使得均線對股票波動的響應較小,而且更加平穩。

pandas提供了輕鬆計算移動均線的功能。下面的程式碼展示了這部分功能,我首先為蘋果股票建立了一條20天(1個月)均線,隨後,將其與股票資料一同繪製在圖表中。

apple["20d"] = np.round(apple["Close"].rolling(window = 20, center = False).mean(), 2)
pandas_candlestick_ohlc(apple.loc['2016-01-04':'2016-08-07',:], otherseries = "20d")
1e812aae12bd68343b2ffdfaf1f0120991186fdd

注意滾動均值是從什麼時候開始的。只有在積累了20天的交易日資料之後,我們才能計算股票的20天均線。這個限制對於長期均線而言更加嚴重。如果我們想要計算股票的200天均線,我們需要多少蘋果公司的股票資料才行?在這裡,我們將主要關注2016年的股票走勢。

start = datetime.datetime(2010,1,1)
apple = web.DataReader("AAPL", "yahoo", start, end)
apple["20d"] = np.round(apple["Close"].rolling(window = 20, center = False).mean(), 2)

pandas_candlestick_ohlc(apple.loc['2016-01-04':'2016-08-07',:], otherseries = "20d")
14bce8b49215114cf9b0b4fbe3c27838bde6255e

你會注意到,移動均線要比實際的股票資料平滑得多。此外,這是一個難以處理的標誌;股票需要在移動均線的上方或下方,以便改變股票走勢的方向。因此,股票走勢越過移動均線的情況表明了股票一種可能的走向,應該引起我們的注意。

交易員通常對多條移動均線感興趣,比如20天均線、50天均線以及200天均線。同時檢查多條移動均線也很容易。

apple["50d"] = np.round(apple["Close"].rolling(window = 50, center = False).mean(), 2)
apple["200d"] = np.round(apple["Close"].rolling(window = 200, center = False).mean(), 2)

pandas_candlestick_ohlc(apple.loc['2016-01-04':'2016-08-07',:], otherseries = ["20d", "50d", "200d"])
6a3af23778f4186216b21f273ebb9c81e3cf137a

其中,20天均線對區域性變化最為敏感,而200天均線對區域性變化最不敏感。在這裡,200天均線表明股票整體呈熊市行情:股票隨著時間的推移趨勢向下。20天均線有時呈熊市行情,而在其他時候呈牛市行情,預期股票會出現積極的波動。你還可以看到,移動均線的交叉表示著股票趨勢的變化。我們將這些交叉看作交易訊號或指示器,表示金融證券正在改變趨勢,我們可能從中獲取利潤。

下週我將釋出第二部分的文章,介紹如何基於移動均線設計並測試一個交易策略。

更正:本篇文章的早期版本提到過演算法交易是高頻交易的同義詞。正如評論所指出的,實際情況並不是這樣;演算法也能用於處理非高頻率的交易。儘管高頻交易在演算法交易中佔很大比例,但二者並不相等。

文章原標題《An Introduction to Stock Market Data Analysis with Python (Part 1)》,作者:Curtis Miller,譯者:6816816151