Skip to content

Commit 58878f6

Browse files
committed
initial commit
0 parents  commit 58878f6

6 files changed

Lines changed: 365 additions & 0 deletions

File tree

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
__pycache__
2+
*.pdf
3+
.DS_Store

arbitrage.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import numpy as np
2+
import cvxpy as cp
3+
4+
# Problem data
5+
global_indices = list(range(4))
6+
local_indices = [
7+
[0, 1, 2, 3],
8+
[0, 1],
9+
[1, 2],
10+
[2, 3],
11+
[2, 3]
12+
]
13+
14+
reserves = list(map(np.array, [
15+
[4, 4, 4, 4],
16+
[10, 1],
17+
[1, 5],
18+
[40, 50],
19+
[10, 10]
20+
]))
21+
22+
fees = [
23+
.998,
24+
.997,
25+
.997,
26+
.997,
27+
.999
28+
]
29+
30+
# "Market value" of tokens (say, in a centralized exchange)
31+
market_value = [
32+
1.5,
33+
10,
34+
2,
35+
3
36+
]
37+
38+
# Build local-global matrices
39+
n = len(global_indices)
40+
m = len(local_indices)
41+
42+
A = []
43+
for l in local_indices:
44+
n_i = len(l)
45+
A_i = np.zeros((n, n_i))
46+
for i, idx in enumerate(l):
47+
A_i[idx, i] = 1
48+
A.append(A_i)
49+
50+
# Build variables
51+
deltas = [cp.Variable(len(l), nonneg=True) for l in local_indices]
52+
lambdas = [cp.Variable(len(l), nonneg=True) for l in local_indices]
53+
54+
psi = cp.sum([A_i @ (L - D) for A_i, D, L in zip(A, deltas, lambdas)])
55+
56+
# Objective is to maximize "total market value" of coins out
57+
obj = cp.Maximize(market_value @ psi)
58+
59+
# Reserves after trade
60+
new_reserves = [R + gamma_i*D - L for R, gamma_i, D, L in zip(reserves, fees, deltas, lambdas)]
61+
62+
# Trading function constraints
63+
cons = [
64+
# Balancer pool with weights 4, 3, 2, 1
65+
cp.geo_mean(new_reserves[0], p=np.array([4, 3, 2, 1])) >= cp.geo_mean(reserves[0]),
66+
67+
# Uniswap v2 pools
68+
cp.geo_mean(new_reserves[1]) >= cp.geo_mean(reserves[1]),
69+
cp.geo_mean(new_reserves[2]) >= cp.geo_mean(reserves[2]),
70+
cp.geo_mean(new_reserves[3]) >= cp.geo_mean(reserves[3]),
71+
72+
# Constant sum pool
73+
cp.sum(new_reserves[4]) >= cp.sum(reserves[4]),
74+
new_reserves[4] >= 0,
75+
76+
# Arbitrage constraint
77+
psi >= 0
78+
]
79+
80+
# Set up and solve problem
81+
prob = cp.Problem(obj, cons)
82+
prob.solve()
83+
84+
print(f"Total output value: {prob.value}")

