Sunday, 11 March 2018

Project Writeup: Experimenting with high-ish frequency Cryptocurrency trading algorithms



On the back of a lot of cryptocurrency hype at the end of 2017 I found myself looking at volatile charts and instant trading APIs and wondering if (with a bit of algorithmics) it would be easy to detect and execute some small but reliable short-term trades (ie: buy and sell in than 60 minutes).

I used Bittrex as the exchange because it has a wide variety of low and high marketcap currencies while also being established enough to have a good API and a feeling of relative safety.

The plan was to examine the feed of trade data, detect a sudden rise in price and then walk back through the previous trades to try and find some patterns. That sounds like a job for machine learning task to me and probably does to you too, but first lets gather the data.

Recording a real-time trade feed

Bittrex provides a REST API to read trade feeds through a handy Python interface, but if you actually subscribe and watch the data, you will see that it lags way behind what appears on the web interface - and misses a bunch of trades out entirely. This makes it useless for us.

Fortunately the folks at Coinigy have reverse engineered the Bittrex UI to feed their own websocket  tradefeed, which provides the following data for each trade:
  • Price
  • Volume traded
  • Whether the trade was a Buy/Sell
I couldn't find an easy way to connect to it with C++, so instead we can use their python bindings to send the results to our C++ Qt tradebot program over a pipe using this script.


The bot also displays the data in real-time for each currency, which is very handy for manual trading too. For the sake of simplicity I've stuck to BTC trade pairs.

Fields are: Currency pair, last price (in BTC), volume over last 10 mins, price change (2, 5, 10, 30, 60 mins), Buy:Sell ratio (2, 5, 10 mins)

Defining trade parameters

After recording ~480 hours of trades from over 200 coins/tokens, we can now backtest any trading algorithms we come up with to see how they might fare against the market. Glaring problems with that idea will be covered later.

There are two sets of parameters to tweak our trades:

Trade Alert Thresholds - the conditions that need to be met to buy a currency
  1. Change in price (%)
  2. Change in volume %
  3. A Buy/Sell percentage threshold exceeded
  4. A minimum Btc volume that must be met (to avoid small activity in tiny coins causing problems) 
These can be adjusted to apply over 5,10,20,30 or 60 mins.

Trade Settings - how we buy and sell
  1. HaltBuyAny - How long to leave the buy order open before giving up. We didn't buy any, so we can exit this trade without issue.
  2. HaltBuyAll - How long to leave the buy order open. We bought some, which has to be sold.
  3. Buy Amount - How much to buy. More means more profit/loss but may be harder to buy and sell at the target prices.
  4. Target Sell - Percentage profit to aim for on each trade. Bittrex charges 0.25% for each trade, so we are making a loss if we don't sell 0.5% higher than we bought.
  5. Stop Loss Sell - How far can the price drop before abandoning the trade at a loss.
  6. Soft Timeout Sell % - If a trade takes a long time then it prevents that capital being used for other trades. If the soft timeout expires then we will sell at or above this profit level.
  7. Soft Timeout - How long until we start looking for a profitable early exit.
  8. Hard Timeout - If the price is flat after this many seconds then we sell at a loss so we can look for more profitable trades with the capital. Set this too low and we lose more on bad buys, too high and we sell before the price jumps up.

An Example Experiment

The base settings we are using for the blogs tests

The pictured settings will start a trade after:
  • a price decrease of 5% over 30 mins combined with 
  • a volume increase of 25% over 30 mins, but 
  • only if there has been an average of 1+ BTC of the currency traded per minute over 30 mins
Trades are carried out according to the following rules:
  • We buy 0.15 BTC worth of currency.
  • If we can't buy any at the target price after 40s we give up.
  • If we buy some (but not all) after 80s then we close the buy order and start selling.
  • If the price increases by 3% we sell for a profit.
  • If the price decreases by 11% we sell at a loss.
  • If 1600s have elapsed and the price has risen by at least 1.2%, we sell
  • If 1800s have elapsed, we sell whatever the price
 If we simulate trades with these settings against that data, we get the following results:

A section from the trade results window. The idea was to be able to 'see' patterns in the data.

All profit percentages listed are after exchange fees.
  • The run took 2 mins 30 seconds
  • 1053 trades started - approx 2 per hour but in practice they often happen in bursts.
  • 375 were sold above our target 3% threshold
  • An additional 223 made at least some profit
  • 16 dropped so far they required a stop loss sale to kick in
  • 13% of trades didn't start, because nobody sold at the initial price
  • The average profit was 0.88%
  • Profit for all trades combined appeared to be 911%
