Revolut as a Cryptocurrency Trading App: Earning or Losing Money?

The unbelievable bull run of the Bitcoin in 2020 (+160.40% gain p.a.) truly dominated investments in S&P500 index tracking instruments (+13.73% p.a.) or in gold itself (+21.60% p.a.). The reality is more dramatic. From the beginning of 2021 (1 BTC = U$28,990) till the moment as you read this (just six days later), BTC gained additional +37.99%. In today’s CoinMarketCap Daily Newsletter we find a note saying that strategists at JPMorgan have predicted that Bitcoin could head as high as $146,000 if investors continue to favor Bitcoin over gold. Nota bene, it is the same bank which was some two years ago very cautious and reserved when it came to possible future investments in numero uno of all cryptocurrencies. Time flies and things change. We can’t stop it. We can adjust or react. But how?

Lots of crypto-trading platforms like Binance, Bitstamp, Coinbase or Coinbase Pro stepped ahead to reach more customers. They have developed easy-to-use desktop online GUIs to facilitate making trades from your laptop. The same companies went additional mile and transferred their solutions to your mobile phone as an app. Therefore, now, you can open and close trading positions with a single move of your finger holding iPhone in your palm. That’s way too easy! Everyone can trade, invest, and become filthy rich just by sitting in the armchair. At least, in theory.




1. Cryptocurrencies with Revolut

One of these companies is Revolut. It quickly became famous as it offers to its users a possibility to exchange currencies at the rates extremely close to their spot exchange rates. All you need to do is open a free account with them. You can transfer your money to the account using your debit or credit card (instant deposit) or use currency account’s IBAN number for a slower (up to 2 days of waiting) bank transfer. With a basic account you can obtain a plastic VISA card that allows you card payments using deposited funds on your Revolut account(s) in all countries that accept that form of payment. A true benefit of using debit Revolut cards are their low spreads. In other words, if you need to pay for a rented car in Mexico with your bank credit card 350 EUR you can do it (that is why we apply to have credit cards, don’t we?!). However, the bank’s exchange rate will have (as always has!) much higher spread (the difference in price at which the bank is willing to sell you EUR and the prices at which it is willing to buy EUR from you). By paying with a bank credit card, your card’s account is charged a lot more than if you use Revolut card offering lower buy-sell spread for EUR. It pays off!

Founded in 2014, Revolut introduced cryptocurrency trading to its app three years later. As of now, it enables you to buy, hold, and sell 6 crypto-coins, i.e. Bitcoin (BTC), Bitcoin Cash (BCH), Litecoin (LTC), Ether (ETH), Ripple (XRP), and Stellar (XLM). You pick up your inner account holding funds (for example, in EUR or USD) as a source account, go into Cryptocurrencies section, click on the coin you want to purchase, specify amount, confirm, and that’s it! You own, say, 0.12 BTC worth U$4,201. Well, not exactly.

A meticulous reading of the Revolut’s Cryptocurrency Terms reveals, inter alia, that by entering into the agreement, you are appointing Revolut as your agent to provide Revolut’s crypto services (services that allow you to buy, sell, receive or spend cryptocurrency). You are also appointing Revolut to provide nominee services, which means that Revolut will act as your ‘nominee’ for the purpose of holding your cryptocurrencies. Moreover, if Revolut sees fit, it may appoint another person or organisation to hold your cryptocurrencies. That person or organisation would be referred to as a sub-custodian. In plain English it simply means that you do not own Bitcoin when you “buy” it with Revolut. You will own the rights to the financial value of any cryptocurrency Revolut buys for you! Revolut will hold your cryptocurrencies on your behalf and you will have a beneficial right to them. This means you can tell Revolut when to sell or transfer it (within the limits of these terms and conditions). You gain complete control of your cryptocurrencies, and Revolut will only act upon instructions you provide to the nominee. You will not be able to carry out transactions yourself — we read it further down on the same page. That explains a lot.

Revolut offers to their customers four plans of membership: Standard, Plus, Premium, and Metal. The first one is free, while the latter ones requires monthly payments of 2.99, 6.99, 12.99 GBP, respectively. The choice of your plan has a direct impact on the applied cryptocurrency-related fees, namely, with Standard and Plus plan, you will be charged an additional 2.5% while buying/selling your crypto-coin, however only 1.5% if you upgrade to Premium or Metal plan. Is it a lot? We will analyse it in a second. There is one more thing you need to know.