latexify.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
#Original Author: Prof. Nipun Batra
2+
# nipunbatra.github.io
3+
from math import sqrt
4+
import matplotlib
5+
6+
SPINE_COLOR = 'gray'
7+
8+
def latexify(fig_width=None, fig_height=None, font_size=12, columns=2):
9+
"""Set up matplotlib's RC params for LaTeX plotting.
10+
Call this before plotting a figure.
11+
Parameters
12+
----------
13+
fig_width : float, optional, inches
14+
fig_height : float, optional, inches
15+
columns : {1, 2}
16+
"""
17+
18+
# code adapted from http://www.scipy.org/Cookbook/Matplotlib/LaTeX_Examples
19+
20+
# Width and max height in inches for IEEE journals taken from
21+
# computer.org/cms/Computer.org/Journal%20templates/transactions_art_guide.pdf
22+
23+
assert(columns in [1,2])
24+
25+
if fig_width is None:
26+
fig_width = 3.39 if columns==1 else 6.9 # width in inches
27+
28+
if fig_height is None:
29+
golden_mean = (sqrt(5)-1.0)/2.0 # Aesthetic ratio
30+
fig_height = fig_width*golden_mean # height in inches
31+
32+
MAX_HEIGHT_INCHES = 8.0
33+
if fig_height > MAX_HEIGHT_INCHES:
34+
print("WARNING: fig_height too large:" + fig_height +
35+
"so will reduce to" + MAX_HEIGHT_INCHES + "inches.")
36+
fig_height = MAX_HEIGHT_INCHES
37+
38+
# NB (bart): default font-size in latex is 11. This should exactly match
39+
# the font size in the text if the figwidth is set appropriately.
40+
# Note that this does not hold if you put two figures next to each other using
41+
# minipage. You need to use subplots.
42+
params = {'backend': 'ps',
43+
'text.latex.preamble': ['\\usepackage{gensymb}'],
44+
'axes.labelsize': font_size, # fontsize for x and y labels (was 12 and before 10)
45+
'axes.titlesize': font_size,
46+
'font.size': font_size, # was 12 and before 10
47+
'legend.fontsize': font_size, # was 12 and before 10
48+
'xtick.labelsize': font_size,
49+
'ytick.labelsize': font_size,
50+
'text.usetex': True,
51+
'figure.figsize': [fig_width,fig_height],
52+
'font.family': 'serif'
53+
}
54+
55+
matplotlib.rcParams.update(params)
56+
57+
58+
def format_axes(ax):
59+
60+
for spine in ['top', 'right']:
61+
ax.spines[spine].set_visible(False)
62+
63+
for spine in ['left', 'bottom']:
64+
ax.spines[spine].set_color(SPINE_COLOR)
65+
ax.spines[spine].set_linewidth(0.5)
66+
67+
ax.xaxis.set_ticks_position('bottom')
68+
ax.yaxis.set_ticks_position('left')
69+
70+
for axis in [ax.xaxis, ax.yaxis]:
71+
axis.set_tick_params(direction='out', color=SPINE_COLOR)
72+
73+
return ax

liquidation.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import numpy as np
2+
import cvxpy as cp
3+
4+
# Problem data
5+
global_indices = list(range(5))
6+
local_indices = [
7+
[0, 1, 2, 3, 4],
8+
[0, 1],
9+
[2, 3],
10+
[3, 4],
11+
[3, 4]
12+
]
13+
14+
reserves = list(map(np.array, [
15+
[4, 4, 4, 4, 4],
16+
[10, 1],
17+
[1, 5],
18+
[40, 50],
19+
[10, 10]
20+
]))
21+
22+
fees = [
23+
.998,
24+
.997,
25+
.997,
26+
.997,
27+
.999
28+
]
29+
30+
current_assets = [
31+
2,
32+
1,
33+
3,
34+
5,
35+
10
36+
]
37+
38+
# Build local-global matrices
39+
n = len(global_indices)
40+
m = len(local_indices)
41+
42+
A = []
43+
for l in local_indices:
44+
n_i = len(l)
45+
A_i = np.zeros((n, n_i))
46+
for i, idx in enumerate(l):
47+
A_i[idx, i] = 1
48+
A.append(A_i)
49+
50+
# Build variables
51+
deltas = [cp.Variable(len(l), nonneg=True) for l in local_indices]
52+
lambdas = [cp.Variable(len(l), nonneg=True) for l in local_indices]
53+
54+
psi = cp.sum([A_i @ (L - D) for A_i, D, L in zip(A, deltas, lambdas)])
55+
56+
# Objective is to liquidate everything into token 4
57+
obj = cp.Maximize(psi[4])
58+
59+
# Reserves after trade
60+
new_reserves = [R + gamma_i*D - L for R, gamma_i, D, L in zip(reserves, fees, deltas, lambdas)]
61+
62+
# Trading function constraints
63+
cons = [
64+
# Balancer pool with weights 5, 4, 3, 2, 1
65+
cp.geo_mean(new_reserves[0], p=np.array([5, 4, 3, 2, 1])) >= cp.geo_mean(reserves[0]),
66+
67+
# Uniswap v2 pools
68+
cp.geo_mean(new_reserves[1]) >= cp.geo_mean(reserves[1]),
69+
cp.geo_mean(new_reserves[2]) >= cp.geo_mean(reserves[2]),
70+
cp.geo_mean(new_reserves[3]) >= cp.geo_mean(reserves[3]),
71+
72+
# Constant sum pool
73+
cp.sum(new_reserves[4]) >= cp.sum(reserves[4]),
74+
new_reserves[4] >= 0,
75+
76+
# Liquidate all assets, except 4
77+
psi[0] + current_assets[0] == 0,
78+
psi[1] + current_assets[1] == 0,
79+
psi[2] + current_assets[2] == 0,
80+
psi[3] + current_assets[3] == 0
81+
]
82+
83+
# Set up and solve problem
84+
prob = cp.Problem(obj, cons)
85+
prob.solve()
86+
87+
print(f"Total liquidated value: {psi.value[4]}")

