Arbitrage is a strategy of taking advantage of price differences in different markets for the same asset. For an arbitrage opportunity to exist there needs to be a price difference between two equivalent assets. Traders then attempt to capitalize on price variation to make a profit. The simplest form of arbitrage is purchasing an asset when the price is lower on one exchange and simultaneously selling the same asset when the asset’s price is higher on a different exchange. In principal arbitrage is risk free.
There are different types of arbitrage that can be executed and a few examples are; cross boarder, convertible bond, triangular, tax, statistical, etc. For the purposes of this document we will focus on price difference arbitrage which means buying an asset for a lower price on one exchange and selling the same asset at a higher price on another exchange.
To determine if I could make money creating an Ethereum arbitrage bot that trades on multiple decentralized exchanges I needed to create a program, build a machine and define a arbitrage strategy.
Building the Arbitrage Program
I created an arbitrage program in Python that monitors prices on different decentralized exchanges (Uniswap and Kyber Network). If the system identified a price difference the program would buy on one exchange and sell on the other exchange in an attempt to make a profit. I later extended the program to monitor amounts rather then prices because of slippage.
Another consideration for this program was scale. It needed the ability to monitor many pair prices simultaneously. Since arbitrage opportunities are rare and potentially have small price differences the process needed to monitor many token pairs.
The Python program was a multi threaded and multi process application that had the following design:
- Node connection information to the Ethereum Ropsten test environment and the Main net environment
- Addresses, abi and fee information for each decentralized exchange
- All token assets that I wanted to monitor and their configuration
- Amount of money to risk for each trade
- Contained functions to get a price for a particular token on Kyber Network, Uniswap, and Coinbase
- The bot performed All trading on Kyber Network and Uniswap
- Coinbase prices were used as reference in the algorithm
def stream_kyber_prices(pair, src_address, src_decimals, dst_address, dst_decimals): def stream_uniswap_prices(pair, uniswap_pair_address, uniswap_pair_flag, src_decimals, dst_decimals):
- Obtained all (slow, standard, fast, etc.) gas prices from the ETH Gas Station
- Gas multipliers
- Function to call the max gas price allowed on Kyber Network. There is a maximum gas price you can use in a Kyber Network transaction. This max gas price is intended to prevent bots and traders from front running your transactions.
- Created a function to submit an individual trade directly to each of the on chain decentralized exchanges
- Created a function to submit a trade to a smart contract that would swap between Kyber and Uniswap in one transaction.
def kyber_trade(src_address, dst_address, amount): def uniswap_trade(src_address, dst_address, amount): def runTokenSwap(amount, src_address, dst_address):
- Created functions to compare prices between two assets on different exchanges
- If the bot calculated a profit a trade would be placed
# Indicators is the brains of the program
Solidity Smart Contract
I created a smart contract that would buy a token on one exchange and sell the same token on a different exchange. This is a requirement for arbitrage because you want to have an atomic transaction (2 trades at the same time). You do not want to be in a position where half your strategy is executed. With a smart contract if both transactions are not successful then the entire transaction is rolled back.
I needed a machine to run my arbitrage program and GETH node. My preference was to have the GETH node and Python program to live on the same machine to reduce delays because in arbitrage speed is an important factor. An option was to use an online service but I determined I could build and tune a machine better then a generic shared service online.
To ensure the machine was performant I focused my research on the processor, memory and M.2 SSD because speed is extremely important. I ended up purchasing a AMD Ryzen 5 3600 6-Core 12-Thread cpu and a Sabrent 1TB Rocket NVMe 4.0 Gen4 PCIe M.2 Internal SSD. In my opinion the M.2 SSD that I bought, which has very fast reads and writes, was critical for the build. To use this SSD I needed a Gen 4 Compatible motherboard and chose the ASUS Prime B550M-A motherboard. It had a lot of the newer features, Gen 4 compatibility and was not that expensive.
For the operating system I chose to use ubuntu Linux server. Linux is a must as there is no bloatware, it is very stable, and downtime is a minimum. It turned out to be easy to install and I used Putty to administer the system. From a stability standpoint you can not go wrong.
I ended up running my node and program in two different Docker containers on the newly built Linux machine. I used Docker so each process would run in its own container and each process would be isolated from each other. Docker made the process very easy and efficient to deploy and expand.
Once I had everything running it was very important to performance tune the machine and program so I did the following:
- over clocked the cpu and memory
- made sure the Linux OS was optimized
- Determined that the fastest way to communicate with a GETH node
- Code was optimize for speed and only called functions when necessary
After testing the program in the Ropsten environment, which was difficult because nothing works, I ran it on the Main net. First I configured the program to only print arbitrage opportunities to a log file so I could monitor and analyze the results. Everything appeared to be working as expected and the program was printing an arbitrage opportunity multiple times per day.
Execution and Results
Since the Python program was working as designed I turned on trading on the Main net to see it in action. I was very surprised at how difficult it was to win an arbitrage trade. The system won very few trades and spent more money in gas (every trade I lost I would have to pay a gas fee) then what I made in profits. I learned a valuable lesson that until you turn it on in production everything is just theory.
Sample of losing trades from Etherscan
After extensive research I determined that a lot of bots are reading the Ethereum transaction pool, re computing all transactions in the pool and determining their profitability. If the transaction in the memory pool is profitable the bot will front run the transaction (copy the transaction with their address and a higher gas fee so they place ahead of you in a block).
If you try to front run the bot the bot will front run you again to the point where the trade is not profitable. These programs rather have you spend all your money on gas then let you win a trade. There is a lot of competition in the transaction pool.
As an example I came across the project Keeper Dao and their game theory is described as join them or they will make you spend all your money on gas rather then let you win an arbitrage trade. There are many other projects, companies, programmers in the space that are performing the same strategy as Keeper Dao. This is probably part of the reason transaction fees are so high.
I think you can you make money running an arbitrage bot on the Ethereum blockchain but you need to be smarter and faster then the competition. Programmers need to develop a strategy that is hidden from the bots that are scanning the transaction pool.
This code is for learning and entertainment purposes only. The code has not been audited and use at your own risk. Remember smart contracts are experimental and could contain bugs.