Triangular Arbitrage: Still profitable in 2023?
Triangular Arbitrage is a technique in which an investor can make profit by exploiting price discrepancies between multiple (usually 3) different trading pairs. This is usually done in the cryptocurrency market, thanks to its high volatility.
Arbitrage, what's that?
According to Investopedia: "Arbitrage is the simultaneous purchase and sale of the same or similar asset in different markets in order to profit from tiny differences in the asset’s listed price. It exploits short-lived variations in the price of identical or similar financial instruments in different markets or in different forms.". In terms of crypto arbitrage, 3 common techniques can be found. Cross-exchange arbitrage, where one will exploit price differences between 2 crypto exchanges. Spatial arbitrage, where arbitrage opportunities can arise from differences in demand from different parts of the world, therefore creating a price gap. And finally triangular arbitrage, which we will be looking into in this blog post.
One might imagine every currency can be swapped for any other instantly by calculating their respective real word value, but that isn't exactly true. In the world of cryptocurrency exchanges, trades are done in pairs. A pair is formed using 2 different currencies, a base currency and a quote currency. For example, a common pair would be BTC-USDT. In this case, BTC is the base currency and USDT the quote currency. You buy BTC with USDT, and sell BTC for USDT. In centralized exchanges, the price of a pair is dictated using an order book. There are two sides to such a system, buy orders and sell orders. Buy orders show the "bid" price for the trading pair; the maximum price buyers are willing to pay for the asset. Inversely, sell orders give the "ask" price which is the minimum price a seller is requesting for the asset. Each order in the limit book also comes with a "volume". Which is the amount of the asset being sold at that particular price.
In order to perform triangular arbitrage, a price difference needs to be found between 3 or more trading pairs. The objective is to start with a certain number of one currency, trade it for another, and again, for you to then end up with more of the same currency you started with. This profit is then yours to keep. Let's create a simple example (I will be using the one shown in the cover image). We have 3 pairs: ETH-USDT, ETH-BTC, and BTC-USDT. Imagine you spot a difference between these 3 pairs using an automated program. 1 USDT could get you 0.1 ETH, 0.1 ETH could get you 0.012 BTC, which could then be turned back into 1.2 USDT. Of course these conversion rates are purely hypothetical and are not real, but I think it explains triangular arbitrage pretty well. All of this is in theory, but since it's being mentioned so often, surely it's applicable in practice, right? Right...?
Triangular arbitrage in practice
For this first experiment, my trading platform of choice will be Coinbase. Coinbase has a great user interface, many trading pairs, and overall good reputation. Additionally, it offers an API for developers to create automated trading algorithm. And on top of that, a Coinbase client library for easily interacting with their API, how neat!
The algorithm is pretty simple. We fetch all prices from Coinbase then start bruteforcing each possible 3 coin combination, easy right?! WRONG! At that time, I didn't know what an order book was, and was very confused by the Coinbase advanced trading tab. I thought most of those colored numbers were not important, but it turns out that they play a crucial role in trading. I blindly executed triangulation opportunities found by my program manually, just to find out that I lost money in the process. It was weird for me as I had made sure Coinbase fees were taken into account while calculating potential triangulation opportunities. I had come to the conclusion that some other trader had already taken advantage of the price difference, making the prices shift again and therefore making the triangulation opportunity no longer profitable. To combat this, I wanted to make it so trades were automated and executed in a split second. Turns out the official Coinbase library was outdated, and the only good alternative was for Coinbase pro which is shutting down soon. Welp, guess I had to make my own implementation after all.
But even after that, my balance was still decreasing after the trades... After having learned a bit about exchanges, I realised I was using the "spot" price for my calculations. That means the average between the ask and bid price, which is far from the real price I would have to pay to buy the currency. These losses made me learn about the basics of an order book, and how orders are fulfilled using a matching engine. In short, when you place a (buy) market order, the matching engine will try to buy the currency at the best price. But depending on how much you buy, it may have the resort to sell orders at a higher price, because the volume being sold at the best price may be smaller than the amount of the currency you are trying to buy. The best sell order gets fulfilled and therefore removed, making the price rise, and the remainder you want to buy being taken from more expensive sell orders.
I modified the program again for it to take ask and bid price (instead of spot price) depending on whether I am buying or selling respectively, but surprisingly, it still found some trades with a profit margin of 1%. After checking them manually, it turns out some people were placing orders with an absurdly low volume, making the triangulation unprofitable (because of fees) and therefore invalid. I had repeated this same experiment for another exchange called Kucoin, but that didn't succeed either. What I've been able to conclude: there are no real triangular arbitrage opportunities (at least on Coinbase and Kucoin) which can make more profit than what is spent on trading fees.
Triangular arbitrage on PancakeSwap v2
PancakeSwap is a so-called decentralized exchange, or DEX for short. This means that it isn't backed up by some large company like Coinbase, but is based entirely on the blockchain. This is possible thanks to the use of "smart contracts". I would describe it as small computer programs living on the blockchain. The difference with a normal program is that a smart contract is immutable (it cannot be changed once deployed to the blockchain) and can be used by everyone.
These programs don't run at all times like normal software, but rather run only when interacted with. One can "read" the state of a smart contract for free, but altering its state ("writing") requires a transaction on the blockchain. Since writing changes the state of the smart contract, the new state will have to be computed. This can be done thanks to the crypto miners. The one creating the transaction will pay a "gas fee", which will incentivise miners to run the smart contract and determine its new state. When a smart contract gets deployed, it will receive a unique address which is where you have to send transactions to to interact with the contract. Most of the time, contracts will have their source code made public as by default you will only get to see the EVM bytecode behind the contract. When the source code is made public, people can freely check whether the contract is trustworthy, and if they can safely interact with it. But this also opens up hackers to freely inspect the code and look for vulnerabilities. Keep in mind that smart contracts are immutable, meaning that if a bug is found it cannot be patched unless a new version of the contract is deployed.
Now PancakeSwap (which is based on Uniswap) doesn't use an order book, like most centralized exchanges, but uses an Automated Market Maker (AMM) instead. Prices aren't determined by buy and sell orders, but with mathematical functions instead. Swaps are done thanks to liquidity pools, where a smart contract will hold both currencies in high volume. Prices for swaps are calculated with the volume of each currency relative to one another. For example, let's say I had a liquidity pool containing 5 USDT and 10 BTC. In this example, the surface price would be 2 BTC for 1 USDT, and 0.5 USDT for 1 BTC. The core formula behind Uniswap (and PancakeSwap) is \(x*y=k\) where x and y are the reserves of each currency in the liquidity pool. K is a constant which should not change after each trade. What's great about this function is that it incorporates the laws of supply and demand, where buying more will increase the price and means you will receive less. In short, you can calculate the exact amount you will receive from a trade using the following formulas (with \(r\) being the fee, such as 0.99 for 1% fee). \[\Delta y=\frac{yr\Delta x}{x+r\Delta x}\] \[\Delta x=\frac{x\Delta y}{r(y-\Delta y)}\]
As pancakeswap is decentralized, anyone can freely create liquidity pools, and determine the price of their currency (anyone can create their own coin on the blockchain). But this only works when liquidity is provided, so both coins need to be in the liquidity pool. And that's exactly why triangulage on pancakeswap is interesting. Compared to typical exchanges where there might be a few hundred trading pairs, pancakeswap has millions all made by the community. This means that there are bound to be a few with price differences making triangular arbitrage possible.
For this, I created a first little program which was going to fetch all instances of the PairCreated
event on the blockchain from the creation of the pancakeswap contract up until the latest block. This event is emitted by the PancakeSwap v2 Factory whenever a new liquidity pool (a pair) is created. After a few hours of letting it run, I ended up with 2 180MB json files containing all the addresses of tokens listed on pancakeswap along with the addresses to the liquidity pool smart contract. This was necessary as pancakeswap itself didn't seem to have any API for fetching a list of tokens, and some of them aren't even listed on the official website.
The second program is the final and most important one, which was going to read each liquidity pool contract to calculate the prices and converion rates thanks to the formulas mentionned previously. It will then try to simulate the trades and see if it ended up with more or less balance than it started with. If we have more then it means there's a potential opportunity!
At first, it was finding tons of possible trades which sounded like great news! So I rushed to the pancakeswap website to find the swaps I had to do. I started swapping until I suddenly get hit by an error: "TRANSFER FAILED". I try another swap, same error. I didn't fully understand what it meant, but I continued anyway to try other possible methods.
The PancakeSwap v2 router (the smart contract which manages all of the swaps) has a cool functionality called path. When performing a swap you don't simply provide the addresses of both tokens you want to swap, but rather give a list of tokens it should pass through. This is handy as not all tokens have pairs with the currency you would like to convert it to, so it can convert to other currencies first to land with the desired currency in the end. As an example, you could use a path like [Token A, Token B, Token C], where Token A will be first converted to Token B, for it to then be converted to Token C. This can be used when there is an existing liquidity pool between Token A and Token B, and one between Token B and Token C, but not one directly between Token A and Token C. This is very useful as it will perform all the required swaps in one go, requiring only one write operation be done, thus making it quicker and less expensive in terms of gas fees.
Smart contracts run bytecode using the EVM (Ethereum Virtual Machine). But of course, you don't have to write the low level instructions yourself. This is why solidity exists, a programming language solely made for creating smart contracts. It's pretty easy to learn and has great documentation. I wanted to make a smart contract which performed the pancakeswap trades for me, automating the whole process of sending the coins and interacting with the pancakeswap router, meaning I could do it all in one go.
Using the smart contract I just made, I was able to send the swap instructions and the money and make it so that the contract would send all the money back to me in one transaction. However, to make a transaction you have to specify the gas fee limit you would like to use. Thankfully web3.js offers an easy function to do this called estimateGas, but weirdly enough it seemed to return an error. From what I've been able to tell, estimateGas simulates the transaction and calculates how much gas it would use up, meaning you can know in advance how much you would have to pay. However, because it failed to estimate the gas, it meant that something went wrong during the simulation and that one of the contracts threw an error. Turns out that I was trying to swap with scam tokens which have long since halted their trades...
I still had hope, and modified the code looking for arbitrage opportunities to first estimate the gas first to see if it would throw an error or not. Using this information I could determine if the swap was actually possible or not, and made it only log the opportunity whenever it was. And suddenly, nothing. No opportunities found anymore. Guess it was too good to be true after all...
Conclusion
What first got me into looking into triangular arbitrage (and arbitrage in general) were the countless number of articles and videos talking about how it works and the potential profit to be made. While I was still a little skeptical of how this would be possible with thousands of bots trying to do the exact same, I decided to give it a try. Needless to say, it was a great learning experience which thaught me a lot about finances, the crypto market, blockchain, and smart contracts. From what I've been able to conclude: there are no triangular arbitrage opportunities on PancakeSwap or Coinbase, and if there were they would probably be abused by bots already. Thank you for reading.