strategy.py 38 KB

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