Limit Bid Order Tutorial

This guide shows you how to place a limit bid order for a digital asset using the Upbit API.

Tutorial Example Code Guide
This tutorial includes step-by-step partial code and API call/response examples to aid user understanding.
The full code examples can be found in the Recipes menu.
Click the button above to navigate to the full code Recipe page of this tutorial.

Get Started

In this tutorial, you’ll build a simple automated flow that places a limit buy order at 3% below the current best bid using the Upbit API.

A typical sequence for buying a digital asset on Upbit is:

  1. Deposit Fiat
  2. Select the asset to buy
  3. Check the current orderbook (best bid/ask)
  4. Enter the price and quantity to create a limit buy order
  5. Check the order status

In this guide, we’ll follow the same steps: read the current best bid for a target market, calculate a price 3% lower, and place a limit buy order. Python code examples are included.

This guide includes code that can place real buy orders using your actual funds.
If you do not intend to trade, please review the API usage examples only and make sure not to run any functions that create real orders.
Check the proper endpoint based on your region.
The examples in this page is written using Singapore fiat code(SGD). Set the quote currency to match your region. The base_url differs by country/region. Make sure to specify the correct region value for your environment.

- Singapore (sg): https://sg-api.upbit.com
- Indonesia (id): https://id-api.upbit.com
- Thailand (th): https://th-api.upbit.com

Authentication Guide

Some APIs require generating a JWT for authentication and including it in the request header when making calls. Please refer to the Authentication documentation and the recipe below to add the authentication header for all Exchange API calls.


Limit Bid Order Tutorial

Check Order Availability

Implement a function that takes a trading pair as input and checks whether Upbit supports trading for it. Use the List Trading Pairs API to verify that the market exists.

def get_trading_pair(trading_pair: str) -> str:
    url = "https://sg-api.upbit.com/v1/market/all"
    headers = {
        "Content-Type": "application/json"
    }
    response = requests.get(url, headers=headers).json()
    trading_pair_list = [
        item for item in response if item.get('market') == trading_pair]
    if len(trading_pair_list) == 0:
        raise ValueError("The trading pair list is empty.")
    return trading_pair_list[0].get('market')

When this function runs, it retrieves an array of trading pair objects supported by Upbit. It then compares the value of the market field in each object with the pair entered by the user. If a match is found, the function returns the corresponding market value. If no matching pair exists, the function raises an error.

[
  {
    "market": "SGD-ETH",
    "english_name": "Ethereum",
    "market_warning": "NONE"
  },
  {
    "market": "SGD-XRP",
    "english_name": "XRP",
    "market_warning": "NONE"
  },
  {
    "market": "SGD-BTC",
    "english_name": "Bitcoin",
    "market_warning": "NONE"
  },
  {
    "market": "SGD-USDT",
    "english_name": "Tether",
    "market_warning": "NONE"
  },
  {
    "market": "SGD-SOL",
    "english_name": "Solana",
    "market_warning": "NONE"
  }
]

Get Current Orderbook

Implement a function that calls the Orderbook API to fetch the current orderbook for the selected trading pair. In this tutorial, the function extracts and returns the highest bid price from the orderbook data.

def get_best_bid_price(trading_pair: str) -> Decimal:
    params = {
        "markets": trading_pair
    }
    url = "https://sg-api.upbit.com/v1/orderbook"
    headers = {
        "Content-Type": "application/json"
    }
    response = requests.get(url, headers=headers, params=params).json()
    orderbook_units = response[0].get('orderbook_units')
    highest_bid_price = Decimal(str(orderbook_units[0].get('bid_price')))
    if highest_bid_price is None:
        raise ValueError(
            "Please check the orderbook. {response}".format(response=response))
    else:
        return highest_bid_price

When this function runs, it returns the highest bid price for the specified trading pair, as shown below.

144882

Tick Size & Rounding Price

Upbit provides market-specific price tick sizes (SGD, IDR, THB markets). Tick size varies by price range. This guide uses SGD tick sizes only (The tick sizes for IDR and THB require a different implementation. Please refer to the link below to check the tick size rules for each market and implement accordingly).

Implement a function that returns the correct tick size for a given price:

def get_tick_size(price: Decimal) -> Decimal:
    if price <= 0:
        raise ValueError("price must be > 0")

    if price >= Decimal("2000"):
        return Decimal("1")
    if price < Decimal("0.01"):
        return Decimal("0.00001")

    decade = int(price.log10().to_integral_value(rounding=ROUND_DOWN))

    if decade == 3:
        leading = price / (Decimal(10) ** decade)
        return Decimal("1") if leading >= Decimal("2") else Decimal("0.5")

    elif decade == 2:
        base = Decimal("0.1")
        leading = price / Decimal("100")
        return base if leading >= Decimal("5") else base / Decimal("2")

    elif decade == 1:
        return Decimal("0.01")

    elif decade == 0:
        return Decimal("0.005")

    elif decade == -1:
        return Decimal("0.001")

    else:
        return Decimal("0.0001")

Using the previously implemented function for retrieving the price unit, implement a function to set the order price. The function rounds down the user-entered digital asset price to the nearest tick size. This ensures that the order price conforms to the tick size rules and helps prevent exceeding the intended order amount.

def round_price_by_tick_size(price: Decimal) -> Decimal:
    tick = get_tick_size(price)
    return (price // tick) * tick

Create Order

To place a limit buy order, you’ll need the following:

  1. The trading pair of the asset to buy (e.g., SGD-BTC)
  2. Order side (buy or sell)
  3. Order type (limit, market, etc.)
  4. Purchase price per unit of the digital asset
  5. Quantity of the digital asset to buy

In this tutorial, we place a limit buy order 3% below the current best bid price. The order side and order type are therefore set to bid and limit. Since we already implemented a function to calculate valid prices based on tick size rules, you only need to provide the trading pair and quantity. The order price will be adjusted automatically to comply with the required tick size.

Check Before Placing an Order!
When placing a real order, it may be rejected according to the exchange’s order policies, regardless of your account balance. Before executing an order, make sure the order conditions comply with the policy.

• Buy orders placed at ±300% or more from the current market price

• Buy/sell orders exceeding 30 ticks from the market price

• Other internal policies

The actual amount paid for a buy order may differ from the total amount specified.
The example code here shows only a simple calculation of buy quantity. In practice, the final payment depends on execution conditions and applicable fees.

  • The execution price (unit price of the digital asset) can fluctuate due to slippage and liquidity.
  • The actual payment amount is therefore calculated as: (Executed Quantity × Execution Price) + Fees
  • This function creates a limit buy order using the trading pair, the unit buy price, and the total purchase amount. It then returns the UUID of the created order.

    To successfully execute a buy order, your account must have enough balance to cover both the order amount and the corresponding fees. If your available balance matches the order amount exactly but does not include the fee, the order will not be created.

    def create_order(
        market: str,
        price: str,
        volume: str
    ) -> str:
        body = {
            "market": market,
            "side": "bid",
            "ord_type": "limit",
            "price": price,
            "volume": volume,
        }
        query_string = _build_query_string(body)
        jwt_token = _create_jwt(access_key, secret_key, query_string)
        url = "https://sg-api.upbit.com/v1/orders"
        headers = {
            "Authorization": "Bearer {jwt_token}".format(jwt_token=jwt_token),
            "Content-Type": "application/json"
        }
        response = requests.post(url, headers=headers, json=body).json()
        uuid = response.get('uuid')
        if uuid is None:
            raise ValueError("Please check the response. {response}".format(response=response))
        else:
            return uuid

    Check Order Status

    Implement a function that takes an order UUID as input and retrieves the current status of the order. In this tutorial, you’ll use it to check the status of the limit bid order.

    def get_order(uuid: str) -> Mapping:
        params = {
            "uuid": uuid
        }
        query_string = _build_query_string(params)
        jwt_token = _create_jwt(access_key, secret_key, query_string)
        url = "https://sg-api.upbit.com/v1/order"
        headers = {
            "Authorization": "Bearer {jwt_token}".format(jwt_token=jwt_token),
            "Content-Type": "application/json"
        }
        response = requests.get(url, headers=headers, params=params).json()
        return response

    When the function is executed, it returns the order information as shown below. You can check the order status, such as pending or completed, through the state field.

    {
      "uuid": "<your_order_uuid>",
      "side": "bid",
      "ord_type": "limit",
      "price": "153559.00",
      "state": "done",
      "market": "SGD-BTC",
      "created_at": "2025-07-10T13:15:08+09:00",
      "volume": "1.0",
      "remaining_volume": "0.0",
      "prevented_volume": "0",
      "reserved_fee": "0.0",
      "remaining_fee": "0.0",
      "paid_fee": "383.8975",
      "locked": "0.0",
      "prevented_locked": "0",
      "executed_volume": "1.0",
      "trades_count": 0,
      "trades": []
    }

    Full Code Example

    The following complete example shows how to deposit Fiat and place a limit buy order for a digital asset at a price 3% lower than the current best ask.

    Limit Bid Order Tutorial

    from urllib.parse import unquote, urlencode
    from typing import Any, Union
    from collections.abc import Mapping
    import hashlib
    import uuid
    import jwt # PyJWT
    import requests
    from decimal import Decimal, getcontext, ROUND_FLOOR
    
    access_key = "<YOUR_ACCESS_KEY>"
    secret_key = "<YOUR_SECRET_KEY>"
    
    # Please add the logic here to generate the JWT authentication token.
    
    def get_trading_pair(trading_pair: str) -> str:
        url = "https://sg-api.upbit.com/v1/market/all"
        headers = {
            "Content-Type": "application/json"
        }
        response = requests.get(url, headers=headers).json()
        trading_pair_list = [
            item for item in response if item.get('market') == trading_pair]
        if len(trading_pair_list) == 0:
            raise ValueError("The trading pair list is empty.")
        return trading_pair_list[0].get('market')
    
    
    getcontext().prec = 16
    
    
    def get_best_bid_price(trading_pair: str) -> Decimal:
        params = {
            "markets": trading_pair
        }
        url = "https://sg-api.upbit.com/v1/orderbook"
        headers = {
            "Content-Type": "application/json"
        }
        response = requests.get(url, headers=headers, params=params).json()
        orderbook_units = response[0].get('orderbook_units')
        highest_bid_price = Decimal(str(orderbook_units[0].get('bid_price')))
        if highest_bid_price is None:
            raise ValueError(
                "Please check the orderbook. {response}".format(response=response))
        else:
          return highest_bid_price
        
    def get_tick_size(price: Decimal) -> Decimal:
        if price <= 0:
            raise ValueError("price must be > 0")
    
        if price >= Decimal("2000"):
            return Decimal("1")
        if price < Decimal("0.01"):
            return Decimal("0.00001")
    
        decade = int(price.log10().to_integral_value(rounding=ROUND_DOWN))
    
        if decade == 3:
            leading = price / (Decimal(10) ** decade)
            return Decimal("1") if leading >= Decimal("2") else Decimal("0.5")
    
        elif decade == 2:
            base = Decimal("0.1")
            leading = price / Decimal("100")
            return base if leading >= Decimal("5") else base / Decimal("2")
    
        elif decade == 1:
            return Decimal("0.01")
    
        elif decade == 0:
            return Decimal("0.005")
    
        elif decade == -1:
            return Decimal("0.001")
    
        else:
            return Decimal("0.0001")
          
    def round_price_by_tick_size(price: Decimal) -> Decimal:
        tick = get_tick_size(price)
        return (price // tick) * tick
    
    def create_order(
        trading_pair: str,
        price: str,
        volume: str
    ) -> str:
        body = {
            "market": trading_pair,
            "side": "bid",
            "ord_type": "limit",
            "price": price,
            "volume": volume,
        }
        query_string = _build_query_string(body)
        jwt_token = _create_jwt(access_key, secret_key, query_string)
        url = "https://sg-api.upbit.com/v1/orders"
        headers = {
            "Authorization": "Bearer {jwt_token}".format(jwt_token=jwt_token),
            "Content-Type": "application/json"
        }
        response = requests.post(url, headers=headers, json=body).json()
        uuid = response.get('uuid')
        if uuid is None:
            raise ValueError(
                "Please check the response. {response}".format(response=response))
        else:
            return uuid
    
    
    def get_order(uuid: str) -> Mapping:
        params = {
            "uuid": uuid
        }
        query_string = _build_query_string(params)
        jwt_token = _create_jwt(access_key, secret_key, query_string)
        url = "https://sg-api.upbit.com/v1/order"
        headers = {
            "Authorization": "Bearer {jwt_token}".format(jwt_token=jwt_token),
            "Content-Type": "application/json"
        }
        response = requests.get(url, headers=headers, params=params).json()
        return response
    
    # if __name__ == '__main__':
    #     trading_pair = "SGD-BTC"
    #     volume = "0.0001"
    #     trading_pair = get_trading_pair(trading_pair)
    #     orderbook_unit = get_best_bid_price(trading_pair)
    #     price_3percent_rounded = str(
    #         round_price_by_tick_size(orderbook_unit * Decimal(0.97)))
    #     volume = str(Decimal(volume).quantize(
    #         Decimal('1e-8'), rounding=ROUND_DOWN))
    
    #     order_uuid = create_order(trading_pair, price_3percent_rounded, volume)
    #     order_info = get_order(order_uuid)
    #     print(order_info)