Python量化交易-回測(cè)交易策略

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

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

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

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

為了提升運(yùn)行效率赦拘,需要將證券數(shù)據(jù)保存到本地?cái)?shù)據(jù)庫(kù)躺同,這里我們選擇的數(shù)據(jù)庫(kù)是MongoDB蹋艺,安裝過程在此不再贅述捎谨,參照http://www.runoob.com/mongodb/mongodb-window-install.html即可涛救,比較簡(jiǎn)單州叠。

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

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

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ù)斟或,這種方式簡(jiǎn)單便捷萝挤,數(shù)據(jù)的準(zhǔn)確性有保障怜珍;第二種是自己編寫數(shù)據(jù)爬蟲獲取數(shù)據(jù),這種方式會(huì)相對(duì)麻煩一點(diǎn)今豆。本文采用的是第一種方式呆躲。使用的數(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ù)庫(kù):

# 解析數(shù)據(jù)并保存到數(shù)據(jù)庫(kù)
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ù)庫(kù)

2.編寫交易邏輯

為了便于描述肆氓,本文選擇了一個(gè)較為簡(jiǎn)單的交易邏輯谢揪。以周為交易周期拨扶,每周一開盤前分析各股的周macd數(shù)據(jù),滿足交易條件則以開盤價(jià)買入并持有一周缩举,再以當(dāng)周五的收盤價(jià)賣出匹颤。這個(gè)交易邏輯比較簡(jiǎn)單且實(shí)操性強(qiáng)印蓖,回測(cè)的結(jié)果也有可圈可點(diǎn)之處(回測(cè)結(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

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

3.模擬交易操作

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

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

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

# 生成時(shí)間軸
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的所有時(shí)間點(diǎn)

生成好時(shí)間軸后炒俱,使用for循環(huán)遍歷時(shí)間軸(模擬時(shí)間推進(jìn)权悟,并且過濾掉周末和節(jié)假日)僵芹,按照之前設(shè)定的交易策略小槐,我們?cè)诿恐芤缓椭芪暹M(jìn)行買入賣出操作即可。由于該策略不涉及加減倉(cāng)件豌,故我們對(duì)交易過程進(jìn)行了簡(jiǎn)化茧彤,通過直接計(jì)算得出每周的收益疆栏。
對(duì)于更為復(fù)雜的交易策略壁顶,建議開發(fā)者分別實(shí)現(xiàn)開倉(cāng)若专、平倉(cāng)和加減倉(cāng)等各種操作:

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

        # 計(jì)算本次操作的收益
        if result_list:
            capital = capital_base / len(result_list)  # 對(duì)當(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)計(jì)結(jié)果和繪圖

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

net_rate = (history_capital[-1] - history_capital[0]) / history_capital[0]  # 計(jì)算回測(cè)結(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()

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

2014年全年收益

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

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末带兜,一起剝皮案震驚了整個(gè)濱河市刚照,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌啊楚,老刑警劉巖恭理,帶你破解...
    沈念sama閱讀 222,590評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件颜价,死亡現(xiàn)場(chǎng)離奇詭異拍嵌,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)横辆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,157評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來划纽,“玉大人勇劣,你說我怎么就攤上這事比默∶溃” “怎么了?”我有些...
    開封第一講書人閱讀 169,301評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵伊佃,是天一觀的道長(zhǎng)航揉。 經(jīng)常有香客問我金刁,道長(zhǎng)胀葱,這世上最難降的妖魔是什么抵屿? 我笑而不...
    開封第一講書人閱讀 60,078評(píng)論 1 300
  • 正文 為了忘掉前任轧葛,我火速辦了婚禮搂抒,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘尿扯。我一直安慰自己求晶,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,082評(píng)論 6 398
  • 文/花漫 我一把揭開白布衷笋。 她就那樣靜靜地躺著芳杏,像睡著了一般。 火紅的嫁衣襯著肌膚如雪辟宗。 梳的紋絲不亂的頭發(fā)上爵赵,一...
    開封第一講書人閱讀 52,682評(píng)論 1 312
  • 那天,我揣著相機(jī)與錄音泊脐,去河邊找鬼空幻。 笑死秕铛,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的镜遣。 我是一名探鬼主播,決...
    沈念sama閱讀 41,155評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼赤拒,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼这敬!你這毒婦竟也來了始衅?” 一聲冷哼從身側(cè)響起蝙茶,我...
    開封第一講書人閱讀 40,098評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤吮廉,失蹤者是張志新(化名)和其女友劉穎轴脐,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體溯捆,經(jīng)...
    沈念sama閱讀 46,638評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡劳跃,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,701評(píng)論 3 342
  • 正文 我和宋清朗相戀三年杉武,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了倘要。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,852評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡缰趋,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出仔涩,到底是詐尸還是另有隱情霞揉,我是刑警寧澤,帶...
    沈念sama閱讀 36,520評(píng)論 5 351
  • 正文 年R本政府宣布蚂会,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,181評(píng)論 3 335
  • 文/蒙蒙 一贴届、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸嗅虏。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,674評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽朋鞍。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,788評(píng)論 1 274
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,279評(píng)論 3 379
  • 正文 我出身青樓挑社,卻偏偏與公主長(zhǎng)得像吼肥,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,851評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容