How does Revolut calculate the cryptocurrency rates? Revolut streams their crypto prices from from the exchanges they have partnered with. It has been revealed in 2017 that it is a Bitstamp exchange (see a famous tweet by @stevensharp1988 from Dec 8, 2017). The price you can see in the Revolut app is locked in at the time of the exchange and is calculated as a Volume-Weighted Average Price (VWAP) with a mark-up of 1.5% to account for trading volatility. Another hidden fee to account for… what exactly? According to Revolut it relates to other factors, such as market depth and volatility. To the VWAP rates they further apply a margin which you are shown separately before you execute a transaction, and afterwards in the transaction details. This is another way to say “We will charge you extra anyway”. C’est la vie. Die or deal with it.

Fees. This is a devil’s playground. This is how you lose your money in the act of buying and selling your cryptocurrencies with Revolut app. This is also how much you lose trading frequently with Revolut app.

In this article, we will analyse the pros and cons of manual-trading of crypto-coins in the scope of Revolut’s portfolio. Since there is no way to download Revolut’s crypto price-series from the offical source using Python API, we will:

  1. Try to recreate VWAP rates displayed by Revolut as the best proxy with BTC/USD as an example.
  2. Verify the fees claimed by Revolut with the ones displayed in the app.
  3. Write a Python code that simulates trading of Bitcoin during the last 2.5 months.
  4. Undertake the analysis of the number of trades you can do and the net profit you can earn.

This will reveal whether trading crypto with Revolut can be profitable enough to promote the app as your cryptocurrency trading partner or… not?

2. Proxy of Revolut’s VWAP for Bitcoin

Let’s assume that Revolut still makes use of data streamed by the Bitstamp exchange and VWAP methodology applied to what we can see in the app when inspecting the price chart. In order to recreate this price-series, first we need to understand what does the VWAP mean in detail. It is Volume-Weighted Average Price, and by definition, it is expressed as:
$$
VWAP = \frac{ \sum_i P_i V_i }{ \sum_i V_i}
$$ where $P_i$ stands for a price estimator calculated as:

$$
P_i = \frac{H_i + L_i + C_i}{3}
$$ where its three components correspond to High, Low, and Close price, respectively. VWAP can be seen as an average price, e.g. Bitcoin, in a given time interval, weighted by traded volume and serve to traders as a tool to identify entry and exit points or for trend confirmation. Above, $V_i$, can be expressed as quantity traded between time $t$ and $t+\Delta t$ denoted by subinterval $i$ where the sum over all $i$’s can relate to a time period between $t$ and, for example, $10t$.

Alternatively, if we do not have an access to direct information of volume/quantity of an asset traded between $t$ and $t+\Delta t$, we can view VWAP $i$-th intervals differently. For instance, if we have an access to 1-min BTC/USD series, we can calculate 5-min VWAP by accumulating five 1-min $P_i$’s and the corresponding five $V_i$ values ($i=1,…,5$). The flexibility to compute VWAP time-series therefore depends on the timeframe we want to select.

Below, we will develop code which uses Bitstamp’s 1-hour price-series for Bitcoin and calculates 3-hour VWAP series as a proxy. This selection is dictated by free data availability. The data provider we are going to employ here is CryptoCompare.com and its URL base call for hourly prices as outlined here. Let’s start coding it in Python:

# Earning or Losing Money in Cryptocurrencies with Revolut: 
# Simulated Trading for Bitcoin
#
# (c) QuantAtRisk.com, written by Pawel Lachowicz, PhD


import numpy as np
import pandas as pd
import requests
import json
from bs4 import BeautifulSoup
from datetime import datetime
import matplotlib.dates as mdates
import matplotlib.pyplot as plt

# fetching hourly price series for Bitcoin
url = "https://min-api.cryptocompare.com/data/v2/histohour?fsym=BTC
       &tsym=USD&limit=2000&e=Bitstamp"
        
# fetch the raw data
response = requests.get(url)
soup = BeautifulSoup(response.content, "html.parser")
dic = json.loads(soup.prettify())  # convert from json to dictionary

As the output we get a Python dictionary with the heaps of information of the following form:

