How to make a TradingView Trade Execution program using PENDAX

Compendium Finance
25 min readAug 4, 2023

by @traderjoe155

TradingView allows you to create and manage alerts for your favorite trading pairs, and while this can be useful for manually executing your trades based off of an indicator or even a highly complex PineScript algorithm, the fact remains that in most cases you have to manually place your trade on the exchange. Well that need not be the case!

Enter PENDAX.

Designed for developers, traders, financial analysts, and data scientists. Built by the team at CompendiumFi, PENDAX simplifies exchange commands and provides interoperability, empowering users to build custom integrations tailored to their specific needs.

Utilizing the PENDAX npm library, we will be making a program that not only allows you to connect your exchange to TradingView alerts, but also expand upon that to include triggers from telegram, discord, or custom API calls.

https://pendax.pro/

Lets get started….

The Github repo for this project can be found at https://github.com/CompendiumFi/PENDAX-Trade-Execution.

For this tutorial we will be tailoring the connection to work with BYBIT Exchange. If you do not already have an account with them you can create one here:

https://partner.bybit.com/b/cmfi.

(yes this is a ref link, no you don’t have to use it, however you WILL receive reduced trading fees by doing so)

Ensure you have VScode installed on your computer, and that you have nodejs installed as we will be programming with nodejs and Javascript.

I would also recommend either creating a new Github repo to host your code, or fork the public repo accompanying this guide.

(it will make your life easier in the later steps of this tutorial)

Setting up the environment

Create a new folder and open it up in VScode. Open a new terminal, ensure you are in the newly created folder and initialize the package.json.

npm init -y

Using the -y for defaults is fine, but feel free to edit as you please.

In the newly created package.json file, make sure to edit the type to read “module”.

"type": "module"

Next we will create a index.js file.

Once the package.json is edited, and you have a blank index.js file, go back to the terminal and install PENDAX.

npm i @compendiumfi/pendax

We also will need to install dotenv.

npm i dotenv

Now in the root of your project, create a .env file. In the .env file we will be storing the API Key and API Secret for the Bybit Account you want to connect and use.

API_KEY="YOURAPIKEY"
API_SECRET="YOURAPISECRET"

Create a .gitignore file in the root directory of your project. Inside that file we will have 2 items, the node_modules and your .env file.

node_modules
.env

Now its time to write some code! Open up the index.js file and import the createExchange function from PENDAX.

import { createExchange } from "@compendiumfi/pendax/exchanges/exchange.js";

We also need to import our dot env file at the top of the index.js file so that we can use the variables inside it.

import 'dotenv/config'

Next we need to create our exchange object. You can have one or many, but this is what formulates the data to be sent to the exchange and handles all of the encryption and validation of your API keys and secret to then be received by Bybit (or another exchange).

let bybit_exchange = createExchange({
exchange: "bybit",
authenticate: true,
key: process.env.API_KEY,
secret: process.env.API_SECRET,
label: "bybit"
});

For my example I am calling it bybit_exchange, however you may call it whatever you like. In the exchange object there are 5 params required.

“exchange” must be lowercase and evaluates to the name of the exchange you wish to connect to.

“authenticate” is a boolean that if true requires you to pass in an api key and a secret. If false, this doesn’t require any keys or secret, however your exchange object will only be allowed to access unauthenticated endpoints. The list of unauthenticated endpoints varies from exchange to exchange but typically includes access to public data such as markets info and order books. For access to account and trading endpoints we mark as true.

“key” is our API key. You could choose to hardcode the key as a string here, however for our purposes we are setting it to the API_KEY specified in the .env file.

“secret” is the API secret. Same as the key you could hardcode the string, but we are using the value of API_SECRET in the .env file.

“label” is an arbitrary label that you can use to refer to an individual exchange object by. Not important to this tutorial so I am just setting it to “bybit”.

Creating first functions

The first function we will create is called getApiKeyInfo(). We want to call it and console log the response.

(this is just to ensure you have your keys correct, the response will give you details on your api keys connected)

async function getApiKeyInfo(exchange) {
try {
let result = await exchange.getApiKeyInfo();
console.log(result);
} catch (error) {
console.log(error.message);
}
}

Call the function with:

const result = await getApiKeyInfo(bybit_exchange)

You should get a response in the console with details on your connected api keys. If not, there is an issue with your keys or you need to revisit the earlier steps.

Creating the API server

