Trading Bots#

Overview#

The algorithmic trading platform is built using an event-driven architecture. Events from the market trigger callback functions. These callbacks can either be private or public updates from the market. Private events include updates to the resting limit orders you have placed in the market, or updates to your positions resulting from an order being filled. Public events include updates to the top-of-book bid and ask quotes, and trades that have occurred due to orders being matched.

A template for an algorithmic trading strategy is shown below. In this template the strategy will generate "signals" which indicate what the model believes to be the fair value of the market. In instances when the signal generated fair value differs significantly from the actual market price, the model will then trigger buy or sell orders. These orders can either be passive (limit) or aggressive (market) reflecting the urgency that the position is sought.

ProfitView provides an interface to develop algorithmic trading strategies using this framework. Each component in the above diagram have callback functions, and API methods available to allow one to write automated strategies that trade the market.

We will describe these component callbacks and methods in detail. Before starting, it is recommended that you are comfortable with Python 3.9, as development of a strategy requires writing in this language. If you are new to programming or are coming from another language we can recommend the following online CodeAcademy Course which covers the basics of Python 3.

Getting Started#

To get started you will need to sign up to ProfitView and be on either a Hobbyist ($29/month), Active Trader ($59/month), or Professional ($299/month) plan. After joining a plan the Trading Bots interface will become available, with a Python code editor.

When in the code editor create a new file and will notice a template script is provided which includes the following class:

class Trading(Link)

Note: trading bot strategies are unable to run if this class is not defined in your file.

We describe below various event-driven callbacks and the API reference for this class.

The base class Link has a number of properties and methods available.

Properties#

self.now

Returns: the current UTC time (datetime.datetime).

self.unix_now

Returns: the current unix time i.e. seconds since midnight UTC on 1 Jan 1970 (int).

self.epoch_now

Returns: the current the epoch time i.e. milliseconds since midnight UTC on 1 Jan 1970 (int).

self.second

Returns: the current second (time elapsed from start of minute) in microsecond precision (float).

self.iso_now

Returns: the current UTC time in ISO 8601 format (str).

self.background_threads

Returns: all live background threads as a dictionary with the key "thread id" and value "thread name" (dict).

Methods#

self.candle_bin(value, level)

Event Callbacks#

Private updates include an update on the order placed on an exchange or confirmation that an order has been filled. These updates are used to manage risk and order placement logic.

Public updates include trades and top-of-book quote market activity for the symbols that have been subscribed to for the strategy that is currently running. These updates are used to generate and update the trading signals relating to a strategy.

Each callback is a function with the following arguments

Argument Type Description
src str Exchange identifier e.g. bitmex. See glossary for supported exchanges
sym str Symbol of event update XBTUSD
data dict Event data depending on particular callback

Order Update (private)#

Receive private order updates from all connected exchanges.

def order_update(self, src, sym, data)

Example Event Data

data = {
    "venue": "BitMEX",
    "side": "Buy",
    "order_price": 20500.0,
    "order_size": 1000.0,
    "remain_size": 1000.0,
    "order_type": "LIMIT",
    "time": 1678320346997,
    "order_id": "e320fbb0-c823-4d59-a9e9-d9a4ec1e8861"
}
Param Type Description
venue str Name of your connected exchange as set in exchange settings
side str Side of order, can be either "Buy" or "Sell"
order_price float Price of an order placement typically "LIMIT" orders
order_size float Submitted size of a currently open order or just closed order
remain_size float Remaining size of a currently open or just closed order
order_type str Type of order typically "LIMIT" or "MARKET"
time int Integer unix time: number of milliseconds since the epoch 1 Jan 1970
order_id str Unique order id provided by exchange used to make order updates

Notes:

  • order_id will always be returned in the order_update payload
  • If the size of an order has been amended, order_price will not be in the data payload
  • If the price of an order has been amended, order_size and remain_size will not be in the data payload

Fill Update (private)#

Receive private trade fill updates from all connected exchanges.

