Skip to content

Instantly share code, notes, and snippets.

@siddharth9903
Forked from noxx3xxon/arbitrage.py
Created November 11, 2022 16:30
Show Gist options
  • Save siddharth9903/c9c7a72b4f3d4448f0977a433a0dca56 to your computer and use it in GitHub Desktop.
Save siddharth9903/c9c7a72b4f3d4448f0977a433a0dca56 to your computer and use it in GitHub Desktop.

Revisions

  1. @noxx3xxon noxx3xxon created this gist Aug 21, 2022.
    182 changes: 182 additions & 0 deletions arbitrage.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,182 @@
    import numpy as np
    import cvxpy as cp
    import itertools

    # Problem data
    global_indices = list(range(4))

    # 0 = TOKEN-0
    # 1 = TOKEN-1
    # 2 = TOKEN-2
    # 3 = TOKEN-3

    local_indices = [
    [0, 1, 2, 3], # TOKEN-0/TOKEN-1/TOKEN-2/TOKEN-3
    [0, 1], # TOKEN-0/TOKEN-1
    [1, 2], # TOKEN-1/TOKEN-2
    [2, 3], # TOKEN-2/TOKEN-3
    [2, 3] # TOKEN-2/TOKEN-3
    ]

    reserves = list(map(np.array, [
    [4, 4, 4, 4], # balancer with 4 assets in pool TOKEN-0, TOKEN-1, TOKEN-2, TOKEN-3 (4 TOKEN-0, 4 TOKEN-1, 4 TOKEN-2 & 4 TOKEN-3 IN POOL)
    [10, 1], # uniswapV2 TOKEN-0/TOKEN-1 (10 TOKEN-0 & 1 TOKEN-1 IN POOL)
    [1, 5], # uniswapV2 TOKEN-1/TOKEN-2 (1 TOKEN-1 & 5 TOKEN-2 IN POOL)
    [40, 50], # uniswapV2 TOKEN-2/TOKEN-3 (40 TOKEN-2 & 50 TOKEN-3 IN POOL)
    [10, 10] # constant_sum TOKEN-2/TOKEN-3 (10 TOKEN-2 & 10 TOKEN-3 IN POOL)
    ]))

    fees = [
    .998, # balancer fees
    .997, # uniswapV2 fees
    .997, # uniswapV2 fees
    .997, # uniswapV2 fees
    .999 # constant_sum fees
    ]

    # "Market value" of tokens (say, in a centralized exchange)
    market_value = [
    1.5, # TOKEN-0
    10, # TOKEN-1
    2, # TOKEN-2
    3 # TOKEN-3
    ]

    # Build local-global matrices
    n = len(global_indices)
    m = len(local_indices)

    A = []
    for l in local_indices: # for each CFMM
    n_i = len(l) # n_i = number of tokens avaiable for CFMM i
    A_i = np.zeros((n, n_i)) # Create matrix of 0's
    for i, idx in enumerate(l):
    A_i[idx, i] = 1
    A.append(A_i)

    # Build variables

    # tender delta
    deltas = [cp.Variable(len(l), nonneg=True) for l in local_indices]

    # receive lambda
    lambdas = [cp.Variable(len(l), nonneg=True) for l in local_indices]

    psi = cp.sum([A_i @ (L - D) for A_i, D, L in zip(A, deltas, lambdas)])

    # Objective is to maximize "total market value" of coins out
    obj = cp.Maximize(market_value @ psi) # matrix multiplication

    # Reserves after trade
    new_reserves = [R + gamma_i*D - L for R, gamma_i, D, L in zip(reserves, fees, deltas, lambdas)]

    # Trading function constraints
    cons = [
    # Balancer pool with weights 4, 3, 2, 1
    cp.geo_mean(new_reserves[0], p=np.array([4, 3, 2, 1])) >= cp.geo_mean(reserves[0]),

    # Uniswap v2 pools
    cp.geo_mean(new_reserves[1]) >= cp.geo_mean(reserves[1]),
    cp.geo_mean(new_reserves[2]) >= cp.geo_mean(reserves[2]),
    cp.geo_mean(new_reserves[3]) >= cp.geo_mean(reserves[3]),

    # Constant sum pool
    cp.sum(new_reserves[4]) >= cp.sum(reserves[4]),
    new_reserves[4] >= 0,

    # Arbitrage constraint
    psi >= 0
    ]

    # Set up and solve problem
    prob = cp.Problem(obj, cons)
    prob.solve()


    # Trade Execution Ordering

    current_tokens = [0, 0, 0, 0]
    new_current_tokens = [0, 0, 0, 0]
    tokens_required_arr = []
    tokens_required_value_arr = []

    pool_names = ["BALANCER 0/1/2/3", "UNIV2 0/1", "UNIV2 1/2", "UNIV2 2/3", "CONSTANT SUM 2/3"]

    permutations = itertools.permutations(list(range(len(local_indices))), len(local_indices))
    permutations2 = []
    for permutation in permutations:
    permutations2.append(permutation)
    current_tokens = [0, 0, 0, 0]
    new_current_tokens = [0, 0, 0, 0]
    tokens_required = [0, 0, 0, 0]
    for pool_id in permutation:
    pool = local_indices[pool_id]
    for global_token_id in pool:
    local_token_index = pool.index(global_token_id)
    new_current_tokens[global_token_id] = current_tokens[global_token_id] + (lambdas[pool_id].value[local_token_index] - deltas[pool_id].value[local_token_index])

    if new_current_tokens[global_token_id] < 0 and new_current_tokens[global_token_id] < current_tokens[global_token_id]:
    if current_tokens[global_token_id] < 0:
    tokens_required[global_token_id] += (current_tokens[global_token_id] - new_current_tokens[global_token_id])
    new_current_tokens[global_token_id] = 0
    else:
    tokens_required[global_token_id] += (-new_current_tokens[global_token_id])
    new_current_tokens[global_token_id] = 0
    current_tokens[global_token_id] = new_current_tokens[global_token_id]

    tokens_required_value = []
    for i1, i2 in zip(tokens_required, market_value):
    tokens_required_value.append(i1*i2)

    tokens_required_arr.append(tokens_required)
    tokens_required_value_arr.append(sum(tokens_required_value))

    min_value = min(tokens_required_value_arr)
    min_value_index = tokens_required_value_arr.index(min_value)


    print("\n-------------------- ARBITRAGE TRADES + EXECUTION ORDER --------------------\n")
    for pool_id in permutations2[min_value_index]:
    pool = local_indices[pool_id]
    print(f"\nTRADE POOL = {pool_names[pool_id]}")

    for global_token_id in pool:
    local_token_index = pool.index(global_token_id)
    if (lambdas[pool_id].value[local_token_index] - deltas[pool_id].value[local_token_index]) < 0:
    print(f"\tTENDERING {-(lambdas[pool_id].value[local_token_index] - deltas[pool_id].value[local_token_index])} TOKEN {global_token_id}")

    for global_token_id in pool:
    local_token_index = pool.index(global_token_id)
    if (lambdas[pool_id].value[local_token_index] - deltas[pool_id].value[local_token_index]) >= 0:
    print(f"\tRECEIVEING {(lambdas[pool_id].value[local_token_index] - deltas[pool_id].value[local_token_index])} TOKEN {global_token_id}")


    print("\n-------------------- REQUIRED TOKENS TO KICK-START ARBITRAGE --------------------\n")
    print(f"TOKEN-0 = {tokens_required_arr[min_value_index][0]}")
    print(f"TOKEN-1 = {tokens_required_arr[min_value_index][1]}")
    print(f"TOKEN-2 = {tokens_required_arr[min_value_index][2]}")
    print(f"TOKEN-3 = {tokens_required_arr[min_value_index][3]}")

    print(f"\nUSD VALUE REQUIRED = ${min_value}")

    print("\n-------------------- TOKENS & VALUE RECEIVED FROM ARBITRAGE --------------------\n")
    net_network_trade_tokens = [0, 0, 0, 0]
    net_network_trade_value = [0, 0, 0, 0]

    for pool_id in permutations2[min_value_index]:
    pool = local_indices[pool_id]
    for global_token_id in pool:
    local_token_index = pool.index(global_token_id)
    net_network_trade_tokens[global_token_id] += lambdas[pool_id].value[local_token_index]
    net_network_trade_tokens[global_token_id] -= deltas[pool_id].value[local_token_index]

    for i in range(0, len(net_network_trade_tokens)):
    net_network_trade_value[i] = net_network_trade_tokens[i] * market_value[i]

    print(f"RECEIVED {net_network_trade_tokens[0]} TOKEN-0 = ${net_network_trade_value[0]}")
    print(f"RECEIVED {net_network_trade_tokens[1]} TOKEN-1 = ${net_network_trade_value[1]}")
    print(f"RECEIVED {net_network_trade_tokens[2]} TOKEN-2 = ${net_network_trade_value[2]}")
    print(f"RECEIVED {net_network_trade_tokens[3]} TOKEN-3 = ${net_network_trade_value[3]}")

    print(f"\nSUM OF RECEIVED TOKENS USD VALUE = ${sum(net_network_trade_value)}")
    print(f"CONVEX OPTIMISATION SOLVER RESULT: ${prob.value}\n")