From 1a84b50a34959d95d954cc983092459ad65d44f6 Mon Sep 17 00:00:00 2001 From: Jonathan Fung <121899091+jonfung-dydx@users.noreply.github.com> Date: Fri, 29 Sep 2023 16:05:43 +0300 Subject: [PATCH] [PY] [CLOB-894] [CLOB-895] `v0.6.8` Quick python example for canceling short term and long term orders (#42) --- v4-client-py/examples/composite_example.py | 13 +- .../long_term_order_cancel_example.py | 83 ++++++++++++ .../short_term_order_cancel_example.py | 73 ++++++++++ ... => short_term_order_composite_example.py} | 20 +-- v4-client-py/examples/utils.py | 22 +++ v4-client-py/pyproject.toml | 2 +- v4-client-py/tests/constants.py | 1 + v4-client-py/v4_client_py/clients/composer.py | 15 +++ .../clients/dydx_composite_client.py | 126 +++++++++++++++--- .../clients/helpers/chain_helpers.py | 67 ++++++++-- .../v4_client_py/clients/modules/post.py | 2 +- 11 files changed, 375 insertions(+), 49 deletions(-) create mode 100644 v4-client-py/examples/long_term_order_cancel_example.py create mode 100644 v4-client-py/examples/short_term_order_cancel_example.py rename v4-client-py/examples/{short_term_composite_example.py => short_term_order_composite_example.py} (75%) diff --git a/v4-client-py/examples/composite_example.py b/v4-client-py/examples/composite_example.py index 3703c543..75da1e66 100644 --- a/v4-client-py/examples/composite_example.py +++ b/v4-client-py/examples/composite_example.py @@ -34,7 +34,16 @@ async def main() -> None: time_in_force_string = orderParams.get("timeInForce", "GTT") time_in_force = OrderTimeInForce[time_in_force_string] price = orderParams.get("price", 1350) - time_in_force_seconds = 60 if time_in_force == OrderTimeInForce.GTT else 0 + + if time_in_force == OrderTimeInForce.GTT: + time_in_force_seconds = 60 + good_til_block = 0 + else: + latest_block = client.validator_client.get.latest_block() + next_valid_block = latest_block.block.header.height + 1 + good_til_block = next_valid_block + 10 + time_in_force_seconds = 0 + post_only = orderParams.get("postOnly", False) try: tx = client.place_order( @@ -46,6 +55,7 @@ async def main() -> None: size=0.01, client_id=randrange(0, 100000000), time_in_force=time_in_force, + good_til_block=good_til_block, good_til_time_in_seconds=time_in_force_seconds, execution=OrderExecution.DEFAULT, post_only=post_only, @@ -70,6 +80,7 @@ async def main() -> None: size=0.01, client_id=randrange(0, 100000000), time_in_force=OrderTimeInForce.GTT, + good_til_block=0, # long term orders use GTBT good_til_time_in_seconds=1000, execution=OrderExecution.IOC, post_only=False, diff --git a/v4-client-py/examples/long_term_order_cancel_example.py b/v4-client-py/examples/long_term_order_cancel_example.py new file mode 100644 index 00000000..c2438341 --- /dev/null +++ b/v4-client-py/examples/long_term_order_cancel_example.py @@ -0,0 +1,83 @@ +'''Example for trading with human readable numbers + +Usage: python -m examples.composite_example +''' +import asyncio +import logging +from random import randrange +from v4_client_py.chain.aerial.wallet import LocalWallet +from v4_client_py.clients import CompositeClient, Subaccount +from v4_client_py.clients.constants import BECH32_PREFIX, Network + +from v4_client_py.clients.helpers.chain_helpers import ( + ORDER_FLAGS_LONG_TERM, + OrderType, + OrderSide, + OrderTimeInForce, + OrderExecution, +) + +from tests.constants import DYDX_TEST_MNEMONIC, MAX_CLIENT_ID + + +async def main() -> None: + wallet = LocalWallet.from_mnemonic(DYDX_TEST_MNEMONIC, BECH32_PREFIX) + network = Network.staging() + client = CompositeClient( + network, + ) + subaccount = Subaccount(wallet, 0) + + """ + Note this example places a stateful order. + Programmatic traders should generally not use stateful orders for following reasons: + - Stateful orders received out of order by validators will fail sequence number validation + and be dropped. + - Stateful orders have worse time priority since they are only matched after they are included + on the block. + - Stateful order rate limits are more restrictive than Short-Term orders, specifically max 2 per + block / 20 per 100 blocks. + - Stateful orders can only be canceled after they’ve been included in a block. + """ + long_term_order_client_id = randrange(0, MAX_CLIENT_ID) + try: + tx = client.place_order( + subaccount, + market='ETH-USD', + type=OrderType.LIMIT, + side=OrderSide.SELL, + price=40000, + size=0.01, + client_id=long_term_order_client_id, + time_in_force=OrderTimeInForce.GTT, + good_til_block=0, # long term orders use GTBT + good_til_time_in_seconds=60, + execution=OrderExecution.DEFAULT, + post_only=False, + reduce_only=False + ) + print('** Long Term Order Tx**') + print(tx.tx_hash) + except Exception as error: + print('**Long Term Order Failed**') + print(str(error)) + + # cancel a long term order. + try: + tx = client.cancel_order( + subaccount, + long_term_order_client_id, + 'ETH-USD', + ORDER_FLAGS_LONG_TERM, + good_til_time_in_seconds=120, + good_til_block=0, # long term orders use GTBT + ) + print('**Cancel Long Term Order Tx**') + print(tx.tx_hash) + except Exception as error: + print('**Cancel Long Term Order Failed**') + print(str(error)) + +if __name__ == '__main__': + logging.basicConfig(level=logging.INFO) + asyncio.get_event_loop().run_until_complete(main()) diff --git a/v4-client-py/examples/short_term_order_cancel_example.py b/v4-client-py/examples/short_term_order_cancel_example.py new file mode 100644 index 00000000..f4d91bf7 --- /dev/null +++ b/v4-client-py/examples/short_term_order_cancel_example.py @@ -0,0 +1,73 @@ +'''Example for trading with human readable numbers + +Usage: python -m examples.composite_example +''' +import asyncio +import logging +from random import randrange +from v4_client_py.chain.aerial.wallet import LocalWallet +from v4_client_py.clients import CompositeClient, Subaccount +from v4_client_py.clients.constants import BECH32_PREFIX, Network + +from v4_client_py.clients.helpers.chain_helpers import ( + ORDER_FLAGS_SHORT_TERM, + Order_TimeInForce, + OrderSide, +) +from tests.constants import DYDX_TEST_MNEMONIC, MAX_CLIENT_ID + + +async def main() -> None: + wallet = LocalWallet.from_mnemonic(DYDX_TEST_MNEMONIC, BECH32_PREFIX) + network = Network.staging() + client = CompositeClient( + network, + ) + subaccount = Subaccount(wallet, 0) + + # place a short term order. + short_term_client_id = randrange(0, MAX_CLIENT_ID) + # 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 + 10 + + try: + tx = client.place_short_term_order( + subaccount, + market='ETH-USD', + side=OrderSide.SELL, + price=40000, + size=0.01, + client_id=short_term_client_id, + good_til_block=good_til_block, + time_in_force=Order_TimeInForce.TIME_IN_FORCE_UNSPECIFIED, + reduce_only=False + ) + print('**Short Term Order Tx**') + print(tx.tx_hash) + except Exception as error: + print('**Short Term Order Failed**') + print(str(error)) + + # cancel a short term order. + try: + tx = client.cancel_order( + subaccount, + short_term_client_id, + 'ETH-USD', + ORDER_FLAGS_SHORT_TERM, + good_til_time_in_seconds=0, # short term orders use GTB. + good_til_block=good_til_block, # GTB should be the same or greater than order to cancel + ) + print('**Cancel Short Term Order Tx**') + print(tx.tx_hash) + except Exception as error: + print('**Cancel Short Term Order Failed**') + print(str(error)) + + +if __name__ == '__main__': + logging.basicConfig(level=logging.INFO) + asyncio.get_event_loop().run_until_complete(main()) \ No newline at end of file diff --git a/v4-client-py/examples/short_term_composite_example.py b/v4-client-py/examples/short_term_order_composite_example.py similarity index 75% rename from v4-client-py/examples/short_term_composite_example.py rename to v4-client-py/examples/short_term_order_composite_example.py index 3488fdd6..c375e308 100644 --- a/v4-client-py/examples/short_term_composite_example.py +++ b/v4-client-py/examples/short_term_order_composite_example.py @@ -10,13 +10,9 @@ from v4_client_py.clients.constants import BECH32_PREFIX, Network from v4_client_py.clients.helpers.chain_helpers import ( - Order_TimeInForce, - OrderType, OrderSide, - OrderTimeInForce, - OrderExecution, ) -from examples.utils import loadJson +from examples.utils import loadJson, orderExecutionToTimeInForce from tests.constants import DYDX_TEST_MNEMONIC @@ -36,7 +32,7 @@ async def main() -> None: 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 + good_til_block = next_valid_block_height + 10 time_in_force = orderExecutionToTimeInForce(orderParams['timeInForce']) @@ -64,18 +60,6 @@ async def main() -> None: await asyncio.sleep(5) # wait for placeOrder to complete -def orderExecutionToTimeInForce(orderExecution: str) -> Order_TimeInForce: - if orderExecution == "DEFAULT": - return Order_TimeInForce.TIME_IN_FORCE_UNSPECIFIED - elif orderExecution == "FOK": - return Order_TimeInForce.TIME_IN_FORCE_FILL_OR_KILL - elif orderExecution == "IOC": - return Order_TimeInForce.TIME_IN_FORCE_IOC - elif orderExecution == "POST_ONLY": - return Order_TimeInForce.TIME_IN_FORCE_POST_ONLY - else: - raise ValueError('Unrecognized order execution') - if __name__ == '__main__': logging.basicConfig(level=logging.INFO) asyncio.get_event_loop().run_until_complete(main()) diff --git a/v4-client-py/examples/utils.py b/v4-client-py/examples/utils.py index 67f86f2d..5ec2ea8b 100644 --- a/v4-client-py/examples/utils.py +++ b/v4-client-py/examples/utils.py @@ -1,6 +1,10 @@ +from enum import Enum import json import os +from typing import Tuple + +from v4_client_py.clients.helpers.chain_helpers import Order_TimeInForce, is_order_flag_stateful_order def loadJson(filename): current_directory = os.path.dirname(os.path.abspath(__file__)) @@ -8,3 +12,21 @@ def loadJson(filename): with open(json_file_path, "r") as file: return json.load(file) + +class HumanReadableOrderTimeInForce(Enum): + DEFAULT = "DEFAULT" + FOK = "FOK" + IOC = "IOC" + POST_ONLY = "POST_ONLY" + +def orderExecutionToTimeInForce(orderExecution: HumanReadableOrderTimeInForce) -> Order_TimeInForce: + if orderExecution == HumanReadableOrderTimeInForce.DEFAULT.value: + return Order_TimeInForce.TIME_IN_FORCE_UNSPECIFIED + elif orderExecution == HumanReadableOrderTimeInForce.FOK.value: + return Order_TimeInForce.TIME_IN_FORCE_FILL_OR_KILL + elif orderExecution == HumanReadableOrderTimeInForce.IOC.value: + return Order_TimeInForce.TIME_IN_FORCE_IOC + elif orderExecution == HumanReadableOrderTimeInForce.POST_ONLY.value: + return Order_TimeInForce.TIME_IN_FORCE_POST_ONLY + else: + raise ValueError('Unrecognized order execution') diff --git a/v4-client-py/pyproject.toml b/v4-client-py/pyproject.toml index 78912a5a..dce77ab3 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.7" +version = "0.6.8" description = "dYdX v4 Client" authors = ["John Huang "] license = "BSL-1.1" diff --git a/v4-client-py/tests/constants.py b/v4-client-py/tests/constants.py index 8c465f86..95c6398f 100644 --- a/v4-client-py/tests/constants.py +++ b/v4-client-py/tests/constants.py @@ -8,6 +8,7 @@ DEFAULT_NETWORK_ID = 1001 SEVEN_DAYS_S = 7 * 24 * 60 * 60 +MAX_CLIENT_ID = 2 ** 32 - 1 DYDX_TEST_ADDRESS = 'dydx14zzueazeh0hj67cghhf9jypslcf9sh2n5k6art' DYDX_TEST_PRIVATE_KEY = 'e92a6595c934c991d3b3e987ea9b3125bf61a076deab3a9cb519787b7b3e8d77' diff --git a/v4-client-py/v4_client_py/clients/composer.py b/v4-client-py/v4_client_py/clients/composer.py index 38ddf6d4..cbd909b6 100644 --- a/v4-client-py/v4_client_py/clients/composer.py +++ b/v4-client-py/v4_client_py/clients/composer.py @@ -6,6 +6,8 @@ from v4_proto.dydxprotocol.sending.transfer_pb2 import Transfer, MsgWithdrawFromSubaccount, MsgDepositToSubaccount from v4_proto.dydxprotocol.sending.tx_pb2 import MsgCreateTransfer +from v4_client_py.clients.helpers.chain_helpers import is_order_flag_stateful_order, validate_good_til_fields + class Composer: def compose_msg_place_order( @@ -77,6 +79,10 @@ def compose_msg_place_order( :returns: Place order message, to be sent to chain ''' subaccount_id = SubaccountId(owner=address, number=subaccount_number) + + is_stateful_order = is_order_flag_stateful_order(order_flags) + validate_good_til_fields(is_stateful_order, good_til_block_time, good_til_block) + order_id = OrderId( subaccount_id=subaccount_id, client_id=client_id, @@ -147,12 +153,21 @@ def compose_msg_cancel_order( :returns: Tx information ''' subaccount_id = SubaccountId(owner=address, number=subaccount_number) + is_stateful_order = is_order_flag_stateful_order(order_flags) + validate_good_til_fields(is_stateful_order, good_til_block_time, good_til_block) + order_id = OrderId( subaccount_id=subaccount_id, client_id=client_id, order_flags=order_flags, clob_pair_id=int(clob_pair_id) ) + + if is_stateful_order: + return MsgCancelOrder( + order_id=order_id, + good_til_block_time=good_til_block_time + ) return MsgCancelOrder( order_id=order_id, good_til_block=good_til_block 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 c54124b9..11d71f87 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 @@ -1,3 +1,4 @@ +from typing import Tuple import grpc from datetime import datetime, timedelta @@ -17,9 +18,8 @@ calculate_time_in_force, calculate_order_flags, ORDER_FLAGS_SHORT_TERM, - ORDER_FLAGS_LONG_TERM, - ORDER_FLAGS_CONDITIONAL, SHORT_BLOCK_WINDOW, + is_order_flag_stateful_order, ) from v4_client_py.clients.constants import Network @@ -45,16 +45,28 @@ def get_current_block(self) -> int: response = self.validator_client.get.latest_block() return response.block.header.height - 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()) + # Helper function to generate the corresponding + # good_til_block, good_til_block_time fields to construct an order. + # good_til_block is the exact block number the short term order will expire on. + # good_til_time_in_seconds is the number of seconds until the stateful order expires. + def generate_good_til_fields( + self, + order_flags: int, + good_til_block: int, + good_til_time_in_seconds: int, + ) -> Tuple[int, int]: + is_stateful_order = is_order_flag_stateful_order(order_flags) + if is_stateful_order: + return 0, self.calculate_good_til_block_time(good_til_time_in_seconds) + else: + return good_til_block, 0 + 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 @@ -82,6 +94,7 @@ def place_order( size: float, client_id: int, time_in_force: OrderTimeInForce, + good_til_block: int, good_til_time_in_seconds: int, execution: OrderExecution, post_only: bool, @@ -112,6 +125,9 @@ def place_order( :param time_in_force: required :type time_in_force: OrderTimeInForce + :param good_til_block: required + :type good_til_block: int + :param good_til_time_in_seconds: required :type good_til_time_in_seconds: int @@ -135,6 +151,7 @@ def place_order( size=size, client_id=client_id, time_in_force=time_in_force, + good_til_block=good_til_block, good_til_time_in_seconds=good_til_time_in_seconds, execution=execution, post_only=post_only, @@ -279,6 +296,7 @@ def place_order_message( size: float, client_id: int, time_in_force: OrderTimeInForce, + good_til_block: int, good_til_time_in_seconds: int, execution: OrderExecution, post_only: bool, @@ -297,8 +315,11 @@ def place_order_message( subticks = calculate_subticks(price, atomic_resolution, quantum_conversion_exponent, subticks_per_tick) order_flags = calculate_order_flags(type, time_in_force) time_in_force = calculate_time_in_force(type, time_in_force, execution, post_only) - good_til_block = self.calculate_good_til_block() if order_flags == ORDER_FLAGS_SHORT_TERM else 0 - good_til_block_time = self.calculate_good_til_block_time(good_til_time_in_seconds) if (order_flags == ORDER_FLAGS_LONG_TERM or order_flags == ORDER_FLAGS_CONDITIONAL) else 0 + good_til_block, good_til_block_time = self.generate_good_til_fields( + order_flags, + good_til_block, + good_til_time_in_seconds, + ) client_metadata = self.calculate_client_metadata(type) condition_type = self.calculate_condition_type(type) conditional_order_trigger_subticks = self.calculate_conditional_order_trigger_subticks( @@ -377,10 +398,10 @@ def cancel_order( self, subaccount: Subaccount, client_id: int, - clob_pair_id: int, + market: str, order_flags: int, + good_til_time_in_seconds: int, good_til_block: int, - good_til_block_time: int, ) -> SubmittedTx: ''' Cancel order @@ -391,22 +412,95 @@ def cancel_order( :param client_id: required :type client_id: int - :param clob_pair_id: required - :type clob_pair_id: int + :param market: required + :type market: str :param order_flags: required :type order_flags: int - :param good_til_block: optional + :param good_til_block: required :type good_til_block: int - :param good_til_block_time: optional + :param good_til_block_time: required :type good_til_block_time: int :returns: Tx information ''' - return self.validator_client.post.cancel_order( - subaccount=subaccount, + msg = self.cancel_order_message( + subaccount, + market, + client_id, + order_flags, + good_til_time_in_seconds, + good_til_block, + ) + + return self.validator_client.post.send_message(subaccount=subaccount, msg=msg, zeroFee=True) + + + def cancel_short_term_order( + self, + subaccount: Subaccount, + client_id: int, + market: str, + good_til_block: int, + ) -> SubmittedTx: + ''' + Cancel order + + :param subaccount: required + :type subaccount: Subaccount + + :param client_id: required + :type client_id: int + + :param clob_pair_id: required + :type clob_pair_id: int + + :param good_til_block: required + :type good_til_block: int + + :returns: Tx information + ''' + msg = self.cancel_order_message( + subaccount, + market, + client_id, + order_flags=ORDER_FLAGS_SHORT_TERM, + good_til_time_in_seconds=0, + good_til_block=good_til_block, + ) + + return self.validator_client.post.send_message(subaccount=subaccount, msg=msg, zeroFee=True) + + + def cancel_order_message( + self, + subaccount: Subaccount, + market: str, + client_id: int, + order_flags: int, + good_til_time_in_seconds: int, + good_til_block: int, + ) -> MsgPlaceOrder: + # Validate the GoodTilBlock for short term orders. + if not is_order_flag_stateful_order(order_flags): + self.validate_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'] + + good_til_block, good_til_block_time = self.generate_good_til_fields( + order_flags, + good_til_block, + good_til_time_in_seconds, + ) + + return self.validator_client.post.composer.compose_msg_cancel_order( + address=subaccount.address, + subaccount_number=subaccount.subaccount_number, client_id=client_id, clob_pair_id=clob_pair_id, order_flags=order_flags, 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 bcdbc034..6b9b26d7 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 @@ -68,6 +68,49 @@ class Order_TimeInForce(Flag): QUOTE_QUANTUMS_ATOMIC_RESOLUTION = -6 +def is_order_flag_stateful_order( + order_flag: int +) -> bool: + if order_flag == ORDER_FLAGS_SHORT_TERM: + return False + elif order_flag == ORDER_FLAGS_LONG_TERM: + return True + elif order_flag == ORDER_FLAGS_CONDITIONAL: + return True + else: + raise ValueError('Invalid order flag') + +def validate_good_til_fields( + is_stateful_order: bool, + good_til_block_time: int, + good_til_block: int, +): + if is_stateful_order: + if good_til_block_time == 0: + raise ValueError( + "stateful orders must have a valid GTBT. GTBT: ${0}".format( + good_til_block_time, + ) + ) + if good_til_block != 0: + raise ValueError( + "stateful order uses GTBT. GTB must be zero. GTB: ${0}".format( + good_til_block, + ) + ) + else: + if good_til_block == 0: + raise ValueError( + "short term orders must have a valid GTB. GTB: ${0}".format( + good_til_block, + ) + ) + if good_til_block_time != 0: + raise ValueError( + "stateful order uses GTB. GTBT must be zero. GTBT: ${0}".format( + good_til_block_time, + ) + ) def round( number: float, @@ -106,30 +149,30 @@ def calculate_time_in_force( time_in_force: OrderTimeInForce, execution: OrderExecution, post_only: bool -) -> Order.TimeInForce: +) -> Order_TimeInForce: if type == OrderType.MARKET: - return Order.TIME_IN_FORCE_IOC + return Order_TimeInForce.TIME_IN_FORCE_IOC elif type == OrderType.LIMIT: if time_in_force == OrderTimeInForce.GTT: if post_only: - return Order.TIME_IN_FORCE_POST_ONLY + return Order_TimeInForce.TIME_IN_FORCE_POST_ONLY else: - return Order.TIME_IN_FORCE_UNSPECIFIED + return Order_TimeInForce.TIME_IN_FORCE_UNSPECIFIED elif time_in_force == OrderTimeInForce.FOK: - return Order.TIME_IN_FORCE_FILL_OR_KILL + return Order_TimeInForce.TIME_IN_FORCE_FILL_OR_KILL elif time_in_force == OrderTimeInForce.IOC: - return Order.TIME_IN_FORCE_IOC + return Order_TimeInForce.TIME_IN_FORCE_IOC else: raise Exception("Unexpected code path: time_in_force") elif type == OrderType.STOP_LIMIT or type == OrderType.TAKE_PROFIT_LIMIT: if execution == OrderExecution.DEFAULT: - return Order.TIME_IN_FORCE_UNSPECIFIED + return Order_TimeInForce.TIME_IN_FORCE_UNSPECIFIED elif execution == OrderExecution.POST_ONLY: - return Order.TIME_IN_FORCE_POST_ONLY + return Order_TimeInForce.TIME_IN_FORCE_POST_ONLY if execution == OrderExecution.FOK: - return Order.TIME_IN_FORCE_FOK + return Order_TimeInForce.TIME_IN_FORCE_FILL_OR_KILL elif execution == OrderExecution.IOC: - return Order.TIME_IN_FORCE_IOC + return Order_TimeInForce.TIME_IN_FORCE_IOC else: raise Exception("Unexpected code path: time_in_force") elif type == OrderType.STOP_MARKET or type == OrderType.TAKE_PROFIT_MARKET: @@ -138,9 +181,9 @@ def calculate_time_in_force( elif execution == OrderExecution.POST_ONLY: raise Exception("Execution value POST_ONLY not supported for STOP_MARKET or TAKE_PROFIT_MARKET") if execution == OrderExecution.FOK: - return Order.TIME_IN_FORCE_FOK + return Order_TimeInForce.TIME_IN_FORCE_FILL_OR_KILL elif execution == OrderExecution.IOC: - return Order.TIME_IN_FORCE_IOC + return Order_TimeInForce.TIME_IN_FORCE_IOC else: raise Exception("Unexpected code path: time_in_force") else: diff --git a/v4-client-py/v4_client_py/clients/modules/post.py b/v4-client-py/v4_client_py/clients/modules/post.py index fff67069..68b93ffc 100644 --- a/v4-client-py/v4_client_py/clients/modules/post.py +++ b/v4-client-py/v4_client_py/clients/modules/post.py @@ -288,4 +288,4 @@ def default_broadcast_mode(self, msg: _message.Message) -> BroadcastMode: return BroadcastMode.BroadcastTxCommit else: return BroadcastMode.BroadcastTxCommit - return BroadcastMode.BroadcastTxSync \ No newline at end of file + return BroadcastMode.BroadcastTxSync