| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888 |
- import time
- import traceback, utils
- import model
- import logging, logging.handlers
- from decimal import Decimal
- from decimal import ROUND_HALF_UP, ROUND_FLOOR
- class Strategy:
- '''
- 策略逻辑
- '''
- def __init__(self, params: model.Config, is_print=0):
- self.params = params
- self.exchange = self.params.exchange
- self.broker_id = params.broker_id
- self.logger = self.get_logger()
- #### 实盘
- ex = self.params.exchange
- pair = self.params.pair
- self.trade_name = ex + '@' + pair
- #### 参考
- refex = self.params.refexchange
- refpair = self.params.refpair
- if len(refex) != len(refpair):
- print("参考盘口数不等于参考品种数 退出")
- return
- self.ref_num = len(refex)
- self.ref_name = []
- for i in range(self.ref_num):
- name = refex[i] + '@' + refpair[i]
- self.ref_name.append(name)
- #### maker mode
- self.maker_mode = 'free'
- ####
- self._print_time = 0
- self.local_orders = dict()
- self.pos = model.Position()
- self.long_hold_value = 0.0
- self.short_hold_value = 0.0
- self.equity = 0.0
- self.coin = 0.0
- self.cash = 0.0
- self.start_equity = 0.0
- self.start_coin = 0.0
- self.start_cash = 0.0
- self.max_equity = 0.0
- self.local_profit = 0.0
- self.total_amount = 0.0
- self.ready = 0
- self._is_print = is_print
- self._min_amount_value = 30.0 # 最小下单额 防止下单失败
- self._max_amount_value = 10000.0 # 最大下单额 防止下单过重 平不掉就很悲剧
- self.local_time = time.time()
- self.local_start_time = time.time()
- self.interval = float(self.params.interval)
- self.mp = None
- self.bp = None
- self.ap = None
- self.ref_price = 0.0
- self.ref_bp = 0.0
- self.ref_ap = 0.0
- self.stepSize = 1e-10
- self.tickSize = 1e-10
- self.maxPos = 0.0
- self.profit = 0.0
- self.daily_return = 0.0
- ####
- self.mp_ewma = None
- self.adjust_leverrate = 1.0
- #### 持仓偏差
- self.long_pos_bias = None
- self.short_pos_bias = None
- self.long_hold_rate = 0.0
- self.short_hold_rate = 0.0
- #### 时间相关参数
- self.leverrate = float(self.params.leverrate) # 最大仓位
- if "spot" in self.exchange:self.leverrate = min(self.leverrate, 1.0)
- self._print_time = time.time()
- self._start_time = time.time()
- self.request_num = 0 # 记录请求次数
- self.request_order_num = 0 # 记录下单次数
- self._print_interval = 5 # 打印信息时间间隔
- #### 距离范围
- self.open_dist = None
- self.close_dist = None
- #### 查单频率
- self._check_local_orders_time = time.time()
- self._check_local_orders_interval = 10.0
- #### 内部限頻
- try:
- self.place_order_limit = float(self.params.place_order_limit)
- except:
- self.place_order_limit = 0
- self.request_limit_check_time = time.time()
- self.request_limit_check_interval = 10.0
- self.limit_requests_num = utils.get_limit_requests_num_per_second(
- self.params.exchange, self.place_order_limit) * self.request_limit_check_interval
- self.limit_order_requests_num = utils.get_limit_order_requests_num_per_second(
- self.params.exchange, self.place_order_limit) * self.request_limit_check_interval
- #### 网络请求频率
- self._req_num_per_window = 0
- # 开仓下单间隔 均匀下单机会
- self.post_open_time = time.time()
- self.post_open_interval = 1/utils.get_limit_order_requests_num_per_second(self.params.exchange)
- #### 策略参数
- # 距离类参数
- self.trade_close_dist = 0.00001 # 基础挂单距离
- self.trade_open_dist = 0.01 # 基础挂单距离
- #### 时间类参数
- # 撤单限頻队列 强制等待 防止频繁发起撤单
- self.in_cancel = dict()
- self.cancel_wait_interval = 0.2
- # 查单限頻队列 强制等待
- self.in_check = dict()
- self.check_wait_interval = 10.0
- # ref index
- self.ref_index = 0
- # predict
- self.predict = 0.0
- self.predict_alpha = 0.0
- # post side
- self.post_side = 0
- # trade vol
- self.trade_vol_24h = 0.0
- # grid num
- self.grid = float(self.params.grid)
-
- def get_logger(self):
- logger = logging.getLogger(__name__)
- logger.setLevel(logging.DEBUG)
- # log to txt
- formatter = logging.Formatter('[%(asctime)s] - %(levelname)s - %(message)s')
- handler = logging.handlers.RotatingFileHandler(f"log.log",maxBytes=1024*1024)
- handler.setLevel(logging.DEBUG)
- handler.setFormatter(formatter)
- logger.addHandler(handler)
- return logger
- # @utils.timeit
- def _update_data(self, data:model.TraderMsg):
- '''更新本地数据'''
- try:
- # 更新信息
- # orders
- self.local_orders.clear()
- self.local_orders.update(data.orders)
- # position
- if self.pos.longPos != data.position.longPos:
- self.pos.longPos = data.position.longPos
- self.pos.longAvg = data.position.longAvg
- if self.pos.shortPos != data.position.shortPos:
- self.pos.shortPos = data.position.shortPos
- self.pos.shortAvg = data.position.shortAvg
- # bp ap
- self.bp = data.market[utils.BP_INDEX]
- self.ap = data.market[utils.AP_INDEX]
- # trade mp
- self.mp = (self.bp+self.ap)*0.5
- ########### 动态杠杆调节 ###########
- if self.mp_ewma == None:
- self.mp_ewma = self.mp
- else:
- self.mp_ewma = self.mp_ewma*0.999 + self.mp*0.001
- if self.mp > self.mp_ewma:
- # 增加杠杆
- self.adjust_leverrate = 1.0
- else:
- # 降低杠杆
- self.adjust_leverrate = 0.8
- ########### 当前持仓价值 ###########
- self.long_hold_value = self.pos.longPos * self.mp
- self.short_hold_value = self.pos.shortPos * self.mp
- ########### 现货 ###########
- if 'spot' in self.exchange:
- ### 计算总保证金情况
- self.max_long_value = self.start_cash * self.leverrate * self.adjust_leverrate
- self.max_short_value = self.start_coin * self.leverrate * self.adjust_leverrate * self.mp
- ########### 合约 ###########
- else:
- ### 计算总保证金情况
- self.max_long_value = self.equity * self.leverrate * self.adjust_leverrate
- self.max_short_value = self.max_long_value
- ###### maker mode ######
- if self.ref_name[self.ref_index] == self.trade_name:
- self.maker_mode = 'free'
- else:
- self.maker_mode = 'follow'
- ###### ref price ######
- if data.ref_price == None:
- self.ref_bp = self.bp
- self.ref_ap = self.ap
- self.ref_price = self.mp
- else:
- self.ref_bp = data.ref_price[self.ref_index][0]
- self.ref_ap = data.ref_price[self.ref_index][1]
- self.ref_price = (self.ref_bp+self.ref_ap)*0.5
- # spread
- self.predict = utils.clip(data.predict*self.predict_alpha, -self.trade_open_dist, self.trade_open_dist)
- # is base spread normal ? can take but can't move too far
- # if abs(self.ref_price - self.mp)/self.mp > self.trade_open_dist*3:
- # back to pure market making strategy
- # self.ref_price = self.mp
- # equity 当前账户可用cash和coin
- self.coin = data.coin
- self.cash = data.cash
- if self.mp:
- self.equity = data.cash + data.coin * self.mp
- # max equity
- if self.equity > self.max_equity:
- self.max_equity = self.equity
- self.total_amount = float(utils.fix_amount(self.equity * self.leverrate * self.adjust_leverrate / self.mp, self.stepSize))
- if self.total_amount == 0.0:
- if self._is_print:
- self.logger.error("总可开数量太少")
- # max pos
- maxPos = max([
- self.pos.longPos, self.pos.shortPos
- ]) * self.mp // (self.equity if self.equity > 0 else 99999999)
- if maxPos > self.maxPos:
- self.maxPos = maxPos
- return 1
- except:
- if self._is_print:
- self.logger.error(traceback.format_exc())
- return 0
- # @utils.timeit
- def _print_summary(self):
- '''
- 打印状态信息
- 耗时700us
- '''
- msg = '>>> '
- msg += '盘口 ' + self.exchange + ' '
- msg += '品种 ' + self.params.pair + ' '
- msg += '现价 ' + str(round(self.mp, 6)) + ' '
- msg += '定价 ' + str(round(self.ref_price, 6)) + ' '
- msg += '偏差 ' + str(round((self.ref_price-self.mp)/self.mp*100, 2)) + '% '
- msg += '净值 ' + str(round(self.equity, 3)) + ' '
- msg += 'Cash ' + str(round(self.cash, 3)) + ' '
- msg += 'Coin ' + str(round(self.coin*self.mp, 3)) + ' '
- msg += '推算利润 ' + str(self.local_profit) + ' '
- self.profit = round(
- (self.equity - self.start_equity) /
- self.start_equity * 100, 3) if self.start_equity > 0 else 0
- msg += '盈亏 ' + str(self.profit) + '% '
- msg += '多杠杆' + str(
- round(
- self.pos.longPos * self.mp /
- (self.equity if self.equity > 0 else 99999999), 3)) + ' '
- self.long_pos_bias = None
- if self.pos.longPos > 0.0:
- self.long_pos_bias = round(100 - 100 * self.pos.longAvg / self.mp, 2)
- msg += '浮盈' + str(self.long_pos_bias) + '% '
- else:
- msg += '浮盈 None '
- msg += '空杠杆' + str(
- round(
- self.pos.shortPos * self.mp /
- (self.equity if self.equity > 0 else 99999999), 3)) + ' '
- self.short_pos_bias = None
- if self.pos.shortPos > 0.0:
- self.short_pos_bias = round(100 * self.pos.shortAvg / self.mp - 100, 2)
- msg += '浮盈' + str(self.short_pos_bias) + '% '
- else:
- msg += '浮盈 None '
- msg += '杠杆' + str(self.leverrate) + ' 动态 ' + str(self.adjust_leverrate) + ' '
- msg += '最大' + str(self.maxPos) + ' '
- msg += '请求' + str(self._req_num_per_window) + ' 上限 ' + str(self.limit_order_requests_num) + '次/10s '
- run_time = time.time() - self._start_time
- self.daily_return = round(self.profit / 100 / run_time * 86400, 5)
- msg += '日化' + str(self.daily_return) + ' '
- msg += '当前参数 ' + \
- ' 开仓 ' + str(round(self.trade_open_dist,6)) + \
- ' 平仓 ' + str(round(self.trade_close_dist,6)) + \
- ' 方向 ' + str(self.post_side) + \
- ' 参考 ' + self.ref_name[self.ref_index] + \
- ' 模式 ' + self.maker_mode + \
- ' 预测 ' + str(round(self.predict,5)) + \
- ' 预估24H成交额 ' + str(self.trade_vol_24h) + 'W' + \
- ' 实时优化 ' + str(self.params.backtest)
- # 写入日志
- if self._is_print:
- self.logger.info(msg)
- #### 本地订单状态列表 ####
- o_num = len(self.local_orders)
- self.logger.info(f"挂单列表 共{o_num}单")
- for cid in self.local_orders:
- i = self.local_orders[cid]
- msg = i['symbol'] + ' ' + str(i['client_id']) + ' ' + i['side'] + ' 杠杆: ' + \
- str(round(i['amount'] * self.mp / (self.equity if self.equity > 0 else 99999999), 3)) + 'x 价值:' + \
- str(round(i['amount'] * self.mp,2)) + 'u' + ' 价格: ' + \
- str(i['price']) + ' 偏离:' + \
- str(round((i['price'] - self.mp) / self.mp * 100, 3)) + '%'
- if self._is_print:
- self.logger.info(msg)
- self.logger.info("撤单列表")
- if len(self.in_cancel) > 0:
- if self._is_print:
- self.logger.info(self.in_cancel)
- self.logger.info("查单列表")
- if len(self.in_check) > 0:
- if self._is_print:
- self.logger.info(self.in_check)
- # def fix_amount(self, amount):
- # '''修补数量向下取整'''
- # return float(Decimal(str(amount)).quantize(Decimal(str(self.stepSize)), ROUND_FLOOR))
- # def fix_price(self, price):
- # '''修补价格四舍五入'''
- # return float(Decimal(str(price)).quantize(Decimal(str(self.tickSize)), ROUND_HALF_UP))
- def _cancel_targit_side_orders(self, order_side=["kd","kk","pd","pk"]):
- '''清理指定类型挂单'''
- signals = dict()
- # 撤销指定类型挂单
- for cid in self.local_orders:
- i = self.local_orders[cid]
- if i["side"] in order_side:
- cid = i['client_id']
- oid = i['order_id']
- signals[f'Cancel{cid}'] = [cid, oid]
- return signals
- def _close_all(self):
- '''
- 清空所有挂单和仓位保持休眠状态
- '''
- signals = dict()
- # 撤销全部挂单
- pd_amount = 0.0
- pk_amount = 0.0
- for cid in self.local_orders:
- i = self.local_orders[cid]
- cid = i['client_id']
- oid = i['order_id']
- if i['side'] == 'pk':
- pk_amount += i['amount']
- elif i['side'] == 'pd':
- pd_amount += i['amount']
- signals[f'Cancel{cid}'] = [cid, oid]
- # 批量挂单
- signals['Limits_close'] = []
- need_close_long = self.pos.longPos - pd_amount
- need_close_short = self.pos.shortPos - pk_amount
- if "spot" in self.exchange:
- if need_close_long * self.mp > self._min_amount_value:
- amount = need_close_long
- price = utils.fix_price(self.mp, self.tickSize)
- amount = utils.fix_amount(amount, self.stepSize)
- signals['Limits_close'].append([amount, 'pd', price, utils.get_cid(self.broker_id)])
- if need_close_short * self.mp > self._min_amount_value:
- amount = need_close_short
- price = utils.fix_price(self.mp, self.tickSize)
- amount = utils.fix_amount(amount, self.stepSize)
- signals['Limits_close'].append([amount, 'pk', price, utils.get_cid(self.broker_id)])
- else:
- if need_close_long > 0:
- # sell
- price = utils.fix_price(self.mp, self.tickSize)
- amount = need_close_long
- if amount * self.mp > self._min_amount_value:
- signals['Limits_close'].append([amount, 'pd', price,utils.get_cid(self.broker_id)])
- if need_close_short > 0:
- # buy
- price = utils.fix_price(self.mp, self.tickSize)
- amount = need_close_short
- if amount * self.mp > self._min_amount_value:
- signals['Limits_close'].append([amount, 'pk', price,utils.get_cid(self.broker_id)])
- return signals
- def _post_close(self):
- '''
- 处理平仓
- '''
- # 准备命令
- signals = dict()
- signals['Limits_close'] = []
- # 撤掉危险挂单
- pdAmount = 0.0
- pdOrderNum = 0
- pkAmount = 0.0
- pkOrderNum = 0
- # 计算
- cond1 = self.close_dist[0]
- cond2 = self.close_dist[1]
- cond3 = self.close_dist[2]
- cond4 = self.close_dist[3]
- # # 获取当前挂单
- for cid in self.local_orders:
- i = self.local_orders[cid]
- if i['side'] == 'pk':
- c1 = i['price'] > cond1
- c2 = i['price'] < cond2
- if c1 or c2:
- signals[f'Cancel{cid}'] = [i['client_id'], i['order_id']]
- # else:
- pkAmount += i['amount']
- pkOrderNum += 1
- elif i['side'] == 'pd':
- c1 = i['price'] < cond3
- c2 = i['price'] > cond4
- if c1 or c2:
- signals[f'Cancel{cid}'] = [i['client_id'], i['order_id']]
- # else:
- pdAmount += i['amount']
- pdOrderNum += 1
- need_cancel_all_close = 0
- if abs(pdAmount - self.pos.longPos) * self.mp > self._min_amount_value or \
- abs(pkAmount - self.pos.shortPos) * self.mp > self._min_amount_value:
- need_cancel_all_close = 1
- if need_cancel_all_close:
- for cid in self.local_orders:
- i = self.local_orders[cid]
- if i['side'] in ['pk','pd']:
- signals[f'Cancel{cid}'] = [i['client_id'], i['order_id']]
- ####################### 检查是否需要挂平仓单
- if 'spot' in self.exchange:
- ### 需要平多的价值大于最小交易价值 执行平多逻辑
- if self.pos.longPos * self.mp > self._min_amount_value:
- if pdOrderNum == 0: # 需要更新平仓挂单
- price = (cond3 + cond4)*0.5
- price = utils.clip(price, self.bp*0.9995, self.ap*1.03)
- price = utils.fix_price(price, self.tickSize)
- amount = self.pos.longPos
- amount = utils.fix_amount(amount, self.stepSize)
- if float(amount) * float(price) > self._min_amount_value:
- signals['Limits_close'].append([
- amount,
- 'pd',
- price,
- utils.get_cid(self.broker_id)
- ])
- if self.pos.shortPos > self._min_amount_value:
- if pkOrderNum == 0: # 需要更新平仓挂单
- price = (cond1 + cond2)*0.5
- price = utils.clip(price, self.bp*0.97, self.ap*1.0005)
- price = utils.fix_price(price, self.tickSize)
- amount = self.pos.shortPos
- amount = utils.fix_amount(amount, self.stepSize)
- if float(amount) * float(price) > self._min_amount_value:
- signals['Limits_close'].append([
- amount,
- 'pk',
- price,
- utils.get_cid(self.broker_id)
- ])
- else:
- if self.pos.longPos > 0.0: # 正常平多
- if pdOrderNum == 0: # 需要更新平仓挂单
- price = cond3*0.5 + cond4*0.5
- price = utils.clip(price, self.bp*0.9995, self.ap*1.03)
- price = utils.fix_price(price, self.tickSize)
- signals['Limits_close'].append([
- self.pos.longPos,
- 'pd',
- price,
- utils.get_cid(self.broker_id)
- ])
- if self.pos.shortPos > 0.0: # 正常平空
- if pkOrderNum == 0: # 需要更新平仓挂单
- price = cond1*0.5 + cond2*0.5
- price = utils.clip(price, self.bp*0.97, self.ap*1.0005)
- price = utils.fix_price(price, self.tickSize)
- signals['Limits_close'].append([
- self.pos.shortPos,
- 'pk',
- price,
- utils.get_cid(self.broker_id)
- ])
- return signals
- def _cancel_open(self):
- '''
- 撤销开仓
- '''
- # 准备命令
- signals = dict()
- # 计算挂单范围
- cond1 = self.open_dist[0]
- cond2 = self.open_dist[1]
- cond3 = self.open_dist[2]
- cond4 = self.open_dist[3]
- # # 获取当前挂单
- for cid in self.local_orders:
- i = self.local_orders[cid]
- if i['side'] == 'kd':
- # 撤开多单
- c1 = i['price'] > cond1
- c2 = i['price'] < cond2
- # c3 = i['price'] > cond1*(1-self.long_hold_rate) + cond2*self.long_hold_rate
- # if c1 or c2 or c3:
- if c1 or c2:
- signals[f'Cancel{cid}'] = [i['client_id'], i['order_id']]
- elif i['side'] == 'kk':
- # 撤开空单
- c1 = i['price'] < cond3
- c2 = i['price'] > cond4
- # c3 = i['price'] < cond3*(1-self.short_hold_rate) + cond4*self.short_hold_rate
- # if c1 or c2 or c3:
- if c1 or c2:
- signals[f'Cancel{cid}'] = [i['client_id'], i['order_id']]
- return signals
- # @utils.timeit
- def _post_open(self):
- '''
- 处理开仓 开仓要一直挂
- '''
- # 准备命令
- signals = dict()
- signals['Limits_open'] = []
- # 计算挂单范围
- cond1 = self.open_dist[0]
- cond2 = self.open_dist[1]
- cond3 = self.open_dist[2]
- cond4 = self.open_dist[3]
- # # 获取当前挂单
- buyP = []
- sellP = []
- buy_value = 0
- sell_value = 0
- for cid in self.local_orders:
- i = self.local_orders[cid]
- if i['side'] in ['kd']:
- buyP.append(i['price'])
- buy_value += i['amount'] * i['price']
- if i['side'] in ['kk']:
- sellP.append(i['price'])
- sell_value += i['amount'] * i['price']
- ########### 现货 ###########
- if 'spot' in self.exchange:
- ### 计算当前持币和持u ###
- coin_value = self.coin * self.mp * self.leverrate * self.adjust_leverrate # 可卖价值
- cash_value = self.cash * self.leverrate * self.adjust_leverrate # 可买价值
- long_free_value = min(cash_value, self.max_long_value) - buy_value
- short_free_value = min(coin_value, self.max_short_value) - sell_value
- ########### 合约 ###########
- else:
- ### 合约只有已有仓位和开仓订单会占用保证金
- long_free_value = self.max_long_value - self.long_hold_value - buy_value
- short_free_value = self.max_short_value - self.short_hold_value - sell_value
- #######################################
- one_hand_long_value = self.max_long_value / self.grid * 0.99
- one_hand_short_value = self.max_short_value / self.grid * 0.99
- ############## 单层挂单 #################
- ########### 挂多单 ###########
- if self.post_side >= 0:
- if len(buyP) == 0:
- # 1
- targit_buy_price = cond1 * 0.5 + cond2 * 0.5
- targit_buy_price = utils.clip(targit_buy_price, self.bp*0.97, self.ap*1.0005)
- targit_buy_price = utils.fix_price(targit_buy_price, self.tickSize)
- value = min(one_hand_long_value, long_free_value)
- amount = utils.fix_amount(value/self.mp, self.stepSize)
- amount_value = float(amount) * self.mp
- if amount_value >= self._min_amount_value and amount_value <= long_free_value:
- signals['Limits_open'].append([amount, 'kd', targit_buy_price, utils.get_cid(self.broker_id)])
- ########### 挂空单 ###########
- if self.post_side <= 0:
- if len(sellP) == 0:
- # 1
- targit_sell_price = cond3 * 0.5 + cond4 * 0.5
- targit_sell_price = utils.clip(targit_sell_price, self.bp*0.9995, self.ap*1.03)
- targit_sell_price = utils.fix_price(targit_sell_price, self.tickSize)
- value = min(one_hand_short_value, short_free_value)
- amount = utils.fix_amount(value/self.mp, self.stepSize)
- amount_value = float(amount) * self.mp
- if amount_value >= self._min_amount_value and amount_value <= short_free_value:
- signals['Limits_open'].append([amount, 'kk', targit_sell_price, utils.get_cid(self.broker_id)])
- return signals
- # @utils.timeit
- def gen_dist(self, open, close, pos_rate, ref_bp, ref_ap, predict, grid=1, mode='free'):
- '''
- Input:
- 开仓距离
- 平仓距离
- 参考价格
- 产生挂单位置
- 4
- 3
- 1
- 2
- 用预测调近挂单距离很危险
- '''
- ###########################
- mp = (ref_bp+ref_ap)*0.5
- buy_start = mp
- sell_start = mp
- ###########################
- # 持有多仓时 平仓sell更近 持有空仓时 平仓buy 更近
- avoid = min(0.0005, close * 0.5) # 平仓位置偏移可以适当大一点
- # 平仓位置
- close_dist = [
- buy_start * ( 1 + predict - close + avoid), # buy upper
- buy_start * ( 1 + predict - close - avoid), # buy lower
- sell_start * ( 1 + predict + close - avoid), # sell lower
- sell_start * ( 1 + predict + close + avoid), # sell upper
- ]
- ######################################################
- if mode == 'free':
- # 自由做市
- buy_start = ref_bp
- sell_start = ref_ap
- elif mode == 'follow':
- # 跟随做市
- mp = (ref_bp+ref_ap)*0.5
- buy_start = mp
- sell_start = mp
- else:
- # 跟随做市
- mp = (ref_bp+ref_ap)*0.5
- buy_start = mp
- sell_start = mp
- ###########################
- ###########################
- # 持有多仓时 开仓buy更远 持有空仓时 开仓sell 更远
- avoid = min(0.001, open * 0.05) # 开仓位置偏移可以适当小一点
- # 持仓偏移
- buy_shift = 1 + pos_rate[0] * grid
- sell_shift = 1 + pos_rate[1] * grid
- # 保护窗口
- open_dist = [
- buy_start * ( 1 + predict - open * buy_shift + avoid), # buy upper
- buy_start * ( 1 + predict - open * buy_shift - avoid), # buy lower
- sell_start * ( 1 + predict + open * sell_shift - avoid), # sell lower
- sell_start * ( 1 + predict + open * sell_shift + avoid), # sell upper
- ]
- ###########################
- return open_dist, close_dist
- def _update_request_num(self, signals):
- '''统计请求次数'''
- if 'Limits_open' in signals:
- self.request_num += len(signals['Limits_open'])
- self.request_order_num += len(signals['Limits_open'])
- if 'Limits_close' in signals:
- self.request_num += len(signals['Limits_close'])
- self.request_order_num += len(signals['Limits_close'])
- for i in signals:
- if 'Cancel' in i:
- self.request_num += 1
- elif 'Check' in i:
- self.request_num += 1
- def _check_request_limit(self, signals):
- '''根据平均请求次数限制开仓下单'''
- if self.request_num > self.limit_requests_num:
- return dict()
- elif self.request_num >= self.limit_requests_num * 0.5 or self.request_order_num >= self.limit_order_requests_num*0.8:
- new_signals = dict()
- for order_name in signals:
- if 'Limits_open' in order_name:
- pass
- elif 'Limits_close' in order_name and self.request_order_num >= self.limit_order_requests_num:
- pass
- else:
- new_signals[order_name] = signals[order_name]
- return new_signals
- else:
- return signals
- def _check_local_orders(self):
- '''超过时间限制触发查单信号'''
- signals = dict()
- if self.local_time - self._check_local_orders_time >= self._check_local_orders_interval:
- for cid in self.local_orders:
- # 如果没在查单队列中
- if cid not in self.in_check:
- # 超过10s没动的订单 进行检查
- if self.local_time - self.local_orders[cid]["localtime"] > self._check_local_orders_interval:
- signals[f"Check{cid}"] = [cid, self.local_orders[cid]['order_id']]
- self.in_check[cid] = self.local_time
- if self._is_print:
- self.logger.debug(f"查询订单 {cid}")
- # 维护查单队列
- self._release_in_check()
- # 更新查单时间
- self._check_local_orders_time = self.local_time
- return signals
- def _release_in_check(self):
- '''检查是否正在撤单'''
- new_dict = dict()
- for cid in self.in_check:
- # 等待超过后移除正在撤单队列
- if self.local_time - self.in_check[cid] <= self.check_wait_interval:
- new_dict[cid] = self.in_check[cid]
- self.in_check = new_dict
- def _release_in_cancel(self):
- '''检查是否正在撤单'''
- new_dict = dict()
- for cid in self.in_cancel:
- # 等待超过后移除正在撤单队列
- if self.local_time - self.in_cancel[cid] <= self.cancel_wait_interval:
- new_dict[cid] = self.in_cancel[cid]
- self.in_cancel = new_dict
- def _update_in_cancel(self, signals):
- '''
- 新增正在撤单
- 检查撤单队列
- 释放过时限制
- '''
- new_signals = dict()
- for i in signals:
- if 'Cancel' in i:
- cid = signals[i][0]
- need_limit_cancel = 1
- # 判断是否在挂单表中
- if cid in self.local_orders:
- # 判断是否在订单创建100ms内
- if self.local_time - self.local_orders[cid]['createtime'] < 0.1:
- # 解除撤单限制
- need_limit_cancel = 0
- if need_limit_cancel:
- # 增加撤单限制
- if cid not in self.in_cancel:
- self.in_cancel[cid] = self.local_time
- new_signals[i] = signals[i]
- else:
- new_signals[i] = signals[i]
- ### 释放撤单限制
- self._release_in_cancel()
- return new_signals
- def _refresh_request_limit(self):
- if self.local_time - self.request_limit_check_time >= self.request_limit_check_interval:
- self._req_num_per_window = self.request_num
- self.request_num = 0
- self.request_order_num = 0
- self.request_limit_check_time = self.local_time
- def _pos_rate(self):
- '''获取持仓比例 0~1'''
- long_hold_rate = 0.0
- short_hold_rate = 0.0
- if self.max_long_value > 0.0:
- long_hold_rate = self.long_hold_value/self.max_long_value
- if self.max_short_value > 0.0:
- short_hold_rate = self.short_hold_value/self.max_short_value
- # print(long_hold_rate, short_hold_rate)
- self.long_hold_rate = long_hold_rate
- self.short_hold_rate = short_hold_rate
- def check_ready(self):
- '''检查准备'''
- pre_hot = 10
- if int(self.params.backtest):
- pre_hot = utils.BACKTEST_PREHOT_SECOND
- if self.ready != 1:
- if isinstance(self.mp, float) and self.local_time - self.local_start_time > pre_hot:
- self.ready = 1
- if self._is_print: print('预热完毕')
- return 1
- else:
- return 0
- def check_allow_post_open(self):
- '''
- 检查是否允许报单
- '''
- ### 接近整点时刻 不允许报单 防止下单bug ###
- diff_time = self.local_time % 3600
- if diff_time < 30 or diff_time > 3570:
- return 0
- ########################################
- return 1
- def onExit(self, data):
- '''
- 全撤全平 准备退出
- '''
- try:
- # 更新状态
- if self._update_data(data):
- # 检查是否准备充分
- if self.check_ready():
- return dict()
- # 交易模式
- signals = self._close_all()
- # 更新撤单队列
- signals = self._update_in_cancel(signals)
- # 交易模式
- signals = self._check_request_limit(signals)
- # 统计请求频率
- self._update_request_num(signals)
- return signals
- except:
- traceback.print_exc()
- def onSleep(self, data):
- '''
- 全撤 不再下新订单了 防止影响check_position执行
- '''
- try:
- # 更新状态
- if self._update_data(data):
- # 检查是否准备充分
- if self.check_ready():
- return dict()
- # 交易模式
- signals = self._cancel_targit_side_orders()
- # 更新撤单队列
- signals = self._update_in_cancel(signals)
- # 交易模式
- signals = self._check_request_limit(signals)
- # 统计请求频率
- self._update_request_num(signals)
- return signals
- except:
- traceback.print_exc()
- # @utils.timeit
- def onTime(self, data):
- '''
- call on time
- '''
- try:
- # 定时打印
- if self.local_time - self._print_time > self._print_interval:
- self._print_time = self.local_time
- if self._is_print:
- if self.ready:
- pass
- else:
- self.logger.info("预热中")
- # 更新状态
- if self._update_data(data):
- # 检查是否准备充分
- if self.check_ready():
- return dict()
- ###### 关键操作 ######
- # 更新挂单距离
- self._pos_rate()
- open_dist, close_dist = self.gen_dist(
- open=self.trade_open_dist,
- close=self.trade_close_dist,
- pos_rate=[self.long_hold_rate, self.short_hold_rate],
- ref_bp=self.ref_bp,
- ref_ap=self.ref_ap,
- predict=self.predict,
- grid=self.grid,
- mode=self.maker_mode,
- )
- self.open_dist = \
- [
- utils.fix_price(open_dist[0], self.tickSize),
- utils.fix_price(open_dist[1], self.tickSize),
- utils.fix_price(open_dist[2], self.tickSize),
- utils.fix_price(open_dist[3], self.tickSize),
- ]
- self.close_dist = \
- [
- utils.fix_price(close_dist[0], self.tickSize),
- utils.fix_price(close_dist[1], self.tickSize),
- utils.fix_price(close_dist[2], self.tickSize),
- utils.fix_price(close_dist[3], self.tickSize),
- ]
- # 获取开平仓指令
- signals = dict()
- # 获取撤单信号
- signals.update(self._cancel_open())
- # 获取开仓信号 整点时刻前后不报单
- if self.check_allow_post_open():
- if self.local_time - self.post_open_time > self.post_open_interval:
- self.post_open_time = self.local_time
- signals.update(self._post_open())
- # 获取平仓信号
- signals.update(self._post_close())
- # 每隔固定时间检查超时订单
- signals.update(self._check_local_orders())
- # 更新撤单队列
- signals = self._update_in_cancel(signals)
- # 限制频率
- signals = self._check_request_limit(signals)
- # 刷新频率限制
- self._refresh_request_limit()
- # 统计请求频率
- self._update_request_num(signals)
- ##########################
- return signals
- except:
- traceback.print_exc()
- if self._is_print:self.logger.error(traceback.format_exc())
|