trigger bugfixes

This commit is contained in:
tim
2024-10-29 00:34:41 -04:00
parent 51852c1250
commit 39be05adaa

View File

@@ -81,8 +81,8 @@ class OrderTriggers:
def fill(self, tx: str, time: int, tranche_index, amount_in, amount_out, fee, next_activation_time): def fill(self, tx: str, time: int, tranche_index, amount_in, amount_out, fee, next_activation_time):
self.order.add_fill(tx, time, tranche_index, amount_in, amount_out, fee, next_activation_time) self.order.add_fill(tx, time, tranche_index, amount_in, amount_out, fee, next_activation_time)
if self.triggers[tranche_index].fill(amount_in, amount_out, next_activation_time): self.triggers[tranche_index].fill(amount_in, amount_out, next_activation_time)
self.check_complete() self.check_complete()
def expire_tranche(self, tranche_index): def expire_tranche(self, tranche_index):
self.triggers[tranche_index].expire() self.triggers[tranche_index].expire()
@@ -119,28 +119,24 @@ async def end_trigger_updates():
Call once after all updates have been handled. This updates the active_tranches array based on final trigger state. Call once after all updates have been handled. This updates the active_tranches array based on final trigger state.
""" """
PriceLineTrigger.end_updates(current_clock.get().timestamp) PriceLineTrigger.end_updates(current_clock.get().timestamp)
# dirty can change
global _dirty
while _dirty: while _dirty:
working_set = _dirty tk = _dirty.pop()
_dirty = set() log.debug(f'check dirty tranche {tk}')
for tk in working_set: if _trigger_state.get(tk,0) == 0:
log.debug(f'check dirty tranche {tk}') # all clear for execution. add to active list with any necessary proofs
if _trigger_state.get(tk,0) == 0: active_tranches[tk] = PriceProof(0)
# all clear for execution. add to active list with any necessary proofs else:
active_tranches[tk] = PriceProof(0) # blocked by one or more triggers being False (nonzero mask)
else: # check expiry constraint
# blocked by one or more triggers being False (nonzero mask) try:
# check expiry constraint TrancheTrigger.all[tk].check_expire()
try: except KeyError:
TrancheTrigger.all[tk].check_expire() pass
except KeyError: # delete from active list.
pass try:
# delete from active list. del active_tranches[tk]
try: except KeyError:
del active_tranches[tk] pass
except KeyError:
pass
def _Order__disable_triggers(order): def _Order__disable_triggers(order):
@@ -187,17 +183,14 @@ class Trigger:
@value.setter @value.setter
def value(self, value): def value(self, value):
state = _trigger_state.get(self.tk) state = _trigger_state.get(self.tk, 0)
if state is not None and value == (state & (1 << self.position) == 0): # NOTE: state is inverted
return
if state is None:
state = 0
_dirty.add(self.tk)
if not value: # this conditional is inverted if not value: # this conditional is inverted
_trigger_state[self.tk] = state | (1 << self.position) # set _trigger_state[self.tk] = state | (1 << self.position) # set
else: else:
_trigger_state[self.tk] = state & ~(1 << self.position) # clear _trigger_state[self.tk] = state & ~(1 << self.position) # clear
self._value_changed() _dirty.add(self.tk)
if value != (state & (1 << self.position) == 0):
self._value_changed()
def _value_changed(self): pass def _value_changed(self): pass
@@ -315,7 +308,7 @@ class TimeTrigger (Trigger):
time = self._time time = self._time
next_active = time_now < time next_active = time_now < time
activate = not self.active and next_active activate = not self.active and next_active
log.debug(f'update_active {self} | {self.active} => {next_active} = {activate}') # log.debug(f'update_active {self} | {self.active} => {next_active} = {activate}')
if activate: if activate:
# log.debug(f'adding time trigger {self}') # log.debug(f'adding time trigger {self}')
TimeTrigger.all.add(self) TimeTrigger.all.add(self)
@@ -359,6 +352,7 @@ class TimeTrigger (Trigger):
class PriceLineTrigger (Trigger): class PriceLineTrigger (Trigger):
by_pool: dict[str,set['PriceLineTrigger']] = defaultdict(set) by_pool: dict[str,set['PriceLineTrigger']] = defaultdict(set)
diagonals: set['PriceLineTrigger'] = set()
@staticmethod @staticmethod
def create(tk: TrancheKey, inverted: bool, price: dec, line: Line, is_min: bool, is_barrier: bool): def create(tk: TrancheKey, inverted: bool, price: dec, line: Line, is_min: bool, is_barrier: bool):
@@ -382,7 +376,11 @@ class PriceLineTrigger (Trigger):
self.is_barrier = is_barrier self.is_barrier = is_barrier
self.pool_address = Order.of(tk).pool_address self.pool_address = Order.of(tk).pool_address
self.index: Optional[int] = None self.index: Optional[int] = None
self.active = True
self.last_price = price_now
PriceLineTrigger.by_pool[self.pool_address].add(self) PriceLineTrigger.by_pool[self.pool_address].add(self)
if self.line.slope != 0:
PriceLineTrigger.diagonals.add(self)
# lines that need evaluating add their data to these arrays, which are then sent to SIMD for evaluation. each # lines that need evaluating add their data to these arrays, which are then sent to SIMD for evaluation. each
# array must always have the same size as the others. # array must always have the same size as the others.
@@ -406,21 +404,32 @@ class PriceLineTrigger (Trigger):
# oldPrice = price # oldPrice = price
if self.inverted: if self.inverted:
price = 1/price price = 1/price
self.last_price = price
log.debug(f'price trigger {price}') log.debug(f'price trigger {price}')
if self not in PriceLineTrigger.triggers_set: if self not in PriceLineTrigger.triggers_set:
self.index = len(PriceLineTrigger.y) self.add_computation(price)
PriceLineTrigger.y.append(price)
PriceLineTrigger.m.append(self.line.slope)
PriceLineTrigger.b.append(self.line.intercept)
PriceLineTrigger.sign.append(1 if self.is_min else -1)
PriceLineTrigger.triggers.append(self)
PriceLineTrigger.triggers_set.add(self)
else: else:
# update an existing equation's price # update an existing equation's price
PriceLineTrigger.y[self.index] = price PriceLineTrigger.y[self.index] = price
def touch(self):
if self not in PriceLineTrigger.triggers_set:
self.add_computation(self.last_price)
def add_computation(self, price):
self.index = len(PriceLineTrigger.y)
PriceLineTrigger.y.append(price)
PriceLineTrigger.m.append(self.line.slope)
PriceLineTrigger.b.append(self.line.intercept)
PriceLineTrigger.sign.append(1 if self.is_min else -1)
PriceLineTrigger.triggers.append(self)
PriceLineTrigger.triggers_set.add(self)
@staticmethod @staticmethod
def end_updates(time: int): def end_updates(time: int):
for t in PriceLineTrigger.diagonals:
t.touch() # always evaluate any line with a slope
# here we use numpy to compute all dirty lines using SIMD # here we use numpy to compute all dirty lines using SIMD
y, m, b, sign = map(np.array, y, m, b, sign = map(np.array,
(PriceLineTrigger.y, PriceLineTrigger.m, PriceLineTrigger.b, PriceLineTrigger.sign)) (PriceLineTrigger.y, PriceLineTrigger.m, PriceLineTrigger.b, PriceLineTrigger.sign))
@@ -434,14 +443,19 @@ class PriceLineTrigger (Trigger):
PriceLineTrigger.clear_data() PriceLineTrigger.clear_data()
def handle_result(self, value: bool): def handle_result(self, value: bool):
if not self.is_barrier or value: # barriers that are False do not update their values to False if self.active and (not self.is_barrier or value): # barriers that are False do not update their values to False
self.value = value self.value = value
def remove(self): def remove(self):
self.active = False
try: try:
PriceLineTrigger.by_pool[self.pool_address].remove(self) PriceLineTrigger.by_pool[self.pool_address].remove(self)
except KeyError: except KeyError:
pass pass
try:
PriceLineTrigger.diagonals.remove(self)
except KeyError:
pass
async def activate_orders(): async def activate_orders():
@@ -501,7 +515,7 @@ class TrancheTrigger:
inverted = order.order.inverted inverted = order.order.inverted
min_trigger = PriceLineTrigger.create(tk, inverted, price, tranche.minLine, True, tranche.minIsBarrier) min_trigger = PriceLineTrigger.create(tk, inverted, price, tranche.minLine, True, tranche.minIsBarrier)
max_trigger = PriceLineTrigger.create(tk, inverted, price, tranche.maxLine, False, tranche.maxIsBarrier) max_trigger = PriceLineTrigger.create(tk, inverted, price, tranche.maxLine, False, tranche.maxIsBarrier)
return TrancheTrigger(order, tk, balance_trigger, activation_trigger, expiration_trigger, min_trigger, max_trigger) return TrancheTrigger(order, tk, balance_trigger, activation_trigger, expiration_trigger, min_trigger, max_trigger, tranche.marketOrder)
def __init__(self, order: Order, tk: TrancheKey, def __init__(self, order: Order, tk: TrancheKey,
balance_trigger: BalanceTrigger, balance_trigger: BalanceTrigger,
@@ -509,6 +523,7 @@ class TrancheTrigger:
expiration_trigger: Optional[TimeTrigger], expiration_trigger: Optional[TimeTrigger],
min_trigger: Optional[PriceLineTrigger], min_trigger: Optional[PriceLineTrigger],
max_trigger: Optional[PriceLineTrigger], max_trigger: Optional[PriceLineTrigger],
market_order: bool,
): ):
assert order.key.vault == tk.vault and order.key.order_index == tk.order_index assert order.key.vault == tk.vault and order.key.order_index == tk.order_index
tranche = order.order.tranches[tk.tranche_index] tranche = order.order.tranches[tk.tranche_index]
@@ -521,6 +536,7 @@ class TrancheTrigger:
self.expiration_trigger = expiration_trigger self.expiration_trigger = expiration_trigger
self.min_trigger = min_trigger self.min_trigger = min_trigger
self.max_trigger = max_trigger self.max_trigger = max_trigger
self.market_order = market_order
self.slippage = tranche.minLine.intercept if tranche.marketOrder else 0 self.slippage = tranche.minLine.intercept if tranche.marketOrder else 0
self.slash_count = 0 self.slash_count = 0
@@ -556,8 +572,9 @@ class TrancheTrigger:
self.disable() self.disable()
else: else:
order_log.debug(f'tranche part-filled {self.tk} in:{_amount_in} out:{_amount_out} remaining:{remaining}') order_log.debug(f'tranche part-filled {self.tk} in:{_amount_in} out:{_amount_out} remaining:{remaining}')
if self.market_order:
self.expire()
self.slash_count = 0 # reset slash count self.slash_count = 0 # reset slash count
return filled
def touch(self): def touch(self):
_dirty.add(self.tk) _dirty.add(self.tk)