Now that we have a connection to our Bybit account, lets make this application a bit more useful shall we? In the next steps we will be creating a web hook endpoint that can receive a TradingView alert, but first we need to organize the code a bit to make life easier for the coming steps.

In the root of your application create 2 folders. The first one is called controllers, and the second is called routers.

Inside the controllers folder, create a new file called bybit.controller.js

Inside the routers folder, create a new file called bybit.router.js

File Structure

Go back to the terminal and install express.

npm i express

Import express at the top of the index.js file

import express from "express";

Now we are going to make a simple express server. At this point of the tutorial your index.js file should look like this. (comment out the functions you made earlier).

import 'dotenv/config'
import express from 'express'

const app = express();
app.use(express.json());

app.listen(8000, () => {
console.log("Server is listening on port 8000");
});

You don’t have to use port 8000, I did for this example, but feel free to use any available port you wish. You could also create a “port” variable and set the app.listen to look at the variable should you wish to have a dynamic port specified in the env file. But I digress…

Next lets move the functions we made earlier out of the index.js file into the bybit.controller.js file, along with the import for createExchange() and the exchange object.

The bybit.controller.js file should now look like this.

import { createExchange } from "@compendiumfi/pendax/exchanges/exchange.js"

let bybit_exchange = createExchange({
exchange: "bybit",
authenticate: true,
key: process.env.API_KEY,
secret: process.env.API_SECRET,
label: "bybit"
});

async function getApiKeyInfo(exchange) {
try {
let result = await exchange.getApiKeyInfo();
return result;
console.log(result);
} catch (error) {
console.log(error.message);
}
}

const result = await getApiKeyInfo(bybit_exchange);

Now we are going to make a test endpoint to make sure we have set up everything properly. Comment out:

//const result = await getApiKeyInfo(bybit_exchange);

Create a “test” function in the bybit.controller.js file

const test = async (req, res) => {
console.log('passed');
res.json({
status: 'passed'
})
}

Since we already defined the getApiKeyInfo function earlier, we can also make an endpoint for it with the following code:

const getApiInfo = async (req, res) => {
try {
let keyInfo = await getApiKeyInfo(bybit_exchange);
res.json(keyInfo)
} catch (error) {
res.json(error);
console.log(error);
}
}

Don’t forget to export the test and getApiKeyInfo functions at the bottom of the file.

export { test, getApiKeyInfo }

The full bybit.controller.js file should now look like this:

import { createExchange } from "@compendiumfi/pendax/exchanges/exchange.js"

let bybit_exchange = createExchange({
exchange: "bybit",
authenticate: true,
key: process.env.API_KEY,
secret: process.env.API_SECRET,
label: "bybit"
});

async function getApiKeyInfo(exchange) {
try {
let result = await exchange.getApiKeyInfo();
return result;
console.log(result);
} catch (error) {
console.log(error.message);
}
}

const getApiInfo = async (req, res) => {
try {
let keyInfo = await getApiKeyInfo(bybit_exchange);
res.json(keyInfo)
} catch (error) {
res.json(error);
console.log(error);
}
}

const test = async (req, res) => {
console.log('passed');
res.json({
status: 'passed'
})
}

export { test, getApiInfo }

Moving on lets open up the bybit.router.js file.

We need to import router from express, as well as the test and getApiInfo from our controller, define a router to use, and define our route. We also need to export the bybitRouter, test, and getApiInfo endpoints.

The bybit.router.js file should look like this now:

import router from "express";
const bybitRouter = router();
import { test, getApiInfo } from "../controllers/bybit.controller.js";

bybitRouter.get("/test", test);
bybitRouter.get("/getApiInfo", getApiInfo);


export { bybitRouter, test, getApiInfo }

Now open up the index.js file and import the bybitRouter we just created, and tell express to use it.

The index.js file should look like this now:

import 'dotenv/config'
import express from "express";
import { bybitRouter } from './routers/bybit.router.js';


const app = express();
app.use(express.json());
app.use("/bybit", bybitRouter)

app.listen(8000, () => {
console.log("Server running on port 8000");
});

app.use(“/bybit”, bybitRouter) is saying that when our IP address receives a GET request to /bybit that it needs to use the bybitRouter defined in the bybit.router.js file. From there it can access the functions we defined.

That should be enough to start testing! Go ahead and start up your server.

node index.js

