Arbitrage is a method of earning risk-free profits by capitalizing on price differences, and accurate calculations are essential for optimal profits. Decentralized exchange (DEX) list liquidity pools of different token pairs, and when liquidity is added, removed, or traded, price differences can occur between different pools. These price differences can be used to perform risk-free arbitrage, or arbitrage, between two pools.
You can see that arbitrage is possible, and you can also see how much liquidity each pool holds. So, how much should you trade to maximize your profits with arbitrage?
Uniswap v2 AMM
In Uniswap v2, we define $y$ as the amount of tokens you can get when you put $x$ tokens into a pool. Where $R_{in}$ is how many tokens of the same type as the token you’re putting into the pool are currently in the pool, and $R_{out}$ is the number of tokens in the pool that you want to get.
Below is the getAmountOut
function, which is a built-in function in Uniswap v2.
function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) internal pure returns (uint amountOut) {
require(amountIn > 0, 'UniswapV2Library: INSUFFICIENT_INPUT_AMOUNT');
require(reserveIn > 0 && reserveOut > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');
uint amountInWithFee = amountIn.mul(997);
uint numerator = amountInWithFee.mul(reserveOut);
uint denominator = reserveIn.mul(1000).add(amountInWithFee);
amountOut = numerator / denominator;
Solidity
Expressed in a formula, this would look like this
$$y = \frac{(1000 – 3)R_{out} x}{1000 R_{in} + (1000 – 3) x}$$
This formula describes the pricing mechanism for the liquidity pool, reflecting a 0.3% transaction fee.
Derivation of arbitrage formulas
Since arbitrage trades with two pools, you need to connect the two formulas to calculate. Below is the process of connecting the two formulas.
- $x$ = amount in
- $y_1$ = amount out of 1st pool
- $y_2$ = amount out of 2nd pool
- $n$ = 1000 where Uniswap v2
- $s$ = 3 where Uniswap v2
- $R_{1, in}$ = reserve in of 1st pool
- $R_{1, out}$ = reserve out of 1st pool
- $R_{2, in}$ = reserve in of 2nd pool
- $R_{2, out}$ = reserve out of 2nd pool
- $y_1 = \frac{(n – s)R_{1, out}x}{nR_{1, in} + (n – s)x}$
- $y_2 = \frac{(n – s)R_{2, out}y_1}{nR_{2, in} + (n – s)y_1}$
- $y_2 = \{ (n – s)R_{2, out} \times \frac{(n – s)R_{1, out}x}{nR_{1, in} + (n – s)x}\} \div \{ nR_{2, in} + (n – s) \frac{(n – s)R_{1, out}x}{nR_{1, in} + (n – s)x} \}$
Knowing the reserve values between pools, we can calculate how many tokens we can get based on the amount of deposit. So, how do we calculate the optimal amount of deposit to get the maximum profit? Let’s derive this as a root formula.
We need to find a solution of $$ to $_2$. Let’s derive it with the formula for roots.
- $y_1 = \frac{(n – s)R_{1, out}x} {nR_{1, in} + (n – s)x}$
- $y_2 = \frac{(n – s)R_{2, out}y_1} {nR_{2, in} + (n – s)y_1}$
- $y_2 = \{ (n – s)R_{2, out} \times \frac{(n – s)R_{1, out}x}{\{nR_{1, in} + (n – s)x\}} \} \div \{ nR_{2, in} + (n – s) \frac{(n – s)R_{1, out}x}{\{nR_{1, in} + (n – s)x\}} \}$
- $y_2 = \{ (n – s)R_{2, out} \times (n – s)R_{1, out}x \} \div [ nR_{2, in} \{ nR_{1, in} + (n – s)x \} + (n – s) (n – s)R_{1, out}x ]$
- $y_2 = \{ (n – s)^2R_{1, out}R_{2, out}x \} \div \{ n^2R_{1, in}R_{2, in} + (n – s)nR_{2, in}x + (n – s)^2R_{1, out}x \}$
- $y_2 = \frac {(n – s)^2R_{1, out}R_{2, out}x} {n^2R_{1, in}R_{2, in} + x { (n – s)nR_{2, in} + (n – s)^2R_{1, out} }}$
- $F(x) = y_2 – x$
- $F^{\prime}(x) = y^{\prime}_1 – 1$
- $f = (n – s)^2R_{1, out}R_{2, out}x$
- $g = n^2R_{1, in}R_{2, in} + x \{ (n – s)nR_{2, in} + (n – s)^2R_{1, out} \}$
- $y^{\prime}_1 = \frac {f^{\prime}g – fg^{\prime}} {g^2}$
- $g^2 = f^{\prime}g – fg^{\prime}$
- $f^{\prime}g – fg^{\prime} = (n – s)^2R_{1, out}R_{2, out} [ n^2R_{1, in}R_{2, in} + x \{ (n – s)nR_{2, in} + (n – s)^2R_{1, out} \} ] – (n – s)^2R_{1, out}R_{2, out}x \{ (n – s)nR_{2, in} + (n – s)^2R_{1, out} \}$
- $f^{\prime}g – fg^{\prime} = (n – s)^2R_{1, out}R_{2, out} \{ n^2R_{1, in}R_{2, in} + (n – s)nR_{2, in} x \} + (n – s)^4R^2_{1, out}R_{2, out} x – (n – s)^3nR_{2, in}R_{1, out}R_{2, out}x – (n – s)^4R^2_{1, out}R_{2, out}x$
- $f^{\prime}g – fg^{\prime} = (n – s)^2R_{1, out}R_{2, out} \{ n^2R_{1, in}R_{2, in} + (n – s)nR_{2, in} x \} – (n – s)^3nR_{2, in}R_{1, out}R_{2, out}x$
- $f^{\prime}g – fg^{\prime} = (n – s)^2n^2R_{1, in}R_{2, in}R_{1, out}R_{2, out}$
- $g^2 = [ n^2R_{1, in}R_{2, in} + x \{ (n – s)nR_{2, in} + (n – s)^2R_{1, out} \}]^2$
- $k = (n – s)nR_{2, in} + (n – s)^2R_{1, out}$
- $g^2 = (n^2R_{1, in}R_{2, in} + kx)^2$
- $g^2 = (n^2R_{1, in}R_{2, in})^2 + 2n^2R_{1, in}R_{2, in} kx + (kx)^2$
- $(n^2R_{1, in}R_{2, in})^2 + 2n^2R_{1, in}R_{2, in} kx + (kx)^2 = (n – s)^2n^2R_{1, in}R_{2, in}R_{1, out}R_{2, out}$
- $k^2x^2 + 2n^2R_{1, in}R_{2, in} kx + (n^2R_{1, in}R_{2, in})^2 – (n – s)^2n^2R_{1, in}R_{2, in}R_{1, out}R_{2, out} = 0$
- $a = k^2$
- $b = 2n^2R_{1, in}R_{2, in} k$
- $c = (n^2R_{1, in}R_{2, in})^2 – (n – s)^2n^2R_{1, in}R_{2, in}R_{1, out}R_{2, out}$
- $x^* = \frac {-b + \sqrt {b^2 – 4ac}} {2a}$
It was complicated, but we derived it using the formula for roots. Now let’s verify that we didn’t make any mistakes.
Consider a situation where there is a 2x price difference between two pools.
- $n = 1000$
- $s = 3$
- $R_{1, in}=100 * 10^{18}$
- $R_{1, out}=1000 * 10^{18}$
- $R_{2, in}=1000 * 10^{18}$
- $R_{2, out}=200 * 10^{18}$
In the graph below, we can see that the value of the arbitrage profit is $8.44176 \times 10^{18}$. We can also see that the quantity of tokens that should be put into the first pool to get the maximum profit is $20.5911 \times 10^{18}$. These values are consistent with the results derived from the root formula, so we can say that the formula is validated.

Calculations and graphs on desmos
Generalizing formulas
In addition to arbitrages between two pools, there are also multi-hop arbitrages that span multiple pools. This allows you to explore more complex arbitraging opportunities.
What if you want to perform an arbitrage through $n$ pools instead of just two? Here is a generalized formula for the $n-hop$ case.
3-hop:
- $k = (n – s)n^2R_{2, in}R_{3, in} + (n – s)^2nR_{1, out}R_{3, in} + (n-s)^3R_{1, out}R_{2, out}$
- $a = k^2$
- $b = 2n^3R_{0, in}R_{1, in}R_{2, in} k$
- $c = (n^3R_{1, in}R_{2, in}R_{3, in})^2 – (n – s)^3n^3R_{1, in}R_{2, in}R_{3, in}R_{1, out}R_{2, out}R_{3, out}$
- $x^* = \frac {-b + \sqrt {b^2 – 4ac}} {2a}$
4-hop:
- $k = (n – s)n^3R_{2, in}R_{3, in}R_{4, in} + (n – s)^2n^2R_{1, out}R_{3, in}R_{4, in} + (n-s)^3nR_{1, out}R_{2, out}R_{4, in} + (n – s)^4R_{1, out}R_{2, out}R_{3, out}$
- $a = k^2$
- $b = 2n^4R_{1, in}R_{2, in}R_{3, in}R_{4, in} k$
- $c = (n^4R_{1, in}R_{2, in}R_{3, in}R_{4, in})^2 – (n – s)^4n^4R_{1, in}R_{2, in}R_{23 in}R_{4, in}R_{1, out}R_{2, out}R_{3, out}R_{4, out}$
- $x^* = \frac {-b + \sqrt {b^2 – 4ac}} {2a}$
Generalize the formula:
- $h$ = hops
- $k = (n – s)n^h \prod_{i=2}^{h} R_{i, in} + \sum_{j=2}^{h} [ (n – s)^{j}n^{h-j} \prod_{i=1}^{j – 1} R_{i, out} \prod_{i=1}^{h-j} R_{i + j, in} ]$
- $a = k^2$
- $b = 2n^{h} \prod_{i=1}^{h} R_{i, in} k$
- $c = (n^{h} \prod_{i=1}^{h} R_{i, in})^2 – (n – s)^{h}n^{h} \prod_{i=1}^{h} R_{i, in} \prod_{i=1}^{h} R_{i, out}$
- $x^* = \frac {-b + \sqrt {b^2 – 4ac}} {2a}$
get_multi_hop_optimal_amount_in(data: List[Tuple[int, int, int, int]]):
"""
data: List[Tuple[int, int, int, int]]
Tuple of (N, S, reserve_in, reserve_out)
"""
h = len(data)
n = 0
s = 0
prod_reserve_in_from_second = 1
prod_reserve_in_all = 1
prod_reserve_out_all = 1
for idx, (N, S, reserve_in, reserve_out) in enumerate(data):
if S > s:
n = N
s = S
if idx > 0:
prod_reserve_in_from_second *= reserve_in
prod_reserve_in_all *= reserve_in
prod_reserve_out_all *= reserve_out
sum_k_value = 0
for j in range(1, h):
prod_reserve_out_without_latest = prod([r[3] for r in data[:-1]])
prod_reserve_in_ = 1
for i in range(0, h-j - 1):
prod_reserve_in_ *= data[i + j + 1][2]
sum_k_value += (n - s) ** (j + 1) * n ** (h - j - 1) * prod_reserve_out_without_latest * prod_reserve_in_
k = (n - s) * n ** (h - 1) * prod_reserve_in_from_second + sum_k_value
a = k ** 2
b = 2 * n ** h * prod_reserve_in_all * k
c = (n ** h * prod_reserve_in_all ) ** 2 - (n - s) ** h * n ** h * prod_reserve_in_all * prod_reserve_out_all
numerator = -b + math.sqrt(b ** 2 - 4 * a * c)
denominator = 2 * a
return math.floor(numerator / denominator)
PythonConclusions and takeaways
The above formulas and algorithms can help you find the best arbitrage opportunities, but in real-world trading, variables such as slippage, transaction fees, and network latency need to be considered, so it’s important to base your strategy on real-time data.
DEX arbitrage is highly competitive with low barriers to entry, so it’s very difficult to win against other bots.