trigger bugfixes
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user