Send a GET request to localhost:8000/bybit/test using postman or some other method. (I use the VScode postman extension, makes life easier).

Successful GET request to our /test endpoint

Let’s test the getApiInfo call next! Send a GET request to localhost:8000/bybit/getApiInfo. The result should be a response with the info on your API keys. (Note: “code”:0 from Bybit means that the request was a success)

Im not showing all my account info bc that would be silly. Code 0 is a success.

Now we know that the express server is up and running, we have configured a minimal REST API, and can begin implementing some logic.

Time to create a trading endpoint!

Make the first trading endpoint

Go back into the bybit.controller.js file and define a constant called placeTrade and a function called placeOrder. We are going to use data from the POST request to submit the params into our trading function.

placeOrder function should look like this:

async function placeOrder(exchange, options) {
try {
let result = await exchange.placeOrder(options);
console.log(result);
return result;
} catch (error) {
console.log(error.message);
}
}

placeTrade constant should look like this:

const placeTrade = async (req, res) => {
try {
const trade = await placeOrder(bybit_exchange, {
category: req.body.category,
symbol: req.body.symbol,
side: req.body.side,
orderType: req.body.orderType,
qty: req.body.qty
})
console.log(trade);
res.json(trade);
} catch (error) {
console.log(error);
res.json(error)
}
}

Basically what is going on here is we have defined a function called placeOrder, this takes 2 params: exchange and options. Exchange is your exchange object created earlier, and the options are the params you are submitting to Bybit for constructing the trade.

We reference this function inside the placeTrade constant and pass through data from the POST request. The documentation for Bybit can be found here: https://bybit-exchange.github.io/docs/v5/order/create-order

Different params are required to be sent depending on what type of order you are trying to place. PENDAX simplifies this a bit and will tell you if you missed something in most cases. For our example we are going the easiest route, a market BUY order on BTCUSDT market. The base params needed to place this order are: category, symbol, side, orderType, and qty.

These params will be explained later on, so for now lets go ahead and export our new function.

The bybit.controller.js file should now look like this:

import { createExchange } from "@compendiumfi/pendax/exchanges/exchange.js"

let bybit_exchange = createExchange({
exchange: "bybit",
authenticate: true,
key: process.env.API_KEY,
secret: process.env.API_SECRET,
label: "bybit"
});

async function getApiKeyInfo(exchange) {
try {
let result = await exchange.getApiKeyInfo();
return result;
console.log(result);
} catch (error) {
console.log(error.message);
}
}

async function placeOrder(exchange, options) {
try {
let result = await exchange.placeOrder(options);
console.log(result);
return result;
} catch (error) {
console.log(error.message);
}
}

const getApiInfo = async (req, res) => {
try {
let keyInfo = await getApiKeyInfo(bybit_exchange);
res.json(keyInfo)
} catch (error) {
res.json(error);
console.log(error);
}
}

const test = async (req, res) => {
console.log('passed');
res.json({
status: 'passed'
})
}

const placeTrade = async (req, res) => {
try {
const trade = await placeOrder(bybit_exchange, {
category: req.body.category,
symbol: req.body.symbol,
side: req.body.side,
orderType: req.body.orderType,
qty: req.body.qty
})
console.log(trade);
res.json(trade);
} catch (error) {
console.log(error);
res.json(error)
}
}

export { test, getApiInfo, placeTrade }

Moving on into the bybit.router.js file, lets import what we just made, and create the endpoint. Don’t forget to export it as well at the bottom of the file.

The bybit.router.js file should now look like this:

import router from "express";
const bybitRouter = router();
import { test, getApiInfo, placeTrade } from "../controllers/bybit.controller.js";

bybitRouter.get("/test", test);
bybitRouter.get("/getApiInfo", getApiInfo);
bybitRouter.get("/placeTrade", placeTrade);

export { bybitRouter, test, getApiInfo, placeTrade }

Save your program and restart the server.

node index.js

To test the trade endpoint we just made, send a POST request to localhost://8000/bybit/placeTrade. The body of the POST request is where we will put a JSON object containing our params.

This is what I am using to place a trade on my account.

{
"category":"linear",
"symbol":"BTCUSDT",
"side":"Buy",
"orderType":"Market",
"qty":"0.003"
}

“category” is linear since I want to place a trade on a linear perpetual market.

“symbol” is BTCUSDT for the Bitcoin-USDT linear perp market.

“side” is Buy since I want to place a long order.

