Skip to main content

Spot Trading Guide

This guide takes you through the core logic of BigONE's Spot Trading API. Instead of providing complex wrapper classes, we focus on the raw HTTP interactions, JSON payloads, and business logic you need to understand to build a robust trading bot.

Prerequisites

  • Base URL: https://api.big.one/api/v3
  • Authentication: Most "Viewer" endpoints (Account, Order) require a Bearer Token (JWT). See the Authentication Guide for details.
  • Required Scopes:
    • view account balance information (to check funds)
    • create orders (to place and cancel orders)
    • view order information (to query status)
  • Content-Type: All POST requests must have Content-Type: application/json.

Lesson 1: Market Rules & Precision

Before placing any order, you must understand the trading rules for your target asset pair. Unlike standard financial markets, crypto markets enforce strict decimal precision limits and minimum order values.

Understanding Asset Pair Configuration

The GET /asset_pairs endpoint provides the rules of engagement. You should call this once at application startup and cache the result.

Key Configuration Fields:

  • base_scale: Controls decimal precision for Amount (Quantity).
    • Example: BTC-USDT has base_scale: 6. Amount 0.123456 is valid; 0.1234567 is invalid.
  • quote_scale: Controls decimal precision for Price.
    • Example: BTC-USDT has quote_scale: 2. Price 50000.12 is valid; 50000.123 is invalid.
  • min_quote_value: The minimum order value (Price × Amount) required.
    • Example: If min_quote_value is 5.0, a buy order for $4.99 will be rejected with 400 Bad Request.

Logic Implementation

  1. Fetch configurations.
  2. Identify your target pair's scales.
  3. Validate: Check if Price * Amount >= min_quote_value.
  4. Format: Convert numbers to strings with exact precision.
import requests

BASE_URL = "https://api.big.one/api/v3"

# 1. Get Configuration
response = requests.get(f"{BASE_URL}/asset_pairs")
pairs = response.json()
pair = next(p for p in pairs if p["name"] == "BTC-USDT")

# 2. Logic: Validation & Formatting
def prepare_order(amount, price, pair_config):
# Check Minimum Value
value = amount * price
min_val = float(pair_config['min_quote_value'])

if value < min_val:
raise ValueError(f"Order value {value} is below minimum {min_val}")

# Format to fixed strings
safe_amount = f"{amount:.{pair_config['base_scale']}f}"
safe_price = f"{price:.{pair_config['quote_scale']}f}"

return safe_amount, safe_price

# Example Usage
try:
amt, prc = prepare_order(0.0001, 50000, pair) # Value = 5.0
print(f"Payload: amount='{amt}', price='{prc}'")
except ValueError as e:
print(f"Validation Error: {e}")

Lesson 2: Funds & Account State

Before trading, verify you have enough Available Balance.

Endpoint: GET /viewer/accounts

Key Response Fields:

  • balance: The amount available for new trades or withdrawals.
  • locked_balance: The amount currently tied up in open orders.

State Transition:

  1. You have 100 USDT available.
  2. You place a limit buy order for 50 USDT.
  3. New State: balance = 50, locked_balance = 50.
  4. If the order fills, locked_balance becomes 0, and you gain BTC.
  5. If you cancel, locked_balance returns to balance.
# Check funds before ordering
accounts = requests.get(f"{BASE_URL}/viewer/accounts", headers=headers).json()
usdt_wallet = next((a for a in accounts if a['asset_symbol'] == 'USDT'), None)

if usdt_wallet and float(usdt_wallet['balance']) > 50.0:
print("Sufficient funds.")
else:
print("Insufficient funds.")

Lesson 3: Placing Orders (The Details)

The POST /viewer/orders endpoint is your primary tool. Let's look at the advanced parameters that control how your order executes.

Order Types (type)

  • LIMIT: "I want to buy at price X or better." (Standard)
  • MARKET: "I want to buy NOW at any price." (Taker)
  • STOP_LIMIT: "If price hits trigger, place a LIMIT order."
  • STOP_MARKET: "If price hits trigger, place a MARKET order."

Execution Flags (Time In Force)

These flags are critical for algorithmic trading (e.g., arbitrage) to prevent partial fills or stuck orders.

  1. immediate_or_cancel (IOC):
    • Logic: "Fill what you can right now, and cancel the rest."
    • Use Case: You see 10 BTC for sale. You want to buy them, but if someone else buys 5 BTC first, you don't want your remaining buy order sitting in the book.
  2. post_only:
    • Logic: "I must be the Maker. If I cross the spread (act as Taker), cancel me."
    • Use Case: Market Making bots that want to earn rebates and avoid taker fees.

Example: The "Safe" Market Maker Order

This order tries to buy at $50,000, but guarantees it won't accidentally pay taker fees.

payload = {
"asset_pair_name": "BTC-USDT",
"side": "BID",
"type": "LIMIT",
"price": "50000.00",
"amount": "0.500000",
"post_only": True, # Crucial for Market Makers
"client_order_id": "mm-bot-001"
}

response = requests.post(f"{BASE_URL}/viewer/orders", json=payload, headers=headers)
# If price is already 49900, this API call will return a cancelled order or error.

Lesson 4: Managing the Lifecycle

Once placed, an order is a living object. You interact with it using its ID.

1. Check Status

Endpoint: GET /viewer/orders/{id}

