Skip to content

Commit

Permalink
Merge pull request #146 from bancorprotocol/optimization
Browse files Browse the repository at this point in the history
Optimizer tests
  • Loading branch information
mikewcasale authored Sep 22, 2023
2 parents 1bc10bd + 610af59 commit 14f51d6
Show file tree
Hide file tree
Showing 10 changed files with 1,456 additions and 1,018 deletions.
44 changes: 38 additions & 6 deletions fastlane_bot/tools/cpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
NOTE: this class is not part of the API of the Carbon protocol, and you must expect breaking
changes even in minor version updates. Use at your own risk.
"""
__VERSION__ = "3.2"
__DATE__ = "15/Sep/2023"
__VERSION__ = "3.3"
__DATE__ = "21/Sep/2023"

from dataclasses import dataclass, field, asdict, InitVar
from .simplepair import SimplePair as Pair
Expand Down Expand Up @@ -371,11 +371,11 @@ class ConstantProductCurve(CurveBase):
def __post_init__(self):

if self.alpha is None:
super().__setattr__("_is_constant_product", True)
super().__setattr__("_is_symmetric", True)
super().__setattr__("alpha", 0.5)
else:
super().__setattr__("_is_constant_product", self.alpha == 0.5)
#print(f"[ConstantProductCurve] _is_constant_product = {self._is_constant_product}")
super().__setattr__("_is_symmetric", self.alpha == 0.5)
#print(f"[ConstantProductCurve] _is_symmetric = {self._is_symmetric}")
assert self.alpha > 0, f"alpha must be > 0 [{self.alpha}]"
assert self.alpha < 1, f"alpha must be < 1 [{self.alpha}]"

Expand Down Expand Up @@ -445,8 +445,24 @@ def eta(self):
return self.alpha / (1 - self.alpha)

def is_constant_product(self):
"True iff alpha == 0.5 (deprecated; use `is_symmetric`)"
return self.is_symmetric()

def is_symmetric(self):
"True iff alpha == 0.5"
return self._is_constant_product
return self._is_symmetric

def is_asymmetric(self):
"True iff alpha != 0.5"
return not self.is_symmetric()

def is_levered(self):
"True iff x!=x_act or y!=y_act"
return not self.is_unlevered()

def is_unlevered(self):
"True iff x==x_act and y==y_act"
return self.x == self.x_act and self.y == self.y_act

TOKENSCALE = ts.TokenScale1Data
# default token scale object is the trivial scale (everything one)
Expand Down Expand Up @@ -1242,6 +1258,8 @@ def invariant(self, xvec=None, *, include_target=False):
@property
def x_min(self):
"minimum (virtual) x value"
if self.is_unlevered():
return 0
assert self.is_constant_product(), "only implemented for constant product curves"

return self.x - self.x_act
Expand Down Expand Up @@ -1272,13 +1290,17 @@ def at_boundary(self):
@property
def y_min(self):
"minimum (virtual) y value"
if self.is_unlevered():
return 0
assert self.is_constant_product(), "only implemented for constant product curves"

return self.y - self.y_act

@property
def x_max(self):
"maximum (virtual) x value"
if self.is_unlevered():
return None
assert self.is_constant_product(), "only implemented for constant product curves"

if self.y_min > 0:
Expand All @@ -1289,6 +1311,8 @@ def x_max(self):
@property
def y_max(self):
"maximum (virtual) y value"
if self.is_unlevered():
return None
assert self.is_constant_product(), "only implemented for constant product curves"

if self.x_min > 0:
Expand All @@ -1299,6 +1323,8 @@ def y_max(self):
@property
def p_max(self):
"maximum pool price (in dy/dx; None if unlimited) = y_max/x_min"
if self.is_unlevered():
return None
assert self.is_constant_product(), "only implemented for constant product curves"

if not self.x_min is None and self.x_min > 0:
Expand All @@ -1308,13 +1334,17 @@ def p_max(self):

def p_max_primary(self, swap=True):
"p_max in the native quote of the curve Pair object (swap=True: p_min)"
if self.is_unlevered():
return None
p = self.p_max if not (swap and not self.isprimary) else self.p_min
if p is None: return None
return p if self.isprimary else 1/p

@property
def p_min(self):
"minimum pool price (in dy/dx; None if unlimited) = y_min/x_max"
if self.is_unlevered():
return 0
assert self.is_constant_product(), "only implemented for constant product curves"

if not self.x_max is None and self.x_max > 0:
Expand All @@ -1324,6 +1354,8 @@ def p_min(self):

def p_min_primary(self, swap=True):
"p_min in the native quote of the curve Pair object (swap=True: p_max)"
if self.is_unlevered():
return 0
p = self.p_min if not (swap and not self.isprimary) else self.p_max
if p is None: return None
return p if self.isprimary else 1/p
Expand Down
162 changes: 6 additions & 156 deletions fastlane_bot/tools/optimizer/pairoptimizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
This module is still subject to active research, and comments and suggestions are welcome.
The corresponding author is Stefan Loesch <[email protected]>
"""
__VERSION__ = "6.0"
__DATE__ = "15/Sep/2023"
__VERSION__ = "6.0.1"
__DATE__ = "21/Sep/2023"

