How to write passive income: Writing a quality trade bot in JS (part 1)

Let's start writing a trading bot that will run on the Binance crypto exchange. The bot should be able to:





  1. trade on your own, bringing in some kind of income





  2. should be convenient for creating and rolling out various trading strategies





  3. 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.








All Articles