backtest.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379
  1. '''
  2. 基于深度信息的异步事件触发回测框架
  3. 作者 千千量化
  4. '''
  5. import os
  6. import sys
  7. import asyncio
  8. import json, ujson
  9. import requests
  10. import hmac
  11. import base64
  12. import zlib
  13. import datetime
  14. import time
  15. import strategy as strategy
  16. import traceback
  17. import joblib
  18. import traceback
  19. import configparser
  20. import signal
  21. import random
  22. import utils
  23. import model
  24. import time
  25. import copy
  26. import random
  27. def timeit(func):
  28. def wrapper(*args, **kwargs):
  29. nowTime = time.time()
  30. func(*args, **kwargs)
  31. spend_time = time.time() - nowTime
  32. spend_time = round(spend_time * 100, 5)
  33. print(f'{func.__name__} 耗时 {spend_time} ms')
  34. return wrapper
  35. class Backtest:
  36. '''
  37. 回测框架
  38. '''
  39. def __init__(self, strategy:strategy.Strategy, data=None, is_plot=0):
  40. '''
  41. 初始化实盘类
  42. '''
  43. # 获取策略对象
  44. self.strategy = strategy
  45. # 历史数据
  46. self.data = data
  47. # 交易品种
  48. self.symbol = 'backtest_symbol'
  49. # 虚拟挂单
  50. self.localOrders = dict()
  51. # 虚拟仓位
  52. self.pos = model.Position()
  53. # 中间价
  54. self.mp = None
  55. # 行情数据
  56. self.reference_price = []
  57. self.last_marketdata = []
  58. # 费率 maker taker
  59. self.fee = model.BacktestFee("v9")
  60. # 是否画图
  61. self._is_plot = is_plot
  62. self.long_hold = []
  63. self.short_hold = []
  64. self.index = []
  65. # 虚拟成交信息
  66. self.trade_num = 0
  67. # 记录持仓时间
  68. self.kd_time = None
  69. self.kk_time = None
  70. self.hold_times = [0.0]
  71. # 权益
  72. self.start_cash = 1000000.0
  73. self.equity = self.start_cash
  74. self.equity_real = self.start_cash
  75. self.equity_high = self.start_cash
  76. self.balance = [self.start_cash]
  77. self.strategy.start_cash = self.start_cash
  78. self.strategy.local_start_time = 0.0
  79. # 成交时间轴
  80. self.priceMotion = []
  81. self.kd_trade = []
  82. self.kk_trade = []
  83. self.pd_trade = []
  84. self.pk_trade = []
  85. # 挂单时间轴
  86. self.localOrders_timeseries = []
  87. # min max fill
  88. self.fill_price = []
  89. # 上次信号
  90. self.signal_series = dict()
  91. # 回测延迟 默认值
  92. self.backtest_delay = utils.BACKTEST_DELAY
  93. # 回测时间
  94. self.backtest_time = time.time()
  95. # 平均持仓时间
  96. self.avg_hold_time = 0.0
  97. # 最近一次处理的信号时间戳
  98. self.last_signal_time = 0.0
  99. def get_available_close_amt(self):
  100. a_pd, a_pk = self.pos.longPos, self.pos.shortPos
  101. for i in self.localOrders:
  102. if self.localOrders[i]["side"] == 'pd':
  103. a_pd -= self.localOrders[i]["amount"]
  104. if self.localOrders[i]["side"] == 'pk':
  105. a_pk -= self.localOrders[i]["amount"]
  106. return a_pd, a_pk
  107. # @timeit
  108. # 处理策略信号
  109. def handle_signal(self, signals):
  110. '''
  111. 当信号>1tick 且时间超过n ms 才允许进行处理
  112. '''
  113. keys = list(signals.keys())
  114. for signal_time in keys:
  115. # 只允许处理上一tick时间戳之前的信号
  116. if signal_time < self.last_signal_time:
  117. pass
  118. else:
  119. continue
  120. if self.backtest_time - signal_time >= self.backtest_delay:
  121. signal = signals[signal_time]
  122. if signal == None:
  123. return
  124. for i in signal:
  125. # 撤销虚拟订单
  126. if 'Cancel' in i:
  127. if signal[i][0] in self.localOrders:
  128. del(self.localOrders[signal[i][0]])
  129. # 执行虚拟下单
  130. elif 'Limits_open' in i:
  131. for j in signal[i]:
  132. order_event = dict()
  133. order_event['symbol'] = self.symbol
  134. order_event['status'] = "NEW"
  135. order_event['amount'] = float(j[0])
  136. order_event['side'] = j[1]
  137. order_event['price'] = float(j[2])
  138. order_event['filled_price'] = 0
  139. order_event['filled'] = 0
  140. order_event['client_id'] = str(random.randint(1,99999))
  141. order_event['order_id'] = str(random.randint(1,99999))
  142. order_event['localtime'] = signal_time
  143. order_event['createtime'] = signal_time
  144. self.localOrders[order_event['client_id']] = order_event
  145. elif 'Limits_close' in i:
  146. a_pd, a_pk = self.get_available_close_amt()
  147. for j in signal[i]:
  148. if j[1] == "pd":
  149. if j[0] > a_pd:
  150. break
  151. elif j[1] == "pk":
  152. if j[0] > a_pk:
  153. break
  154. order_event = dict()
  155. order_event['symbol'] = self.symbol
  156. order_event['status'] = "NEW"
  157. order_event['amount'] = float(j[0])
  158. order_event['side'] = j[1]
  159. order_event['price'] = float(j[2])
  160. order_event['filled_price'] = 0
  161. order_event['filled'] = 0
  162. order_event['client_id'] = str(random.randint(1,99999))
  163. order_event['order_id'] = str(random.randint(1,99999))
  164. order_event['localtime'] = signal_time
  165. order_event['createtime'] = signal_time
  166. self.localOrders[order_event['client_id']] = order_event
  167. del(signals[signal_time])
  168. # @timeit
  169. def matching(self, data):
  170. max_fill = 0 if data[utils.MAX_FILL_INDEX] == 0 else data[utils.MAX_FILL_INDEX] # 最高成交价
  171. min_fill = 9999999999 if data[utils.MIN_FILL_INDEX] == 0 else data[utils.MIN_FILL_INDEX] # 最低成交价
  172. # 本tick产生的pnl
  173. pnl = 0.0
  174. # 保存成交时间轴信息
  175. if self._is_plot:
  176. self.kd_trade.append(0)
  177. self.kk_trade.append(0)
  178. self.pd_trade.append(0)
  179. self.pk_trade.append(0)
  180. # 检查订单是否符合撮合条件
  181. max_kd = 0
  182. max_pk = 0
  183. min_kk = 9999999999
  184. min_pd = 9999999999
  185. # 本ticker
  186. now_bp = data[utils.BP_INDEX]
  187. now_ap = data[utils.AP_INDEX]
  188. self.mp = (now_ap + now_bp)*0.5
  189. match_bp = now_bp
  190. match_ap = now_ap
  191. if self.last_marketdata != []:
  192. # 前ticker
  193. bp = self.last_marketdata[utils.BP_INDEX]
  194. ap = self.last_marketdata[utils.AP_INDEX]
  195. localOrdersCid = list(self.localOrders)
  196. # 初始化成交标记
  197. filled_flag = 0
  198. filled_fee = 0.0
  199. filled_price = 0.0
  200. # 检查所有挂单是否被成交
  201. for cid in localOrdersCid:
  202. # 为每个订单重置成交标记
  203. filled_flag = 0
  204. filled_fee = 0.0
  205. filled_price = 0.0
  206. # 获取订单
  207. i = self.localOrders[cid]
  208. # 买单
  209. if i["side"] == "kd":
  210. if self._is_plot: max_kd = max(max_kd,i['price'])
  211. # 判断吃单成交还是挂单成交
  212. if i['price'] > ap: # 吃单成交
  213. filled_flag = 1
  214. filled_fee = self.fee.taker
  215. filled_price = ap
  216. elif i["price"] > match_ap: # 挂单成交
  217. filled_flag = 1
  218. filled_fee = self.fee.maker
  219. filled_price = i["price"]
  220. elif i["price"] > min_fill: # 一定概率被挂单成交
  221. filled_flag = 1
  222. filled_fee = self.fee.maker
  223. filled_price = i["price"]
  224. if filled_flag:
  225. del(self.localOrders[cid])
  226. self.trade_num += 1
  227. value = i['amount'] * filled_price
  228. self.pos.longAvg = (self.pos.longPos * self.pos.longAvg + value)/(self.pos.longPos + i['amount'])
  229. self.pos.longPos += i['amount']
  230. pnl -= filled_fee * value
  231. self.kd_time = self.strategy.local_time
  232. if self._is_plot:
  233. self.kd_trade[-1] = filled_price
  234. elif i["side"] == "pk":
  235. if self._is_plot: max_pk = max(max_pk,i['price'])
  236. # 判断吃单成交还是挂单成交
  237. if i["price"] > ap: # 吃单成交
  238. filled_flag = 1
  239. filled_fee = self.fee.taker
  240. filled_price = ap
  241. elif i["price"] > match_ap: # 挂单成交
  242. filled_flag = 1
  243. filled_fee = self.fee.maker
  244. filled_price = i["price"]
  245. elif i["price"] > min_fill: # 一定概率被挂单成交
  246. filled_flag = 1
  247. filled_fee = self.fee.maker
  248. filled_price = i["price"]
  249. if filled_flag:
  250. del(self.localOrders[cid])
  251. self.trade_num += 1
  252. pct = (self.pos.shortAvg - filled_price)/self.pos.shortAvg
  253. value = i['amount'] * filled_price
  254. pnl += pct * value - filled_fee * value
  255. self.pos.shortPos -= i['amount']
  256. if self._is_plot:
  257. self.pk_trade[-1] = filled_price
  258. self.hold_times.append(self.strategy.local_time - self.kk_time)
  259. else:
  260. if self.avg_hold_time == 0.0:
  261. self.avg_hold_time = self.strategy.local_time - self.kk_time
  262. else:
  263. self.avg_hold_time = self.avg_hold_time * 0.9 + (self.strategy.local_time - self.kk_time) * 0.1
  264. # 卖单
  265. elif i["side"] == "kk":
  266. if self._is_plot: min_kk = min(min_kk,i['price'])
  267. # 判断吃单成交还是挂单成交
  268. if i["price"] < bp: # 吃单成交
  269. filled_flag = 1
  270. filled_fee = self.fee.taker
  271. filled_price = i["price"]
  272. elif i["price"] < match_bp: # 挂单成交
  273. filled_flag = 1
  274. filled_fee = self.fee.maker
  275. filled_price = i["price"]
  276. elif i["price"] < max_fill: # 一定概率挂单成交
  277. filled_flag = 1
  278. filled_fee = self.fee.maker
  279. filled_price = i["price"]
  280. if filled_flag:
  281. del(self.localOrders[cid])
  282. self.trade_num += 1
  283. value = filled_price * i['amount']
  284. self.pos.shortAvg = (self.pos.shortAvg*self.pos.shortPos + value)/(self.pos.shortPos + i['amount'])
  285. self.pos.shortPos += i['amount']
  286. pnl -= filled_fee * value
  287. self.kk_time = self.strategy.local_time
  288. if self._is_plot:
  289. self.kk_trade[-1] = filled_price
  290. elif i["side"] == "pd":
  291. if self._is_plot: min_pd = min(min_pd,i['price'])
  292. # 判断吃单成交还是挂单成交
  293. if i["price"] < bp: # 吃单成交
  294. filled_flag = 1
  295. filled_fee = self.fee.taker
  296. filled_price = i["price"]
  297. elif i["price"] < match_bp: # 挂单成交
  298. filled_flag = 1
  299. filled_fee = self.fee.maker
  300. filled_price = i["price"]
  301. elif i["price"] < max_fill: # 一定概率挂单成交
  302. filled_flag = 1
  303. filled_fee = self.fee.maker
  304. filled_price = i["price"]
  305. if filled_flag:
  306. del(self.localOrders[cid])
  307. self.trade_num += 1
  308. pct = (filled_price - self.pos.longAvg)/self.pos.longAvg
  309. value = i['amount'] * i['price']
  310. pnl += pct * value - filled_fee * value
  311. self.pos.longPos -= i['amount']
  312. if self._is_plot:
  313. self.pd_trade[-1] = filled_price
  314. self.hold_times.append(self.strategy.local_time - self.kd_time)
  315. else:
  316. if self.avg_hold_time == 0.0:
  317. self.avg_hold_time = self.strategy.local_time - self.kd_time
  318. else:
  319. self.avg_hold_time = self.avg_hold_time * 0.9 + (self.strategy.local_time - self.kd_time) * 0.1
  320. # 当前浮动盈亏
  321. unrealized_pnl = 0
  322. if self.pos.longPos > 0:
  323. unrealized_pnl += (self.mp - self.pos.longAvg)/self.pos.longAvg * self.pos.longPos * self.mp
  324. if self.pos.shortPos > 0:
  325. unrealized_pnl += (self.pos.shortAvg - self.mp)/self.pos.shortAvg * self.pos.shortPos * self.mp
  326. # 如果需要画图
  327. if self._is_plot:
  328. # 保存挂单信息时间轴
  329. max_kd = 0 if max_kd == 0 else max_kd
  330. max_pk = 0 if max_pk == 0 else max_pk
  331. min_kk = 0 if min_kk == 9999999999 else min_kk
  332. min_pd = 0 if min_pd == 9999999999 else min_pd
  333. self.localOrders_timeseries.append([max_kd,max_pk,min_kk,min_pd])
  334. self.priceMotion.append(self.strategy.mp)
  335. # 参考价格
  336. self.reference_price.append(self.strategy.ref_price)
  337. # 成交情况
  338. self.fill_price.append([max_fill,min_fill])
  339. # 持仓情况
  340. self.long_hold.append(self.pos.longPos)
  341. self.short_hold.append(self.pos.shortPos)
  342. # 净值
  343. self.index.append(self.strategy.equity)
  344. # 记录浮动净值
  345. self.balance.append(self.equity + pnl + unrealized_pnl)
  346. # 记录净值 包含未实现盈亏
  347. self.equity_real = self.equity + pnl + unrealized_pnl
  348. # 记录净值 已实现盈亏
  349. self.equity = self.equity + pnl
  350. # 记录最高净值记录
  351. self.equity_high = max(self.equity_high, self.equity)
  352. # 结束本次回测
  353. # 更新上一次的行情记录
  354. self.last_marketdata = data
  355. # @timeit
  356. def run_by_tick(self, tradeMsg:model.TraderMsg):
  357. # 更新策略本地时间
  358. self.strategy.local_time = self.backtest_time
  359. # 执行信号
  360. self.handle_signal(self.signal_series)
  361. # 用最新行情进行撮合
  362. self.matching(tradeMsg.market)
  363. # 更新账户信息
  364. tradeMsg.cash = self.equity
  365. tradeMsg.orders = self.localOrders
  366. tradeMsg.position = self.pos
  367. # 产生本tick信号
  368. signal = self.strategy.onTime(tradeMsg)
  369. self.signal_series[self.backtest_time] = signal
  370. self.last_signal_time = self.backtest_time