Skip to content

Commit

Permalink
[PY] [CLOB-894] [CLOB-895] v0.6.8 Quick python example for cancelin…
Browse files Browse the repository at this point in the history
…g short term and long term orders (#42)
  • Loading branch information
jonfung-dydx authored Sep 29, 2023
1 parent 6aca7bd commit 1a84b50
Show file tree
Hide file tree
Showing 11 changed files with 375 additions and 49 deletions.
13 changes: 12 additions & 1 deletion v4-client-py/examples/composite_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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,
Expand All @@ -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,
Expand Down
83 changes: 83 additions & 0 deletions v4-client-py/examples/long_term_order_cancel_example.py
Original file line number Diff line number Diff line change
@@ -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())
73 changes: 73 additions & 0 deletions v4-client-py/examples/short_term_order_cancel_example.py
Original file line number Diff line number Diff line change
@@ -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())
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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'])

Expand Down Expand Up @@ -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())
22 changes: 22 additions & 0 deletions v4-client-py/examples/utils.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,32 @@

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__))
json_file_path = os.path.join(current_directory, 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')
2 changes: 1 addition & 1 deletion v4-client-py/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 <contact@dydx.exchange>"]
license = "BSL-1.1"
Expand Down
1 change: 1 addition & 0 deletions v4-client-py/tests/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
15 changes: 15 additions & 0 deletions v4-client-py/v4_client_py/clients/composer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down
Loading

0 comments on commit 1a84b50

Please sign in to comment.