diff --git a/src/dexorder/blocks.py b/src/dexorder/blocks.py index 69dabb0..b2e4e7f 100644 --- a/src/dexorder/blocks.py +++ b/src/dexorder/blocks.py @@ -22,6 +22,11 @@ from dexorder.util import hexbytes 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: block = await get_block(block_id) if block is None: diff --git a/src/dexorder/configuration/schema.py b/src/dexorder/configuration/schema.py index d5c2551..609ac19 100644 --- a/src/dexorder/configuration/schema.py +++ b/src/dexorder/configuration/schema.py @@ -48,6 +48,9 @@ class Config: slash_delay_mul: float = 2 # double the delay each time 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_flush_interval: float = 300 walker_stop: Optional[int] = None # block number of the last block the walker should process diff --git a/src/dexorder/event_handler.py b/src/dexorder/event_handler.py index c833950..c598ea8 100644 --- a/src/dexorder/event_handler.py +++ b/src/dexorder/event_handler.py @@ -137,7 +137,7 @@ async def handle_transfer(transfer: EventData): vault = None if vault is not None: 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. # if is_tracked_address(to_address): # # noinspection PyTypeChecker diff --git a/src/dexorder/order/triggers.py b/src/dexorder/order/triggers.py index 5543a29..45c0238 100644 --- a/src/dexorder/order/triggers.py +++ b/src/dexorder/order/triggers.py @@ -2,8 +2,9 @@ import asyncio import logging from abc import abstractmethod from collections import defaultdict +from datetime import timedelta from enum import Enum, auto -from typing import Optional, Sequence +from typing import Optional, Sequence, Union import numpy as np from sortedcontainers import SortedList @@ -99,7 +100,8 @@ def start_trigger_updates(): 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), [])] await asyncio.gather(*updates) @@ -226,20 +228,20 @@ async def has_funds(tk: TrancheKey): 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: - 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 # amount is an output amount, so we need to know the price 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: 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) price *= dec(10) ** -pool['decimals'] inverted = order.order.tokenIn != pool['base'] 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 @@ -261,7 +263,7 @@ class BalanceTrigger (Trigger): async def update(self, 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): try: @@ -316,8 +318,8 @@ class TimeTrigger (Trigger): if time == self._time: return self._time = time - self.remove() - self.update_active(time_now) + in_future = time_now >= time + self.value = in_future is self.is_start def update_active(self, time_now: int = None, time: int = None): if time_now is None: @@ -599,7 +601,8 @@ class TrancheTrigger: else: order_log.debug(f'tranche part-filled {self.tk} in:{_amount_in} out:{_amount_out} remaining:{remaining}') 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 def touch(self): @@ -631,15 +634,24 @@ class TrancheTrigger: self.kill() else: 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. log.debug(f'deactivating tranche {self.tk} until {from_timestamp(until)}') if self.activation_trigger is None: self.activation_trigger = TimeTrigger.create(True, self.tk, until) else: self.activation_trigger.time = until + try: + del active_tranches[self.tk] + except KeyError: + pass def disable(self): # permanently stop this trigger and deconstruct