Automatic Stop Loss and Take Profit Orders for BitMEX Using Python
Repository With Sample Code
https://github.com/imwatsi/crypto-market-samples
Actual script on GitHub: bitmex_auto_sl_tp.py
My Profile On GitHub
You can find code used in this tutorial as well as other tools and examples in the GitHub repositories under my profile:
What Will I Learn
- Making authenticated requests to BitMEX APIs
- Using loops in different threads to maintain order and position status in memory
- Calculating stop loss and take profit price levels and placing the orders
- Implementing price steps
Requirements
- Python 3.7
- ACtive internet connection
- BitMEX account with API keys
Difficulty
Intermediate
Tutorial Content
For those who like to day/swing trade with leverage on BitMEX, this tool can help automate the process of placing stop loss and take profit orders. In this tutorial, you will learn how to write a Python script that can run in the background while you trade and automatically place stop loss and/or take profit orders for any positions you open.
Definitions of terms
If you're not familiar with trading terms used in this tutorial, these definitions might be of help. You can skip this section if you already know these terms.
- Stop Loss: an order you place to close or reduce a position you have, if the market moves against you. For example, it can be a sell order priced just below the price at which you bought in.
- Take Profit: an order you place to close or reduce your position profitably. For example selling at a price higher than what you bought for.
- LONG: a position in which you hold the asset in anticipation of a rise in price (most commonly bought with borrowed funds, when margin trading).
- SHORT: a position in which you sold assets that you have borrowed (in margin trading).
Install dependencies
This tutorial assumes you already have a Python installation on your system. You only need to install the requests
dependency for this tutorial. All other modules used should be included in the standard library that comes with your installation.
If pip is not installed on your system, use the following commands to install it:
Debian/ Ubuntu: apt install python3-pip
Fedora: dnf install python3-pip
pip is already included in Windows/MacOS installations.
Now, to install the requests
module:
On Linux:
pip3 install requests
On Windows/MacOS:
pip install requests
Obtain the API keys
You need to create API keys for your BitMEX account to use this script. To create them, click the API tab in your account and find the section labelled API key management. Make sure to select "Order" under key permissions, to give the keys permission to place orders on your account.
The process will generate two keys, ID and secret. Save these for use later.
The ID is assigned to the variable "API_KEY" in our code and secret is assigned to the variable "API_SECRET".
Writing the code
With our sole external dependency installed, we can now write the code.
Import the needed modules
import requests
import hmac
import hashlib
import json
import time
import urllib
from threading import Thread
requests to send and receive requests from the BitMEX API
hmac and hashlib handles the encryption part of authenticated requests
json helps parse the responses we get from the API and to format data we send
time to implement time delays and calculate timestamps
urllib to format some strings before sending to API
threading to initiate threaded functions that handle different aspects of the script simultaneously
Define constants and variables
Next, we will define the constants and variables.
BASE_URL = 'https://www.bitmex.com/api/v1/'
API_KEY = ''
API_SECRET = ''
STOP_LOSS = 0.004 # i.e. default = 0.4%
TAKE_PROFIT = 0.01 # i.e. default = 1%
ENABLE_STOP_LOSS = True
ENABLE_TAKE_PROFIT = True
trade_symbols = ["XBTUSD", "ETHUSD"]
positions = {
'XBTUSD': {'qty': 0},
'ETHUSD': {'qty': 0}
}
orders = {'XBTUSD': [], 'ETHUSD': []}
- API_KEY and API_SECRET store the API keys needed
- STOP_LOSS and TAKE_PROFIT set the distance from your position's entry price at which these orders are placed
- ENABLE_STOP_LOSS and ENABLE_TAKE_PROFIT toggle the individual features on and off
- trade_symbols is a list of symbols that the script works with
- positions is a dictionary that will contain position information for each market symbol
- orders is a dictionary that will contain lists of all open orders for each market symbol
Write the API functions
Next, we will write two functions that will handle two different aspects of our API interactions.
- Authenticated GET requests (for obtaining order and position data)
- Authenticated POST requests (for placing orders)
def auth_req_get(endpoint, query):
global API_SECRET, API_KEY
path = BASE_URL + endpoint
e_path = '/api/v1/' + endpoint
if query != '':
path = path + "?" + query
e_path = e_path + "?" + query
expires = int(round(time.time()) + 10)
message = str ('GET' + e_path + str(expires))
signature = hmac.new(bytes(API_SECRET, 'utf8'),\
bytes(message,'utf8'), digestmod=hashlib.sha256)\
.hexdigest()
request_headers = {
'api-expires' : str(expires),
'api-key' : API_KEY,
'api-signature' : signature,
}
while True:
resp = requests.get(path, headers=request_headers)
if resp.status_code == 200:
return json.loads(resp.content)
time.sleep(1)
def auth_req_post(endpoint, payload):
global API_KEY, API_SECRET
path = BASE_URL + endpoint
e_path = '/api/v1/' + endpoint
expires = int(round(time.time()) + 10)
payload2 = str(payload.replace(' ', ''))
message = str ('POST' + e_path + str(expires) + payload2)
signature = hmac.new(bytes(API_SECRET, 'utf8'),\
bytes(message,'utf8'), digestmod=hashlib.sha256).\
hexdigest()
request_headers = {
'Content-type' : 'application/json',
'api-expires' : str(expires),
'api-key' : API_KEY,
'api-signature' : signature,
}
resp = requests.post(path, headers=request_headers, data=payload2)
return resp
These are base level functions that interface with the BitMEX API. A signature is generated from the API_SECRET as well as the data and url info, to sign requests before sending them.
Implement price steps
BitMEX has specific requirements for placing orders. The prices, as of this writing, of each symbol need to be made in increments of $0.50 for XBTUSD and $0.05 for ETHUSD.
To implement this, we write a function that formats these prices according to each symbol's requirement.
def rounded_price(number, symbol):
if symbol == "XBTUSD":
return round(number * 2.0) / 2.0
elif symbol == "ETHUSD":
return round(number * 20.0) / 20.0
Function to place orders
We will use one function for both stop orders and take profit orders. It calculates the price according to the type of order it is and position taken. Stop loss orders go below entry price when a LONG position is open and above entry price when a SHORT position is open.
Take profit orders go above entry when in a LONG position and below when in a SHORT position.
def place_order(symbol, side, qty, ref_price, stop=False):
#wait until order reflects in dict before returning
if side == 'Buy':
if stop == True:
price = ref_price * (1+STOP_LOSS)
else:
price = ref_price * (1-TAKE_PROFIT)
elif side == 'Sell':
if stop == True:
price = ref_price * (1-STOP_LOSS)
else:
price = ref_price * (1+TAKE_PROFIT)
order_details = {
'symbol': symbol,
'side': side,
'orderQty': qty,
}
if stop == True:
order_details['ordType'] = 'Stop'
order_details['stopPx'] = rounded_price(price, symbol)
else:
order_details['price'] = rounded_price(price, symbol)
result = auth_req_post('order', json.dumps(order_details))
if result.status_code == 200:
print('Order placed successfully.')
get_positions()
get_orders()
else:
print(result.content)
Import data from account
Next we define the functions the script will use to import position and order data from the API. They make authenticated GET requests through the functions we defined above, parse the results and save them in the appropriate variables.
def get_positions():
global positions
# load positions in memory
req = auth_req_get('position', '')
for pos in req:
sym = pos['symbol']
positions[sym]['qty'] = pos['currentQty']
positions[sym]['entry_price'] = pos['avgEntryPrice']
def get_orders():
global trade_symbols, orders
# load open orders in memory
query = "filter=" + urllib.parse.quote_plus('{"open":true}')
req = auth_req_get('order', query)
for sym in trade_symbols:
lst_orders = []
for order in req:
if order['symbol'] == sym:
ord_details = {
'side': order['side'],
'o_id': order['orderID'],
'type': order['ordType']
}
lst_orders.append(ord_details)
orders[sym] = lst_orders
Define functions for threads
Now we define the functions that we will open different threads for. Each function is a loop with a time delay, to continuously import data and check for uncovered positions.
A set of booleans work to control when and if an order is made, according to the current status of the positions and active orders.
def maintain_positions():
global positions
print('Positions are now loaded...')
while True:
get_positions()
time.sleep(10)
def maintain_orders():
global orders
print('Orders are now loaded...')
while True:
get_orders()
time.sleep(10)
def cover_positions():
global positions, orders, STOP_LOSS, TAKE_PROFIT
print('Actively scanning for open positions now.')
while True:
# cover open positions that do not have stop loss / take profit
for sym in positions:
if positions[sym]['qty'] > 0: # long position entered
price = positions[sym]['entry_price']
has_tp = False
has_sl = False
for od in orders[sym]:
if od['side'] == 'Sell' and od['type'] == 'Stop':
has_sl = True
elif od['side'] == 'Sell':
has_tp = True
if has_sl == False:
if ENABLE_STOP_LOSS == True:
place_order(sym, 'Sell', abs(positions[sym]['qty']),\
price, True)
if has_tp == False:
if ENABLE_TAKE_PROFIT == True:
place_order(sym, 'Sell', abs(positions[sym]['qty']),\
price)
elif positions[sym]['qty'] < 0: # short position entered
price = positions[sym]['entry_price']
has_tp = False
has_sl = False
for od in orders[sym]:
if od['side'] == 'Buy' and od['type'] == 'Stop':
has_sl = True
elif od['side'] == 'Buy':
has_tp = True
if has_sl == False:
if ENABLE_STOP_LOSS == True:
place_order(sym, 'Buy', abs(positions[sym]['qty']),\
price, True)
Initiation code
Finally, we write code to initialize the script. This starts all three threaded functions and sets the components in motion.
if __name__ == '__main__':
Thread(target=maintain_positions).start()
Thread(target=maintain_orders).start()
Thread(target=cover_positions).start()
This brings us to the end of this tutorial. Below are screenshots of when I ran the script to cover an example position I had opened.
Terminal
BitMEX screenshot with stop loss and take profit orders placed
Thank you for your contribution @imwatsi
After reviewing your contribution, we suggest you following points:
Again a good tutorial with trade and python language.
Nice work on the explanations of your code, although adding a bit more comments to the code can be helpful as well.
It would be interesting throughout your tutorial to put more images on the results of what you are explaining.
In the next tutorial structure your tutorial better, there are parts that are a bit confusing.
Thank you for following some suggestions we put on your previous tutorial.
Thank you for your work in developing this tutorial.
Looking forward to your upcoming tutorials.
Your contribution has been evaluated according to Utopian policies and guidelines, as well as a predefined set of questions pertaining to the category.
To view those questions and the relevant answers related to your post, click here.
Need help? Chat with us on Discord.
[utopian-moderator]
Thanks @portugalcoin for your valuable feedback and for helping me improve my tutorials.
Thank you for your review, @portugalcoin! Keep up the good work!
Hey, @imwatsi!
Thanks for contributing on Utopian.
We’re already looking forward to your next contribution!
Get higher incentives and support Utopian.io!
Simply set @utopian.pay as a 5% (or higher) payout beneficiary on your contribution post (via SteemPlus or Steeditor).
Want to chat? Join us on Discord https://discord.gg/h52nFrV.
Vote for Utopian Witness!
Congratulations @imwatsi! You have completed the following achievement on the Steem blockchain and have been rewarded with new badge(s) :
You can view your badges on your Steem Board and compare to others on the Steem Ranking
If you no longer want to receive notifications, reply to this comment with the word
STOP
Vote for @Steemitboard as a witness to get one more award and increased upvotes!