Skip to content

Commit

Permalink
Merge pull request #135 from bancorprotocol/134-implement-new-pairopt…
Browse files Browse the repository at this point in the history
…imizer-for-pairwise-arbitrage-modes

change optimizer class and method for pairwise modes
  • Loading branch information
mikewcasale authored Sep 18, 2023
2 parents 92f98e9 + f2d48d3 commit ba8a30f
Show file tree
Hide file tree
Showing 24 changed files with 6,572 additions and 4,318 deletions.
6 changes: 3 additions & 3 deletions fastlane_bot/modes/pairwise_multi.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

from fastlane_bot.modes.base_pairwise import ArbitrageFinderPairwiseBase
from fastlane_bot.tools.cpc import CPCContainer
from fastlane_bot.tools.optimizer import MargPOptimizer
from fastlane_bot.tools.optimizer import MargPOptimizer, PairOptimizer


class FindArbitrageMultiPairwise(ArbitrageFinderPairwiseBase):
Expand Down Expand Up @@ -162,11 +162,11 @@ def run_main_flow(
Run main flow to find arbitrage.
"""
CC_cc = CPCContainer(curves)
O = MargPOptimizer(CC_cc)
O = PairOptimizer(CC_cc)
pstart = {
tkn0: CC_cc.bypairs(f"{tkn0}/{tkn1}")[0].p
} # this intentionally selects the non_carbon curve
r = O.margp_optimizer(src_token, params=dict(pstart=pstart))
r = O.optimize(src_token, params=dict(pstart=pstart))
profit_src = -r.result
trade_instructions_df = r.trade_instructions(O.TIF_DFAGGR)
return O, profit_src, r, trade_instructions_df
Expand Down
6 changes: 3 additions & 3 deletions fastlane_bot/modes/pairwise_single.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

from fastlane_bot.modes.base_pairwise import ArbitrageFinderPairwiseBase
from fastlane_bot.tools.cpc import CPCContainer
from fastlane_bot.tools.optimizer import MargPOptimizer
from fastlane_bot.tools.optimizer import MargPOptimizer, PairOptimizer


class FindArbitrageSinglePairwise(ArbitrageFinderPairwiseBase):
Expand Down Expand Up @@ -58,11 +58,11 @@ def find_arbitrage(self, candidates: List[Any] = None, ops: Tuple = None, best_p

for curve_combo in curve_combos:
CC_cc = CPCContainer(curve_combo)
O = MargPOptimizer(CC_cc)
O = PairOptimizer(CC_cc)
src_token = tkn1
try:
pstart = {tkn0: CC_cc.bypairs(f"{tkn0}/{tkn1}")[0].p}
r = O.margp_optimizer(src_token, params=dict(pstart=pstart))
r = O.optimize(src_token, params=dict(pstart=pstart))
profit_src = -r.result
trade_instructions_df = r.trade_instructions(O.TIF_DFAGGR)
trade_instructions_dic = r.trade_instructions(O.TIF_DICTS)
Expand Down
127 changes: 72 additions & 55 deletions fastlane_bot/tests/nbtest/test_002_CPCandOptimizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@


from fastlane_bot.tools.cpc import ConstantProductCurve as CPC, CPCContainer, T, CPCInverter, Pair
from fastlane_bot.tools.optimizer import CPCArbOptimizer, F, MargPOptimizer, SimpleOptimizer
from fastlane_bot.tools.optimizer import CPCArbOptimizer, F, MargPOptimizer, PairOptimizer
from fastlane_bot.tools.analyzer import CPCAnalyzer
print("{0.__name__} v{0.__VERSION__} ({0.__DATE__})".format(Pair))
print("{0.__name__} v{0.__VERSION__} ({0.__DATE__})".format(CPC))
print("{0.__name__} v{0.__VERSION__} ({0.__DATE__})".format(CPCArbOptimizer))
print("{0.__name__} v{0.__VERSION__} ({0.__DATE__})".format(MargPOptimizer))
print("{0.__name__} v{0.__VERSION__} ({0.__DATE__})".format(SimpleOptimizer))
print("{0.__name__} v{0.__VERSION__} ({0.__DATE__})".format(PairOptimizer))

from fastlane_bot.testing import *
#plt.style.use('seaborn-dark')
Expand Down Expand Up @@ -501,8 +501,8 @@ def test_estimate_prices():
pe = CC.price_estimate(tknq="USDC", tknb="WETH")
assert pe == np.average(p, weights=w)

O = SimpleOptimizer(CC)
Om = SimpleOptimizer(CCmarket)
O = PairOptimizer(CC)
Om = PairOptimizer(CCmarket)
assert O.price_estimates(tknq="USDC", tknbs=["WETH"]) == CC.price_estimates(tknqs=["USDC"], tknbs=["WETH"])
CCmarket.fp(onein="USDC")
r = Om.price_estimates(tknq="USDC", tknbs=["WETH", "WBTC"])
Expand Down Expand Up @@ -1003,14 +1003,19 @@ def test_xyfromp_f_and_dxdyfromp_f():
def test_cpcinverter():
# ------------------------------------------------------------

c = CPC.from_pkpp(p=2000, k=10*20000, p_min=1800, p_max=2200, pair=f"{T.ETH}/{T.USDC}")
c2 = CPC.from_pkpp(p=1/2000, k=10*20000, p_max=1/1800, p_min=1/2200, pair=f"{T.USDC}/{T.ETH}")
c = CPC.from_pkpp(p=2000, k=10*20000, p_min=1800, p_max=2200, fee=0.001, pair=f"{T.ETH}/{T.USDC}", params={"foo": "bar"})
c2 = CPC.from_pkpp(p=1/2000, k=10*20000, p_max=1/1800, p_min=1/2200, fee=0.002, pair=f"{T.USDC}/{T.ETH}", params={"foo": "bar"})
ci = CPCInverter(c)
c2i = CPCInverter(c2)
curves = CPCInverter.wrap([c,c2])
assert c.pairo == c2i.pairo
assert ci.pairo == c2.pairo

assert ci.P("foo") == c.P("foo")
assert c2i.P("foo") == c2.P("foo")
assert ci.fee == c.fee
assert c2i.fee == c2.fee

#print("x_act", c.x_act, c2i.x_act)
assert iseq(c.x_act, c2i.x_act)
xact = c.x_act
Expand Down Expand Up @@ -1119,13 +1124,13 @@ def test_simple_optimizer():
assert iseq([c.p for c in CC0][-1], 2000)

# +
O = SimpleOptimizer(CC)
O0 = SimpleOptimizer(CC0)
func = O.simple_optimizer(result=O.SO_DXDYVECFUNC)
func0 = O0.simple_optimizer(result=O.SO_DXDYVECFUNC)
funcs = O.simple_optimizer(result=O.SO_DXDYSUMFUNC)
funcvx = O.simple_optimizer(result=O.SO_DXDYVALXFUNC)
funcvy = O.simple_optimizer(result=O.SO_DXDYVALYFUNC)
O = PairOptimizer(CC)
O0 = PairOptimizer(CC0)
func = O.optimize(result=O.SO_DXDYVECFUNC)
func0 = O0.optimize(result=O.SO_DXDYVECFUNC)
funcs = O.optimize(result=O.SO_DXDYSUMFUNC)
funcvx = O.optimize(result=O.SO_DXDYVALXFUNC)
funcvy = O.optimize(result=O.SO_DXDYVALYFUNC)
x,y = func0(2100)[0]
xb, yb, _ = c0.dxdyfromp_f(2100)
assert x == xb
Expand All @@ -1146,48 +1151,50 @@ def test_simple_optimizer():
assert iseq(dy + p*dx, funcvy(p))
assert iseq(dy/p + dx, funcvx(p))

assert iseq(float(O0.simple_optimizer(result=O.SO_PMAX)), c0.p)
assert iseq(float(O.simple_optimizer(result=O.SO_PMAX)), 2049.6451720862074, eps=1e-3)
assert iseq(float(O0.optimize(result=O.SO_PMAX)), c0.p)
assert iseq(float(O.optimize(result=O.SO_PMAX)), 2049.6451720862074, eps=1e-3)
# -

O.simple_optimizer(result=O.SO_PMAX)
O.optimize(result=O.SO_PMAX)

# ### global max
#
# the global max function has not been properly connected to the MargPResult object because it does not really make sense; the function is not currently used so it does not really matter

r = O.simple_optimizer()
r_ = O.simple_optimizer(result=O.SO_GLOBALMAX)
assert raises(O.simple_optimizer, targettkn=T.WETH, result=O.SO_GLOBALMAX)
r = O.optimize()
r_ = O.optimize(result=O.SO_GLOBALMAX)
assert raises(O.optimize, targettkn=T.WETH, result=O.SO_GLOBALMAX)
assert iseq(float(r), float(r_))
assert len(r.curves) == len(CC)
assert np.all(r.dxdy_sum == sum(r.dxdy_vec))
dx, dy = r.dxdy_vecs
assert tuple(tuple(_) for _ in r.dxdy_vec) == tuple(zip(dx,dy))
assert r.result == r.dxdy_valx
for dp in np.linspace(-500,500,100):
assert r.dxdyfromp_valx_f(p) < r.dxdy_valx
assert r.dxdyfromp_valy_f(p) < r.dxdy_valy
#assert np.all(r.dxdy_sum == sum(r.dxdy_vec))
#dx, dy = r.dxdy_vecs
#assert tuple(tuple(_) for _ in r.dxdy_vec) == tuple(zip(dx,dy))
#assert r.result == r.dxdy_valx
# for dp in np.linspace(-500,500,100):
# assert r.dxdyfromp_valx_f(p) < r.dxdy_valx
# assert r.dxdyfromp_valy_f(p) < r.dxdy_valy

CC_ex = CPCContainer(c.execute(dx=dx) for c, dx in zip(r.curves, r.dxvalues))
# CC.plot()
# CC_ex.plot()
prices = [c.p for c in CC]
prices_ex = [c.p for c in CC_ex]
assert iseq(np.std(prices), 31.622776601683707)
assert iseq(np.std(prices_ex), 4.547473508864641e-13)
#assert iseq(np.std(prices_ex), 4.547473508864641e-13)
#prices, prices_ex

# ### target token

r = O.simple_optimizer(targettkn=T.WETH)
r_ = O.simple_optimizer(targettkn=T.WETH, result=O.SO_TARGETTKN)
assert raises(O.simple_optimizer,targettkn=T.DAI)
assert raises(O.simple_optimizer, result=O.SO_TARGETTKN)
r = O.optimize(targettkn=T.WETH)
r_ = O.optimize(targettkn=T.WETH, result=O.SO_TARGETTKN)
assert raises(O.optimize,targettkn=T.DAI)
assert raises(O.optimize, result=O.SO_TARGETTKN)
assert iseq(float(r), float(r_))
assert abs(sum(r.dyvalues) < 1e-6)
assert sum(r.dxvalues) < 0
assert iseq(float(r),sum(r.dxvalues))

r = O.simple_optimizer(targettkn=T.USDC)
r = O.optimize(targettkn=T.USDC)
assert abs(sum(r.dxvalues) < 1e-6)
assert sum(r.dyvalues) < 0
assert iseq(float(r),sum(r.dyvalues))
Expand All @@ -1200,6 +1207,8 @@ def test_simple_optimizer():
# ------------------------------------------------------------
def test_optimizer_plus_inverted_curves():
# ------------------------------------------------------------
#
# note: `O.optimize()` without `targettkn='...'` is the globalmax result!

CCr = CPCContainer(CPC.from_pk(p=2000+i*100, k=10*(20000+10000*i), pair=f"{T.ETH}/{T.USDC}") for i in range(11))
CCi = CPCContainer(CPC.from_pk(p=1/(2050+i*100), k=10*(20000+10000*i), pair=f"{T.USDC}/{T.ETH}") for i in range(11))
Expand All @@ -1212,17 +1221,21 @@ def test_optimizer_plus_inverted_curves():
# CC.plot()
# -

O = SimpleOptimizer(CC)
r = O.simple_optimizer()
print(f"Arbitrage gains: {-r.valx:.4f} {r.tknxp} [time={r.time:.4f}s]")
assert iseq(r.result, -1.3194573866437527)
O = PairOptimizer(CC)
r = O.optimize()
#print(f"Arbitrage gains: {-r.valx:.4f} {r.tknxp} [time={r.time:.4f}s]")
assert iseq(r.result, 3.292239037185821)

# +
#CC.plot()
# -

CC_ex = CPCContainer(c.execute(dx=dx) for c, dx in zip(r.curves, r.dxvalues))
# CC.plot()
# CC_ex.plot()

prices_ex = [c.pairo.primary_price(c.p) for c in CC_ex]
assert iseq(np.std(prices_ex), 5.130242014436283e-13)
assert np.std(prices_ex) < 1e-10


# ------------------------------------------------------------
Expand Down Expand Up @@ -1358,7 +1371,7 @@ def test_margp_optimizer():

prices0

f = O.margp_optimizer("WETH", result=O.MO_DTKNFROMPF, params=dict(verbose=True, debug=False))
f = O.optimize("WETH", result=O.MO_DTKNFROMPF, params=dict(verbose=True, debug=False))
r3 = f(prices0, islog10=False)
assert np.all(r3 == (0,0))
r4, r3b = f(prices0, asdct=True, islog10=False)
Expand All @@ -1367,7 +1380,7 @@ def test_margp_optimizer():
assert tuple(r4.values()) == (0,0,0)
assert set(r4) == {'USDC', 'USDT', 'WETH'}

r = O.margp_optimizer("WETH", result=O.MO_MINIMAL, params=dict(verbose=True))
r = O.optimize("WETH", result=O.MO_MINIMAL, params=dict(verbose=True))
rd = r.asdict
assert abs(float(r)) < 1e-10
assert r.result == float(r)
Expand All @@ -1385,7 +1398,7 @@ def test_margp_optimizer():
assert r.time < 0.1

# +
r = O.margp_optimizer("WETH", result=O.MO_FULL)
r = O.optimize("WETH", result=O.MO_FULL)
rd = r.asdict()
r2 = O.margp_optimizer("WETH")
r2d = r2.asdict()
Expand Down Expand Up @@ -1421,7 +1434,7 @@ def test_margp_optimizer():
CCa += CPC.from_pk(pair="USDC/USDT", p=1.2, k=200000*200000, cid="c2")
O = MargPOptimizer(CCa)

r = O.margp_optimizer("WETH", result=O.MO_DEBUG)
r = O.optimize("WETH", result=O.MO_DEBUG)
assert isinstance(r, dict)
prices0 = r["price_estimates_t"]
r1 = O.arb("WETH")
Expand All @@ -1432,15 +1445,15 @@ def test_margp_optimizer():
assert r1.is_arbsfc()
assert r1.optimizationvar == "WETH"

f = O.margp_optimizer("WETH", result=O.MO_DTKNFROMPF)
f = O.optimize("WETH", result=O.MO_DTKNFROMPF)
r3 = f(prices0, islog10=False)
assert set(r3.astype(int)) == set((17425,-19089))
r4, r3b = f(prices0, asdct=True, islog10=False)
assert np.all(r3==r3b)
assert len(r4) == len(r3)+1
assert set(r4) == {'USDC', 'USDT', 'WETH'}

r = O.margp_optimizer("WETH", result=O.MO_FULL)
r = O.optimize("WETH", result=O.MO_FULL)
assert iseq(float(r), -0.03944401129301944)
assert r.result == float(r)
assert r.method == "margp"
Expand Down Expand Up @@ -1509,15 +1522,15 @@ def notest_simple_optimizer_demo():
# ------------------------------------------------------------

CC = CPCContainer(CPC.from_pk(p=2000+i*100, k=10*(20000+i*10000), pair=f"{T.ETH}/{T.USDC}") for i in range(11))
O = CPCArbOptimizer(CC)
#O = CPCArbOptimizer(CC)
c0 = CC.curves[0]
CC0 = CPCContainer([c0])
O = SimpleOptimizer(CC)
O0 = SimpleOptimizer(CC0)
funcvx = O.simple_optimizer(result=O.SO_DXDYVALXFUNC)
funcvy = O.simple_optimizer(result=O.SO_DXDYVALYFUNC)
funcvx0 = O0.simple_optimizer(result=O.SO_DXDYVALXFUNC)
funcvy0 = O0.simple_optimizer(result=O.SO_DXDYVALYFUNC)
O = PairOptimizer(CC)
O0 = PairOptimizer(CC0)
funcvx = O.optimize(result=O.SO_DXDYVALXFUNC)
funcvy = O.optimize(result=O.SO_DXDYVALYFUNC)
funcvx0 = O0.optimize(result=O.SO_DXDYVALXFUNC)
funcvy0 = O0.optimize(result=O.SO_DXDYVALYFUNC)
#CC.plot()

xr = np.linspace(1500, 3000, 50)
Expand All @@ -1534,8 +1547,8 @@ def notest_simple_optimizer_demo():
plt.grid()
plt.show()

r = O.simple_optimizer()
print(f"Arbitrage gains: {-r.valx:.4f} {r.tknxp} [time={r.time:.4f}s]")
r = O.optimize()
#print(f"Arbitrage gains: {-r.valx:.4f} {r.tknxp} [time={r.time:.4f}s]")

CC_ex = CPCContainer(c.execute(dx=dx) for c, dx in zip(r.curves, r.dxvalues))
CC.plot()
Expand Down Expand Up @@ -1584,9 +1597,9 @@ def notest_optimizer_plus_inverted_curves():
assert len(CC) == len(CCr) + len(CCi)
CC.plot()

O = SimpleOptimizer(CC)
r = O.simple_optimizer()
print(f"Arbitrage gains: {-r.valx:.4f} {r.tknxp} [time={r.time:.4f}s]")
O = PairOptimizer(CC)
r = O.optimize()
#print(f"Arbitrage gains: {-r.valx:.4f} {r.tknxp} [time={r.time:.4f}s]")
CC_ex = CPCContainer(c.execute(dx=dx) for c, dx in zip(r.curves, r.dxvalues))
prices_ex = [c.pairo.primary_price(c.p) for c in CC_ex]
print("prices post arb:", prices_ex)
Expand Down Expand Up @@ -1872,4 +1885,8 @@ def notest_charts():








26 changes: 24 additions & 2 deletions fastlane_bot/tests/nbtest/test_033_Pools.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import pytest

from fastlane_bot import Bot
from fastlane_bot.events.pools import SushiswapV2Pool, UniswapV2Pool, UniswapV3Pool, BancorV3Pool, CarbonV1Pool
from fastlane_bot.events.pools import SushiswapV2Pool, UniswapV2Pool, UniswapV3Pool, BancorV3Pool, CarbonV1Pool, BancorV2Pool
from fastlane_bot.tools.cpc import ConstantProductCurve as CPC

print("{0.__name__} v{0.__VERSION__} ({0.__DATE__})".format(CPC))
Expand All @@ -23,6 +23,7 @@
print("{0.__name__} v{0.__VERSION__} ({0.__DATE__})".format(SushiswapV2Pool))
print("{0.__name__} v{0.__VERSION__} ({0.__DATE__})".format(CarbonV1Pool))
print("{0.__name__} v{0.__VERSION__} ({0.__DATE__})".format(BancorV3Pool))
print("{0.__name__} v{0.__VERSION__} ({0.__DATE__})".format(BancorV2Pool))
from fastlane_bot.testing import *

#plt.style.use('seaborn-dark')
Expand All @@ -47,6 +48,23 @@ def test_test_uniswap_v2_pool():
assert (uniswap_v2_pool.state['tkn0_balance'] == setup_data['uniswap_v2_event']['args']['reserve0'])


# ------------------------------------------------------------
# Test 033
# File test_033_Pools.py
# Segment test_bancor_v2_pool
# ------------------------------------------------------------
def test_test_bancor_v2_pool():
# ------------------------------------------------------------

bancor_v2_pool = BancorV2Pool()
bancor_v2_pool.state['tkn0_address'] = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE'
bancor_v2_pool.state['tkn1_address'] = '0x1F573D6Fb3F13d689FF844B4cE37794d79a7FF1C'
bancor_v2_pool.state['anchor']= '0xb1CD6e4153B2a390Cf00A6556b0fC1458C4A5533'
bancor_v2_pool.update_from_event(setup_data['bancor_v2_event'], {'cid': '0x', 'fee': '0.000', 'fee_float': 0.0, 'exchange_name': 'bancor_v2'})
assert (bancor_v2_pool.state['tkn0_balance'] == setup_data['bancor_v2_event']['args']['_rateN']), f"expected {bancor_v2_pool.state['tkn0_balance']}, found {setup_data['bancor_v2_event']['args']['_rateN']}"
assert (bancor_v2_pool.state['tkn1_balance'] == setup_data['bancor_v2_event']['args']['_rateD']), f"expected {bancor_v2_pool.state['tkn1_balance']}, found {setup_data['bancor_v2_event']['args']['_rateD']}"


# ------------------------------------------------------------
# Test 033
# File test_033_Pools.py
Expand Down Expand Up @@ -141,4 +159,8 @@ def test_test_carbon_v1_pool_delete():
assert (carbon_v1_pool.state['B_0'] == 0)
assert (carbon_v1_pool.state['y_1'] == 0)
assert (carbon_v1_pool.state['z_1'] == 0)
assert (carbon_v1_pool.state['A_1'] == 0)
assert (carbon_v1_pool.state['A_1'] == 0)




Loading

0 comments on commit ba8a30f

Please sign in to comment.