“orderType” is Market and will place a market order rather than a limit order. (I’m impatient when testing and want it to execute immediately)

“qty” is the amount of BTC I am placing an order for. 0.003 BTC is roughly $87 at time of writing this.

code 0 success message. response also includes the orderId.

Submit the POST request and take a look at your Bybit UI, if you followed the steps correctly you should now have an open long position on the BTCUSDT market!

Go ahead and manually close this position as we dont yet have a mechanism in our program to close via API.

Deploying to cloud server

Now we have a barebones REST API that we can send requests to and execute basic trades! If we want to use the webhook URL functionality of TradingView there is a bit more work to be done. TradingView requires a open port 80 for http requests or a url with an ssl certificate to use https. Since I have no desire to open up port 80 on my computer (along with dynamic IP not a static IP) we will need to host our program on a server.

My preferred deployment for a server is going to be Digital Ocean, however feel free to use AWS, or Google Cloud, or whatever cloud service your heart desires. I am using Ubuntu 22.04 LTS for the OS.

If you created a Github Repo for this program at the beginning like I recommended, then after you SSH into your new server instance and do the usual apt-get update/upgrade etc etc, go ahead and git clone your project onto this server. Otherwise you will need to recreate the program on your new cloud instance.

Don’t forget that the .env file was in the gitignore file and wont be included with the git clone. Go ahead and create a new one in the root of the project and copy the contents over.

Since this is a brand new server, we likely wont have node.js installed. Lets install it using Node Version Manager.

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bash
source ~/.bashrc
nvm list-remote
nvm install v18.17.0

If you want more detail on this process here is a good guide: https://www.digitalocean.com/community/tutorials/how-to-install-node-js-on-ubuntu-20-04

Now that we have node installed we can use npm install in our project directory to install all the dependencies. Go ahead and do that now.

Since we will be needing to open up ports on our server to serve this API, we will use nginx to manage the firewall.

apt install nginx

Be careful in the next steps if you are SSH into your server. If you screw up you could accidentally block yourself out of being able to connect to your machine. We want to allow Nginx HTTP as well as OpenSSH (so we dont lock ourselves out).

ufw allow "Nginx HTTP"
ufw allow "OpenSSH"

Turn on the firewall:

ufw enable

View the active firewall rules:

ufw status numbered

Check the status of nginx:

systemctl status nginx

Visit the IP address of your server in a browser to verify that you can see the Welcome to nginx page. This lets you know that the nginx server is active.

It works!

Next we want to edit our sites-available and sites-enabled folders.

Go to /etc/nginx/sites-available and delete the default file. Do the same for /etc/nginx/sites-enabled.

(alot of people don’t take advantage of the ability to use VScode to directly SSH into a server and use it as a file explorer. Highly recommend.)

Create a new file in the sites-available folder. I called mine pendax. Name yours whatever you want.

(don’t put an extension on the file)

Inside your newly created file insert this code:

server {
listen 80;
server_name http://YOURSERVERIPADDRESS;
location / {
proxy_pass http://localhost:8000;
proxy_set_header X-Forwarded-Proto $scheme;
}}

Save the file, and open up a terminal. Type in the following:

ln -s /etc/nginx/sites-available/pendax /etc/nginx/sites-enabled/

This command will add your file to the sites-enabled folder.

Note that where I wrote pendax you will need to change to whatever you named your file.

Go ahead and restart nginx:

systemctl restart nginx

Start your program again and send a GET request to http://YOURIPADDRESS/bybit/test. This should respond with our “passed” response.

TradingView webhook alerts send a POST request to the URL provided. Lets make test POST endpoint to ensure we are receiving properly from TradingView.

In the bybit.controller.js file add this code:

const testPost = async (req, res) => {
console.log('sent from tradingview');
res.json({
status: 'passed'
})
}

As per usual export it at the bottom of the file.

Open up the bybit.router.js file, import testPost, create the endpoint, and export. Your file should look like this:

import router from "express";
const bybitRouter = router();
import { test, getApiInfo, placeTrade, testPost } from "../controllers/bybit.controller.js";

bybitRouter.get("/test", test);
bybitRouter.get("/getApiInfo", getApiInfo);
bybitRouter.post("/placeTrade", placeTrade);
bybitRouter.post("/testPost", testPost);

export { bybitRouter, test, getApiInfo, placeTrade, testPost }

Now for the fun part! Open up TradingView and a chart. Create an alert.

