Posts Bitwatch - specialised cryptocurrency monitoring and analysis
Post
Cancel

Bitwatch - specialised cryptocurrency monitoring and analysis

Cryptocurrency has created a very confusing world for many people, and presents so many alluring opportunities at the same time, assuming that one can make heads and tails of it all. However, experience has taught me how valuable taking a bite-sized approach is to the ever-growing and subjective topics within cryptocurrency.

What I had in mind was one question: Can one make money by trading through a “proxy” coin and then selling back to fiat?

Of course, this started as a few calculations by hand and through Apple’s amazing Spotlight feature. It then grew as I started to perform the calculation over multiple timeframes and between greater numbers of coin combinations.

Within a short space of time, I felt that the scale and complexity of the kinds of questions I was asking lent itself nicely to an app, as with so many other app ideas I’ve had in the past.

In comes Bitwatch:

Coin list

Introductions

I didn’t want to turn this app into yet-another-crypto-site, so I tried to keep the functionality as specialised as I could afford. The coin list shown above may give that impression, but is really laying ground-work for what is to come after it.

This magic happens in coin details:

Coin details

Here, it becomes clear that Bitwatch is getting time-series data from somewhere, and here that is stored in the application itself. I will keep the source of the data anonymous for privacy reasons, but to make it sound cool and mysterious, we can call it Service X. Yes, that will do nicely.

Service X provides the ability to swap coins for a specified price, and being a many-to-many relationship, this lookup needs to be performed on a coin-combination basis. If you look closely enough at the above graph, you’ll have noticed this:

Coin graph key

This brings us to the precipice of Bitwatch…

The Objective in a Nutshell

Here’s an example: Am I better off selling my ETH, or first swapping to XLM and then selling?

Coin graph key

If the crypto brokers and exchanges are doing their job, then this should never be possible, but I didn’t know if this was the case, and so I thought best to let the empirical results do the talking, so even a negative result is a result.

As an aside, this can become cumbersome if the list of swaps grows too large. To counteract this, convenience shortcuts were put in to show or hide sets of series:

Show all buttons in coin details

If swapping is financially superior to selling directly, one of the swap lines should be noticeably higher than the sell line. In the above image, this is not the case. Over time, I was able to prove that careful monitoring and rule-setting ensures that Service X is a careful and consistent provider.

Walk Through

The sell price requires no calculation, but the swap prices take some more understanding. Given that the page shows coin details for a particular cryptocurrency, a swap price takes into account the “final sale” price achieved when swapping and then selling, combining the two rates. It also accommodates fees and other rate alterations.

1
2
3
4
5
6
7
8
9
10
11
for combo in coin_holding_combos:
  from_symbol = combo['coin']
  from_coin_price_obj = from_coin_dict.get(from_symbol)
  from_sell_price = from_coin_price_obj.sell_rate if from_coin_price_obj else None
  to_symbol = combo['coins_of_interest']
  if not from_sell_price:
    logger.warning(f'Skipping {from_symbol}/{to_symbol} as no {from_symbol} sell price exists.')
    continue

  from_amount = 1000 / from_sell_price
  logger.debug(f'Asking to swap {from_amount} {from_symbol} into {to_symbol}.')

There’s a lot to explain behind the logic and process of this code snippet, but it segues nicely into a “coin holding”.

1
2
3
4
5
6
7
8
9
10
11
class CoinHolding(models.Model):
  portfolio = models.ForeignKey(Portfolio, on_delete=models.CASCADE)
  coin = models.ForeignKey(Coin, on_delete=models.CASCADE)
  amount = models.DecimalField(decimal_places=30, max_digits=50, default=0)
  current_fiat_balance = models.DecimalField(decimal_places=30, max_digits=50, default=0)
  coins_of_interest = models.ManyToManyField(Coin, related_name='coinsofinterest', blank=True)
  is_fiat_of_interest = models.BooleanField(default=False)
  is_active = models.BooleanField(default=True)

  def __str__(self):
    return f'{self.coin.symbol} ({self.amount})'

A coin holding says that a particular Trader (representing a registered user on the site) has a certain amount of a given coin. coins_of_interest is a ManyToManyField that connects two coins together to indicate that Bitwatch can consider it as a proxy when evaluating swap options across coins.

Every time Bitwatch goes to fetch swap prices, it can then enumerate the list of coins_of_interest to find which coins to ask for.

Price Fetch Logic

Bitwatch uses Celery to manage and execute tasks to fetch price information. Task logic is confined to tasks.py, a file within the portfolios app. With the project being deployed and run through Docker, Celery is executed through its own container which piggybacks on the project code itself:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
services:
  web:
    build: .
    env_file:
      - .env
      - docker.env
    depends_on:
      - rabbitmq
      - redis
      - db

  # [...]

  celery:
    build: .
    command: celery -A bitwatch worker --beat --scheduler django -l INFO
    depends_on:
      - rabbitmq
      - db
    env_file:
      - .env
      - docker.env

This uses the celery command to start the program execution loop, as per Celery’s public documentation. The tasks are queued in the tasks.py file itself:

1
2
3
4
@app.on_after_finalize.connect
def setup_periodic_tasks(sender, **kwargs):
  sender.add_periodic_task(FETCH_PRICE_SECS, fetch_prices.s(), name='Fetch prices')
  sender.add_periodic_task(FETCH_SWAP_SECS, fetch_swaps.s(), name='Fetch swaps')

FETCH_PRICE_SECS and FETCH_SWAP_SECS are abstracted to allow configuration from Docker and other environment sources.

Other Aspects and Features

It’s the usual affair with the fantastic and invaluable Django Admin:

Bitwatch Admin

As expected, one can manage lists of all models through the admin, including coin prices:

Bitwatch Admin coin price

…coin transactions:

Bitwatch Admin coin transactions

…coins:

Bitwatch Admin coin list

…coin holdings:

Bitwatch Admin coin holding list

…and more. A Trader is the one-to-one model associated with a User to allow for decoration with additional data:

Bitwatch Admin trader

That then determines particular characteristics about a Portfolio and how it’s viewed:

Bitwatch Admin portfolios

Let’s not forget that the Portfolio also has a user-side representation, which I won’t go into too much detail about here:

Portfolio list

Portfolio details

Bitwatch uses django-axes to keep the baddies out:

Bitwatch Admin access log

Criticism and Improvements

This was quite a rushed project and didn’t have too much upfront design behind it. It does show this quite obviously, especially in the back-end logic and some areas of the UI. That said, it was very much a single-purpose mission to prove a very specialised question, which it did. The fact that it got turned into a full-blown website with a (very small) real user base was quite a feat, and also unexpected.

Conclusion

It seems that with the fast pace of cryptocurrency, the providers are able to keep up with technological changes with very impressive efficacy. It’s quite enjoyable to create tools like this, as one learns very interesting and educationally valuable lessons along the way, which would otherwise be lost amidst the noise and chaos that is the cryptocurrency world as it stands.

However, small steps is the key here, at least for me. You never know how much you’ll end up taking away if you break a problem as elusive as this one into bite-sized chunks.

Until next time, all the best!

This post is licensed under CC BY-SA 4.0 by the author.