Welcome to a series of posts where I document my thoughts and experiments as an avid tinkerer, learner, and explorer in Technology

Experiments : Creating a Crypto Twitter Bot to Announce Newly Listed CryptoCoins

In crypto trading, there are big movements in a cryptocurrency’s price in a short period after it is added to an exchange.

The positive effect of being listed on a popular exchange has been quite substantial for altcoins and newly-issued ICO tokens as it not only provides the digital asset with a certain level of industry approval but it also allows a much larger investor base to invest in it.

Aside from being listed on international cryptocurrency exchanges, a listing on a popular regional exchange can also have a profound impact as it allows local investors to invest and withdraw profits using their own local fiat currency.

LINK bithumb price jump

The screenshot above illustrates the price jump in the LISK coin when it was announced that it had been listed on the South Korean Bithumb Exchange.

Experiment time!

What if… I made a bot that polls the top cryptocurrency exchanges for new coins and posts on Twitter whenever it detects a new cryptocurrency being listed on an exchange?

As it turns out, the idea was very achievable! The bot has found and posted about a whopping 1000+ new coins, and has an audience of 800+ followers so far!

In fact, I noticed that most exchanges add new cryptocurrencies to their APIs ahead of any official announcement, so this is a huge advantage!

See more tweets and follow at this link!

Otherwise, read on to find out more about how I made the bot!


Challenges :

Challenge #1 : Getting a consistent API to as many exchanges as possible

Though most exchanges offer a public API that can return information on what coins are supported on their exchange, there is no standard form so it would have been too painstakingly slow to write custom logic for each exchange to parse the each exchange’s response individually.

Luckily, I came across the excellent ccxt library in previous experiments that offers a simple consistent API for the top-100 exchanges in nodeJs, Python and PHP. Perfect!

Challenge #2 : Detecting when new cryptocurrencies are added to an exchange

Using the ccxt library described above, it returns all of an exchange’s supported coins, but it doesn’t tell us when a new cryptocurrency is added.

So, the strategy here is to repeatedly query an exchange every few minutes for supported cryptocurrencies. Then, we compare the list of currently supported cryptocurrencies with a list we stored from the previous execution.

When we find a difference between the old list and the current list, the newly added items in the list are the newly supported cryptocurrencies!

Challenge #3 : Running a node process on a schedule, forever

Thanks to npm, there are easy solutions for these that do exactly what it says on the box!

To run a task on a schedule, node-schedule comes to the rescue and provides a cron-like syntax to run tasks on a schedule

As for making sure a script keeps running, there is foreverjs to make sure a script keeps running and gets revived if it crashes or gets killed


Solution goals

The goal here was to keep things as simple as possible.

Minimise callback hell (aka callback pyramids) and promise chaining

In order to keep this as simple as possible, I was keen to leverage the ES2017 async/await pattern to replace the ugly callback pyramids and promise chaining when dealing with lots of asynchronous programming.

The dreaded callback pyramid

Try a Virtual Private Server

I’ve tried cloud services from Google and AWS, but haven’t actually tried a VPS (Virtual Private Server) from one of the smaller providers. This was an opportunity to try a little server for $5 a month from Vultr


Source code

const schedule = require("node-schedule");
const ccxt = require("ccxt");
const jsonfile = require("jsonfile");
const _ = require("underscore");
const Twit = require("twit");

const file = "./previouslyFoundCoins.json";

const T = new Twit({
  consumer_key: "XXX",
  consumer_secret: "XXX",
  access_token: "XXX",
  access_token_secret: "XXX",
  timeout_ms: 60 * 1000 // optional HTTP request timeout to apply to all requests.
});

let exchanges = [];

async function main() {
  // List the exchanges that we want to scan for new coins
  exchanges = [
    new ccxt.binance(),
    new ccxt.btcmarkets(),
    new ccxt.kucoin(),
    new ccxt.bittrex(),
    new ccxt.bithumb(),
    new ccxt.bitfinex2(),
    new ccxt.okex(),
    new ccxt.gateio(),
    new ccxt.huobipro(),
    new ccxt.hitbtc2(),
    new ccxt.gdax(),
    new ccxt.poloniex(),
    new ccxt.bitflyer(),
    new ccxt.bitmex(),
    new ccxt.bitstamp(),
    new ccxt.gemini()
  ];

  // Read our file containing previously found coins
  jsonfile.readFile(file, async function(err, previouslySavedData) {
    // Query an exchange for supported coins
    for (let exchange of exchanges) {
      await exchange.loadMarkets();
      var currencies = await exchange.currencies;

      // Compare with previous list of supported coins
      var differences = _.difference(
        Object.keys(currencies),
        previouslySavedData[exchange.id]
      );

      // Notify if we found a difference between current list and previous list
      // A newly added coin was found
      if (differences.length > 0) {
        var newPairMessage = `Found newly added coin ${differences.join(
          ", "
        )} on ${exchange.id}`;
        console.log(new Date().toString() + " " + newPairMessage);

        T.post("statuses/update", { status: newPairMessage }, function(
          err,
          data,
          response
        ) {
          console.log(data);
        });

        // Save all currencies including newly found ones to memory
        previouslySavedData[exchange.id] = Object.keys(currencies);
      } else {
        console.log(
          new Date().toString() + "No new coins found on " + exchange.id
        );
      }
    }

    // Save new list of supported coins back to file
    jsonfile.writeFile(file, previouslySavedData, function(err) {
      console.error(err);
    });
  });
}

// Run this loop every 3 minutes
schedule.scheduleJob(`*/3 * * * *`, function() {
  main();
});


Next explorations/Things to do differently next time