In this guide, we will construct the trading bot which will be transacting on the QUANTA blockchain. By the end of this guide, you will have the trading bot functioning. The language chosen is javascript, with es6, async.
The core control code will boot up, cancel all existing orders, and execute orders every 8 seconds. In the stock market, typical traders choose the frequency when they'd like to trade such as 1m, 5m, 30m and so on.
Control Logic
Copy class TradeAgent {
constructor (key , spread , levels) {
}
async cancelAll () {
}
async runMarket (market) {
}
async runOnce () {
for ( const m of this .quantaMarket) {
console .log ( "running for market " , m .ticker);
await this .runMarket (m)
}
}
async run () {
const self = this ;
await sleep ( 1000 );
await self .cancelAll ();
setInterval ( async () => {
await self .runOnce ();
} , 8000 )
}
}
new TradeAgent ( process . env . KEY , 0.05 , 5 ) .run ()
Creating the Client
The client will leverage two pre-built npm libraries which can be found here: quantajs , quantajs-ws
Copy npm i --save @quantadex/bitsharesjs
npm i --save @quantadex/bitsharesjs-ws
The client has the following interface:
Copy class QuantaClient {
constructor (ws_url , onReady) {
}
async setupUser (key) {
}
close () {
Apis .close ()
}
async sendLimitOrder (user , base , counter , orders) {
}
async openOrders (user , base) {
}
async cancelOrder (user , orders) {
}
}
Connecting to the websocket
We depend on the @quantadex/bitsharesjs-ws library for websocket connection. QUANTA uses bi-directional communication to send commands, retrieve information, as well as getting pushed messages to the user. In the connection below, we will retrieve all of the assets information so we know how to reference buy orders.
Copy constructor (ws_url , onReady) {
Apis .instance (ws_ur , true , 3000 , { enableOrders : true }). init_promise .then ((res) => {
console .log ( "connected to " , ws);
return Apis .instance () .db_api () .exec ( "list_assets" , [ "A" , 100 ]) .then ((assets) => {
self .assets = lodash .keyBy (assets , "id" )
self .assetsBySymbol = lodash .keyBy (assets , "symbol" )
if (onReady) {
onReady ()
}
});
});
}
Retrieving account information
Often functions require user to reference their userId so here we look up the public key for the account information. In QUANTA blockchain, account system is independent from the key-signature. An account has userid, username, balances, orders, and assigns public key authorized to sign it. If you ever want to change the key, you can retain all the account information.
Copy async setupUser (key) {
const privateKey = PrivateKey .fromWif (key);
const publicKey = privateKey .toPublicKey () .toString ()
const result = await Apis .instance ()
.db_api ()
.exec ( "get_key_references" , [[publicKey]]);
return {
privateKey : privateKey ,
userId : result[ 0 ][ 0 ] ,
publicKey : publicKey
}
}
Send Limit Order
The send limit order takes in a user (from the retrieve account information section), base, counter asset, price, and amount. Orders are always specified from the seller. So we have a conversion function to make this easier for us.
If you were buying BTC with USD, then you are selling USD and receiving BTC.
If you were selling BTC and receive USD, then you are selling BTC, and receive USD.
Copy async sendLimitOrder (user , base , counter , price , amount) {
const tr = new TransactionBuilder ();
const expiration = new Date ();
expiration .setYear ( expiration .getFullYear () + 5 );
const { forSale , toReceive } = this .calculatePrice (is_buy , base , counter , price , amount)
tr .add_type_operation ( "limit_order_create" , {
fee : {
amount : 0 ,
asset_id : "1.3.0"
} ,
seller : user .userId ,
amount_to_sell : {
amount : forSale .getAmount () ,
asset_id : forSale .asset_id
} ,
min_to_receive : {
amount : toReceive .getAmount () ,
asset_id : toReceive .asset_id
} ,
expiration : expiration ,
fill_or_kill : false
});
return await signAndBroadcast (tr , user);
}
Signing and Broadcasting
Signing is straight froward with the user object built earlier, and adding a signature to it, then broadcast to the network.
Copy export function signAndBroadcast (tr , user) {
return tr .set_required_fees () .then (() => {
const pKey = user .privateKey;
tr .add_signer (pKey , pKey .toPublicKey () .toPublicKeyString ());
return tr .broadcast ()
.then ((res) => {
return res;
})
});
}
Cancel order
Cancel order is similar to limit order, which needs to be signed.
Copy async cancelOrder (user , orders) {
if ( orders . length == 0 ) {
return
}
const fee_asset_id = "1.3.0" ;
const tr = new TransactionBuilder ();
for ( var order_id of orders) {
tr .add_type_operation ( "limit_order_cancel" , {
fee : {
amount : 0 ,
asset_id : fee_asset_id
} ,
fee_paying_account : user .userId ,
order : order_id
});
}
const res = await signAndBroadcast (tr , user)
return res;
}
Putting it together
Copy class TradeAgent {
constructor (key , spread , levels) {
this .quantaClient = new Client (default_ws , key , userId , null );
this .lastOrders = {};
this .quantaMarket = [{ ticker : "QDEX/USD" ,
price : new ConstantPrice ( 0.30 ) , clear : false , placed : false } ,
{ ticker : "ETH/USD" ,
price : new GdaxPrice ( "ETH-USD" , spread) , clear : true , placed : false }
];
}
async cancelAll () {
const orders = await this . quantaClient .openOrders ( "USD" )
for ( const o of orders) {
console .log ( "cancel order " , o .id);
await this . quantaClient .cancelOrder ( o .id)
}
}
async runMarket (market) {
if ( market .clear) {
const orders = this .lastOrders[ market .ticker] || []
for ( const o of orders) {
const result = await this . quantaClient .cancelOrder ( o .id);
console .log ( "cancel " , o , result .status);
}
}
const parts = market . ticker .split ( "/" )
if ( ! market .clear) {
if ( ! market .placed) {
market .placed = true ;
await this . quantaClient .sendLimitOrder ( true , parts[ 0 ] , parts[ 1 ] , market . price .getBid () , market . price .getAmount ());
await this . quantaClient .sendLimitOrder ( false , parts[ 0 ] , parts[ 1 ] , market . price .getAsk () , market . price .getAmount ());
}
} else {
market . price .update ()
await this .cancelAll ()
await this . quantaClient .sendLimitOrder ( true , parts[ 0 ] , parts[ 1 ] , market . price .getBid () , market . price .getAmount ());
await this . quantaClient .sendLimitOrder ( false , parts[ 0 ] , parts[ 1 ] , market . price .getAsk () , market . price .getAmount ());
}
}
async runOnce () {
for ( const m of this .quantaMarket) {
console .log ( "running for market " , m .ticker);
await this .runMarket (m)
}
}
async run () {
const self = this ;
await sleep ( 1000 );
await self .cancelAll ();
setInterval ( async () => {
await self .runOnce ();
} , 8000 )
}
}
Source code on Github