這篇文章主要介紹如何使用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)位的條件下得出的醉旦,如果取消該限制桨啃,收益率將更高):
到此為止匈棘,我們就完成了對(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