> For the complete documentation index, see [llms.txt](https://hot-labs.gitbook.io/hex/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://hot-labs.gitbook.io/hex/concepts-and-systems/python-sdk.md).

# Python SDK

### Overview

HeX Orderbook provides:

* **Limit Orders** - Place orders at specific prices that wait in the orderbook
* **Market Orders** - Execute immediately at best available prices
* **Order Management** - Cancel orders, view pending balances
* **Intent-based Execution** - All operations go through NEAR Intents for atomic settlement

#### Key Components

| Component         | Description                                        |
| ----------------- | -------------------------------------------------- |
| `OrderbookClient` | Main client for interacting with HeX               |
| `OmniBalance`     | Intent manager for authentication and transactions |
| `orderbook.fi.tg` | Orderbook smart contract                           |

### Orderbook Architecture

#### Token Pairs

Each orderbook operates on a token pair:

* **Base Token** - The asset being traded (e.g., HOT token)
* **Quote Token** - The pricing currency (default: USDT)

```python
ob = OrderbookClient(
    near_intent,
    base=OmniToken.GNK,  # Base: GNK
    decimal_base=9,
    quote=OmniToken.USDT,  # Quote: USDT
    decimal_quote=6,
)
```

#### Order Sides

* **Ask (Sell)** - Selling base token for quote token
* **Bid (Buy)** - Buying base token with quote token

#### Price Format

Prices are stored in a special format accounting for decimal differences:

```
raw_price = float_price * (10^decimal_quote) / (10^decimal_base)
```

Use `get_price()` to convert human-readable prices:

```python
# $1.50 per HOT (base=9 decimals, quote=6 decimals)
raw_price = ob.get_price(1.50)
# Result: "0.0015" (1.50 * 10^6 / 10^9)
```

#### Orderbook Depth

```python
orderbook = await ob.get_orderbook(depth=10)
orderbook.print()
```

Output:

```
ASKS:↓ 9 6
$1.8 0.5
$1.7 1.2
$1.6 0.8
-----
$1.5 2.0
$1.4 1.5
$1.3 3.0
BIDS:↑ 9 6
```

* **Asks** - Sorted by price ascending (lowest first)
* **Bids** - Sorted by price descending (highest first)

### Fee Structure

#### Fee Components

HeX uses a referral-based fee system:

| Fee Type     | Who Pays     | Description                                      |
| ------------ | ------------ | ------------------------------------------------ |
| Protocol Fee | Order placer | Default fee for the orderbook                    |
| Referral Fee | Order placer | Custom fee for providing the frontend / the user |

#### Fee Configuration

```python
ob = OrderbookClient(
    near_intent,
    base=OmniToken.GNK,
    decimal_base=9,
    fee_collector="intents.fi.tg",  # Fee recipient
    fee=100,  # 100 basis points = 0.01%
)
```

#### Fee Calculation

Fees are specified in **basis points** (1 bp = 0.0001%):

| Basis Points | Percentage | Example (100 USDT trade) |
| ------------ | ---------- | ------------------------ |
| 1            | 0.0001%    | 0.0001 USDT              |
| 30           | 0.003%     | 0.003 USDT               |
| 10000        | 1%         | 1.00 USDT                |

#### Protocol Fees

Query protocol fees:

```python
fees = await ob.get_protocol_fee()

# Taker fee collectors
for fc in fees.taker_fee_collectors:
    print(f"Taker: {fc.collector} - {fc.fee} bp")

# Maker fee collectors  
for fc in fees.maker_fee_collectors:
    print(f"Maker: {fc.collector} - {fc.fee} bp")
```

#### Fee Flow

```
User Order
    │
    ├──► Maker Order ──► No fees
    │
    └──► Taker Order ──► Taker Fee ──► Fee Collectors
              │
              └──► Trade Execution
```

### Getting Started

#### Installation

```bash
pip install py-near
```

#### Basic Setup

```python
from py_near.omni_balance import OmniBalance
from py_near.dapps.hex import OrderbookClient
import asyncio

ACCOUNT_ID = "your-account.near"
PRIVATE_KEY = "ed25519:..."

async def main():
    async with OmniBalance(ACCOUNT_ID, PRIVATE_KEY) as omni:
        ob = OrderbookClient(
            omni,
            base=OmniToken.GNK,
            decimal_base=9,
        )
        
        # Your trading logic here
        orderbook = await ob.get_orderbook()
        orderbook.print()

asyncio.run(main())
```

### Trading Operations

#### 1. View Orderbook

```python
orderbook = await ob.get_orderbook(depth=20)
orderbook.print()

# Access raw data
for ask in orderbook.asks:
    price = float(ask.price) / 10**(ob.decimal_quote - ob.decimal_base)
    amount = int(ask.order_balance) / 10**ob.decimal_base
    print(f"Ask: ${price:.4f} - {amount:.4f}")
```

#### 2. Simulate Market Order

Always simulate before executing:

```python
amount = str(int(0.1 * 10**9))  # 0.1 GNK
result = await ob.simulate_market_order(
    amount,
    token_in=OmniToken.GNK,
    min_amount_out="0", # fill all availible orders
)

# Calculate expected output
receive = int(result.book_fill_result.taker_receive_total)
unspent = int(result.book_fill_result.taker_unspent)
spent = int(amount) - unspent

print(f"Spend: {spent / 10**9:.4f} GNK")
print(f"Receive: {receive / 10**6:.4f} USDT")
print(f"Price: ${receive / spent * 10**3:.4f}")
```

#### 3. Place Limit Order

```python
# Buy order: spend USDT to buy GNK at $1.40
tx = await ob.place_limit_order(
    price=ob.get_price(1.40),
    amount=str(int(10 * 10**6)),  # 10 USDT
    token_in=OmniToken.USDT,
)
print(f"Order placed: {tx}")
```

#### 4. Place Market Order

```python
# Sell 0.5 HOT at market price
tx = await ob.place_market_order(
    amount=str(int(0.5 * 10**9)),
    token_in=OmniToken.GNK,
    min_amount_out=str(int(0.5 * 10**6)),  # At least 0.5 USDT
)
print(f"Order executed: {tx}")
```

#### 5. Cancel Orders

```python
orders = await ob.get_orders(limit=50)

for order in orders:
    if order.order.maker == omni.account_id:
        tx = await ob.cancel_order(order.hash)
        print(f"Cancelled: {order.hash}")
```

#### 6. Claim Pending Balance

```python
pending = await ob.get_pending_balance()
if pending:
    print(f"Pending: {pending}")
    tx = await ob.claim_pending_balance()
    print(f"Claimed: {tx}")
```

See `examples/hex_grid_bot.py` for a complete working implementation.

{% embed url="<https://github.com/pvolnov/py-near/blob/master/examples/hex_grid_bot.py>" %}

### Best Practices

#### 1. Risk Management

```python
# Always set slippage protection
min_output = expected_output * Decimal("0.99")  # 1% slippage
await ob.place_market_order(
    amount=amount,
    token_in=token,
    min_amount_out=str(int(min_output)),
)
```

#### 2. Error Handling

```python
async def safe_place_order(ob, price, amount, token_in, max_retries=3):
    for attempt in range(max_retries):
        try:
            return await ob.place_limit_order(
                price=ob.get_price(price),
                amount=str(int(amount)),
                token_in=token_in,
            )
        except Exception as e:
            print(f"Attempt {attempt + 1} failed: {e}")
            if attempt < max_retries - 1:
                await asyncio.sleep(2 ** attempt)
    return None
```

#### 3. Simulate Before Execution

```python
# Always simulate market orders first
result = await ob.simulate_market_order(amount, token_in)

expected = int(result.book_fill_result.taker_receive_total)
if expected < min_acceptable:
    print("Price impact too high, skipping")
    return

# Execute only if simulation is acceptable
await ob.place_market_order(amount, token_in, min_amount_out=str(expected))
```

#### 4. Monitor Pending Balances

```python
async def claim_if_pending(ob, threshold=0):
    pending = await ob.get_pending_balance()
    if pending:
        for token, amount in pending.items():
            if int(amount) > threshold:
                await ob.claim_pending_balance()
                return True
    return False
```

#### 5. Graceful Shutdown

```python
import signal

class TradingBot:
    def __init__(self):
        self.running = True
        signal.signal(signal.SIGINT, self.shutdown)
        signal.signal(signal.SIGTERM, self.shutdown)
    
    def shutdown(self, signum, frame):
        print("Shutting down...")
        self.running = False
    
    async def run(self):
        while self.running:
            # Bot logic
            pass
        
        # Cleanup: cancel all orders
        await self.cancel_all_orders()
```

#### 6. Logging

```python
import logging

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(levelname)s] %(message)s",
    handlers=[
        logging.FileHandler("trading_bot.log"),
        logging.StreamHandler(),
    ],
)

logger = logging.getLogger(__name__)

# Usage
logger.info(f"Order placed: {tx_hash}")
logger.warning(f"High slippage detected: {slippage}%")
logger.error(f"Order failed: {error}")
```

### API Reference

#### OrderbookClient Methods

| Method                       | Description                  |
| ---------------------------- | ---------------------------- |
| `get_orderbook(depth)`       | Get orderbook with asks/bids |
| `get_price(float_price)`     | Convert price to raw format  |
| `simulate_market_order(...)` | Get quote for market order   |
| `place_limit_order(...)`     | Place limit order            |
| `place_market_order(...)`    | Execute market order         |
| `get_orders(skip, limit)`    | Get orders for pair          |
| `cancel_order(hash)`         | Cancel order by hash         |
| `get_protocol_fee()`         | Get fee configuration        |
| `get_pending_balance()`      | Get pending balance          |
| `claim_pending_balance()`    | Claim pending balance        |

#### Models

| Model                              | Description              |
| ---------------------------------- | ------------------------ |
| `RawOrderBookModel`                | Orderbook with asks/bids |
| `OrderEntryModel`                  | Single order entry       |
| `GetOrdersItemModel`               | Order from get\_orders   |
| `SimulateMarketOrderResponseModel` | Simulation result        |
| `OrderBookFeesModel`               | Fee configuration        |

### Troubleshooting

#### Common Issues

| Issue                   | Solution                                |
| ----------------------- | --------------------------------------- |
| "Insufficient balance"  | Deposit tokens via OmniBalance          |
| "Order not found"       | Order may have been filled or cancelled |
| "Price impact too high" | Reduce order size or use limit orders   |
| "Simulation failed"     | Check token IDs and amounts             |

#### Debugging

```python
# Enable debug logging
import logging
logging.getLogger("py_near").setLevel(logging.DEBUG)

# Print raw simulation result
result = await ob.simulate_market_order(...)
print(result.dict())
```
