Python量化交易-回測交易策略

這篇文章主要介紹如何使用Python對一些簡單的交易策略進(jìn)行回測,對這塊比較感興趣的朋友可以看一看。

1.獲取證券數(shù)據(jù)

本文以A股市場為例,先獲取A股近10年的數(shù)據(jù)并保存到數(shù)據(jù)庫。

1.1.安裝數(shù)據(jù)庫(MongoDB)

為了提升運(yùn)行效率,需要將證券數(shù)據(jù)保存到本地數(shù)據(jù)庫,這里我們選擇的數(shù)據(jù)庫是MongoDB,安裝過程在此不再贅述,參照http://www.runoob.com/mongodb/mongodb-window-install.html即可,比較簡單。

1.2.編寫數(shù)據(jù)庫操作類

安裝完數(shù)據(jù)庫,我們先編寫一個工具類來管理數(shù)據(jù)庫的增刪改查等操作:

class DBManager:
    def __init__(self, table_name):
        self.client = MongoClient("127.0.0.1", 27017)
        self.db = self.client["my_database"]
        self.table = self.db[table_name]

    def clsoe_db(self):
        self.client.close()

    # 獲取股票代碼列表(sz格式)
    def get_code_list(self):
        return self.table.find({}, {"ticker": 1}, no_cursor_timeout=True)

    # 查詢多條數(shù)據(jù)
    def find_by_key(self, request=None):
        if request is None:
            request = {}
        return self.table.find(request)

    # 查詢單條數(shù)據(jù)
    def find_one_by_key(self, request=None):
        if request is None:
            request = {}
        return self.table.find_one(request)

    # 添加單條數(shù)據(jù)
    def add_one(self, post, created_time=datetime.datetime.now()):
        # 添加一條數(shù)據(jù)
        post['created_time'] = created_time
        return self.table.insert_one(post)

1.3.獲取數(shù)據(jù)

獲取證券數(shù)據(jù)的途徑主要有兩種,第一種是去網(wǎng)上找現(xiàn)成的數(shù)據(jù)接口,通過調(diào)用接口獲取數(shù)據(jù),這種方式簡單便捷,數(shù)據(jù)的準(zhǔn)確性有保障;第二種是自己編寫數(shù)據(jù)爬蟲獲取數(shù)據(jù),這種方式會相對麻煩一點。本文采用的是第一種方式。使用的數(shù)據(jù)接口是http://www.baostock.com/。

調(diào)用數(shù)據(jù)接口:

bs.login()  # 初始化baostock
code_list = dm.get_code_list()  # 獲取股票代碼列表
for item in code_list:
    max_try = 8  # 失敗重連的最大次數(shù)
    ticker = item["ticker"]
    for tries in range(max_try):
        rs = bs.query_history_k_data(ticker, "date,code,open,high,low,close,volume,amount,adjustflag,turn,"
                                                 "pctChg", frequency="w", adjustflag="3")
        if rs.error_code == '0':
            parse_pager(rs, ticker)  # 解析數(shù)據(jù)
            break
        elif tries < (max_try - 1):
            sleep(2)
            continue
        else:
            log.logger.error("加載數(shù)據(jù)失敗:" + str(ticker))
log.logger.info("加載數(shù)據(jù)完成")
bs.logout()

解析數(shù)據(jù)并保存到數(shù)據(jù)庫:

# 解析數(shù)據(jù)并保存到數(shù)據(jù)庫
def parse_pager(content, ticker):
    while content.next():
        item_row = content.get_row_data()
        __dict = {
            "date": item_row[0],
            "code": item_row[1],
            "open": item_row[2],
            "high": item_row[3],
            "low": item_row[4],
            "close": item_row[5],
            "volume": item_row[6],
            "amount": item_row[7],
            "adjustflag": item_row[8],
            "turn": item_row[9],
            "pctChg": item_row[10]
        }
        dm.add_tk_item(ticker, __dict)  # 將數(shù)據(jù)保存到數(shù)據(jù)庫

2.編寫交易邏輯

為了便于描述,本文選擇了一個較為簡單的交易邏輯。以周為交易周期,每周一開盤前分析各股的周macd數(shù)據(jù),滿足交易條件則以開盤價買入并持有一周,再以當(dāng)周五的收盤價賣出。這個交易邏輯比較簡單且實操性強(qiáng),回測的結(jié)果也有可圈可點之處(回測結(jié)果見文末)。交易邏輯的核心代碼如下:

if wmacd_list[-1] > 0 >= wmacd_list[-2]:  # 判斷某支股票是否符合當(dāng)前交易邏輯
    if np.mean(volume_list[-5:-1]) < volume_list[-1]:
        if 0.1 >= diff_list[-1] >= 0:
        data = [x for x in dm_tk.find_one_by_key({"ticker": item["ticker"]})["data_list"] if x["date"] == cur_date][0]
        result_list.append(data)
