# 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())
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://hot-labs.gitbook.io/hex/concepts-and-systems/python-sdk.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
