strategy.py 46 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080
  1. import time
  2. import traceback, utils
  3. import model
  4. import logging, logging.handlers
  5. from decimal import Decimal
  6. from decimal import ROUND_HALF_UP, ROUND_FLOOR
  7. class Strategy:
  8. '''
  9. 策略逻辑
  10. '''
  11. def __init__(self, params: model.Config, is_print=0):
  12. self.params = params
  13. self.exchange = self.params.exchange
  14. self.broker_id = params.broker_id
  15. self.logger = self.get_logger()
  16. #### 实盘
  17. ex = self.params.exchange
  18. pair = self.params.pair
  19. self.trade_name = ex + '@' + pair
  20. #### 参考
  21. refex = self.params.refexchange
  22. refpair = self.params.refpair
  23. if len(refex) != len(refpair):
  24. print("参考盘口数不等于参考品种数 退出")
  25. return
  26. self.ref_num = len(refex)
  27. self.ref_name = []
  28. for i in range(self.ref_num):
  29. name = refex[i] + '@' + refpair[i]
  30. self.ref_name.append(name)
  31. #### maker mode
  32. self.maker_mode = 'free'
  33. ####
  34. self._print_time = 0
  35. self.local_orders = dict()
  36. self.pos = model.Position()
  37. self.long_hold_value = 0.0
  38. self.short_hold_value = 0.0
  39. self.equity = 0.0
  40. self.coin = 0.0
  41. self.cash = 0.0
  42. self.start_equity = 0.0
  43. self.start_coin = 0.0
  44. self.start_cash = 0.0
  45. self.max_equity = 0.0
  46. self.local_profit = 0.0
  47. self.total_amount = 0.0
  48. self.ready = 0
  49. self._is_print = is_print
  50. self._min_amount_value = 5.0 # 最小下单额 防止下单失败
  51. self._max_amount_value = 10000.0 # 最大下单额 防止下单过重 平不掉就很悲剧
  52. self.local_time = time.time()
  53. self.local_start_time = time.time()
  54. self.interval = float(self.params.interval)
  55. self.mp = None
  56. self.bp = None
  57. self.ap = None
  58. self.ref_price = 0.0
  59. self.ref_bp = 0.0
  60. self.ref_ap = 0.0
  61. self.stepSize = 1e-10
  62. self.tickSize = 1e-10
  63. self.maxPos = 0.0
  64. self.profit = 0.0
  65. self.daily_return = 0.0
  66. ####
  67. self.mp_ewma = None
  68. self.adjust_leverrate = 1.0
  69. #### 持仓偏差
  70. self.long_pos_bias = None
  71. self.short_pos_bias = None
  72. self.long_hold_rate = 0.0
  73. self.short_hold_rate = 0.0
  74. #### 时间相关参数
  75. self.leverrate = float(self.params.leverrate) # 最大仓位
  76. if "spot" in self.exchange:self.leverrate = min(self.leverrate, 1.0)
  77. self._print_time = time.time()
  78. self._start_time = time.time()
  79. self.request_num = 0 # 记录请求次数
  80. self.request_order_num = 0 # 记录下单次数
  81. self._print_interval = 5 # 打印信息时间间隔
  82. #### 距离范围
  83. self.open_dist = None
  84. self.close_dist = None
  85. #### 查单频率
  86. self._check_local_orders_time = time.time()
  87. self._check_local_orders_interval = 10.0
  88. #### 内部限頻
  89. try:
  90. self.place_order_limit = float(self.params.place_order_limit)
  91. except:
  92. self.place_order_limit = 0
  93. self.request_limit_check_time = time.time()
  94. self.request_limit_check_interval = 10.0
  95. self.limit_requests_num = utils.get_limit_requests_num_per_second(
  96. self.params.exchange, self.place_order_limit) * self.request_limit_check_interval
  97. self.limit_order_requests_num = utils.get_limit_order_requests_num_per_second(
  98. self.params.exchange, self.place_order_limit) * self.request_limit_check_interval
  99. #### 网络请求频率
  100. self._req_num_per_window = 0
  101. # 开仓下单间隔 均匀下单机会
  102. self.post_open_time = time.time()
  103. self.post_open_interval = 1/utils.get_limit_order_requests_num_per_second(self.params.exchange)
  104. #### 策略参数
  105. # 距离类参数
  106. self.trade_close_dist = 0.00001 # 基础挂单距离
  107. self.trade_open_dist = 0.01 # 基础挂单距离
  108. #### 时间类参数
  109. # 撤单限頻队列 强制等待 防止频繁发起撤单
  110. self.in_cancel = dict()
  111. self.cancel_wait_interval = 0.2
  112. # 查单限頻队列 强制等待
  113. self.in_check = dict()
  114. self.check_wait_interval = 10.0
  115. # ref index
  116. self.ref_index = 0
  117. # predict
  118. self.predict = 0.0
  119. self.predict_alpha = 0.0
  120. # post side
  121. self.post_side = 0
  122. # trade vol
  123. self.trade_vol_24h = 0.0
  124. # grid num
  125. self.grid = float(self.params.grid)
  126. def get_logger(self):
  127. logger = logging.getLogger(__name__)
  128. logger.setLevel(logging.DEBUG)
  129. # log to txt
  130. formatter = logging.Formatter('[%(asctime)s] - %(levelname)s - %(message)s')
  131. handler = logging.handlers.RotatingFileHandler(f"log.log",maxBytes=1024*1024)
  132. handler.setLevel(logging.DEBUG)
  133. handler.setFormatter(formatter)
  134. logger.addHandler(handler)
  135. return logger
  136. # @utils.timeit
  137. def _update_data(self, data:model.TraderMsg):
  138. '''更新本地数据'''
  139. try:
  140. # 更新信息
  141. # orders
  142. self.local_orders.clear()
  143. self.local_orders.update(data.orders)
  144. # position
  145. if self.pos.longPos != data.position.longPos:
  146. self.pos.longPos = data.position.longPos
  147. self.pos.longAvg = data.position.longAvg
  148. if self.pos.shortPos != data.position.shortPos:
  149. self.pos.shortPos = data.position.shortPos
  150. self.pos.shortAvg = data.position.shortAvg
  151. # bp ap
  152. self.bp = data.market[utils.BP_INDEX]
  153. self.ap = data.market[utils.AP_INDEX]
  154. # trade mp
  155. self.mp = (self.bp+self.ap)*0.5
  156. ########### 动态杠杆调节 ###########
  157. if self.mp_ewma == None:
  158. self.mp_ewma = self.mp
  159. else:
  160. self.mp_ewma = self.mp_ewma*0.999 + self.mp*0.001
  161. if self.mp > self.mp_ewma:
  162. # 增加杠杆
  163. self.adjust_leverrate = 1.0
  164. else:
  165. # 降低杠杆
  166. self.adjust_leverrate = 0.8
  167. ########### 当前持仓价值 ###########
  168. self.long_hold_value = self.pos.longPos * self.mp
  169. self.short_hold_value = self.pos.shortPos * self.mp
  170. ########### 现货 ###########
  171. if 'spot' in self.exchange:
  172. ### 计算总保证金情况
  173. self.max_long_value = self.start_cash * self.leverrate * self.adjust_leverrate
  174. self.max_short_value = self.start_coin * self.leverrate * self.adjust_leverrate * self.mp
  175. ########### 合约 ###########
  176. else:
  177. ### 计算总保证金情况
  178. self.max_long_value = self.equity * self.leverrate * self.adjust_leverrate
  179. self.max_short_value = self.max_long_value
  180. ###### maker mode ######
  181. if self.ref_name[self.ref_index] == self.trade_name:
  182. self.maker_mode = 'free'
  183. else:
  184. self.maker_mode = 'follow'
  185. ###### ref price ######
  186. if data.ref_price == None:
  187. self.ref_bp = self.bp
  188. self.ref_ap = self.ap
  189. self.ref_price = self.mp
  190. else:
  191. self.ref_bp = data.ref_price[self.ref_index][0]
  192. self.ref_ap = data.ref_price[self.ref_index][1]
  193. self.ref_price = (self.ref_bp+self.ref_ap)*0.5
  194. # spread
  195. self.predict = utils.clip(data.predict*self.predict_alpha, -self.trade_open_dist, self.trade_open_dist)
  196. # is base spread normal ? can take but can't move too far
  197. # if abs(self.ref_price - self.mp)/self.mp > self.trade_open_dist*3:
  198. # back to pure market making strategy
  199. # self.ref_price = self.mp
  200. # equity 当前账户可用cash和coin
  201. self.coin = data.coin
  202. self.cash = data.cash
  203. if self.mp:
  204. self.equity = data.cash + data.coin * self.mp
  205. # max equity
  206. if self.equity > self.max_equity:
  207. self.max_equity = self.equity
  208. self.total_amount = float(utils.fix_amount(self.equity * self.leverrate * self.adjust_leverrate / self.mp, self.stepSize))
  209. if self.total_amount == 0.0:
  210. if self._is_print:
  211. self.logger.error("总可开数量太少")
  212. # max pos
  213. maxPos = max([
  214. self.pos.longPos, self.pos.shortPos
  215. ]) * self.mp // (self.equity if self.equity > 0 else 99999999)
  216. if maxPos > self.maxPos:
  217. self.maxPos = maxPos
  218. return 1
  219. except:
  220. if self._is_print:
  221. self.logger.error(traceback.format_exc())
  222. return 0
  223. # @utils.timeit
  224. def _print_summary(self):
  225. '''
  226. 打印状态信息
  227. 耗时700us
  228. '''
  229. msg = '>>> '
  230. msg += '盘口 ' + self.exchange + ' '
  231. msg += '品种 ' + self.params.pair + ' '
  232. msg += '现价 ' + str(round(self.mp, 6)) + ' '
  233. msg += '定价 ' + str(round(self.ref_price, 6)) + ' '
  234. msg += '偏差 ' + str(round((self.ref_price-self.mp)/self.mp*100, 2)) + '% '
  235. msg += '净值 ' + str(round(self.equity, 3)) + ' '
  236. msg += 'Cash ' + str(round(self.cash, 3)) + ' '
  237. msg += 'Coin ' + str(round(self.coin*self.mp, 3)) + ' '
  238. msg += '推算利润 ' + str(self.local_profit) + ' '
  239. self.profit = round(
  240. (self.equity - self.start_equity) /
  241. self.start_equity * 100, 3) if self.start_equity > 0 else 0
  242. msg += '盈亏 ' + str(self.profit) + '% '
  243. msg += '多杠杆' + str(
  244. round(
  245. self.pos.longPos * self.mp /
  246. (self.equity if self.equity > 0 else 99999999), 3)) + ' '
  247. self.long_pos_bias = None
  248. if self.pos.longPos > 0.0:
  249. self.long_pos_bias = round(100 - 100 * self.pos.longAvg / self.mp, 2)
  250. msg += '浮盈' + str(self.long_pos_bias) + '% '
  251. else:
  252. msg += '浮盈 None '
  253. msg += '空杠杆' + str(
  254. round(
  255. self.pos.shortPos * self.mp /
  256. (self.equity if self.equity > 0 else 99999999), 3)) + ' '
  257. self.short_pos_bias = None
  258. if self.pos.shortPos > 0.0:
  259. self.short_pos_bias = round(100 * self.pos.shortAvg / self.mp - 100, 2)
  260. msg += '浮盈' + str(self.short_pos_bias) + '% '
  261. else:
  262. msg += '浮盈 None '
  263. msg += '杠杆' + str(self.leverrate) + ' 动态 ' + str(self.adjust_leverrate) + ' '
  264. msg += '最大' + str(self.maxPos) + ' '
  265. msg += '请求' + str(self._req_num_per_window) + ' 上限 ' + str(self.limit_order_requests_num) + '次/10s '
  266. run_time = time.time() - self._start_time
  267. self.daily_return = round(self.profit / 100 / run_time * 86400, 5)
  268. msg += '日化' + str(self.daily_return) + ' '
  269. msg += '当前参数 ' + \
  270. ' 开仓 ' + str(round(self.trade_open_dist,6)) + \
  271. ' 平仓 ' + str(round(self.trade_close_dist,6)) + \
  272. ' 方向 ' + str(self.post_side) + \
  273. ' 参考 ' + self.ref_name[self.ref_index] + \
  274. ' 模式 ' + self.maker_mode + \
  275. ' 预测 ' + str(round(self.predict,5)) + \
  276. ' 预估24H成交额 ' + str(self.trade_vol_24h) + 'W' + \
  277. ' 实时优化 ' + str(self.params.backtest)
  278. # 写入日志
  279. if self._is_print:
  280. self.logger.info(msg)
  281. #### 本地订单状态列表 ####
  282. o_num = len(self.local_orders)
  283. self.logger.info(f"挂单列表 共{o_num}单")
  284. for cid in self.local_orders:
  285. i = self.local_orders[cid]
  286. msg = i['symbol'] + ' ' + str(i['client_id']) + ' ' + i['side'] + ' 杠杆: ' + \
  287. str(round(i['amount'] * self.mp / (self.equity if self.equity > 0 else 99999999), 3)) + 'x 价值:' + \
  288. str(round(i['amount'] * self.mp,2)) + 'u' + ' 价格: ' + \
  289. str(i['price']) + ' 偏离:' + \
  290. str(round((i['price'] - self.mp) / self.mp * 100, 3)) + '%'
  291. if self._is_print:
  292. self.logger.info(msg)
  293. self.logger.info("撤单列表")
  294. if len(self.in_cancel) > 0:
  295. if self._is_print:
  296. self.logger.info(self.in_cancel)
  297. self.logger.info("查单列表")
  298. if len(self.in_check) > 0:
  299. if self._is_print:
  300. self.logger.info(self.in_check)
  301. # def fix_amount(self, amount):
  302. # '''修补数量向下取整'''
  303. # return float(Decimal(str(amount)).quantize(Decimal(str(self.stepSize)), ROUND_FLOOR))
  304. # def fix_price(self, price):
  305. # '''修补价格四舍五入'''
  306. # return float(Decimal(str(price)).quantize(Decimal(str(self.tickSize)), ROUND_HALF_UP))
  307. def _cancel_targit_side_orders(self, order_side=["kd","kk","pd","pk"]):
  308. '''清理指定类型挂单'''
  309. signals = dict()
  310. # 撤销指定类型挂单
  311. for cid in self.local_orders:
  312. i = self.local_orders[cid]
  313. if i["side"] in order_side:
  314. cid = i['client_id']
  315. oid = i['order_id']
  316. signals[f'Cancel{cid}'] = [cid, oid]
  317. return signals
  318. def _close_all(self):
  319. '''
  320. 清空所有挂单和仓位保持休眠状态
  321. '''
  322. signals = dict()
  323. # 撤销全部挂单
  324. pd_amount = 0.0
  325. pk_amount = 0.0
  326. for cid in self.local_orders:
  327. i = self.local_orders[cid]
  328. cid = i['client_id']
  329. oid = i['order_id']
  330. if i['side'] == 'pk':
  331. pk_amount += i['amount']
  332. elif i['side'] == 'pd':
  333. pd_amount += i['amount']
  334. signals[f'Cancel{cid}'] = [cid, oid]
  335. # 批量挂单
  336. signals['Limits_close'] = []
  337. need_close_long = self.pos.longPos - pd_amount
  338. need_close_short = self.pos.shortPos - pk_amount
  339. if "spot" in self.exchange:
  340. if need_close_long * self.mp > self._min_amount_value:
  341. amount = need_close_long
  342. price = utils.fix_price(self.mp, self.tickSize)
  343. amount = utils.fix_amount(amount, self.stepSize)
  344. signals['Limits_close'].append([amount, 'pd', price, utils.get_cid(self.broker_id)])
  345. if need_close_short * self.mp > self._min_amount_value:
  346. amount = need_close_short
  347. price = utils.fix_price(self.mp, self.tickSize)
  348. amount = utils.fix_amount(amount, self.stepSize)
  349. signals['Limits_close'].append([amount, 'pk', price, utils.get_cid(self.broker_id)])
  350. else:
  351. if need_close_long > 0:
  352. # sell
  353. price = utils.fix_price(self.mp, self.tickSize)
  354. amount = need_close_long
  355. if amount * self.mp > self._min_amount_value:
  356. signals['Limits_close'].append([amount, 'pd', price,utils.get_cid(self.broker_id)])
  357. if need_close_short > 0:
  358. # buy
  359. price = utils.fix_price(self.mp, self.tickSize)
  360. amount = need_close_short
  361. if amount * self.mp > self._min_amount_value:
  362. signals['Limits_close'].append([amount, 'pk', price,utils.get_cid(self.broker_id)])
  363. return signals
  364. # def _cancel_close(self):
  365. # '''
  366. # 取消平仓订单
  367. # '''
  368. # # 准备命令
  369. # signals = dict()
  370. # # 撤掉危险挂单
  371. # cond1 = self.close_dist[0]
  372. # cond2 = self.close_dist[1]
  373. # cond3 = self.close_dist[2]
  374. # cond4 = self.close_dist[3]
  375. # # # 获取当前挂单
  376. # for cid in self.local_orders:
  377. # i = self.local_orders[cid]
  378. # if i['side'] == 'pk':
  379. # if (i['price'] >= cond1 or i['price'] <= cond2):
  380. # cid = i['client_id']
  381. # oid = i['order_id']
  382. # signals[f'Cancel{cid}'] = [cid, oid]
  383. # if i['side'] == 'pd':
  384. # if (i['price'] <= cond3 or i['price'] >= cond4):
  385. # cid = i['client_id']
  386. # oid = i['order_id']
  387. # signals[f'Cancel{cid}'] = [cid, oid]
  388. # return signals
  389. def _post_close(self):
  390. '''
  391. 处理平仓
  392. '''
  393. # 准备命令
  394. signals = dict()
  395. signals['Limits_close'] = []
  396. # 撤掉危险挂单
  397. pdAmount = 0.0
  398. pdOrderNum = 0
  399. pkAmount = 0.0
  400. pkOrderNum = 0
  401. # 计算
  402. cond1 = self.close_dist[0]
  403. cond2 = self.close_dist[1]
  404. cond3 = self.close_dist[2]
  405. cond4 = self.close_dist[3]
  406. # # 获取当前挂单
  407. for cid in self.local_orders:
  408. i = self.local_orders[cid]
  409. if i['side'] == 'pk':
  410. c1 = i['price'] > cond1
  411. c2 = i['price'] < cond2
  412. if c1 or c2:
  413. signals[f'Cancel{cid}'] = [i['client_id'], i['order_id']]
  414. # else:
  415. pkAmount += i['amount']
  416. pkOrderNum += 1
  417. elif i['side'] == 'pd':
  418. c1 = i['price'] < cond3
  419. c2 = i['price'] > cond4
  420. if c1 or c2:
  421. signals[f'Cancel{cid}'] = [i['client_id'], i['order_id']]
  422. # else:
  423. pdAmount += i['amount']
  424. pdOrderNum += 1
  425. need_cancel_all_close = 0
  426. if abs(pdAmount - self.pos.longPos) * self.mp > self._min_amount_value or \
  427. abs(pkAmount - self.pos.shortPos) * self.mp > self._min_amount_value:
  428. need_cancel_all_close = 1
  429. if need_cancel_all_close:
  430. for cid in self.local_orders:
  431. i = self.local_orders[cid]
  432. if i['side'] in ['pk','pd']:
  433. signals[f'Cancel{cid}'] = [i['client_id'], i['order_id']]
  434. ####################### 检查是否需要挂平仓单
  435. if 'spot' in self.exchange:
  436. ### 需要平多的价值大于最小交易价值 执行平多逻辑
  437. if self.pos.longPos * self.mp > self._min_amount_value:
  438. if pdOrderNum == 0: # 需要更新平仓挂单
  439. price = (cond3 + cond4)*0.5
  440. price = utils.clip(price, self.bp*0.9995, self.ap*1.03)
  441. price = utils.fix_price(price, self.tickSize)
  442. amount = self.pos.longPos
  443. amount = utils.fix_amount(amount, self.stepSize)
  444. if float(amount) * float(price) > self._min_amount_value:
  445. signals['Limits_close'].append([
  446. amount,
  447. 'pd',
  448. price,
  449. utils.get_cid(self.broker_id)
  450. ])
  451. if self.pos.shortPos > self._min_amount_value:
  452. if pkOrderNum == 0: # 需要更新平仓挂单
  453. price = (cond1 + cond2)*0.5
  454. price = utils.clip(price, self.bp*0.97, self.ap*1.0005)
  455. price = utils.fix_price(price, self.tickSize)
  456. amount = self.pos.shortPos
  457. amount = utils.fix_amount(amount, self.stepSize)
  458. if float(amount) * float(price) > self._min_amount_value:
  459. signals['Limits_close'].append([
  460. amount,
  461. 'pk',
  462. price,
  463. utils.get_cid(self.broker_id)
  464. ])
  465. else:
  466. if self.pos.longPos > 0.0: # 正常平多
  467. if pdOrderNum == 0: # 需要更新平仓挂单
  468. price = cond3*0.5 + cond4*0.5
  469. price = utils.clip(price, self.bp*0.9995, self.ap*1.03)
  470. price = utils.fix_price(price, self.tickSize)
  471. signals['Limits_close'].append([
  472. self.pos.longPos,
  473. 'pd',
  474. price,
  475. utils.get_cid(self.broker_id)
  476. ])
  477. if self.pos.shortPos > 0.0: # 正常平空
  478. if pkOrderNum == 0: # 需要更新平仓挂单
  479. price = cond1*0.5 + cond2*0.5
  480. price = utils.clip(price, self.bp*0.97, self.ap*1.0005)
  481. price = utils.fix_price(price, self.tickSize)
  482. signals['Limits_close'].append([
  483. self.pos.shortPos,
  484. 'pk',
  485. price,
  486. utils.get_cid(self.broker_id)
  487. ])
  488. return signals
  489. def _cancel_open(self):
  490. '''
  491. 撤销开仓
  492. '''
  493. # 准备命令
  494. signals = dict()
  495. # 计算挂单范围
  496. cond1 = self.open_dist[0]
  497. cond2 = self.open_dist[1]
  498. cond3 = self.open_dist[2]
  499. cond4 = self.open_dist[3]
  500. # # 获取当前挂单
  501. for cid in self.local_orders:
  502. i = self.local_orders[cid]
  503. if i['side'] == 'kd':
  504. # 撤开多单
  505. c1 = i['price'] > cond1
  506. c2 = i['price'] < cond2
  507. # c3 = i['price'] > cond1*(1-self.long_hold_rate) + cond2*self.long_hold_rate
  508. # if c1 or c2 or c3:
  509. if c1 or c2:
  510. signals[f'Cancel{cid}'] = [i['client_id'], i['order_id']]
  511. elif i['side'] == 'kk':
  512. # 撤开空单
  513. c1 = i['price'] < cond3
  514. c2 = i['price'] > cond4
  515. # c3 = i['price'] < cond3*(1-self.short_hold_rate) + cond4*self.short_hold_rate
  516. # if c1 or c2 or c3:
  517. if c1 or c2:
  518. signals[f'Cancel{cid}'] = [i['client_id'], i['order_id']]
  519. return signals
  520. # @utils.timeit
  521. def _post_open(self):
  522. '''
  523. 处理开仓 开仓要一直挂
  524. '''
  525. # 准备命令
  526. signals = dict()
  527. signals['Limits_open'] = []
  528. # 计算挂单范围
  529. cond1 = self.open_dist[0]
  530. cond2 = self.open_dist[1]
  531. cond3 = self.open_dist[2]
  532. cond4 = self.open_dist[3]
  533. # # 获取当前挂单
  534. buyP = []
  535. sellP = []
  536. buy_value = 0
  537. sell_value = 0
  538. for cid in self.local_orders:
  539. i = self.local_orders[cid]
  540. if i['side'] in ['kd']:
  541. buyP.append(i['price'])
  542. buy_value += i['amount'] * i['price']
  543. if i['side'] in ['kk']:
  544. sellP.append(i['price'])
  545. sell_value += i['amount'] * i['price']
  546. ########### 现货 ###########
  547. if 'spot' in self.exchange:
  548. ### 计算当前持币和持u ###
  549. coin_value = self.coin * self.mp * self.leverrate * self.adjust_leverrate # 可卖价值
  550. cash_value = self.cash * self.leverrate * self.adjust_leverrate # 可买价值
  551. long_free_value = min(cash_value, self.max_long_value) - buy_value
  552. short_free_value = min(coin_value, self.max_short_value) - sell_value
  553. ########### 合约 ###########
  554. else:
  555. ### 合约只有已有仓位和开仓订单会占用保证金
  556. long_free_value = self.max_long_value - self.long_hold_value - buy_value
  557. short_free_value = self.max_short_value - self.short_hold_value - sell_value
  558. #######################################
  559. one_hand_long_value = self.max_long_value / self.grid * 0.99
  560. one_hand_short_value = self.max_short_value / self.grid * 0.99
  561. ############## 单层挂单 #################
  562. ########### 挂多单 ###########
  563. if self.post_side >= 0:
  564. if len(buyP) == 0:
  565. # 1
  566. targit_buy_price = cond1 * 0.5 + cond2 * 0.5
  567. targit_buy_price = utils.clip(targit_buy_price, self.bp*0.97, self.ap*1.0005)
  568. targit_buy_price = utils.fix_price(targit_buy_price, self.tickSize)
  569. value = min(one_hand_long_value, long_free_value)
  570. amount = utils.fix_amount(value/self.mp, self.stepSize)
  571. amount_value = float(amount) * self.mp
  572. if amount_value >= self._min_amount_value and amount_value <= long_free_value:
  573. signals['Limits_open'].append([amount, 'kd', targit_buy_price, utils.get_cid(self.broker_id)])
  574. ########### 挂空单 ###########
  575. if self.post_side <= 0:
  576. if len(sellP) == 0:
  577. # 1
  578. targit_sell_price = cond3 * 0.5 + cond4 * 0.5
  579. targit_sell_price = utils.clip(targit_sell_price, self.bp*0.9995, self.ap*1.03)
  580. targit_sell_price = utils.fix_price(targit_sell_price, self.tickSize)
  581. value = min(one_hand_short_value, short_free_value)
  582. amount = utils.fix_amount(value/self.mp, self.stepSize)
  583. amount_value = float(amount) * self.mp
  584. if amount_value >= self._min_amount_value and amount_value <= short_free_value:
  585. signals['Limits_open'].append([amount, 'kk', targit_sell_price, utils.get_cid(self.broker_id)])
  586. ############## 多层挂单 #################
  587. # step = (cond1-cond2) / self.grid
  588. # ########### 挂多单 ###########
  589. # if self.post_side >= 0:
  590. # if len(buyP) == 0:
  591. # # 1
  592. # targit_buy_price = cond1 * 0.1 + cond2 * 0.9
  593. # targit_buy_price = utils.clip(targit_buy_price, self.bp*0.97, self.ap*1.0005)
  594. # targit_buy_price = self.fix_price(targit_buy_price)
  595. # value = min(one_hand_long_value, long_free_value)
  596. # amount = self.fix_amount(value/self.mp)
  597. # amount_value = float(amount) * self.mp
  598. # if targit_buy_price < cond1*(1-self.long_hold_rate) + cond2*self.long_hold_rate:
  599. # if amount_value >= self._min_amount_value and amount_value <= long_free_value:
  600. # signals['Limits_open'].append([amount, 'kd', targit_buy_price, utils.get_cid(self.broker_id)])
  601. # else:
  602. # # 2
  603. # targit_buy_price = max(buyP) + step
  604. # targit_buy_price = utils.clip(targit_buy_price, self.bp*0.97, self.ap*1.0005)
  605. # targit_buy_price = self.fix_price(targit_buy_price)
  606. # value = min(one_hand_long_value, long_free_value)
  607. # amount = self.fix_amount(value/self.mp)
  608. # amount_value = float(amount) * self.mp
  609. # if targit_buy_price < cond1*(1-self.long_hold_rate) + cond2*self.long_hold_rate:
  610. # if targit_buy_price < cond1 - 0.1*step and targit_buy_price > cond2 + 0.1*step:
  611. # if amount_value >= self._min_amount_value and amount_value <= long_free_value:
  612. # signals['Limits_open'].append([amount, 'kd', targit_buy_price, utils.get_cid(self.broker_id)])
  613. # # 3
  614. # targit_buy_price = min(buyP) - step
  615. # targit_buy_price = utils.clip(targit_buy_price, self.bp*0.97, self.ap*1.0005)
  616. # targit_buy_price = self.fix_price(targit_buy_price)
  617. # value = min(one_hand_long_value, long_free_value)
  618. # amount = self.fix_amount(value/self.mp)
  619. # amount_value = float(amount) * self.mp
  620. # if targit_buy_price < cond1*(1-self.long_hold_rate) + cond2*self.long_hold_rate:
  621. # if targit_buy_price < cond1 - 0.1*step and targit_buy_price > cond2 + 0.1*step:
  622. # if amount_value >= self._min_amount_value and amount_value <= long_free_value:
  623. # signals['Limits_open'].append([amount, 'kd', targit_buy_price, utils.get_cid(self.broker_id)])
  624. # ########### 挂空单 ###########
  625. # if self.post_side <= 0:
  626. # if len(sellP) == 0:
  627. # # 1
  628. # targit_sell_price = cond3 * 0.1 + cond4 * 0.9
  629. # targit_sell_price = utils.clip(targit_sell_price, self.bp*0.9995, self.ap*1.03)
  630. # targit_sell_price = self.fix_price(targit_sell_price)
  631. # value = min(one_hand_short_value, short_free_value)
  632. # amount = self.fix_amount(value/self.mp)
  633. # amount_value = float(amount) * self.mp
  634. # if targit_sell_price > cond3*(1-self.short_hold_rate) + cond4*self.short_hold_rate:
  635. # if amount_value >= self._min_amount_value and amount_value <= short_free_value:
  636. # signals['Limits_open'].append([amount, 'kk', targit_sell_price, utils.get_cid(self.broker_id)])
  637. # else:
  638. # # 2
  639. # targit_sell_price = min(sellP) - step
  640. # targit_sell_price = utils.clip(targit_sell_price, self.bp*0.9995, self.ap*1.03)
  641. # targit_sell_price = self.fix_price(targit_sell_price)
  642. # value = min(one_hand_short_value, short_free_value)
  643. # amount = self.fix_amount(value/self.mp)
  644. # amount_value = float(amount) * self.mp
  645. # if targit_sell_price > cond3*(1-self.short_hold_rate) + cond4*self.short_hold_rate:
  646. # if targit_sell_price > cond3 + 0.1*step and targit_sell_price < cond4 - 0.1*step:
  647. # if amount_value >= self._min_amount_value and amount_value <= short_free_value:
  648. # signals['Limits_open'].append([amount, 'kk', targit_sell_price, utils.get_cid(self.broker_id)])
  649. # # 3
  650. # targit_sell_price = max(sellP) + step
  651. # targit_sell_price = utils.clip(targit_sell_price, self.bp*0.9995, self.ap*1.03)
  652. # targit_sell_price = self.fix_price(targit_sell_price)
  653. # value = min(one_hand_short_value, short_free_value)
  654. # amount = self.fix_amount(value/self.mp)
  655. # amount_value = float(amount) * self.mp
  656. # if targit_sell_price > cond3*(1-self.short_hold_rate) + cond4*self.short_hold_rate:
  657. # if targit_sell_price > cond3 + 0.1*step and targit_sell_price < cond4 - 0.1*step:
  658. # if amount_value >= self._min_amount_value and amount_value <= short_free_value:
  659. # signals['Limits_open'].append([amount, 'kk', targit_sell_price, utils.get_cid(self.broker_id)])
  660. ############################################
  661. return signals
  662. # @utils.timeit
  663. def gen_dist(self, open, close, pos_rate, ref_bp, ref_ap, predict, grid=1, mode='free'):
  664. '''
  665. Input:
  666. 开仓距离
  667. 平仓距离
  668. 参考价格
  669. 产生挂单位置
  670. 4
  671. 3
  672. 1
  673. 2
  674. 用预测调近挂单距离很危险
  675. '''
  676. ###########################
  677. mp = (ref_bp+ref_ap)*0.5
  678. buy_start = mp
  679. sell_start = mp
  680. ###########################
  681. # 持有多仓时 平仓sell更近 持有空仓时 平仓buy 更近
  682. avoid = min(0.0005, close * 0.5) # 平仓位置偏移可以适当大一点
  683. # 平仓位置
  684. close_dist = [
  685. buy_start * ( 1 + predict - close + avoid), # buy upper
  686. buy_start * ( 1 + predict - close - avoid), # buy lower
  687. sell_start * ( 1 + predict + close - avoid), # sell lower
  688. sell_start * ( 1 + predict + close + avoid), # sell upper
  689. ]
  690. ######################################################
  691. if mode == 'free':
  692. # 自由做市
  693. buy_start = ref_bp
  694. sell_start = ref_ap
  695. elif mode == 'follow':
  696. # 跟随做市
  697. mp = (ref_bp+ref_ap)*0.5
  698. buy_start = mp
  699. sell_start = mp
  700. else:
  701. # 跟随做市
  702. mp = (ref_bp+ref_ap)*0.5
  703. buy_start = mp
  704. sell_start = mp
  705. ###########################
  706. ###########################
  707. # 持有多仓时 开仓buy更远 持有空仓时 开仓sell 更远
  708. avoid = min(0.001, open * 0.05) # 开仓位置偏移可以适当小一点
  709. # 持仓偏移
  710. buy_shift = 1 + pos_rate[0] * grid
  711. sell_shift = 1 + pos_rate[1] * grid
  712. # 保护窗口
  713. open_dist = [
  714. buy_start * ( 1 + predict - open * buy_shift + avoid), # buy upper
  715. buy_start * ( 1 + predict - open * buy_shift - avoid), # buy lower
  716. sell_start * ( 1 + predict + open * sell_shift - avoid), # sell lower
  717. sell_start * ( 1 + predict + open * sell_shift + avoid), # sell upper
  718. ]
  719. ###########################
  720. return open_dist, close_dist
  721. def _update_request_num(self, signals):
  722. '''统计请求次数'''
  723. if 'Limits_open' in signals:
  724. self.request_num += len(signals['Limits_open'])
  725. self.request_order_num += len(signals['Limits_open'])
  726. if 'Limits_close' in signals:
  727. self.request_num += len(signals['Limits_close'])
  728. self.request_order_num += len(signals['Limits_close'])
  729. for i in signals:
  730. if 'Cancel' in i:
  731. self.request_num += 1
  732. elif 'Check' in i:
  733. self.request_num += 1
  734. def _check_request_limit(self, signals):
  735. '''根据平均请求次数限制开仓下单'''
  736. if self.request_num > self.limit_requests_num:
  737. return dict()
  738. elif self.request_num >= self.limit_requests_num * 0.5 or self.request_order_num >= self.limit_order_requests_num*0.8:
  739. new_signals = dict()
  740. for order_name in signals:
  741. if 'Limits_open' in order_name:
  742. pass
  743. elif 'Limits_close' in order_name and self.request_order_num >= self.limit_order_requests_num:
  744. pass
  745. else:
  746. new_signals[order_name] = signals[order_name]
  747. return new_signals
  748. else:
  749. return signals
  750. def _check_local_orders(self):
  751. '''超过时间限制触发查单信号'''
  752. signals = dict()
  753. if self.local_time - self._check_local_orders_time >= self._check_local_orders_interval:
  754. for cid in self.local_orders:
  755. # 如果没在查单队列中
  756. if cid not in self.in_check:
  757. # 超过10s没动的订单 进行检查
  758. if self.local_time - self.local_orders[cid]["localtime"] > self._check_local_orders_interval:
  759. signals[f"Check{cid}"] = [cid, self.local_orders[cid]['order_id']]
  760. self.in_check[cid] = self.local_time
  761. if self._is_print:
  762. self.logger.debug(f"查询订单 {cid}")
  763. # 维护查单队列
  764. self._release_in_check()
  765. # 更新查单时间
  766. self._check_local_orders_time = self.local_time
  767. return signals
  768. def _release_in_check(self):
  769. '''检查是否正在撤单'''
  770. new_dict = dict()
  771. for cid in self.in_check:
  772. # 等待超过后移除正在撤单队列
  773. if self.local_time - self.in_check[cid] <= self.check_wait_interval:
  774. new_dict[cid] = self.in_check[cid]
  775. self.in_check = new_dict
  776. def _release_in_cancel(self):
  777. '''检查是否正在撤单'''
  778. new_dict = dict()
  779. for cid in self.in_cancel:
  780. # 等待超过后移除正在撤单队列
  781. if self.local_time - self.in_cancel[cid] <= self.cancel_wait_interval:
  782. new_dict[cid] = self.in_cancel[cid]
  783. self.in_cancel = new_dict
  784. def _update_in_cancel(self, signals):
  785. '''
  786. 新增正在撤单
  787. 检查撤单队列
  788. 释放过时限制
  789. '''
  790. new_signals = dict()
  791. for i in signals:
  792. if 'Cancel' in i:
  793. cid = signals[i][0]
  794. need_limit_cancel = 1
  795. # 判断是否在挂单表中
  796. if cid in self.local_orders:
  797. # 判断是否在订单创建100ms内
  798. if self.local_time - self.local_orders[cid]['createtime'] < 0.1:
  799. # 解除撤单限制
  800. need_limit_cancel = 0
  801. if need_limit_cancel:
  802. # 增加撤单限制
  803. if cid not in self.in_cancel:
  804. self.in_cancel[cid] = self.local_time
  805. new_signals[i] = signals[i]
  806. else:
  807. new_signals[i] = signals[i]
  808. ### 释放撤单限制
  809. self._release_in_cancel()
  810. return new_signals
  811. def _refresh_request_limit(self):
  812. if self.local_time - self.request_limit_check_time >= self.request_limit_check_interval:
  813. self._req_num_per_window = self.request_num
  814. self.request_num = 0
  815. self.request_order_num = 0
  816. self.request_limit_check_time = self.local_time
  817. def _pos_rate(self):
  818. '''获取持仓比例 0~1'''
  819. long_hold_rate = 0.0
  820. short_hold_rate = 0.0
  821. if self.max_long_value > 0.0:
  822. long_hold_rate = self.long_hold_value/self.max_long_value
  823. if self.max_short_value > 0.0:
  824. short_hold_rate = self.short_hold_value/self.max_short_value
  825. # print(long_hold_rate, short_hold_rate)
  826. self.long_hold_rate = long_hold_rate
  827. self.short_hold_rate = short_hold_rate
  828. def check_ready(self):
  829. '''检查准备'''
  830. pre_hot = 10
  831. if int(self.params.backtest):
  832. pre_hot = utils.BACKTEST_PREHOT_SECOND
  833. if self.ready != 1:
  834. if isinstance(self.mp, float) and self.local_time - self.local_start_time > pre_hot:
  835. self.ready = 1
  836. if self._is_print: print('预热完毕')
  837. return 1
  838. else:
  839. return 0
  840. def check_allow_post_open(self):
  841. '''
  842. 检查是否允许报单
  843. '''
  844. ### 接近整点时刻 不允许报单 防止下单bug ###
  845. diff_time = self.local_time % 3600
  846. if diff_time < 30 or diff_time > 3570:
  847. return 0
  848. ########################################
  849. return 1
  850. def onExit(self, data):
  851. '''
  852. 全撤全平 准备退出
  853. '''
  854. try:
  855. # 更新状态
  856. if self._update_data(data):
  857. # 检查是否准备充分
  858. if self.check_ready():
  859. return dict()
  860. # 交易模式
  861. signals = self._close_all()
  862. # 更新撤单队列
  863. signals = self._update_in_cancel(signals)
  864. # 交易模式
  865. signals = self._check_request_limit(signals)
  866. # 统计请求频率
  867. self._update_request_num(signals)
  868. return signals
  869. except:
  870. traceback.print_exc()
  871. def onSleep(self, data):
  872. '''
  873. 全撤 不再下新订单了 防止影响check_position执行
  874. '''
  875. try:
  876. # 更新状态
  877. if self._update_data(data):
  878. # 检查是否准备充分
  879. if self.check_ready():
  880. return dict()
  881. # 交易模式
  882. signals = self._cancel_targit_side_orders()
  883. # 更新撤单队列
  884. signals = self._update_in_cancel(signals)
  885. # 交易模式
  886. signals = self._check_request_limit(signals)
  887. # 统计请求频率
  888. self._update_request_num(signals)
  889. return signals
  890. except:
  891. traceback.print_exc()
  892. # @timeit
  893. # def onOrder(self, data):
  894. # '''
  895. # call on order update event
  896. # '''
  897. # try:
  898. # # 更新状态
  899. # if self._update_data(data):
  900. # # 检查是否准备充分
  901. # if self.check_ready():
  902. # return dict()
  903. # ###### 关键操作 ######
  904. # # 更新挂单距离
  905. # self._pos_rate()
  906. # open_dist, close_dist = self.gen_dist(
  907. # open=self.trade_open_dist,
  908. # close=self.trade_close_dist,
  909. # pos_rate=[self.long_hold_rate, self.short_hold_rate],
  910. # ref_bp=self.ref_bp,
  911. # ref_ap=self.ref_ap,
  912. # predict=self.predict,
  913. # grid=self.grid,
  914. # mode=self.maker_mode,
  915. # )
  916. # self.open_dist = \
  917. # [
  918. # self.fix_price(open_dist[0]),
  919. # self.fix_price(open_dist[1]),
  920. # self.fix_price(open_dist[2]),
  921. # self.fix_price(open_dist[3]),
  922. # ]
  923. # self.close_dist = \
  924. # [
  925. # self.fix_price(close_dist[0]),
  926. # self.fix_price(close_dist[1]),
  927. # self.fix_price(close_dist[2]),
  928. # self.fix_price(close_dist[3]),
  929. # ]
  930. # # 获取开平仓指令
  931. # signals = dict()
  932. # # 获取平仓信号
  933. # signals.update(self._post_close())
  934. # # 更新撤单队列
  935. # signals = self._update_in_cancel(signals)
  936. # # 限制频率
  937. # signals = self._check_request_limit(signals)
  938. # # 统计请求频率
  939. # self._update_request_num(signals)
  940. # ##########################
  941. # return signals
  942. # except:
  943. # traceback.print_exc()
  944. # if self._is_print:self.logger.error(traceback.format_exc())
  945. # @timeit
  946. # def onTick(self, data):
  947. # '''
  948. # call on ticker update event
  949. # '''
  950. # try:
  951. # # 更新状态
  952. # if self._update_data(data):
  953. # # 检查是否准备充分
  954. # if self.check_ready():
  955. # return dict()
  956. # ###### 关键操作 ######
  957. # # 更新挂单距离
  958. # self.open_dist, self.close_dist = utils.gen_dist(
  959. # self.trade_open_dist,
  960. # self.trade_close_dist,
  961. # self._pos_rate(),
  962. # self.ref_bp,
  963. # self.ref_ap,
  964. # self.predict
  965. # )
  966. # # 获取开平仓指令
  967. # signals = dict()
  968. # # 获取平仓信号
  969. # signals.update(self._cancel_open())
  970. # # 更新撤单队列
  971. # signals = self._update_in_cancel(signals)
  972. # # 限制频率
  973. # signals = self._check_request_limit(signals)
  974. # # 统计请求频率
  975. # self._update_request_num(signals)
  976. # ##########################
  977. # return signals
  978. # except:
  979. # traceback.print_exc()
  980. # if self._is_print:self.logger.error(traceback.format_exc())
  981. # @utils.timeit
  982. def onTime(self, data):
  983. '''
  984. call on time
  985. '''
  986. try:
  987. # 定时打印
  988. if self.local_time - self._print_time > self._print_interval:
  989. self._print_time = self.local_time
  990. if self._is_print:
  991. if self.ready:
  992. pass
  993. else:
  994. self.logger.info("预热中")
  995. # 更新状态
  996. if self._update_data(data):
  997. # 检查是否准备充分
  998. if self.check_ready():
  999. return dict()
  1000. ###### 关键操作 ######
  1001. # 更新挂单距离
  1002. self._pos_rate()
  1003. open_dist, close_dist = self.gen_dist(
  1004. open=self.trade_open_dist,
  1005. close=self.trade_close_dist,
  1006. pos_rate=[self.long_hold_rate, self.short_hold_rate],
  1007. ref_bp=self.ref_bp,
  1008. ref_ap=self.ref_ap,
  1009. predict=self.predict,
  1010. grid=self.grid,
  1011. mode=self.maker_mode,
  1012. )
  1013. self.open_dist = \
  1014. [
  1015. utils.fix_price(open_dist[0], self.tickSize),
  1016. utils.fix_price(open_dist[1], self.tickSize),
  1017. utils.fix_price(open_dist[2], self.tickSize),
  1018. utils.fix_price(open_dist[3], self.tickSize),
  1019. ]
  1020. self.close_dist = \
  1021. [
  1022. utils.fix_price(close_dist[0], self.tickSize),
  1023. utils.fix_price(close_dist[1], self.tickSize),
  1024. utils.fix_price(close_dist[2], self.tickSize),
  1025. utils.fix_price(close_dist[3], self.tickSize),
  1026. ]
  1027. # 获取开平仓指令
  1028. signals = dict()
  1029. # 获取撤单信号
  1030. signals.update(self._cancel_open())
  1031. # 获取开仓信号 整点时刻前后不报单
  1032. if self.check_allow_post_open():
  1033. if self.local_time - self.post_open_time > self.post_open_interval:
  1034. self.post_open_time = self.local_time
  1035. signals.update(self._post_open())
  1036. # 获取平仓信号
  1037. signals.update(self._post_close())
  1038. # 每隔固定时间检查超时订单
  1039. signals.update(self._check_local_orders())
  1040. # 更新撤单队列
  1041. signals = self._update_in_cancel(signals)
  1042. # 限制频率
  1043. signals = self._check_request_limit(signals)
  1044. # 刷新频率限制
  1045. self._refresh_request_limit()
  1046. # 统计请求频率
  1047. self._update_request_num(signals)
  1048. ##########################
  1049. return signals
  1050. except:
  1051. traceback.print_exc()
  1052. if self._is_print:self.logger.error(traceback.format_exc())