An easy alert to make that triggers instantly is BTCUSDT Greater than $1,000. As soon as you hit save it will trigger. Before saving, click on the notifications tab.

Check the box for Webhook URL and type in: http://YOURIPADDRESS/bybit/testPost

Hit save and your alert should trigger.

Checking over on your terminal, you should see a console log saying “sent from tradingview”.

It works!

Let’s change the web hook URL in the TradingView alert from the test endpoint to the placeTrade endpoint and send in the same market order we tested manually.

(Make sure you input valid JSON into the Message of the alert. TradingView can be weird about the formatting sometimes)

{
"category":"linear",
"symbol":"BTCUSDT",
"side":"Buy",
"orderType":"Market",
"qty":"0.003"
}

Execute the alert.

Check your Bybit UI to see the order it placed.

Order sent from TradingView Alert successfully executed on the account

Alright! It placed the order to our account. The order you send can be formatted in a multitude of ways. We could add a stoploss, have it close our position, add a take profit, etc. All of the order params are available in the Bybit api documentation here:

Expanding on functionality

What if you didn’t want to have to use the same amount each time, or you wanted to use a percentage of equity for placing trades? In order to facilitate that we can make a function in the code that queries the account balance and calculates the size, and we can then reference that function in our trading view alert rather than submitting the entire order. This is a way to “pre-form” trades and execute them through a variable.

Example: I want to place a trade on BTCUSDT market with 100% of my available account balance, and then I want to have another alert trigger to close 50% of the open position as a take profit and another to close the remaining position.

We start out by making a function called tradeByEquity in the bybit.controller.js file. It takes one param in the body of the POST request called “type”. For this example we will have 4 types: long, short, close long, and close short. Make sure to export the function and create the endpoint in the router. For now it will just console log the type when an alert is received.

const tradeByEquity = async (req,res) => {
try {
const type= req.body.type;
switch(type) {
case "long":
console.log("long");
break;
case "short":
console.log("short");
break;
case "close long":
console.log("close long");
break;
case "close short":
console.log("close short");
break;
default:
console.log("error");
break;
}
} catch (error) {
console.log(error);
res.json(error)
}
}

Export tradeByEquity from the bybit.controller.js file

Import it into the router. Your bybit.router.js file should now look like this:

import router from "express";
const bybitRouter = router();
import { test, getApiInfo, placeTrade, testPost, tradeByEquity } from "../controllers/bybit.controller.js";

bybitRouter.get("/test", test);
bybitRouter.get("/getApiInfo", getApiInfo);
bybitRouter.post("/placeTrade", placeTrade);
bybitRouter.post("/testPost", testPost);
bybitRouter.post("/tradeByEquity", tradeByEquity);

export { bybitRouter, test, getApiInfo, placeTrade, testPost, tradeByEquity }

Start your server again

node index.js

In your TradingView alert, change the endpoint to http://YOURIPADDRESS/bybit/tradeByEquity, and structure the message like this:

{
"type":"long"
}

Save and execute the TradingView alert, you should see a message in your console that reads “long”.

Now that we know the alert is being received, and our switch statement works, we can manipulate some data.

In order to know how much of a trade to place, we need to know the balance of the account.

For the long and short calls we will look at the balance, for the close long and close short we will be looking at the open position.

We are looking at the UNIFIED trading account right now since the pair we are trading is a linear perp. If we were looking at trading coin margined then we would want to know the balance on the CONTRACT wallet.

We are trading a USDT pair, so for now we only need to know the USDT balance of the account. If you don’t specify a coin then all non zero balances will be returned.

The return of this call to Bybit includes a lot of data that we don’t need. So we should drill down into the response and only return the exact data point we need. Here is the function we are going to use to get the account balance:

async function getbalance(exchange, options) {
try {
let result = await exchange.getWalletBalance(options);
let usdtBalance = result.data.list[0].coin[0].walletBalance;
let usdtBalanceparsed = parseFloat(usdtBalance);
return usdtBalanceparsed
} catch (error) {
console.log(error.message);
}
}

We are calling the getWalletBalance function from PENDAX, and passing in the coin (USDT) when the function gets called, trimming down the data returned and only taking the USDT Balance. This is returned to us as a string from Bybit, so we need to convert it to a float in order to do math on it. Return the parsed data.

Now we have a number we can actually use and do math on to calculate the trade size. Lets formulate the trade now.