output/empty

Whitespace-only changes.

two-asset.py

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import numpy as np
2+
import cvxpy as cp
3+
import matplotlib.pyplot as plt
4+
from latexify import latexify
5+
6+
# Problem data
7+
global_indices = list(range(3))
8+
local_indices = [
9+
[0, 1, 2],
10+
[0, 1],
11+
[1, 2],
12+
[0, 2],
13+
[0, 2]
14+
]
15+
16+
reserves = list(map(np.array, [
17+
[3, .2, 1],
18+
[10, 1],
19+
[1, 10],
20+
21+
# Note that there is arbitrage in the next two pools:
22+
[20, 50],
23+
[10, 10]
24+
]))
25+
26+
fees = np.array([
27+
.98,
28+
.99,
29+
.96,
30+
.97,
31+
.99
32+
])
33+
34+
amounts = np.linspace(0, 50)
35+
36+
u_t = np.zeros(len(amounts))
37+
38+
all_values = [np.zeros((len(l), len(amounts))) for l in local_indices]
39+
40+
for j, t in enumerate(amounts):
41+
current_assets = np.array([
42+
t,
43+
0,
44+
0,
45+
])
46+
47+
# Build local-global matrices
48+
n = len(global_indices)
49+
m = len(local_indices)
50+
51+
A = []
52+
for l in local_indices:
53+
n_i = len(l)
54+
A_i = np.zeros((n, n_i))
55+
for i, idx in enumerate(l):
56+
A_i[idx, i] = 1
57+
A.append(A_i)
58+
59+
# Build variables
60+
deltas = [cp.Variable(len(l), nonneg=True) for l in local_indices]
61+
lambdas = [cp.Variable(len(l), nonneg=True) for l in local_indices]
62+
63+
psi = cp.sum([A_i @ (L - D) for A_i, D, L in zip(A, deltas, lambdas)])
64+
65+
# Objective is to trade t of asset 1 for a maximum amount of asset 3
66+
obj = cp.Maximize(psi[2])
67+
68+
# Reserves after trade
69+
new_reserves = [R + gamma_i*D - L for R, gamma_i, D, L in zip(reserves, fees, deltas, lambdas)]
70+
71+
# Trading function constraints
72+
cons = [
73+
# Balancer pool with weights 3, 2, 1
74+
cp.geo_mean(new_reserves[0], p=np.array([3, 2, 1])) >= cp.geo_mean(reserves[0], p=np.array([3, 2, 1])),
75+
76+
# Uniswap v2 pools
77+
cp.geo_mean(new_reserves[1]) >= cp.geo_mean(reserves[1]),
78+
cp.geo_mean(new_reserves[2]) >= cp.geo_mean(reserves[2]),
79+
cp.geo_mean(new_reserves[3]) >= cp.geo_mean(reserves[3]),
80+
81+
# Constant sum pool
82+
cp.sum(new_reserves[4]) >= cp.sum(reserves[4]),
83+
new_reserves[4] >= 0,
84+
85+
# Allow all assets at hand to be traded
86+
psi + current_assets >= 0
87+
]
88+
89+
# Set up and solve problem
90+
prob = cp.Problem(obj, cons)
91+
prob.solve()
92+
93+
for k in range(len(local_indices)):
94+
all_values[k][:, j] = lambdas[k].value - deltas[k].value
95+
96+
print(f"Total liquidated value: {psi.value[2]}")
97+
for i in range(5):
98+
print(f"Market {i}, delta: {deltas[i].value}, lambda: {lambdas[i].value}")
99+
100+
u_t[j] = obj.value
101+
102+
latexify(fig_width=6, fig_height=3.5)
103+
for k in range(len(local_indices)):
104+
curr_value = all_values[k]
105+
for i in range(curr_value.shape[0]):
106+
coin_out = curr_value[i, :]
107+
plt.plot(amounts, coin_out, label=f"$(\\Lambda_{{{k+1}}} - \\Delta_{{{k+1}}})_{{{i+1}}}$")
108+
109+
plt.legend(loc='center left', bbox_to_anchor=(1, .5))
110+
plt.xlabel("$t$")
111+
plt.savefig("output/all_plot.pdf", bbox_inches="tight")
112+
113+
latexify(fig_width=4, fig_height=3)
114+
plt.plot(amounts, u_t, "k")
115+
plt.ylim([0, np.max(u_t)+2])
116+
plt.ylabel("$u(t)$")
117+
plt.xlabel("$t$")
118+
plt.savefig("output/u_plot.pdf", bbox_inches="tight")

0 commit comments

Comments
 (0)