Status values to handle:

  • PENDING: Still waiting.
  • FILLED: Done. Check avg_deal_price to see the actual trade price.
  • CANCELLED: Stopped by user or system.

2. Cancel Order

Endpoint: POST /viewer/orders/{id}/cancel

Always use the POST method.

3. Batch Cancel (Reset)

Endpoint: POST /viewer/orders/cancel

Useful when your bot restarts or detects a market anomaly.

# Panic Button: Cancel everything
requests.post(f"{BASE_URL}/viewer/orders/cancel",
json={"asset_pair_name": "BTC-USDT"},
headers=headers)

Lesson 5: Error Handling

Don't just check for HTTP 200. BigONE returns useful error codes in the JSON body.

Common Error Codes:

CodeMessageMeaningAction
10014Insufficient fundsAvailable balance too low.Check locked_balance or deposit.
40304Order forbiddenMarket might be in maintenance.Stop bot and alert human.
54041Duplicate orderclient_order_id reused.Generate new ID.
50047Liquidity taken too muchpost_only order would execute as taker.Adjust price and retry.
response = requests.post(f"{BASE_URL}/viewer/orders", json=payload, headers=headers)

if response.status_code != 200:
error = response.json()
code = error.get('code')

if code == 10014:
print("Not enough cash!")
elif code == 50047:
print("Price crossed spread, retrying...")
else:
print(f"Critical API Error: {error['message']}")

Complete Example

Here is a runnable script that combines all the lessons above. It implements a safe "Maker" buy order with status monitoring.

import requests
import time
import jwt # pip install PyJWT

# --- Configuration ---
API_KEY = "YOUR_ACCESS_KEY"
SECRET_KEY = "YOUR_SECRET_KEY"
BASE_URL = "https://api.big.one/api/v3"
PAIR = "BTC-USDT"

# --- Authentication Helper ---
def get_headers():
token = jwt.encode({
"sub": API_KEY,
"nonce": int(time.time() * 1000000), # Microsecond nonce
"iat": int(time.time()),
"exp": int(time.time()) + 60
}, SECRET_KEY, algorithm="HS256")
return {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}

def run_bot():
print(f"--- Starting Spot Bot for {PAIR} ---")

# 1. Get Precision Rules
print("1. Fetching Market Rules...")
pairs = requests.get(f"{BASE_URL}/asset_pairs").json()
pair_cfg = next(p for p in pairs if p["name"] == PAIR)
base_scale = pair_cfg['base_scale'] # Amount precision
quote_scale = pair_cfg['quote_scale'] # Price precision
min_val = float(pair_cfg['min_quote_value'])

# 2. Define Targets
target_price = 40000.50 # Example Price
target_amount = 0.002 # Example Amount

# 3. Validate & Format
order_val = target_price * target_amount
if order_val < min_val:
print(f"Error: Order value {order_val} < Minimum {min_val}")
return

# FORMATTING AS STRINGS IS CRITICAL FOR SPOT API
safe_price = f"{target_price:.{quote_scale}f}"
safe_amount = f"{target_amount:.{base_scale}f}"

print(f" Target: Buy {safe_amount} @ {safe_price} (Val: {order_val})")

# 4. Check Balance
print("2. Checking Funds...")
accounts = requests.get(f"{BASE_URL}/viewer/accounts", headers=get_headers()).json()
# Find Quote Asset (USDT)
quote_asset = pair_cfg['quote_asset']['symbol']
wallet = next((a for a in accounts if a['asset_symbol'] == quote_asset), None)

available = float(wallet['balance']) if wallet else 0.0
if available < order_val:
print(f" Insufficient funds: Have {available}, Need {order_val}")
return

# 5. Place Order (Post-Only)
print("3. Placing Order...")
payload = {
"asset_pair_name": PAIR,
"side": "BID",
"type": "LIMIT",
"price": safe_price, # String
"amount": safe_amount, # String
"post_only": True,
"client_order_id": f"bot-{int(time.time())}"
}

res = requests.post(f"{BASE_URL}/viewer/orders", json=payload, headers=get_headers())

if res.status_code != 200:
print(" Order Failed:", res.json())
return

order_id = res.json()['id']
print(f" Order {order_id} Placed. State: {res.json()['state']}")

# 6. Monitor Status
print("4. Monitoring (5s)...")
time.sleep(5)

status = requests.get(f"{BASE_URL}/viewer/orders/{order_id}", headers=get_headers()).json()
print(f" Current State: {status['state']}, Filled: {status['filled_amount']}")

# 7. Cancel if not filled
if status['state'] == "PENDING":
print(" Order still pending. Cancelling...")
cancel = requests.post(f"{BASE_URL}/viewer/orders/{order_id}/cancel", headers=get_headers())
print(" Result:", cancel.json()['state'])

if __name__ == "__main__":
run_bot()

Conclusion

You now have the tools to build a robust Spot trading application on BigONE. By respecting market precision rules and handling the order lifecycle correctly, you can avoid the most common integration pitfalls.

Key Takeaways:

  • Always fetch and cache asset_pair configurations to handle precision (base_scale, quote_scale).
  • Send numeric values as Strings to prevent floating-point errors.
  • Use post_only for market-making strategies to ensure you remain a liquidity provider.

Next Steps:

  • Explore the WebSocket API to receive real-time order updates instead of polling.
  • Review the API Reference for the full list of order types and error codes.