Formulate trade logic

Bybit linear perp markets require qty in the base currency. In our case with BTCUSDT, the base currency is BTC. So our qty will need to be a calculation of the current market price of BTCUSDT, divided by our proposed USDT value of the trade. Bc there is an extra step to this, when using 100% equity there is a chance for the trade to fail to place due to fluctuations in the market price in the time it takes to calculate and submit the trade. Leaving a small buffer is wise. IE 95% equity. Also keep in mind minimum order sizes. The balance on this account is small to show that there are some precautions to take to ensure you don’t encounter rounding errors that cause you to go over the account risk defined by your input param when dealing with small amounts and larger minimum order sizes. IE on Bybit the min order size on BTCUSDT is 0.001 BTC.

Create a function to get the current bitcoin price using the getTickers call from PENDAX and specify the market. Remember to return the value as a float.

async function getTickers(exchange, options) {
try {
let result = await exchange.getTickers(options);
let price = result.data.list[0].lastPrice;
return parseFloat(price);
} catch (error) {
console.log(error.message);
}
}

Again, we are drilling down into the response and only returning the data we need.

Now we have functions to get the USDT balance and the current ticker price, to get the qty we take the balance and divide it by the ticker price.

I created a variable called “amount” to show the result of the calculation. We have a slight problem though… The minimum tick size for a BTCUSDT trade on Bybit is 0.001 BTC, this amount has too many decimal places. To rectify this one would think that just using amount.toFixed(3) would work, but this has a major issue, if we round up then we could have an eventuality where the qty submitted is greater than the amount of equity in the account, resulting in a failed trade.

The correct way to handle this is to find out the decimal precision of the pair we are trading, calculate the proposed trade amount based on amount/assetPrice and compare the result to the balance*equity. If the result is > balance * equity, then decrement the last decimal by 1. Sounds complicated, but its really not.

Lets start by getting the min tick size (decimal precision) of our trading pair. Create a function that looks like this:

async function getInstrumentsInfo(exchange, options) {
try {
let result = await exchange.getInstrumentsInfo(options);
console.log(result);
let precision = result.data.list[0].lotSizeFilter.minOrderQty;
return precision;
} catch (error) {
console.log(error.message);
}
}

We are going to call it inside the tradeByEquity() function like so:

        let precision = await getInstrumentsInfo(bybit_exchange, 
{
category: 'linear',
symbol: req.body.symbol
});

Now that we know the decimal precision, lets make a function to handle verifying our proposed trade size. Im going to call it calcQty. This function will take the equity, decimal precision of the trading pair, our balance, and the price of the trading pair. Multiply the equity * balance to find our proposed size denoted in USDT. This will be our maxAmt. Next we will take the maxAmt divided by the price of the trading pair (ticker), and using toFixed(decimals) to limit the amount of decimal places by what is required of the trading pair. (this is the info we grabbed from the getInstrumentsInfo() call.) Now we are going to “trial” the math to make sure that the amount we want to submit for the trade is in fact going to be accepted by Bybit. The whole point of this is to rectify issues with trading small size that come up on min order sizes after rounding from the toFixed(). If the trial is > maxAmt, then we will essentially subtract one 1000th from the trade size. This works on all scenarios where a rounding up issue pushes it over the threshold. The code for the function is below:

async function calcQty(equity, precision, balance, ticker) {
let decimals = Math.log10(1 / precision)
let maxAmt = equity * balance
let amt = (maxAmt / ticker).toFixed(decimals)
let trial = amt * ticker
let result = trial > maxAmt? amt - (1/Math.pow(10, decimals)): amt
return result
}

Next lets call this from our “long” case:

const tradeByEquity = async (req, res) => {
try {
let precision = await getInstrumentsInfo(bybit_exchange,
{
category: 'linear',
symbol: req.body.symbol
});
let balance;
let equity = req.body.equity;
let trade;
let qty;
let amount;
let assetPrice;
let currentPosition;
const type = req.body.type;
switch (type) {
case "long":
balance = await getbalance(bybit_exchange,
{
accountType: "UNIFIED",
coin: "USDT"
});
console.log("long")
console.log(balance)
assetPrice = await getTickers(bybit_exchange,
{
category: 'linear',
symbol: "BTCUSDT"
});
console.log(assetPrice);
amount = (balance * equity) / assetPrice;
qty = await calcQty(equity, precision, balance, assetPrice);
trade = await placeOrder(bybit_exchange, {
category: "linear",
symbol: req.body.symbol,
side: "Buy",
orderType: req.body.orderType,
qty: qty.toString()
});
console.log(trade);
break;
case "short":
console.log("short")
break;
case "close long":
console.log("close long")
break;
case "close short":
console.log("close short");
break;
default:
console.log("error")
break;
}

} catch (error) {
console.log(error);
res.json(error)
}

}

