-
-
Save luapz/3eb089bcdb3bb40096bbefc7422d6d68 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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