from dataclasses import dataclass, field, fields, asdict, astuple, InitVar
#import pandas as pd
Expand Down Expand Up @@ -41,132 +41,6 @@ class PairOptimizer(CPCArbOptimizer):
def kind(self):
return "pair"

# @dataclass
# class PairOptimizerResult(OptimizerBase.OptimizerResult):
# """
# results of the pairs optimizer

# :curves: list of curves used in the optimization, possibly wrapped in CPCInverter objects*
# :dxdyfromp_vec_f: vector of tuples (dx, dy), as a function of p
# :dxdyfromp_sum_f: sum of the above, also as a function of p
# :dxdyfromp_valx_f: valx = dy/p + dx, also as a function of p
# :dxdyfromp_valy_f: valy = dy + p*dx/p, also as a function of p
# :p_optimal: optimal p value

# *the CPCInverter object ensures that all curves in the list correspond to the same quote
# conventions, according to the primary direction of the pair (as determined by the Pair
# object). Accordingly, tknx and tkny are always the same for all curves in the list, regardless
# of the quote direction of the pair. The CPCInverter object abstracts this away, but of course
# only for functions that are accessible through it.
# """

# NONEFUNC = lambda x: None

# curves: list = field(repr=False, default=None)
# dxdyfromp_vec_f: any = field(repr=False, default=NONEFUNC)
# dxdyfromp_sum_f: any = field(repr=False, default=NONEFUNC)
# dxdyfromp_valx_f: any = field(repr=False, default=NONEFUNC)
# dxdyfromp_valy_f: any = field(repr=False, default=NONEFUNC)
# p_optimal: float = field(repr=False, default=None)
# errormsg: str = field(repr=True, default=None)

# def __post_init__(self, *args, **kwargs):
# super().__post_init__(*args, **kwargs)
# # print("[PairOptimizerResult] post_init")
# assert (
# self.p_optimal is not None or self.errormsg is not None
# ), "p_optimal must be set unless errormsg is set"
# if self.method is None:
# self.method = "pair"

# @property
# def is_error(self):
# return self.errormsg is not None

# def detailed_error(self):
# return self.errormsg

# def status(self):
# return "error" if self.is_error else "converged"

# def dxdyfromp_vecs_f(self, p):
# """returns dx, dy as separate vectors instead as a vector of tuples"""
# return tuple(zip(*self.dxdyfromp_vec_f(p)))

# @property
# def tknx(self):
# return self.curves[0].tknx

# @property
# def tkny(self):
# return self.curves[0].tkny

# @property
# def tknxp(self):
# return self.curves[0].tknxp

# @property
# def tknyp(self):
# return self.curves[0].tknyp

# @property
# def pair(self):
# return self.curves[0].pair

# @property
# def pairp(self):
# return self.curves[0].pairp

# @property
# def dxdy_vecs(self):
# return self.dxdyfromp_vecs_f(self.p_optimal)

# @property
# def dxvalues(self):
# return self.dxdy_vecs[0]

# dxv = dxvalues

# @property
# def dyvalues(self):
# return self.dxdy_vecs[1]

# dyv = dyvalues

# @property
# def dxdy_vec(self):
# return self.dxdyfromp_vec_f(self.p_optimal)

# @property
# def dxdy_sum(self):
# return self.dxdyfromp_sum_f(self.p_optimal)

# @property
# def dxdy_valx(self):
# return self.dxdyfromp_valx_f(self.p_optimal)

# valx = dxdy_valx

# @property
# def dxdy_valy(self):
# return self.dxdyfromp_valy_f(self.p_optimal)

# valy = dxdy_valy