Highlights of the stop-lossses include:
  • Mysterium (staking involves being an exit node for a pseudo-VPN) illustrates why having a stop loss is important: Bittrex delisted it because they didn't have a working product. Now you can't buy it on any sites I would trust enough to visit without a script blocker - let alone send money to. 
  • eBoost we bought at 17200 sats on the 10th and then 8000 sats 4 days later. At the time of writing it's worth 1814. Ouch.
  • Ubiq hit 50k sats at the end of Jan so we could have made a profit on our 30-32k sat buys if we held over multiple weeks, though it's now down to around 24k.
  • DigitalNote we bought near an All Time High and has sunk 80% since then. Another disaster of a trade.
  • I don't know what Apex is but it isn't listed on Bittrex anymore, despite the price not having crashed that much on Cointopia. 
This neat tick will let you lose 100% of your money in 9 easy trades. Credit Card companies hate it!

Doing Science

Now our testing platform is built, lets use a highly technical, scientific and respected method of finding optimal settings: brute forcing a bunch of different numbers.

Here are some graphs that illustrate how the profit changes by changing each of the variables. All time periods used are 30 mins unless otherwise stated.

The blue line in graphs is the total profit we made across all trades (the one we care about).
The red is average profit per trade; if this is high and the blue line is low that means a very small number of higher profit transactions was happening.


Trade Trigger Tests
When will a currency rise in price?

Test 1:  Predicting price rises using recent price changes

Test 1 results - Don't buy high. More groundbreaking analysis head.

Q: How much should the price change before we decide to buy?

Result: Between -4% and -6% provided greatest overall profit.. After a sudden pump, the price tends to fall - so don't chase pumps kids. Buy after a sudden dip and the price will tend to recover. Extreme drops of 10% or more provided the most profit per trade but there were less of them. 

Test 2: Changing the BTC volume threshold for starting a trade

Can we boost profits by filtering out low volume coins?
Test 2 results

Result: No. Restricting yourself to high volume coins will increase your profit per-trade (up to about 4 BTC/min) but massively decreases the number of trades carried out - and total profits.


Test 3: Predicting price rises using Volume Delta%



Q: Can you predict a big price change based on a sudden change in volume? I had no real idea of the answer to this.

Test 3 results




Result: Not useful with the settings chosen (how last 5 mins volume compares to last 30). 

Requiring between -100% and +200% had a neligible affect on average profit. Above 200% is an unusual event for a currency, improving profitability of each trade while drastically reducing the number of trades.
It's possible that looking for shorter term volume changes will help, but in my testing they didn't mean very much - just triggered on every big trade.

Test 4: Predicting price rises using Buy/Sell %

Q: What does a change in Buy/Sell % over the last 10 mins tell us?

Test A - At least X % of trades are Buys

Test 4A Results: >60% is where people are buying the dip - a sharp price rise is coming

Result A: There is a tiny boost in profits if we filter out markets with less than 20% of trades (by volume) being Buys, but for lots of trades - restricting to high Buy % is a bad idea. Very high buy percentages (60% +) were rare - bearing in mind that these trades are accompanied by a 5% price drop - but when they happen it's a really good indicator of an incoming price spike,


Test B - At Most X % of trades are Buys

Test 4B results

Result B: As above, chasing price dips makes more total profits when 60% or more of trades (by volume) are buys. Profit per trade was actually slightly better when at most 30-40% were buys - but we don't care about that.

This metric is not really useful.



Trade Execution Tests
How do we sell our currency for the highest profit?


Test 5: How much currency to trade

This one is both very important and least suitable for this kind of testing, because the more you want to buy, the more it actually affects the market.

Result: The amount of bitcoin spent on each trade had no significant effect on profit %. This feels like a bug but the meaninglessness of the result when used against historical data means there is not point dedicating time to verifying/fixing it.


Test 6: When to give up on a trade after failing to buy any currency

It would probably be more reliable to look at the sell orders and buy currency on offer rather than place a buy order at the last price (as this experiment does). May as well test it though...

Test 6 Results


Result: If your buy order doesn't get any bites after 60-90 seconds, waiting longer is unlikely to help much.


