strategy.py 38 KB

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