trigger fixes

This commit is contained in:
tim
2025-03-10 21:09:40 -04:00
parent be8c8bf019
commit f3bdfdf97b
4 changed files with 33 additions and 13 deletions

View File

@@ -22,6 +22,11 @@ from dexorder.util import hexbytes
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
def blocktime():
""" timestamp of the most recent block seen in real-time, NOT the current block being worked on """
return latest_block[current_chain.get().id].timestamp
async def get_block_timestamp(block_id: Union[bytes,int]) -> int: async def get_block_timestamp(block_id: Union[bytes,int]) -> int:
block = await get_block(block_id) block = await get_block(block_id)
if block is None: if block is None:

View File

@@ -48,6 +48,9 @@ class Config:
slash_delay_mul: float = 2 # double the delay each time slash_delay_mul: float = 2 # double the delay each time
slash_delay_max: int = 15 * 60 slash_delay_max: int = 15 * 60
# Tranches are paused for this long after they trigger a slippage control
slippage_control_delay: float = 10 # matches the 10-second TWAP used by our uniswap router
walker_name: str = 'default' walker_name: str = 'default'
walker_flush_interval: float = 300 walker_flush_interval: float = 300
walker_stop: Optional[int] = None # block number of the last block the walker should process walker_stop: Optional[int] = None # block number of the last block the walker should process

View File

@@ -137,7 +137,7 @@ async def handle_transfer(transfer: EventData):
vault = None vault = None
if vault is not None: if vault is not None:
await adjust_balance(vault, token_address, amount) await adjust_balance(vault, token_address, amount)
await update_balance_triggers(vault, token_address, amount) await update_balance_triggers(vault, token_address)
# This wuold double-count fill fees. Instead, we book the transfer when sending money to the account as part of a refill. # This wuold double-count fill fees. Instead, we book the transfer when sending money to the account as part of a refill.
# if is_tracked_address(to_address): # if is_tracked_address(to_address):
# # noinspection PyTypeChecker # # noinspection PyTypeChecker

View File