Test 7: When to start selling a partially filled buy order

This is a little different to the above: once we have bought any currency we are (in this scenario) committed to selling it. If we have bought some, how long should we wait for more or all of the order to be filled before we try to sell it?

Result: The results were unaffected by this variable. If an order was filled at all, it tended to be filled completely (with the 0.15 BTC test amount) - so this had no significant effect. This is something that is very much going to be determined by the amount of currency you are buying (see Test 5) and is another thing that doesn't work well when backtesting using old data.


Test 8: Target Profit Percentage

This is more interesting. Set it too high and trades will take a long time without filling many sell orders. Set it too low and you will barely make enough after fees to cover the bad trades.

Test 8 results


Result: The best results are found around the 1-5% range. As the target sell price is rarely reached the average profit is essentially just pinned to the the soft timeout profit threshold. 

The target is still regularly being met however and is very much affecting the total profit - even a 30% profit target was fulfilled during a massive pump and dump with the Apex currency we saw earlier. Profits drop as the target gets more and more unreachable - this shows that we rely on occasional big profits to make up for occasional big stop losses.


The Apex (APX) spike we caught for a 30% profit, as it happened on the Cryptopia marketplace






Test 9: Stop Loss Percentage



Test 9 results - Stop loss profit percentage



Result: We are mostly protected from a currency crash by the hard trade timeout. A drop of any more than 20% within that period was so rare that we can be quite bold with our stop loss - being less risk averse causes us to sell before dips bounce and is a bigger risk to profits than a crash.


Test 10: Soft Sell Target Percentage

The question now is: Since our average profits are tied to our soft timeout profit target, what happens if we raise it? This test will require changing both this variable and the target sell % variable.
Lets keep the target 5% higher than the soft sell-off % for this test.

Test 10 results

Result: Effectively this means once the timeout had expired, we should sell as soon as we can without taking a loss. This is our reminder that getting many high profits on every trade on a sub-60 minute scale is not going to happen. It's better to aim for lots of easy targets.

Test 11: Soft Exit Timeout

Test 10 started selling at a low profit after around 27 minutes. What if we change that time?

Test 11 Results 



Result: Minimal effect on total profit.

I was hesitant to include this graph because after 1400s we start encroaching upon the hard cap of 1800 seconds. To deal with this I extended the hard cap to 400s past the soft cap, which boosted the profit in the second half of the graph by giving the currency more time to hit the price targets. This leads us onto the next test, where we deal with this.


Test 12: Hard Exit Timeout

This is tricky because the longer we leave a bad trade before selling at a loss, the longer our capital is tied up. If you are doing lots of trades with small amounts of capital then this isn't a problem, but if you are trading with a majority of your capital for each trade then it's not good.

Another question is where to set the soft cap. I'll leave it at 50% of the hard cap.

A final extra problem with this is that some of my recorded sessions are quite short. A long timeout means more trades where the data runs out, so the sample size for this test is reduced.


Test 12 results

Result: Waiting longer tends to result in better results. After a 5% dip, currencies tended to recover very quickly. We could say that longer trades result in more profit, but this kinda goes against the spirit of an experiment to find a good algorithm for very quick trades.

Summary

There are a number of reasons why you probably shouldn't get excited about the 100% profit figure in almost 500 hours of trading.

  • It's historical data - your trades might change the market
  • Trades are simulated by looking at the trades that happened at our target prices and pretending we made them. If we actually put buy/sell orders up then - they might not have been filled.
  • In practice there is non-trivial latency between your desired actions and their execution on the exchange via the API.
  • Many trades were abandoned because the data stopped. It would help to collect thousands of hours of uninterrupted trade data - or you can buy it. I think Coinigy either sells it or plans to.
Improvements we can make:

  • Multi-variable experimentation. Most of the graphs represent the change of a single variable. There are 14 variables available - plus different time periods to apply them over - all interacting in different ways with each other. I'd almost be tempted to just fuzz it with random numbers for a few weeks and see which combination comes up with the best profits.
  • Filling existing buy/sell orders instead of placing our own orders. There is a websocket feed for those.
  • More exchanges.
  • More coin pairs.
  • Examining longer periods of time for trade triggering and trade execution. The 10-60 minute thing was arbitrary.
If I were going to pay someone to do this, I'd probably find a machine learning specialist and buy some big datasets for them to use.

The code is available here