backtest.py 16 KB

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