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] v0.6.2: Update python client code to place Short-Term orders #32

Merged
merged 3 commits into from
Sep 19, 2023
Merged
Show file tree
Hide file tree
Changes from 2 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
21 changes: 10 additions & 11 deletions v4-client-py/examples/composite_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
johnqh marked this conversation as resolved.
Show resolved Hide resolved

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(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we have a different function to test place_short_term_order? place_order still need to be tested.

Copy link
Contributor Author

@lucas-dydx lucas-dydx Sep 19, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jonfung-dydx will add this testing in a future PR. I don't think this is important for place_order since it shouldn't be used by anyone (since frontend uses typescript client), but we will keep this in mind for the Typescript client.

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**')
Expand Down
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.1"
version = "0.6.2"
description = "dYdX v4 Client"
authors = ["John Huang <contact@dydx.exchange>"]
license = "BSL-1.1"
Expand Down
125 changes: 122 additions & 3 deletions v4-client-py/v4_client_py/clients/dydx_composite_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally this code is updated such that it doesn't need to fetch the current block on each order placement. I just hacked this together to get it working for now.

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
Expand Down Expand Up @@ -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:
'''
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -367,4 +486,4 @@ def withdraw_from_subaccount(
subaccount=subaccount,
asset_id=0,
quantums=amount * 10 ** (- QUOTE_QUANTUMS_ATOMIC_RESOLUTION),
)
)
6 changes: 4 additions & 2 deletions v4-client-py/v4_client_py/clients/helpers/chain_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,17 @@ class OrderTimeInForce(Flag):
FOK = auto() # Fill or Kill

class OrderExecution(Flag):
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should just be removed since it's confusing people, detailed reasons in this ticket here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commented in your ticket

DEFAULT = auto() # Default, STOP_LIMIT and TAKE_PROFIT_LIMIT
POST_ONLY = auto() # Post-only, STOP_LIMIT and TAKE_PROFIT_LIMIT
DEFAULT = auto() # Default
IOC = auto() # Immediate or Cancel
POST_ONLY = auto() # Post-only
FOK = auto() # Fill or Kill
johnqh marked this conversation as resolved.
Show resolved Hide resolved

ORDER_FLAGS_SHORT_TERM = 0
ORDER_FLAGS_LONG_TERM = 64
ORDER_FLAGS_CONDITIONAL = 32

SHORT_BLOCK_WINDOW = 20

QUOTE_QUANTUMS_ATOMIC_RESOLUTION = -6


Expand Down