Let's start writing a trading bot that will run on the Binance crypto exchange. The bot should be able to:
trade on your own, bringing in some kind of income
should be convenient for creating and rolling out various trading strategies
test the strategy on historical data
Let's start with architecture
We have a Binance exchange that has a gorgeous api. Therefore, the architecture could look like this:
Call a couple of "buy cheaper" and "sell more expensive" methods. But the task for us is to write such a bot in which a conditional programmer-trader can create and test new strategies for profitability. Therefore, it is necessary to separate the trading logic from everything else. And also the logic module shouldn't care which exchange it was connected to: to a real API or to a pseudo-API (for testing). Taking all this into account, we got something like this architecture:
The base was chosen by PostgreSQL. There is no secret intent here. You can use any.
Due to the fact that each module is worth your attention, all this will not fit into one article. Therefore, I am starting a mini-series: "Writing a high-quality trade bot in JS". Therefore, subscribe, make yourself comfortable - let's start
Service for logs
, log
error
. :
class LoggerService {
constructor(prefix) {
this.logPrefix = prefix
}
log(...props) {
console.log(new Date().toISOString().substr(0, 19), this.logPrefix, ...props)
}
error(...props) {
console.error(new Date().toISOString().substr(0, 19), this.logPrefix, ...props)
}
}
yarn add node-binance-api
BaseApiService. Binance SDK, LoggerService. Binance , . , futuresExchangeInfo()
. getAssetPricePrecision
getAssetQuantityPrecision
.
class BaseApiService {
constructor({ client, secret }) {
const { log, error } = new Logger('BaseApiService')
this.log = log
this.error = error
this.api = new NodeBinanceApi().options({
APIKEY: client,
APISECRET: secret,
hedgeMode: true,
})
this.exchangeInfo = {}
}
async init() {
try {
this.exchangeInfo = await this.api.futuresExchangeInfo()
} catch (e) {
this.error('init error', e)
}
}
getAssetQuantityPrecision(symbol) {
const { symbols = [] } = this.exchangeInfo
const s = symbols.find(s => s.symbol === symbol) || { quantityPrecision: 3 }
return s.quantityPrecision
}
getAssetPricePrecision(symbol) {
const { symbols = [] } = this.exchangeInfo
const s = symbols.find(s => s.symbol === symbol) || { pricePrecision: 2 }
return s.pricePrecision
}
}
, :
async futuresOrder(side, symbol, qty, price, params={}) {
try {
qty = Number(qty).toFixed(this.getAssetQuantityPrecision(symbol))
price = Number(price).toFixed(this.getAssetPricePrecision(symbol))
if (!params.type) {
params.type = ORDER.TYPE.MARKET
}
const res = await this.api.futuresOrder(side, symbol, qty, price || false, params)
this.log('futuresOrder', res)
return res
} catch (e) {
console.log('futuresOrder error', e)
}
}
. , . TradeService.
class TradeService {
constructor({client, secret}) {
const { log, error } = new LoggerService('TradeService')
this.log = log
this.error = error
this.api = new NodeBinanceApi().options({
APIKEY: client,
APISECRET: secret,
hedgeMode: true,
})
this.events = new EventEmitter()
}
marginCallCallback = (data) => this.log('marginCallCallback', data)
accountUpdateCallback = (data) => this.log('accountUpdateCallback', data)
orderUpdateCallback = (data) => this.emit(data)
subscribedCallback = (data) => this.log('subscribedCallback', data)
accountConfigUpdateCallback = (data) => this.log('accountConfigUpdateCallback', data)
startListening() {
this.api.websockets.userFutureData(
this.marginCallCallback,
this.accountUpdateCallback,
this.orderUpdateCallback,
this.subscribedCallback,
this.accountConfigUpdateCallback,
)
}
subscribe(cb) {
this.events.on('trade', cb)
}
emit = (data) => {
this.events.emit('trade', data)
}
}
SDK this.api.websockets.userFutureData
. this.orderUpdateCallback
. . EventEmitter
, , subscribe
.
? , . . /. . sequlize.
yarn add sequelize-cli -D
yarn add sequelize
npx sequelize-cli init
docker-compose.yml :
version: '3.1'
services:
db:
image: 'postgres:12'
restart: unless-stopped
volumes:
- ./volumes/postgresql/data:/var/lib/postgresql/data
environment:
POSTGRES_USER: root
POSTGRES_PASSWORD: example
POSTGRES_DB: bot
ports:
- 5432:5432
networks:
- postgres
networks:
postgres:
driver: bridge
. User
, Order
To be continued.
In the next article, we will write a core that will connect all these parts and make the bot trade.