def fill_update(self, src, sym, data)

Example Event Data

data = {
    "venue": "BitMEX"
    "side": "Sell",
    "fill_price": 21638.5,
    "fill_size": 100.0,
    "order_type": "MARKET",
    "time": 1678361696643,
    "order_id": "0ffd735b-8371-45dc-9085-b0883c17a23b",
    "trade_id": "1e0fb276-9302-4bd7-8080-67fd1a6c7069"
}
Param Type Description
venue str Name of your connected exchange as set in exchange settings
side str Side of order, can be either "Buy" or "Sell" only
fill_price float Price for which an order has filled, received in a position update
fill_size float Size of an order that has been filled, received in a position update
order_type str Type of order typically "LIMIT" or "MARKET"
order_id str Unique order id provided by exchange used to make order updates
trade_id str Unique trade id provided by exchange received in a position update
time int Integer unix time: number of milliseconds since the epoch 1 Jan 1970

Quote Update (public)#

Receive public market top-of-book quote updates for all subscribed symbols.

def quote_update(self, src, sym, data)

Example Event Data

data = {
    "bid": [21601, 170000],
    "ask": [21601.5, 223000],
    "time": 1678364572949
}
Param Type Description
bid list Two item list consisting of bid price and bid size
ask list Two item list consisting of ask price and ask size
time int Integer unix time: number of milliseconds since the epoch 1 Jan 1970

Trade Update (public)#

Receive public market trade updates for all subscribed symbols.

def trade_update(self, src, sym, data)

Example Event Data

data = {
    "side": "Buy",
    "price": 21611.5,
    "size": 200.0,
    "time": 1678364683876
}
Param Type Description
side str Side of trade, can be either "Buy" or "Sell" only
price float Price at which the trade occurred
size float Size of the trade which occurred
time int Integer unix time: number of milliseconds since the epoch 1 Jan 1970

Exchange API#

Trading bots interact with their connected exchange accounts using a common API. This includes: getting candle data, open orders, current positions; creating limit and market orders; cancelling and amending exisiting orders.

Each Exchange API call will return a dict object with the following keys

Param Type Description
src str Exchange identifier - see glossary for supported exchanges
venue str Name of your connected exchange as set in exchange settings
error dict Exchange request error - returns None if no error - example below
data list or dict Payload response from calling exchange endpoint
rate_limits dict Remaining exchange API rate limits - examples in endpoints

Rate Limits#

This dict object will contain the following keys:

  • remaining: the remaining exchange API credits; if this number gets too low, the trading bot automatically backs off exchange requests to avoid a 429 Too Many Requests error.
  • reset: the unix time in seconds when the rate limits will be reset; like remaining this is set by the exchange

Example

"rate_limits": {
    "remaining": 42,
    "reset": 1681187280
},

Error#

If this is not None it will be a dict object containing the following keys:

  • type: the type of API error; this can be one of:
    • "api_error": there was an error with the request payload or exchange; an exception will be thrown in your trading bot in this instance
    • "rate_limits": too many exchange API requests; handle this by retrying your request after the reset value specified in rate_limits; no exception will be thrown in your trading bot
  • message: more information about the error to help with debugging

Example

"error": {
    "type": "api_error",
    "message": "The symbol 'DOGGUSD' does not exist"
}

Fetch Candles#

self.fetch_candles(venue, sym=sym, level='1m', since=None)

Fetch open, high, low, close, volume (OHLCV) candle data for symbol from an exchange account.

Param Type Required Description
venue str Name of exchange API key to make call with e.g. "BitMEX"
sym str Symbol for which candle data is required
level str Time frame for candle data e.g. "1m", "15m", "1h", "1d"
since int Timestamp in milliseconds of the earliest candle to fetch

Example Response