{'Response': 'Success',
 'Message': '',
 'HasWarning': False,
 'Type': 100,
 'RateLimit': {},
 'Data': {'Aggregated': False,
  'TimeFrom': 1602759600,
  'TimeTo': 1609959600,
  'Data': [{'time': 1602759600,
    'close': 11317.62,
    'high': 11360.81,
    'low': 11308.36,
    'open': 11334.74,
    'volumefrom': 308.06,
    'volumeto': 3492223.15,
    'conversionType': 'force_direct',
    'conversionSymbol': ''},
   {'time': 1602763200,
    'close': 11380.22,
    'high': 11403.1,
    'low': 11278.3,
    'open': 11317.62,
    'volumefrom': 378.22,
    'volumeto': 4287363.04,
    'conversionType': 'force_direct',
    'conversionSymbol': ''},
   {'time': 1602766800,
    'close': 11410.52,

which we need to filter in the next step. Here the most relevant data are stored inside:

dic['Data']['Data']

inner dictionary. The ‘time’ is a CryptoCompare’s timestamp. It’s meaningless as an integer number but a help of the following function, we can translate the number into meaningful date and time mark:

def timestamp2date_hms(timestamp):
    # function converts a Unix timestamp into Gregorian date
    return datetime.fromtimestamp(int(timestamp)).strftime('%Y-%m-%d %H:%M')

# an example of use
print(timestamp2date_hms(1602759600))  # returns '2020-10-15 13:00'

Grabbing correct data requires a plain loop over all available timestamps. Therefore, for each iteration we collect information for $k$ internals in order to calculate $k$-hour VWAP series. Analyse the action figures inside the loop carefully. It should be straightforward to note where we extract prices and stores VWAP into DataFrame.

i, j = 0, 1
nom, dnom = 0, 0  # nominator and denominator for VWAP

# create empty DataFrame for stroing results
df = pd.DataFrame(columns=['Time', 'Close', 'VWAP'])

for e in dic['Data']['Data']:
    ts = e['time']  # timestamp
    high, low, close, volume = float(e['high']), float(e['low']), \
                               float(e['close']), float(e['volumeto'])
    tprice = (high + low + close) / 3
    nom += tprice * volume
    dnom += volume 
    i += 1
    if i == 3:
        vwap = nom / dnom
        df.loc[j-1] = [timestamp2date_hms(ts), np.round(close,2), \
                       np.round(vwap,2)]
        # reset local counters
        i = 0
        j += 1
        nom, dnom = 0, 0

        
df.set_index('Time', drop=True, inplace=True)

An exemplary output can be:

and when visualised, both VWAP and spot price time-series will look like:

import matplotlib.dates as mdates

fig, ax = plt.subplots(figsize=(12, 6))
ax.plot(df.VWAP, label="3-hour Bitcoin VWAP (Revolut's proxy)")
ax.plot(df.Close, label='Hourly Bitcoin Spot Price (Bitstamp)')
ax.legend()
ax.grid()
plt.gcf().autofmt_xdate()
myFmt = mdates.DateFormatter('%Y-%m-%d %H:%M:%S')
plt.gca().xaxis.set_major_formatter(myFmt)
plt.savefig("vwap_btc.png", bbox_inches='tight')

We can analyse the residual signal by expressing the relative deviations of VWAP from spot prices every 3 hours:

fig, ax = plt.subplots(figsize=(12, 6))
ax.plot((df.VWAP-df.Close)/df.Close, label="(VWAP - Spot)/Spot")
ax.legend()
ax.grid()
plt.gcf().autofmt_xdate()
myFmt = mdates.DateFormatter('%Y-%m-%d %H:%M:%S')
plt.gca().xaxis.set_major_formatter(myFmt)
plt.savefig("vwap_btc_resid.png", bbox_inches='tight')

# standard deviation of the residual signal
print(((df.VWAP-df.Close)/df.Close).std())


The standard deviation of the residual signal is 0.59% in this case but we can observe deviations as high as up to 2.8%.

My best guess is that Revolut uses spot time-series streamed by Bitstamp based on much finer grid that the one computed above. Nevertheless, for our further pursposes, 3-hour VWAP time-series is more than sufficient to simulate trades over this 2.5 month long test time period.

3. Revolut Fee Verification

The number of complexity related to VWAP data processing by Revolut plus what is being said about fees attached to cryptocurrencies for internal trading purposes nearly shout for the need of elementary verification whether what is claimed to be the fee, it is in the app. As an example, let’s have a look at the the selling attempt as shown below:

The screen informs us the if we intend to sell 0.5 BTC we will get in return U\$17,070.19 at BTC/USD rate displayed in the middle, and the fee for this transaction of U\$260.49 will be subtracted from U\$17,330.69. Since I am using Premium plan, this fee should be equal to 1.5% according to the Revolut’s website.

I have conducted an empirical analysis with various combination of BTC quantity to sell and deposit amount (after fees) converted to USD and PLN. The table below summarises the test screenshots which, of course, refers to random timing as of Jan 6, 2021:

With an accuracy of $\pm$U\$1 and $\pm$1 PLN, the fees calculated by Revolut app stay in an agreement with what is being promised to the app user. It is important to note, that Revolut allows itself to add additional +0.5% fee (for Standard, Plus, and Premium plan members) if the total amount of your cryptocurrency transactions in a single calendar month exceeds 5000 GBP. This means you, with a limited amount of trades per month you are allowed to buy or sell with 1.5% fee (2.5% for Standard and Plus plans), otherwise the next transaction you will make will have 2.0% and 3.0% fee applied, respectively. In this way, Revolut encourages you to upgrade to its Metal plan to avoid these extra 0.5% fees per any additional transaction.

4. Simulating Manual BTC/USD trading for Different Revolut Users

Empowered with the aforementioned details on fees, we are ready to write a piece of code which will allow us to simulate armchair-based buying and selling of BTC using USD account. We need to assume some initial amount we want to use to buy BTC. Let it be U\$10,000. We will simulate random times when we buy and sell counted in the multiple of 3 hour timestamps as provided by our VWAP time-series (Sect. 2). Every random timestamp will be a random integer number drawn from the uniform distribution spanned between 1 and par which is one of two model parameters. For the tests let us use it equal to, say, 25 and fix it. As for the second model’s parameter we will use the fee level expressed as a decimal percentile and denoted in the code as fee_level variable. As discussed it can be 0.025 or 0.015 depending on your membership.

The following code return the DataFrame with key information about all closed trades:

np.random.seed(1311)

log = pd.DataFrame(columns=['i_open', 'i_close', 'Open Price', 'Close Price', 'Quantity', 
                            'At Open', 'At Close', 'PnL', 'Account'])


fee_level = 0.0  # enter fee level based on Revolut user plan (0.025, 0.015)

i, n_trade = 0, 1
fi = True  # first iteration
trade = 'buy'
account = 10000  # USD

# model parameter (random)
par = 40  # hours

while i <=  n - par :
    
    if fi:
        open_trade = True
        j = np.random.randint(1,par+1)
        i = j
        open_price = vwap_btc[i]
        n_btc = np.round(account / open_price, 6)
        print(account, np.round(account / open_price, 4))
        usd = n_btc * open_price * (1 - fee_level)
        n_btc = np.round(usd / open_price, 6)
        at_open = n_btc * open_price
        i_open = i
        fi = False
    else:
        j = np.random.randint(1,par+1)
        i = i + j
        if open_trade:
            i_close = i
            close_price = vwap_btc[i]
            at_close = n_btc * close_price * (1 - fee_level)
            pnl = at_close - at_open
            account += pnl
            open_trade = False
            log.loc[n_trade-1] = np.array([i_open, i_close, open_price, close_price, n_btc, 
                                           at_open, at_close, pnl, account])
            n_trade += 1
        else:
            i_open = i
            open_price = vwap_btc[i]
            open_price = vwap_btc[i]
            n_btc = round(account / open_price, 6)
            at_open = n_btc * open_price
            open_trade = True

The log of trades looks like:

These results are for the case where no fees are applied. You can see that after a number of trades distributed randomly:

fig, ax = plt.subplots(figsize=(12, 6))
ax.plot(df.VWAP, label="3-hour Bitcoin VWAP (Revolut's proxy)")
ax.plot(df.VWAP.index[log.i_open.astype(int)], log['Open Price'].values, 
                                               'b^', label="Open Trade")
ax.plot(df.VWAP.index[log.i_close.astype(int)], log['Close Price'].values, 
                                               'rv',label="Close Trade")
ax.legend()
ax.grid()
plt.gcf().autofmt_xdate()
myFmt = mdates.DateFormatter('%Y-%m-%d %H:%M:%S')
plt.gca().xaxis.set_major_formatter(myFmt)
plt.savefig("/users/pawel/Desktop/vwap_btc_trades.png", bbox_inches='tight')

the account level (in USD)

fig, ax = plt.subplots(figsize=(12, 6))
ax.plot(df.VWAP.index[log.i_close.astype(int)], log.Account, label='Cummulative PnL')
ax.legend()
ax.grid()
plt.gcf().autofmt_xdate()
myFmt = mdates.DateFormatter('%Y-%m-%d %H:%M:%S')
plt.gca().xaxis.set_major_formatter(myFmt)

resulting in:

reach the level of U\$16,355.80 or the fee-free profit of +63.56%.

The situation changes when we rerun the same model with different fee structure applied:

For this case study, regardless of the plan we have, the random acts of buying and selling Bitcoin paid off. We ended up with a positive profit but we can observe how crucial the impact of the fees is.

It’s worth playing with this code to understand the model’s feedback to various values of the par parameter (code line #99). It may occur that the lack of BTC in its bull run plus 2.5% in fees will kill any profit every time you trade just by looking at the BTC/USD price chart. This is how the beginners start their journey with cryptocurrencies. Random points of trading.

So, tell me, is Revolut your favourite choice for cryptocurrency trading now?




Leave a Reply

Your email address will not be published. Required fields are marked *