From 555564df380325e98d6b9d5393d33bc1dfc6eb73 Mon Sep 17 00:00:00 2001 From: lucas-dydx <76970939+lucas-dydx@users.noreply.github.com> Date: Tue, 19 Sep 2023 16:12:54 -0400 Subject: [PATCH] [PY] `v0.6.2`: Update python client code to place Short-Term orders (#32) --- v4-client-py/examples/composite_example.py | 21 ++- v4-client-py/pyproject.toml | 2 +- .../clients/dydx_composite_client.py | 125 +++++++++++++++++- .../clients/helpers/chain_helpers.py | 6 +- 4 files changed, 137 insertions(+), 17 deletions(-) diff --git a/v4-client-py/examples/composite_example.py b/v4-client-py/examples/composite_example.py index ab820f5f..8f6276e5 100644 --- a/v4-client-py/examples/composite_example.py +++ b/v4-client-py/examples/composite_example.py @@ -29,26 +29,25 @@ async def main() -> None: subaccount = Subaccount(wallet, 0) ordersParams = loadJson('human_readable_orders.json') for orderParams in ordersParams: - type = OrderType[orderParams["type"]] side = OrderSide[orderParams["side"]] - time_in_force_string = orderParams.get("timeInForce", "GTT") - time_in_force = OrderTimeInForce[time_in_force_string] + + # Get the expiration block. + current_block = client.get_current_block() + next_valid_block_height = current_block + 1 + # Note, you can change this to any number between `next_valid_block_height` to `next_valid_block_height + SHORT_BLOCK_WINDOW` + good_til_block = next_valid_block_height + 3 + price = orderParams.get("price", 1350) - time_in_force_seconds = 60 if time_in_force == OrderTimeInForce.GTT else 0 - post_only = orderParams.get("postOnly", False) try: - tx = client.place_order( + tx = client.place_short_term_order( subaccount, market='ETH-USD', - type=type, side=side, price=price, size=0.01, client_id=randrange(0, 100000000), - time_in_force=time_in_force, - good_til_time_in_seconds=time_in_force_seconds, - execution=OrderExecution.DEFAULT, - post_only=post_only, + good_til_block=good_til_block, + time_in_force=OrderExecution.DEFAULT, reduce_only=False ) print('**Order Tx**') diff --git a/v4-client-py/pyproject.toml b/v4-client-py/pyproject.toml index 9c0507ef..51936468 100644 --- a/v4-client-py/pyproject.toml +++ b/v4-client-py/pyproject.toml @@ -5,7 +5,7 @@ package-dir = {"" = "v4_client_py"} [tool.poetry] name = "v4-client-py" -version = "0.6.1" +version = "0.6.2" description = "dYdX v4 Client" authors = ["John Huang "] license = "BSL-1.1" diff --git a/v4-client-py/v4_client_py/clients/dydx_composite_client.py b/v4-client-py/v4_client_py/clients/dydx_composite_client.py index ccfaffa7..ff37e85d 100644 --- a/v4-client-py/v4_client_py/clients/dydx_composite_client.py +++ b/v4-client-py/v4_client_py/clients/dydx_composite_client.py @@ -18,6 +18,7 @@ ORDER_FLAGS_SHORT_TERM, ORDER_FLAGS_LONG_TERM, ORDER_FLAGS_CONDITIONAL, + SHORT_BLOCK_WINDOW, ) from v4_client_py.clients.constants import Network @@ -42,13 +43,25 @@ def __init__( def calculate_good_til_block(self) -> int: response = self.validator_client.get.latest_block() return response.block.header.height + 3 - + def calculate_good_til_block_time(self, good_til_time_in_seconds: int) -> int: now = datetime.now() interval = timedelta(seconds=good_til_time_in_seconds) future = now + interval return int(future.timestamp()) + def validate_good_til_block(self, good_til_block: int) -> None: + response = self.validator_client.get.latest_block() + next_valid_block_height = response.block.header.height + 1 + lower_bound = next_valid_block_height + upper_bound = next_valid_block_height + SHORT_BLOCK_WINDOW + if good_til_block < lower_bound or good_til_block > upper_bound: + raise Exception( + f"Invalid Short-Term order GoodTilBlock. " + f"Should be greater-than-or-equal-to {lower_bound} " + f"and less-than-or-equal-to {upper_bound}. " + f"Provided good til block: {good_til_block}" + ) # Only MARKET and LIMIT types are supported right now # Use human readable form of input, including price and size @@ -124,6 +137,66 @@ def place_order( trigger_price=trigger_price, ) return self.validator_client.post.send_message(subaccount=subaccount, msg=msg, zeroFee=True) + + def place_short_term_order( + self, + subaccount: Subaccount, + market: str, + side: OrderSide, + price: float, + size: float, + client_id: int, + good_til_block: int, + execution: OrderExecution, + reduce_only: bool, + trigger_price: float = None, + ) -> SubmittedTx: + ''' + Place Short-Term order + + :param subaccount: required + :type subaccount: Subaccount + + :param market: required + :type market: str + + :param side: required + :type side: Order.Side + + :param price: required + :type price: float + + :param size: required + :type size: float + + :param client_id: required + :type client_id: int + + :param good_til_block: required + :type good_til_block: int + + :param execution: required + :type execution: OrderExecution + + :param reduce_only: required + :type reduce_only: bool + + :returns: Tx information + ''' + msg = self.place_short_term_order_message( + subaccount=subaccount, + market=market, + type=type, + side=side, + price=price, + size=size, + client_id=client_id, + good_til_block=good_til_block, + execution=execution, + reduce_only=reduce_only, + trigger_price=trigger_price, + ) + return self.validator_client.post.send_message(subaccount=subaccount, msg=msg, zeroFee=True) def calculate_client_metadata(self, order_type: OrderType) -> int: ''' @@ -249,7 +322,53 @@ def place_order_message( condition_type=condition_type, conditional_order_trigger_subticks=conditional_order_trigger_subticks, ) - + + def place_short_term_order_message( + self, + subaccount: Subaccount, + market: str, + type: OrderType, + side: OrderSide, + price: float, + size: float, + client_id: int, + time_in_force: OrderExecution, + good_til_block: int, + reduce_only: bool, + ) -> MsgPlaceOrder: + # Validate the GoodTilBlock. + self.validate_good_til_block(good_til_block=good_til_block) + + # Construct the MsgPlaceOrder. + markets_response = self.indexer_client.markets.get_perpetual_markets(market) + market = markets_response.data['markets'][market] + clob_pair_id = market['clobPairId'] + atomic_resolution = market['atomicResolution'] + step_base_quantums = market['stepBaseQuantums'] + quantum_conversion_exponent = market['quantumConversionExponent'] + subticks_per_tick = market['subticksPerTick'] + order_side = calculate_side(side) + quantums = calculate_quantums(size, atomic_resolution, step_base_quantums) + subticks = calculate_subticks(price, atomic_resolution, quantum_conversion_exponent, subticks_per_tick) + order_flags = ORDER_FLAGS_SHORT_TERM + client_metadata = self.calculate_client_metadata(type) + return self.validator_client.post.composer.compose_msg_place_order( + address=subaccount.address, + subaccount_number=subaccount.subaccount_number, + client_id=client_id, + clob_pair_id=clob_pair_id, + order_flags=order_flags, + good_til_block=good_til_block, + good_til_block_time=0, + side=order_side, + quantums=quantums, + subticks=subticks, + time_in_force=time_in_force, + reduce_only=reduce_only, + client_metadata=client_metadata, + condition_type=Order.CONDITION_TYPE_UNSPECIFIED, + conditional_order_trigger_subticks=0, + ) def cancel_order( self, @@ -367,4 +486,4 @@ def withdraw_from_subaccount( subaccount=subaccount, asset_id=0, quantums=amount * 10 ** (- QUOTE_QUANTUMS_ATOMIC_RESOLUTION), - ) \ No newline at end of file + ) diff --git a/v4-client-py/v4_client_py/clients/helpers/chain_helpers.py b/v4-client-py/v4_client_py/clients/helpers/chain_helpers.py index 7eb0fdeb..ff25374b 100644 --- a/v4-client-py/v4_client_py/clients/helpers/chain_helpers.py +++ b/v4-client-py/v4_client_py/clients/helpers/chain_helpers.py @@ -20,15 +20,17 @@ class OrderTimeInForce(Flag): FOK = auto() # Fill or Kill class OrderExecution(Flag): - DEFAULT = auto() # Default, STOP_LIMIT and TAKE_PROFIT_LIMIT - POST_ONLY = auto() # Post-only, STOP_LIMIT and TAKE_PROFIT_LIMIT + DEFAULT = 0 # Default. Note proto enums start at 0, which is why this start at 0. IOC = auto() # Immediate or Cancel + POST_ONLY = auto() # Post-only FOK = auto() # Fill or Kill ORDER_FLAGS_SHORT_TERM = 0 ORDER_FLAGS_LONG_TERM = 64 ORDER_FLAGS_CONDITIONAL = 32 +SHORT_BLOCK_WINDOW = 20 + QUOTE_QUANTUMS_ATOMIC_RESOLUTION = -6