utils.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528
  1. import traceback
  2. import model
  3. import toml, time, random
  4. import sys, aiohttp
  5. import socket
  6. import requests
  7. from decimal import Decimal
  8. import gzip
  9. from Crypto.Cipher import AES
  10. import os
  11. import base64
  12. import json
  13. parentdir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
  14. sys.path.insert(0, parentdir)
  15. ############### 全局配置
  16. VERSION = "2022-04-18"
  17. CHILD_RUN_SECOND = 60 * 60 * 24 # child process max run time per loop
  18. EARLY_STOP_SECOND = 60 * 60 * 2 # child early stop min check time
  19. BACKTEST_PREHOT_SECOND = 60 * 30 # backtest pre hot time
  20. DUMMY_RUN_SECOND = 60 * 60 * 12 # dummy process max run time per loop
  21. DUMMY_EARLY_STOP_SECOND = 60 * 60 # dummy process max run time per loop
  22. POST_SIDE_LIMIT = [0] # post side limit
  23. MARKET_DELAY_LIMIT = 30000 # market update delay limit threhold unit:ms
  24. GRID = 1
  25. STOPLOSS = 0.02
  26. GAMMA = 0.999
  27. ###### market行情数据长度 标准化n档深度+6档成交信息 ######
  28. LEVEL = 1
  29. TRADE_LEN = 2 # 最高 最低 成交价
  30. LEN = LEVEL * 4 + TRADE_LEN # 总长度
  31. BP_INDEX = LEVEL * 0
  32. BQ_INDEX = LEVEL * 0 + 1
  33. AP_INDEX = LEVEL * 2
  34. AQ_INDEX = LEVEL * 2 + 1
  35. MAX_FILL_INDEX = LEVEL * 4 + 0
  36. MIN_FILL_INDEX = LEVEL * 4 + 1
  37. # BUY_Q_INDEX = LEVEL * 4 + 2
  38. # BUY_V_INDEX = LEVEL * 4 + 3
  39. # SELL_Q_INDEX = LEVEL * 4 + 4
  40. # SELL_V_INDEX = LEVEL * 4 + 5
  41. #### depth/trade effient range #####
  42. EFF_RANGE = 0.001
  43. ### init backtest delay ###
  44. BACKTEST_DELAY = 0.15
  45. global base_cid
  46. base_cid = 0
  47. def get_cid(broker=None):
  48. global base_cid
  49. base_cid += 1
  50. if base_cid > 999:
  51. base_cid = 0
  52. cid = str(time.time())[4:10] + str(random.randint(1, 999)) + str(base_cid)
  53. if broker:
  54. cid = broker + cid
  55. return cid
  56. def csv_to_gz_and_remove():
  57. def List_files(filepath, substr):
  58. X = []
  59. Y = []
  60. for path, subdirs, files in sorted(os.walk(filepath), reverse=True):
  61. for name in files:
  62. X.append(os.path.join(path, name))
  63. Y = [line for line in X if substr in line]
  64. return Y
  65. for file in List_files('./', '.csv'):
  66. if '.gz' not in file:
  67. data = open(file, 'rb').read()
  68. with gzip.open(file + '.gz', 'a') as zip:
  69. zip.write(data)
  70. zip.close()
  71. os.remove(file)
  72. def get_params(fname):
  73. # 读取配置
  74. try:
  75. params = toml.load(fname)
  76. except:
  77. f = open(fname)
  78. data = f.read()
  79. text = base64.b64decode(data)
  80. cryptor = AES.new(key=bytes("qFHFPv6MugrSTkEsWFs8wCDg3iC6!er%".encode()), mode=AES.MODE_ECB)
  81. plain_text = cryptor.decrypt(text)
  82. paddingLen = plain_text[len(plain_text) - 1]
  83. msg = plain_text[0:-paddingLen]
  84. msg = msg.decode()
  85. params = toml.loads(msg)
  86. p = model.Config()
  87. # 账号昵称
  88. p.account_name = params['account_name'] if 'account_name' in params else 'Unknown Account'
  89. # api
  90. p.access_key = params['access_key'].replace(" ", "") if 'access_key' in params else '***'
  91. p.secret_key = params['secret_key'].replace(" ", "") if 'secret_key' in params else '***'
  92. p.pass_key = params['pass_key'].replace(" ", "") if 'pass_key' in params else 'qwer1234'
  93. # 经纪商id
  94. broker_id_from_config = params['broker_id'] if 'broker_id' in params else ""
  95. p.broker_id = get_broker_id(broker_id_from_config, params['exchange'])
  96. # 交易盘口
  97. p.exchange = params['exchange'] if 'exchange' in params else ""
  98. # 交易品种
  99. p.pair = params['pair'] if 'pair' in params else ""
  100. # 调试模式开关
  101. p.debug = params['debug'] if 'debug' in params else "False"
  102. # 开仓间距,0.01代表1%
  103. p.open = params['open'] if 'open' in params else "0.002"
  104. # 平仓间距,0.01代表1%
  105. p.close = params['close'] if 'close' in params else "0.0002"
  106. # 监听端口
  107. p.server_port = params['server_port'] if 'server_port' in params else 6000
  108. # 杠杆大小
  109. p.leverrate = float(params['leverrate']) if 'leverrate' in params else 1.0
  110. # 参考盘口
  111. p.refexchange = params['refexchange'].replace('[', '').replace(']', '').replace("'", '').replace(" ", "").split(
  112. ',') if "refexchange" in params else ""
  113. # 参考品种
  114. p.refpair = params['refpair'].replace('[', '').replace(']', '').replace("'", '').replace(" ", "").split(
  115. ',') if "refpair" in params else ""
  116. # 网络代理
  117. p.proxy = params['proxy'] if 'proxy' in params else None # 仅在win下有效
  118. # 账户资金使用比例
  119. p.used_pct = params['used_pct'] if 'used_pct' in params else "0.9"
  120. # discord播报地址
  121. p.webhook = params[
  122. 'webhook'] if 'webhook' in params else "https://discord.com/api/webhooks/907870708481265675/IfN4GqH4fj8HWS_FecH3Lrc2qtRyqsCHsSJVLFHlxY8ioHprfdxIMUNAfqkZZ6opzVEP"
  123. # 默认第n参考盘口
  124. p.index = params['index'] if 'index' in params else 0
  125. # 止损比例 0.02 = 2%
  126. p.stoploss = params['stoploss'] if 'stoploss' in params else STOPLOSS
  127. # 平滑系数 默认0.999
  128. p.gamma = params['gamma'] if 'gamma' in params else GAMMA
  129. # 分批建仓功能 小资金建议1 大资金建议3
  130. p.grid = params['grid'] if 'grid' in params else GRID
  131. # 实时调参开关 会有巨大性能损耗
  132. p.backtest = params['backtest'] if 'backtest' in params else 1
  133. # 保存实时行情 会有巨大性能损耗
  134. p.save = params['save'] if 'save' in params else 0
  135. p.place_order_limit = params['place_order_limit'] if 'place_order_limit' in params else 0 # 允许的每秒下单次数
  136. # 是否启用colocation技术
  137. p.colo = params['colo'] if 'colo' in params else 0
  138. # 是否启用fast行情 会增加性能开销
  139. p.fast = params['fast'] if 'fast' in params else 1
  140. # 选择指定的私有ip进行网络通信 默认0 用于多网卡多ip的实例
  141. p.ip = params['ip'] if 'ip' in params else 0
  142. # 合约不允许holdcoin持有底仓币
  143. if "spot" in p.exchange:
  144. p.hold_coin = params['hold_coin'] if 'hold_coin' in params else 0.0
  145. else:
  146. p.hold_coin = 0.0
  147. # 是否开启日志记录 会有一定性能损耗
  148. p.log = params['log'] if 'log' in params else 1
  149. #### 特殊情况处理
  150. if p.exchange == 'binance_usdt_swap':
  151. if p.pair in ['shib_usdt', 'xec_usdt', 'bttc_usdt']:
  152. p.pair = "1000" + p.pair
  153. ref_num = len(p.refexchange)
  154. for i in range(ref_num):
  155. if p.refexchange[i] == 'binance_usdt_swap':
  156. if p.refpair[i] in ['shib_usdt', 'xec_usdt', 'bttc_usdt']:
  157. p.refpair[i] = "1000" + p.refpair[i]
  158. ####
  159. print('debu11g')
  160. print(p)
  161. return p
  162. def get_broker_id(broker_id, exchange_name):
  163. '''处理brokerid特殊情况'''
  164. if 'binance' in exchange_name:
  165. return broker_id
  166. elif 'gate' in exchange_name:
  167. return "t-"
  168. else:
  169. return ""
  170. # 报单频率限制等级
  171. BASIC_LIMIT = 100
  172. GATE_SPOT_LIMIT = 10.0
  173. GATE_USDT_SWAP_LIMIT = 100.0
  174. KUCOIN_SPOT_LIMIT = 15.0
  175. KUCOIN_USDT_SWAP_LIMIT = 10.0
  176. BINANCE_USDT_SWAP_LIMIT = 5.0
  177. BINANCE_SPOT_LIMIT = 2.0
  178. COINEX_SPOT_LIMIT = 20.0
  179. COINEX_USDT_SWAP_LIMIT = 20.0
  180. OKEX_USDT_SWAP_LIMIT = 30.0
  181. BITGET_USDT_SWAP_LIMIT = 10.0
  182. BYBIT_USDT_SWAP_LIMIT = 1.0
  183. MEXC_SPOT_LIMIT = 333
  184. RATIO = 4.0
  185. def get_limit_requests_num_per_second(exchange, limit=0):
  186. '''每秒请求频率'''
  187. if limit != 0:
  188. return limit * RATIO
  189. elif exchange == "gate_spot":
  190. return GATE_SPOT_LIMIT * RATIO
  191. elif exchange == "gate_usdt_swap": # 100/s
  192. return GATE_USDT_SWAP_LIMIT * RATIO
  193. elif exchange == "kucoin_spot": # 15/s
  194. return KUCOIN_SPOT_LIMIT * RATIO
  195. elif exchange == "kucoin_usdt_swap":
  196. return KUCOIN_USDT_SWAP_LIMIT * RATIO
  197. elif exchange == "binance_usdt_swap":
  198. return BINANCE_USDT_SWAP_LIMIT * RATIO
  199. elif exchange == "binance_spot":
  200. return BINANCE_SPOT_LIMIT * RATIO
  201. elif exchange == "coinex_spot":
  202. return COINEX_SPOT_LIMIT * RATIO
  203. elif exchange == "coinex_usdt_swap":
  204. return COINEX_USDT_SWAP_LIMIT * RATIO
  205. elif exchange == "okex_usdt_swap":
  206. return OKEX_USDT_SWAP_LIMIT * RATIO
  207. elif exchange == "bitget_usdt_swap":
  208. return BITGET_USDT_SWAP_LIMIT * RATIO
  209. elif exchange == "bybit_usdt_swap":
  210. return BYBIT_USDT_SWAP_LIMIT * RATIO
  211. elif exchange == "mexc_spot":
  212. return MEXC_SPOT_LIMIT * RATIO
  213. else:
  214. print("限频规则未找到")
  215. return BASIC_LIMIT * RATIO
  216. def get_limit_order_requests_num_per_second(exchange, limit=0):
  217. '''每秒下单请求频率'''
  218. if limit != 0:
  219. return limit
  220. elif exchange == "gate_spot": # 10/s
  221. return GATE_SPOT_LIMIT
  222. elif exchange == "gate_usdt_swap": # 100/s
  223. return GATE_USDT_SWAP_LIMIT
  224. elif exchange == "kucoin_spot": # 15/s
  225. return KUCOIN_SPOT_LIMIT
  226. elif exchange == "kucoin_usdt_swap": # 10/s
  227. return KUCOIN_USDT_SWAP_LIMIT
  228. elif exchange == "binance_usdt_swap": # 5/s
  229. return BINANCE_USDT_SWAP_LIMIT
  230. elif exchange == "binance_spot": # 2/s
  231. return BINANCE_SPOT_LIMIT
  232. elif exchange == "coinex_spot": # 20/s
  233. return COINEX_SPOT_LIMIT
  234. elif exchange == "coinex_usdt_swap": # 20/s
  235. return COINEX_USDT_SWAP_LIMIT
  236. elif exchange == "okex_usdt_swap": # 30/s
  237. return OKEX_USDT_SWAP_LIMIT
  238. elif exchange == "bitget_usdt_swap": # 10/s
  239. return BITGET_USDT_SWAP_LIMIT
  240. elif exchange == "bybit_usdt_swap": # 2/s
  241. return BYBIT_USDT_SWAP_LIMIT
  242. elif exchange == "mexc_spot": # 2/s
  243. return MEXC_SPOT_LIMIT
  244. else:
  245. print("限频规则未找到")
  246. return BASIC_LIMIT
  247. def dist_to_weight(price, mp, eff_range=EFF_RANGE):
  248. '''
  249. 距离转换为权重
  250. '''
  251. dist = abs(price - mp) / mp
  252. weight = 1 - clip(dist / eff_range, 0.0, 0.95)
  253. weight = weight if weight > 0 else 0
  254. return weight
  255. def change_params(fname, params, changes):
  256. # 更改配置
  257. for i in changes:
  258. params[i[0]] = i[1]
  259. with open(f"{fname}", "w") as f:
  260. toml.dump(params, f)
  261. def show_memory(unit='B', threshold=1024):
  262. '''查看变量占用内存情况
  263. :param unit: 显示的单位,可为`B`,`KB`,`MB`,`GB`
  264. :param threshold: 仅显示内存数值大于等于threshold的变量
  265. '''
  266. from sys import getsizeof
  267. scale = {'B': 1, 'KB': 1024, 'MB': 1048576, 'GB': 1073741824}[unit]
  268. msg = '内存占用情况: \n'
  269. for i in list(globals().keys()):
  270. memory = eval("getsizeof({})".format(i)) // scale
  271. if memory >= threshold:
  272. msg += f'{i} {memory} {unit}\n'
  273. print(msg)
  274. return msg
  275. def clip(num, _min, _max):
  276. if num > _max: num = _max
  277. if num < _min: num = _min
  278. return num
  279. async def ding(msg, at_all, webhook, proxy=None):
  280. '''
  281. 发送钉钉消息
  282. '''
  283. header = {
  284. "Content-Type": "application/json",
  285. "Charset": "UTF-8"
  286. }
  287. embed = {
  288. "title": "策略通知",
  289. "description": msg
  290. }
  291. message = {
  292. "content": "大吉大利 今晚吃鸡",
  293. "username": "千千喵",
  294. "embeds": [
  295. embed
  296. ],
  297. }
  298. message_json = json.dumps(message)
  299. if 'win' in sys.platform:
  300. proxy = proxy
  301. else:
  302. proxy = None
  303. async with aiohttp.ClientSession() as session:
  304. await session.post(url=webhook, data=message_json, headers=header, proxy=proxy, timeout=10)
  305. def _get_params(url, proxy, params):
  306. '''更新参数'''
  307. import requests
  308. try:
  309. res = requests.post(url=url, json=params, timeout=10)
  310. return json.loads(res.text)
  311. except:
  312. traceback.print_exc()
  313. return []
  314. async def _post_params(url, proxy, params):
  315. '''更新参数'''
  316. try:
  317. if 'win' in sys.platform:
  318. proxy = proxy
  319. else:
  320. proxy = None
  321. async with aiohttp.ClientSession() as session:
  322. res = await session.post(url=url, proxy=proxy, data=params, timeout=10)
  323. data = await res.text()
  324. print(data)
  325. return data
  326. except:
  327. print(traceback.format_exc())
  328. return "post_params error"
  329. return None
  330. def get_ip():
  331. try:
  332. s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  333. s.connect(('8.8.8.8', 80))
  334. ip = s.getsockname()[0]
  335. finally:
  336. s.close()
  337. return ip
  338. def check_auth():
  339. print("*** 检查使用权限1 ***")
  340. ip = get_ip()
  341. print(f"当前IP {ip}")
  342. white_list = requests.get(f"http://158.247.204.56:7777/ip_list")
  343. if ip in white_list:
  344. print("当前IP位于白名单中")
  345. else:
  346. print("@@@ 本版本仅限指定IP白名单运行 @@@")
  347. os._exit(0)
  348. print("*** 符合要求 ***")
  349. def check_time():
  350. print("*** 检查使用权限2 ***")
  351. if time.time() > int(time.mktime(time.strptime('2021-11-17 00:00:00', "%Y-%m-%d %H:%M:%S"))):
  352. print("@@@ 此版本目前已过试用期 @@@")
  353. os._exit(0)
  354. print("*** 符合要求 ***")
  355. def num_to_str(num, d):
  356. if d >= 1.0:
  357. return "%d" % num
  358. elif d in [0.1, 0.5]:
  359. return "%.1f" % num
  360. elif d in [0.01, 0.05]:
  361. return "%.2f" % num
  362. elif d in [0.001, 0.005]:
  363. return "%.3f" % num
  364. elif d in [0.0001, 0.0005]:
  365. return "%.4f" % num
  366. elif d in [0.00001, 0.00005]:
  367. return "%.5f" % num
  368. elif d in [0.000001, 0.000005]:
  369. return "%.6f" % num
  370. elif d in [0.0000001, 0.0000005]:
  371. return "%.7f" % num
  372. elif d in [0.00000001, 0.00000005]:
  373. return "%.8f" % num
  374. elif d in [0.000000001, 0.000000005]:
  375. return "%.9f" % num
  376. elif d in [0.0000000001, 0.0000000005]:
  377. return "%.10f" % num
  378. else:
  379. return str(num)
  380. def num_to_decimal(num):
  381. '''根据小数点位数获取精度'''
  382. num = str(num)
  383. if '.' not in num:
  384. return 0
  385. elif '.' == num[-2]:
  386. return 1
  387. elif '.' == num[-3]:
  388. return 2
  389. elif '.' == num[-4]:
  390. return 3
  391. elif '.' == num[-5]:
  392. return 4
  393. elif '.' == num[-6]:
  394. return 5
  395. elif '.' == num[-7]:
  396. return 6
  397. elif '.' == num[-8]:
  398. return 7
  399. elif '.' == num[-9]:
  400. return 8
  401. elif '.' == num[-10]:
  402. return 9
  403. elif '.' == num[-11]:
  404. return 10
  405. else:
  406. return 11
  407. def fix_amount(amount, stepSize):
  408. '''修补数量向下取整'''
  409. return float(
  410. Decimal(str(amount)) // Decimal(str(stepSize)
  411. ) \
  412. * Decimal(str(stepSize)))
  413. # return float(Decimal(str(amount)).quantize(Decimal(str(stepSize)), ROUND_FLOOR))
  414. def fix_price(price, tickSize):
  415. '''修补价格四舍五入'''
  416. return float(
  417. round(Decimal(str(price)) / Decimal(str(tickSize))
  418. ) \
  419. * Decimal(str(tickSize)))
  420. # return float(Decimal(str(price)).quantize(Decimal(str(tickSize)), ROUND_HALF_UP))
  421. def timeit(func):
  422. def wrapper(*args, **kwargs):
  423. nowTime = time.time()
  424. res = func(*args, **kwargs)
  425. spend_time = time.time() - nowTime
  426. spend_time = round(spend_time * 1e6, 3)
  427. print(f'{func.__name__} 耗时 {spend_time} us')
  428. return res
  429. return wrapper
  430. def get_backtest_set(base=""):
  431. '''生成预设参数'''
  432. # 开仓距离不能太近必须超过大部分价格tick运动的距离
  433. open_list = [
  434. 0.0055,
  435. 0.0045,
  436. 0.0035,
  437. 0.0030,
  438. 0.0025,
  439. 0.0020,
  440. 0.0015,
  441. ]
  442. close_dict = dict()
  443. for open in open_list:
  444. close_dict[open] = [
  445. open * 0.1,
  446. open * 0.2,
  447. ]
  448. alpha_list = [0.0]
  449. return open_list, close_dict, alpha_list
  450. def get_local_ip_list():
  451. '''获取本地ip'''
  452. import netifaces as ni
  453. ipList = []
  454. # print('检测服务器网络配置')
  455. for dev in ni.interfaces():
  456. print('dev:', dev)
  457. if 'ens' in dev or 'eth' in dev or 'enp' in dev:
  458. # print(ni.ifaddresses(dev))
  459. for i in ni.ifaddresses(dev)[2]:
  460. ip = i['addr']
  461. print(f"检测到私有ip:{ip}")
  462. if ip not in ipList:
  463. ipList.append(ip)
  464. print(f"当前服务器私有ip为{ipList}")
  465. return ['127.0.0.1']
  466. if __name__ == '__main__':
  467. gamma = float(0.5)
  468. kappa = float(2)
  469. factor = 1 + gamma / kappa
  470. _optimal_spread = 2 * Decimal(factor).ln()
  471. print(_optimal_spread)