utils.py 17 KB

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