{
    "src": "bitmex",
    "venue": "BitMEX",
    "error": None,
    "data": [
        {
            "open": 30140.0,
            "high": 30138.5,
            "low": 30124.5,
            "close": 30132.0,
            "volume": 100300.0,
            "time": 1681187280000,
        },
    ],
    "rate_limits": {
        "remaining": 56,
        "reset": 1681187280
    },
}
Param Type Description
open float Open price for time interval
high float Highest price in the time interval
low float Lowest price in the time interval
close float Close price for time interval
volume float Total volume traded in the time interval
time int Unix time of beginning of time interval

Fetch Balances#

self.fetch_balances(venue)

Fetch all wallet balances for an exchange account.

Param Type Required Description
venue str Name of exchange API key to make call with e.g. "BitMEX"

Example Response

{
    "src": "bitmex",
    "venue": "BitMEX",
    "error": None,
    "data": [
        {
            "asset": "USDT",
            "amount": 4250.0
        },
        {
            "asset": "BMEX",
            "amount": 10.5
        },
        {
            "asset": "BTC",
            "amount": 0.56283
        }
    ],
    "rate_limits": {
        "remaining": 56,
        "reset": 1681187280
    },
}
Param Type Description
asset str Asset symbol of wallet balance
amount float Wallet balance amount

Fetch Open Orders#

self.fetch_open_orders(venue)

Fetch all open orders for an exchange account.

Param Type Required Description
venue str Name of exchange API key to make call with e.g. "BitMEX"

Example Response

{
    "src": "bitmex",
    "venue": "BitMEX",
    "error": None,
    "data": [
        {
            "sym": "XBTUSD",
            "side": "Buy",
            "order_price": 18650.0,
            "order_size": 800.0,
            "remain_size": 300.0,
            "order_type": "LIMIT",
            "time": 1681264318158,
            "order_id": "46f383f8-4a94-4b47-8b0a-e6a5869bf201",
        }
    ],
    "rate_limits": {
        "remaining": 56,
        "reset": 1681264328
    },
}
Param Type Description
sym str Symbol of open order
side str Side of order, can be either "Buy" or "Sell" only
order_price float Price at which an order has been placed
order_size float Submitted size of currently open order
remain_size float Remaining size of currently open order
order_type str Type of order, typically "LIMIT"
time int Integer unix time: number of milliseconds since the epoch 1 Jan 1970
order_id str Unique order id provided by exchange used to make order updates

Fetch Open Positions#

self.fetch_positions(venue)

Fetch all open positions for an exchange account.

Param Type Required Description
venue str Name of exchange API key to make call with e.g. "BitMEX"

Example Response

{
    "src": "bitmex",
    "venue": "BitMEX",
    "error": None,
    "data": [
        {
            "sym": "XBTUSD",
            "side": "Buy",
            "entry_price": 21638.5,
            "liq_price": 8453.0,
            "pos_size": 100.0,
            "time": 1681264314158
        }
    ],
    "rate_limits": {
        "remaining": 56,
        "reset": 1681264334
    },
}
Param Type Description
sym str Symbol of open position
side str Side of open position, can be either "Buy" or "Sell" only
entry_price float Average price at which a position has been entered into
liq_price float Liquidation price, only returned for swap, futures, margin positions
pos_size float Size of current position for the given instrument
time int Unix timestamp for which the position data was returned

Create Limit Order#

self.create_limit_order(venue, sym=sym, side=side, size=size, price=price)

Create a limit order for an exchange account.

Param Type Required Description
venue str Name of exchange API key to make call with e.g. "BitMEX"
sym str Symbol of order to be submitted
side str Side of order, can be either "Buy" or "Sell" only
size float Size of order to be placed
price float Price of order to be placed

Example Response