def get_w_macd(price_list): # 生成每支股票的周macd數(shù)據(jù)
    ema_12_list = list()
    for index in range(len(price_list)):
        if index == 0:
            ema_12_list.append(price_list[0])
        else:
            ema_12_list.append(round(ema_12_list[index - 1] * 11 / 13 + price_list[index] * 2 / 13, 4))
    ema_26_list = list()
    for index in range(len(price_list)):
        if index == 0:
            ema_26_list.append(price_list[0])
        else:
            ema_26_list.append(round(ema_26_list[index - 1] * 25 / 27 + price_list[index] * 2 / 27, 4))
    diff_list = list()
    for index in range(len(ema_12_list)):
        diff = ema_12_list[index] - ema_26_list[index]
        diff_list.append(diff)
    dea_list = list()
    for index in range(len(diff_list)):
        if index == 0:
            dea_list.append(diff_list[0])
        else:
            dea_list.append(round(dea_list[index - 1] * 0.8 + diff_list[index] * 0.2, 4))
    wmacd_list = list()
    for index in range(len(dea_list)):
        bar = (diff_list[index] - dea_list[index]) * 3
        wmacd_list.append(bar)
    return wmacd_list, diff_list, dea_list

以上只是該交易策略的部分代碼,讀者不需要看懂其中的邏輯,實際操作過程中應(yīng)該使用自己的交易策略。

3.模擬交易操作

編寫好交易策略后,我們開始對交易策略進(jìn)行回測。首先我們設(shè)定一些初始數(shù)據(jù),這些數(shù)據(jù)是我們?nèi)粘=灰字谐R姷降模热绯跏假Y金總額、當(dāng)前可用資金、最大倉位等等,由于該交易策略比較簡單,所以我們只需要設(shè)定起始資金就可以了:

capital_base = 1000000  # 起始資金設(shè)定為100萬
history_capital = list()  # 用于記錄交易結(jié)果

接著我們創(chuàng)建一條時間軸,所有的交易操作都將跟隨時間軸進(jìn)行:

# 生成時間軸
def date_range(start, end, step=1, format="%Y-%m-%d"):
    strptime, strftime = datetime.datetime.strptime, datetime.datetime.strftime
    days = (strptime(end, format) - strptime(start, format)).days + 1
    return [strftime(strptime(start, format) + datetime.timedelta(i), format) for i in range(0, days, step)]
date_list = date_range("2016-01-01", "2016-12-31")  # 生成2016-01-01至2016-12-31的所有時間點

生成好時間軸后,使用for循環(huán)遍歷時間軸(模擬時間推進(jìn),并且過濾掉周末和節(jié)假日),按照之前設(shè)定的交易策略,我們在每周一和周五進(jìn)行買入賣出操作即可。由于該策略不涉及加減倉,故我們對交易過程進(jìn)行了簡化,通過直接計算得出每周的收益。
對于更為復(fù)雜的交易策略,建議開發(fā)者分別實現(xiàn)開倉、平倉和加減倉等各種操作:

for cur_date in date_list:
    if datetime.datetime.strptime(cur_date, "%Y-%m-%d").weekday() == 4:  # 判斷當(dāng)前日期是否需要操作    
        result_list = list()  # 用于記錄當(dāng)前時間符合交易條件的股票代碼
        for item in code_list:  # 遍歷各支股票,篩選出符合交易條件的股票
            -執(zhí)行交易邏輯-
            if 符合交易條件:
                result_list.append(data)

        # 計算本次操作的收益
        if result_list:
            capital = capital_base / len(result_list)  # 對當(dāng)前資金進(jìn)行均分
            temp_capital = 0
            for item in result_list:
                close_price = float(item["close"])
                open_price = float(item["open"])
                max_price = float(item["high"])
                profit = (close_price - open_price) / open_price
                temp_capital += (capital * (1 + profit))
            capital_base = temp_capital 
        history_capital.append(capital_base)  # 記錄本次操作后剩余的資金

4.統(tǒng)計結(jié)果和繪圖

模擬交易完成后我們來對結(jié)果進(jìn)行統(tǒng)計,由于我們已經(jīng)將交易的過程記錄在history_capital中,此時我們可以輕松的計算出收益率:

net_rate = (history_capital[-1] - history_capital[0]) / history_capital[0]  # 計算回測結(jié)果
log.logger.info("total_profit:" + str(round(net_rate * 100, 2)) + "%")

為了讓交易的結(jié)果更加直觀,我們還可以將其繪制成折線圖,這里使用matplotlib進(jìn)行繪圖:

plt.subplot(111)
lable_x = np.arange(len(history_capital))
plt.plot(lable_x, history_capital, color="r", linewidth=1.0, linestyle="-")
plt.xlim(lable_x.min(), lable_x.max() * 1.1)
plt.ylim(min(history_capital) * 0.9, max(history_capital) * 1.1)
plt.grid(True)
plt.show()

回測結(jié)果展示(該收益曲線是在限制最大倉位的條件下得出的,如果取消該限制,收益率將更高):

2014年全年收益

2015年全年收益
2016年全年收益
2017年全年收益

到此為止,我們就完成了對某個交易策略進(jìn)行回測的全部流程,從回測結(jié)果中可以看出,該交易策略在2014-2017這4年中有著不錯的表現(xiàn)。按照策略的規(guī)則,交易者只需要在每周一開盤前運(yùn)行策略,開盤后買進(jìn)策略推薦的股票,最后在周五收盤前賣掉即可,是易于實盤操作的。
讀者也可以自由變換交易邏輯來獲取不同的結(jié)果,通過對回測結(jié)果進(jìn)行分析,可以對我們?nèi)粘5慕灰讕硪恍椭?br> 與我交流:1003882179@qq.com

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容