To demonstrate why we did this, in a debugger putting a breakpoint at the return on the calcQty function, we can see that if we had not manipulated the qty to place in our trade, it would have come in over our 100% equity. (rounding and math in javascript is a entire topic i could go into for hours but will refrain from doing so here) We fixed the issue with this line:

let result = trial > maxAmt ? amt - (1 / Math.pow(10, decimals)) : trial

Resulting in an actual trade of 0.001 BTC to be placed.

Now that the math checks out, lets restart our alert and run it full speed!

Thats great! Now to move on to the short type. We have all the code we need to perform the trade, and all thats really needed is to change the side to “Sell” in our trade params. Copy the code from the “long” case and paste it under the “short” case. Then change the side to “Sell”. It should look like this:

 case "short":
balance = await getbalance(bybit_exchange,
{
accountType: "UNIFIED",
coin: "USDT"
});
console.log("long")
console.log(balance)
assetPrice = await getTickers(bybit_exchange,
{
category: 'linear',
symbol: "BTCUSDT"
});
console.log(assetPrice);
amount = (balance * equity) / assetPrice;
qty = await calcQty(equity, precision, balance, assetPrice);
trade = await placeOrder(bybit_exchange, {
category: "linear",
symbol: req.body.symbol,
side: "Sell",
orderType: req.body.orderType,
qty: qty.toString()
});
console.log(trade);
break;

Save and restart your program, then edit your TradingView alert to this:

{
"type":"short",
"equity":1,
"symbol":"BTCUSDT",
"orderType":"Market"
}

Execute the alert.

Awesome! So now we can execute both long and short trades according to our equity. Moving on, the next step is to handle closing positions either partially, or completely.

Lets first open up a leveraged trade at 5x so that we have a position to work with. In the TradingView Alert change the equity to 5 and type to “long”. Remember that equity is a number, and type is a string in the JSON.

{
"type":"long",
"equity":5,
"symbol":"BTCUSDT",
"orderType":"Market"
}

For the “close long” case we will take any number up to 1. This represents a percentage of the position to close. 0.5 = 50% of the open position, 0.9 = 90% of the position, 1 = 100% of the position, so on, and so on. To calculate this we don’t need to look at our balances like we did for placing the trades, rather we want to look at the current open position, and make the calculations against that. Create a function for getting the position info like so:

async function getPositionInfo(exchange, options) {
try {
let result = await exchange.getPositionInfo(options);
console.log(result);
let openSize = result.data.list[0].size;
return parseFloat(openSize)
} catch (error) {
console.log(error.message);
}
}

Under the “close long” case the code should look like this:

case "close long":
currentPosition = await getPositionInfo(bybit_exchange, {
category: 'linear',
symbol: req.body.symbol
});
amount = currentPosition * equity;
qty = amount.toFixed(Math.log10(1 / precision))
console.log("close long")
trade = await placeOrder(bybit_exchange, {
category: "linear",
symbol: req.body.symbol,
side: "Sell",
orderType: req.body.orderType,
qty: qty.toString()
});
console.log(trade);
break;

This is getting the currentPosition expressed in our case as the amount of BTC. Then we will multiply that by our equity. For arguments sake we will use 0.5 in the TradingView Alert to close half the position. Next we want to make sure that the qty we are placing is in the correct format as far as decimals are concerned. Doing this with:

qty = amount.toFixed(Math.log10(1 / precision))

Now in the trade, since this is closing a part or whole of a long position, we want to make sure the side is “Sell”. Go ahead and edit your TradingView Alert to look like this:

{
"type":"close long",
"equity":0.5,
"symbol":"BTCUSDT",
"orderType":"Market"
}

Restart your program and execute the alert.

We have closed half of the position! (or as close to it as we could given minimum trade sizes and decimal places).

Now send an alert but this time change equity to 1, this will close the position entirely.

We can now move on to the “close short” case. The code for this is exactly the same as the “close long” case, aside from needing to change the “side” to “Buy”. Here is what it should look like:

