strategy.py 37 KB

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