Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[PY] [CLOB-894] [CLOB-895] v0.6.8 Quick python example for canceling short term and long term orders #42

Merged
merged 5 commits into from
Sep 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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')
lucas-dydx marked this conversation as resolved.
Show resolved Hide resolved
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