case "close short":
currentPosition = await getPositionInfo(bybit_exchange, {
category: 'linear',
symbol: req.body.symbol
});
amount = currentPosition * equity;
qty = amount.toFixed(Math.log10(1 / precision))
console.log("close short")
trade = await placeOrder(bybit_exchange, {
category: "linear",
symbol: req.body.symbol,
side: "Buy",
orderType: req.body.orderType,
qty: qty.toString()
});
console.log(trade);
break;

To test it, lets open up a short position using our “short” type in the alert. Again I am using 5x leverage.

Now test the “close short” the same as we did for the “close long”. First with a partial close, then with a full close.

{
"type":"close short",
"equity":0.5,
"symbol":"BTCUSDT",
"orderType":"Market"
}
{
"type":"close short",
"equity":1,
"symbol":"BTCUSDT",
"orderType":"Market"
}

All of our alerts work perfect!

A few housekeeping tasks that should be done before deploying this, in the getTickers() function, we currently have it hardcoded to the BTCUSDT market. Go ahead and change that to look at the market sent in by the TradingView Alert. I created a variable in the tradeByEquity function for symbol, and am pulling that value from req.body.symbol.

Your full tradeByEquity function should look like this:

const tradeByEquity = async (req, res) => {
try {
let precision = await getInstrumentsInfo(bybit_exchange,
{
category: 'linear',
symbol: req.body.symbol
});
let balance;
let equity = req.body.equity;
let trade;
let qty;
let amount;
let assetPrice;
let currentPosition;
let symbol = req.body.symbol;
const type = req.body.type;
switch (type) {
case "long":
balance = await getbalance(bybit_exchange,
{
accountType: "UNIFIED",
coin: "USDT"
});
console.log("long")
console.log(balance)
assetPrice = await getTickers(bybit_exchange,
{
category: 'linear',
symbol: symbol
});
console.log(assetPrice);
amount = (balance * equity) / assetPrice;
qty = await calcQty(equity, precision, balance, assetPrice);
trade = await placeOrder(bybit_exchange, {
category: "linear",
symbol: req.body.symbol,
side: "Buy",
orderType: req.body.orderType,
qty: qty.toString()
});
console.log(trade);
break;
case "short":
balance = await getbalance(bybit_exchange,
{
accountType: "UNIFIED",
coin: "USDT"
});
console.log("long")
console.log(balance)
assetPrice = await getTickers(bybit_exchange,
{
category: 'linear',
symbol: symbol
});
console.log(assetPrice);
amount = (balance * equity) / assetPrice;
qty = await calcQty(equity, precision, balance, assetPrice);
trade = await placeOrder(bybit_exchange, {
category: "linear",
symbol: req.body.symbol,
side: "Sell",
orderType: req.body.orderType,
qty: qty.toString()
});
console.log(trade);
break;
case "close long":
currentPosition = await getPositionInfo(bybit_exchange, {
category: 'linear',
symbol: req.body.symbol
});
amount = currentPosition * equity;
qty = amount.toFixed(Math.log10(1 / precision))
console.log("close long")
trade = await placeOrder(bybit_exchange, {
category: "linear",
symbol: req.body.symbol,
side: "Sell",
orderType: req.body.orderType,
qty: qty.toString()
});
console.log(trade);
break;
case "close short":
currentPosition = await getPositionInfo(bybit_exchange, {
category: 'linear',
symbol: req.body.symbol
});
amount = currentPosition * equity;
qty = amount.toFixed(Math.log10(1 / precision))
console.log("close short")
trade = await placeOrder(bybit_exchange, {
category: "linear",
symbol: req.body.symbol,
side: "Buy",
orderType: req.body.orderType,
qty: qty.toString()
});
console.log(trade);
break;
default:
console.log("error")
break;
}

} catch (error) {
console.log(error);
res.json(error)
}

}

There are a million and one ways to alter this program to suit different circumstances and needs, but I have to end this guide somewhere!

Our end result is a program that can take in POST requests from postman or TradingView and execute specific or generalized trades on Bybit Linear Perpetual Markets.

If you have any questions or comments feel free to reach out on Discord, Telegram, or Twitter!

https://t.me/CompendiumFinanceOfficial

--

--

Compendium Finance

Making life in the crypto markets & metaverse simpler. Customizable automated strategies and social trading features for multiple exchanges.