# def trade_instructions(self, ti_format=None):
# """returns list of TradeInstruction objects"""
# result = (
# CPCArbOptimizer.TradeInstruction.new(
# curve_or_cid=c, tkn1=self.tknx, amt1=dx, tkn2=self.tkny, amt2=dy
# )
# for c, dx, dy in zip(self.curves, self.dxvalues, self.dyvalues)
# if dx != 0 or dy != 0
# )
# assert ti_format != CPCArbOptimizer.TIF_DFAGGR, "TIF_DFAGGR not implemented for convex optimization"
# assert ti_format != CPCArbOptimizer.TIF_DFPG, "TIF_DFPG not implemented for convex optimization"
# return CPCArbOptimizer.TradeInstruction.to_format(result, ti_format=ti_format)


SO_DXDYVECFUNC = "dxdyvecfunc"
SO_DXDYSUMFUNC = "dxdysumfunc"
SO_DXDYVALXFUNC = "dxdyvalxfunc"
Expand Down Expand Up @@ -203,7 +77,7 @@ def optimize(self, targettkn=None, result=None, *, params=None):
curves_t = CPCInverter.wrap(self.curve_container)
assert len(curves_t) > 0, "no curves found"
c0 = curves_t[0]
print("[PairOptimizer.optimize] curves_t", curves_t[0].pair)
#print("[PairOptimizer.optimize] curves_t", curves_t[0].pair)
pairs = set(c.pair for c in curves_t)
assert (len(pairs) == 1), f"pair_optimizer only works on curves of exactly one pair [{pairs}]"
assert not (targettkn is None and result == self.SO_TARGETTKN), "targettkn must be set if result==SO_TARGETTKN"
Expand Down Expand Up @@ -268,7 +142,7 @@ def optimize(self, targettkn=None, result=None, *, params=None):
p_optimal_t = (float(p_optimal),)
full_result = dxdyfromp_sum_f(float(p_optimal))
opt_result = full_result[1]
print("[PairOptimizer.optimize] p_optimal", p_optimal, "full_result", full_result)
#print("[PairOptimizer.optimize] p_optimal", p_optimal, "full_result", full_result)
method = "margp-pair"

else:
Expand All @@ -278,19 +152,6 @@ def optimize(self, targettkn=None, result=None, *, params=None):
# allows to mask certain long portions of the result if desired, the same way
# the main margpoptimizer does it; however, this not currently considered necessary
if p_optimal.is_error:
# return self.PairOptimizerResult(
# result=None,
# time=time.time() - start_time,
# curves=curves_t,
# dxdyfromp_vec_f=dxdyfromp_vec_f,
# dxdyfromp_sum_f=dxdyfromp_sum_f,
# dxdyfromp_valx_f=dxdyfromp_valx_f,
# dxdyfromp_valy_f=dxdyfromp_valy_f,
# p_optimal=None,
# errormsg=p_optimal.errormsg,
# method=method,
# optimizer=self,
# )
return self.MargpOptimizerResult(
method=method,
optimizer=NOMR(self),
Expand All @@ -305,18 +166,7 @@ def optimize(self, targettkn=None, result=None, *, params=None):
n_iterations=None,
errormsg="bisection did not converge",
)
# return self.PairOptimizerResult(
# result=full_result,
# time=time.time() - start_time,
# curves=curves_t,
# dxdyfromp_vec_f=dxdyfromp_vec_f,
# dxdyfromp_sum_f=dxdyfromp_sum_f,
# dxdyfromp_valx_f=dxdyfromp_valx_f,
# dxdyfromp_valy_f=dxdyfromp_valy_f,
# p_optimal=float(p_optimal),
# method=method,
# optimizer=self,
# )

return self.MargpOptimizerResult(
method=method,
optimizer=NOMR(self),
Expand All @@ -326,7 +176,7 @@ def optimize(self, targettkn=None, result=None, *, params=None):
curves=NOMR(curves_t),
p_optimal_t=p_optimal_t,
dtokens={c0.tknx:full_result[0], c0.tkny:full_result[1]},
dtokens_t=(full_result[0] if targettkn==c0.tknx else full_result[1],),
dtokens_t=(full_result[1] if targettkn==c0.tknx else full_result[0],),
tokens_t=(c0.tknx if targettkn==c0.tkny else c0.tkny,),
n_iterations=None, # not available
)
Expand Down
Loading

0 comments on commit 14f51d6

Please sign in to comment.