strategy.py 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907
  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 = 30.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. # TODO 目前看来是开仓前的一些准备,对本地数据状态进行更新
  137. # @utils.timeit
  138. def _update_data(self, data:model.TraderMsg):
  139. '''更新本地数据'''
  140. try:
  141. # 更新信息
  142. # orders
  143. self.local_orders.clear()
  144. self.local_orders.update(data.orders)
  145. # position
  146. if self.pos.longPos != data.position.longPos:
  147. self.pos.longPos = data.position.longPos
  148. self.pos.longAvg = data.position.longAvg
  149. if self.pos.shortPos != data.position.shortPos:
  150. self.pos.shortPos = data.position.shortPos
  151. self.pos.shortAvg = data.position.shortAvg
  152. # TODO data.market里面是组合盘口信息,所以此处的bp和ap都是从组合盘口里拿出来的
  153. # bp ap
  154. self.bp = data.market[utils.BP_INDEX]
  155. self.ap = data.market[utils.AP_INDEX]
  156. # trade mp
  157. self.mp = (self.bp+self.ap)*0.5
  158. ########### 动态杠杆调节 ###########
  159. if self.mp_ewma == None:
  160. self.mp_ewma = self.mp
  161. else:
  162. self.mp_ewma = self.mp_ewma*0.999 + self.mp*0.001
  163. if self.mp > self.mp_ewma:
  164. # 增加杠杆
  165. self.adjust_leverrate = 1.0
  166. else:
  167. # 降低杠杆
  168. self.adjust_leverrate = 0.8
  169. ########### 当前持仓价值 ###########
  170. self.long_hold_value = self.pos.longPos * self.mp
  171. self.short_hold_value = self.pos.shortPos * self.mp
  172. # TODO 177-186计算单边最大持仓数量
  173. ########### 现货 ###########
  174. if 'spot' in self.exchange:
  175. ### 计算总保证金情况
  176. self.max_long_value = self.start_cash * self.leverrate * self.adjust_leverrate
  177. self.max_short_value = self.start_coin * self.leverrate * self.adjust_leverrate * self.mp
  178. ########### 合约 ###########
  179. else:
  180. ### 计算总保证金情况
  181. self.max_long_value = self.equity * self.leverrate * self.adjust_leverrate
  182. self.max_short_value = self.max_long_value
  183. # TODO 做市模式识别
  184. ###### maker mode ######
  185. if self.ref_name[self.ref_index] == self.trade_name:
  186. self.maker_mode = 'free'
  187. else:
  188. self.maker_mode = 'follow'
  189. ###### ref price ######
  190. if data.ref_price == None:
  191. self.ref_bp = self.bp
  192. self.ref_ap = self.ap
  193. self.ref_price = self.mp
  194. else:
  195. self.ref_bp = data.ref_price[self.ref_index][0]
  196. self.ref_ap = data.ref_price[self.ref_index][1]
  197. self.ref_price = (self.ref_bp+self.ref_ap)*0.5
  198. # spread
  199. self.predict = utils.clip(data.predict*self.predict_alpha, -self.trade_open_dist, self.trade_open_dist)
  200. # is base spread normal ? can take but can't move too far
  201. # if abs(self.ref_price - self.mp)/self.mp > self.trade_open_dist*3:
  202. # back to pure market making strategy
  203. # self.ref_price = self.mp
  204. # equity 当前账户可用cash和coin
  205. self.coin = data.coin
  206. self.cash = data.cash
  207. if self.mp:
  208. self.equity = data.cash + data.coin * self.mp
  209. # max equity
  210. if self.equity > self.max_equity:
  211. self.max_equity = self.equity
  212. self.total_amount = float(utils.fix_amount(self.equity * self.leverrate * self.adjust_leverrate / self.mp, self.stepSize))
  213. if self.total_amount == 0.0:
  214. if self._is_print:
  215. self.logger.error("总可开数量太少")
  216. # max pos
  217. maxPos = max([
  218. self.pos.longPos, self.pos.shortPos
  219. ]) * self.mp // (self.equity if self.equity > 0 else 99999999)
  220. if maxPos > self.maxPos:
  221. self.maxPos = maxPos
  222. return 1
  223. except:
  224. if self._is_print:
  225. self.logger.error(traceback.format_exc())
  226. return 0
  227. # @utils.timeit
  228. def _print_summary(self):
  229. '''
  230. 打印状态信息
  231. 耗时700us
  232. '''
  233. msg = '>>> '
  234. msg += '盘口 ' + self.exchange + ' '
  235. msg += '品种 ' + self.params.pair + ' '
  236. msg += '现价 ' + str(round(self.mp, 6)) + ' '
  237. msg += '定价 ' + str(round(self.ref_price, 6)) + ' '
  238. msg += '偏差 ' + str(round((self.ref_price-self.mp)/self.mp*100, 2)) + '% '
  239. msg += '净值 ' + str(round(self.equity, 3)) + ' '
  240. msg += 'Cash ' + str(round(self.cash, 3)) + ' '
  241. msg += 'Coin ' + str(round(self.coin*self.mp, 3)) + ' '
  242. msg += '推算利润 ' + str(self.local_profit) + ' '
  243. self.profit = round(
  244. (self.equity - self.start_equity) /
  245. self.start_equity * 100, 3) if self.start_equity > 0 else 0
  246. msg += '盈亏 ' + str(self.profit) + '% '
  247. msg += '多杠杆' + str(
  248. round(
  249. self.pos.longPos * self.mp /
  250. (self.equity if self.equity > 0 else 99999999), 3)) + ' '
  251. self.long_pos_bias = None
  252. if self.pos.longPos > 0.0:
  253. self.long_pos_bias = round(100 - 100 * self.pos.longAvg / self.mp, 2)
  254. msg += '浮盈' + str(self.long_pos_bias) + '% '
  255. else:
  256. msg += '浮盈 None '
  257. msg += '空杠杆' + str(
  258. round(
  259. self.pos.shortPos * self.mp /
  260. (self.equity if self.equity > 0 else 99999999), 3)) + ' '
  261. self.short_pos_bias = None
  262. if self.pos.shortPos > 0.0:
  263. self.short_pos_bias = round(100 * self.pos.shortAvg / self.mp - 100, 2)
  264. msg += '浮盈' + str(self.short_pos_bias) + '% '
  265. else:
  266. msg += '浮盈 None '
  267. msg += '杠杆' + str(self.leverrate) + ' 动态 ' + str(self.adjust_leverrate) + ' '
  268. msg += '最大' + str(self.maxPos) + ' '
  269. msg += '请求' + str(self._req_num_per_window) + ' 上限 ' + str(self.limit_order_requests_num) + '次/10s '
  270. run_time = time.time() - self._start_time
  271. self.daily_return = round(self.profit / 100 / run_time * 86400, 5)
  272. msg += '日化' + str(self.daily_return) + ' '
  273. msg += '当前参数 ' + \
  274. ' 开仓 ' + str(round(self.trade_open_dist,6)) + \
  275. ' 平仓 ' + str(round(self.trade_close_dist,6)) + \
  276. ' 方向 ' + str(self.post_side) + \
  277. ' 参考 ' + self.ref_name[self.ref_index] + \
  278. ' 模式 ' + self.maker_mode + \
  279. ' 预测 ' + str(round(self.predict,5)) + \
  280. ' 预估24H成交额 ' + str(self.trade_vol_24h) + 'W' + \
  281. ' 实时优化 ' + str(self.params.backtest)
  282. # 写入日志
  283. if self._is_print:
  284. self.logger.info(msg)
  285. #### 本地订单状态列表 ####
  286. o_num = len(self.local_orders)
  287. self.logger.info(f"挂单列表 共{o_num}单")
  288. for cid in self.local_orders:
  289. i = self.local_orders[cid]
  290. msg = i['symbol'] + ' ' + str(i['client_id']) + ' ' + i['side'] + ' 杠杆: ' + \
  291. str(round(i['amount'] * self.mp / (self.equity if self.equity > 0 else 99999999), 3)) + 'x 价值:' + \
  292. str(round(i['amount'] * self.mp,2)) + 'u' + ' 价格: ' + \
  293. str(i['price']) + ' 偏离:' + \
  294. str(round((i['price'] - self.mp) / self.mp * 100, 3)) + '%'
  295. if self._is_print:
  296. self.logger.info(msg)
  297. self.logger.info("撤单列表")
  298. if len(self.in_cancel) > 0:
  299. if self._is_print:
  300. self.logger.info(self.in_cancel)
  301. self.logger.info("查单列表")
  302. if len(self.in_check) > 0:
  303. if self._is_print:
  304. self.logger.info(self.in_check)
  305. # def fix_amount(self, amount):
  306. # '''修补数量向下取整'''
  307. # return float(Decimal(str(amount)).quantize(Decimal(str(self.stepSize)), ROUND_FLOOR))
  308. # def fix_price(self, price):
  309. # '''修补价格四舍五入'''
  310. # return float(Decimal(str(price)).quantize(Decimal(str(self.tickSize)), ROUND_HALF_UP))
  311. def _cancel_targit_side_orders(self, order_side=["kd","kk","pd","pk"]):
  312. '''清理指定类型挂单'''
  313. signals = dict()
  314. # 撤销指定类型挂单
  315. for cid in self.local_orders:
  316. i = self.local_orders[cid]
  317. if i["side"] in order_side:
  318. cid = i['client_id']
  319. oid = i['order_id']
  320. signals[f'Cancel{cid}'] = [cid, oid]
  321. return signals
  322. def _close_all(self):
  323. '''
  324. 清空所有挂单和仓位保持休眠状态
  325. '''
  326. signals = dict()
  327. # 撤销全部挂单
  328. pd_amount = 0.0
  329. pk_amount = 0.0
  330. for cid in self.local_orders:
  331. i = self.local_orders[cid]
  332. cid = i['client_id']
  333. oid = i['order_id']
  334. if i['side'] == 'pk':
  335. pk_amount += i['amount']
  336. elif i['side'] == 'pd':
  337. pd_amount += i['amount']
  338. signals[f'Cancel{cid}'] = [cid, oid]
  339. # 批量挂单
  340. signals['Limits_close'] = []
  341. need_close_long = self.pos.longPos - pd_amount
  342. need_close_short = self.pos.shortPos - pk_amount
  343. if "spot" in self.exchange:
  344. if need_close_long * self.mp > self._min_amount_value:
  345. amount = need_close_long
  346. price = utils.fix_price(self.mp, self.tickSize)
  347. amount = utils.fix_amount(amount, self.stepSize)
  348. signals['Limits_close'].append([amount, 'pd', price, utils.get_cid(self.broker_id)])
  349. if need_close_short * self.mp > self._min_amount_value:
  350. amount = need_close_short
  351. price = utils.fix_price(self.mp, self.tickSize)
  352. amount = utils.fix_amount(amount, self.stepSize)
  353. signals['Limits_close'].append([amount, 'pk', price, utils.get_cid(self.broker_id)])
  354. else:
  355. if need_close_long > 0:
  356. # sell
  357. price = utils.fix_price(self.mp, self.tickSize)
  358. amount = need_close_long
  359. if amount * self.mp > self._min_amount_value:
  360. signals['Limits_close'].append([amount, 'pd', price,utils.get_cid(self.broker_id)])
  361. if need_close_short > 0:
  362. # buy
  363. price = utils.fix_price(self.mp, self.tickSize)
  364. amount = need_close_short
  365. if amount * self.mp > self._min_amount_value:
  366. signals['Limits_close'].append([amount, 'pk', price,utils.get_cid(self.broker_id)])
  367. return signals
  368. def _post_close(self):
  369. '''
  370. 处理平仓
  371. '''
  372. # 准备命令
  373. signals = dict()
  374. signals['Limits_close'] = []
  375. # 撤掉危险挂单
  376. pdAmount = 0.0
  377. pdOrderNum = 0
  378. pkAmount = 0.0
  379. pkOrderNum = 0
  380. # 计算
  381. long_upper = self.close_dist[0]
  382. long_lower = self.close_dist[1]
  383. short_lower = self.close_dist[2]
  384. short_upper = self.close_dist[3]
  385. # # 获取当前挂单
  386. for cid in self.local_orders:
  387. i = self.local_orders[cid]
  388. if i['side'] == 'pk':
  389. c1 = i['price'] > long_upper
  390. c2 = i['price'] < long_lower
  391. if c1 or c2:
  392. signals[f'Cancel{cid}'] = [i['client_id'], i['order_id']]
  393. # else:
  394. pkAmount += i['amount']
  395. pkOrderNum += 1
  396. elif i['side'] == 'pd':
  397. c1 = i['price'] < short_lower
  398. c2 = i['price'] > short_upper
  399. if c1 or c2:
  400. signals[f'Cancel{cid}'] = [i['client_id'], i['order_id']]
  401. # else:
  402. pdAmount += i['amount']
  403. pdOrderNum += 1
  404. need_cancel_all_close = 0
  405. if abs(pdAmount - self.pos.longPos) * self.mp > self._min_amount_value or \
  406. abs(pkAmount - self.pos.shortPos) * self.mp > self._min_amount_value:
  407. need_cancel_all_close = 1
  408. if need_cancel_all_close:
  409. for cid in self.local_orders:
  410. i = self.local_orders[cid]
  411. if i['side'] in ['pk','pd']:
  412. signals[f'Cancel{cid}'] = [i['client_id'], i['order_id']]
  413. ####################### 检查是否需要挂平仓单
  414. if 'spot' in self.exchange:
  415. ### 需要平多的价值大于最小交易价值 执行平多逻辑
  416. if self.pos.longPos * self.mp > self._min_amount_value:
  417. if pdOrderNum == 0: # 需要更新平仓挂单
  418. price = (short_lower + short_upper)*0.5
  419. price = utils.clip(price, self.bp*0.9995, self.ap*1.03)
  420. price = utils.fix_price(price, self.tickSize)
  421. amount = self.pos.longPos
  422. amount = utils.fix_amount(amount, self.stepSize)
  423. if float(amount) * float(price) > self._min_amount_value:
  424. signals['Limits_close'].append([
  425. amount,
  426. 'pd',
  427. price,
  428. utils.get_cid(self.broker_id)
  429. ])
  430. if self.pos.shortPos > self._min_amount_value:
  431. if pkOrderNum == 0: # 需要更新平仓挂单
  432. price = (long_upper + long_lower)*0.5
  433. price = utils.clip(price, self.bp*0.97, self.ap*1.0005)
  434. price = utils.fix_price(price, self.tickSize)
  435. amount = self.pos.shortPos
  436. amount = utils.fix_amount(amount, self.stepSize)
  437. if float(amount) * float(price) > self._min_amount_value:
  438. signals['Limits_close'].append([
  439. amount,
  440. 'pk',
  441. price,
  442. utils.get_cid(self.broker_id)
  443. ])
  444. else:
  445. if self.pos.longPos > 0.0: # 正常平多
  446. if pdOrderNum == 0: # 需要更新平仓挂单
  447. price = short_lower*0.5 + short_upper*0.5
  448. price = utils.clip(price, self.bp*0.9995, self.ap*1.03)
  449. price = utils.fix_price(price, self.tickSize)
  450. signals['Limits_close'].append([
  451. self.pos.longPos,
  452. 'pd',
  453. price,
  454. utils.get_cid(self.broker_id)
  455. ])
  456. if self.pos.shortPos > 0.0: # 正常平空
  457. if pkOrderNum == 0: # 需要更新平仓挂单
  458. price = long_upper*0.5 + long_lower*0.5
  459. price = utils.clip(price, self.bp*0.97, self.ap*1.0005)
  460. price = utils.fix_price(price, self.tickSize)
  461. signals['Limits_close'].append([
  462. self.pos.shortPos,
  463. 'pk',
  464. price,
  465. utils.get_cid(self.broker_id)
  466. ])
  467. return signals
  468. def _cancel_open(self):
  469. '''
  470. 撤销开仓
  471. '''
  472. # 准备命令
  473. signals = dict()
  474. # 计算挂单范围
  475. long_upper = self.open_dist[0] # long upper--------高风险
  476. long_lower = self.open_dist[1] # long lower--------低风险
  477. short_lower = self.open_dist[2] # short lower------高风险
  478. short_upper = self.open_dist[3] # short upper------低风险
  479. # # 获取当前挂单
  480. for cid in self.local_orders:
  481. i = self.local_orders[cid]
  482. if i['side'] == 'kd':
  483. # TODO 在挂单范围之外时
  484. # 撤开多单
  485. c1 = i['price'] > long_upper
  486. c2 = i['price'] < long_lower
  487. # c3 = i['price'] > long_upper*(1-self.long_hold_rate) + long_lower*self.long_hold_rate
  488. # if c1 or c2 or c3:
  489. if c1 or c2:
  490. signals[f'Cancel{cid}'] = [i['client_id'], i['order_id']]
  491. elif i['side'] == 'kk':
  492. # TODO 在挂单范围之外时
  493. # 撤开空单
  494. c1 = i['price'] < short_lower
  495. c2 = i['price'] > short_upper
  496. # c3 = i['price'] < short_lower*(1-self.short_hold_rate) + short_upper*self.short_hold_rate
  497. # if c1 or c2 or c3:
  498. if c1 or c2:
  499. signals[f'Cancel{cid}'] = [i['client_id'], i['order_id']]
  500. return signals
  501. # @utils.timeit
  502. def _post_open(self):
  503. '''
  504. 处理开仓 开仓要一直挂
  505. '''
  506. # 准备命令
  507. signals = dict()
  508. signals['Limits_open'] = []
  509. # 计算挂单范围
  510. long_upper = self.open_dist[0]
  511. long_lower = self.open_dist[1]
  512. short_lower = self.open_dist[2]
  513. short_upper = self.open_dist[3]
  514. # # 获取当前挂单
  515. buyP = []
  516. sellP = []
  517. buy_value = 0
  518. sell_value = 0
  519. for cid in self.local_orders:
  520. i = self.local_orders[cid]
  521. if i['side'] in ['kd']:
  522. buyP.append(i['price'])
  523. buy_value += i['amount'] * i['price']
  524. if i['side'] in ['kk']:
  525. sellP.append(i['price'])
  526. sell_value += i['amount'] * i['price']
  527. # TODO 计算可开价值
  528. ########### 现货 ###########
  529. if 'spot' in self.exchange:
  530. ### 计算当前持币和持u ###
  531. coin_value = self.coin * self.mp * self.leverrate * self.adjust_leverrate # 可卖价值
  532. cash_value = self.cash * self.leverrate * self.adjust_leverrate # 可买价值
  533. long_free_value = min(cash_value, self.max_long_value) - buy_value
  534. short_free_value = min(coin_value, self.max_short_value) - sell_value
  535. ########### 合约 ###########
  536. else:
  537. ### 合约只有已有仓位和开仓订单会占用保证金
  538. long_free_value = self.max_long_value - self.long_hold_value - buy_value
  539. short_free_value = self.max_short_value - self.short_hold_value - sell_value
  540. #######################################
  541. one_hand_long_value = self.max_long_value / self.grid * 0.99
  542. one_hand_short_value = self.max_short_value / self.grid * 0.99
  543. ############## 单层挂单 #################
  544. ########### 挂多单 ###########
  545. if self.post_side >= 0:
  546. if len(buyP) == 0:
  547. # TODO 计算开单价格,并限定范围,要求价格有效,截取小数位数。
  548. # 1
  549. targit_buy_price = long_upper * 0.5 + long_lower * 0.5
  550. targit_buy_price = utils.clip(targit_buy_price, self.bp*0.97, self.ap*1.0005)
  551. targit_buy_price = utils.fix_price(targit_buy_price, self.tickSize)
  552. value = min(one_hand_long_value, long_free_value)
  553. amount = utils.fix_amount(value/self.mp, self.stepSize)
  554. amount_value = float(amount) * self.mp
  555. # TODO 下单前最后检测
  556. if amount_value >= self._min_amount_value and amount_value <= long_free_value:
  557. signals['Limits_open'].append([amount, 'kd', targit_buy_price, utils.get_cid(self.broker_id)])
  558. ########### 挂空单 ###########
  559. if self.post_side <= 0:
  560. if len(sellP) == 0:
  561. # TODO 计算开单价格,并限定范围,要求价格有效,截取小数位数。
  562. # 1
  563. targit_sell_price = short_lower * 0.5 + short_upper * 0.5
  564. targit_sell_price = utils.clip(targit_sell_price, self.bp*0.9995, self.ap*1.03)
  565. targit_sell_price = utils.fix_price(targit_sell_price, self.tickSize)
  566. value = min(one_hand_short_value, short_free_value)
  567. amount = utils.fix_amount(value/self.mp, self.stepSize)
  568. amount_value = float(amount) * self.mp
  569. # TODO 下单前最后检测
  570. if amount_value >= self._min_amount_value and amount_value <= short_free_value:
  571. signals['Limits_open'].append([amount, 'kk', targit_sell_price, utils.get_cid(self.broker_id)])
  572. return signals
  573. # @utils.timeit
  574. def gen_dist(self, open, close, pos_rate, ref_bp, ref_ap, predict, grid=1, mode='free'):
  575. '''
  576. Input:
  577. 开仓距离
  578. 平仓距离
  579. 参考价格
  580. 产生挂单位置
  581. 4
  582. 3
  583. 1
  584. 2
  585. 用预测调近挂单距离很危险
  586. '''
  587. ###########################
  588. mp = (ref_bp+ref_ap)*0.5
  589. buy_start = mp
  590. sell_start = mp
  591. ###########################
  592. # 持有多仓时 平仓sell更近 持有空仓时 平仓buy 更近
  593. avoid = min(0.0005, close * 0.5) # 平仓位置偏移可以适当大一点
  594. # 平仓位置
  595. close_dist = [
  596. buy_start * ( 1 + predict - close + avoid), # buy upper
  597. buy_start * ( 1 + predict - close - avoid), # buy lower
  598. sell_start * ( 1 + predict + close - avoid), # sell lower
  599. sell_start * ( 1 + predict + close + avoid), # sell upper
  600. ]
  601. ######################################################
  602. if mode == 'free':
  603. # 自由做市
  604. buy_start = ref_bp
  605. sell_start = ref_ap
  606. elif mode == 'follow':
  607. # 跟随做市
  608. mp = (ref_bp+ref_ap)*0.5
  609. buy_start = mp
  610. sell_start = mp
  611. else:
  612. # 跟随做市
  613. mp = (ref_bp+ref_ap)*0.5
  614. buy_start = mp
  615. sell_start = mp
  616. ###########################
  617. ###########################
  618. # 持有多仓时 开仓buy更远 持有空仓时 开仓sell 更远
  619. avoid = min(0.001, open * 0.05) # 开仓位置偏移可以适当小一点
  620. # 持仓偏移
  621. buy_shift = 1 + pos_rate[0] * grid
  622. sell_shift = 1 + pos_rate[1] * grid
  623. # 保护窗口
  624. open_dist = [
  625. buy_start * ( 1 + predict - open * buy_shift + avoid), # buy upper
  626. buy_start * ( 1 + predict - open * buy_shift - avoid), # buy lower
  627. sell_start * ( 1 + predict + open * sell_shift - avoid), # sell lower
  628. sell_start * ( 1 + predict + open * sell_shift + avoid), # sell upper
  629. ]
  630. ###########################
  631. return open_dist, close_dist
  632. def _update_request_num(self, signals):
  633. '''统计请求次数'''
  634. if 'Limits_open' in signals:
  635. self.request_num += len(signals['Limits_open'])
  636. self.request_order_num += len(signals['Limits_open'])
  637. if 'Limits_close' in signals:
  638. self.request_num += len(signals['Limits_close'])
  639. self.request_order_num += len(signals['Limits_close'])
  640. for i in signals:
  641. if 'Cancel' in i:
  642. self.request_num += 1
  643. elif 'Check' in i:
  644. self.request_num += 1
  645. def _check_request_limit(self, signals):
  646. '''根据平均请求次数限制开仓下单'''
  647. if self.request_num > self.limit_requests_num:
  648. return dict()
  649. elif self.request_num >= self.limit_requests_num * 0.5 or self.request_order_num >= self.limit_order_requests_num*0.8:
  650. new_signals = dict()
  651. for order_name in signals:
  652. if 'Limits_open' in order_name:
  653. pass
  654. elif 'Limits_close' in order_name and self.request_order_num >= self.limit_order_requests_num:
  655. pass
  656. else:
  657. new_signals[order_name] = signals[order_name]
  658. return new_signals
  659. else:
  660. return signals
  661. def _check_local_orders(self):
  662. '''超过时间限制触发查单信号'''
  663. signals = dict()
  664. if self.local_time - self._check_local_orders_time >= self._check_local_orders_interval:
  665. for cid in self.local_orders:
  666. # 如果没在查单队列中
  667. if cid not in self.in_check:
  668. # 超过10s没动的订单 进行检查
  669. if self.local_time - self.local_orders[cid]["localtime"] > self._check_local_orders_interval:
  670. signals[f"Check{cid}"] = [cid, self.local_orders[cid]['order_id']]
  671. self.in_check[cid] = self.local_time
  672. if self._is_print:
  673. self.logger.debug(f"查询订单 {cid}")
  674. # 维护查单队列
  675. self._release_in_check()
  676. # 更新查单时间
  677. self._check_local_orders_time = self.local_time
  678. return signals
  679. def _release_in_check(self):
  680. '''检查是否正在撤单'''
  681. new_dict = dict()
  682. for cid in self.in_check:
  683. # 等待超过后移除正在撤单队列
  684. if self.local_time - self.in_check[cid] <= self.check_wait_interval:
  685. new_dict[cid] = self.in_check[cid]
  686. self.in_check = new_dict
  687. def _release_in_cancel(self):
  688. '''检查是否正在撤单'''
  689. new_dict = dict()
  690. for cid in self.in_cancel:
  691. # 等待超过后移除正在撤单队列
  692. if self.local_time - self.in_cancel[cid] <= self.cancel_wait_interval:
  693. new_dict[cid] = self.in_cancel[cid]
  694. self.in_cancel = new_dict
  695. def _update_in_cancel(self, signals):
  696. '''
  697. 新增正在撤单
  698. 检查撤单队列
  699. 释放过时限制
  700. '''
  701. new_signals = dict()
  702. for i in signals:
  703. if 'Cancel' in i:
  704. cid = signals[i][0]
  705. need_limit_cancel = 1
  706. # 判断是否在挂单表中
  707. if cid in self.local_orders:
  708. # 判断是否在订单创建100ms内
  709. if self.local_time - self.local_orders[cid]['createtime'] < 0.1:
  710. # 解除撤单限制
  711. need_limit_cancel = 0
  712. if need_limit_cancel:
  713. # 增加撤单限制
  714. if cid not in self.in_cancel:
  715. self.in_cancel[cid] = self.local_time
  716. new_signals[i] = signals[i]
  717. else:
  718. new_signals[i] = signals[i]
  719. ### 释放撤单限制
  720. self._release_in_cancel()
  721. return new_signals
  722. def _refresh_request_limit(self):
  723. if self.local_time - self.request_limit_check_time >= self.request_limit_check_interval:
  724. self._req_num_per_window = self.request_num
  725. self.request_num = 0
  726. self.request_order_num = 0
  727. self.request_limit_check_time = self.local_time
  728. def _pos_rate(self):
  729. '''获取持仓比例 0~1'''
  730. long_hold_rate = 0.0
  731. short_hold_rate = 0.0
  732. if self.max_long_value > 0.0:
  733. long_hold_rate = self.long_hold_value/self.max_long_value
  734. if self.max_short_value > 0.0:
  735. short_hold_rate = self.short_hold_value/self.max_short_value
  736. # print(long_hold_rate, short_hold_rate)
  737. self.long_hold_rate = long_hold_rate
  738. self.short_hold_rate = short_hold_rate
  739. def check_ready(self):
  740. '''检查准备'''
  741. pre_hot = 10
  742. if int(self.params.backtest):
  743. pre_hot = utils.BACKTEST_PREHOT_SECOND
  744. if self.ready != 1:
  745. if isinstance(self.mp, float) and self.local_time - self.local_start_time > pre_hot:
  746. self.ready = 1
  747. if self._is_print: print('预热完毕')
  748. return 1
  749. else:
  750. return 0
  751. def check_allow_post_open(self):
  752. '''
  753. 检查是否允许报单
  754. '''
  755. ### 接近整点时刻 不允许报单 防止下单bug ###
  756. diff_time = self.local_time % 3600
  757. if diff_time < 30 or diff_time > 3570:
  758. return 0
  759. ########################################
  760. return 1
  761. def onExit(self, data):
  762. '''
  763. 全撤全平 准备退出
  764. '''
  765. try:
  766. # 更新状态
  767. if self._update_data(data):
  768. # 检查是否准备充分
  769. if self.check_ready():
  770. return dict()
  771. # 交易模式
  772. signals = self._close_all()
  773. # 更新撤单队列
  774. signals = self._update_in_cancel(signals)
  775. # 交易模式
  776. signals = self._check_request_limit(signals)
  777. # 统计请求频率
  778. self._update_request_num(signals)
  779. return signals
  780. except:
  781. traceback.print_exc()
  782. def onSleep(self, data):
  783. '''
  784. 全撤 不再下新订单了 防止影响check_position执行
  785. '''
  786. try:
  787. # 更新状态
  788. if self._update_data(data):
  789. # 检查是否准备充分
  790. if self.check_ready():
  791. return dict()
  792. # 交易模式
  793. signals = self._cancel_targit_side_orders()
  794. # 更新撤单队列
  795. signals = self._update_in_cancel(signals)
  796. # 交易模式
  797. signals = self._check_request_limit(signals)
  798. # 统计请求频率
  799. self._update_request_num(signals)
  800. return signals
  801. except:
  802. traceback.print_exc()
  803. # @utils.timeit
  804. def onTime(self, data):
  805. '''
  806. call on time
  807. '''
  808. try:
  809. # 定时打印
  810. if self.local_time - self._print_time > self._print_interval:
  811. self._print_time = self.local_time
  812. if self._is_print:
  813. if self.ready:
  814. pass
  815. else:
  816. self.logger.info("预热中")
  817. # 更新状态
  818. if self._update_data(data):
  819. # TODO 我吐了啊,准备不充分才执行下面的,准备充分是直接return
  820. # 检查是否准备充分
  821. if self.check_ready():
  822. return dict()
  823. ###### 关键操作 ######
  824. # 更新挂单距离
  825. self._pos_rate()
  826. open_dist, close_dist = self.gen_dist(
  827. open=self.trade_open_dist,
  828. close=self.trade_close_dist,
  829. pos_rate=[self.long_hold_rate, self.short_hold_rate],
  830. ref_bp=self.ref_bp,
  831. ref_ap=self.ref_ap,
  832. predict=self.predict,
  833. grid=self.grid,
  834. mode=self.maker_mode,
  835. )
  836. self.open_dist = \
  837. [
  838. utils.fix_price(open_dist[0], self.tickSize),
  839. utils.fix_price(open_dist[1], self.tickSize),
  840. utils.fix_price(open_dist[2], self.tickSize),
  841. utils.fix_price(open_dist[3], self.tickSize),
  842. ]
  843. self.close_dist = \
  844. [
  845. utils.fix_price(close_dist[0], self.tickSize),
  846. utils.fix_price(close_dist[1], self.tickSize),
  847. utils.fix_price(close_dist[2], self.tickSize),
  848. utils.fix_price(close_dist[3], self.tickSize),
  849. ]
  850. # 获取开平仓指令
  851. signals = dict()
  852. # 获取撤单信号
  853. signals.update(self._cancel_open())
  854. # 获取开仓信号 整点时刻前后不报单
  855. if self.check_allow_post_open():
  856. # TODO 报单延迟检测
  857. if self.local_time - self.post_open_time > self.post_open_interval:
  858. self.post_open_time = self.local_time
  859. # TODO 开仓指令获取
  860. signals.update(self._post_open())
  861. # 获取平仓信号
  862. signals.update(self._post_close())
  863. # 每隔固定时间检查超时订单
  864. signals.update(self._check_local_orders())
  865. # 更新撤单队列
  866. signals = self._update_in_cancel(signals)
  867. # 限制频率
  868. signals = self._check_request_limit(signals)
  869. # 刷新频率限制
  870. self._refresh_request_limit()
  871. # 统计请求频率
  872. self._update_request_num(signals)
  873. ##########################
  874. return signals
  875. except:
  876. traceback.print_exc()
  877. if self._is_print:self.logger.error(traceback.format_exc())