@@ -2,8 +2,9 @@ import asyncio
import logging import logging
from abc import abstractmethod from abc import abstractmethod
from collections import defaultdict from collections import defaultdict
from datetime import timedelta
from enum import Enum, auto from enum import Enum, auto
from typing import Optional, Sequence from typing import Optional, Sequence, Union
import numpy as np import numpy as np
from sortedcontainers import SortedList from sortedcontainers import SortedList
@@ -99,7 +100,8 @@ def start_trigger_updates():
PriceLineTrigger.clear_data() PriceLineTrigger.clear_data()
async def update_balance_triggers(vault: str, token: str, balance: int): async def update_balance_triggers(vault: str, token: str):
balance = vault_balances.get(vault, {}).get(token)
updates = [bt.update(balance) for bt in BalanceTrigger.by_vault_token.get((vault, token), [])] updates = [bt.update(balance) for bt in BalanceTrigger.by_vault_token.get((vault, token), [])]
await asyncio.gather(*updates) await asyncio.gather(*updates)
@@ -226,20 +228,20 @@ async def has_funds(tk: TrancheKey):
async def input_amount_is_sufficient(order, token_balance): async def input_amount_is_sufficient(order, token_balance):
log.debug(f'input is sufficient? {order.min_fill_amount}') # log.debug(f'input is sufficient? {order.min_fill_amount}')
if order.amount_is_input: if order.amount_is_input:
log.debug(f'amount is input: {token_balance} >= {order.min_fill_amount}') # log.debug(f'amount is input: {token_balance} >= {order.min_fill_amount}')
return token_balance >= order.min_fill_amount return token_balance >= order.min_fill_amount
# amount is an output amount, so we need to know the price # amount is an output amount, so we need to know the price
price = pool_prices.get(order.pool_address) price = pool_prices.get(order.pool_address)
log.debug(f'amount is output amount. price={price}') # log.debug(f'amount is output amount. price={price}')
if price is None: if price is None:
return token_balance > 0 # we don't know the price so we allow any nonzero amount to be sufficient return token_balance > 0 # we don't know the price so we allow any nonzero amount to be sufficient
pool = await get_pool(order.pool_address) pool = await get_pool(order.pool_address)
price *= dec(10) ** -pool['decimals'] price *= dec(10) ** -pool['decimals']
inverted = order.order.tokenIn != pool['base'] inverted = order.order.tokenIn != pool['base']
minimum = dec(order.min_fill_amount)*price if inverted else dec(order.min_fill_amount)/price minimum = dec(order.min_fill_amount)*price if inverted else dec(order.min_fill_amount)/price
log.debug(f'order minimum amount is {order.min_fill_amount} '+ ("input" if order.amount_is_input else f"output @ {price} = {minimum} ")+f'< {token_balance} balance') # log.debug(f'order minimum amount is {order.min_fill_amount} '+ ("input" if order.amount_is_input else f"output @ {price} = {minimum} ")+f'< {token_balance} balance')
return token_balance >= minimum return token_balance >= minimum
@@ -261,7 +263,7 @@ class BalanceTrigger (Trigger):
async def update(self, balance): async def update(self, balance):
self.value = await input_amount_is_sufficient(self.order, balance) self.value = await input_amount_is_sufficient(self.order, balance)
log.debug(f'update balance {balance} was sufficient? {self.value} {self.order.key}') # log.debug(f'update balance {balance} was sufficient? {self.value} {self.order.key}')
def remove(self): def remove(self):
try: try:
@@ -316,8 +318,8 @@ class TimeTrigger (Trigger):
if time == self._time: if time == self._time:
return return
self._time = time self._time = time
self.remove() in_future = time_now >= time
self.update_active(time_now) self.value = in_future is self.is_start
def update_active(self, time_now: int = None, time: int = None): def update_active(self, time_now: int = None, time: int = None):
if time_now is None: if time_now is None:
@@ -599,7 +601,8 @@ class TrancheTrigger:
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: if self.market_order:
self.expire() order_log.debug(f'tranche {self.tk} delayed {config.slippage_control_delay} seconds due to slippage control')
self.deactivate(config.slippage_control_delay)
self.slash_count = 0 # reset slash count self.slash_count = 0 # reset slash count
def touch(self): def touch(self):
@@ -631,15 +634,24 @@ class TrancheTrigger:
self.kill() self.kill()
else: else:
delay = round(config.slash_delay_base * config.slash_delay_mul ** (self.slash_count-1)) delay = round(config.slash_delay_base * config.slash_delay_mul ** (self.slash_count-1))
self.deactivate(timestamp()+delay) self.deactivate(delay)
def deactivate(self, until): def deactivate(self, interval: Union[timedelta, int, float]):
# todo this timestamp should be consistent with the trigger time which is blockchain
now = current_clock.get().timestamp
self.deactivate_until(now + (interval.total_seconds() if isinstance(interval, timedelta) else interval))
def deactivate_until(self, until):
# Temporarily deactivate the tranche due to a rate limit. Use disable() to permanently halt the trigger. # Temporarily deactivate the tranche due to a rate limit. Use disable() to permanently halt the trigger.
log.debug(f'deactivating tranche {self.tk} until {from_timestamp(until)}') log.debug(f'deactivating tranche {self.tk} until {from_timestamp(until)}')
if self.activation_trigger is None: if self.activation_trigger is None:
self.activation_trigger = TimeTrigger.create(True, self.tk, until) self.activation_trigger = TimeTrigger.create(True, self.tk, until)
else: else:
self.activation_trigger.time = until self.activation_trigger.time = until
try:
del active_tranches[self.tk]
except KeyError:
pass
def disable(self): def disable(self):
# permanently stop this trigger and deconstruct # permanently stop this trigger and deconstruct