{
    "src": "bitmex",
    "venue": "BitMEX",
    "error": None,
    "data": {
        "sym": "XBTUSD",
        "side": "Buy",
        "order_price": 25600.0,
        "order_size": 200.0,
        "remain_size": 200.0,
        "order_type": "LIMIT",
        "time": 1681262857139,
        "order_id": "f34afdeb-0c14-4864-9838-7bd914c59b98",
    },
    "rate_limits": {
        "remaining": 56,
        "reset": 1681264334
    },
}
Param Type Description
sym str Symbol of order that has been placed
side str Side of order that has been placed
order_price float Price of order that has been placed
order_size float Order size of submitted order
remain_size float Remaining size of a submitted order
order_type str Type of order placed, only "LIMIT" orders are supported
time int Unix timestamp the order was entered into the order book
order_id str Unique order id of order that has been placed

Create Market Order#

self.create_market_order(venue, sym=sym, side=side, size=size)

Create a market order for an exchange account.

Param Type Required Description
venue str Name of exchange API key to make call with e.g. "BitMEX"
sym str Symbol of order to be submitted
side str Side of order, can be either "Buy" or "Sell" only
size float Size of order to be placed
price float Price of order to be placed

Example Response

{
    "src": "bitmex",
    "venue": "BitMEX",
    "error": None,
    "data": {
        "sym": "XBTUSD",
        "side": "Sell",
        "order_price": 30002.5,
        "order_size": 100.0,
        "remain_size": 0.0,
        "order_type": "MARKET",
        "time": 1681267174064,
        "order_id": "2c56f74d-5fa4-3022-a2ec-89909e8f7ef3",
    },
    "rate_limits": {
        "remaining": 56,
        "reset": 1681264334
    },
}
Param Type Description
sym str Symbol of order that has been placed
side str Side of order that has been placed
order_price float Price of order that has been placed by market
order_size float Order size of submitted order
remain_size float Remaining size of a submitted order
order_type str Type of order placed, only "MARKET" order type returned
time int Unix timestamp the order was entered into the order book
order_id str Unique order id of order that has been placed

Cancel Order#

self.cancel_order(venue, order_id=order_id, sym=sym)

Cancel a single or multiple open orders for an exchange account.

Param Type Required Description
venue str Name of exchange API key to make call with e.g. "BitMEX"
order_id str ID of order to be cancelled
sym str Symbol of order to be submitted

Notes:

  • If neither order_id or sym are provided all open orders will be cancelled
  • If only order_id is provided, then this particular order will be cancelled
  • If only sym is provided, then all orders with this symbol will be cancelled

Example Response

{
    "src": "bitmex",
    "venue": "BitMEX",
    "error": None,
    "data": [
        {
            "sym": "XBTUSD",
            "side": "Buy",
            "order_price": 29684.5,
            "order_size": 100.0,
            "remain_size": 0.0,
            "order_type": "LIMIT",
            "time": 1681263047320,
            "order_id": "c00b2035-3a25-47a8-95e3-852a2c92d37a"
        }
    ],
    "rate_limits": {
        "remaining": 56,
        "reset": 1681264334
    },
}
Param Type Description
sym str Symbol of order that has been cancelled
side str Side of order that has been cancelled
order_price float Price of order that has been cancelled
order_size float Order size of cancelled order
remain_size float Remaining size of cancelled order
order_type str Type of order cancelled
time int Unix timestamp the order was cancelled
order_id str Unique order id of order of cancelled order

Amend Order#

self.amend_order(venue, order_id=order_id, size=size, price=price)

Amend a single open order for an exchange account.

Param Type Required Description
venue str Name of exchange API key to make call with e.g. "BitMEX"
order_id str Order id of order to be amended
size str New size of order to be amended
price float New price of order to be amended

Note: at least one of price or size needs to be provided for the order amendment to be valid.

Example Response

{
    "src": "bitmex",
    "venue": "BitMEX",
    "error": None,
    "data": {
        "sym": "XBTUSD",
        "side": "Buy",
        "order_price": 27875.5,
        "order_size": 100.0,
        "remain_size": 100.0,
        "order_type": "LIMIT",
        "time": 1681268424914,
        "order_id": "3da7e21a-35ce-482a-9b3e-2e424dafb8cc",
    },
    "rate_limits": {
        "remaining": 56,
        "reset": 1681264334
    },
}
Param Type Description
sym str Symbol of order that has amended
side str Side of order that has been amended
order_price float Price of order that has been amended
order_size float Order size of amended order
remain_size float Remaining size of amended order
order_type str Type of order amended
time int Unix timestamp the order was amended
order_id str Unique order id of order of amended order

