Automatic Stop Loss and Take Profit Orders for BitMEX Using Python

in #utopian-io6 years ago

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:

https://github.com/imwatsi

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

python-crypto-market-tutorials.jpg

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.

img_terminal.png
Terminal

img_position.png
BitMEX screenshot with stop loss and take profit orders placed

Other Tutorials In The Series

Sort:  

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 received more than 6000 upvotes. Your next target is to reach 7000 upvotes.

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!