Skip to content

Instantly share code, notes, and snippets.

@hokie-sam
Last active March 24, 2019 06:38
Show Gist options
  • Save hokie-sam/d29376c398bc352421ac665ebd32dc94 to your computer and use it in GitHub Desktop.
Save hokie-sam/d29376c398bc352421ac665ebd32dc94 to your computer and use it in GitHub Desktop.
import requests
import uuid
import time
import json
from random import *
import hmac
import hashlib
import urllib3
import urllib.parse
from urllib.parse import urlparse
import asyncio
import websocket
from multiprocessing import Process
import threading
import logging
import sys
import math
import statistics
# ----------------------- INTRO -------------------------
# This bot works on Bitmex's API.
# Bitmex provides a couple ways of communicating with their servers.
# The first is a REST API. This is a very basic way to talk to
# servers. There are 4 different types of requests:
# DELETE (delete a previous order)
# GET (get some info about market our your account)
# POST (place an order or make
# PUT (modify an existing order)
# These requests fall into 2 catagories:
# Authenticated
# Authenticated requests are required if you want to do stuff with
# your account. For example, place an order, check account balance, etc.
# With these requests, you must send a "header" with your request, which
# contains your "API passwords" (API Secret and API ID). These prove to
# the server that you have permission _______.
# Un-authenticated
# With un-authenticated requests, you are simply asking for info
# about the market. Thus, you do not need to need to provide your API
# passwords (API Secret and API ID), because the information you are
# requesting is not specific to your account.
# To better understand what an API request is, put the following link into your brower:
# https://www.bitmex.com/api/v1/quote
# This will bring up a page with 100 of the most recent price quotes from all of the
# contracts which Bitmex provides. This request falls into the catagory of an unauthenticated
# GET request. This is because you are GETting price data from the market, so you do not
# need to provide your API Secret and API ID.
#You will see that each of the 100 entries has 6 values...
# timestamp
# symbol
# bidSize
# bidPrice
# askPrice
# askSize
# We can narrow down the results by adding a *query* to the end of the url.
# To view only the 5 most recent price ticks, add ?count=5 to the url...
# https://www.bitmex.com/api/v1/quote?count=5
# Great. Look at the timestamp on each of the entries. You will notice that
# the most recent quotes are at the end. To reverse the order of the quotes,
# add &reverse=TRUE to the end of the url...
# https://www.bitmex.com/api/v1/quote?count=5&reverse=true
# Now say you are only interested in seeing ____
# https://www.bitmex.com/api/v1/quote?count=5&reverse=true&symbol=ETC7D
# ____
# Notice
# The trickiest part is understanding how the
# Un-comment following line if you want to see the connections logs.
# Useful if you want to make sure the HTTP connection is being kept-alive.
# logging.basicConfig(level=logging.DEBUG)
bm_id = 'YourBitmexID1234'
bm_secret = 'YourBitmexSecret1234567890'
bit_url = 'https://www.bitmex.com'
api_url = '/api/v1'
ws_url = 'wss://www.bitmex.com/realtime'
s = requests.Session()
k = 1000 # Variable which makes large numbers easier to read. Ie, 250*k instead of 250000
# ----------------------------------------------------------------
# A little function I use for debugging code.
# I include it before, within, and after all loops and conditional statements.
# Makes it easy to see where the script currently is.
def mm(mile): print(' '*(60 + mile*2) + 'MM #' + str(mile))
def slep(secs = 0.5):
'''Pauses the program for desired number of seconds.
If more than 5.0 seconds, it prints a little countdown.'''
if secs < 5.0: time.sleep(secs)
else:
for i in range(-secs, 0):
sys.stdout.write('\r{0}..'.format(str(abs(i))))
sys.stdout.flush()
time.sleep(1)
#end
# Places an order for 0 contracts. Returns the time it takes for Bitmex to respond.
# Times over 1.0 seconds indicate the server is running slow.
# The fastest time I have been able to consistantly achieve is 0.15 seconds.
# (Note: you might need to adjust the price at which the empty order is placed.)
def checktime(): T = time.time(); place('Buy', 0, 8000); return(time.time() - T)
# Prints a little banner.
def pb(words): print('\n\n ' + str(words).upper() + '\n\n')
def open_sock(which = 'book'):
'''Opens a websocket stream.
Currently it only contains the options to get the orderbook ('book'),
and the highest bid price/lowest ask price/quantity at each price ('quote').'''
global sock
paws(False)
sock = websocket.WebSocket()
sock.connect(ws_url)
print(sock.recv())
if which == 'book': sock.send(b'{"op": "subscribe", "args": ["orderBook10:XBTUSD"] }')
elif which == 'quote': sock.send(b'{"op": "subscribe", "args": ["quote:XBTUSD"] }')
slep(2)
print(sock.recv())
slep(2)
print(sock.recv())
#end
def set_que(param_dict = None):
'''If your HTTP API request has paramters, this will convert them
from a Python dictionary in the form...
my_params = {'symbol':'XBTUSD','count':50}
...to a query url in the form....
?symbol=XBTUSD&count=50
'''
global que_url
que_url = '' # If no query dictionary arguement is provided, a blank query url string is set
if isinstance(param_dict, dict):
que_url = '?' + urllib.parse.urlencode(param_dict)
#end
def set_head():
'''This updates the header which will be sent with the HTTP request.'''
my_header = {"api-key": bm_id,
"api-signature": gen_sig(),
"api-nonce": str(nonce)}
s.headers.update(my_header)
# end---------------------------------------------
def gen_sig():
global nonce
nonce = int(time.time()*1000)
my_message = VERB + api_url + com_url + que_url + str(nonce)
#print(my_message + '\n')
sig = hmac.new(bm_secret.encode(), my_message.encode(),
digestmod=hashlib.sha256).hexdigest()
return(sig)
# end----------------------------------------------------------
def printer(toprint=None):
'''Pretty prints r.content, the response from the API.
Note: r is set as a global variable with each response.'''
if toprint is None: toprint = r.content
print(json.dumps(json.loads(toprint),
sort_keys = True,
indent=4,
separators = (',', ':')))
# end------------------------------------------------------
def set_lev(my_lev):
'''Sets leverage for XBTUSD.
0 is cross-margin (no lev).
100 is max lev.'''
global VERB, com_url, r
VERB = 'POST'
com_url = '/position/leverage'
my_params = {'symbol': 'XBTUSD',
'leverage': my_lev}
set_que(my_params)
set_head()
r = s.post(url = bit_url + api_url + com_url,
params = my_params)
printer()
# end ----------------------------------------------------
def get_open():
'''Gets a list of dictionaries representing all open orders.
In each dictionary...
'orderQty': original quantity of order
'cumQty': the quantity which has been filled
'leavesQty': the quantity which has not been filled
'''
global VERB, com_url, r
temp_dict = b'{"open" : "true"}'
VERB = 'GET'
com_url = '/order'
my_params = {'symbol': 'XBTUSD',
'filter': temp_dict,
'count': '' }
set_que(my_params)
set_head()
r = s.get(url = bit_url + api_url + com_url,
params = my_params)
# end
def roundTo(This, toThis):
return(round(This/toThis)*toThis)
# Rounds <This> the nearest <toThis>.
def set_nudge():
global nudge
nudge = 0
QP = 0
Q = 0
get_open()
ordList = json.loads(r.content)
M = curMP()
if len(ordList) > 0:
for order in ordList:
dif = M - order['price'] + 0.0001
sign = (dif/abs(dif))
QP += sign * math.sqrt(abs(dif)) * order['leavesQty']
Q += order['leavesQty']
nudge = roundTo(QP/Q, 0.5) # round to nearest 0.25
#end
def num_conti():
'''Bitmex allows a maximum of 10 contigent orders at any given time.
Use this to periodically make sure you don't go over that limit'''
get_open()
total_conti = 0
order_list = json.loads(r.content)
for cur_ord in order_list:
# Contigent orders are either Triggered, or NotTriggered...
if (cur_ord['triggered'] == 'Triggered') or (cur_ord['triggered'] == 'NotTriggered'):
total_conti += 1
print('You have --> ' + str(total_conti) + ' <-- contigent orders open.')
return(total_conti)
# end ---------------------------------------------------------
def get_wallet():
'''Gets account margin info.'''
global VERB, com_url, r, wallet
VERB = 'GET'
com_url = '/user/margin'
my_params = {'currency': 'XBt'}
set_que(my_params)
set_head()
r = s.get(url = bit_url + api_url + com_url,
params = my_params)
wallet = json.loads(r.content)
print(' YOU HAVE ' + str(wallet['walletBalance']))
return(wallet['availableMargin'])
# end
def CANALL():
'''Uh oh, is your bot out of control?
This will CANcel ALL your open orders!'''
global VERB, com_url, r
VERB = 'DELETE'
com_url = '/order/all'
my_params = {'symbol':'XBTUSD'}
set_que(my_params)
set_head()
r = s.delete(url = bit_url + api_url + com_url,
params = my_params)
printer()
#end
def place(doWhat, howMuch, atPrice, cont_id = '', cont_type = '', note = '', client_id = ''):
'''Places order.'''
global VERB, com_url, r
VERB = 'POST'
com_url = '/order'
my_params = {'symbol': 'XBTUSD',
'side': doWhat, # 'Buy' or 'Sell'
'orderQty': howMuch,
'price': atPrice,
'ordType': 'Limit',
'text': note,
'clOrdLinkID': cont_id,
'clOrdID': client_id,
'contingencyType': cont_type, # OneTriggersTheOther
'execInst': 'ParticipateDoNotInitiate'} # ParticipateDoNotInitiate makes it post-only
set_que(my_params)
set_head()
r = s.post(url = bit_url + api_url + com_url,
params = my_params)
if howMuch != 0:
if 'error' in json.loads(r.content): printer(); slep(30)
# end
def bulkPair(buyAT, sellAT, quant = 30):
global VERB, com_url, r
t1 = time.time()
my_id = 'pair' + str(randint(0,10000))
SELL = {'symbol': 'XBTUSD', 'side': 'Sell',
'orderQty': quant,
'price': sellAT,
'ordType': 'Limit',
'clOrdID': my_id,}
BUY = {'symbol': 'XBTUSD', 'side': 'Buy',
'orderQty': quant,
'price': buyAT,
'ordType': 'Limit',
'clOrdID': my_id,}
VERB = 'POST'
com_url = '/order/bulk'
my_params = {'orders[0]': json.dumps(SELL).encode(),
'orders[1]': json.dumps(BUY).encode()}
set_que(my_params)
set_head()
r = s.post(url = bit_url + api_url + com_url,
params = my_params)
print(' TOTAL TIME -----------------> ' + str(time.time()-t1))
if 'error' in json.loads(r.content): printer(); slep(30)
#end
def tradeGap():
mm(1)
open_sock()
mm(2)
thread_sock()
slep(5)
mm(3)
while True:
mm(4)
paws(True)
pb('doing consolequant, checkor, and consolidator right now')
while True:
mm(4)
if get_wallet() < 200*k: slep(30); mm(5);break
else:
mm(7)
pb('have enough $$$')
if num_conti() > 8: slep(30); mm(8); break
else:
mm(9)
pb('not near max order limit')
while checktime() > 1.0: pb('s l o w'); mm(10);slep(10) # does response time stay constant?
pb('f a s t')
mm(11)
paws(False)
slep(10) # how long will it take for it to get up to date with responses???
while not scan_book(): slep(0.03)
pb(str(my_bid) + ' ' + str(my_ask) + '\n')
bulkpair(my_bid + 1, my_ask -1)
mm(12)
slep(5)
break
mm(13)
break
# if not false, place order with mybid myask
#end
def scan_book(dist = 12, avgQ = 10*k):
global my_ask, my_bid, temp_book,dif
sum_ask = 0
sum_bid = 0
temp_book = res
asks = temp_book['asks']
bids = temp_book['bids']
low_ask = asks[0][0]
high_bid = bids[0][0]
my_ask = low_ask
my_bid = high_bid
# This will stop if an early price has large quantity, but others are empty
for tick in asks:
sum_ask += tick[1]
if sum_ask/(tick[0] - low_ask + 1) < avgQ: my_ask = tick[0]
else: break
for tick in bids:
sum_bid += tick[1]
if sum_bid/(high_bid - tick[0] + 1) < avgQ: my_bid = tick[0]
else: break
dif = my_ask - my_bid
if dif > dist: print('\nDif is ------> ' + str(dif)); mm(15); slep(5); return(True)
else: return(False)
#end
def get_sock():
global res
while True:
res = json.loads(sock.recv())['data'][0]
if paws(): mm(16); slep()
#end
def thread_sock():
mm(17)
threading.Thread(target = get_sock).start()
#end
def curMP():
'''Returns the current market price (avg of lowest ask and highest bid price).
Note: you must be subscribed to the *quote* websocket.'''
temp = res
return((temp['bidPrice'] + temp['askPrice'])/2)
def tradeQuick():
mm(1)
open_sock('quote')
mm(2)
thread_sock()
slep(5)
mm(3)
while True:
mm(4)
while get_wallet() < 0.333*wallet['walletBalance']: pb('too poor'); slep(5); vizOr(); slep(30); mm(5) # 0.333*wallet['walletBalance']
mm(9)
while checktime() > 1.0: pb('s l o w'); mm(10);slep(10) # does response time stay constant?
pb('f a s t')
mm(11)
set_nudge(); pb(nudge)
myAsk = res['askPrice'] + 6 + nudge
myBid = res['bidPrice'] - 6 + nudge
bulkPair(myBid,myAsk)
print(myBid, curMP(), myAsk)
mm(12)
slep(15)
vizOr()
slep(105 + 60)
#end
def assign():
'''The *quote* websocket runs in the background.
This grabs values from the most recent quote
and sets them as global variables.'''
global tempBidP, tempAskP, tempBidQ, tempAskQ
temp = res
tempBidP = round(temp['bidPrice'])
tempAskP = round(temp['askPrice'])
tempBidQ = temp['bidSize']
tempAskQ = temp['askSize']
#end
def bulkBB(quant = 50, spread = 7.5, safe = 3.0):
global VERB, com_url, r
t1 = time.time()
assign()
rand_num = randint(1,10000)
higher_o = {'symbol': 'XBTUSD', 'side': 'Sell',
'orderQty': quant,
'price': tempAskP + safe,
'ordType': 'Limit',
'clOrdLinkID': 'aboveID' + str(rand_num),
'contingencyType': 'OneTriggersTheOther'}
high_o = {'symbol': 'XBTUSD', 'side': 'Buy',
'orderQty': quant,
'price': tempAskP - spread + safe,
'ordType': 'Limit',
'clOrdLinkID': 'aboveID' + str(rand_num),
'contingencyType': ''}
rand_num = randint(1,10000)
low_o = {'symbol': 'XBTUSD', 'side': 'Sell',
'orderQty': quant,
'price': tempBidP + spread - safe,
'ordType': 'Limit',
'clOrdLinkID': 'belowID' + str(rand_num),
'contingencyType': ''}
lower_o = {'symbol': 'XBTUSD', 'side': 'Buy',
'orderQty': quant,
'price': tempBidP - safe,
'ordType': 'Limit',
'clOrdLinkID': 'belowID' + str(rand_num),
'contingencyType': 'OneTriggersTheOther'}
VERB = 'POST'
com_url = '/order/bulk'
if marknow is 'bear': my_params = {'orders[0]': json.dumps(higher_o).encode(),
'orders[1]': json.dumps(high_o).encode()}
elif marknow is 'bull': my_params = {'orders[0]': json.dumps(low_o).encode(),
'orders[1]': json.dumps(lower_o).encode()}
elif marknow is None: my_params = {'orders[0]': json.dumps(higher_o).encode(),
'orders[1]': json.dumps(high_o).encode(),
'orders[2]': json.dumps(low_o).encode(),
'orders[3]': json.dumps(lower_o).encode()}
set_que(my_params)
set_head()
r = s.post(url = bit_url + api_url + com_url,
params = my_params)
t4 = time.time()
print(' TOTAL TIME -----------------> ' + str(t4-t1))
if 'error' in json.loads(r.content): printer()
def checkor():
global far_notouch_pairs
cur_mp = (res['askPrice'] + res['bidPrice'])/2
get_open()
orderlist = json.loads(r.content)
far_notouch_pairs = []
for first_ord in orderlist:
for sec_ord in orderlist:
if first_ord['clOrdLinkID'] == sec_ord['clOrdLinkID'] :
print('1. Same cont-ID... ',
str(first_ord['price']),
str(sec_ord['price']))
if first_ord['side'] != sec_ord['side']:
print(' 2. One buy, one sell... ',
str(first_ord['price']),
str(sec_ord['price']))
if abs(first_ord['price'] - cur_mp) > 30 :
print(' 3. They are far away... ',
str(first_ord['price']),
str(sec_ord['price']))
if (first_ord['ordStatus'] == 'New') & \
(sec_ord['triggered'] == 'NotTriggered') :
print(' 4. Both are untouched. Adding to list... ',
str(first_ord['price']),
str(sec_ord['price']))
far_notouch_pairs.append(first_ord)
far_notouch_pairs.append(sec_ord)
if len(far_notouch_pairs) != 0:
to_delete = ''
for delete_this in far_notouch_pairs:
print('Going to cancel order.... ' + delete_this['orderID'])
to_delete += delete_this['orderID'] + ','
slep()
cancelor(to_delete[:-1])
slep(5)
# end
def tradeLrgGap():
open_sock('quote')
slep(5)
quik_sock()
slep(10)
while True:
while checktime() > 1.0: pb('s l o w . . .'); slep(5)
pb(' c h e c k')
while True:
temp_gap = gap
ask = temp_gap['askPrice']
bid = temp_gap['bidPrice']
if bid - ask > 5: print(bid-ask); break
#end
def tradeTrig(first, second):
mm(1)
open_sock('quote')
mm(2)
thread_sock()
slep(5)
mm(3)
while True:
mm(4)
pb('doing consolequant, checkor, and consolidator right now')
while True:
mm(4)
if get_wallet() < 100*k: slep(30); mm(5);break
else:
mm(9)
while checktime() > 0.50: pb('s l o w'); mm(10);slep(5) # does response time stay constant?
pb('f a s t')
mm(11)
bulkBB(marknow = first, safe = second)
mm(12)
slep(60)
break
#end
def vizOr():
MP = curMP()
get_open()
slep(2)
maxChar = 160
pList = []
strBuy =''; strSell = ''
if len(json.loads(r.content)) > 1:
for order in json.loads(r.content): pList.append(order['price'])
pList.append(MP)
pList.sort()
minP = min(pList); maxP = max(pList); pRange = maxP - minP
for i in range(0, len(pList) - 1):
if pList[i] < MP:
strBuy = strBuy + '+' + ' ' * round(maxChar*(pList[i + 1] - pList[i])/pRange)
elif pList[i] == MP:
strSell = strSell + str(MP) + ' ' + ' ' * round(maxChar*(pList[i + 1] - pList[i])/pRange)
elif pList[i] > MP:
strSell = strSell + '-' + ' ' * round(maxChar*(pList[i + 1] - pList[i])/pRange)
if minP < MP: strBuy = ' ' + str(math.floor(minP)) + ' ' + strBuy
if maxP > MP: strSell = strSell + '-' + ' ' + str(math.ceil(maxP))
print('\n'*2 + strBuy + strSell + '\n'*2)
else: pb('no open orders')
#end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment