dydxprotocol / dydx-v3-python

Python client for dYdX (API v3)
Apache License 2.0
304 stars 174 forks source link

Error when placing a simple MARKET order #200

Open Izem0 opened 1 year ago

Izem0 commented 1 year ago

Using this code:

account_response = client.private.get_account()
position_id = account_response.data['account']['positionId']
market_data = client.public.get_markets(market=MARKET_MATIC_USD)
price = market_data.data['markets'][MARKET_MATIC_USD]['oraclePrice']
expiration_epoch_seconds = int((pd.Timestamp.utcnow() + pd.Timedelta(weeks=1)).timestamp())

placed_order = client.private.create_order(
  position_id=position_id, # required for creating the order signature
  market=MARKET_MATIC_USD,
  side=ORDER_SIDE_SELL,
  order_type=ORDER_TYPE_MARKET,
  post_only=False,
  size='10',
  price=str(price),
  limit_fee='1',
  expiration_epoch_seconds=expiration_epoch_seconds,
  time_in_force=TIME_IN_FORCE_GTT,
)

I get this error: dydx3.errors.DydxApiError: DydxApiError(status_code=400, response={'errors': [{'msg': 'Order with timeInForce: GTT, triggerPrice: undefined or trailingPercent: undefined will not be treated as MARKET'}]})

When I try with time_in_force = 'FOK' or 'IOC', the order is placed but cancelled immediately.

Thanks

jadch commented 1 year ago

I've been having the same issue with the node SDK 🙃

It's honestly hard to understand why a small market order sometimes fails to be filled, especially when there is obviously liquidity in the order books. It's also weird to have to pass a price when creating a market order.

Would love an explanation or to know how to stop this from happening.

sirEven commented 1 year ago

Have you guys tried placing these market orders with the best bid or best ask price from the orderbook? I have no experience doing it with oracle price - however I do my market orders with orderbook data and they normally go through. If they don't however bribing with a slightly better price (resubmitting with a price that is slightly worse for you) helps. I think there is an example in the dydx3 github repo.

Edit: Here is the example: https://github.com/dydxprotocol/dydx-v3-python/blob/master/examples/orders.py

Izem0 commented 1 year ago

Just tried with different prices from the orderbook but the error remains.

The example order in the docs you provided uses a LIMIT order type, not a MARKET one. I have no issue when placing a LIMIT order, but I still have with a MARKET order.

traderblakeq commented 1 year ago

I am in the same boat, I cant figure this "MARKET" order out.

traderblakeq commented 1 year ago

This does work though: (But still I would like some more explanation on the logic)

    position_id = account_response.data['account']['positionId']
    market_data = private_client.public.get_markets(market=MARKET_BTC_USD)
    price = float(market_data.data['markets'][MARKET_BTC_USD]['oraclePrice'])
    expiration_epoch_seconds = int((pd.Timestamp.utcnow() + pd.Timedelta(weeks=1)).timestamp())

    order_params = {
        'position_id': position_id, 
        'market' : MARKET_BTC_USD,
        'side' : ORDER_SIDE_SELL,
        'order_type' : ORDER_TYPE_MARKET,
        'post_only': False,
        'size' : '0.01',
        'price' : str(round(price,0)),
        'limit_fee' : '0.015',
        'expiration_epoch_seconds' : expiration_epoch_seconds,
        'time_in_force' : TIME_IN_FORCE_IOC,
        }

    order_response = private_client.private.create_order(**order_params)
    order_id = order_response.data['order']['id']
traderblakeq commented 1 year ago

For other users to use if of any good: ( This is my take ):

def sell_market_order(symbol: str, size: float , percent_slippage=0.003):
    """
    Place a market sell order for a specified symbol and size, considering the specified percent slippage.

    Args:
        symbol (str): The trading pair symbol (e.g., 'BTC-USD', 'ETH-USD').
        size (float): The size of the asset to sell.
        percent_slippage (float, optional): The maximum allowed slippage percentage. Defaults to 0.003 (0.3%).

    Returns:
        str: The ID of the created sell order.

    """

    market_data = client.public.get_markets(market=symbol)
    price = float(market_data.data['markets'][symbol]['oraclePrice'])
    price = price * (1 - percent_slippage) # min allowed price incl slippage 
    expiration_epoch_seconds = int((pd.Timestamp.utcnow() + pd.Timedelta(weeks=1)).timestamp())

    order_params = {
        'position_id': position_id, 
        'market' : symbol,
        'side' : ORDER_SIDE_SELL,
        'order_type' : ORDER_TYPE_MARKET,
        'post_only': False,
        'size' : str(size),
        'price' : str(round(price,0)),
        'limit_fee' : '0.0015',
        'expiration_epoch_seconds' : expiration_epoch_seconds,
        'time_in_force' : TIME_IN_FORCE_IOC,
        }

    order_response = client.private.create_order(**order_params)
    order_id = order_response.data['order']['id']

    return order_id

def buy_market_order(symbol: str, size: float, percent_slippage=0.003):
    """
    Place a market buy order for a specified symbol and size, considering the specified percent slippage.

    Args:
        symbol (str): The trading pair symbol (e.g., 'BTC-USD', 'ETH-USD').
        size (float): The size of the asset to buy.
        percent_slippage (float, optional): The maximum allowed slippage percentage. Defaults to 0.003 (0.3%).

    Returns:
        str: The ID of the created buy order.

    """

    market_data = client.public.get_markets(market=symbol)
    price = float(market_data.data['markets'][symbol]['oraclePrice'])
    price = price * (1 + percent_slippage) # max allowed price incl slippage 
    expiration_epoch_seconds = int((pd.Timestamp.utcnow() + pd.Timedelta(weeks=1)).timestamp())

    order_params = {
        'position_id': position_id, 
        'market' : symbol,
        'side' : ORDER_SIDE_BUY,
        'order_type' : ORDER_TYPE_MARKET,
        'post_only': False,
        'size' : str(size),
        'price' : str(round(price,0)),
        'limit_fee' : '0.0015',
        'expiration_epoch_seconds' : expiration_epoch_seconds,
        'time_in_force' : TIME_IN_FORCE_IOC,
        }

    order_response = client.private.create_order(**order_params)
    order_id = order_response.data['order']['id']

    return order_id
trimblomit commented 1 year ago

any progress on this? i dunno why it needs to worry about price for a market order and I cant figure out why it will create an order and then not create it another time

traderblakeq commented 1 year ago

As it is a DEX you need Price variable to Control slippage. (DYDX matching will though always ensure you get the best price possible on the order book).

Here is some of my code, hope it helps.

loss]

def place_market_order(self, symbol: str, size: float, side: str, atr: float, percent_slippage=0.003) -> Optional[str]:
    """
    Place a market order for a specified symbol, size, and side, considering the specified percent slippage.

    Args:
        symbol (str): The trading pair symbol (e.g., 'BTC-USD', 'ETH-USD').
        size (float): The size of the asset to buy or sell.
        side (str): The order side ('BUY' or 'SELL').
        percent_slippage (float, optional): The maximum allowed slippage percentage. Defaults to 0.003 (0.3%).

    Returns:
        Optional[str]: The ID of the created order or None if an error occurred.

    """
    try:
        market_data = self.client.public.get_markets(market=symbol).data
        if market_data is None:
            raise ValueError(f"Market data not found for symbol: {symbol}")

        price = float(market_data['markets'][symbol]['indexPrice'])
        if price <= 0:
            raise ValueError(f"Invalid indexPrice: {price}")

        tick_size = float(market_data['markets'][symbol]['tickSize'])
        if tick_size <= 0:
            raise ValueError(f"Invalid tick size: {tick_size}")

        if side not in ['BUY', 'SELL']:
            raise ValueError("Invalid side value. Must be 'BUY' or 'SELL'.")

        # Adjust price based on slippage and side
        if side == 'BUY':
            price = price * (1 + percent_slippage) # max allowed price incl slippage
        elif side == 'SELL':
            price = price * (1 - percent_slippage) # min allowed price incl slippage

        # Normalize price to tick size
        price = round(price / tick_size) * tick_size
        decimals = abs((decimal.Decimal(market_data['markets'][symbol]['indexPrice']).as_tuple().exponent))
        expiration_epoch_seconds = int((pd.Timestamp.utcnow() + pd.Timedelta(weeks=1)).timestamp())
        order_ids = dict()

        order_params = {
            'position_id': self.position_id, 
            'market' : symbol,
            'side' : side,
            'order_type' : ORDER_TYPE_MARKET,
            'post_only': False,
            'size' : str(size),
            'price' : str(round(price, decimals)),
            'limit_fee' : '0.0015',
            'expiration_epoch_seconds' : expiration_epoch_seconds,
            'time_in_force' : TIME_IN_FORCE_IOC,
            }

        order_response = self.client.private.create_order(**order_params)
        order_id = order_response.data['order']['id']
        if not order_id:
           self.logger.error(f'place {side} market order for {symbol} did not go through')
           return

        order_ids['position'] = order_response.data['order']
        order_ids['position']['decimals'] = str(decimals)
        order_ids['position']['tickSize'] = str(tick_size)

        return order_ids