Algorithmic trading with bitcoin – part 1

Adverse selection
Submit to StumbleUpon

Hi and welcome back to my blog! This is the first in a series of articles which describes the process of building a trading algorithm for bitcoins. All the algorithms featured will be live tested on a real bitcoin exchange and results of profitability given.

I will discuss the problems with commonly used trading algorithms, indicators and adverse selection.

It’s aimed primarily at programmers although the content ought to be readable enough in general to interested traders.

Readers unfamiliar with algorithmic trading in general should read my primer on the subject.

Why?

  • There is very little documentation on the internet which covers the various different technologies and processes required to build a algorithmic trading framework for bitcoin.
  • Bitcoin exchanges have now reached a point where there is sufficient liquidity to make live testing feasible without waiting hours for a trade to occur.
  • Unlike in Forex, bitcoin trading cuts out the middle-man; there is no broker taking his cut of your profits, you deal directly with the exchanges yourself.
  • There are bitcoin exchanges which offer zero trading fees, which is very attractive and totally unheard of in Forex.

Which exchange to chose?

As I write this, there are approximately 100 bitcoin exchanges to chose from. So how to pick the right one?

My requirements are:

  • High liquidity
  • Zero trade fees
  • API available

To my knowledge there are only two exchanges offering zero trade fees at the moment. BTC China and Huobi – both of which are Chinese exchanges, but the websites do have english sections, if you can find them. You can deposit and withdraw bitcoins instantly from both exchanges so needing access to Chinese Yuan isn’t necessary, luckily! Both also offer API access.

Taking a look at a graph of the same time period in both exchanges gives some idea of the liquidity difference:

BTC China

BTC China

Huobi

Huobi

The above are graphs from the 1 minute time period; notice the gaps in the BTC China graph in the earlier part of the time period compared to the Huobi graph? That is a difference in trade frequency which goes hand in hand with liquidity. A closer look at the traded volume for both exchanges (enlarged from above) during the same period is revealing:

10 BTC traded on BTC China

10 BTC traded on BTC China

200 BTC traded on Huobi

200 BTC traded on Huobi

So, Huobi has over 20 times the traded volume and has a higher trade frequency than BTC China making it the best choice.

The Huobi API

The Huobi API is somewhat quirky and inadequate in a few places. In particular:

  • Every authenticated request must include a created field which is a unix timestamp. Unfortunately, their server’s clock is about 1 minute 30 seconds behind actual UTC, which means you have to offset the values you send to compensate, otherwise you get error 70 back from their API.
  • They have a rate limit for every API call of 1 second, and each call has individually monitored limits. However in practice I found I actually needed to wait 1.5 seconds between calling the same method twice.
  • The only way to get public trade data from their API gives back a structure which is highly lacking; the time returned is a string with no date component and there is no trade ID which means differentiating between old and new trades is impossible with absolute certainty.
  • During periods of high activity it is very common for API requests to fail or timeout, so you must implement a robust error handling and retry policy.

All prices submitted to Huobi must be truncated to 2 decimal places. All amounts should be truncated to 4 decimal places.

The documentation can be found here:

https://www.huobi.com/help/index.php?a=market_help
https://www.huobi.com/help/index.php?a=api_help

To save you the trouble of implementing this all yourself, please take a look at the C# Huobi API I have made available on Github.

The trading framework

The trading framework itself is very much more than just API access to Huobi. In particular we want to be able to visualise:

  • The price action in graph form in real time
  • Our buy/sell orders which have been placed by the algorithm(s) at the location on the graph where they occurred
  • Whether said buy/sell orders were actually filled
  • Profit/loss chart
Trading action on left, profit/loss on right

Trading action on left, profit/loss on right

Something like the above is really quite necessary in order to quickly help identify problems with trading algorithms and to give an idea about the profitability.

Calculating profit

Return on investment, or ROI is used to as the profitability measure of each algorithm; this way the amount of BTC invested doesn’t affect the displayed profitability because its a relative percentage. This allows you to get consistent results between tests as your balance changes. You calculate ROI by taking a snapshot of the amount of fiat and BTC you have at start time, then convert fiat into BTC using the current mid price (bid + ask) / 2 and subtract what you have now from what you used to have, then divide by what you used to have and multiply by 100 to get a percentage.

decimal totalBtcValueStart = m_startInfo.m_TotalCny / midPrice + m_startInfo.m_TotalBtc;
decimal totalBtcValueNow = infoNow.m_TotalCny / midPrice + infoNow.m_TotalBtc;
decimal profitPercent = 100*(totalBtcValueNow - totalBtcValueStart) / totalBtcValueStart;

The price series

Raw tick data is used as the price series because candlesticks hide too much information.

Raw tick data

Raw tick data