Call Endpoint#

self.call_endpoint(venue, path, version, method='GET', params=None)

Call a native REST API of the venue.

Param Type Required Description
venue str Name of exchange API key to make call with e.g. "BitMEX"
path str Path of the native API endpoint, e.g. "instrument"
version str "public" or "private"
method str e.g. "GET" or "POST"
params dict Dictionary of parameters to pass to the endpoint

Example Response

{
    "src": "bitmex",
    "venue": "BitMEX",
    "venue_id": "636f2042-c0e1-4d3e-95b9-cbf0dde58d5d",
    "error": None,
    "data": [{
        ...
        },
        {
        ...
        }, ...
    ]
    "rate_limits": {
        "remaining": 56,
        "reset": 1681264334
    },
}

Note: the data member will contain results that depend on the specific native API called.

Create Websocket Feeds#

Trading bots allow you to create your own private websocket feeds, that can be streamed out to other applications. To start streaming websocket messages, simply call:

self.publish(topic, data=None)

Publish message to private websocket stream.

Param Type Required Description
topic str Name of the websocket topic
data multiple Payload can be any JSON serializable object e.g. dict, list, etc

To connect to your private websocket feed use the url:

wss://profitview.net/stream?token=YOUR_API_KEY

Note: YOUR_API_KEY can be found in Account Settings.

Once connected you will receive messages in the following format

< { "type": your_topic, "data": your_data }

Example use cases:

  • Streaming strategy state to a Grafana dashboard
  • Stream signals to your local environment or another server for processing
  • Build a web dashboard to view metrics of your strategy

Create Webhooks#

The trading bot comes with a built in HTTP server on which you can make GET and POST requests. To create an HTTP endpoint i.e. a webhook, each callback method needs to include the following decorator:

@http.route

Calling a webhook requires a WEBHOOK_SECRET which unique to your account. The list of registered webhook URLs (with the secret) can be found by clicking the bolt icon on the navigation panel of the code editor.

GET requests#

All GET requests must start with "get_" in the method name. For example the below webhook returns account balances for the queried exchange account:

@http.route
def get_balances(self, data):
    return self.fetch_balances(data['venue'])

This webhook can be accessed using cURL for example as follows:

curl https://profitview.net/trading/bot/WEBHOOK_SECRET/balances?venue=BitMEX

POST requests#

All POST requests must start with "post_" in the method name. For example the below webhook cancels the order provided in the payload:

@http.route
def post_cancel_order(self, data):
    return self.cancel_order(data['venue'], order_id=data['order_id'])

This webhook can be accessed using cURL for example as follows:

curl -X POST https://profitview.net/trading/bot/WEBHOOK_SECRET/cancel_order \
    -d 'venue=BitMEX' \
    -d 'order_id=ORDER_ID'

Example use cases:

  • Send TradingView signals to trigger an order
  • View and set the trading strategy state on a Google Sheet
  • Use the HTTP REST interface in your local environment or another server

Glossary#

Installed Libraries#

Each trading instance comes pre-installed with the following popular libraries useful for algoritmic trading and technical analysis:

  • numpy: high performance library for multidimension array and matrix calculations
  • pandas: library for creating data structures and performing analysis
  • scikit-learn: machine learning library for predictive analysis, built on NumPy and SciPy
  • scipy: library for mathematical algorithms and convenience functions
  • TA-Lib: popular trading software library to perform technical analysis of market data

If there is a Python library that is not listed here that you would like to see available, please request if by filling in the form here.

Supported Exchanges#

ID Name Spot Margin Swap Futures Options
bitmex BitMEX
coinbasepro Coinbase Pro
woo WOO X

More exchanges coming soon!