Metadata-Version: 2.4
Name: IndodaxClient
Version: 2.0.4
Summary: Python client library for the Indodax New Trade API V2
Author: Sutarno Sarba
Author-email: anovanmaximuz@gmail.com
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Requires-Python: >=3.7
Description-Content-Type: text/markdown
Requires-Dist: requests>=2.25.1
Dynamic: author
Dynamic: author-email
Dynamic: classifier
Dynamic: description
Dynamic: description-content-type
Dynamic: requires-dist
Dynamic: requires-python
Dynamic: summary

# indodax_client

Python client library for the **Indodax - New Trade API V2**, generated from
the provided Postman collection. It reproduces the request-signing logic
from the collection's pre-request scripts (HMAC-SHA256 over a canonical,
`encodeURIComponent`-style query string) so you can call the API directly
from Python without re-implementing the signing by hand.

## Requirements

- Python 3.8+
- [`requests`](https://pypi.org/project/requests/)

```bash
pip install requests
```

## Installation

Just drop `indodax_client.py` into your project and import it — it's a
single, dependency-light file.

```python
from indodax_client import IndodaxClient
```

## Quick start

```python
from indodax_client import IndodaxClient

client = IndodaxClient(
    api_key="YOUR_API_KEY",
    api_secret="YOUR_API_SECRET",
    base_url="https://your-Indodax-host.example",  # the collection's {{IndodaxUrl}}
)

print(client.get_account())
print(client.get_orders(symbol="usdtidr", limit=10))

order = client.create_order_buy(symbol="usdtidr", quantity=3, price=18000)
print(order)
```

> **Note:** `base_url` is not defined in the Postman collection (it's the
> `{{IndodaxUrl}}` environment variable) — set it to your actual API host
> before using the client.

## How signing works

This mirrors the collection's pre-request scripts exactly:

1. Build a params dict, in a fixed order.
2. Append either:
   - `nonce` (current time in ms) — **default mode**, or
   - `timestamp` + `recvWindow`, if timestamp mode is used.
3. URL-encode every key/value the same way JavaScript's `encodeURIComponent`
   does, then join as `key1=val1&key2=val2&...`. This is the **canonical
   string**.
4. Compute `HMAC-SHA256(secret, canonical)` as a hex digest → sent as the
   `Sign` header.
5. For `GET` / `DELETE` requests, the canonical string becomes the URL query
   string. For `POST` requests, it becomes the raw
   `application/x-www-form-urlencoded` body.

Most endpoints send the key via the `X-APIKEY` header, but the withdrawal
endpoints (`withdraw_coin_username`, `withdraw_coin_address`) use `Key`
instead — the library already handles this per-endpoint.

## Dry-run mode: `prevent_execution`

Every `POST` and `DELETE` method accepts a `prevent_execution=True` keyword
argument. When set, the client still builds and signs the request exactly
as it normally would, but **never actually calls the endpoint**. Instead it
returns a JSON string describing the request that would have been sent:
method, path, full URL, headers (including the computed `Sign`), the params
dict, and the signed body/query string.

This is useful for debugging, unit testing, or previewing a request before
firing it for real.

```python
result = client.create_order_buy(
    symbol="usdtidr",
    quantity=3,
    price=18000,
    prevent_execution=True,
)
print(result)
```

```json
{
  "preventExecution": true,
  "message": "prevent execution with parameter to json from parameter",
  "method": "POST",
  "path": "/api/v2/order",
  "url": "https://your-Indodax-host.example/api/v2/order",
  "headers": {
    "X-APIKEY": "YOUR_API_KEY",
    "Sign": "...",
    "Content-Type": "application/x-www-form-urlencoded"
  },
  "params": {
    "symbol": "usdtidr",
    "side": "BUY",
    "quantity": 3,
    "quoteOrderQty": 0,
    "type": "LIMIT",
    "price": 18000,
    "newClientOrderId": "",
    "selfTradePreventionMode": "EXPIRE_MAKER",
    "timeInForce": ""
  },
  "body": "symbol=usdtidr&side=BUY&quantity=3&...&nonce=..."
}
```

The same works for `DELETE` requests (e.g. `cancel_order`), where the
returned JSON includes `"method": "DELETE"` and a `query` field instead of
`body`.

Methods that support `prevent_execution`:

- `create_order`
- `create_order_buy`
- `create_order_sell`
- `cancel_order`
- `withdraw_coin_username`
- `withdraw_coin_address`
- `withdraw_idr`

(`GET` methods always call the endpoint — there's nothing to "prevent".)

## API reference

### Account & market data

| Method | Endpoint | Description |
|---|---|---|
| `get_account()` | `GET /api/v2/account` | Account balances/info |
| `get_orders(symbol, start_time=None, end_time=None, limit=10, sort="asc")` | `GET /api/v2/order/histories` | Order history |
| `get_order_detail(symbol, order_id=None, orig_client_order_id=None)` | `GET /api/v2/order` | Single order detail |
| `get_pending_orders(symbol="")` | `GET /api/v2/openOrders` | Open/pending orders (empty symbol = all pairs) |
| `get_trade_history(symbol, order_id=None, client_order_id=None, start_time=None, end_time=None, limit=10, sort="desc")` | `GET /api/v2/myTrades` | Executed trade history |

### Orders

| Method | Endpoint | Description |
|---|---|---|
| `create_order(symbol, side, order_type, quantity=None, quote_order_qty=None, price=None, new_client_order_id="", self_trade_prevention_mode="", time_in_force="", prevent_execution=False)` | `POST /api/v2/order` | Generic order creation (`side`: `BUY`/`SELL`) |
| `create_order_buy(symbol, quantity=None, quote_order_qty=None, order_type="LIMIT", price=None, new_client_order_id="", self_trade_prevention_mode="EXPIRE_MAKER", time_in_force="", prevent_execution=False)` | `POST /api/v2/order` | Convenience wrapper for a BUY order |
| `create_order_sell(symbol, quantity=None, quote_order_qty=None, order_type="LIMIT", price=None, new_client_order_id="", self_trade_prevention_mode="", time_in_force="", prevent_execution=False)` | `POST /api/v2/order` | Convenience wrapper for a SELL order |
| `cancel_order(symbol, order_id=None, orig_client_order_id=None, prevent_execution=False)` | `DELETE /api/v2/order` | Cancel an order |

`order_type`: `"LIMIT"` or `"MARKET"`.
`quantity` is the coin amount (required for LIMIT); `quote_order_qty` is the
estimated quote-currency amount and is mutually exclusive with `quantity`
(MARKET BUY only).
`self_trade_prevention_mode`: `"EXPIRE_MAKER"` | `"EXPIRE_TAKER"` | `"EXPIRE_BOTH"`.
`time_in_force`: `"MOC"` | `"GTC"` (default).

### Withdrawals & deposits

| Method | Endpoint | Description |
|---|---|---|
| `withdraw_coin_username(coin, network, withdraw_username, amount, address_tag="", withdraw_order_id="", address="", prevent_execution=False)` | `POST /api/v2/capital/withdraw/apply` | Internal transfer to another user by username |
| `withdraw_coin_address(coin, network, address, amount, username="", address_tag="", withdraw_order_id="", prevent_execution=False)` | `POST /api/v2/capital/withdraw/apply` | On-chain withdrawal to an external address |
| `withdraw_idr(amount, account_number, bank_code, api_payment_method="bank_transfer", currency="idr", client_request_id="", prevent_execution=False)` | `POST /api/v2/fiat/withdraw` | Fiat (IDR) withdrawal |
| `get_fiat_history(transaction_type, begin_time, end_time)` | `GET /api/v2/fiat/orders` | Fiat deposit/withdraw history (`transaction_type`: `0`=deposit, `1`=withdraw) |
| `get_coin_deposit_history(coin="", start_time=None, end_time=None, limit=100, deposit_status="success")` | `GET /api/v2/capital/deposit/hisrec` | Coin deposit history |
| `get_coin_withdraw_history(coin="", start_time=None, end_time=None, limit=100, withdraw_status="")` | `GET /api/v2/capital/withdraw/history` | Coin withdrawal history |
| `get_deposit_address_list(coin, network="")` | `GET /api/v2/capital/deposit/address/list` | List deposit addresses for a coin/network |

Withdrawal endpoints send the API key via the `Key` header (not
`X-APIKEY`) — this is handled automatically.

## Error handling

Any non-2xx HTTP response raises `IndodaxAPIError`:

```python
from indodax_client import IndodaxClient, IndodaxAPIError

try:
    client.cancel_order(symbol="usdtidr", order_id=25184)
except IndodaxAPIError as e:
    print(e.status_code, e.payload)
```

## Examples

**Place a limit buy order:**
```python
client.create_order_buy(symbol="usdtidr", quantity=3, price=18000)
```

**Place a market sell order (by coin quantity):**
```python
client.create_order_sell(symbol="usdtidr", quantity=1.5, order_type="MARKET")
```

**Cancel an order:**
```python
client.cancel_order(symbol="usdtidr", order_id=25184)
```

**Withdraw USDT to an external address:**
```python
client.withdraw_coin_address(
    coin="usdt",
    network="bep20",
    address="0x7adcbb207f76d0fed23ea579e0daa76d8e71c5de",
    amount="5",
    withdraw_order_id="wd-usdt-3",
)
```

**Withdraw IDR to a bank account:**
```python
client.withdraw_idr(
    amount="100000",
    account_number="6042229896",
    bank_code="014",
)
```

**Preview a request without sending it:**
```python
preview = client.create_order_buy(
    symbol="usdtidr", quantity=3, price=18000, prevent_execution=True,
)
print(preview)
```

## Notes & caveats

- `base_url` must be set to the real API host — it isn't hardcoded since the
  Postman collection only references `{{IndodaxUrl}}` as an environment
  variable.
- The default time mode is **nonce** (matches `timeMode: 'nonce'` in the
  original scripts). Timestamp + `recvWindow` mode isn't currently exposed
  as a public option but the internal `_prepare`/`_build_canonical` methods
  support it if you need to extend the client.
- All requests are synchronous (built on `requests`). Wrap calls in your own
  retry/backoff logic if needed.
