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.
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 thedata
payload - If the price of an order has been amended,
order_size
andremain_size
will not be in thedata
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)
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)
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
orsym
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)
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 |
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)
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 | ![]() |
|||||
coinbasepro | ![]() |
|||||
woo | ![]() |
More exchanges coming soon!