import matplotlib.pyplot as plt
from matplotlib import cm
import numpy as np
class Ledger:
def __init__(self):
self.names = {}
self.time = 0
def resolve(self, key):
if key not in self.names:
index = len(self.names)
self.names[key] = index
return self.names[key]
class Resource:
def __init__(self, ledger, name):
self.ledger = ledger
self.name = name
self.assets = np.zeros(len(ledger.names))
def find(self, account):
idx = self.ledger.resolve(account)
if len(self.ledger.names) != len(self.assets):
self.assets.resize(len(self.ledger.names))
return idx
def __getitem__(self, key):
idx = self.find(key)
return self.assets[idx]
def __setitem__(self, key, value):
if value < 0:
raise ValueError("Amount should be positive or null.")
idx = self.find(key)
self.assets[idx] = value
def transfer(self, sender, receiver, amount):
if amount <= 0:
raise ValueError("Amount should be positive.")
if self[sender] < amount:
raise ValueError("Insufficient balance.")
self[sender] -= amount
self[receiver] += amount
def supply(self):
return self.assets.sum()
def __str__(self):
balances = [f"'{account}': {self.assets[idx]}" for account, idx in self.ledger.names.items() if idx < len(self.assets)]
return f"({self.name} supply={self.supply()} assets={{" + ", ".join(balances) + "})"
class Surface:
def __init__(self, model, parent, children, verbose=False):
self.model = model
self.ledger = parent.ledger
self.parent = parent
self.children = children
self.history = np.zeros((1, len(children) + 2))
self.parent[id(self)] = 0
self.verbose = verbose
def value(self):
return self.parent[id(self)]
def weight(self, save=False):
x = np.array([r.supply() for r in self.children])
y = self.model(x, self.ledger.time)
v = self.value()
if save:
self.history = np.vstack((self.history, np.hstack((x, y, v)).reshape((1, len(x) + 2))))
return y
def swap(self, output, account, amount):
if not output in self.children:
raise ValueError("Output resource not found.")
w0 = self.weight()
v0 = self.value()
density = w0 / v0 if v0 > 0 and w0 > 0 else 1
output[account] += amount
w1 = self.weight()
v1 = w1 / density
dv = v1 - v0
self.parent[account] -= dv
self.parent[id(self)] += dv
self.weight(save=True)
if self.verbose:
print(f"(Swap w={w1} v={v1} account={account} output={output.name} amount={amount})")
def plot(self):
if len(self.children) != 2:
raise ValueError("Unsupported plot")
M = np.amax(self.history, 0)
fig, ax = plt.subplots(subplot_kw={"projection": "3d"})
X = np.arange(0, M[0], 0.25)
Y = np.arange(0, M[1], 0.25)
X, Y = np.meshgrid(X, Y)
Z = np.array([ self.model(np.array([ x, y ]), self.ledger.time) for x, y in zip(X.flatten(), Y.flatten()) ]).reshape(X.shape)
surf = ax.plot_surface(X, Y, Z, edgecolor="gray", alpha=0.8, cmap=cm.coolwarm, linewidth=0.1, rstride=10, cstride=10, antialiased=True)
fig.colorbar(surf, shrink=0.5, aspect=10)
Cx, Cy, Cw, Cv = np.hsplit(self.history, self.history.shape[1])
ax.plot(Cx, Cy, Cw, "g", label="Weight")
ax.plot(Cx, Cy, Cv, "m", label="Valuation")
ax.legend()
plt.show()
%config InlineBackend.figure_format = 'retina'
def test_model(model):
ledger = Ledger()
Rp = Resource(ledger, "Rp")
Ra = Resource(ledger, "Ra")
Rb = Resource(ledger, "Rb")
Rp["Bunny"] = 100
Rp["Fox"] = 100
S0 = Surface(model, Rp, [ Ra, Rb ], verbose=True)
print("--- (t = 0)")
S0.swap(Ra, "Bunny", 10)
S0.swap(Rb, "Fox", 10)
S0.swap(Ra, "Bunny", 10)
S0.swap(Rb, "Fox", 30)
print(Rp)
print(Ra)
print(Rb)
print("--- (t = 0.5)")
ledger.time = 0.5
S0.swap(Ra, "Bunny", -10)
S0.swap(Rb, "Fox", -20)
S0.swap(Ra, "Bunny", -10)
S0.swap(Rb, "Fox", -20)
print(Rp)
print(Ra)
print(Rb)
S0.plot()
## Euclidean Distance
test_model(lambda x, t: np.linalg.norm(x))
--- (t = 0) (Swap w=10.0 v=10.0 account=Bunny output=Ra amount=10) (Swap w=14.142135623730951 v=14.142135623730951 account=Fox output=Rb amount=10) (Swap w=22.360679774997898 v=22.360679774997898 account=Bunny output=Ra amount=10) (Swap w=44.721359549995796 v=44.721359549995796 account=Fox output=Rb amount=30) (Rp supply=200.0 assets={'Bunny': 81.78145584873306, 'Fox': 73.49718460127116, '4423850160': 44.721359549995796}) (Ra supply=20.0 assets={'Bunny': 20.0, 'Fox': 0.0, '4423850160': 0.0}) (Rb supply=40.0 assets={'Bunny': 0.0, 'Fox': 40.0, '4423850160': 0.0}) --- (t = 0.5) (Swap w=41.23105625617661 v=41.23105625617661 account=Bunny output=Ra amount=-10) (Swap w=22.360679774997898 v=22.360679774997898 account=Fox output=Rb amount=-20) (Swap w=20.0 v=20.0 account=Bunny output=Ra amount=-10) (Swap w=0.0 v=0.0 account=Fox output=Rb amount=-20) (Rp supply=200.0 assets={'Bunny': 87.63243891755013, 'Fox': 112.36756108244987, '4423850160': 0.0}) (Ra supply=0.0 assets={'Bunny': 0.0, 'Fox': 0.0, '4423850160': 0.0}) (Rb supply=0.0 assets={'Bunny': 0.0, 'Fox': 0.0, '4423850160': 0.0})
## Power sum
def exponent(t):
q = 1 # 27/4
return np.maximum(0, 1 + q * t**2 - q * t**3)
def plot_exp():
t = np.arange(0.0, 2.0, 0.01)
s = exponent(t)
fig, ax = plt.subplots()
ax.plot(t, s)
ax.set(xlabel='time', ylabel='exponent', title='Element-wise exponentiation by time')
ax.grid()
plt.show()
def powsum(x, t):
# This surface internally adjusts valuation based on density derived from reserve and model's weight.
# This results in the divergence of historical weights and valuations.
# Normalize the output such as: exponent(t) = 0 => w = 0
return ((1 + x) ** exponent(t) - 1).sum()
plot_exp()
test_model(powsum)
--- (t = 0) (Swap w=10.0 v=10.0 account=Bunny output=Ra amount=10) (Swap w=20.0 v=20.0 account=Fox output=Rb amount=10) (Swap w=30.0 v=30.0 account=Bunny output=Ra amount=10) (Swap w=60.0 v=60.0 account=Fox output=Rb amount=30) (Rp supply=200.0 assets={'Bunny': 80.0, 'Fox': 60.0, '5344951216': 60.0}) (Ra supply=20.0 assets={'Bunny': 20.0, 'Fox': 0.0, '5344951216': 0.0}) (Rb supply=40.0 assets={'Bunny': 0.0, 'Fox': 40.0, '5344951216': 0.0}) --- (t = 0.5) (Swap w=78.06469773940964 v=49.857438513321085 account=Bunny output=Ra amount=-10) (Swap w=43.56988155547131 v=27.826697003759143 account=Fox output=Rb amount=-20) (Swap w=29.72534064955635 v=18.984629245219022 account=Bunny output=Ra amount=-10) (Swap w=0.0 v=0.0 account=Fox output=Rb amount=-20) (Rp supply=200.0 assets={'Bunny': 98.98462924521904, 'Fox': 101.01537075478097, '5344951216': 0.0}) (Ra supply=0.0 assets={'Bunny': 0.0, 'Fox': 0.0, '5344951216': 0.0}) (Rb supply=0.0 assets={'Bunny': 0.0, 'Fox': 0.0, '5344951216': 0.0})
# https://balancer.fi/whitepaper.pdf#value-function
def balancer_value_fn(x, _):
W = 0.5
return (x ** W).prod()
test_model(balancer_value_fn)
--- (t = 0) (Swap w=0.0 v=0.0 account=Bunny output=Ra amount=10) (Swap w=10.000000000000002 v=10.000000000000002 account=Fox output=Rb amount=10) (Swap w=14.142135623730953 v=14.142135623730953 account=Bunny output=Ra amount=10) (Swap w=28.284271247461906 v=28.284271247461906 account=Fox output=Rb amount=30) (Rp supply=200.0 assets={'Bunny': 95.85786437626905, 'Fox': 75.85786437626905, '5344474784': 28.284271247461906}) (Ra supply=20.0 assets={'Bunny': 20.0, 'Fox': 0.0, '5344474784': 0.0}) (Rb supply=40.0 assets={'Bunny': 0.0, 'Fox': 40.0, '5344474784': 0.0}) --- (t = 0.5) (Swap w=20.000000000000004 v=20.000000000000004 account=Bunny output=Ra amount=-10) (Swap w=14.142135623730953 v=14.142135623730953 account=Fox output=Rb amount=-20) (Swap w=0.0 v=0.0 account=Bunny output=Ra amount=-10) (Swap w=0.0 v=0.0 account=Fox output=Rb amount=-20) (Rp supply=200.0 assets={'Bunny': 118.2842712474619, 'Fox': 81.7157287525381, '5344474784': 0.0}) (Ra supply=0.0 assets={'Bunny': 0.0, 'Fox': 0.0, '5344474784': 0.0}) (Rb supply=0.0 assets={'Bunny': 0.0, 'Fox': 0.0, '5344474784': 0.0})