The graphing is handled by System.Windows.Forms.DataVisualization.Charting tools.

The framework

The framework should provide a modular system within which to try multiple different algorithms. To that end I have a class AlgoBase which each algorithm I’ll describe in this series derives from.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
using HuobiApi;
 
namespace bitcoinTradingFramework.Algorithms
{
	public class AlgoBase
	{
		protected List<HuobiOrder> m_lastOpenOrders;
		protected Huobi m_huobi;
		protected HuobiMarket m_market;
		protected Rendering m_renderer;
		protected HuobiAccountInfo m_startInfo;
 
		/// <summary>
		/// 
		/// </summary>
		/// <param name="huobi"></param>
		/// <param name="market"></param>
		/// <param name="renderer"></param>
		public AlgoBase(Huobi huobi, HuobiMarket market, Rendering renderer)
		{
			m_huobi = huobi;
			m_market = market;
			m_lastOpenOrders = m_huobi.GetOpenOrders(m_market);
			m_startInfo = m_huobi.GetAccountInfo();
			m_renderer = renderer;
		}
 
		/// <summary>
		/// 
		/// </summary>
		/// <param name="now"></param>
		/// <param name="infoNow"></param>
		protected void CalculateProfit(DateTime now, decimal midPrice, HuobiAccountInfo infoNow)
		{
			//
			// total ROI
			//
 
			decimal totalBtcValueStart = m_startInfo.m_TotalCny / midPrice + m_startInfo.m_TotalBtc;
			decimal totalBtcValueNow = infoNow.m_TotalCny / midPrice + infoNow.m_TotalBtc;
 
			decimal profitPercent = 100*(totalBtcValueNow - totalBtcValueStart) / totalBtcValueStart;
 
			Console.WriteLine("profit % = " + profitPercent);
 
			m_renderer.AddProfitDataPoints(profitPercent, now);
		}
 
		/// <summary>
		/// 
		/// </summary>
		/// <param name="o"></param>
		/// <returns></returns>
		protected bool CancelOrder(HuobiOrder o)
		{
			bool traded = false;
 
			try
			{
				HuobiSimpleResult cancelResult = m_huobi.CancelOrder(m_market, o.id);
			}
			catch (HuobiException e)
			{
				// ignore order which have been filled, or cancelled
				if (e.m_error.code != 41 && e.m_error.code != 42)
				{
					throw;
				}
				else
				{
					if (e.m_error.code == 41)
					{
						// not found, so filled
						m_renderer.AddMarker(o.type == HuobiOrderType.buy, true, o.order_price, UnixTime.ConvertToDateTime(o.order_time));
 
						traded = true;
					}
 
					m_lastOpenOrders.RemoveAll(loo => loo.id == o.id);
				}
			}
 
			return traded;
		}
 
		/// <summary>
		/// 
		/// </summary>
		protected void CancelOpenOrders()
		{
			foreach (HuobiOrder o in m_lastOpenOrders.ToList())
			{
				CancelOrder(o);
			}
		}
 
		/// <summary>
		/// 
		/// </summary>
		public virtual void Update(DateTime now)
		{
 
		}
	}
}

AlgoBase defines a number of utility functions, like CancelOpenOrders() and CalculateProfit() and also provides a virtual Update() function which each algorithm can implement. This Update function is called at regular intervals by the main program and is where the trading actually occurs.

Algorithm choice

The choice of algorithm to implement is governed by a few fundamental truths, learned from experience:

  • Trading using ‘technical analysis’ simply does not work
  • Indicators only work on one predefined timescale, yet the market is constantly changing
  • Attempting to make indicators adapt to the market is like trying to fit a square peg into a round hole
  • The only true sources of information are the orderbook and trade history

With these points in mind, the first set of algorithms I’m going to explore is the Market Maker(s).

Market maker

A market making algorithm is one which posts limit orders on both sides of the book, attempting to make profit by providing liquidity to eager traders who have an opinion about the future direction of the price (alpha). These class of algorithms are directionless, in that they do not make profit from the directional movement of the price, but rather from pairs of buys and sells, buying price being lower than selling, of course.

The key problem for the market marker is that of adverse selection.

Adverse selection

Adverse selection is when your buy order gets filled but then the price falls, leaving the twin sell order unfilled. The same thing can happen with sell orders, when the sell is filled and price raises.

Adverse selection

Adverse selection

In the above image, white arrows represent unfilled orders, red arrows are filled sell orders and green arrows are filled buy orders. The price is the red line. Notice how the first sell order gets filled and then the price goes up, leaving the twin buy order unfilled? The same thing happens to the next three buy orders. If the unfilled orders had been filled, each would have been a profitable set of trades, but due to adverse selection each was a losing trade.

To demonstrate this problem, the first market making algorithm I’m going to explore is the naive market maker.

The Naive Market Maker

The naive market maker places buy/sell orders at the best bid/ask price in the book at each update period. The idea being to capture the spread each time and make the associated profit. The algorithm is simple:

  • Cancel all open orders
  • Place two new buy/sell orders at the best bid/ask in the orderbook
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
using HuobiApi;
 
namespace bitcoinTradingFramework.Algorithms
{
	public class NaiveMarketMaker : AlgoBase
	{
		const decimal kMinTradeThresh = Huobi.kCent;
 
		/// <summary>
		/// 
		/// </summary>
		/// <param name="huobi"></param>
		/// <param name="market"></param>
		/// <param name="renderer"></param>
		public NaiveMarketMaker(Huobi huobi, HuobiMarket market, Rendering renderer) : base(huobi, market, renderer)
		{
		}
 
		/// <summary>
		/// 
		/// </summary>
		public override void Update(DateTime now)
		{
			base.Update(now);
 
			CancelOpenOrders();
 
			HuobiAccountInfo info = m_huobi.GetAccountInfo();
			HuobiMarketSummary summary = m_huobi.GetMarketSummary(m_market);
 
			decimal midPrice = (summary.GetAskPrice(0) + summary.GetBidPrice(0)) * 0.5M;
 
			CalculateProfit(now, midPrice, info);
 
			decimal buyPrice = Huobi.NormalisePrice(summary.GetBidPrice(0));
			decimal sellPrice = Huobi.NormalisePrice(summary.GetAskPrice(0));
 
			decimal amountCanBuy = Huobi.NormaliseAmount(info.available_cny_display / buyPrice);
			decimal amountCanSell = Huobi.NormaliseAmount(info.available_btc_display);
 
			bool canBuy = amountCanBuy >= Huobi.kMinAmount;
			bool canSell = amountCanSell >= Huobi.kMinAmount;
			bool dontTrade = sellPrice <= buyPrice + kMinTradeThresh;
 
 
			if (!dontTrade)
			{
				if (canBuy)
				{
					// we can action a buy!
					HuobiOrderResult result = m_huobi.Buy(m_market, buyPrice, Huobi.kMinAmount);
					Console.WriteLine("Buy " + Huobi.kMinAmount + "BTC at " + buyPrice);
 
					m_renderer.AddMarker(true, false, buyPrice, now);
				}
				if (canSell)
				{
					// we can action a buy!
					HuobiOrderResult result = m_huobi.Sell(m_market, sellPrice, Huobi.kMinAmount);
					Console.WriteLine("Sell " + Huobi.kMinAmount + "BTC at " + sellPrice);
 
					m_renderer.AddMarker(false, false, sellPrice, now);
				}
			}
 
			m_lastOpenOrders.AddRange( m_huobi.GetOpenOrders(m_market) );
 
			m_renderer.ReformatGraph();
		}
	}
}

The results of live testing this algorithm for 6 hours on Huobi are illuminating.

Click to enlarge

Click to enlarge

It consistently suffers the adverse selection problem, losing an impressive 2% of invested BTC over the 6 hour period! In addition, because of the erratic movement of the best bid/ask prices, adverse selection losses are exacerbated because often pair trades are part filled, price moves away, algorithm re-prices, opposite trade gets filled and price moves back again, losing on both sides of the trade.

Dealing with adverse selection

In the next article I’m going to talk about dealing with adverse selection. In particular I’m going to explore the academic literature on the subject and whether techniques like those described by Glosten and Milgrom actually still have any relevance these days in practice.

The source

You can access the source code accompanying this article via this GitHub repro. It contains the Huobi Api and everything else I’ve described here so far, including the Naive Market maker.

Until next time, have fun!

Cheers, Paul.

Submit to StumbleUpon

About Paul Firth

A games industry veteran of ten years, seven of which spent at Sony Computer Entertainment Europe, he has had key technical roles on triple-A titles like the Bafta Award Winning Little Big Planet (PSP), 24: The Game (PS2), special effects work on Heavenly Sword (PS3), some in-show graphics on the BBCs version of Robot Wars, the TV show, as well as a few more obscure projects.   Now joint CEO of Wildbunny, he is able to give himself hiccups simply by coughing.   1NobNQ88UoYePFi5QbibuRJP3TtLhh65Jp
This entry was posted in Algorithmic trading, Bitcoin, Finance and tagged , , , . Bookmark the permalink.

2 Responses to Algorithmic trading with bitcoin – part 1

  1. No Body says:

    Wait to you get to do the taxes on this. Then you will know what pain is.

  2. someone says:

    I’m hungry for more. Can please you link to some information that you have been learning from, especially the technical aspects on the programming side.

    Looking forward to part 2.

Leave a Reply

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

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

WP-SpamFree by Pole Position Marketing