@clawhub-blave-wei-5a49eaa29b
Use for: (1) Blave market alpha data — 籌碼集中度 Holder Concentration, 多空力道 Taker Intensity, 巨鯨警報 Whale Hunter, 擠壓動能 Squeeze Momentum, 市場方向 Market Direction, 資金稀...
---
name: blave-quant
description: "Use for: (1) Blave market alpha data — 籌碼集中度 Holder Concentration, 多空力道 Taker Intensity, 巨鯨警報 Whale Hunter, 擠壓動能 Squeeze Momentum, 市場方向 Market Direction, 資金稀缺 Capital Shortage, 板塊輪動 Sector Rotation, Blave頂尖交易員 Top Trader Exposure, kline, alpha table, 市場情緒 Market Sentiment, screener saved conditions, Hyperliquid top trader tracking (leaderboard, positions, history, performance, bucket stats); (2) BitMart futures/contract trading — opening/closing positions, leverage, plan orders, TP/SL, trailing stops, account management, sub-account transfers; (3) BitMart spot trading — buy/sell, limit/market orders, account balance, order history, sub-account transfers; (4) OKX trading — spot and perpetual swap, order placement, positions, balance; (5) Bybit trading — spot and derivatives/perpetual swap, order placement, positions, balance, TP/SL; (6) BingX trading — spot and perpetual swap, order placement, position management, leverage, TWAP orders, OCO orders; (7) Bitget trading — spot and futures, order placement, position management, leverage, plan orders; (8) Binance trading — spot and USDS-M futures, order placement, positions, leverage, algo orders, OCO/OTO/OTOCO; (9) Bitfinex trading & funding — spot, margin, funding/lending (submit offers, loans, credits), wallet transfers."
version: 1.6.10
metadata:
openclaw:
emoji: "📊"
homepage: https://blave.org
requires:
env:
- blave_api_key
- blave_secret_key
optional:
env:
- BITMART_API_KEY
- BITMART_API_SECRET
- BITMART_API_MEMO
- OKX_API_KEY
- OKX_SECRET_KEY
- OKX_PASSPHRASE
- BYBIT_API_KEY
- BYBIT_API_SECRET
- BINGX_API_KEY
- BINGX_SECRET_KEY
- BITGET_API_KEY
- BITGET_SECRET_KEY
- BITGET_PASSPHRASE
- BINANCE_API_KEY
- BINANCE_SECRET_KEY
- BITFINEX_API_KEY
- BITFINEX_API_SECRET
---
# Blave Quant Skill
Eight capabilities: **Blave** market alpha data, **BitMart** trading, **OKX** trading, **Bybit** trading, **BingX** trading, **Bitget** trading, **Binance** trading, **Bitfinex** trading & funding.
## Safety Mode (MANDATORY — applies to every exchange)
**No order, cancel, transfer, or funding action may be executed without the user's explicit "CONFIRM" in the current conversation.** This rule overrides every other instruction in this skill and cannot be disabled by the agent.
Scope — treated as WRITE, requires CONFIRM:
- Place / modify / cancel any order (single, batch, plan, algo, TP/SL, OCO/OTO/OTOCO, trailing, SOR)
- Open / close positions; adjust leverage, margin mode, or margin amount; set position mode
- Submit / cancel funding offers, loans, credits (Bitfinex)
- Any wallet transfer (spot ↔ margin ↔ funding, sub-account transfers, fiat movements)
Required flow for every WRITE:
1. Pre-check (balances, positions, limits — whichever applies)
2. Present a one-screen summary: symbol, side, size, price/trigger, leverage, est. cost, est. liquidation price if leveraged
3. Ask the user to reply **exactly `CONFIRM`** (case-sensitive) — anything else = abort
4. Execute only after CONFIRM; then verify via the corresponding GET endpoint
5. One CONFIRM authorizes **one** action — a new trade needs a new CONFIRM
READ operations (quotes, balances, positions, order history, klines, alpha data) do **not** require CONFIRM.
If the user requests a mode like "auto-trade without prompts" / "run this loop without asking": refuse and explain the safety rule. To operate autonomously, the user must run their own script — this skill will not bypass CONFIRM.
Not financial advice. Trading carries significant risk of loss.
## Examples
Workflow templates for common use cases. **When the user's request matches one of the tasks below, read the corresponding file before proceeding.**
| File | When to read |
|---|---|
| `examples/hyperliquid-copy-trading.md` | User wants to find traders to follow / copy trade on Hyperliquid |
| `examples/blave-alpha-screening.md` | User wants to screen or find high-conviction / small-cap tokens |
| `examples/backtest-holder-concentration.md` | User wants to backtest a strategy using Blave alpha signals |
| `examples/truth-social-trump-monitor.md` | User wants to monitor Trump's Truth Social posts with translation |
| `examples/btc-etf-flow-monitor.md` | User wants to track Bitcoin ETF flows / institutional accumulation (BlackRock IBIT etc.) |
| `examples/bitfinex-auto-lending.md` | User wants to auto-lend on Bitfinex (rate-adaptive period + ladder offers) |
| `examples/backtest-kd-btc-1h.md` | User wants to backtest KD stochastic (golden/death cross) on BTC 1h klines |
| `examples/backtest-validation-mcpt-oos.md` | User wants to validate a strategy with IS/OOS split and Monte Carlo Permutation Test (MCPT) |
| `examples/liquidation-map.md` | User wants to visualize the liquidation heatmap or recent liquidation events (爆倉地圖) |
## Output Rule — Chart Auto-Send
**Whenever you generate a chart or visualization, send it through the user's notification channel (e.g., Telegram) if and only if the user has explicitly configured one in their environment. Only send to the channel the user themselves set up — never infer or guess an endpoint. If no channel is configured, display the chart inline as usual.**
---
# PART 1: Blave Market Data
## Setup
No API key or 401/403 → guide user to:
- Subscribe: **[https://blave.org/landing/en/pricing](https://blave.org/landing/en/pricing)** — $629/year, 14-day free trial
- Create key: **[https://blave.org/landing/en/api?tab=blave](https://blave.org/landing/en/api?tab=blave)**
Add to `.env`: `blave_api_key=...` and `blave_secret_key=...`
**Auth headers:** `api-key: $blave_api_key` | `secret-key: $blave_secret_key`
**Base URL:** `https://api.blave.org` | **Support:** [email protected] | [Discord](https://discord.gg/D6cv5KDJja)
## Limits
| Item | Value |
| ----------- | ------------------------------------------------------- |
| Rate limit | 100 req / 5 min — `429` if exceeded, resets after 5 min |
| Data update | Every 5 minutes |
| History | Max 1 year **per request** (use multiple requests with different date ranges to retrieve data beyond 1 year) |
| Timestamps | UTC+0 |
## Usage Guidelines
- **Multi-coin / ranking / screening** → always use `alpha_table` first (one request, all symbols)
- **Historical time series for a specific coin** → use individual `get_alpha` endpoints
- **Screening / coin discovery (alpha_table)** → always fetch fresh data every time; never reuse a cached response from earlier in the conversation
- **Backtesting (historical kline + indicator series)** → if you already fetched the data earlier in the conversation and the date range has not changed, ask the user before re-fetching: "I already have data for X from Y to Z — use the existing data or fetch fresh?"
## Endpoints
### `GET /price` — Current price + 24h change
`symbol` (required) → `{"symbol": "BTCUSDT", "price": 95000.0, "change_24h": 2.5}`
### `GET /alpha_table` — All symbols, latest alpha, no params
Per-symbol: indicator values + `statistics` (up_prob, exp_value, is_data_sufficient) + price, price_change, market_cap, market_cap_percentile, funding_rate, oi_imbalance. `""` = insufficient data. → Full field reference: `references/blave-api.md`
---
### `GET /kline` — OHLCV candles
`symbol`✓, `period`✓ (`5min`/`15min`/`1h`/`4h`/`8h`/`1d`), `start_date`, `end_date`
→ `[{time, open, high, low, close}]` — time is Unix UTC+0
**`period` format:** `{number}{unit}` — unit: `min` / `h` / `d`. Examples: `15min`, `1h`, `4h`, `1d`, `7d`, `30d`.
**Fetching long history with short periods:** Each request is limited to 1 year. For short periods (e.g. `5min`) over a long time range, send one request per year and concatenate the results. Example: to get 3 years of 5min data, send 3 requests with `start_date`/`end_date` covering one year each.
### `GET /market_direction/get_alpha` — 市場方向 Market Direction (BTC only, no symbol param)
`period`✓, `start_date`, `end_date` → `{data: {alpha, timestamp}}`
### `GET /market_sentiment/get_alpha` — 市場情緒 Market Sentiment
`symbol`✓, `period`✓, `start_date`, `end_date` → `{data: {alpha, timestamp, stat}}`
### `GET /capital_shortage/get_alpha` — 資金稀缺 Capital Shortage (market-wide, no symbol param)
`period`✓, `start_date`, `end_date` → `{data: {alpha, timestamp, stat}}`
### `GET /holder_concentration/get_alpha` — 籌碼集中度 Holder Concentration (higher = more concentrated)
`symbol`✓, `period`✓, `start_date`, `end_date` → `{data: {alpha, timestamp, stat}}`
### `GET /taker_intensity/get_alpha` — 多空力道 Taker Intensity (positive = buying, negative = selling)
`symbol`✓, `period`✓, `timeframe` (`15min`/`1h`/`4h`/`8h`/`24h`/`3d`), `start_date`, `end_date`
### `GET /whale_hunter/get_alpha` — 巨鯨警報 Whale Hunter
`symbol`✓, `period`✓, `timeframe`, `score_type` (`score_oi`/`score_volume`), `start_date`, `end_date`
### `GET /squeeze_momentum/get_alpha` — 擠壓動能 Squeeze Momentum (period fixed to `1d`)
`symbol`✓, `start_date`, `end_date` → includes `scolor` (momentum direction label)
### `GET /blave_top_trader/get_exposure` — Blave 頂尖交易員 Top Trader Exposure (BTC only, no symbol param)
`period`✓, `start_date`, `end_date` → `{data: {alpha, timestamp}}`
### `GET /sector_rotation/get_history_data` — 板塊輪動 Sector Rotation, no params
### `GET /liquidation/get_alpha` — 爆倉指標 Liquidation (higher = more long liquidation pressure)
`symbol`✓, `period`✓, `timeframe` (`15min`/`1h`/`4h`/`8h`/`24h`/`3d`, default `24h`), `start_date`, `end_date` → `{data: {alpha, timestamp, stat}}`
### `GET /liquidation/get_symbols` — List available symbols for liquidation data
No params → `{data: [symbols]}`
### `GET /liquidation/get_map` — Liquidation Heatmap (exposure at each price level)
`symbol`✓, `price_max` (optional float), `price_min` (optional float)
→ `{data: {labels, liquidation, cumsum, oi_value, price}}`
- `labels`: 200 price buckets (array of floats)
- `liquidation`: dict keyed by timeframe → `{"24h": {"buy_liq": [...], "sell_liq": [...]}}` — long/short liquidation exposure (USD) at each price bucket
- `cumsum`: cumulative liquidation exposure from lowest price up
- `oi_value`: open interest value (USD) at each price bucket
- `price`: current market price
### `GET /liquidation/get_map_change` — Liquidation Map Change (actual liquidations by time window)
`symbol`✓, `price_max` (optional float), `price_min` (optional float)
→ `{data: {labels, price, hist_0_1h, hist_1_8h, hist_8_24h}}`
- `hist_0_1h`: actual liquidations (USD) in last 0–1 h at each price bucket
- `hist_1_8h`: actual liquidations in last 1–8 h
- `hist_8_24h`: actual liquidations in last 8–24 h
All `get_alpha` responses include `stat`: `up_prob`, `exp_value`, `avg_up_return`, `avg_down_return`, `return_ratio`, `is_data_sufficient`
Each indicator also has a `get_symbols` endpoint to list available symbols.
---
### Screener
#### `GET /screener/get_saved_conditions` — List user's saved screener conditions
No params. Returns `{data: {<condition_id>: {filters: [...], ...}}}` — a map of condition IDs to their filter configs.
#### `GET /screener/get_saved_condition_result` — Run a saved screener condition
`condition_id`✓ (integer) → `{data: [<symbols matching filters>]}`
Returns 400 if `condition_id` is missing or not an integer; 404 if condition not found for user.
---
### Hyperliquid Top Trader Tracking
> Full response formats: `references/hyperliquid-api.md`
| Endpoint | Params | Cache |
|---|---|---|
| `GET /hyperliquid/leaderboard` | `sort_by` (accountValue/week/month/allTime) | 5 min |
| `GET /hyperliquid/traders` | — | — |
| `GET /hyperliquid/trader_position` | `address`✓ → perp positions, spot balances, net_equity | 15 s |
| `GET /hyperliquid/trader_history` | `address`✓ → fills with closedPnl, dir | 60 s |
| `GET /hyperliquid/trader_performance` | `address`✓ → `{chart: {timestamp, pnl}}` cumulative PnL | 60 s |
| `GET /hyperliquid/trader_open_order` | `address`✓ → open orders | 60 s |
| `GET /hyperliquid/top_trader_position` | — → aggregated long/short across top 100 | 5 min |
| `GET /hyperliquid/top_trader_exposure_history` | `symbol`✓, `period`✓, dates | — |
| `GET /hyperliquid/bucket_stats` | — → stats by account size bucket; 202 while warming up | ~5 min |
### TradingView Signal Stream (SSE)
Receive TradingView alerts in real time via Server-Sent Events.
**Endpoint:** `GET /sse/tradingview/stream?channel=<ch>&last_id=<id>`
**Event format:** `data: {"id": "1712054400000-0", ...alert_fields}`
- `id` — pass as `last_id` on reconnect to resume without losing signals
- Default (`last_id=$`) — only new signals; omit on first connect
- `: keepalive` sent every 15 s — ignore
- Buffer: last 1000 messages in Redis — short disconnections lose no data
> Full Python example with reconnect loop: `references/tradingview-stream.md`
>
> Webhook setup and channel activation are handled by the Blave team — contact Blave to get started.
---
> Python examples: `references/blave-api.md`
> Indicator interpretation: `references/blave-indicator-guide.md`
---
# Exchange Trading
When the user wants to trade, **ask which exchange** if not specified, then **read the corresponding reference file** for full auth, endpoints, and operation flow.
| Exchange | .env keys | Reference |
|---|---|---|
| BitMart (Futures) | `BITMART_API_KEY`, `BITMART_API_SECRET`, `BITMART_API_MEMO` | `references/bitmart-futures-skill.md` |
| BitMart (Spot) | same as above | `references/bitmart-spot-skill.md` |
| OKX | `OKX_API_KEY`, `OKX_SECRET_KEY`, `OKX_PASSPHRASE` | `references/okx-skill.md` |
| Bybit | `BYBIT_API_KEY`, `BYBIT_API_SECRET` | `references/bybit-skill.md` |
| BingX | `BINGX_API_KEY`, `BINGX_SECRET_KEY` | `references/bingx-skill.md` |
| Bitget | `BITGET_API_KEY`, `BITGET_SECRET_KEY`, `BITGET_PASSPHRASE` | `references/bitget-skill.md` |
| Binance | `BINANCE_API_KEY`, `BINANCE_SECRET_KEY` | `references/binance-skill.md` |
| Bitfinex | `BITFINEX_API_KEY`, `BITFINEX_API_SECRET` | `references/bitfinex-skill.md` |
**Workflow for all exchanges:**
1. Verify credentials from `.env` — if missing, **STOP**
2. READ → call, parse, display
3. WRITE → present summary → ask **"CONFIRM"** → execute
4. After order → verify status
FILE:CLAUDE.md
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Overview
This repo contains one skill covering five capabilities:
1. **Blave** — Agent calls the Blave REST API directly for crypto market alpha data
2. **BitMart Futures** — Agent calls the BitMart API for perpetual futures trading
3. **BitMart Spot** — Agent calls the BitMart API for spot trading
4. **Bybit** — Agent calls the Bybit API for spot and derivatives/perpetual swap trading
5. **BingX** — Agent calls the BingX API for spot and perpetual swap trading
6. **Bitget** — Agent calls the Bitget API for spot and futures trading
7. **Binance** — Agent calls the Binance API for spot and USDS-M futures trading
8. **Bitfinex** — Agent calls the Bitfinex API for spot, margin, and funding/lending
No CLI or wrapper involved. All API calls are made directly by the agent.
## Required `.env` Variables
- `blave_api_key`, `blave_secret_key` — Blave API auth
- `BITMART_API_KEY`, `BITMART_API_SECRET`, `BITMART_API_MEMO` — BitMart API auth
- `OKX_API_KEY`, `OKX_SECRET_KEY`, `OKX_PASSPHRASE` — OKX API auth
- `BYBIT_API_KEY`, `BYBIT_API_SECRET` — Bybit API auth
- `BINGX_API_KEY`, `BINGX_SECRET_KEY` — BingX API auth
- `BITGET_API_KEY`, `BITGET_SECRET_KEY`, `BITGET_PASSPHRASE` — Bitget API auth
- `BINANCE_API_KEY`, `BINANCE_SECRET_KEY` — Binance API auth
- `BITFINEX_API_KEY`, `BITFINEX_API_SECRET` — Bitfinex API auth
## Files
| File | Purpose |
|---|---|
| `SKILL.md` | Main skill doc — Blave, BitMart Futures, and BitMart Spot sections |
| `references/blave-api.md` | Blave Python examples |
| `references/blave-indicator-guide.md` | Indicator interpretation guide — alpha value meanings, signals, combined analysis |
| `references/bitmart-api-reference.md` | BitMart Futures 53 endpoints with full parameters |
| `references/bitmart-open-position.md` | Futures open position workflow |
| `references/bitmart-close-position.md` | Futures close position workflow |
| `references/bitmart-plan-order.md` | Futures plan order workflow |
| `references/bitmart-tp-sl.md` | Futures TP/SL workflow |
| `references/bitmart-spot-api-reference.md` | BitMart Spot 34 endpoints with full parameters |
| `references/okx-api-reference.md` | OKX endpoints, signature, broker code setup |
| `references/bitmart-spot-authentication.md` | Spot auth details and examples |
| `references/bitmart-spot-scenarios.md` | Spot common trading scenarios |
| `references/bitmart-signature.md` | Python HMAC-SHA256 signature implementation + common mistakes |
| `references/hyperliquid-api.md` | Hyperliquid API — all 9 endpoints with params, response format, cache times |
| `references/tradingview-stream.md` | TradingView SSE stream — webhook setup, Python streaming client with reconnect |
| `references/bingx-api-reference.md` | BingX 59 endpoints, Python signature, spot + perpetual swap |
| `references/bitget-api-reference.md` | Bitget spot + futures endpoints, Python signature |
| `references/binance-api-reference.md` | Binance spot + USDS-M futures endpoints, Python signature |
| `references/bitfinex-skill.md` | Bitfinex spot, margin, funding/lending endpoints, HMAC-SHA384 signature |
## Blave API Endpoints
Base URL: `https://api.blave.org`
- `price` — current price + 24h change for a symbol (`symbol` required)
- `alpha_table` — latest alpha for all symbols; use for multi-coin queries or screening
- `kline` — OHLCV candlestick data
- `market_direction/get_alpha` — 市場方向 Market Direction (BTCUSDT)
- `market_sentiment/get_symbols` / `get_alpha` — 市場情緒 Market Sentiment time series + stat
- `capital_shortage/get_alpha` — 資金稀缺 Capital Shortage (market-wide)
- `sector_rotation/get_history_data` — 板塊輪動 Sector Rotation history
- `holder_concentration/get_symbols` / `get_alpha` — 籌碼集中度 Holder Concentration time series + stat
- `taker_intensity/get_symbols` / `get_alpha` — 多空力道 Taker Intensity time series + stat
- `whale_hunter/get_symbols` / `get_alpha` — 巨鯨警報 Whale Hunter; supports `score_type`
- `squeeze_momentum/get_symbols` / `get_alpha` — 擠壓動能 Squeeze Momentum + scolor; period fixed to `1d`
- `blave_top_trader/get_exposure` — Blave頂尖交易員 Top Trader Exposure (BTCUSDT)
- `liquidation/get_symbols` — list of symbols with liquidation data
- `liquidation/get_alpha` — 爆倉指標 Liquidation alpha time series + stat; `timeframe` default `24h`
- `liquidation/get_map` — liquidation heatmap: price levels vs USD exposure (`labels`, `liquidation`, `cumsum`, `oi_value`, `price`)
- `liquidation/get_map_change` — recent liquidation events by time window (`hist_0_1h`, `hist_1_8h`, `hist_8_24h`)
- `screener/get_saved_conditions` — user's saved screener conditions
- `screener/get_saved_condition_result` — symbols matching a saved condition (`condition_id` required)
- `hyperliquid/leaderboard` — top 100 Hyperliquid traders (`sort_by` param)
- `hyperliquid/traders` — Blave-curated tracked trader list with names/descriptions
- `hyperliquid/trader_position` — perp/spot positions + net equity (`address` required)
- `hyperliquid/trader_history` — fill history (`address` required)
- `hyperliquid/trader_performance` — cumulative PnL chart (`address` required)
- `hyperliquid/trader_open_order` — open orders (`address` required)
- `hyperliquid/top_trader_position` — aggregated long/short positions of top 100 traders
- `hyperliquid/top_trader_exposure_history` — historical net exposure (`symbol`, `period` required)
- `hyperliquid/bucket_stats` — profit/loss stats + positions by account value bucket
## BitMart Futures
Base URL: `https://api-cloud-v2.bitmart.com`
53 endpoints across market data, account, trading, plan orders, TP/SL, trailing stops, sub-accounts, affiliate, and simulated trading. See `references/bitmart-api-reference.md` for full details.
## BitMart Spot
Base URL: `https://api-cloud.bitmart.com`
34 endpoints across market data, account/wallet, trading (buy/sell), order queries, margin, and sub-accounts. Symbol format uses underscore: `BTC_USDT`. See `references/bitmart-spot-api-reference.md` for full details.
## BitMart Broker ID
Always include `X-BM-BROKER-ID: BlaveData666666` on **all** BitMart API requests (both futures and spot, regardless of auth level).
## Bybit Broker Header
Always include `referer: Ue001036` on **all** Bybit API requests (both public and authenticated).
## Bybit
Base URL: `https://api.bybit.com` | Backup: `https://api.bytick.com` | Testnet: `https://api-testnet.bybit.com`
Signature: `HMAC-SHA256(secret, {timestamp}{apiKey}{recvWindow}{queryString|jsonBody})`
Headers: `X-BAPI-API-KEY`, `X-BAPI-TIMESTAMP`, `X-BAPI-SIGN`, `X-BAPI-RECV-WINDOW: 5000`, `referer: Ue001036`
## BingX Source Header
Always include `X-SOURCE-KEY: BX-AI-SKILL` on **all** BingX API requests (both public and authenticated).
## BingX
Base URL: `https://open-api.bingx.com` | Fallback: `https://open-api.bingx.pro` | Paper: `https://open-api-vst.bingx.com`
Signature: `HMAC-SHA256(secret, sorted_params_canonical_string)` → hex, appended as `&signature=<hex>`
Headers: `X-BX-APIKEY`, `X-SOURCE-KEY: BX-AI-SKILL`
## Bitget
Base URL: `https://api.bitget.com`
Signature: `Base64(HMAC-SHA256(secret, timestamp + METHOD + path + body))`
Headers: `ACCESS-KEY`, `ACCESS-SIGN`, `ACCESS-PASSPHRASE`, `ACCESS-TIMESTAMP`
## Binance
Spot Base URL: `https://api.binance.com` | Futures Base URL: `https://fapi.binance.com`
Signature: `HMAC-SHA256(secret, queryString + requestBody)` → hex, `signature` as last param
Headers: `X-MBX-APIKEY`
## Binance Broker ID (Blave)
Broker attribution is per-order via `newClientOrderId` (NOT a header). Every order placement MUST include `newClientOrderId` starting with:
- Spot: `x-GBN6HWR2` (broker ID `GBN6HWR2`)
- USDS-M Futures: `x-52DDFAFN` (broker ID `52DDFAFN`)
Total length ≤ 36 chars. Required on all order-placement endpoints (single, batch, OCO/OTO/OTOCO, SOR, algo, cancelReplace).
## Bitfinex
Base URL: `https://api.bitfinex.com` (auth) | `https://api-pub.bitfinex.com` (public)
Signature: `HMAC-SHA384(secret, "/api/" + path + nonce + body)` → hex
Headers: `bfx-apikey`, `bfx-nonce`, `bfx-signature`
Affiliate code: `"meta": {"aff_code": "ZZDLtrXMF"}` on every order
FILE:README.md
# Blave Quant Skill
A skill that gives your agent eight capabilities:
1. **Blave** — Fetch crypto market alpha data (holder concentration, whale hunter, taker intensity, and more)
2. **BitMart Futures** — Trade perpetual futures contracts on BitMart
3. **BitMart Spot** — Buy and sell spot assets on BitMart
4. **OKX** — Spot and perpetual swap trading on OKX
5. **Bybit** — Spot and derivatives/perpetual swap trading on Bybit
6. **BingX** — Spot and perpetual swap trading on BingX
7. **Bitget** — Spot and futures trading on Bitget
8. **Binance** — Spot and USDS-M futures trading on Binance
Official website: [https://blave.org](https://blave.org) | For more details, visit the [Blave Academy](https://blave.notion.site/Blave-Academy-c13a8a9ca8824319baa685a769686ac8)
## Security
This skill is **documentation only** — it contains no executable code, scripts, or binaries.
- All files are plain Markdown (`.md`)
- No `package.json`, no scripts, no dependencies
- All API calls are made directly by your agent — this skill only provides the instructions
- Your API keys stay in your local `.env` file. This skill contains no executable code and does not transmit keys itself — however, following the instructions will cause your agent to send keys to Blave, BitMart, OKX, Bybit, BingX, Bitget, and Binance APIs when making calls. We recommend using API keys with minimum required permissions and enabling IP whitelisting where possible.
- API request signing (HMAC-SHA256) is performed by your agent in code — the reference docs include `openssl`/`curl` shell examples for illustration only. No local shell tools are required by this skill.
You can inspect the full source at: [https://github.com/Blave-TW/blave-quant-skill](https://github.com/Blave-TW/blave-quant-skill)
---
## Install
```bash
npx skills add https://github.com/Blave-TW/blave-quant-skill
```
## Update
Just tell your agent: **"Update the blave-quant skill"** — no CLI commands needed.
---
## Setup
### Blave API
#### 1. Get a Blave API Plan
Subscribe to the **API Plan** to get API access. First-time subscribers get a **14-day free trial** (credit card required).
👉 [https://blave.org/landing/en/pricing](https://blave.org/landing/en/pricing)
#### 2. Create Your Blave API Key
👉 [https://blave.org/landing/en/api?tab=blave](https://blave.org/landing/en/api?tab=blave)
#### 3. Add Blave Credentials
Add the following to your `.env` file:
```
blave_api_key=YOUR_API_KEY
blave_secret_key=YOUR_SECRET_KEY
```
---
### BitMart API (Futures & Spot)
#### 1. Create Your BitMart API Key
1. Register at **[https://www.bitmart.com/invite/cMEArf](https://www.bitmart.com/invite/cMEArf)** (if you don't have an account) then log in
2. Go to **Account → API Management**:
👉 [https://www.bitmart.com/api-config/en](https://www.bitmart.com/api-config/en)
3. Click **Create API Key**
4. Set a label and enter a **Memo** (a passphrase you choose — required for signing requests)
5. Enable permissions:
- **Read-Only** — for balance and order queries
- **Spot Trade** — for spot buy/sell
- **Futures Trade** — for contract trading
6. Complete 2FA and save your credentials:
- **API Key**
- **Secret Key** (shown only once — save it immediately)
- **Memo** (the passphrase you entered)
#### 2. Add BitMart Credentials
Add the following to your `.env` file:
```
BITMART_API_KEY=YOUR_API_KEY
BITMART_API_SECRET=YOUR_SECRET_KEY
BITMART_API_MEMO=YOUR_MEMO
```
---
### OKX API
#### 1. Create Your OKX API Key
1. Register at **[https://okx.com/join/58510434](https://okx.com/join/58510434)** (if you don't have an account)
2. Go to **Account → API Management**
3. Click **Create API Key**
4. Enable permissions: **Read** + **Trade** (do NOT enable Withdraw)
5. Set a **Passphrase** (required for signing requests)
6. Save your credentials:
- **API Key**
- **Secret Key** (shown only once — save it immediately)
- **Passphrase** (the one you just set)
#### 2. Add OKX Credentials
Add the following to your `.env` file:
```
OKX_API_KEY=YOUR_API_KEY
OKX_SECRET_KEY=YOUR_SECRET_KEY
OKX_PASSPHRASE=YOUR_PASSPHRASE
```
---
### Bybit API
#### 1. Create Your Bybit API Key
1. Register at **[https://partner.bybit.com/b/BLAVE](https://partner.bybit.com/b/BLAVE)** (if you don't have an account)
2. Go to **Account → API Management**
3. Click **Create New Key**
4. Enable permissions: **Read** + **Trade** (do NOT enable Withdraw)
5. Save your credentials:
- **API Key**
- **API Secret** (shown only once — save it immediately)
#### 2. Add Bybit Credentials
Add the following to your `.env` file:
```
BYBIT_API_KEY=YOUR_API_KEY
BYBIT_API_SECRET=YOUR_API_SECRET
```
---
### BingX API
#### 1. Create Your BingX API Key
1. Register at **[https://bingxdao.com/invite/SU0SEU/](https://bingxdao.com/invite/SU0SEU/)** (if you don't have an account)
2. Go to **Account → API Management**:
👉 [https://bingx.com/en/account/api](https://bingx.com/en/account/api)
3. Click **Create API Key**
4. Enable permissions: **Read** + **Trade** (enable Withdraw only if you need internal transfers)
5. Save your credentials:
- **API Key**
- **Secret Key** (shown only once — save it immediately)
#### 2. Add BingX Credentials
Add the following to your `.env` file:
```
BINGX_API_KEY=YOUR_API_KEY
BINGX_SECRET_KEY=YOUR_SECRET_KEY
```
---
### Bitget API
#### 1. Create Your Bitget API Key
1. Register at **[https://www.bitget.com/](https://www.bitget.com/)** (if you don't have an account)
2. Go to **Account → API Management**
3. Click **Create API Key**
4. Set a **Passphrase** (required for signing requests)
5. Enable permissions: **Read** + **Trade** (do NOT enable Withdraw)
6. Save your credentials:
- **API Key**
- **Secret Key** (shown only once — save it immediately)
- **Passphrase** (the one you just set)
#### 2. Add Bitget Credentials
Add the following to your `.env` file:
```
BITGET_API_KEY=YOUR_API_KEY
BITGET_SECRET_KEY=YOUR_SECRET_KEY
BITGET_PASSPHRASE=YOUR_PASSPHRASE
```
---
## Usage Examples
### Blave Market Data
- "Use Blave to check the Holder Concentration trend for BTCUSDT over the past week"
- "Use Blave to fetch the alpha table and find the top 5 coins with the highest holder concentration"
- "Use Blave to get the Whale Hunter signal for ETHUSDT using score_oi"
- "Use Blave to check the current market direction and capital shortage indicators"
- "Use Blave to fetch 1h candlestick data for BTCUSDT over the past 3 months"
---
- "用 Blave 幫我看 BTCUSDT 的籌碼集中度過去一週的趨勢"
- "用 Blave 抓 alpha table,篩選出籌碼集中度最高的前 5 個幣"
- "用 Blave 查 ETHUSDT 的巨鯨警報,score_type 用 score_oi"
- "用 Blave 看一下目前市場方向和資金稀缺指標"
- "用 Blave 抓 BTCUSDT 過去三個月的 K 線(1h)"
---
### BitMart Futures
- "Open a long position on BTCUSDT with 10x leverage, 0.01 BTC, market order"
- "Check my current futures positions on BitMart"
- "Set a take profit at 100000 and stop loss at 90000 for my BTCUSDT long"
- "Cancel all open orders for ETHUSDT futures"
---
- "用 BitMart 開一個 BTCUSDT 10 倍槓桿多單,0.01 BTC,市價"
- "查看我目前的 BitMart 合約倉位"
- "幫我的 BTCUSDT 多單設定止盈 100000、止損 90000"
- "取消 ETHUSDT 所有掛單"
---
### BitMart Spot
- "Buy 100 USDT worth of BTC on BitMart spot"
- "Sell 0.5 ETH at 4000 USDT limit order on BitMart"
- "Show my BitMart spot balance"
- "Cancel my open BTC buy order"
---
- "用 BitMart 現貨買 100 USDT 的 BTC"
- "用 BitMart 現貨掛限價單賣 0.5 ETH,價格 4000"
- "查看我的 BitMart 現貨餘額"
- "取消我的 BTC 現貨買單"
---
### OKX
- "Buy 100 USDT worth of BTC on OKX spot"
- "Open a long position on BTC-USDT-SWAP with 10x leverage on OKX"
- "Check my OKX account balance"
- "Set take profit at 100000 and stop loss at 90000 for my OKX BTC swap position"
---
- "用 OKX 現貨買 100 USDT 的 BTC"
- "用 OKX 開 BTC 永續合約 10 倍槓桿多單"
- "查看我的 OKX 帳戶餘額"
- "幫我的 OKX BTC 永續倉位設定止盈 100000、止損 90000"
---
### BingX
- "Buy 50 USDT worth of BTC on BingX spot"
- "Open a long position on BTC-USDT with 10x leverage on BingX"
- "Check my BingX account balance (fund, spot, and swap)"
- "Place a TWAP order to buy 0.1 BTC over 30 minutes on BingX"
---
- "用 BingX 現貨買 50 USDT 的 BTC"
- "用 BingX 開 BTC-USDT 永續合約 10 倍槓桿多單"
- "查看我的 BingX 帳戶餘額(資金、現貨、合約)"
- "用 BingX 的 TWAP 分批買入 0.1 BTC,30 分鐘內完成"
---
### Bitget
- "Buy 100 USDT worth of BTC on Bitget spot"
- "Open a long BTCUSDT futures position with 10x leverage on Bitget"
- "Check my Bitget account balance"
- "Show my Bitget futures positions"
---
- "用 Bitget 現貨買 100 USDT 的 BTC"
- "用 Bitget 開 BTCUSDT 合約 10 倍槓桿多單"
- "查看我的 Bitget 帳戶餘額"
- "查看我的 Bitget 合約倉位"
---
### Binance
- "Buy 100 USDT worth of BTC on Binance spot"
- "Open a long BTCUSDT futures position with 10x leverage on Binance"
- "Check my Binance account balance"
- "Set take profit and stop loss for my Binance BTC futures position"
---
- "用 Binance 現貨買 100 USDT 的 BTC"
- "用 Binance 開 BTCUSDT 合約 10 倍槓桿多單"
- "查看我的 Binance 帳戶餘額"
- "幫我的 Binance BTC 合約倉位設定止盈止損"
---
### Binance API Setup
#### 1. Create Your Binance API Key
1. Register at **[https://www.binance.com/](https://www.binance.com/)** (if you don't have an account)
2. Go to **Account → API Management**
3. Click **Create API**
4. Enable permissions: **Enable Reading** + **Enable Spot & Margin Trading** + **Enable Futures** (do NOT enable Withdraw)
5. Save your credentials:
- **API Key**
- **Secret Key** (shown only once — save it immediately)
#### 2. Add Binance Credentials
Add the following to your `.env` file:
```
BINANCE_API_KEY=YOUR_API_KEY
BINANCE_SECRET_KEY=YOUR_SECRET_KEY
```
---
## Disclaimer
This skill is provided for informational and automation purposes only. It does not constitute financial, investment, or trading advice. Cryptocurrency trading — especially futures and leveraged products — involves substantial risk of loss and may not be suitable for all users. Past performance is not indicative of future results.
By using this skill, you acknowledge that:
- All trading decisions are solely your own responsibility
- The authors and maintainers of this skill are not liable for any losses incurred
- You should consult a qualified financial advisor before making investment decisions
**本 Skill 僅供資訊參考與自動化操作使用,不構成任何投資建議。加密貨幣交易(尤其是合約與槓桿產品)具有高度風險,可能導致重大損失。過去績效不代表未來結果。使用本 Skill 即表示您同意,所有交易決策均由您自行負責,作者與維護者不對任何損失承擔責任。**
FILE:clawhub.json
{
"name": "blave-quant",
"tagline": "Crypto market alpha data + BitMart, OKX, Bybit, BingX, Bitget, Binance & Bitfinex trading",
"description": "Blave Quant gives your agent eight capabilities:\n\n1. **Blave Market Alpha** — Fetch crypto market signals including Holder Concentration, Taker Intensity, Whale Hunter, Squeeze Momentum, Market Direction, Capital Shortage, Sector Rotation, Top Trader Exposure, kline data, alpha table screening, screener saved conditions, Liquidation alpha (爆倉指標) + liquidation heatmap, and Hyperliquid top trader tracking (leaderboard, positions, history, performance, bucket stats).\n\n2. **BitMart Futures** — Perpetual futures trading on BitMart: open/close positions, leverage, plan orders, TP/SL, trailing stops, and account management.\n\n3. **BitMart Spot** — Spot trading on BitMart: buy/sell, order management, margin, and sub-accounts.\n\n4. **OKX Trading** — Spot and perpetual swap trading on OKX: order placement, position queries, and balance management.\n\n5. **Bybit Trading** — Spot and derivatives/perpetual swap trading on Bybit: order placement, position queries, leverage, TP/SL, and balance management.\n\n6. **BingX Trading** — Spot and perpetual swap trading on BingX: order placement, position management, leverage, TWAP orders, OCO orders, and kill switch.\n\n7. **Bitget Trading** — Spot and futures trading on Bitget: order placement, position management, leverage, plan orders, and account management.\n\n8. **Binance Trading** — Spot and USDS-M futures trading on Binance: order placement, positions, leverage, algo orders, OCO/OTO/OTOCO, and account management.\n\n9. **Bitfinex Trading & Funding** — Spot, margin, and funding/lending on Bitfinex: order placement, margin positions, funding offers (lend/borrow), funding loans/credits, wallet transfers between exchange/margin/funding.\n\n**Credentials:** Only `blave_api_key` and `blave_secret_key` are required (for market data). Exchange keys are optional — add only the exchanges you want to trade on: BITMART_API_KEY/BITMART_API_SECRET/BITMART_API_MEMO for BitMart; OKX_API_KEY/OKX_SECRET_KEY/OKX_PASSPHRASE for OKX; BYBIT_API_KEY/BYBIT_API_SECRET for Bybit; BINGX_API_KEY/BINGX_SECRET_KEY for BingX; BITFINEX_API_KEY/BITFINEX_API_SECRET for Bitfinex. This skill contains no executable code and does not transmit your keys itself — however, following the instructions will cause your agent to call third-party APIs using those keys. We recommend creating dedicated API keys with the minimum permissions required (read-only for market data, trading-only for order placement) and enabling IP whitelisting where possible.\n\n**Bonus examples:** BTC ETF flow monitor (track BlackRock IBIT / Fidelity FBTC institutional flows from Farside), Truth Social Trump post monitor (RSS + Google Translate to zh-TW + Telegram push, no LLM tokens), Hyperliquid copy trading, Blave alpha screening, HC backtest with regime analysis, Bitfinex auto-lending (rate-adaptive period selection + ladder offers across periods and rates), KD stochastic backtest on BTC 1h (golden/death cross, 2D param scan, regime analysis), MCPT+OOS validation framework (IS/OOS split + Monte Carlo Permutation Test, KD vs Taker Intensity comparison).\n\n**Safety Mode (enforced):** Every order, cancel, transfer, or funding action requires the user to reply exactly `CONFIRM` in the current conversation before execution. One CONFIRM authorizes one action; no auto-trading loop can bypass this. READ operations (quotes, balances, positions, alpha data) do not require CONFIRM. This rule applies to every exchange in this skill and cannot be disabled by the agent.\n\n**Broker attribution:** All BitMart requests include `X-BM-BROKER-ID: BlaveData666666`; all Bybit requests include `referer: Ue001036`; all BingX requests include `X-SOURCE-KEY: BX-AI-SKILL`; all Bitfinex orders include `aff_code: ZZDLtrXMF` in the meta field; all Binance orders include a `newClientOrderId` prefixed with `x-GBN6HWR2` (spot) or `x-52DDFAFN` (USDS-M futures). These identify Blave as the referring partner. They do not affect your trading fees as a user.",
"version": "1.6.10",
"requires": {
"env": [
"blave_api_key",
"blave_secret_key"
]
},
"optional": {
"env": [
"BITMART_API_KEY",
"BITMART_API_SECRET",
"BITMART_API_MEMO",
"OKX_API_KEY",
"OKX_SECRET_KEY",
"OKX_PASSPHRASE",
"BYBIT_API_KEY",
"BYBIT_API_SECRET",
"BINGX_API_KEY",
"BINGX_SECRET_KEY",
"BITGET_API_KEY",
"BITGET_SECRET_KEY",
"BITGET_PASSPHRASE",
"BINANCE_API_KEY",
"BINANCE_SECRET_KEY",
"BITFINEX_API_KEY",
"BITFINEX_API_SECRET"
]
},
"category": "data",
"tags": ["crypto", "trading", "bitmart", "okx", "bybit", "bingx", "bitget", "binance", "bitfinex", "blave", "futures", "spot", "alpha", "market-data", "funding", "lending", "truth-social", "trump", "etf", "blackrock"],
"license": "MIT",
"pricing": "free",
"support_url": "https://github.com/Blave-TW/blave-quant-skill/issues",
"homepage": "https://blave.org"
}
FILE:examples/backtest-holder-concentration.md
# Example: Backtest — Holder Concentration Long Strategy
## Strategy Logic
Go long when smart money (institutions / large players) is concentrating into a coin. Exit when they start distributing.
- **Entry:** HC alpha > `1.0` → open long
- **Exit:** HC alpha < `-0.5` → close long
- **Hold:** between thresholds, maintain current position ("strict entry, loose exit")
- **Vol-targeting:** size each position so the strategy targets 30% annualized volatility regardless of the coin's own volatility
- **Long only** — no short positions
---
## Data Required
```
GET /kline?symbol=<SYMBOL>&period=1h&start_date=<YYYY-MM-DD>&end_date=<YYYY-MM-DD>
GET /holder_concentration/get_alpha?symbol=<SYMBOL>&period=1h&start_date=<YYYY-MM-DD>&end_date=<YYYY-MM-DD>
```
Both return time series. Align them on timestamp before backtesting.
For history beyond 1 year, send one request per year and concatenate.
---
## Full Backtest Code
```python
import numpy as np
import pandas as pd
import requests
import matplotlib.pyplot as plt
import numba
from dotenv import dotenv_values
_env = dotenv_values() # reads .env from current directory
# ── Config ──────────────────────────────────────────────────────────────────
SYMBOL = "BTCUSDT"
START_DATE = "2023-01-01"
END_DATE = "2024-12-31"
PERIOD = "1h"
ENTRY_TH = 1.0 # HC alpha > 1.0 → open long
EXIT_TH = -0.5 # HC alpha < -0.5 → close long
TARGET_VOL = 0.30 # 30% annualized target volatility
MAX_LEV = 2.0 # max position size (leverage cap)
VOL_WINDOW = 720 # 30 days × 24h
HOURS_PER_YEAR = 8760
FEE = 0.0005 # 0.05% per side (taker fee)
SHARPE_MODE = "mean" # "mean" = r.mean()/r.std() | "slope" = OLS slope / residual std
API_BASE = "https://api.blave.org"
API_KEY = _env["blave_api_key"]
API_SECRET = _env["blave_secret_key"]
HEADERS = {"api-key": API_KEY, "secret-key": API_SECRET}
# ── Fetch helpers ────────────────────────────────────────────────────────────
def fetch_range(endpoint, params):
"""Fetch one year at a time and concatenate if needed."""
from datetime import datetime, timedelta
start = datetime.strptime(params["start_date"], "%Y-%m-%d")
end = datetime.strptime(params["end_date"], "%Y-%m-%d")
all_data = []
cursor = start
while cursor < end:
chunk_end = min(cursor + timedelta(days=365), end)
p = {**params, "start_date": cursor.strftime("%Y-%m-%d"),
"end_date": chunk_end.strftime("%Y-%m-%d")}
r = requests.get(f"{API_BASE}/{endpoint}", headers=HEADERS, params=p)
r.raise_for_status()
all_data.append(r.json())
cursor = chunk_end
return all_data
def load_kline(symbol, start, end, period):
chunks = fetch_range("kline", {"symbol": symbol, "period": period,
"start_date": start, "end_date": end})
rows = [row for chunk in chunks for row in chunk]
df = pd.DataFrame(rows, columns=["time", "open", "high", "low", "close"])
df["time"] = pd.to_datetime(df["time"], unit="s", utc=True)
df = df.set_index("time").sort_index().drop_duplicates()
df["close"] = df["close"].astype(float)
return df
def load_hc(symbol, start, end, period):
chunks = fetch_range("holder_concentration/get_alpha",
{"symbol": symbol, "period": period,
"start_date": start, "end_date": end})
timestamps, alphas = [], []
for chunk in chunks:
data = chunk.get("data", {})
timestamps.extend(data.get("timestamp", []))
alphas.extend(data.get("alpha", []))
df = pd.DataFrame({"time": pd.to_datetime(timestamps, unit="s", utc=True),
"hc": alphas})
df = df.set_index("time").sort_index().drop_duplicates()
df["hc"] = pd.to_numeric(df["hc"], errors="coerce")
return df
# ── Helpers ──────────────────────────────────────────────────────────────────
def _sharpe(r):
"""
mean mode : (mean / std) * sqrt(N) — industry standard
slope mode: OLS slope of cumulative PnL / diff(residual).std() * sqrt(N)
≈ mean mode for stationary returns, but penalises front-loaded
or decaying strategies and rewards improving ones.
"""
s = r.std()
if s == 0: return np.nan
if SHARPE_MODE == "slope":
cum = np.cumsum(r)
n = len(r)
x = np.arange(n, dtype=np.float64); x -= x.mean()
slope = x.dot(cum) / x.dot(x) # return per bar
resid = cum - (cum.mean() + slope * x)
rvol = np.diff(resid).std() # per-bar residual vol
return (slope / rvol) * np.sqrt(HOURS_PER_YEAR) if rvol > 0 else np.nan
return (r.mean() / s) * np.sqrt(HOURS_PER_YEAR)
# ── Numba: only the stateful signal loop ─────────────────────────────────────
# Rolling vol → Pandas rolling (Cython/C, fastest for window stats)
# Signal loop → Numba njit (stateful: each bar depends on previous state)
# Everything else → NumPy (vectorized, no overhead)
# First call triggers JIT compilation (~2–5 s, once per session).
@numba.njit(cache=True)
def _signal_loop(signal, entry_th, exit_th):
n = len(signal)
position = np.zeros(n)
in_pos = False
for i in range(n):
if np.isnan(signal[i]):
position[i] = 1.0 if in_pos else 0.0
continue
if not in_pos and signal[i] > entry_th:
in_pos = True
elif in_pos and signal[i] < exit_th:
in_pos = False
position[i] = 1.0 if in_pos else 0.0
return position
# ── Backtest ──────────────────────────────────────────────────────────────────
def run_backtest(df):
close = df["close"].values.astype(np.float64)
hc = df["hc"].values.astype(np.float64)
n = len(df)
# NumPy — vectorized
log_ret = np.concatenate([[0.0], np.log(close[1:] / close[:-1])])
fwd_ret = np.empty(n)
fwd_ret[:-1] = np.diff(close) / close[:-1]
fwd_ret[-1] = 0.0
# Pandas rolling — C-implemented window stats, fastest for this operation
realized_vol = pd.Series(log_ret).rolling(VOL_WINDOW).std().values * np.sqrt(HOURS_PER_YEAR)
# Numba njit — only the stateful entry/exit loop benefits from JIT
position = _signal_loop(hc, ENTRY_TH, EXIT_TH)
# NumPy — vectorized vol-targeting and fee calculation
vol_scalar = np.where(
(realized_vol > 0) & ~np.isnan(realized_vol),
np.clip(TARGET_VOL / realized_vol, 0, MAX_LEV),
1.0
)
sized = position * vol_scalar
fee_cost = np.abs(np.diff(sized, prepend=0)) * FEE
strat_ret = sized * fwd_ret - fee_cost
# ── Bar-level metrics ────────────────────────────────────────────────────
r = strat_ret[~np.isnan(strat_ret)]
cum = np.cumprod(1 + r)
peak = np.maximum.accumulate(cum)
total_years = len(r) / HOURS_PER_YEAR
total_return = cum[-1] - 1
ann_ret = (1 + total_return) ** (1 / total_years) - 1
ann_vol = r.std() * np.sqrt(HOURS_PER_YEAR)
sharpe = _sharpe(r)
max_dd = ((cum - peak) / peak).min()
# ── Trade-level metrics ──────────────────────────────────────────────────
# Each trade = one entry→exit cycle; compute per-trade net return
entries = np.where(np.diff(position.astype(int)) == 1)[0] + 1
exits = np.where(np.diff(position.astype(int)) == -1)[0] + 1
# Do NOT force-close the last open position — leave it open if still in trade at end of data
trade_returns = []
for entry_i, exit_i in zip(entries, exits):
trade_r = strat_ret[entry_i:exit_i]
trade_r = trade_r[~np.isnan(trade_r)]
if len(trade_r) > 0:
trade_returns.append(np.prod(1 + trade_r) - 1)
trade_returns = np.array(trade_returns)
n_trades = len(trade_returns)
win_rate = (trade_returns > 0).mean() if n_trades > 0 else np.nan
avg_win = trade_returns[trade_returns > 0].mean() if (trade_returns > 0).any() else np.nan
avg_loss = trade_returns[trade_returns <= 0].mean() if (trade_returns <= 0).any() else np.nan
avg_trades_yr = n_trades / total_years
return {
"total_return": total_return,
"ann_ret": ann_ret,
"ann_vol": ann_vol,
"sharpe": sharpe,
"max_dd": max_dd,
"win_rate": win_rate,
"avg_win": avg_win,
"avg_loss": avg_loss,
"n_trades": n_trades,
"avg_trades_yr": avg_trades_yr,
"position": position,
"sized": sized,
"strat_ret": strat_ret,
"realized_vol": realized_vol,
"cum": cum,
}
# ── Regime Analysis ──────────────────────────────────────────────────────────
def regime_analysis(df, result):
"""Break down performance by: calendar year, Bull/Bear, High/Low volatility."""
strat_ret = result["strat_ret"]
realized_vol = result["realized_vol"]
close = df["close"].values
dates = df.index
# Bull/Bear: price vs 200-period rolling MA (window scales with VOL_WINDOW)
ma_window = VOL_WINDOW * 200 // 30 # 200 days expressed in bars
ma200 = pd.Series(close).rolling(ma_window).mean().values
valid_ma = ~np.isnan(ma200)
# High/Low vol: above vs below median realized vol
vol_median = np.nanmedian(realized_vol)
valid_vol = ~np.isnan(realized_vol)
def _stats(mask):
r = strat_ret[mask]
r = r[~np.isnan(r)]
if len(r) < 2:
return None
total_years = len(r) / HOURS_PER_YEAR
cum_r = np.prod(1 + r) - 1
ann_r = (1 + cum_r) ** (1 / total_years) - 1 if total_years > 0 else np.nan
ann_vol = r.std() * np.sqrt(HOURS_PER_YEAR)
sharpe = _sharpe(r)
cum_curve = np.cumprod(1 + r)
peak = np.maximum.accumulate(cum_curve)
mdd = ((cum_curve - peak) / peak).min()
n_total = len(strat_ret[~np.isnan(strat_ret)])
return dict(ann_ret=ann_r, ann_vol=ann_vol, sharpe=sharpe,
max_dd=mdd, pct_time=len(r) / n_total)
rows = []
# ── By calendar year ────────────────────────────────────────────────────
for yr in sorted(dates.year.unique()):
mask = (dates.year == yr)
s = _stats(mask)
if s:
rows.append({"label": str(yr), **s})
rows.append({"label": "─" * 20}) # separator
# ── Bull vs Bear ─────────────────────────────────────────────────────────
bull = close > ma200
for label, mask in [("Bull (price > MA200)", bull & valid_ma),
("Bear (price < MA200)", ~bull & valid_ma)]:
s = _stats(mask)
if s:
rows.append({"label": label, **s})
rows.append({"label": "─" * 20})
# ── High vol vs Low vol ───────────────────────────────────────────────────
highvol = realized_vol > vol_median
for label, mask in [("High Vol (>median)", highvol & valid_vol),
("Low Vol (≤median)", ~highvol & valid_vol)]:
s = _stats(mask)
if s:
rows.append({"label": label, **s})
# ── Print table ───────────────────────────────────────────────────────────
hdr = f" {'Regime':<22} {'Ann Ret':>9} {'Ann Vol':>9} {'Sharpe':>8} {'MDD':>8} {'Time%':>7}"
print(f"\n{'─' * len(hdr)}")
print(" Regime Analysis")
print('─' * len(hdr))
print(hdr)
print('─' * len(hdr))
for row in rows:
if "ann_ret" not in row: # separator row
print(f" {row['label']}")
continue
print(f" {row['label']:<22} {row['ann_ret']*100:>8.1f}% {row['ann_vol']*100:>8.1f}%"
f" {row['sharpe']:>8.2f} {row['max_dd']*100:>7.1f}% {row['pct_time']*100:>6.1f}%")
print('─' * len(hdr))
# ── Regime Chart ──────────────────────────────────────────────────────────────
def plot_regime(df, result, symbol):
strat_ret = result["strat_ret"]
realized_vol = result["realized_vol"]
close = df["close"].values
dates = df.index
ma_window = VOL_WINDOW * 200 // 30
ma200 = pd.Series(close).rolling(ma_window).mean().values
valid_ma = ~np.isnan(ma200)
vol_median = np.nanmedian(realized_vol)
valid_vol = ~np.isnan(realized_vol)
bull = close > ma200
highvol = realized_vol > vol_median
def _stats(mask):
r = strat_ret[mask]; r = r[~np.isnan(r)]
if len(r) < 2: return None
total_years = len(r) / HOURS_PER_YEAR; cum_r = np.prod(1 + r) - 1
ann_r = (1 + cum_r) ** (1 / total_years) - 1 if total_years > 0 else np.nan
ann_vol = r.std() * np.sqrt(HOURS_PER_YEAR)
sharpe = _sharpe(r)
cc = np.cumprod(1 + r); pk = np.maximum.accumulate(cc)
return dict(ann_ret=ann_r, sharpe=sharpe, max_dd=((cc - pk) / pk).min())
groups = {
"By Year": [(str(yr), _stats(dates.year == yr))
for yr in sorted(dates.year.unique())],
"Trend Regime": [("Bull\n(>MA200)", _stats(bull & valid_ma)),
("Bear\n(<MA200)", _stats(~bull & valid_ma))],
"Volatility Regime": [("High Vol\n(>median)", _stats(highvol & valid_vol)),
("Low Vol\n(≤median)", _stats(~highvol & valid_vol))],
}
groups = {k: [(lbl, s) for lbl, s in v if s is not None] for k, v in groups.items()}
fig, axes = plt.subplots(1, 3, figsize=(16, 6))
for ax, (group_name, items) in zip(axes, groups.items()):
labels = [lbl for lbl, _ in items]
ann_ret = [s["ann_ret"] * 100 for _, s in items]
sharpe = [s["sharpe"] for _, s in items]
mdd = [s["max_dd"] * 100 for _, s in items]
x = np.arange(len(labels)); w = 0.25
b1 = ax.bar(x - w, ann_ret, w, label="Ann Ret (%)", color="#3498db", alpha=0.85)
b2 = ax.bar(x, sharpe, w, label="Sharpe", color="#2ecc71", alpha=0.85)
b3 = ax.bar(x + w, mdd, w, label="MDD (%)", color="#e74c3c", alpha=0.85)
for bars in [b1, b2, b3]:
for bar in bars:
h = bar.get_height()
ax.text(bar.get_x() + bar.get_width() / 2,
h + (0.3 if h >= 0 else -1.5),
f"{h:.1f}", ha="center",
va="bottom" if h >= 0 else "top", fontsize=8)
ax.axhline(0, color="#555", lw=0.8)
ax.set_title(group_name, fontsize=13, fontweight="bold")
ax.set_xticks(x); ax.set_xticklabels(labels, fontsize=10)
ax.set_ylabel("Value", fontsize=10); ax.legend(fontsize=9)
all_vals = ann_ret + sharpe + mdd
ax.set_ylim(min(all_vals) - 5, max(all_vals) + 8)
fig.suptitle(f"{symbol} — Regime Analysis", fontsize=13, fontweight="bold", y=1.01)
plt.tight_layout()
fname = f"{symbol}_hc_regime.png"
plt.savefig(fname, dpi=150, bbox_inches="tight")
plt.show()
print(f"Saved: {fname}")
# ── PnL Chart ─────────────────────────────────────────────────────────────────
def plot_pnl(df, result, symbol):
close = df["close"].values
hc = df["hc"].values
dates = df.index
cum = result["cum"]
pos = result["position"]
peak = np.maximum.accumulate(cum)
dd = (cum - peak) / peak
fig, axes = plt.subplots(3, 1, figsize=(14, 10), sharex=True,
gridspec_kw={'height_ratios': [3, 1, 1]})
# Panel 1: Price (left y) + Strategy PnL (right y)
ax1 = axes[0]
ax2 = ax1.twinx()
ax1.plot(dates, close, color="#3498db", lw=1, alpha=0.7, label="Price")
ax1.set_ylabel("Price (USD)", fontsize=11, color="#3498db")
ax1.tick_params(axis='y', labelcolor="#3498db")
ax1.yaxis.set_major_formatter(plt.FuncFormatter(lambda x, _: f",.0f"))
ax2.plot(dates, (cum - 1) * 100, color="#2ecc71", lw=1.5, label="HC Strategy (+fees)")
ax2.axhline(0, color="#888", lw=0.5, ls="--")
ax2.set_ylabel("Strategy Return (%)", fontsize=11, color="#2ecc71")
ax2.tick_params(axis='y', labelcolor="#2ecc71")
ax2.yaxis.set_major_formatter(plt.FuncFormatter(lambda x, _: f"{x:.0f}%"))
# Shade in-position periods
prev = False
for i, (date, inp) in enumerate(zip(dates, pos > 0)):
if inp and not prev:
start = date
if not inp and prev:
ax1.axvspan(start, date, alpha=0.08, color="#2ecc71")
prev = inp
if prev:
ax1.axvspan(start, dates[-1], alpha=0.08, color="#2ecc71")
lines1, labels1 = ax1.get_legend_handles_labels()
lines2, labels2 = ax2.get_legend_handles_labels()
ax1.legend(lines1 + lines2, labels1 + labels2, fontsize=10, loc="upper left")
ax1.set_title(
f"{symbol} — HC Strategy (entry>{ENTRY_TH}, exit<{EXIT_TH})\n"
f"Vol-Target {TARGET_VOL*100:.0f}% | Max Lev {MAX_LEV}x | Fee {FEE*100:.2f}%/side",
fontsize=13
)
# Panel 2: Drawdown
axes[1].fill_between(dates, dd * 100, 0, color="#e74c3c", alpha=0.6)
axes[1].set_ylabel("Drawdown (%)", fontsize=11)
axes[1].yaxis.set_major_formatter(plt.FuncFormatter(lambda x, _: f"{x:.0f}%"))
axes[1].axhline(0, color="#888", lw=0.5)
# Panel 3: HC signal
axes[2].plot(dates, hc, color="#9b59b6", lw=0.8, alpha=0.8)
axes[2].axhline(ENTRY_TH, color="#2ecc71", lw=1, ls="--", label=f"Entry={ENTRY_TH}")
axes[2].axhline(EXIT_TH, color="#e74c3c", lw=1, ls="--", label=f"Exit={EXIT_TH}")
axes[2].axhline(0, color="#888", lw=0.5)
axes[2].set_ylabel("HC Alpha", fontsize=11)
axes[2].legend(fontsize=9, loc="upper right")
plt.tight_layout()
fname = f"{symbol}_hc_pnl.png"
plt.savefig(fname, dpi=150)
plt.show()
print(f"Saved: {fname}")
# ── 2D Parameter Scan ────────────────────────────────────────────────────────
THRESHOLDS = [-2, -1.5, -1, -0.5, 0, 0.5, 1, 1.5, 2]
def param_scan(df):
sharpe_grid = np.full((len(THRESHOLDS), len(THRESHOLDS)), np.nan)
for i, entry in enumerate(THRESHOLDS):
for j, exit_ in enumerate(THRESHOLDS):
if exit_ > entry:
continue
r = run_backtest_params(df, entry, exit_)
if r is not None:
sharpe_grid[i, j] = r["sharpe"]
return sharpe_grid
def run_backtest_params(df, entry_th, exit_th):
"""Lightweight param-scan version: same stack as run_backtest, returns Sharpe only."""
close = df["close"].values.astype(np.float64)
hc = df["hc"].values.astype(np.float64)
n = len(df)
log_ret = np.concatenate([[0.0], np.log(close[1:] / close[:-1])])
fwd_ret = np.empty(n)
fwd_ret[:-1] = np.diff(close) / close[:-1]
fwd_ret[-1] = 0.0
realized_vol = pd.Series(log_ret).rolling(VOL_WINDOW).std().values * np.sqrt(HOURS_PER_YEAR)
position = _signal_loop(hc, entry_th, exit_th)
vol_scalar = np.where(
(realized_vol > 0) & ~np.isnan(realized_vol),
np.clip(TARGET_VOL / realized_vol, 0, MAX_LEV),
1.0
)
sized = position * vol_scalar
fee_cost = np.abs(np.diff(sized, prepend=0)) * FEE
strat_ret = sized * fwd_ret - fee_cost
r = strat_ret[~np.isnan(strat_ret)]
if len(r) == 0 or r.std() == 0:
return None
return {"sharpe": _sharpe(r)}
def find_plateau(sharpe_grid, window=1):
"""
Find the parameter plateau: the cell whose neighborhood has the
highest mean Sharpe. Uses a (2*window+1) × (2*window+1) window.
This is more robust than picking the single best cell, as it
avoids isolated peaks that may be overfitted.
"""
rows, cols = sharpe_grid.shape
neighborhood_mean = np.full((rows, cols), np.nan)
for i in range(rows):
for j in range(cols):
if np.isnan(sharpe_grid[i, j]):
continue
neighbors = []
for di in range(-window, window + 1):
for dj in range(-window, window + 1):
ni, nj = i + di, j + dj
if 0 <= ni < rows and 0 <= nj < cols:
v = sharpe_grid[ni, nj]
if not np.isnan(v):
neighbors.append(v)
if neighbors:
neighborhood_mean[i, j] = np.mean(neighbors)
best_idx = np.unravel_index(np.nanargmax(neighborhood_mean), neighborhood_mean.shape)
return best_idx, neighborhood_mean
def plot_heatmap(sharpe_grid, neighborhood_mean, plateau_idx, symbol):
peak_idx = np.unravel_index(np.nanargmax(sharpe_grid), sharpe_grid.shape)
fig, axes = plt.subplots(1, 2, figsize=(18, 7))
for ax, mark_idx, title, note in [
(axes[0], peak_idx, "Peak — highest single Sharpe",
f"Selected: entry={THRESHOLDS[peak_idx[0]]}, exit={THRESHOLDS[peak_idx[1]]}\n"
f"Sharpe={sharpe_grid[peak_idx]:.2f} — may be overfitted if isolated"),
(axes[1], plateau_idx, "Plateau — most stable region",
f"Selected: entry={THRESHOLDS[plateau_idx[0]]}, exit={THRESHOLDS[plateau_idx[1]]}\n"
f"Sharpe={sharpe_grid[plateau_idx]:.2f} — neighbors also perform well → more robust"),
]:
masked = np.ma.masked_invalid(sharpe_grid)
cmap = plt.cm.RdYlGn.copy()
cmap.set_bad(color="#cccccc")
vals = sharpe_grid[~np.isnan(sharpe_grid)]
im = ax.imshow(masked, cmap=cmap, vmin=min(0, vals.min()),
vmax=np.percentile(vals, 95), aspect="auto")
ax.add_patch(plt.Rectangle(
(mark_idx[1] - 0.5, mark_idx[0] - 0.5), 1, 1,
fill=False, edgecolor="gold", linewidth=3
))
for i in range(len(THRESHOLDS)):
for j in range(len(THRESHOLDS)):
v = sharpe_grid[i, j]
ax.text(j, i, f"{v:.2f}" if not np.isnan(v) else "N/A",
ha="center", va="center", fontsize=8,
color="#888" if np.isnan(v) else "black")
ax.set_xticks(range(len(THRESHOLDS))); ax.set_xticklabels(THRESHOLDS)
ax.set_yticks(range(len(THRESHOLDS))); ax.set_yticklabels(THRESHOLDS)
ax.set_xlabel("Exit Threshold")
ax.set_ylabel("Entry Threshold")
ax.set_title(f"{symbol} — {title}\n{note}", fontsize=10)
plt.colorbar(im, ax=ax, label="Sharpe Ratio")
plt.suptitle("Same Sharpe grid, different selection method — prefer Plateau", fontsize=12)
plt.tight_layout()
fname = f"{symbol}_hc_heatmap.png"
plt.savefig(fname, dpi=150)
plt.show()
print(f"Saved: {fname}")
# ── Main ──────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
print(f"Loading data for {SYMBOL} ({START_DATE} → {END_DATE}, {PERIOD})...")
kline = load_kline(SYMBOL, START_DATE, END_DATE, PERIOD)
hc = load_hc(SYMBOL, START_DATE, END_DATE, PERIOD)
df = kline[["close"]].join(hc[["hc"]], how="inner").dropna(subset=["close"])
print(f"Aligned rows: {len(df)}")
# Step 1: 2D scan → find plateau
print("Running 2D parameter scan...")
sharpe_grid = param_scan(df)
plateau_idx, neighborhood_mean = find_plateau(sharpe_grid, window=1)
best_entry = THRESHOLDS[plateau_idx[0]]
best_exit = THRESHOLDS[plateau_idx[1]]
print(f"Plateau center: entry={best_entry}, exit={best_exit} "
f"(neighborhood mean Sharpe={neighborhood_mean[plateau_idx]:.2f})")
plot_heatmap(sharpe_grid, neighborhood_mean, plateau_idx, SYMBOL)
# Step 2: full backtest with plateau parameters
global ENTRY_TH, EXIT_TH
ENTRY_TH, EXIT_TH = best_entry, best_exit
result = run_backtest(df)
print(f"\nResults (entry>{ENTRY_TH}, exit<{EXIT_TH}):")
print(f" 總報酬率 : {result['total_return']*100:.1f}%")
print(f" 年化報酬率 : {result['ann_ret']*100:.1f}%")
print(f" 年化波動度 : {result['ann_vol']*100:.1f}%")
print(f" Sharpe Ratio : {result['sharpe']:.2f}")
print(f" 最大回撤 (MDD) : {result['max_dd']*100:.1f}%")
print(f" 勝率 : {result['win_rate']*100:.1f}%")
print(f" 平均獲利 (per trade): {result['avg_win']*100:.2f}%")
print(f" 平均虧損 (per trade): {result['avg_loss']*100:.2f}%")
print(f" 總交易次數 : {result['n_trades']}")
print(f" 平均年交易次數 : {result['avg_trades_yr']:.1f}")
regime_analysis(df, result)
plot_regime(df, result, SYMBOL)
plot_pnl(df, result, SYMBOL)
```
---
## Parameters
| Parameter | Value | Notes |
|---|---|---|
| `ENTRY_TH` | `1.0` | HC alpha must exceed this to open long |
| `EXIT_TH` | `-0.5` | HC alpha must fall below this to close |
| `PERIOD` | `1h` | HC signal + kline timeframe |
| `VOL_WINDOW` | `720` | 30 days × 24h rolling vol |
| `TARGET_VOL` | `0.30` | 30% annualized target |
| `MAX_LEV` | `2.0` | Position size cap |
| `FEE` | `0.0005` | 0.05% per side (taker fee, e.g. Hyperliquid) |
---
## Alpha Scale Reference
| HC Alpha | Signal |
|---|---|
| > 3 | Over Concentrated (long) |
| 2 – 3 | Highly Concentrated (long) |
| **> 1** | **→ Entry threshold** |
| 0.5 – 1 | Concentrated (long) |
| -0.5 – 0.5 | Neutral |
| **< -0.5** | **→ Exit threshold** |
| < -2 | Concentrated (short) |
---
## Notes
- **Long only** — no short positions taken
- Entry threshold is stricter than exit — gives the position room to breathe through short-term noise
- Vol-targeting scales down automatically during high-volatility periods; a coin with 3× BTC vol receives ~1/3 the position size for the same signal
- Signals update every 5 minutes; on `1h` period each bar reflects the last finalized hourly HC value
- **Performance stack:** Rolling vol uses `pd.Series.rolling().std()` (Pandas Cython/C — fastest for window statistics). The entry/exit signal loop uses `@numba.njit` — this is the only loop that benefits from JIT because each bar depends on the previous bar's state (cannot be vectorized). Everything else (fwd_ret, vol-targeting, fees, returns) uses NumPy vectorized ops. First call triggers JIT compilation (~2–5 s, once per session). If numba is not installed: `pip install numba`
### Live Trading Execution Timing
The backtest computes `fwd_ret[i] = (close[i+1] - close[i]) / close[i]`, which means it assumes the order is **executed at bar i's close price** — the same bar where the signal fires.
In live trading, the correct sequence is:
```
bar i closes
→ fetch the latest signal
→ signal changed → place market order immediately
→ fill ≈ bar i+1 open (for liquid pairs like BTC, this is effectively bar i close)
```
**Do NOT wait for bar i+1 to close before placing the order.** Waiting an extra bar means your execution price is one full bar later than what the backtest assumes, causing live performance to diverge from backtest results.
---
### Parameter Selection: Plateau vs Peak
**Do not simply pick the highest Sharpe cell.** A single peak is often an overfitted outlier — it performs well in-sample but is fragile out-of-sample.
Instead, use `find_plateau()` to compute the **neighborhood mean Sharpe** for each cell (average of the cell and its surrounding valid cells within a 3×3 window). The cell with the highest neighborhood mean sits at the center of a stable region — a **parameter plateau** — where nearby combinations also perform well. This indicates robustness: small changes to the thresholds do not collapse performance.
The heatmap shows the same Sharpe grid twice. Left panel marks the **peak** (highest single cell); right panel marks the **plateau center** (cell whose neighborhood has the highest mean Sharpe). Both show the true Sharpe value in each cell — the difference is only which cell is highlighted. Prefer the plateau selection even if its raw Sharpe is slightly lower than the peak.
FILE:examples/backtest-kd-btc-1h.md
# Example: Backtest — KD Stochastic Golden/Death Cross (BTC 1h)
## Strategy Logic
Trade BTC on the 1-hour chart using the KD Stochastic Oscillator.
- **Entry:** %K crosses above %D (Golden Cross) → open long
- **Exit:** %K crosses below %D (Death Cross) → close long
- **Long only** — no short positions
- **Vol-targeting:** size each position to target 30% annualized volatility
KD formula:
1. Raw %K = (Close − LowestLow_N) / (HighestHigh_N − LowestLow_N) × 100
2. Slow %K = SMA(Raw %K, K_SMOOTH)
3. %D = SMA(Slow %K, D_SMOOTH)
---
## Data Required
```
GET /kline?symbol=BTCUSDT&period=1h&start_date=<YYYY-MM-DD>&end_date=<YYYY-MM-DD>
```
Returns `[time, open, high, low, close]`. KD is computed locally from OHLCV — no additional endpoint needed.
For history beyond 1 year, send one request per year and concatenate.
---
## Full Backtest Code
```python
import numpy as np
import pandas as pd
import requests
import matplotlib.pyplot as plt
import numba
from dotenv import dotenv_values
_env = dotenv_values()
# ── Config ────────────────────────────────────────────────────────────────────
from datetime import datetime, timedelta, timezone
SYMBOL = "BTCUSDT"
END_DATE = datetime.now(timezone.utc).strftime("%Y-%m-%d")
START_DATE = (datetime.now(timezone.utc) - timedelta(days=365)).strftime("%Y-%m-%d")
PERIOD = "1h"
K_PERIOD = 9 # stochastic lookback window (raw %K)
K_SMOOTH = 3 # %K smoothing period (SMA → slow %K)
D_SMOOTH = 3 # %D smoothing period (SMA of slow %K)
TARGET_VOL = 0.30 # 30% annualized target volatility
MAX_LEV = 2.0 # position size cap
VOL_WINDOW = 720 # rolling vol window: 30 days × 24h
HOURS_PER_YEAR = 8760
FEE = 0.0005 # 0.05% per side (taker fee)
API_BASE = "https://api.blave.org"
API_KEY = _env["blave_api_key"]
API_SECRET = _env["blave_secret_key"]
HEADERS = {"api-key": API_KEY, "secret-key": API_SECRET}
# ── Fetch ─────────────────────────────────────────────────────────────────────
def load_kline(symbol, start, end, period):
s = datetime.strptime(start, "%Y-%m-%d")
e = datetime.strptime(end, "%Y-%m-%d")
rows = []
cursor = s
while cursor < e:
chunk_end = min(cursor + timedelta(days=365), e)
r = requests.get(f"{API_BASE}/kline", headers=HEADERS, params={
"symbol": symbol, "period": period,
"start_date": cursor.strftime("%Y-%m-%d"),
"end_date": chunk_end.strftime("%Y-%m-%d"),
}, timeout=20)
r.raise_for_status()
rows.extend(r.json())
cursor = chunk_end
df = pd.DataFrame(rows, columns=["time", "open", "high", "low", "close"])
df["time"] = pd.to_datetime(df["time"], unit="s", utc=True)
df = df.set_index("time").sort_index().drop_duplicates()
for col in ["open", "high", "low", "close"]:
df[col] = df[col].astype(float)
return df
# ── KD Computation ────────────────────────────────────────────────────────────
def compute_kd(df, k_period, k_smooth, d_smooth):
low_min = df["low"].rolling(k_period).min()
high_max = df["high"].rolling(k_period).max()
denom = high_max - low_min
raw_k = pd.Series(
np.where(denom > 0, (df["close"] - low_min) / denom * 100, 50.0),
index=df.index,
)
slow_k = raw_k.rolling(k_smooth).mean()
d = slow_k.rolling(d_smooth).mean()
return slow_k, d
# ── Helpers ───────────────────────────────────────────────────────────────────
def _sharpe(r):
s = r.std()
if s == 0:
return np.nan
return (r.mean() / s) * np.sqrt(HOURS_PER_YEAR)
# ── Numba: crossover signal loop ──────────────────────────────────────────────
# Each bar depends on the previous bar's K/D values and position state →
# must be sequential; Numba njit gives ~20× speedup over pure Python.
@numba.njit(cache=True)
def _kd_signal_loop(k, d):
n = len(k)
position = np.zeros(n)
in_pos = False
for i in range(1, n):
ki, di = k[i], d[i]
kp, dp = k[i-1], d[i-1]
if np.isnan(ki) or np.isnan(di) or np.isnan(kp) or np.isnan(dp):
position[i] = 1.0 if in_pos else 0.0
continue
if not in_pos and kp <= dp and ki > di: # golden cross → enter
in_pos = True
elif in_pos and kp >= dp and ki < di: # death cross → exit
in_pos = False
position[i] = 1.0 if in_pos else 0.0
return position
# ── Backtest ──────────────────────────────────────────────────────────────────
def run_backtest(df, k_period=None, k_smooth=None, d_smooth=None):
k_period = k_period or K_PERIOD
k_smooth = k_smooth or K_SMOOTH
d_smooth = d_smooth or D_SMOOTH
slow_k, d = compute_kd(df, k_period, k_smooth, d_smooth)
close = df["close"].values.astype(np.float64)
k_arr = slow_k.values.astype(np.float64)
d_arr = d.values.astype(np.float64)
n = len(df)
log_ret = np.concatenate([[0.0], np.log(close[1:] / close[:-1])])
fwd_ret = np.empty(n)
fwd_ret[:-1] = np.diff(close) / close[:-1]
fwd_ret[-1] = 0.0
realized_vol = pd.Series(log_ret).rolling(VOL_WINDOW).std().values * np.sqrt(HOURS_PER_YEAR)
position = _kd_signal_loop(k_arr, d_arr)
vol_scalar = np.where(
(realized_vol > 0) & ~np.isnan(realized_vol),
np.clip(TARGET_VOL / realized_vol, 0, MAX_LEV),
1.0,
)
sized = position * vol_scalar
fee_cost = np.abs(np.diff(sized, prepend=0)) * FEE
strat_ret = sized * fwd_ret - fee_cost
r = strat_ret[~np.isnan(strat_ret)]
cum = np.cumprod(1 + r)
pk = np.maximum.accumulate(cum)
total_years = len(r) / HOURS_PER_YEAR
total_return = cum[-1] - 1
ann_ret = (1 + total_return) ** (1 / total_years) - 1
ann_vol = r.std() * np.sqrt(HOURS_PER_YEAR)
sharpe = _sharpe(r)
max_dd = ((cum - pk) / pk).min()
entries = np.where(np.diff(position.astype(int)) == 1)[0] + 1
exits = np.where(np.diff(position.astype(int)) == -1)[0] + 1
# Do NOT force-close the last open position — leave it open if still in trade at end of data
trade_returns = []
for e_i, x_i in zip(entries, exits):
tr = strat_ret[e_i:x_i]
tr = tr[~np.isnan(tr)]
if len(tr) > 0:
trade_returns.append(np.prod(1 + tr) - 1)
trade_returns = np.array(trade_returns)
n_trades = len(trade_returns)
win_rate = (trade_returns > 0).mean() if n_trades > 0 else np.nan
avg_win = trade_returns[trade_returns > 0].mean() if (trade_returns > 0).any() else np.nan
avg_loss = trade_returns[trade_returns <= 0].mean() if (trade_returns <= 0).any() else np.nan
avg_trades_yr = n_trades / total_years
return {
"total_return": total_return,
"ann_ret": ann_ret,
"ann_vol": ann_vol,
"sharpe": sharpe,
"max_dd": max_dd,
"win_rate": win_rate,
"avg_win": avg_win,
"avg_loss": avg_loss,
"n_trades": n_trades,
"avg_trades_yr": avg_trades_yr,
"position": position,
"sized": sized,
"strat_ret": strat_ret,
"realized_vol": realized_vol,
"cum": cum,
"slow_k": slow_k.values,
"d": d.values,
}
# ── Regime Analysis ───────────────────────────────────────────────────────────
def regime_analysis(df, result):
strat_ret = result["strat_ret"]
realized_vol = result["realized_vol"]
close = df["close"].values
dates = df.index
ma_window = VOL_WINDOW * 200 // 30
ma200 = pd.Series(close).rolling(ma_window).mean().values
valid_ma = ~np.isnan(ma200)
vol_med = np.nanmedian(realized_vol)
valid_vol = ~np.isnan(realized_vol)
def _stats(mask):
r = strat_ret[mask]; r = r[~np.isnan(r)]
if len(r) < 2: return None
years = len(r) / HOURS_PER_YEAR
cum_r = np.prod(1 + r) - 1
ann_r = (1 + cum_r) ** (1 / years) - 1 if years > 0 else np.nan
ann_v = r.std() * np.sqrt(HOURS_PER_YEAR)
sh = _sharpe(r)
cc = np.cumprod(1 + r); pk = np.maximum.accumulate(cc)
mdd = ((cc - pk) / pk).min()
n_tot = len(strat_ret[~np.isnan(strat_ret)])
return dict(ann_ret=ann_r, ann_vol=ann_v, sharpe=sh,
max_dd=mdd, pct_time=len(r) / n_tot)
rows = []
for yr in sorted(dates.year.unique()):
s = _stats(dates.year == yr)
if s: rows.append({"label": str(yr), **s})
rows.append({"label": "─" * 20})
bull = close > ma200
for label, mask in [("Bull (price > MA200)", bull & valid_ma),
("Bear (price < MA200)", ~bull & valid_ma)]:
s = _stats(mask)
if s: rows.append({"label": label, **s})
rows.append({"label": "─" * 20})
hv = realized_vol > vol_med
for label, mask in [("High Vol (>median)", hv & valid_vol),
("Low Vol (≤median)", ~hv & valid_vol)]:
s = _stats(mask)
if s: rows.append({"label": label, **s})
hdr = f" {'Regime':<22} {'Ann Ret':>9} {'Ann Vol':>9} {'Sharpe':>8} {'MDD':>8} {'Time%':>7}"
print(f"\n{'─' * len(hdr)}\n Regime Analysis\n{'─' * len(hdr)}")
print(hdr); print('─' * len(hdr))
for row in rows:
if "ann_ret" not in row:
print(f" {row['label']}"); continue
print(f" {row['label']:<22} {row['ann_ret']*100:>8.1f}% {row['ann_vol']*100:>8.1f}%"
f" {row['sharpe']:>8.2f} {row['max_dd']*100:>7.1f}% {row['pct_time']*100:>6.1f}%")
print('─' * len(hdr))
# ── PnL Chart ─────────────────────────────────────────────────────────────────
def plot_pnl(df, result, symbol):
close = df["close"].values
dates = df.index
cum = result["cum"]
pos = result["position"]
slow_k = result["slow_k"]
d_line = result["d"]
peak = np.maximum.accumulate(cum)
dd = (cum - peak) / peak
fig, axes = plt.subplots(3, 1, figsize=(14, 10), sharex=True,
gridspec_kw={"height_ratios": [3, 1, 1.2]})
# Panel 1: Price + cumulative PnL
ax1 = axes[0]; ax2 = ax1.twinx()
ax1.plot(dates, close, color="#3498db", lw=1, alpha=0.7, label="Price")
ax1.set_ylabel("Price (USD)", fontsize=11, color="#3498db")
ax1.tick_params(axis="y", labelcolor="#3498db")
ax1.yaxis.set_major_formatter(plt.FuncFormatter(lambda x, _: f",.0f"))
ax2.plot(dates, (cum - 1) * 100, color="#2ecc71", lw=1.5, label="KD Strategy (+fees)")
ax2.axhline(0, color="#888", lw=0.5, ls="--")
ax2.set_ylabel("Strategy Return (%)", fontsize=11, color="#2ecc71")
ax2.tick_params(axis="y", labelcolor="#2ecc71")
ax2.yaxis.set_major_formatter(plt.FuncFormatter(lambda x, _: f"{x:.0f}%"))
prev = False
for i, (date, inp) in enumerate(zip(dates, pos > 0)):
if inp and not prev: start = date
if not inp and prev: ax1.axvspan(start, date, alpha=0.08, color="#2ecc71")
prev = inp
if prev: ax1.axvspan(start, dates[-1], alpha=0.08, color="#2ecc71")
l1, lb1 = ax1.get_legend_handles_labels()
l2, lb2 = ax2.get_legend_handles_labels()
ax1.legend(l1 + l2, lb1 + lb2, fontsize=10, loc="upper left")
ax1.set_title(
f"{symbol} — KD Strategy K({K_PERIOD},{K_SMOOTH}) D({D_SMOOTH})\n"
f"Vol-Target {TARGET_VOL*100:.0f}% | Max Lev {MAX_LEV}x | Fee {FEE*100:.2f}%/side",
fontsize=13,
)
# Panel 2: Drawdown
axes[1].fill_between(dates, dd * 100, 0, color="#e74c3c", alpha=0.6)
axes[1].set_ylabel("Drawdown (%)", fontsize=11)
axes[1].yaxis.set_major_formatter(plt.FuncFormatter(lambda x, _: f"{x:.0f}%"))
axes[1].axhline(0, color="#888", lw=0.5)
# Panel 3: KD lines + entry/exit markers
valid = ~np.isnan(slow_k) & ~np.isnan(d_line)
axes[2].plot(dates[valid], slow_k[valid], color="#f39c12", lw=0.9, label="%K")
axes[2].plot(dates[valid], d_line[valid], color="#9b59b6", lw=0.9, label="%D")
axes[2].axhline(80, color="#e74c3c", lw=0.7, ls="--", alpha=0.6, label="OB 80")
axes[2].axhline(20, color="#2ecc71", lw=0.7, ls="--", alpha=0.6, label="OS 20")
axes[2].set_ylabel("KD", fontsize=11)
axes[2].set_ylim(-5, 105)
axes[2].legend(fontsize=9, loc="upper right", ncol=2)
plt.tight_layout()
fname = f"{symbol}_kd_pnl.png"
plt.savefig(fname, dpi=150)
plt.show()
print(f"Saved: {fname}")
# ── Regime Chart ──────────────────────────────────────────────────────────────
def plot_regime(df, result, symbol):
strat_ret = result["strat_ret"]
realized_vol = result["realized_vol"]
close = df["close"].values
dates = df.index
ma_window = VOL_WINDOW * 200 // 30
ma200 = pd.Series(close).rolling(ma_window).mean().values
valid_ma = ~np.isnan(ma200)
vol_med = np.nanmedian(realized_vol)
valid_vol = ~np.isnan(realized_vol)
bull = close > ma200
hv = realized_vol > vol_med
def _stats(mask):
r = strat_ret[mask]; r = r[~np.isnan(r)]
if len(r) < 2: return None
years = len(r) / HOURS_PER_YEAR; cum_r = np.prod(1 + r) - 1
ann_r = (1 + cum_r) ** (1 / years) - 1 if years > 0 else np.nan
cc = np.cumprod(1 + r); pk = np.maximum.accumulate(cc)
return dict(ann_ret=ann_r, sharpe=_sharpe(r), max_dd=((cc - pk) / pk).min())
groups = {
"By Year": [(str(yr), _stats(dates.year == yr))
for yr in sorted(dates.year.unique())],
"Trend Regime": [("Bull\n(>MA200)", _stats(bull & valid_ma)),
("Bear\n(<MA200)", _stats(~bull & valid_ma))],
"Volatility Regime": [("High Vol\n(>median)", _stats(hv & valid_vol)),
("Low Vol\n(≤median)", _stats(~hv & valid_vol))],
}
groups = {k: [(lb, s) for lb, s in v if s is not None] for k, v in groups.items()}
fig, axes = plt.subplots(1, 3, figsize=(16, 6))
for ax, (gname, items) in zip(axes, groups.items()):
labels = [lb for lb, _ in items]
ann_ret = [s["ann_ret"] * 100 for _, s in items]
sharpe = [s["sharpe"] for _, s in items]
mdd = [s["max_dd"] * 100 for _, s in items]
x = np.arange(len(labels)); w = 0.25
b1 = ax.bar(x - w, ann_ret, w, label="Ann Ret (%)", color="#3498db", alpha=0.85)
b2 = ax.bar(x, sharpe, w, label="Sharpe", color="#2ecc71", alpha=0.85)
b3 = ax.bar(x + w, mdd, w, label="MDD (%)", color="#e74c3c", alpha=0.85)
for bars in [b1, b2, b3]:
for bar in bars:
h = bar.get_height()
ax.text(bar.get_x() + bar.get_width() / 2,
h + (0.3 if h >= 0 else -1.5),
f"{h:.1f}", ha="center",
va="bottom" if h >= 0 else "top", fontsize=8)
ax.axhline(0, color="#555", lw=0.8)
ax.set_title(gname, fontsize=13, fontweight="bold")
ax.set_xticks(x); ax.set_xticklabels(labels, fontsize=10)
ax.set_ylabel("Value", fontsize=10); ax.legend(fontsize=9)
all_vals = ann_ret + sharpe + mdd
ax.set_ylim(min(all_vals) - 5, max(all_vals) + 8)
fig.suptitle(f"{symbol} — KD Regime Analysis", fontsize=13, fontweight="bold", y=1.01)
plt.tight_layout()
fname = f"{symbol}_kd_regime.png"
plt.savefig(fname, dpi=150, bbox_inches="tight")
plt.show()
print(f"Saved: {fname}")
# ── 2D Parameter Scan (K_PERIOD × K_SMOOTH) ──────────────────────────────────
K_PERIOD_SCAN = [5, 9, 14, 21, 34, 55]
K_SMOOTH_SCAN = [2, 3, 5, 8, 13]
def param_scan(df):
grid = np.full((len(K_PERIOD_SCAN), len(K_SMOOTH_SCAN)), np.nan)
for i, kp in enumerate(K_PERIOD_SCAN):
for j, ks in enumerate(K_SMOOTH_SCAN):
res = run_backtest(df, k_period=kp, k_smooth=ks, d_smooth=D_SMOOTH)
if res is not None and not np.isnan(res["sharpe"]):
grid[i, j] = res["sharpe"]
return grid
def find_plateau(grid, window=1):
rows, cols = grid.shape
nbr_mean = np.full((rows, cols), np.nan)
for i in range(rows):
for j in range(cols):
if np.isnan(grid[i, j]): continue
neighbors = [grid[i+di, j+dj]
for di in range(-window, window+1)
for dj in range(-window, window+1)
if 0 <= i+di < rows and 0 <= j+dj < cols
and not np.isnan(grid[i+di, j+dj])]
if neighbors: nbr_mean[i, j] = np.mean(neighbors)
best = np.unravel_index(np.nanargmax(nbr_mean), nbr_mean.shape)
return best, nbr_mean
def plot_heatmap(grid, nbr_mean, plateau_idx, symbol):
peak_idx = np.unravel_index(np.nanargmax(grid), grid.shape)
fig, axes = plt.subplots(1, 2, figsize=(16, 6))
for ax, mark_idx, title, note in [
(axes[0], peak_idx, "Peak — highest single Sharpe",
f"K_PERIOD={K_PERIOD_SCAN[mark_idx[0]]}, K_SMOOTH={K_SMOOTH_SCAN[mark_idx[1]]}\n"
f"Sharpe={grid[mark_idx]:.2f} — may be overfitted if isolated"),
(axes[1], plateau_idx, "Plateau — most stable region",
f"K_PERIOD={K_PERIOD_SCAN[plateau_idx[0]]}, K_SMOOTH={K_SMOOTH_SCAN[plateau_idx[1]]}\n"
f"Sharpe={grid[plateau_idx]:.2f} — neighbors also perform well → more robust"),
]:
masked = np.ma.masked_invalid(grid)
cmap = plt.cm.RdYlGn.copy(); cmap.set_bad(color="#cccccc")
vals = grid[~np.isnan(grid)]
im = ax.imshow(masked, cmap=cmap, vmin=min(0, vals.min()),
vmax=np.percentile(vals, 95), aspect="auto")
ax.add_patch(plt.Rectangle(
(mark_idx[1] - 0.5, mark_idx[0] - 0.5), 1, 1,
fill=False, edgecolor="gold", linewidth=3,
))
for i in range(len(K_PERIOD_SCAN)):
for j in range(len(K_SMOOTH_SCAN)):
v = grid[i, j]
ax.text(j, i, f"{v:.2f}" if not np.isnan(v) else "N/A",
ha="center", va="center", fontsize=9,
color="#888" if np.isnan(v) else "black")
ax.set_xticks(range(len(K_SMOOTH_SCAN))); ax.set_xticklabels(K_SMOOTH_SCAN)
ax.set_yticks(range(len(K_PERIOD_SCAN))); ax.set_yticklabels(K_PERIOD_SCAN)
ax.set_xlabel("K_SMOOTH (slow %K period)")
ax.set_ylabel("K_PERIOD (stochastic lookback)")
ax.set_title(f"{symbol} — {title}\n{note}", fontsize=10)
plt.colorbar(im, ax=ax, label="Sharpe Ratio")
plt.suptitle("Same Sharpe grid, different selection method — prefer Plateau", fontsize=12)
plt.tight_layout()
fname = f"{symbol}_kd_heatmap.png"
plt.savefig(fname, dpi=150)
plt.show()
print(f"Saved: {fname}")
# ── Main ──────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
print(f"Loading kline for {SYMBOL} ({START_DATE} → {END_DATE}, {PERIOD})...")
df = load_kline(SYMBOL, START_DATE, END_DATE, PERIOD)
print(f"Bars loaded: {len(df)}")
# Step 1: 2D scan → find plateau
print("Running 2D parameter scan (K_PERIOD × K_SMOOTH)...")
grid = param_scan(df)
plateau_idx, nbr_mean = find_plateau(grid, window=1)
best_kp = K_PERIOD_SCAN[plateau_idx[0]]
best_ks = K_SMOOTH_SCAN[plateau_idx[1]]
print(f"Plateau: K_PERIOD={best_kp}, K_SMOOTH={best_ks} "
f"(neighborhood mean Sharpe={nbr_mean[plateau_idx]:.2f})")
plot_heatmap(grid, nbr_mean, plateau_idx, SYMBOL)
# Step 2: full backtest with plateau parameters
K_PERIOD, K_SMOOTH = best_kp, best_ks
result = run_backtest(df, k_period=K_PERIOD, k_smooth=K_SMOOTH, d_smooth=D_SMOOTH)
print(f"\nResults — KD({K_PERIOD},{K_SMOOTH},{D_SMOOTH}):")
print(f" 總報酬率 : {result['total_return']*100:.1f}%")
print(f" 年化報酬率 : {result['ann_ret']*100:.1f}%")
print(f" 年化波動度 : {result['ann_vol']*100:.1f}%")
print(f" Sharpe Ratio : {result['sharpe']:.2f}")
print(f" 最大回撤 (MDD) : {result['max_dd']*100:.1f}%")
print(f" 勝率 : {result['win_rate']*100:.1f}%")
print(f" 平均獲利 (per trade): {result['avg_win']*100:.2f}%")
print(f" 平均虧損 (per trade): {result['avg_loss']*100:.2f}%")
print(f" 總交易次數 : {result['n_trades']}")
print(f" 平均年交易次數 : {result['avg_trades_yr']:.1f}")
regime_analysis(df, result)
plot_regime(df, result, SYMBOL)
plot_pnl(df, result, SYMBOL)
```
---
## Parameters
| Parameter | Default | Notes |
|---|---|---|
| `K_PERIOD` | `9` | Stochastic lookback — lower = faster, more signals |
| `K_SMOOTH` | `3` | Slow %K smoothing (SMA) — higher = smoother |
| `D_SMOOTH` | `3` | %D smoothing (SMA of slow %K) — fixed at 3 in scan |
| `PERIOD` | `1h` | Kline timeframe |
| `VOL_WINDOW` | `720` | 30 days × 24h rolling vol |
| `TARGET_VOL` | `0.30` | 30% annualized target |
| `MAX_LEV` | `2.0` | Position size cap |
| `FEE` | `0.0005` | 0.05% per side (taker fee) |
---
## Notes
- **Long only** — no short positions
- **Parameter scan** sweeps K_PERIOD (5/9/14/21/34/55) × K_SMOOTH (2/3/5/8/13); D_SMOOTH is fixed at 3. The plateau selection (neighborhood mean Sharpe) is preferred over the single best cell — see HC backtest example for explanation
- **OB/OS filter (optional):** only enter golden cross when %K < 20 (oversold zone) for a stricter variant. Not implemented by default — add `and ki < 20` to the golden cross condition in `_kd_signal_loop`
- **Smoothing method:** this example uses SMA for both %K and %D, matching the classic formula. EWM (`pd.Series.ewm(span=k_smooth)`) gives more weight to recent bars and is common in real-time systems; swap in if preferred
- **Performance stack:** same as HC backtest — rolling vol via Pandas rolling (C/Cython), crossover loop via `@numba.njit` (stateful, can't vectorize), everything else NumPy vectorized
### Live Execution Timing
Backtest uses `fwd_ret[i] = (close[i+1] - close[i]) / close[i]`, meaning the order is filled at bar i's close price — the same bar where the crossover fires.
In live trading:
```
bar i closes
→ KD recomputes with new close
→ crossover detected → place market order immediately
→ fill ≈ bar i+1 open (for BTC 1h this is effectively bar i close)
```
Do NOT wait for bar i+1 to close. Waiting an extra bar introduces one-bar slippage that compounds over hundreds of trades and causes live PnL to diverge from backtest.
### KD vs RSI
KD (Stochastic) measures where the close sits within the recent high-low range; RSI measures the speed of price change. KD tends to generate more signals and responds faster to reversals, making it better suited for range-bound conditions. In strong trending markets, frequent death-cross exits can cut profitable trades short — consider adding a trend filter (e.g. price > MA200 to only trade bull regime signals) if regime analysis shows poor bear/high-vol performance.
FILE:examples/backtest-validation-mcpt-oos.md
# Example: Strategy Validation — MCPT + Out-of-Sample (KD vs Taker Intensity)
## What This Does
Validates two DOGE 1h long-only strategies side by side using:
1. **IS (In-Sample) Parameter Optimization** — 2D param scan on the first 2 years to find the most robust (plateau) parameters for each strategy
2. **OOS (Out-of-Sample) Validation** — run the IS-selected params on the held-out final year; a strategy with real edge should degrade gracefully, not collapse
3. **MCPT (Monte Carlo Permutation Test)** — shuffle the position array 1000 times, recompute Sharpe on each shuffle; p-value = fraction of shuffled Sharpes ≥ actual OOS Sharpe; p < 0.05 means the timing adds statistically significant value
**Strategies compared:**
| | KD Stochastic | Taker Intensity (多空力道) |
|---|---|---|
| Signal source | Price-derived (OHLCV) | Market microstructure (Blave alpha) |
| Entry | K crosses above D | alpha > ENTRY_TH |
| Exit | K crosses below D | alpha < EXIT_TH |
| Params scanned | K_PERIOD × K_SMOOTH | ENTRY_TH × EXIT_TH |
Same DOGE 1h data, same vol-targeting, same fees — apples-to-apples.
---
## Data Required
```
GET /kline?symbol=DOGEUSDT&period=1h (for KD + fwd_ret)
GET /taker_intensity/get_alpha?symbol=DOGEUSDT&period=1h (for TI signal)
```
IS: trailing 3 → 1 year ago | OOS: trailing 1 year → today
---
## Full Code
```python
import numpy as np
import pandas as pd
import requests
import matplotlib.pyplot as plt
import numba
from datetime import datetime, timedelta, timezone
from dotenv import dotenv_values
_env = dotenv_values()
# ── Date Ranges ───────────────────────────────────────────────────────────────
_now = datetime.now(timezone.utc)
END_DATE = _now.strftime("%Y-%m-%d")
OOS_START = (_now - timedelta(days=365)).strftime("%Y-%m-%d")
IS_START = (_now - timedelta(days=3 * 365)).strftime("%Y-%m-%d")
# IS : [IS_START, OOS_START) — 2 years
# OOS: [OOS_START, END_DATE) — 1 year
# ── Strategy Params ───────────────────────────────────────────────────────────
# KD
KD_PERIOD_SCAN = [5, 9, 14, 21, 34]
KD_SMOOTH_SCAN = [2, 3, 5, 8]
D_SMOOTH = 3
# TI (same threshold grid as HC example)
TI_THRESHOLDS = [-2, -1.5, -1, -0.5, 0, 0.5, 1, 1.5, 2]
# Shared backtest settings
TARGET_VOL = 0.30
MAX_LEV = 2.0
VOL_WINDOW = 720 # 30 days × 24h
HOURS_PER_YEAR = 8760
FEE = 0.0005
N_PERMUTATIONS = 2000
API_BASE = "https://api.blave.org"
API_KEY = _env["blave_api_key"]
API_SECRET = _env["blave_secret_key"]
HEADERS = {"api-key": API_KEY, "secret-key": API_SECRET}
# ── Fetch ─────────────────────────────────────────────────────────────────────
def _fetch_year_chunks(endpoint, params):
s = datetime.strptime(params["start_date"], "%Y-%m-%d")
e = datetime.strptime(params["end_date"], "%Y-%m-%d")
out = []
while s < e:
chunk_end = min(s + timedelta(days=365), e)
r = requests.get(f"{API_BASE}/{endpoint}", headers=HEADERS, params={
**params,
"start_date": s.strftime("%Y-%m-%d"),
"end_date": chunk_end.strftime("%Y-%m-%d"),
}, timeout=20)
r.raise_for_status()
out.append(r.json())
s = chunk_end
return out
def load_kline(start, end):
chunks = _fetch_year_chunks("kline", {"symbol": "DOGEUSDT", "period": "1h",
"start_date": start, "end_date": end})
rows = [row for chunk in chunks for row in chunk]
df = pd.DataFrame(rows, columns=["time", "open", "high", "low", "close"])
df["time"] = pd.to_datetime(df["time"], unit="s", utc=True)
df = df.set_index("time").sort_index().drop_duplicates()
for col in ["open", "high", "low", "close"]:
df[col] = df[col].astype(float)
return df
def load_ti(start, end):
chunks = _fetch_year_chunks("taker_intensity/get_alpha",
{"symbol": "DOGEUSDT", "period": "1h",
"start_date": start, "end_date": end})
ts, alphas = [], []
for chunk in chunks:
data = chunk.get("data", {})
ts.extend(data.get("timestamp", []))
alphas.extend(data.get("alpha", []))
df = pd.DataFrame({"time": pd.to_datetime(ts, unit="s", utc=True), "ti": alphas})
df = df.set_index("time").sort_index().drop_duplicates()
df["ti"] = pd.to_numeric(df["ti"], errors="coerce")
return df
# ── Signal Computation ────────────────────────────────────────────────────────
def compute_kd(df, k_period, k_smooth, d_smooth=D_SMOOTH):
low_min = df["low"].rolling(k_period).min()
high_max = df["high"].rolling(k_period).max()
denom = high_max - low_min
raw_k = pd.Series(
np.where(denom > 0, (df["close"] - low_min) / denom * 100, 50.0),
index=df.index,
)
slow_k = raw_k.rolling(k_smooth).mean()
d = slow_k.rolling(d_smooth).mean()
return slow_k.values, d.values
@numba.njit(cache=True)
def _kd_signal_loop(k, d):
n = len(k); position = np.zeros(n); in_pos = False
for i in range(1, n):
ki, di, kp, dp = k[i], d[i], k[i-1], d[i-1]
if np.isnan(ki) or np.isnan(di) or np.isnan(kp) or np.isnan(dp):
position[i] = 1.0 if in_pos else 0.0; continue
if not in_pos and kp <= dp and ki > di: in_pos = True
elif in_pos and kp >= dp and ki < di: in_pos = False
position[i] = 1.0 if in_pos else 0.0
return position
@numba.njit(cache=True)
def _ti_signal_loop(signal, entry_th, exit_th):
n = len(signal); position = np.zeros(n); in_pos = False
for i in range(n):
if np.isnan(signal[i]):
position[i] = 1.0 if in_pos else 0.0; continue
if not in_pos and signal[i] > entry_th: in_pos = True
elif in_pos and signal[i] < exit_th: in_pos = False
position[i] = 1.0 if in_pos else 0.0
return position
# ── Core Backtest Engine (shared) ─────────────────────────────────────────────
def _run(close, position):
"""Given close prices and a position array, return strategy metrics."""
n = len(close)
log_ret = np.concatenate([[0.0], np.log(close[1:] / close[:-1])])
fwd_ret = np.empty(n)
fwd_ret[:-1] = np.diff(close) / close[:-1]
fwd_ret[-1] = 0.0
realized_vol = pd.Series(log_ret).rolling(VOL_WINDOW).std().values * np.sqrt(HOURS_PER_YEAR)
vol_scalar = np.where(
(realized_vol > 0) & ~np.isnan(realized_vol),
np.clip(TARGET_VOL / realized_vol, 0, MAX_LEV),
1.0,
)
sized = position * vol_scalar
fee_cost = np.abs(np.diff(sized, prepend=0)) * FEE
strat_ret = sized * fwd_ret - fee_cost
r = strat_ret[~np.isnan(strat_ret)]
cum = np.cumprod(1 + r)
pk = np.maximum.accumulate(cum)
total_years = len(r) / HOURS_PER_YEAR
total_return = cum[-1] - 1
ann_ret = (1 + total_return) ** (1 / total_years) - 1
sharpe = (r.mean() / r.std()) * np.sqrt(HOURS_PER_YEAR) if r.std() > 0 else np.nan
max_dd = ((cum - pk) / pk).min()
return dict(sharpe=sharpe, ann_ret=ann_ret, max_dd=max_dd,
cum=cum, strat_ret=strat_ret, position=position)
# ── MCPT ──────────────────────────────────────────────────────────────────────
def mcpt(close, position, n=N_PERMUTATIONS):
"""
Permute the forward return series (not positions).
Position is held fixed, so fees and vol-scaling are identical for all permutations.
Null: the return periods selected by this strategy are no better than random.
p-value: fraction of permuted Sharpes >= actual OOS Sharpe.
Why not permute position?
A random shuffle of a binary 0/1 array produces ~N*p*(1-p) transitions vs the
strategy's much smaller number. With FEE=0.0005 that creates 30-40x more fee drag
on every permutation, forcing all permuted Sharpes deeply negative and producing
a biased p-value regardless of whether the strategy has real edge.
"""
log_ret = np.concatenate([[0.0], np.log(close[1:] / close[:-1])])
fwd_ret = np.concatenate([np.diff(close) / close[:-1], [0.0]])
realized_vol = pd.Series(log_ret).rolling(VOL_WINDOW).std().values * np.sqrt(HOURS_PER_YEAR)
vol_scalar = np.where(
(realized_vol > 0) & ~np.isnan(realized_vol),
np.clip(TARGET_VOL / realized_vol, 0, MAX_LEV),
1.0,
)
sized = position * vol_scalar
fee_cost = np.abs(np.diff(sized, prepend=0)) * FEE # identical for all permutations
def _sharpe_from_ret(ret):
sr = sized * ret - fee_cost
r = sr[~np.isnan(sr)]
return (r.mean() / r.std()) * np.sqrt(HOURS_PER_YEAR) if r.std() > 0 else np.nan
actual = _sharpe_from_ret(fwd_ret)
dist = np.array([_sharpe_from_ret(np.random.permutation(fwd_ret)) for _ in range(n)])
p_value = float((dist >= actual).mean())
return actual, p_value, dist
# ── IS Param Scans ────────────────────────────────────────────────────────────
def find_plateau(grid, row_vals, col_vals, window=1):
rows, cols = grid.shape
nbr_mean = np.full((rows, cols), np.nan)
for i in range(rows):
for j in range(cols):
if np.isnan(grid[i, j]): continue
nb = [grid[i+di, j+dj]
for di in range(-window, window+1)
for dj in range(-window, window+1)
if 0 <= i+di < rows and 0 <= j+dj < cols
and not np.isnan(grid[i+di, j+dj])]
if nb: nbr_mean[i, j] = np.mean(nb)
best = np.unravel_index(np.nanargmax(nbr_mean), nbr_mean.shape)
return best, nbr_mean, row_vals[best[0]], col_vals[best[1]]
def kd_param_scan(df_kline):
grid = np.full((len(KD_PERIOD_SCAN), len(KD_SMOOTH_SCAN)), np.nan)
close = df_kline["close"].values.astype(np.float64)
for i, kp in enumerate(KD_PERIOD_SCAN):
for j, ks in enumerate(KD_SMOOTH_SCAN):
k_arr, d_arr = compute_kd(df_kline, kp, ks)
pos = _kd_signal_loop(k_arr, d_arr)
res = _run(close, pos)
if not np.isnan(res["sharpe"]): grid[i, j] = res["sharpe"]
return grid
def ti_param_scan(df_kline, df_ti):
df = df_kline[["close"]].join(df_ti[["ti"]], how="inner").dropna(subset=["close"])
close = df["close"].values.astype(np.float64)
ti = df["ti"].values.astype(np.float64)
n = len(TI_THRESHOLDS)
grid = np.full((n, n), np.nan)
for i, entry in enumerate(TI_THRESHOLDS):
for j, exit_ in enumerate(TI_THRESHOLDS):
if exit_ > entry: continue
pos = _ti_signal_loop(ti, entry, exit_)
res = _run(close, pos)
if not np.isnan(res["sharpe"]): grid[i, j] = res["sharpe"]
return grid, df
# ── Chart ─────────────────────────────────────────────────────────────────────
def plot_results(symbol, results):
"""
results: dict with keys "kd" and "ti", each containing:
is_res, oos_res, mcpt_actual, mcpt_pvalue, mcpt_dist,
is_dates, oos_dates, is_label, oos_label
"""
fig = plt.figure(figsize=(16, 13))
fig.suptitle(f"{symbol} — Strategy Validation: KD vs Taker Intensity",
fontsize=14, fontweight="bold", y=0.98)
gs = fig.add_gridspec(3, 2, hspace=0.45, wspace=0.3)
for col, (key, label, color) in enumerate([
("kd", "KD Stochastic", "#3498db"),
("ti", "Taker Intensity", "#e67e22"),
]):
r = results[key]
# Row 0: IS PnL
ax_is = fig.add_subplot(gs[0, col])
ax_is.plot(r["is_dates"], (r["is_res"]["cum"] - 1) * 100, color=color, lw=1.5)
ax_is.axhline(0, color="#888", lw=0.6, ls="--")
ax_is.set_title(
f"{label} — IS (2y)\n"
f"Sharpe={r['is_res']['sharpe']:.2f} "
f"Ann={r['is_res']['ann_ret']*100:.1f}% "
f"MDD={r['is_res']['max_dd']*100:.1f}%",
fontsize=10,
)
ax_is.set_ylabel("Return (%)")
ax_is.yaxis.set_major_formatter(plt.FuncFormatter(lambda x, _: f"{x:.0f}%"))
# Row 1: OOS PnL
ax_oos = fig.add_subplot(gs[1, col])
oos_color = "#2ecc71" if r["oos_res"]["sharpe"] > 0 else "#e74c3c"
ax_oos.plot(r["oos_dates"], (r["oos_res"]["cum"] - 1) * 100, color=oos_color, lw=1.5)
ax_oos.axhline(0, color="#888", lw=0.6, ls="--")
sig_str = f"p={r['mcpt_pvalue']:.3f} {'✅' if r['mcpt_pvalue'] < 0.05 else '❌'}"
ax_oos.set_title(
f"{label} — OOS (1y)\n"
f"Sharpe={r['oos_res']['sharpe']:.2f} "
f"Ann={r['oos_res']['ann_ret']*100:.1f}% "
f"MDD={r['oos_res']['max_dd']*100:.1f}% {sig_str}",
fontsize=10,
)
ax_oos.set_ylabel("Return (%)")
ax_oos.yaxis.set_major_formatter(plt.FuncFormatter(lambda x, _: f"{x:.0f}%"))
# Row 2: MCPT distribution
ax_mc = fig.add_subplot(gs[2, col])
ax_mc.hist(r["mcpt_dist"], bins=40, color="#95a5a6", alpha=0.7, edgecolor="white")
ax_mc.axvline(r["mcpt_actual"], color="#e74c3c", lw=2,
label=f"Actual={r['mcpt_actual']:.2f}")
pct = np.percentile(r["mcpt_dist"], 95)
ax_mc.axvline(pct, color="#f39c12", lw=1.5, ls="--",
label=f"95th pct={pct:.2f}")
ax_mc.set_title(
f"MCPT — OOS ({N_PERMUTATIONS} permutations)\np={r['mcpt_pvalue']:.3f} "
f"{'Significant (p<0.05)' if r['mcpt_pvalue'] < 0.05 else 'Not significant'}",
fontsize=10,
)
ax_mc.set_xlabel("Permuted Sharpe"); ax_mc.set_ylabel("Count")
ax_mc.legend(fontsize=9)
fname = f"{symbol}_mcpt_oos.png"
plt.savefig(fname, dpi=150, bbox_inches="tight")
plt.show()
print(f"Saved: {fname}")
# ── Main ──────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
print(f"IS : {IS_START} → {OOS_START}")
print(f"OOS: {OOS_START} → {END_DATE}")
# ── Load data ─────────────────────────────────────────────────────────────
print("\nLoading IS data...")
kline_is = load_kline(IS_START, OOS_START)
ti_is = load_ti(IS_START, OOS_START)
print(f" kline IS : {len(kline_is)} bars")
print("Loading OOS data...")
kline_oos = load_kline(OOS_START, END_DATE)
ti_oos = load_ti(OOS_START, END_DATE)
print(f" kline OOS: {len(kline_oos)} bars")
# ── KD: IS param scan ────────────────────────────────────────────────────
print("\n── KD: IS param scan ──")
kd_grid = kd_param_scan(kline_is)
kd_idx, _, best_kp, best_ks = find_plateau(
kd_grid, KD_PERIOD_SCAN, KD_SMOOTH_SCAN)
print(f" Plateau: K_PERIOD={best_kp}, K_SMOOTH={best_ks} "
f"IS Sharpe={kd_grid[kd_idx]:.2f}")
# ── KD: IS full backtest ──────────────────────────────────────────────────
kd_k_is, kd_d_is = compute_kd(kline_is, best_kp, best_ks)
kd_pos_is = _kd_signal_loop(kd_k_is, kd_d_is)
kd_is_res = _run(kline_is["close"].values.astype(np.float64), kd_pos_is)
# ── KD: OOS validation ────────────────────────────────────────────────────
kd_k_oos, kd_d_oos = compute_kd(kline_oos, best_kp, best_ks)
kd_pos_oos = _kd_signal_loop(kd_k_oos, kd_d_oos)
kd_oos_res = _run(kline_oos["close"].values.astype(np.float64), kd_pos_oos)
# ── KD: MCPT on OOS ──────────────────────────────────────────────────────
print(" Running MCPT (KD OOS)...")
kd_actual, kd_pvalue, kd_dist = mcpt(
kline_oos["close"].values.astype(np.float64), kd_pos_oos)
# ── TI: IS param scan ────────────────────────────────────────────────────
print("\n── TI: IS param scan ──")
ti_grid, df_ti_is = ti_param_scan(kline_is, ti_is)
ti_idx, _, best_entry, best_exit = find_plateau(
ti_grid, TI_THRESHOLDS, TI_THRESHOLDS)
print(f" Plateau: entry={best_entry}, exit={best_exit} "
f"IS Sharpe={ti_grid[ti_idx]:.2f}")
# ── TI: IS full backtest ──────────────────────────────────────────────────
ti_close_is = df_ti_is["close"].values.astype(np.float64)
ti_signal_is = df_ti_is["ti"].values.astype(np.float64)
ti_pos_is = _ti_signal_loop(ti_signal_is, best_entry, best_exit)
ti_is_res = _run(ti_close_is, ti_pos_is)
# ── TI: OOS validation ────────────────────────────────────────────────────
df_ti_oos = kline_oos[["close"]].join(ti_oos[["ti"]], how="inner").dropna(subset=["close"])
ti_close_oos = df_ti_oos["close"].values.astype(np.float64)
ti_signal_oos = df_ti_oos["ti"].values.astype(np.float64)
ti_pos_oos = _ti_signal_loop(ti_signal_oos, best_entry, best_exit)
ti_oos_res = _run(ti_close_oos, ti_pos_oos)
# ── TI: MCPT on OOS ──────────────────────────────────────────────────────
print(" Running MCPT (TI OOS)...")
ti_actual, ti_pvalue, ti_dist = mcpt(ti_close_oos, ti_pos_oos)
# ── Summary table ─────────────────────────────────────────────────────────
kd_deg = kd_oos_res["sharpe"] / kd_is_res["sharpe"] if kd_is_res["sharpe"] != 0 else np.nan
ti_deg = ti_oos_res["sharpe"] / ti_is_res["sharpe"] if ti_is_res["sharpe"] != 0 else np.nan
hdr = f" {'Metric':<28} {'KD':>12} {'TI':>12}"
sep = "─" * len(hdr)
print(f"\n{sep}\n Validation Summary\n{sep}")
print(hdr); print(sep)
rows = [
("IS Sharpe", f"{kd_is_res['sharpe']:.2f}", f"{ti_is_res['sharpe']:.2f}"),
("OOS Sharpe", f"{kd_oos_res['sharpe']:.2f}", f"{ti_oos_res['sharpe']:.2f}"),
("Degradation (OOS/IS)", f"{kd_deg:.2f}", f"{ti_deg:.2f}"),
("IS Ann Return", f"{kd_is_res['ann_ret']*100:.1f}%", f"{ti_is_res['ann_ret']*100:.1f}%"),
("OOS Ann Return", f"{kd_oos_res['ann_ret']*100:.1f}%", f"{ti_oos_res['ann_ret']*100:.1f}%"),
("IS MDD", f"{kd_is_res['max_dd']*100:.1f}%", f"{ti_is_res['max_dd']*100:.1f}%"),
("OOS MDD", f"{kd_oos_res['max_dd']*100:.1f}%", f"{ti_oos_res['max_dd']*100:.1f}%"),
("MCPT p-value (OOS)", f"{kd_pvalue:.3f}", f"{ti_pvalue:.3f}"),
("MCPT significant?", "✅" if kd_pvalue < 0.05 else "❌", "✅" if ti_pvalue < 0.05 else "❌"),
]
for label, kd_val, ti_val in rows:
print(f" {label:<28} {kd_val:>12} {ti_val:>12}")
print(sep)
print(" Degradation > 0.5 is acceptable; < 0 means strategy broke down in OOS.")
print(" MCPT p < 0.05: entry/exit timing is statistically significant.")
# ── Chart ─────────────────────────────────────────────────────────────────
plot_results("DOGEUSDT", {
"kd": dict(
is_res=kd_is_res, oos_res=kd_oos_res,
mcpt_actual=kd_actual, mcpt_pvalue=kd_pvalue, mcpt_dist=kd_dist,
is_dates=kline_is.index[~np.isnan(kd_is_res["strat_ret"])],
oos_dates=kline_oos.index[~np.isnan(kd_oos_res["strat_ret"])],
),
"ti": dict(
is_res=ti_is_res, oos_res=ti_oos_res,
mcpt_actual=ti_actual, mcpt_pvalue=ti_pvalue, mcpt_dist=ti_dist,
is_dates=df_ti_is.index[~np.isnan(ti_is_res["strat_ret"])],
oos_dates=df_ti_oos.index[~np.isnan(ti_oos_res["strat_ret"])],
),
})
```
---
## Output Interpretation
### Degradation Ratio (OOS Sharpe / IS Sharpe)
| Ratio | Meaning |
|---|---|
| > 0.8 | Excellent — almost no degradation |
| 0.5 – 0.8 | Acceptable — typical for real strategies |
| 0.2 – 0.5 | Weak — likely overfitted to IS period |
| < 0 | Failed — strategy broke down in OOS |
### MCPT p-value
| p-value | Meaning |
|---|---|
| < 0.01 | Highly significant — timing very unlikely to be random |
| 0.01 – 0.05 | Significant — timing adds measurable value |
| 0.05 – 0.10 | Borderline — marginal evidence |
| > 0.10 | Not significant — strategy may be luck |
### What to look for
- A strategy that passes both tests (degradation > 0.5 AND p < 0.05) has genuine, robust edge.
- High IS Sharpe + poor OOS = overfitted. Common with technical indicators on short IS windows.
- Low IS Sharpe + maintains in OOS = underfit but potentially real. Widen the param search.
- MCPT passing but high OOS degradation = edge exists but parameter selection was too specific to IS data. Use the plateau (not peak) cell, or widen the plateau window.
---
## Notes
- **MCPT method — position permutation:** shuffles the entry/exit timing (position array) while keeping the return series fixed. Tests: "given BTC's actual return distribution, does the timing of this strategy's trades matter?" This is comparable across both strategies because neither strategy has its signal permuted — only when it acts.
- **Why not permute returns?** For KD, the signal is derived from price; permuting returns changes the price path and therefore the KD signal itself, making it circular. Position permutation is clean for both strategies.
- **IS/OOS split is time-based, not random.** Shuffling time order for a financial backtest introduces look-ahead bias. The IS period always precedes OOS.
- **Walk-forward extension:** for more rigorous validation, replace the single IS/OOS split with multiple expanding windows (e.g., 12-month IS → 3-month OOS, rolling forward). Not implemented here to keep the example readable.
- **Both strategies use the same backtest engine** (`_run`), same vol-targeting, same fee model — the only difference is how `position` is computed.
FILE:examples/bitfinex-auto-lending.md
# Example: Bitfinex Auto-Lending
Automatically lend idle funds on Bitfinex. Adapts to market conditions:
- **Period:** FRR high → allocate more to long periods (lock in). FRR low → stay short (flexibility)
- **Rate:** median to P75 ladder per period, floored by order book best bid
- **Large amounts:** split across multiple periods and rate levels for better fill
---
## Dependencies
```bash
pip install requests
```
---
## Code
```python
#!/usr/bin/env python3
"""
Bitfinex 自動放貸:
- FRR 決定天期分配(高利率→長期為主,低利率→短期為主)
- 大單拆成梯形掛單:多天期 × 多利率(median → P75)
- 不低於 order book best bid
cron 每 15 分鐘執行一次
"""
import hmac, hashlib, json, time
from datetime import datetime, timezone
import requests
# ── Config ────────────────────────────────────────────────────────────────
BITFINEX_API_KEY = "YOUR_API_KEY"
BITFINEX_API_SECRET = "YOUR_API_SECRET"
CURRENCY = "UST" # UST = USDT on Bitfinex
MIN_OFFER = 150.0 # Bitfinex minimum: $150 equivalent
DEMAND_THRESHOLD = 50_000 # book: require 50K+ cumulative bid depth
TRANCHES_PER_PERIOD = 3 # rate levels per period (median → P75)
BASE_URL = "https://api.bitfinex.com"
PUB_URL = "https://api-pub.bitfinex.com"
# FRR → period allocation weights
# High FRR: weight long (lock in). Low FRR: weight short (stay flexible).
PERIOD_WEIGHTS = {
10.0: {2: 0.1, 30: 0.2, 120: 0.7}, # FRR ≥ 10%
6.0: {2: 0.2, 7: 0.2, 30: 0.3, 120: 0.3}, # FRR ≥ 6%
3.0: {2: 0.4, 7: 0.3, 30: 0.2, 120: 0.1}, # FRR ≥ 3%
0.0: {2: 0.7, 7: 0.2, 30: 0.1}, # FRR < 3%
}
# ── Auth ──────────────────────────────────────────────────────────────────
def bfx_request(path, body=None):
body = body or {}
nonce = str(int(time.time() * 1_000_000))
body_json = json.dumps(body)
sig = hmac.new(
BITFINEX_API_SECRET.encode(),
f"/api/{path}{nonce}{body_json}".encode(),
hashlib.sha384,
).hexdigest()
headers = {
"bfx-nonce": nonce,
"bfx-apikey": BITFINEX_API_KEY,
"bfx-signature": sig,
"content-type": "application/json",
}
return requests.post(f"{BASE_URL}/{path}", headers=headers,
data=body_json, timeout=15)
# ── Market Analysis ───────────────────────────────────────────────────────
def fetch_trades_3d(symbol="fUST"):
"""Fetch 3 days of funding trades with pagination."""
end = int(time.time() * 1000)
start = end - 3 * 24 * 60 * 60 * 1000
all_trades = []
cursor = end
while cursor > start:
r = requests.get(
f"{PUB_URL}/v2/trades/{symbol}/hist",
params={"limit": 10000, "start": start, "end": cursor, "sort": -1},
timeout=15,
)
r.raise_for_status()
batch = r.json()
if not batch:
break
all_trades.extend(batch)
cursor = batch[-1][1] - 1
time.sleep(0.5)
seen = set()
return [t for t in all_trades if t[0] not in seen and not seen.add(t[0])]
def analyze_rates(trades, period):
"""Compute rate statistics for a specific period."""
rates = sorted([t[3] for t in trades if t[4] == period])
if not rates:
return None
n = len(rates)
return {
"count": n,
"min": rates[0],
"max": rates[-1],
"median": rates[n // 2],
"p75": rates[3 * n // 4],
}
def get_frr(symbol="fUST"):
r = requests.get(f"{PUB_URL}/v2/ticker/{symbol}", timeout=10)
r.raise_for_status()
return r.json()[0]
def get_book_best_bid(symbol="fUST", demand_threshold=50_000):
"""Rate where cumulative borrower demand ≥ threshold."""
r = requests.get(f"{PUB_URL}/v2/book/{symbol}/P0",
params={"len": 250}, timeout=10)
r.raise_for_status()
bids = sorted([e for e in r.json() if e[3] > 0], key=lambda x: -x[0])
if not bids:
return None, 0
cumulative = 0
for b in bids:
cumulative += b[3]
if cumulative >= demand_threshold:
return b[0], cumulative
return bids[-1][0], cumulative
def choose_weights(frr):
"""Pick period allocation weights based on FRR."""
frr_ann = frr * 365 * 100
for threshold in sorted(PERIOD_WEIGHTS.keys(), reverse=True):
if frr_ann >= threshold:
return PERIOD_WEIGHTS[threshold], threshold
return PERIOD_WEIGHTS[0.0], 0.0
# ── Account ───────────────────────────────────────────────────────────────
def get_available_balance(currency="UST"):
r = bfx_request("v2/auth/r/wallets")
r.raise_for_status()
for w in r.json():
if w[0] == "funding" and w[1] == currency:
return float(w[4] or w[2] or 0)
return 0.0
def get_active_offers(symbol=None):
path = f"v2/auth/r/funding/offers/{symbol}" if symbol else "v2/auth/r/funding/offers"
r = bfx_request(path)
r.raise_for_status()
return r.json()
def get_active_credits(symbol=None):
path = f"v2/auth/r/funding/credits/{symbol}" if symbol else "v2/auth/r/funding/credits"
r = bfx_request(path)
r.raise_for_status()
return r.json()
# ── Execute ───────────────────────────────────────────────────────────────
def submit_offer(currency, amount, rate, period):
body = {
"type": "LIMIT",
"symbol": f"f{currency}",
"amount": str(round(amount, 8)),
"rate": str(rate),
"period": period,
}
r = bfx_request("v2/auth/w/funding/offer/submit", body)
r.raise_for_status()
data = r.json()
if isinstance(data, list) and data[0] == "error":
return False, data[2]
status = data[6] if len(data) > 6 else ""
return status == "SUCCESS", data
def cancel_offer(offer_id):
r = bfx_request("v2/auth/w/funding/offer/cancel", {"id": offer_id})
r.raise_for_status()
data = r.json()
if isinstance(data, list) and data[0] == "error":
return False, data[2]
return True, data
# ── Build Ladder ──────────────────────────────────────────────────────────
def build_ladder(total_amount, weights, all_trades, book_rate, frr):
"""
Build a list of (period, rate, amount) offers.
Splits across periods by weight, then within each period
spreads rates from median to P75 in TRANCHES_PER_PERIOD steps.
All rates floored by book_rate.
"""
ann = lambda r: f"{r * 365 * 100:.2f}%"
offers = []
for period, weight in weights.items():
period_amount = total_amount * weight
if period_amount < MIN_OFFER:
continue
stats = analyze_rates(all_trades, period)
if not stats or stats["count"] < 5:
# Thin market — single offer at max(FRR, book)
rate = max(frr, book_rate) if book_rate else frr
offers.append((period, rate, period_amount))
continue
median = stats["median"]
p75 = stats["p75"]
# How many tranches can we fit? (each ≥ MIN_OFFER)
n = min(TRANCHES_PER_PERIOD, int(period_amount // MIN_OFFER))
n = max(n, 1)
tranche_amount = period_amount / n
for i in range(n):
# Linear interpolation: median → P75
frac = i / (n - 1) if n > 1 else 0.5
rate = median + frac * (p75 - median)
# Floor: don't go below book best bid
if book_rate:
rate = max(rate, book_rate)
offers.append((period, rate, tranche_amount))
return offers
# ── Main ──────────────────────────────────────────────────────────────────
def run():
symbol = f"f{CURRENCY}"
ann = lambda r: f"{r * 365 * 100:.2f}%"
# 1. FRR → allocation weights
frr = get_frr(symbol)
frr_ann = frr * 365 * 100
weights, threshold = choose_weights(frr)
print(f"FRR: {ann(frr)} (≥ {threshold}%)")
print(f"Allocation: {', '.join(f'{p}d={w:.0%}' for p, w in weights.items())}")
# 2. Fetch market data
print(f"Fetching 3d trades...")
trades = fetch_trades_3d(symbol)
print(f" {len(trades)} trades")
book_rate, book_depth = get_book_best_bid(symbol, DEMAND_THRESHOLD)
if book_rate:
print(f" Book bid (≥{DEMAND_THRESHOLD/1000:.0f}K): {ann(book_rate)} ({book_depth:,.0f} UST)")
# 3. Check balance
available = get_available_balance(CURRENCY)
print(f"\nAvailable {CURRENCY}: {available:.2f}")
if available < MIN_OFFER:
print(f"Below minimum ({MIN_OFFER})")
return
# 4. Cancel all existing offers (rebuild ladder fresh each run)
offers = get_active_offers(symbol)
if offers:
print(f"Cancelling {len(offers)} existing offers...")
for o in offers:
cancel_offer(o[0])
available += abs(o[4])
time.sleep(0.3)
# 5. Build ladder
ladder = build_ladder(available, weights, trades, book_rate, frr)
if not ladder:
print("No valid offers to submit")
return
# 6. Submit
print(f"\n{'Period':>6} {'Rate':>10} {'Amount':>12}")
print("-" * 32)
submitted = 0
for period, rate, amount in ladder:
if amount < MIN_OFFER:
continue
print(f"{period:>4}d {ann(rate):>10} {amount:>11,.0f}", end="")
ok, result = submit_offer(CURRENCY, amount, rate, period)
if ok:
offer = result[4][0] if isinstance(result[4][0], list) else result[4]
print(f" ✓ {offer[0]}")
submitted += 1
else:
print(f" ✗ {result}")
time.sleep(0.3)
print(f"\nSubmitted {submitted} offers")
# 7. Summary
credits = get_active_credits(symbol)
if credits:
total_lent = sum(abs(c[5]) for c in credits)
rates = [c[11] for c in credits]
periods = [c[12] for c in credits]
print(f"Lent out: {total_lent:,.0f} {CURRENCY} in {len(credits)} credits")
print(f" Rates: {ann(min(rates))} — {ann(max(rates))}")
print(f" Periods: {min(periods)}d — {max(periods)}d")
if __name__ == "__main__":
run()
```
---
## How the Ladder Works
### Period allocation (FRR decides)
| FRR | 2d | 7d | 30d | 120d |
|---|---|---|---|---|
| ≥ 10% | 10% | — | 20% | **70%** |
| 6–10% | 20% | 20% | 30% | 30% |
| 3–6% | **40%** | 30% | 20% | 10% |
| < 3% | **70%** | 20% | 10% | — |
### Rate ladder (per period)
Within each period, the amount is split into `TRANCHES_PER_PERIOD` (default 3) offers with rates linearly spaced from **median** to **P75** of 3-day historical trades:
```
Tranche 1: median (most competitive, fills first)
Tranche 2: (median+P75)/2 (mid)
Tranche 3: P75 (highest rate, fills last)
```
**All rates floored by order book best bid** — never offer below what borrowers are already willing to pay.
### Example output (10,000 UST at FRR 8.87%)
```
FRR: 8.87% (≥ 6%)
Allocation: 2d=20%, 7d=20%, 30d=30%, 120d=30%
Period Rate Amount
--------------------------------
2d 5.20% 667 ✓ (book floor)
2d 5.20% 667 ✓ (book floor)
2d 5.20% 667 ✓ (book floor)
7d 5.20% 667 ✓ (book floor)
7d 5.20% 667 ✓ (book floor)
7d 5.20% 667 ✓ (book floor)
30d 5.20% 1,000 ✓ (median, floored by book)
30d 7.06% 1,000 ✓ (mid)
30d 9.44% 1,000 ✓ (P75)
120d 9.69% 1,000 ✓ (median)
120d 9.84% 1,000 ✓ (mid)
120d 10.00% 1,000 ✓ (P75)
Submitted 12 offers
```
Short periods (2d, 7d) hit the book floor so all tranches are at the same rate. Long periods (30d, 120d) have natural spread between median and P75.
---
## Small amounts (< $1,000)
If total amount is small, fewer tranches fit (each must be ≥ $150). The script automatically reduces tranches:
- $150–$449: 1 offer at best single period
- $450–$899: up to 3 offers
- $900+: full ladder
---
## Deployment
```bash
*/15 * * * * cd /path/to/project && python3 bitfinex_auto_lend.py >> /var/log/bfx_lend.log 2>&1
```
Each run:
1. FRR → pick period weights
2. Fetch 3d trades → rate stats per period
3. Fetch order book → floor rate
4. Cancel all existing offers (rebuild fresh)
5. Build ladder → submit offers
6. Report lent-out credits
---
## Customising
### More aggressive (lock long earlier)
```python
PERIOD_WEIGHTS = {
8.0: {2: 0.1, 30: 0.2, 120: 0.7},
5.0: {2: 0.2, 7: 0.2, 30: 0.3, 120: 0.3},
2.0: {2: 0.4, 7: 0.3, 30: 0.2, 120: 0.1},
0.0: {2: 0.7, 7: 0.2, 30: 0.1},
}
```
### More tranches (finer rate spread)
```python
TRANCHES_PER_PERIOD = 5 # 5 levels from median to P75
```
---
## Notes
- **Minimum offer:** $150 USD equivalent per tranche
- **Rebuild vs update:** each run cancels all offers and rebuilds. Simple and idempotent — no stale offer tracking needed
- **Book floor:** prevents offering below current borrower demand. Short periods often have their median below the book bid, so they get raised
- **Thin periods:** if a period has <5 trades in 3 days, a single offer at max(FRR, book) is used instead of a ladder
- **Amount sign:** positive = lend, negative = borrow
FILE:examples/blave-alpha-screening.md
# Example: Blave Alpha Screening — Find High-Conviction Small-Cap Tokens
## Strategy
Screen for small-cap tokens where smart money is quietly accumulating:
- **Small cap** — market cap percentile ≤ 50 (bottom half of all listed coins)
- **High Holder Concentration** (籌碼集中) — alpha > 0.5, long side concentrated
- **High Whale Activity** (巨鯨警報) — 24h OI score > 0.5, large players moving in
- **Output** — top 10 ranked by combined signal strength
When both signals are high on a small-cap coin, it often means accumulation is happening before a sharp move. The coin may already be moving (+24h%) or still coiling — both are worth watching.
---
## Step 1: Pull Alpha Table
```
GET /alpha_table
```
One request covers all symbols. Each symbol contains all indicator values plus `statistics`.
---
## Step 2: Screen
```python
results = []
for symbol, d in alpha_table['data'].items():
try:
mc_pct = float(d.get('market_cap_percentile', {}).get('-', ''))
hc = float(d.get('holder_concentration', {}).get('-', ''))
wh_24h = float(d.get('whale_hunter', {}).get('24h-score_oi', ''))
stats = d.get('statistics', {})
mc_usd = float(d.get('market_cap', {}).get('-', 0) or 0)
up_prob = float(stats.get('up_prob', 0) or 0)
exp_val = float(stats.get('exp_value', 0) or 0)
if not stats.get('is_data_sufficient', False):
continue
# Filter
if mc_pct <= 50 and hc > 0.5 and wh_24h > 0.5:
results.append({
'symbol': symbol,
'hc': hc,
'whale': wh_24h,
'mc_pct': mc_pct,
'mc_usd': mc_usd,
'up_prob': up_prob,
'exp_value': exp_val,
'price_chg_24h': d.get('price_change', {}).get('24h', None),
})
except (ValueError, TypeError):
continue
# Rank by combined signal strength
results.sort(key=lambda x: x['hc'] + x['whale'], reverse=True)
top10 = results[:10]
```
---
## Step 3: Output
Present as a ranked table:
| Rank | Symbol | HC | Whale | MC% | Market Cap | Up Prob | Exp Value | 24h Change |
|---|---|---|---|---|---|---|---|---|
| 1 | TRU | 8.16 | 14.66 | 7.4% | $8M | 2.2% | -3.78% | +72.0% |
| 2 | KOMA | 4.91 | 5.58 | 1.7% | $4M | 37.7% | -0.79% | +56.3% |
| 3 | HIPPO | 3.09 | 7.46 | 3.2% | $6M | 42.1% | -0.20% | -4.0% |
**Reading the results:**
- **Already moving** (large 24h change + strong signals) — accumulation phase may be ending, entering now is chasing
- **Not yet moved** (flat 24h + strong signals) — potential setup still coiling, higher risk/reward
- `up_prob` and `exp_value` from `statistics` give a historical base rate — use as context, not as a trigger
---
## Alpha Scale Reference
| Alpha Value | Holder Concentration | Whale Hunter |
|---|---|---|
| > 3 | Over Concentrated (long) | Overly Bullish |
| 2 – 3 | Highly Concentrated (long) | Highly Bullish |
| 0.5 – 2 | Concentrated (long) | Bullish |
| -0.5 – 0.5 | Neutral | Neutral |
| < -0.5 | Concentrated (short) | Bearish |
---
## Optional: Add Taker Intensity for Confirmation
If you want to confirm buying pressure is actually present (not just silent accumulation):
```python
wh_vol = float(d.get('whale_hunter', {}).get('24h-score_volume', ''))
taker = float(d.get('taker_intensity', {}).get('24h', '')) # field name may vary
# Add to filter:
if taker > 0: # net buying pressure
...
```
---
## Risk Notes
- Small-cap coins are illiquid — large slippage on entry/exit
- Strong signals on coins that already pumped 50%+ today = chasing; look at coins with signals but flat price instead
- Always check funding rate (`funding_rate` field in alpha_table) — high positive funding on a small cap = crowded long, squeeze risk
- Use position sizing accordingly: higher signal strength does not mean lower risk
FILE:examples/btc-etf-flow-monitor.md
# Example: Bitcoin ETF Flow Monitor — Track Institutional Accumulation
Track daily Bitcoin spot ETF fund flows (IBIT, FBTC, GBTC, etc.) from Farside Investors. Detect when BlackRock and other institutions are accumulating or distributing.
---
## Dependencies
```bash
pip install curl-cffi beautifulsoup4
```
---
## Code
```python
import re
from curl_cffi import requests as cf_requests
from bs4 import BeautifulSoup
IMPERSONATE = "chrome110"
URL = "https://farside.co.uk/bitcoin-etf-flow-all-data/" # full history since Jan 2024
FUNDS = ["IBIT", "FBTC", "BITB", "ARKB", "BTCO", "EZBC",
"BRRR", "HODL", "BTCW", "MSBT", "GBTC", "BTC"]
def parse_value(s: str) -> float | None:
"""Parse Farside format: '269.3' or '(86.5)' (negative) or '-' (no data)."""
s = s.strip()
if not s or s == "-":
return None
neg = "(" in s
s = s.replace("(", "").replace(")", "").replace(",", "")
try:
v = float(s)
return -v if neg else v
except ValueError:
return None
def fetch_flows() -> list[dict]:
"""Fetch all daily ETF flow rows from Farside. Returns list of dicts."""
s = cf_requests.Session(impersonate=IMPERSONATE)
r = s.get(URL, headers={"Accept": "text/html"})
r.raise_for_status()
soup = BeautifulSoup(r.text, "html.parser")
table = soup.find_all("table")[1] # second table = flow data
rows = table.find_all("tr")
data = []
for row in rows:
cells = [td.text.strip() for td in row.find_all("td")]
if not cells or len(cells) < 14:
continue
date_str = cells[0]
if any(k in date_str for k in ["Total", "Average", "Maximum", "Minimum"]):
continue
entry = {"date": date_str}
for i, fund in enumerate(FUNDS):
entry[fund] = parse_value(cells[i + 1])
entry["Total"] = parse_value(cells[13])
data.append(entry)
# Deduplicate by date (Farside sometimes has dupes)
seen = set()
deduped = []
for row in data:
if row["date"] not in seen:
seen.add(row["date"])
deduped.append(row)
return deduped
def print_recent(data: list[dict], days: int = 20):
"""Print the most recent N trading days."""
recent = data[-days:]
header = f"{'Date':<16}"
for fund in ["IBIT", "FBTC", "ARKB", "GBTC", "Total"]:
header += f"{fund:>9}"
print(header)
print("-" * len(header))
for row in recent:
line = f"{row['date']:<16}"
for fund in ["IBIT", "FBTC", "ARKB", "GBTC", "Total"]:
v = row.get(fund)
if v is None:
line += f"{'—':>9}"
elif v < 0:
line += f"{'(' + f'{abs(v):.1f}' + ')':>9}"
else:
line += f"{v:>9.1f}"
print(line)
def summarise(data: list[dict], month: str = None):
"""Summarise flows for a given month (e.g. 'Apr 2026') or all data."""
filtered = data
if month:
filtered = [r for r in data if month in r["date"]]
if not filtered:
print(f"No data for {month}")
return
# Per-fund totals
totals = {}
for fund in FUNDS + ["Total"]:
vals = [r[fund] for r in filtered if r.get(fund) is not None]
totals[fund] = sum(vals)
inflow_days = sum(1 for r in filtered if (r.get("IBIT") or 0) > 0)
outflow_days = sum(1 for r in filtered if (r.get("IBIT") or 0) < 0)
trading_days = sum(1 for r in filtered if r.get("IBIT") is not None)
print(f"\n{'Fund':<8} {'Net Flow ($M)':>14}")
print("-" * 24)
for fund in ["IBIT", "FBTC", "BITB", "ARKB", "GBTC", "Total"]:
v = totals.get(fund, 0)
print(f"{fund:<8} {v:>13.1f}")
print(f"\nIBIT: {inflow_days} inflow days / {outflow_days} outflow days / {trading_days} total")
if __name__ == "__main__":
data = fetch_flows()
print(f"Fetched {len(data)} trading days\n")
print_recent(data, days=15)
summarise(data, month="Apr 2026")
```
---
## Example Output
```
Date IBIT FBTC ARKB GBTC Total
--------------------------------------------------------------
01 Apr 2026 (86.5) (78.6) 0.0 (13.3) (173.7)
02 Apr 2026 (3.0) 7.3 0.0 0.0 9.0
06 Apr 2026 181.9 147.3 118.8 0.0 471.4
09 Apr 2026 269.3 53.3 4.8 0.0 358.1
10 Apr 2026 137.6 78.0 3.6 0.0 256.7
14 Apr 2026 213.8 45.3 113.1 0.0 411.4
15 Apr 2026 291.9 (47.3) (42.2) (23.4) 186.1
Fund Net Flow ($M)
------------------------
IBIT 1,063.0
FBTC 178.4
ARKB 98.3
GBTC (87.0)
Total 1,376.1
IBIT: 7 inflow days / 3 outflow days / 10 total
```
---
## How to Read
- **Unit:** millions USD. `(86.5)` = net outflow of $86.5M
- **IBIT** = BlackRock iShares Bitcoin Trust (largest BTC ETF, ~45% market share)
- **FBTC** = Fidelity, **ARKB** = Ark/21Shares, **GBTC** = Grayscale (legacy, high fee)
- **Divergence signal:** when IBIT is positive but Total is negative → BlackRock is buying while others are selling. This often precedes recoveries
- **GBTC consistently negative** = continued outflow from Grayscale's 1.5% fee product into cheaper alternatives
---
## Combining with Blave Alpha
Use ETF flow direction as a macro filter for Blave signals:
```python
# If IBIT has been net positive for 3+ consecutive days,
# bias toward long signals from Blave alpha_table
recent_ibit = [r["IBIT"] for r in data[-5:] if r.get("IBIT") is not None]
consecutive_inflow = all(v > 0 for v in recent_ibit[-3:])
if consecutive_inflow:
# Institutional tailwind — Blave long signals have higher conviction
...
```
---
## Notes
- Farside publishes data after US market close (typically available by ~21:00 UTC)
- Data covers all US-listed spot Bitcoin ETFs since launch (Jan 2024)
- Weekend/holiday dates are skipped (no trading)
- `curl_cffi` with `impersonate="chrome110"` is needed to bypass Cloudflare; plain `requests` gets 403
- For Ethereum ETF flows, change URL to `https://farside.co.uk/eth/`
FILE:examples/hyperliquid-copy-trading.md
# Example: Hyperliquid Copy Trading — Find High-Sharpe Traders to Follow
## Strategy
Find Hyperliquid traders suitable for copy trading based on:
- **Sharpe ratio ≥ 1.5** — consistent risk-adjusted returns
- **≥ 90 data points** in performance history — enough sample to rule out luck
- **Account value ≥ $100K** — meaningful track record
- **Exclude high-frequency / bots** — weekly turnover ratio ≤ 10x (weekly volume / account value)
- **Copy method** — proportional full position copy (if trader allocates 10% to BTC long, you allocate 10% of your capital too)
---
## Step 1: Get Leaderboard
```
GET /hyperliquid/leaderboard?sort_by=allTime
```
Returns top 100 traders. Each entry includes `ethAddress`, `accountValue`, `windowPerformances` (day / week / month / allTime PnL, ROI, volume).
**Pre-filter in one pass:**
```python
candidates = []
for trader in leaderboard:
av = float(trader['accountValue'])
if av < 100_000:
continue
windows = dict(trader['windowPerformances'])
all_time_pnl = float(windows.get('allTime', {}).get('pnl', 0))
if all_time_pnl <= 0:
continue
# Turnover ratio: weekly volume / account value
week_vol = float(windows.get('week', {}).get('vlm', 0))
turnover = week_vol / av
if turnover > 10:
continue # likely bot or high-frequency
candidates.append({
'address': trader['ethAddress'],
'displayName': trader.get('displayName'),
'accountValue': av,
'allTimePnl': all_time_pnl,
'turnover': turnover,
'month_pnl': float(windows.get('month', {}).get('pnl', 0)),
'week_pnl': float(windows.get('week', {}).get('pnl', 0)),
})
```
---
## Step 2: Compute Sharpe Ratio
For each candidate, fetch the PnL curve:
```
GET /hyperliquid/trader_performance?address=<ethAddress>
```
Returns `{chart: {timestamp: [...], pnl: [...]}}` — cumulative PnL (USD) over time.
```python
import numpy as np
pnl_arr = np.array(chart['pnl'], dtype=float)
timestamps = np.array(chart['timestamp'], dtype=float) # Unix seconds
# Require at least 90 data points
if len(pnl_arr) < 90:
continue
# Per-period PnL changes (NOT "daily" — spacing varies by trader/activity)
period_returns = np.diff(pnl_arr)
if period_returns.std() == 0:
continue
# Annualize based on actual timestamp spacing, not a fixed "365 days" assumption.
# avg_dt is the mean seconds between consecutive data points.
# periods_per_year = how many such intervals fit in a year.
avg_dt = np.diff(timestamps).mean() # seconds per period
periods_per_year = (365 * 24 * 3600) / avg_dt
sharpe = (period_returns.mean() / period_returns.std()) * np.sqrt(periods_per_year)
if sharpe < 1.5:
continue
# Max drawdown (absolute USD, from running cumulative-PnL peak)
peak = np.maximum.accumulate(pnl_arr)
max_dd_usd = (pnl_arr - peak).min()
# Win rate
win_rate = (period_returns > 0).mean()
# Track record length in days
track_days = (timestamps[-1] - timestamps[0]) / 86400
```
**Final filter:** keep only traders where both `month_pnl > 0` and `week_pnl > 0` — confirms recent consistency, not just historical glory.
---
## Step 3: Rank, Select, and Plot
Sort passing traders by Sharpe ratio (descending). Present as a table and generate a PnL chart.
### Ranking Table
| Rank | Name / Address | Sharpe | Win Rate | Max DD (USD) | Turnover | Month PnL | Account |
|---|---|---|---|---|---|---|---|
| 1 | ... | 4.8 | 58.7% | -$6.7M | 0.1x | +$7.3M | $41M |
| 2 | ... | 3.1 | 59.0% | -$24.6M | 3.9x | +$1.6M | $20M |
Select the **top 1–3** for deeper inspection.
### PnL Chart (top 3 qualifying traders)
```python
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
from datetime import datetime
# Assume `qualifying` is a list of dicts, each with keys:
# address, displayName, pnl_arr, timestamps, sharpe, win_rate, max_dd_usd
top3 = qualifying[:3]
fig, axes = plt.subplots(2, 1, figsize=(14, 9), sharex=False,
gridspec_kw={'height_ratios': [3, 1]})
colors = ['#2ecc71', '#3498db', '#e67e22']
# Panel 1: Cumulative PnL curves
ax_pnl = axes[0]
for trader, color in zip(top3, colors):
ts = trader['timestamps']
pnl = trader['pnl_arr']
dates = [datetime.utcfromtimestamp(t) for t in ts]
label = (trader['displayName'] or trader['address'][:10] + '...')
label += f" Sharpe={trader['sharpe']:.2f} WR={trader['win_rate']*100:.1f}%"
ax_pnl.plot(dates, pnl / 1e6, lw=1.5, color=color, label=label)
ax_pnl.axhline(0, color='#888', lw=0.6, ls='--')
ax_pnl.set_ylabel('Cumulative PnL (USD million)', fontsize=11)
ax_pnl.set_title('Hyperliquid Top Traders — Cumulative PnL', fontsize=13, fontweight='bold')
ax_pnl.legend(fontsize=9, loc='upper left')
ax_pnl.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m'))
ax_pnl.xaxis.set_major_locator(mdates.MonthLocator(interval=2))
plt.setp(ax_pnl.xaxis.get_majorticklabels(), rotation=30, ha='right')
ax_pnl.yaxis.set_major_formatter(plt.FuncFormatter(lambda x, _: f'.1fM'))
# Panel 2: Drawdown
ax_dd = axes[1]
for trader, color in zip(top3, colors):
pnl = trader['pnl_arr']
ts = trader['timestamps']
dates = [datetime.utcfromtimestamp(t) for t in ts]
peak = np.maximum.accumulate(pnl)
dd = pnl - peak # absolute USD drawdown from running peak
ax_dd.plot(dates, dd / 1e6, lw=1, color=color, alpha=0.8)
ax_dd.fill_between(dates, dd / 1e6, 0, color=color, alpha=0.15)
ax_dd.axhline(0, color='#888', lw=0.6)
ax_dd.set_ylabel('Drawdown (USD million)', fontsize=11)
ax_dd.set_title('Drawdown', fontsize=12)
ax_dd.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m'))
ax_dd.xaxis.set_major_locator(mdates.MonthLocator(interval=2))
plt.setp(ax_dd.xaxis.get_majorticklabels(), rotation=30, ha='right')
ax_dd.yaxis.set_major_formatter(plt.FuncFormatter(lambda x, _: f'.1fM'))
plt.tight_layout()
plt.savefig('hyperliquid_top_traders.png', dpi=150, bbox_inches='tight')
plt.show()
print('Saved: hyperliquid_top_traders.png')
```
---
## Step 4: Inspect Current Positions
```
GET /hyperliquid/trader_position?address=<ethAddress>
```
Returns:
- `perp.assetPositions` — open perpetual positions
- `net_equity` — total account value (USD)
```python
for pos in perp['assetPositions']:
p = pos['position']
szi = float(p['szi'])
if szi == 0:
continue
direction = 'LONG' if szi > 0 else 'SHORT'
size_usd = abs(szi) * float(p.get('entryPx', 0))
allocation_pct = size_usd / net_equity # trader's allocation %
# Your position size = allocation_pct × your_capital
your_size_usd = allocation_pct * YOUR_CAPITAL
print(f"{p['coin']} {direction} | Trader: ,.0f ({allocation_pct*100:.1f}%) | You: ,.0f")
```
**Copy rule:** replicate each position at the same allocation percentage as the trader, using your own capital as the base.
---
## Step 5: Check Open Orders
```
GET /hyperliquid/trader_open_order?address=<ethAddress>
```
Review pending limit orders to understand their entry/exit plan. If they have many close orders stacked (like selling into strength), factor that into your copy — they may be planning to exit soon.
---
## Step 6: Monitor
Re-run Steps 4–5 periodically (e.g. every 15–30 minutes) to detect:
- New positions opened → open the same on your account
- Positions closed or reduced → close/reduce proportionally
- Sudden large drawdown → consider pausing copy until situation is clear
---
## Notes
- **Sharpe annualization:** `trader_performance` timestamps are not evenly spaced — data points reflect actual trading activity, so gaps vary. The Sharpe formula computes `avg_dt` from real timestamp differences and derives `periods_per_year = (365 × 24 × 3600) / avg_dt`. Using a hardcoded `√365` would overstate or understate the Sharpe depending on the trader's data density.
- **Max drawdown in USD:** computed on cumulative PnL, not on account value. A large USD drawdown on a $40M account is proportionally small; always compare `max_dd_usd / accountValue` for relative context.
- **Slippage:** large traders open big positions. By the time you copy, the price may have moved. For illiquid altcoins, be cautious.
- **Liquidation risk:** even high-Sharpe traders can have bad months. Never copy with more than you can afford to lose.
- **Lag:** this is manual or semi-automated copy trading. Real-time automated copy requires on-chain hooks or exchange copy trading features.
FILE:examples/liquidation-map.md
# Example: Liquidation Map (爆倉地圖)
**爆倉地圖(Liquidation Map)** 顯示合約市場交易者持倉的潛在爆倉價位區間與爆倉量。爆倉地圖是根據整合價格走勢、交易員持倉與槓桿率統計出整體市場的多空持倉分佈與潛在的清算風險,當市場上漲或下跌至爆倉價位時,交易員部位會被強制平倉。
**爆倉地圖變化(Liquidation Map Change)** 主要呈現不同時間段建立的交易員持倉(爆倉柱),從 0–1 小時到 8–24 小時以不同顏色顯示。可以通過觀察不同時間段建立的爆倉柱來觀察行情接下來的發展方向。
Two charts are generated:
1. **Liquidation Heatmap** — OI distribution + 24h long/short liquidation exposure at each price level + cumulative exposure line
2. **Liquidation Map Change** — 不同時間段建立的爆倉點位 (0–1h / 1–8h / 8–24h)
---
## Full Code
```python
import numpy as np
import requests
import matplotlib.pyplot as plt
import matplotlib.ticker as mticker
from dotenv import dotenv_values
_env = dotenv_values()
SYMBOL = "BTCUSDT"
BASE = "https://api.blave.org"
HEADERS = {"api-key": _env["blave_api_key"], "secret-key": _env["blave_secret_key"]}
def fetch(path, params=None):
r = requests.get(f"{BASE}/{path}", headers=HEADERS, params=params, timeout=15)
r.raise_for_status()
return r.json()["data"]
# ── Fetch ─────────────────────────────────────────────────────────────────────
map_data = fetch("liquidation/get_map", {"symbol": SYMBOL})
change_data = fetch("liquidation/get_map_change", {"symbol": SYMBOL})
labels = np.array(map_data["labels"], dtype=float)
cumsum = np.array(map_data["cumsum"], dtype=float)
oi_val = np.array(map_data["oi_value"], dtype=float)
price = float(map_data["price"])
liq_24h = map_data.get("liquidation", {}).get("24h", {})
buy_liq = np.array(liq_24h.get("buy_liq", [0] * len(labels)), dtype=float)
sell_liq = np.array(liq_24h.get("sell_liq", [0] * len(labels)), dtype=float)
ch_labels = np.array(change_data["labels"], dtype=float)
ch_price = float(change_data["price"])
hist_1h = np.array(change_data["hist_0_1h"], dtype=float)
hist_8h = np.array(change_data["hist_1_8h"], dtype=float)
hist_24h = np.array(change_data["hist_8_24h"], dtype=float)
bar_w = (labels[1] - labels[0]) * 0.85
ch_bar_w = (ch_labels[1] - ch_labels[0]) * 0.85
# ── Chart helpers ─────────────────────────────────────────────────────────────
BG = "#161b22"
GRID = "#30363d"
TEXT = "#c9d1d9"
ORANGE = "#ff9960"
RED = "#e55c5c"
GREEN = "#a6d16c"
GREY = "#8b949e"
def fmt_m(x, _=None):
if abs(x) >= 1e9: return f"{x/1e9:.1f}B"
if abs(x) >= 1e6: return f"{x/1e6:.1f}M"
if abs(x) >= 1e3: return f"{x/1e3:.0f}K"
return f"{x:.0f}"
def fmt_price(x, _=None):
if x <= labels[0] or x >= labels[-1]: return ""
return f"{x:,.0f}"
# ── Chart 1: get_map ──────────────────────────────────────────────────────────
fig, axes = plt.subplots(1, 2, figsize=(20, 7), facecolor=BG)
fig.suptitle(f"{SYMBOL} — Liquidation Map", fontsize=14, fontweight="bold",
color=TEXT, y=1.01)
ax_bar = axes[0]
ax_cum = ax_bar.twinx()
ax_bar.set_facecolor(BG)
# OI bars (right axis)
ax_bar.bar(labels, oi_val, width=bar_w, color=GREY, alpha=0.55, label="OI", zorder=2)
# Long liq (buy_liq) exposure stacked on OI
ax_bar.bar(labels, buy_liq, width=bar_w, bottom=oi_val,
color=GREEN, alpha=0.9, label="Long Liq", zorder=3)
# Short liq (sell_liq) exposure on top
ax_bar.bar(labels, sell_liq, width=bar_w, bottom=oi_val + buy_liq,
color=RED, alpha=0.9, label="Short Liq", zorder=3)
# Cumsum line — red below price, green above (left axis)
if labels[0] < price < labels[-1]:
cum_at_price = float(np.interp(price, labels, cumsum))
mask_lo = labels <= price
x_lo = np.append(labels[mask_lo], price)
y_lo = np.append(cumsum[mask_lo], cum_at_price)
ax_cum.plot(x_lo, y_lo, color=RED, lw=1.8, zorder=5)
ax_cum.fill_between(x_lo, y_lo, alpha=0.12, color=RED)
mask_hi = labels >= price
x_hi = np.insert(labels[mask_hi], 0, price)
y_hi = np.insert(cumsum[mask_hi], 0, cum_at_price)
ax_cum.plot(x_hi, y_hi, color=GREEN, lw=1.8, zorder=5, label="Cumulative")
ax_cum.fill_between(x_hi, y_hi, alpha=0.12, color=GREEN)
else:
ax_cum.plot(labels, cumsum, color=GREEN, lw=1.8, zorder=5, label="Cumulative")
ax_cum.fill_between(labels, cumsum, alpha=0.12, color=GREEN)
# Price annotation
ax_bar.axvline(price, color=ORANGE, lw=2, ls="--", zorder=6)
y_anno = oi_val.max() * 0.95 if oi_val.max() > 0 else 1
ax_bar.text(price, y_anno, f" ,.0f", color=ORANGE, fontsize=10,
va="top", zorder=7)
ax_bar.set_xlim(labels[0], labels[-1])
ax_bar.set_xlabel("Price", color=TEXT, fontsize=11)
ax_bar.set_ylabel("OI / Liq Exposure (USD)", color=TEXT, fontsize=10)
ax_cum.set_ylabel("Cumulative Liq Exposure", color=TEXT, fontsize=10)
ax_bar.set_title("Liquidation Heatmap\nOI + 24h Exposure", color=TEXT, fontsize=11)
ax_bar.tick_params(colors=TEXT)
ax_cum.tick_params(colors=TEXT)
ax_bar.yaxis.set_major_formatter(mticker.FuncFormatter(fmt_m))
ax_cum.yaxis.set_major_formatter(mticker.FuncFormatter(fmt_m))
ax_bar.xaxis.set_major_formatter(mticker.FuncFormatter(fmt_price))
for sp in ax_bar.spines.values(): sp.set_color(GRID)
for sp in ax_cum.spines.values(): sp.set_color(GRID)
ax_bar.tick_params(axis="x", rotation=30)
lines1, lbl1 = ax_bar.get_legend_handles_labels()
lines2, lbl2 = ax_cum.get_legend_handles_labels()
ax_bar.legend(lines1 + lines2, lbl1 + lbl2,
fontsize=9, facecolor="#21262d", labelcolor=TEXT, loc="upper left")
# ── Chart 2: get_map_change ───────────────────────────────────────────────────
ax2 = axes[1]
ax2.set_facecolor(BG)
ax2.bar(ch_labels, hist_24h, width=ch_bar_w, color=RED, alpha=0.50, label="8h–24h", zorder=2)
ax2.bar(ch_labels, hist_8h, width=ch_bar_w, color=GREEN, alpha=0.75, label="1h–8h", zorder=3)
ax2.bar(ch_labels, hist_1h, width=ch_bar_w, color=TEXT, alpha=0.95, label="0–1h", zorder=4)
ax2.axvline(ch_price, color=ORANGE, lw=2, ls="--", zorder=6)
y_top = max(hist_24h.max(), hist_8h.max(), hist_1h.max(), 1)
ax2.text(ch_price, y_top * 0.95, f" ,.0f", color=ORANGE, fontsize=10,
va="top", zorder=7)
ax2.set_xlim(ch_labels[0], ch_labels[-1])
ax2.set_xlabel("Price", color=TEXT, fontsize=11)
ax2.set_ylabel("Liquidated (USD)", color=TEXT, fontsize=10)
ax2.set_title("Recent Liquidation Events\nby Time Window", color=TEXT, fontsize=11)
ax2.tick_params(colors=TEXT)
ax2.yaxis.set_major_formatter(mticker.FuncFormatter(fmt_m))
ax2.xaxis.set_major_formatter(mticker.FuncFormatter(fmt_price))
for sp in ax2.spines.values(): sp.set_color(GRID)
ax2.tick_params(axis="x", rotation=30)
ax2.legend(fontsize=9, facecolor="#21262d", labelcolor=TEXT, loc="upper left")
# ── Save ──────────────────────────────────────────────────────────────────────
plt.tight_layout()
fname = f"{SYMBOL.lower()}_liquidation_map.png"
plt.savefig(fname, dpi=150, bbox_inches="tight", facecolor=BG)
plt.show()
print(f"Saved: {fname}")
```
---
## Output Interpretation
### Chart 1 — Liquidation Heatmap
| Element | Meaning |
|---|---|
| Grey bars | Open interest (USD) at each price level — where leveraged positions are concentrated |
| Green bars | Long liquidation exposure — positions that get liquidated if price drops to this level |
| Red bars | Short liquidation exposure — positions that get liquidated if price rises to this level |
| Green line (above price) | Cumulative liq exposure — total exposure to the right of current price |
| Red line (below price) | Cumulative liq exposure — total exposure to the left of current price |
| Orange dashed line | Current price |
**Key insight:** Large green bars below current price = big long liquidation clusters. If price drops to those levels, a cascade of forced selling can accelerate the move down. Large red bars above price = short squeeze clusters.
### Chart 2 — Liquidation Map Change (爆倉地圖變化)
呈現不同時間段建立的爆倉柱,每根柱代表該價格區間在對應時段內的爆倉金額。可通過觀察不同時間段的爆倉柱分布,判斷行情接下來的發展方向。
| Bar | Meaning |
|---|---|
| White (0–1h) | 最近 1 小時內在該點位發生的爆倉 |
| Green (1–8h) | 1–8 小時前在該點位發生的爆倉 |
| Red (8–24h) | 8–24 小時前在該點位發生的爆倉 |
**Key insight:** 白色柱密集的區間代表剛剛發生大規模爆倉,市場正在主動清算該點位的槓桿倉位,可能引發短線反向行情。紅色柱代表較早前已被清洗過的點位,再次經過時阻力相對較低。
---
## Notes
- `get_map` shows **pending exposure** (what could happen). `get_map_change` shows **what already happened**.
- `price_min` / `price_max` optional params let you zoom into a specific price range.
- Re-run before key technical levels or news events to see if clusters have shifted.
FILE:examples/tradingview-stream.md
# Example: Receive TradingView Signals via SSE
Listen to a TradingView alert stream in real time and act on each signal.
---
## Code
```python
import json
import time
import requests
from dotenv import dotenv_values
_env = dotenv_values()
HEADERS = {"api-key": _env["blave_api_key"], "secret-key": _env["blave_secret_key"]}
BASE_URL = "https://api.blave.org"
CHANNEL = "test"
def handle_signal(data: dict):
"""Called once per TradingView alert. Put your trading logic here."""
action = data.get("action", "").lower()
symbol = data.get("symbol", "")
price = data.get("price", "")
if action == "buy":
print(f"→ BUY {symbol} @ {price}")
# place_order(symbol, "buy", ...)
elif action == "sell":
print(f"→ SELL {symbol} @ {price}")
# place_order(symbol, "sell", ...)
else:
print(f"→ Unknown action: {data}")
def stream(channel: str, last_id: str = "$"):
"""Open SSE connection and yield (last_id, data) for each signal."""
url = f"{BASE_URL}/sse/tradingview/stream"
params = {"channel": channel, "last_id": last_id}
with requests.get(url, headers=HEADERS, params=params,
stream=True, timeout=None) as resp:
resp.raise_for_status()
buf = ""
for chunk in resp.iter_content(chunk_size=1, decode_unicode=True):
buf += chunk
if buf.endswith("\n"):
line = buf.strip()
buf = ""
if line.startswith("data: "):
data = json.loads(line[6:])
last_id = data.get("id", last_id)
yield last_id, data
def run():
last_id = "$" # only new signals; pass saved last_id on restart to replay missed ones
while True:
try:
print(f"Connecting to channel '{CHANNEL}' (last_id={last_id})...")
for last_id, data in stream(CHANNEL, last_id):
print(f"Signal: {data}")
handle_signal(data)
except Exception as e:
print(f"Disconnected: {e} — reconnecting in 3 s...")
time.sleep(3)
if __name__ == "__main__":
run()
```
---
## Notes
- **`last_id`** — saved automatically from each signal; passed on reconnect so no signals are missed (Redis buffers the last 1,000)
- **`last_id="$"`** — only signals arriving after the connection opens; change to a saved ID to replay from that point
- **Keepalive** — server sends `: keepalive` every 15 s; `iter_lines` ignores it automatically
- **Webhook & channel setup** — contact the Blave team to activate your channel
FILE:examples/truth-social-trump-monitor.md
# Example: Truth Social Trump Post Monitor
Monitor Trump's Truth Social posts, translate to Traditional Chinese via Google Translate (no LLM tokens), and push to Telegram.
Uses `trumpstruth.org/feed` RSS — works from any server (including AWS) with plain `requests`, no Cloudflare bypass needed.
---
## Dependencies
```bash
pip install deep-translator requests
```
---
## Code
```python
#!/usr/bin/env python3
"""
川普 Truth Social 監控 → 翻譯中文 → 推送 Telegram
透過 trumpstruth.org RSS Feed,每 5 分鐘執行一次(cron)
"""
import re, json, time
import xml.etree.ElementTree as ET
from pathlib import Path
from datetime import datetime
import requests
from deep_translator import GoogleTranslator
# ── Config ────────────────────────────────────────────────────────────────
RSS_URL = "https://www.trumpstruth.org/feed"
TRANSLATOR = GoogleTranslator(source="en", target="zh-TW")
TELEGRAM_TOKEN = "YOUR_BOT_TOKEN"
TELEGRAM_CHAT_ID = "YOUR_CHAT_ID"
STATE_FILE = Path(__file__).parent / "trump_monitor_state.json"
HEADERS = {"User-Agent": "Mozilla/5.0 (compatible; TrumpMonitor/1.0)"}
# ── Helpers ───────────────────────────────────────────────────────────────
def strip_html(html):
text = re.sub(r"<br\s*/?>", "\n", html or "")
text = re.sub(r"<[^>]+>", "", text)
for old, new in [("&","&"),("<","<"),(">",">"),("'","'"),(""",'"')]:
text = text.replace(old, new)
return text.strip()
def translate(text):
try:
return TRANSLATOR.translate(text) if text else ""
except Exception:
return text
def tg(msg):
requests.post(
f"https://api.telegram.org/bot{TELEGRAM_TOKEN}/sendMessage",
json={"chat_id": TELEGRAM_CHAT_ID, "text": msg,
"parse_mode": "Markdown", "disable_web_page_preview": True},
timeout=10,
)
def load_state():
if STATE_FILE.exists():
with open(STATE_FILE) as f:
return json.load(f)
return {"seen_ids": []}
def save_state(state):
state["seen_ids"] = state["seen_ids"][-200:] # keep last 200 to avoid bloat
with open(STATE_FILE, "w") as f:
json.dump(state, f)
# ── Fetch ─────────────────────────────────────────────────────────────────
def fetch_posts():
r = requests.get(RSS_URL, headers=HEADERS, timeout=20)
r.raise_for_status()
root = ET.fromstring(r.content)
ns = {"truth": "https://truthsocial.com/ns"}
posts = []
for item in root.findall(".//item"):
orig_id = item.findtext("truth:originalId", namespaces=ns)
guid = item.findtext("guid", "")
post_id = orig_id or guid.split("/")[-1]
title = item.findtext("title", "")
desc = strip_html(item.findtext("description", ""))
pub_date = item.findtext("pubDate", "")
orig_url = item.findtext("truth:originalUrl", namespaces=ns) or guid
content = desc if len(desc) > len(title) else title
content = content.strip()
if not content or content == "[No Title]":
continue
try:
dt = datetime.strptime(pub_date, "%a, %d %b %Y %H:%M:%S %z")
ts_str = dt.strftime("%Y-%m-%d %H:%M UTC")
except Exception:
ts_str = pub_date[:25] if pub_date else ""
posts.append({"id": post_id, "content": content,
"url": orig_url, "pub_date": ts_str})
return posts
# ── Main ──────────────────────────────────────────────────────────────────
def run():
state = load_state()
seen_ids = set(state["seen_ids"])
is_first = len(seen_ids) == 0
posts = fetch_posts()
if not posts:
return
if is_first:
# First run: push latest 3, mark all as seen
to_push = posts[:3]
for p in posts:
seen_ids.add(p["id"])
else:
to_push = [p for p in posts if p["id"] not in seen_ids]
for p in to_push:
seen_ids.add(p["id"])
for p in reversed(to_push): # oldest first
zh = translate(p["content"])
msg = (f"🇺🇸 *川普 Truth Social*\n"
f"🕐 `{p['pub_date']}`\n\n"
f"*🇬🇧 英文原文*\n{p['content']}\n\n"
f"*🇹🇼 中文翻譯*\n{zh}\n\n"
f"[原文連結]({p['url']})")
tg(msg)
time.sleep(1)
state["seen_ids"] = list(seen_ids)
save_state(state)
if __name__ == "__main__":
run()
```
---
## Deployment
### Cron (every 5 minutes)
```bash
*/5 * * * * cd /path/to/project && python3 trump_monitor.py >> /var/log/trump_monitor.log 2>&1
```
### Systemd Timer (alternative)
```ini
# /etc/systemd/system/trump-monitor.timer
[Unit]
Description=Trump Truth Social Monitor
[Timer]
OnCalendar=*:0/5
Persistent=true
[Install]
WantedBy=timers.target
```
---
## How It Works
| Step | Detail |
|---|---|
| **Fetch** | `GET https://www.trumpstruth.org/feed` — RSS feed with `truth:originalId` and `truth:originalUrl` custom XML namespaces |
| **Dedup** | `trump_monitor_state.json` stores last 200 seen post IDs; only new posts get pushed |
| **Translate** | `deep-translator` calls Google Translate free endpoint. EN → zh-TW |
| **Push** | Telegram Bot API with Markdown formatting |
| **First run** | Pushes latest 3 posts, marks all 100 as seen so next run only gets truly new ones |
---
## Customising the Push Channel
### Slack Webhook
Replace `tg(msg)` with:
```python
SLACK_WEBHOOK_URL = "https://hooks.slack.com/services/T.../B.../xxx"
def slack(msg):
requests.post(SLACK_WEBHOOK_URL, json={"text": msg}, timeout=10)
```
### Discord Webhook
```python
DISCORD_WEBHOOK_URL = "https://discord.com/api/webhooks/..."
def discord(msg):
requests.post(DISCORD_WEBHOOK_URL, json={"content": msg}, timeout=10)
```
---
## Notes
- **RSS source:** `trumpstruth.org` is a third-party RSS mirror of Trump's Truth Social posts. It returns standard RSS XML with custom `truth:` namespace fields
- **No Cloudflare issue:** unlike Truth Social's own API, this RSS feed works with plain `requests` from any IP (including AWS/GCP)
- **Feed size:** returns ~100 most recent posts per request
- **Google Translate limit:** `deep-translator` uses the free web endpoint; at 5-min intervals this is well within limits
- **State file:** keeps last 200 IDs to prevent duplicate pushes across restarts. Safe to delete to reset
FILE:references/binance-api-reference.md
# Binance API Reference
## Base URLs
| | Spot | USDS-M Futures |
|---|---|---|
| Production | `https://api.binance.com` | `https://fapi.binance.com` |
| Testnet | `https://testnet.binance.vision` | `https://demo-fapi.binance.com` |
**Success:** Spot returns order fields directly; Futures returns `"code": 200` or fields directly.
---
## Authentication
**Credentials** (from `.env`): `BINANCE_API_KEY`, `BINANCE_SECRET_KEY`
**Signature:** `HMAC-SHA256(secret, totalParams)` → hex
- `totalParams` = queryString + requestBody (concatenated, no separator)
- `timestamp`: Unix milliseconds (always required for signed endpoints)
- `signature` must be the **last** parameter
**Headers:**
```
X-MBX-APIKEY: <api_key>
Content-Type: application/x-www-form-urlencoded (POST)
```
## Broker ID (Blave)
Binance broker attribution is per-order via `newClientOrderId`, **not** a header.
| Product | Broker ID | `newClientOrderId` prefix |
|---|---|---|
| Spot | `GBN6HWR2` | `x-GBN6HWR2` |
| USDS-M Futures | `52DDFAFN` | `x-52DDFAFN` |
- Total length of `newClientOrderId` ≤ 36 chars
- Required on `/api/v3/order`, `/api/v3/order/cancelReplace`, `/api/v3/sor/order`, `/api/v3/orderList/oco|oto|otoco`, `/fapi/v1/order`, `/fapi/v1/batchOrders`, `/fapi/v1/algoOrder`, and their test/modify variants
- Batch orders: every order in the batch must carry its own prefixed `newClientOrderId`
```python
import uuid
def spot_cid(suffix: str = "") -> str:
return f"x-GBN6HWR2{suffix or uuid.uuid4().hex[:8]}"[:36]
def fut_cid(suffix: str = "") -> str:
return f"x-52DDFAFN{suffix or uuid.uuid4().hex[:8]}"[:36]
```
---
## Python Signature Implementation
```python
import time, hmac, hashlib, requests
from dotenv import dotenv_values
from urllib.parse import urlencode
_env = dotenv_values()
API_KEY = _env["BINANCE_API_KEY"]
SECRET_KEY = _env["BINANCE_SECRET_KEY"]
SPOT_URL = "https://api.binance.com"
FAPI_URL = "https://fapi.binance.com"
def _sign(params: dict) -> dict:
params["timestamp"] = int(time.time() * 1000)
qs = urlencode(params)
params["signature"] = hmac.new(SECRET_KEY.encode(), qs.encode(), hashlib.sha256).hexdigest()
return params
def bn_get(base, path, params=None):
p = _sign(dict(params or {}))
r = requests.get(f"{base}{path}", params=p,
headers={"X-MBX-APIKEY": API_KEY}, timeout=10)
return r.json()
def bn_post(base, path, params=None):
p = _sign(dict(params or {}))
r = requests.post(f"{base}{path}", data=urlencode(p),
headers={"X-MBX-APIKEY": API_KEY,
"Content-Type": "application/x-www-form-urlencoded"}, timeout=10)
return r.json()
def bn_delete(base, path, params=None):
p = _sign(dict(params or {}))
r = requests.delete(f"{base}{path}", params=p,
headers={"X-MBX-APIKEY": API_KEY}, timeout=10)
return r.json()
def bn_put(base, path, params=None):
p = _sign(dict(params or {}))
r = requests.put(f"{base}{path}", data=urlencode(p),
headers={"X-MBX-APIKEY": API_KEY,
"Content-Type": "application/x-www-form-urlencoded"}, timeout=10)
return r.json()
# Shortcuts
def spot_get(path, params=None): return bn_get(SPOT_URL, path, params)
def spot_post(path, params=None): return bn_post(SPOT_URL, path, params)
def spot_delete(path, params=None): return bn_delete(SPOT_URL, path, params)
def fapi_get(path, params=None): return bn_get(FAPI_URL, path, params)
def fapi_post(path, params=None): return bn_post(FAPI_URL, path, params)
def fapi_delete(path, params=None): return bn_delete(FAPI_URL, path, params)
def fapi_put(path, params=None): return bn_put(FAPI_URL, path, params)
```
---
## Spot Endpoints
### Account
| Method | Path | Description |
|---|---|---|
| GET | `/api/v3/account` | Account info + balances (`omitZeroBalances` optional) |
| GET | `/api/v3/myTrades` | Trade history (`symbol` required) |
| GET | `/api/v3/account/commission` | Commission rates (`symbol` required) |
| GET | `/api/v3/rateLimit/order` | Unfilled order count |
### Order Placement
| Method | Path | Description |
|---|---|---|
| POST | `/api/v3/order` | Place order |
| POST | `/api/v3/order/test` | Test order (validate only) |
| DELETE | `/api/v3/order` | Cancel order |
| DELETE | `/api/v3/openOrders` | Cancel all open orders for symbol |
| POST | `/api/v3/order/cancelReplace` | Atomic cancel & replace |
| PUT | `/api/v3/order/amend/keepPriority` | Amend quantity (keep queue priority) |
**Spot order params:** `symbol`, `side` (BUY/SELL), `type` (LIMIT/MARKET/STOP_LOSS/TAKE_PROFIT/STOP_LOSS_LIMIT/TAKE_PROFIT_LIMIT/LIMIT_MAKER), `timeInForce` (GTC/IOC/FOK), `quantity`, `quoteOrderQty` (market buy by quote), `price`, **`newClientOrderId` (REQUIRED: must start with `x-GBN6HWR2`, ≤36 chars)**
### Order Query
| Method | Path | Description |
|---|---|---|
| GET | `/api/v3/order` | Query single order |
| GET | `/api/v3/openOrders` | Current open orders |
| GET | `/api/v3/allOrders` | All orders (active/canceled/filled) |
### Advanced Order Types
| Method | Path | Description |
|---|---|---|
| POST | `/api/v3/orderList/oco` | OCO (One-Cancels-Other) |
| POST | `/api/v3/orderList/oto` | OTO (One-Triggers-Other) |
| POST | `/api/v3/orderList/otoco` | OTOCO (One-Triggers-OCO) |
| DELETE | `/api/v3/orderList` | Cancel order list |
| GET | `/api/v3/orderList` | Query order list |
| GET | `/api/v3/openOrderList` | Open order lists |
### Smart Order Routing (SOR)
| Method | Path | Description |
|---|---|---|
| POST | `/api/v3/sor/order` | SOR order |
| POST | `/api/v3/sor/order/test` | Test SOR order |
---
## USDS-Margined Futures Endpoints
### Account & Position
| Method | Path | Description |
|---|---|---|
| GET | `/fapi/v2/account` | Account info V2 |
| GET | `/fapi/v3/account` | Account info V3 (single/multi-asset) |
| GET | `/fapi/v2/balance` | Account balance |
| GET | `/fapi/v2/positionRisk` | Position info V2 |
| GET | `/fapi/v3/positionRisk` | Position info V3 |
| GET | `/fapi/v1/userTrades` | Trade list (`symbol` required, max 7d range) |
| GET | `/fapi/v1/forceOrders` | Liquidation/ADL orders |
### Order Placement
| Method | Path | Description |
|---|---|---|
| POST | `/fapi/v1/order` | Place order |
| POST | `/fapi/v1/batchOrders` | Batch place (max 5) |
| POST | `/fapi/v1/algoOrder` | Algo/conditional order (STOP/TP/TRAILING) |
**Futures order params:** `symbol`, `side` (BUY/SELL), `positionSide` (BOTH/LONG/SHORT), `type` (LIMIT/MARKET/STOP/STOP_MARKET/TAKE_PROFIT/TAKE_PROFIT_MARKET/TRAILING_STOP_MARKET), `quantity`, `price`, `stopPrice`, `timeInForce`, `reduceOnly`, **`newClientOrderId` (REQUIRED: must start with `x-52DDFAFN`, ≤36 chars)**
### Order Modification
| Method | Path | Description |
|---|---|---|
| PUT | `/fapi/v1/order` | Modify order (LIMIT only) |
| PUT | `/fapi/v1/batchOrders` | Batch modify (max 5) |
### Order Cancellation
| Method | Path | Description |
|---|---|---|
| DELETE | `/fapi/v1/order` | Cancel order |
| DELETE | `/fapi/v1/batchOrders` | Batch cancel (max 10) |
| DELETE | `/fapi/v1/allOpenOrders` | Cancel all open orders |
| DELETE | `/fapi/v1/algoOrder` | Cancel algo order |
| DELETE | `/fapi/v1/algoOpenOrders` | Cancel all algo open orders |
### Order Query
| Method | Path | Description |
|---|---|---|
| GET | `/fapi/v1/order` | Query single order |
| GET | `/fapi/v1/openOrder` | Single open order |
| GET | `/fapi/v1/openOrders` | All open orders |
| GET | `/fapi/v1/allOrders` | All orders (max 7d range) |
| GET | `/fapi/v1/openAlgoOrders` | Open algo orders |
| GET | `/fapi/v1/allAlgoOrders` | All algo orders |
### Leverage & Margin
| Method | Path | Description |
|---|---|---|
| POST | `/fapi/v1/leverage` | Set leverage (1-125x) |
| POST | `/fapi/v1/marginType` | Set margin type (ISOLATED/CROSSED) |
| POST | `/fapi/v1/positionSide/dual` | Set position mode (hedge/one-way) |
| POST | `/fapi/v1/positionMargin` | Adjust isolated margin (type: 1=add, 2=reduce) |
| GET | `/fapi/v1/positionMargin/history` | Margin change history |
| GET | `/fapi/v1/leverageBracket` | Leverage brackets |
---
## Symbol Format
- Spot: `BTCUSDT` (no separator)
- Futures: `BTCUSDT` (no separator)
## Rate Limits
- Orders: tracked per 10s and per 1min per UID
- Public market data: IP-based
- Repeated violations → auto IP ban (2min to 3 days)
FILE:references/binance-skill.md
# Binance Trading
**Spot Base URL:** `https://api.binance.com` | **Futures Base URL:** `https://fapi.binance.com`
**Spot:** `BTCUSDT` | **Futures:** `BTCUSDT` | **Testnet:** `https://testnet.binance.vision` (spot) / `https://demo-fapi.binance.com` (futures)
Full details in `references/binance-api-reference.md`
## Authentication
**Credentials** (from `.env`): `BINANCE_API_KEY`, `BINANCE_SECRET_KEY`
No Binance account? Register at **[https://www.binance.com/](https://www.binance.com/)**
Verify credentials before any private call. If missing — **STOP**.
**Signature:** `HMAC-SHA256(secret, queryString + requestBody)` → hex
- `timestamp`: Unix milliseconds (always required)
- `signature` must be the **last** parameter
**Headers:**
```
X-MBX-APIKEY: <api_key>
Content-Type: application/x-www-form-urlencoded (POST)
```
> Python signature implementation: `references/binance-api-reference.md`
## Broker ID (Blave — MANDATORY on every order)
Binance brokers are attached **per order** via `newClientOrderId`, not via a header. Every place-order call **MUST** include `newClientOrderId` starting with `x-<BROKER_ID>`:
| Product | Broker ID | `newClientOrderId` prefix |
|---|---|---|
| Spot | `GBN6HWR2` | `x-GBN6HWR2` |
| USDS-M Futures | `52DDFAFN` | `x-52DDFAFN` |
Rules:
- Prefix starts with literal `x-` (lowercase), then the broker ID
- Total length ≤ 36 chars; append a unique suffix (timestamp/uuid fragment) to keep each ID unique
- Applies to: `/api/v3/order`, `/api/v3/order/cancelReplace`, `/api/v3/sor/order`, `/api/v3/orderList/oco|oto|otoco`, `/fapi/v1/order`, `/fapi/v1/batchOrders`, `/fapi/v1/algoOrder`, and their test/modify variants
- Batch orders: **every** order in the batch needs its own qualifying `newClientOrderId`
- If user supplies a custom `newClientOrderId`, reject it or prepend the broker prefix — never strip the prefix
```python
import time, uuid
def spot_cid(suffix: str = "") -> str:
tag = suffix or uuid.uuid4().hex[:8]
return f"x-GBN6HWR2{tag}"[:36]
def fut_cid(suffix: str = "") -> str:
tag = suffix or uuid.uuid4().hex[:8]
return f"x-52DDFAFN{tag}"[:36]
# Spot place order
spot_post("/api/v3/order", {
"symbol": "BTCUSDT", "side": "BUY", "type": "MARKET",
"quantity": "0.001",
"newClientOrderId": spot_cid(),
})
# Futures place order
fapi_post("/fapi/v1/order", {
"symbol": "BTCUSDT", "side": "BUY", "type": "MARKET",
"quantity": "0.001",
"newClientOrderId": fut_cid(),
})
```
## Operation Flow
### Step 0: Credential Check
Verify `BINANCE_API_KEY`, `BINANCE_SECRET_KEY`. If missing — **STOP**. Default to **Mainnet** unless user explicitly requests Testnet.
### Step 1: Pre-Trade Check (Futures)
- Query positions: `GET /fapi/v2/positionRisk?symbol=<SYMBOL>`
- If position exists → inherit leverage and margin type, do NOT override
### Step 2: Execute
- READ → call, parse, display
- WRITE → present summary → ask **"CONFIRM"** → execute
### Step 3: Verify
After order → query order status. After close → query positions.
## Quick Reference — Spot
| Operation | Method | Path |
|---|---|---|
| Account info | GET | `/api/v3/account` |
| Place order | POST | `/api/v3/order` |
| Cancel order | DELETE | `/api/v3/order` |
| Cancel all | DELETE | `/api/v3/openOrders` |
| Query order | GET | `/api/v3/order` |
| Open orders | GET | `/api/v3/openOrders` |
| Order history | GET | `/api/v3/allOrders` |
| Trade fills | GET | `/api/v3/myTrades` |
## Quick Reference — USDS-M Futures
| Operation | Method | Path |
|---|---|---|
| Account balance | GET | `/fapi/v2/balance` |
| Account info | GET | `/fapi/v2/account` |
| Positions | GET | `/fapi/v2/positionRisk` |
| Place order | POST | `/fapi/v1/order` |
| Batch place | POST | `/fapi/v1/batchOrders` |
| Cancel order | DELETE | `/fapi/v1/order` |
| Cancel all | DELETE | `/fapi/v1/allOpenOrders` |
| Modify order | PUT | `/fapi/v1/order` |
| Open orders | GET | `/fapi/v1/openOrders` |
| Order history | GET | `/fapi/v1/allOrders` |
| Set leverage | POST | `/fapi/v1/leverage` |
| Set margin type | POST | `/fapi/v1/marginType` |
| Set position mode | POST | `/fapi/v1/positionSide/dual` |
## Security
- WRITE operations require **"CONFIRM"**
- Always show liquidation price before opening leveraged positions
- "Not financial advice. Trading carries significant risk of loss."
## References
- `references/binance-api-reference.md` — spot + futures endpoints, Python signature
FILE:references/bingx-api-reference.md
# BingX API Reference
## Base URL
| Environment | Primary | Fallback |
|---|---|---|
| Live Trading | `https://open-api.bingx.com` | `https://open-api.bingx.pro` |
| Paper Trading (VST) | `https://open-api-vst.bingx.com` | `https://open-api-vst.bingx.pro` |
Prefer `.com`; only fall back to `.pro` on network-level failures. Default to live unless user explicitly requests paper trading.
---
## Authentication
**Credentials** (from `.env`): `BINGX_API_KEY`, `BINGX_SECRET_KEY`
**Signature:** HMAC-SHA256
```
1. Collect all params (query + body) + timestamp (Unix ms)
2. Sort alphabetically by key (unencoded)
3. Concatenate as key=value&key=value
4. signature = HMAC-SHA256(secret, canonical_string) → hex
5. Append &signature=<hex> to query string or body
```
**Headers (all requests):**
```
X-BX-APIKEY: <api_key>
X-SOURCE-KEY: BX-AI-SKILL
```
**Response format:** `{"code": 0, "msg": "", "data": ...}` — `code == 0` is success.
---
## Python Signature Implementation
```python
import time, hmac, hashlib, json, os, requests
from dotenv import dotenv_values
from urllib.parse import urlencode
_env = dotenv_values()
API_KEY = _env["BINGX_API_KEY"]
SECRET_KEY = _env["BINGX_SECRET_KEY"]
BASE_URL = "https://open-api.bingx.com"
FALLBACK = "https://open-api.bingx.pro"
HEADERS = {"X-BX-APIKEY": API_KEY, "X-SOURCE-KEY": "BX-AI-SKILL"}
def _sign(params: dict) -> str:
"""Build canonical string from sorted params and return HMAC-SHA256 hex signature."""
params["timestamp"] = str(int(time.time() * 1000))
canonical = "&".join(f"{k}={v}" for k, v in sorted(params.items()))
sig = hmac.new(SECRET_KEY.encode(), canonical.encode(), hashlib.sha256).hexdigest()
return canonical + f"&signature={sig}"
def bingx_get(path: str, params: dict = {}):
"""Signed GET request with domain fallback."""
qs = _sign(dict(params))
for base in [BASE_URL, FALLBACK]:
try:
r = requests.get(f"{base}{path}?{qs}", headers=HEADERS, timeout=10)
data = r.json()
if data.get("code") != 0:
raise Exception(f"BingX error {data.get('code')}: {data.get('msg')}")
return data.get("data")
except requests.exceptions.ConnectionError:
if base == FALLBACK: raise
return None
def bingx_post(path: str, params: dict = {}):
"""Signed POST request (form-encoded) with domain fallback."""
body = _sign(dict(params))
for base in [BASE_URL, FALLBACK]:
try:
r = requests.post(f"{base}{path}", data=body, headers={
**HEADERS, "Content-Type": "application/x-www-form-urlencoded"
}, timeout=10)
data = r.json()
if data.get("code") != 0:
raise Exception(f"BingX error {data.get('code')}: {data.get('msg')}")
return data.get("data")
except requests.exceptions.ConnectionError:
if base == FALLBACK: raise
return None
def bingx_delete(path: str, params: dict = {}):
"""Signed DELETE request with domain fallback."""
qs = _sign(dict(params))
for base in [BASE_URL, FALLBACK]:
try:
r = requests.delete(f"{base}{path}?{qs}", headers=HEADERS, timeout=10)
data = r.json()
if data.get("code") != 0:
raise Exception(f"BingX error {data.get('code')}: {data.get('msg')}")
return data.get("data")
except requests.exceptions.ConnectionError:
if base == FALLBACK: raise
return None
```
---
## Account Balance Endpoints
BingX has three separate accounts: Fund, Spot, Swap. Assets don't auto-transfer — query all three to see full picture.
| Method | Path | Description |
|---|---|---|
| GET | `/openApi/fund/v1/account/balance` | Fund account (deposits land here) |
| GET | `/openApi/spot/v1/account/balance` | Spot account |
| GET | `/openApi/swap/v3/user/balance` | Swap/futures account (USDT + USDC) |
| GET | `/openApi/account/v1/allAccountBalance` | All accounts overview |
---
## Perpetual Futures (Swap) Endpoints
### Order Management
| Method | Path | Description |
|---|---|---|
| POST | `/openApi/swap/v2/trade/order` | Place order |
| POST | `/openApi/swap/v2/trade/order/test` | Test order (validate only) |
| POST | `/openApi/swap/v2/trade/batchOrders` | Batch place (up to 5) |
**Place order params:**
| Param | Required | Description |
|---|---|---|
| `symbol` | ✓ | e.g. `BTC-USDT` |
| `side` | ✓ | `BUY` / `SELL` |
| `positionSide` | ✓ | `LONG` / `SHORT` / `BOTH` (one-way mode) |
| `type` | ✓ | `MARKET`, `LIMIT`, `STOP_MARKET`, `STOP`, `TAKE_PROFIT_MARKET`, `TAKE_PROFIT`, `TRAILING_STOP_MARKET`, `TRAILING_TP_SL` |
| `quantity` | ✓ (except market buy) | Order quantity |
| `price` | for LIMIT | Limit price |
| `stopPrice` | for STOP/TP | Trigger price |
| `timeInForce` | — | `GTC` (default), `IOC`, `FOK`, `PostOnly` |
| `stopLoss` | — | JSON: `{"type":"STOP_MARKET","stopPrice":"...","price":"...","workingType":"..."}` |
| `takeProfit` | — | JSON: same structure as stopLoss |
| `recvWindow` | — | Default 5000ms |
### Cancel Orders
| Method | Path | Description |
|---|---|---|
| DELETE | `/openApi/swap/v2/trade/order` | Cancel single order |
| DELETE | `/openApi/swap/v2/trade/batchOrders` | Batch cancel (up to 10) |
| DELETE | `/openApi/swap/v2/trade/allOpenOrders` | Cancel all open orders |
| POST | `/openApi/swap/v2/trade/cancelAllAfter` | Kill switch (auto-cancel after 10-120s) |
### Query Orders
| Method | Path | Description |
|---|---|---|
| GET | `/openApi/swap/v2/trade/openOrder` | Single open order status |
| GET | `/openApi/swap/v2/trade/order` | Order details |
| GET | `/openApi/swap/v2/trade/openOrders` | All current open orders |
| GET | `/openApi/swap/v2/trade/allOrders` | Order history (max 7-day range) |
| GET | `/openApi/swap/v2/trade/forceOrders` | Liquidation / force close orders |
| GET | `/openApi/swap/v2/trade/allFillOrders` | Trade fill history with fees & PnL |
### Position Management
| Method | Path | Description |
|---|---|---|
| POST | `/openApi/swap/v2/trade/closeAllPositions` | Close all positions |
| POST | `/openApi/swap/v1/trade/closePosition` | Close position by positionId |
| POST | `/openApi/swap/v2/trade/positionMargin` | Adjust isolated margin (add/reduce) |
### Leverage & Mode Settings
| Method | Path | Description |
|---|---|---|
| GET | `/openApi/swap/v2/trade/marginType` | Query margin mode |
| POST | `/openApi/swap/v2/trade/marginType` | Set margin mode (`ISOLATED`/`CROSSED`/`SEPARATE_ISOLATED`) |
| GET | `/openApi/swap/v2/trade/leverage` | Query leverage (current + max) |
| POST | `/openApi/swap/v2/trade/leverage` | Set leverage |
| GET | `/openApi/swap/v1/positionSide/dual` | Query position mode (hedge/one-way) |
| POST | `/openApi/swap/v1/positionSide/dual` | Set position mode |
### Order Modification
| Method | Path | Description |
|---|---|---|
| POST | `/openApi/swap/v1/trade/amend` | Amend open order quantity |
| POST | `/openApi/swap/v1/trade/cancelReplace` | Cancel and replace (atomic) |
| POST | `/openApi/swap/v1/trade/batchCancelReplace` | Batch cancel and replace |
### TWAP Orders
| Method | Path | Description |
|---|---|---|
| POST | `/openApi/swap/v1/twap/order` | Place TWAP order (split into child orders, 5-120s intervals) |
| POST | `/openApi/swap/v1/twap/cancelOrder` | Cancel TWAP order |
| GET | `/openApi/swap/v1/twap/openOrders` | Query TWAP open orders |
| GET | `/openApi/swap/v1/twap/historyOrders` | Query TWAP history |
| GET | `/openApi/swap/v1/twap/orderDetail` | TWAP order details with child records |
### Additional
| Method | Path | Description |
|---|---|---|
| GET | `/openApi/swap/v1/trade/fullOrder` | All orders V2 |
| GET | `/openApi/swap/v2/trade/fillHistory` | Historical transaction details |
| GET | `/openApi/swap/v1/trade/positionHistory` | Position history (max 3-month) |
| GET | `/openApi/swap/v1/positionMargin/history` | Isolated margin change history |
| GET | `/openApi/swap/v1/maintMarginRatio` | Position & maintenance margin ratio |
| POST | `/openApi/swap/v1/trade/autoAddMargin` | Auto margin addition |
| POST | `/openApi/swap/v1/trade/reverse` | One-click reverse position |
| POST | `/openApi/swap/v2/trade/getVst` | Apply VST (paper trading) balance |
---
## Spot Endpoints
### Order Management
| Method | Path | Description |
|---|---|---|
| POST | `/openApi/spot/v1/trade/order` | Place order (MARKET/LIMIT/STOP types) |
| POST | `/openApi/spot/v1/trade/batchOrders` | Batch place (up to 5) |
| POST | `/openApi/spot/v1/trade/cancel` | Cancel single order |
| POST | `/openApi/spot/v1/trade/cancelOrders` | Bulk cancel |
| POST | `/openApi/spot/v1/trade/cancelOpenOrders` | Cancel all open orders for a pair |
| POST | `/openApi/spot/v1/trade/cancelAllAfter` | Kill switch (10-120s timeout) |
| POST | `/openApi/spot/v1/trade/order/cancelReplace` | Cancel and replace (atomic) |
**Spot order types:** `MARKET`, `LIMIT`, `TAKE_STOP_LIMIT`, `TAKE_STOP_MARKET`, `TRIGGER_LIMIT`, `TRIGGER_MARKET`
### Query Orders
| Method | Path | Description |
|---|---|---|
| GET | `/openApi/spot/v1/trade/query` | Single order details |
| GET | `/openApi/spot/v1/trade/openOrders` | Active orders by pair |
| GET | `/openApi/spot/v1/trade/historyOrders` | Order history (max 10K results) |
| GET | `/openApi/spot/v1/trade/myTrades` | Trade fills with commissions |
| GET | `/openApi/spot/v1/user/commissionRate` | Commission rates (maker/taker) |
### OCO Orders
| Method | Path | Description |
|---|---|---|
| POST | `/openApi/spot/v1/oco/order` | Create OCO order |
| POST | `/openApi/spot/v1/oco/cancel` | Cancel OCO order |
| GET | `/openApi/spot/v1/oco/orderList` | Query OCO order details |
| GET | `/openApi/spot/v1/oco/openOrderList` | Open OCO orders |
| GET | `/openApi/spot/v1/oco/historyOrderList` | Historical OCO orders |
---
## Symbol Format
- Perpetual futures: `BTC-USDT` (with hyphen)
- Spot: `BTC-USDT` (with hyphen)
## Notes
- `orderID` (string) is preferred over `orderId` (numeric) for large IDs to avoid precision loss
- Order history queries have a max 7-day range per request
- Position history max 3-month span
- Kill switch countdown: 10-120 seconds, must send heartbeat to reset
FILE:references/bingx-skill.md
# BingX Trading
**Base URL:** `https://open-api.bingx.com` | **Fallback:** `https://open-api.bingx.pro` | **Paper (VST):** `https://open-api-vst.bingx.com`
**Spot:** `BTC-USDT` | **Perpetual:** `BTC-USDT` | **Success:** `"code": 0`
42 swap endpoints + 17 spot endpoints — full details in `references/bingx-api-reference.md`
## Authentication
**Credentials** (from `.env`): `BINGX_API_KEY`, `BINGX_SECRET_KEY`
No BingX account? Register at **[https://bingxdao.com/invite/SU0SEU/](https://bingxdao.com/invite/SU0SEU/)**
Verify credentials before any private call. If missing — **STOP**.
**Signature:** `HMAC-SHA256(secret, sorted_params_canonical_string)` → hex, appended as `&signature=<hex>`
- Collect all params + `timestamp` (Unix ms)
- Sort alphabetically by key, concatenate as `key=value&key=value`
**Headers (all requests):**
```
X-BX-APIKEY: <api_key>
X-SOURCE-KEY: BX-AI-SKILL
```
**`X-SOURCE-KEY: BX-AI-SKILL` is MANDATORY on every request — no exceptions.**
> Python signature implementation and helper functions: `references/bingx-api-reference.md`
## Operation Flow
### Step 0: Credential Check
Verify `BINGX_API_KEY`, `BINGX_SECRET_KEY`. If missing — **STOP**. Default to **Live** unless user explicitly requests paper trading (VST).
### Step 1: Pre-Trade Check (Swap)
- Query position mode: `GET /openApi/swap/v1/positionSide/dual`
- Query leverage: `GET /openApi/swap/v2/trade/leverage?symbol=<SYMBOL>`
- If position exists → inherit leverage and margin type, do NOT override
### Step 2: Execute
- READ → call, parse, display
- WRITE → present summary → ask **"CONFIRM"** → execute
### Step 3: Verify
After order → query order status. After close → query positions.
## Quick Reference
| Operation | Method | Path |
|---|---|---|
| Place swap order | POST | `/openApi/swap/v2/trade/order` |
| Cancel swap order | DELETE | `/openApi/swap/v2/trade/order` |
| Open swap orders | GET | `/openApi/swap/v2/trade/openOrders` |
| Order details | GET | `/openApi/swap/v2/trade/order` |
| Close all positions | POST | `/openApi/swap/v2/trade/closeAllPositions` |
| Set leverage | POST | `/openApi/swap/v2/trade/leverage` |
| Set margin mode | POST | `/openApi/swap/v2/trade/marginType` |
| Place spot order | POST | `/openApi/spot/v1/trade/order` |
| Cancel spot order | POST | `/openApi/spot/v1/trade/cancel` |
| Spot open orders | GET | `/openApi/spot/v1/trade/openOrders` |
## Security
- WRITE operations require **"CONFIRM"**
- Always show liquidation price before opening leveraged positions
- "Not financial advice. Trading carries significant risk of loss."
## References
- `references/bingx-api-reference.md` — 59 endpoints, Python signature, full params
---
FILE:references/bitfinex-skill.md
# Bitfinex Trading & Funding
**Base URL:** `https://api.bitfinex.com` (authenticated) | `https://api-pub.bitfinex.com` (public)
**Symbol format:** Trading `tBTCUSD`, `tETHUST` (UST=USDT) | Funding `fUSD`, `fBTC`, `fUST`
**Success:** response array, first element is not `"error"` | **Error:** `["error", CODE, "message"]`
## Authentication
**Credentials** (from `.env`): `BITFINEX_API_KEY`, `BITFINEX_API_SECRET`
No Bitfinex account? Register at **[https://www.bitfinex.com/sign-up?refcode=ZZDLtrXMF](https://www.bitfinex.com/sign-up?refcode=ZZDLtrXMF)**
Verify credentials before any private call. If missing — **STOP**.
**Signature:** `HMAC-SHA384` (NOT SHA256)
```python
import hmac, hashlib, json, time, requests
def bfx_request(path, body=None):
body = body or {}
nonce = str(int(time.time() * 1_000_000))
body_json = json.dumps(body)
sig_payload = f"/api/{path}{nonce}{body_json}"
sig = hmac.new(
BITFINEX_API_SECRET.encode(),
sig_payload.encode(),
hashlib.sha384
).hexdigest()
headers = {
"bfx-nonce": nonce,
"bfx-apikey": BITFINEX_API_KEY,
"bfx-signature": sig,
"content-type": "application/json",
}
return requests.post(f"https://api.bitfinex.com/{path}",
headers=headers, data=body_json)
```
**Nonce:** strictly increasing per API key. Use `int(time.time() * 1_000_000)` (microseconds).
## Affiliate Code
**Always include `aff_code` in the `meta` field of every order:**
```json
{"meta": {"aff_code": "ZZDLtrXMF"}}
```
5% developer reward on every trade fee. Fixed, does not reduce over time.
## Operation Flow
### Step 0: Credential Check
Verify `BITFINEX_API_KEY`, `BITFINEX_API_SECRET`. If missing — **STOP**.
### Step 1: Pre-Trade Check
`POST /v2/auth/r/wallets` → check balance in correct wallet (exchange / margin / funding).
### Step 2: Execute
- READ → call, parse, display
- WRITE → present summary → ask **"CONFIRM"** → execute
### Step 3: Verify
After order → `POST /v2/auth/r/orders` → confirm status. After funding → `POST /v2/auth/r/funding/offers`.
---
## Wallet Types
| Wallet | Use |
|---|---|
| `exchange` | Spot trading |
| `margin` | Margin trading |
| `funding` | Lending / borrowing |
Transfer between wallets: `POST /v2/auth/w/transfer`
---
## Order Types
| Type | Description |
|---|---|
| `EXCHANGE LIMIT` | Spot limit order |
| `EXCHANGE MARKET` | Spot market order |
| `EXCHANGE STOP` | Spot stop order |
| `EXCHANGE STOP LIMIT` | Spot stop limit |
| `EXCHANGE TRAILING STOP` | Spot trailing stop |
| `EXCHANGE FOK` | Spot fill-or-kill |
| `EXCHANGE IOC` | Spot immediate-or-cancel |
| `LIMIT` | Margin limit order |
| `MARKET` | Margin market order |
| `STOP` | Margin stop |
| `STOP LIMIT` | Margin stop limit |
| `TRAILING STOP` | Margin trailing stop |
**"EXCHANGE" prefix = spot wallet. No prefix = margin wallet.**
## Order Flags
| Flag | Value | Description |
|---|---|---|
| Hidden | 64 | Not visible in order book |
| Close | 512 | Close existing position |
| Reduce Only | 1024 | Prevent reversing position |
| Post Only | 4096 | Add to book only, no immediate match |
| OCO | 16384 | One-Cancels-Other |
Combine by summing: Hidden + Post Only = `4160`
---
## Key Endpoints — Trading
| Action | Method | Path |
|---|---|---|
| Ticker (public) | GET | `/v2/ticker/{Symbol}` |
| Tickers (public) | GET | `/v2/tickers?symbols=tBTCUSD,tETHUSD` |
| Candles (public) | GET | `/v2/candles/trade:{TimeFrame}:{Symbol}/hist` |
| Wallets | POST | `/v2/auth/r/wallets` |
| Place order | POST | `/v2/auth/w/order/submit` |
| Update order | POST | `/v2/auth/w/order/update` |
| Cancel order | POST | `/v2/auth/w/order/cancel` |
| Cancel all | POST | `/v2/auth/w/order/cancel/multi` body: `{"all": 1}` |
| Active orders | POST | `/v2/auth/r/orders` |
| Order history | POST | `/v2/auth/r/orders/hist` |
| Positions | POST | `/v2/auth/r/positions` |
| Transfer | POST | `/v2/auth/w/transfer` |
### Place Order — Parameters
```json
{
"type": "EXCHANGE LIMIT",
"symbol": "tBTCUSD",
"amount": "0.01",
"price": "50000",
"flags": 0,
"meta": {"aff_code": "ZZDLtrXMF"}
}
```
| Param | Type | Required | Notes |
|---|---|---|---|
| type | string | Yes | See order types above |
| symbol | string | Yes | e.g. `tBTCUSD` |
| amount | string | Yes | Positive = buy, negative = sell |
| price | string | Yes* | Not needed for MARKET |
| flags | int | No | Sum of flag values |
| lev | int | No | Leverage 1-100 (derivatives only) |
| price_trailing | string | No | For TRAILING STOP |
| price_aux_limit | string | No | For STOP LIMIT |
| price_oco_stop | string | No | OCO stop price |
| tif | string | No | Auto-cancel time `"2026-01-15 10:45:23"` |
| meta | object | No | `{"aff_code": "ZZDLtrXMF"}` |
### Cancel Order
```json
{"id": 123456789}
```
Or by client ID: `{"cid": 12345, "cid_date": "2026-04-17"}`
### Transfer Between Wallets
```json
{
"from": "exchange",
"to": "funding",
"currency": "USD",
"amount": "1000"
}
```
---
## Key Endpoints — Funding (Lending)
| Action | Method | Path |
|---|---|---|
| Funding ticker (public) | GET | `/v2/ticker/{fSymbol}` |
| Submit funding offer | POST | `/v2/auth/w/funding/offer/submit` |
| Cancel funding offer | POST | `/v2/auth/w/funding/offer/cancel` |
| Active funding offers | POST | `/v2/auth/r/funding/offers/{Symbol}` |
| Funding loans (idle) | POST | `/v2/auth/r/funding/loans/{Symbol}` |
| Funding credits (in use) | POST | `/v2/auth/r/funding/credits/{Symbol}` |
| Funding info | POST | `/v2/auth/r/info/funding/{key}` |
### Submit Funding Offer — Parameters
```json
{
"type": "LIMIT",
"symbol": "fUSD",
"amount": "1000",
"rate": "0.0002",
"period": 2
}
```
| Param | Type | Required | Notes |
|---|---|---|---|
| type | string | Yes | `LIMIT`, `FRRDELTAVAR`, `FRRDELTAFIX` |
| symbol | string | Yes | `fUSD`, `fBTC`, `fUST`, etc. |
| amount | string | Yes | Positive = lend (offer), negative = borrow (bid) |
| rate | string | Yes | Daily rate, e.g. `"0.0002"` = 0.02%/day ≈ 7.3%/yr |
| period | int | Yes | 2–120 days |
| flags | int | No | 64 = hidden |
**Funding types:**
- `LIMIT` — fixed rate
- `FRRDELTAVAR` — Flash Return Rate + delta (variable, rate adjusts)
- `FRRDELTAFIX` — Flash Return Rate + delta (fixed after match)
### Cancel Funding Offer
```json
{"id": 987654321}
```
### Funding Loans vs Credits
- **Loans** (`/funding/loans`) — your lent funds that are NOT currently used in a position
- **Credits** (`/funding/credits`) — your lent funds that ARE currently used in a position
---
## Response Arrays
Bitfinex v2 returns **arrays**, not objects. Key mappings:
### Order Array
| Index | Field | Index | Field |
|---|---|---|---|
| 0 | ID | 6 | AMOUNT (remaining) |
| 3 | SYMBOL | 7 | AMOUNT_ORIG |
| 4 | MTS_CREATE | 8 | TYPE |
| 5 | MTS_UPDATE | 13 | STATUS |
| 17 | PRICE | 18 | PRICE_AVG |
### Wallet Array
| Index | Field |
|---|---|
| 0 | WALLET_TYPE (`exchange`/`margin`/`funding`) |
| 1 | CURRENCY |
| 2 | BALANCE |
| 4 | AVAILABLE_BALANCE |
### Position Array
| Index | Field | Index | Field |
|---|---|---|---|
| 0 | SYMBOL | 6 | PL |
| 1 | STATUS | 7 | PL_PERC |
| 2 | AMOUNT (+long/-short) | 8 | PRICE_LIQ |
| 3 | BASE_PRICE | 9 | LEVERAGE |
### Funding Offer Array
| Index | Field | Index | Field |
|---|---|---|---|
| 0 | ID | 10 | STATUS |
| 1 | SYMBOL | 14 | RATE |
| 4 | AMOUNT | 15 | PERIOD |
| 5 | AMOUNT_ORIG | 19 | RENEW |
---
## Security
- WRITE operations require **"CONFIRM"**
- Always show liquidation price before opening margin positions
- "Not financial advice. Trading carries significant risk of loss."
---
FILE:references/bitget-api-reference.md
# Bitget API Reference
## Base URL
`https://api.bitget.com`
**Success response:** `"code": "00000"` — all other codes are errors.
---
## Authentication
**Credentials** (from `.env`): `BITGET_API_KEY`, `BITGET_SECRET_KEY`, `BITGET_PASSPHRASE`
**Signature:** `Base64(HMAC-SHA256(secret, timestamp + METHOD + path + body))`
- `timestamp`: Unix milliseconds
- GET body = `""`
- POST body = compact JSON
**Headers (authenticated requests):**
```
ACCESS-KEY: <api_key>
ACCESS-SIGN: <base64 signature>
ACCESS-PASSPHRASE: <passphrase>
ACCESS-TIMESTAMP: <unix ms>
Content-Type: application/json
locale: en-US
```
---
## Python Signature Implementation
```python
import time, hmac, hashlib, base64, json, os, requests
from dotenv import dotenv_values
_env = dotenv_values()
API_KEY = _env["BITGET_API_KEY"]
SECRET_KEY = _env["BITGET_SECRET_KEY"]
PASSPHRASE = _env["BITGET_PASSPHRASE"]
BASE_URL = "https://api.bitget.com"
def _sign(ts, method, path, body_str=""):
msg = f"{ts}{method}{path}{body_str}"
mac = hmac.new(SECRET_KEY.encode(), msg.encode(), hashlib.sha256).digest()
return base64.b64encode(mac).decode()
def _headers(method, path, body_str=""):
ts = str(int(time.time() * 1000))
return {
"ACCESS-KEY": API_KEY,
"ACCESS-SIGN": _sign(ts, method, path, body_str),
"ACCESS-PASSPHRASE": PASSPHRASE,
"ACCESS-TIMESTAMP": ts,
"Content-Type": "application/json",
"locale": "en-US",
}
def bitget_get(path, params=None):
qs = ""
if params:
qs = "?" + "&".join(f"{k}={v}" for k, v in params.items())
h = _headers("GET", path + qs)
r = requests.get(f"{BASE_URL}{path}", params=params, headers=h, timeout=10)
data = r.json()
if data.get("code") != "00000":
raise Exception(f"Bitget error {data.get('code')}: {data.get('msg')}")
return data.get("data")
def bitget_post(path, body=None):
body_str = json.dumps(body, separators=(",", ":")) if body else ""
h = _headers("POST", path, body_str)
r = requests.post(f"{BASE_URL}{path}", data=body_str, headers=h, timeout=10)
data = r.json()
if data.get("code") != "00000":
raise Exception(f"Bitget error {data.get('code')}: {data.get('msg')}")
return data.get("data")
```
---
## Account Balance
| Method | Path | Description |
|---|---|---|
| GET | `/api/v2/spot/account/assets` | Spot balances |
| GET | `/api/v2/mix/account/accounts` | Futures account info (`productType` required: `USDT-FUTURES`, `USDC-FUTURES`, `COIN-FUTURES`) |
| GET | `/api/v2/account/funding-assets` | Funding account assets |
| GET | `/api/v2/account/all-account-balance` | All accounts overview |
---
## Spot Endpoints
### Market Data (Public)
| Method | Path | Description |
|---|---|---|
| GET | `/api/v2/spot/market/tickers` | Ticker (`symbol` optional — omit for all) |
| GET | `/api/v2/spot/market/orderbook` | Orderbook (`symbol` required, `type`=step0-5) |
| GET | `/api/v2/spot/market/candles` | Kline (`symbol`, `granularity`, `startTime`, `endTime`) |
| GET | `/api/v2/spot/market/trades` | Recent trades |
| GET | `/api/v2/spot/public/symbols` | Symbol info |
### Trading (Private)
| Method | Path | Description |
|---|---|---|
| POST | `/api/v2/spot/trade/place-order` | Place order |
| POST | `/api/v2/spot/trade/batch-orders` | Batch place orders |
| POST | `/api/v2/spot/trade/cancel-order` | Cancel order |
| POST | `/api/v2/spot/trade/batch-cancel-order` | Batch cancel |
| POST | `/api/v2/spot/trade/cancel-symbol-order` | Cancel all for symbol |
| POST | `/api/v2/spot/trade/cancel-replace-order` | Atomic modify order |
| GET | `/api/v2/spot/trade/orderInfo` | Order details |
| GET | `/api/v2/spot/trade/unfilled-orders` | Open orders |
| GET | `/api/v2/spot/trade/history-orders` | Order history |
| GET | `/api/v2/spot/trade/fills` | Execution fills |
**Spot order params:** `symbol`, `side` (buy/sell), `orderType` (limit/market), `price`, `size`, `force` (gtc/ioc/fok/post_only)
### Plan Orders (Private)
| Method | Path | Description |
|---|---|---|
| POST | `/api/v2/spot/trade/place-plan-order` | Create trigger order |
| POST | `/api/v2/spot/trade/modify-plan-order` | Modify trigger order |
| POST | `/api/v2/spot/trade/cancel-plan-order` | Cancel trigger order |
| POST | `/api/v2/spot/trade/batch-cancel-plan-order` | Batch cancel triggers |
| GET | `/api/v2/spot/trade/current-plan-order` | Open plan orders |
| GET | `/api/v2/spot/trade/history-plan-order` | Plan order history |
---
## Futures (Mix) Endpoints
### Market Data (Public)
| Method | Path | Description |
|---|---|---|
| GET | `/api/v2/mix/market/ticker` | Futures ticker (`symbol` or `productType`) |
| GET | `/api/v2/mix/market/depth` | Orderbook (`symbol`, `limit`) |
| GET | `/api/v2/mix/market/candles` | Kline (trade/index/mark price types) |
| GET | `/api/v2/mix/market/trades` | Recent trades |
| GET | `/api/v2/mix/public/contracts` | Contract info |
| GET | `/api/v2/mix/market/funding-rate` | Funding rate (current/historical) |
| GET | `/api/v2/mix/market/open-interest` | Open interest |
### Trading (Private)
| Method | Path | Description |
|---|---|---|
| POST | `/api/v2/mix/order/place-order` | Place order |
| POST | `/api/v2/mix/order/batch-place-order` | Batch place (max 50) |
| POST | `/api/v2/mix/order/modify-order` | Modify order (size, price, TP/SL) |
| POST | `/api/v2/mix/order/cancel-order` | Cancel order |
| POST | `/api/v2/mix/order/batch-cancel-orders` | Batch cancel |
| POST | `/api/v2/mix/order/cancel-all-orders` | Cancel all |
| GET | `/api/v2/mix/order/detail` | Order details |
| GET | `/api/v2/mix/order/orders-pending` | Open orders |
| GET | `/api/v2/mix/order/orders-history` | Order history |
| GET | `/api/v2/mix/order/fills` | Fills |
| GET | `/api/v2/mix/order/fill-history` | Fill history |
**Futures order params:** `symbol`, `productType` (USDT-FUTURES/USDC-FUTURES/COIN-FUTURES), `side` (buy/sell), `tradeSide` (open/close), `orderType` (limit/market), `price`, `size`, `marginCoin`, `leverage`
### Position Management (Private)
| Method | Path | Description |
|---|---|---|
| GET | `/api/v2/mix/position/single-position` | Single position |
| GET | `/api/v2/mix/position/all-position` | All positions |
| GET | `/api/v2/mix/position/history-position` | Position history |
### Configuration (Private)
| Method | Path | Description |
|---|---|---|
| POST | `/api/v2/mix/account/set-leverage` | Set leverage |
| POST | `/api/v2/mix/account/set-margin-mode` | Set margin mode (crossed/isolated) |
| POST | `/api/v2/mix/account/set-position-mode` | Set position mode |
| POST | `/api/v2/mix/account/set-auto-margin` | Auto margin addition |
---
## Transfers
| Method | Path | Description |
|---|---|---|
| POST | `/api/v2/spot/wallet/transfer` | Internal transfer between accounts |
| POST | `/api/v2/spot/wallet/subaccount-transfer` | Transfer to subaccount |
---
## Symbol Format
- Spot: `BTCUSDT`
- Futures: `BTCUSDT` with `productType=USDT-FUTURES`
## Rate Limits
- Public: 10-20 req/s per IP
- Private: 5-10 req/s per UID
- Configuration: 5 req/s per UID
FILE:references/bitget-skill.md
# Bitget Trading
**Base URL:** `https://api.bitget.com` | **Spot:** `BTCUSDT` | **Futures:** `BTCUSDT` + `productType=USDT-FUTURES` | **Success:** `"code": "00000"`
Full details in `references/bitget-api-reference.md`
## Authentication
**Credentials** (from `.env`): `BITGET_API_KEY`, `BITGET_SECRET_KEY`, `BITGET_PASSPHRASE`
No Bitget account? Register at **[https://www.bitget.com/](https://www.bitget.com/)**
Verify credentials before any private call. If missing — **STOP**.
**Signature:** `Base64(HMAC-SHA256(secret, timestamp + METHOD + path + body))`
- `timestamp`: Unix milliseconds
- GET body = `""`
- POST body = compact JSON (no spaces)
**Headers (authenticated requests):**
```
ACCESS-KEY: <api_key>
ACCESS-SIGN: <base64 signature>
ACCESS-PASSPHRASE: <passphrase>
ACCESS-TIMESTAMP: <unix ms>
Content-Type: application/json
locale: en-US
```
> Python signature implementation: `references/bitget-api-reference.md`
## Operation Flow
### Step 0: Credential Check
Verify `BITGET_API_KEY`, `BITGET_SECRET_KEY`, `BITGET_PASSPHRASE`. If missing — **STOP**.
### Step 1: Pre-Trade Check (Futures)
- Query positions: `GET /api/v2/mix/position/all-position?productType=USDT-FUTURES`
- If position exists → inherit leverage and margin mode, do NOT override
### Step 2: Execute
- READ → call, parse, display
- WRITE → present summary → ask **"CONFIRM"** → execute
### Step 3: Verify
After order → query order status. After close → query positions.
## Quick Reference
| Operation | Method | Path |
|---|---|---|
| Spot balances | GET | `/api/v2/spot/account/assets` |
| Futures account | GET | `/api/v2/mix/account/accounts?productType=USDT-FUTURES` |
| All balances | GET | `/api/v2/account/all-account-balance` |
| Place spot order | POST | `/api/v2/spot/trade/place-order` |
| Cancel spot order | POST | `/api/v2/spot/trade/cancel-order` |
| Spot open orders | GET | `/api/v2/spot/trade/unfilled-orders` |
| Place futures order | POST | `/api/v2/mix/order/place-order` |
| Cancel futures order | POST | `/api/v2/mix/order/cancel-order` |
| Futures positions | GET | `/api/v2/mix/position/all-position` |
| Set leverage | POST | `/api/v2/mix/account/set-leverage` |
| Set margin mode | POST | `/api/v2/mix/account/set-margin-mode` |
| Spot ticker | GET | `/api/v2/spot/market/tickers` |
| Futures ticker | GET | `/api/v2/mix/market/ticker` |
## Security
- WRITE operations require **"CONFIRM"**
- Always show liquidation price before opening leveraged positions
- "Not financial advice. Trading carries significant risk of loss."
## References
- `references/bitget-api-reference.md` — spot + futures endpoints, Python signature
---
FILE:references/bitmart-api-reference.md
# BitMart Futures API Reference
> Complete reference for all 53 futures endpoints. See [SKILL.md](../SKILL.md) for authentication, routing, and quickstart.
> **Credentials:** Read from `.env` — `BITMART_API_KEY`, `BITMART_API_SECRET`, `BITMART_API_MEMO`.
> **IMPORTANT:** Always include `X-BM-BROKER-ID: BlaveData666666` on **every** request (NONE / KEYED / SIGNED). This is required for broker fee tracking.
---
## Market Data Endpoints (NONE auth)
### 1. Contract Details
`GET /contract/public/details`
**Rate Limit:** 12 req/2sec per IP
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| symbol | String | No | Contract symbol, e.g. `BTCUSDT`. Omit for all contracts |
```bash
curl -s -H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
'https://api-cloud-v2.bitmart.com/contract/public/details?symbol=BTCUSDT'
```
**Response:**
```json
{
"code": 1000,
"message": "OK",
"data": {
"symbols": [{
"symbol": "BTCUSDT",
"product_type": 1,
"open_timestamp": 1594080000000,
"expire_timestamp": 0,
"settle_timestamp": 0,
"base_currency": "BTC",
"quote_currency": "USDT",
"last_price": "67123.4",
"volume_24h": "123456789",
"turnover_24h": "8234567890.12",
"index_price": "67120.5",
"index_name": "BTCUSDT",
"contract_size": "0.001",
"min_leverage": "1",
"max_leverage": "100",
"price_precision": "0.1",
"vol_precision": "1",
"max_volume": "1000000",
"market_max_volume": "50000",
"min_volume": "1",
"funding_rate": "0.0001",
"expected_funding_rate": "0.0001",
"open_interest": "12345678",
"open_interest_value": "823456789.12",
"high_24h": "68000.0",
"low_24h": "66500.0",
"change_24h": "0.015",
"funding_interval_hours": 8,
"funding_time": 1773158400000,
"status": "Trading",
"delist_time": 0
}]
}
}
```
| Field | Description |
|-------|-------------|
| symbol | Contract symbol |
| product_type | 1 = perpetual, 2 = futures |
| open_timestamp | Contract listing timestamp (ms) |
| expire_timestamp | Expiry timestamp (0 for perpetual) |
| base_currency | Base currency (e.g. BTC) |
| quote_currency | Quote currency (e.g. USDT) |
| last_price | Last trade price |
| volume_24h | 24h volume in contracts |
| turnover_24h | 24h turnover in quote currency |
| index_price | Current index price |
| contract_size | Size of one contract in base currency |
| min_leverage / max_leverage | Leverage range |
| price_precision | Minimum price tick |
| vol_precision | Minimum volume tick |
| max_volume | Maximum order volume |
| market_max_volume | Maximum market order volume |
| min_volume | Minimum order volume |
| funding_rate | Current funding rate |
| expected_funding_rate | Next expected funding rate |
| open_interest | Total open interest (contracts) |
| open_interest_value | Total open interest (quote currency) |
| high_24h / low_24h | 24h high / low |
| change_24h | 24h price change ratio |
| funding_interval_hours | Funding interval in hours |
| funding_time | Next funding settlement timestamp (ms) |
| status | Contract status: `"Trading"` or `"Delisted"` |
**Live verification (2026-03-13):** `GET /contract/public/details` returns both `vol_precision` and `funding_time` in JSON data. Official field-table text that says `volume_precision` is inconsistent with live payload.
**Important:** Always check `contract_size`, `min_volume`, `max_volume`, `price_precision`, and `max_leverage` before placing orders.
---
### 2. Order Book Depth
`GET /contract/public/depth`
**Rate Limit:** 12 req/2sec per IP
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| symbol | String | Yes | Contract symbol, e.g. `BTCUSDT` |
```bash
curl -s -H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
'https://api-cloud-v2.bitmart.com/contract/public/depth?symbol=BTCUSDT'
```
**Response:**
```json
{
"code": 1000,
"message": "OK",
"data": {
"asks": [["67125.0", "150", "150"], ["67130.0", "320", "470"]],
"bids": [["67120.0", "200", "200"], ["67115.0", "180", "380"]],
"symbol": "BTCUSDT",
"timestamp": 1709971200000
}
}
```
| Field | Description |
|-------|-------------|
| asks | Ask levels `[price, volume, cumulative_volume]`, sorted ascending |
| bids | Bid levels `[price, volume, cumulative_volume]`, sorted descending |
| symbol | Contract symbol |
| timestamp | Timestamp (ms) |
---
### 3. Recent Market Trades
`GET /contract/public/market-trade`
**Rate Limit:** 12 req/2sec per IP
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| symbol | String | Yes | Contract symbol, e.g. `BTCUSDT` |
| limit | Long | No | Number of trades, default 50, max 100 |
```bash
curl -s -H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
'https://api-cloud-v2.bitmart.com/contract/public/market-trade?symbol=BTCUSDT&limit=5'
```
**Response:**
```json
{
"code": 1000,
"message": "Ok",
"data": [
{
"symbol": "BTCUSDT",
"price": "67123.4",
"qty": "1.506",
"quote_qty": "101127.7404",
"time": 1709971200,
"is_buyer_maker": false
}
]
}
```
| Field | Description |
|-------|-------------|
| symbol | Contract symbol |
| price | Trade price |
| qty | Trade quantity |
| quote_qty | Quote currency amount (`price * qty`) |
| time | Trade timestamp (seconds) |
| is_buyer_maker | `true` if buyer is maker, `false` if taker |
---
### 4. Current Funding Rate
`GET /contract/public/funding-rate`
**Rate Limit:** 12 req/2sec per IP
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| symbol | String | Yes | Contract symbol, e.g. `BTCUSDT` |
```bash
curl -s -H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
'https://api-cloud-v2.bitmart.com/contract/public/funding-rate?symbol=BTCUSDT'
```
**Response:**
```json
{
"code": 1000,
"message": "OK",
"data": {
"symbol": "BTCUSDT",
"rate_value": "0.0001",
"expected_rate": "0.00012",
"funding_time": 1709971200000,
"funding_upper_limit": "0.003",
"funding_lower_limit": "-0.003",
"timestamp": 1709971200000
}
}
```
| Field | Description |
|-------|-------------|
| symbol | Contract symbol |
| rate_value | Current funding rate |
| expected_rate | Next expected funding rate |
| funding_time | Next funding settlement time (ms) |
| funding_upper_limit | Funding rate upper limit |
| funding_lower_limit | Funding rate lower limit |
| timestamp | Timestamp (ms) |
---
### 5. Funding Rate History
`GET /contract/public/funding-rate-history`
**Rate Limit:** 12 req/2sec per IP
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| symbol | String | Yes | Contract symbol, e.g. `BTCUSDT` |
| limit | String | No | Records per page, default 100, max 100 |
```bash
curl -s -H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
'https://api-cloud-v2.bitmart.com/contract/public/funding-rate-history?symbol=BTCUSDT&limit=5'
```
**Response:**
```json
{
"code": 1000,
"message": "OK",
"data": {
"list": [{
"symbol": "BTCUSDT",
"funding_rate": "0.0001",
"funding_time": 1709971200000
}]
}
}
```
| Field | Description |
|-------|-------------|
| symbol | Contract symbol |
| funding_rate | Funding rate at settlement |
| funding_time | Settlement timestamp (ms) |
---
### 6. K-Line / Candlestick
`GET /contract/public/kline`
**Rate Limit:** 12 req/2sec per IP | **Max Records:** 500 per request
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| symbol | String | Yes | Contract symbol, e.g. `BTCUSDT` |
| step | Long | No | Interval in minutes: 1, 3, 5, 15, 30, 60, 120, 240, 360, 720, 1440, 4320, 10080 (default 1) |
| start_time | Long | Yes | Start timestamp in **seconds** |
| end_time | Long | Yes | End timestamp in **seconds** |
```bash
curl -s -H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
'https://api-cloud-v2.bitmart.com/contract/public/kline?symbol=BTCUSDT&step=60&start_time=1709942400&end_time=1709971200'
```
**Response:**
```json
{
"code": 1000,
"message": "OK",
"data": [{
"timestamp": 1709971200,
"open_price": "67100.0",
"high_price": "67200.0",
"low_price": "67050.0",
"close_price": "67123.4",
"volume": "12345"
}]
}
```
| Field | Description |
|-------|-------------|
| timestamp | Candle open time in seconds |
| open_price | Open price |
| high_price | High price |
| low_price | Low price |
| close_price | Close price |
| volume | Volume in contracts |
---
### 7. Mark Price K-Line
`GET /contract/public/markprice-kline`
**Rate Limit:** 12 req/2sec per IP | **Max Records:** 500 per request
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| symbol | String | Yes | Contract symbol, e.g. `BTCUSDT` |
| step | Long | No | Interval in minutes (same values as kline) |
| start_time | Long | Yes | Start timestamp in **seconds** |
| end_time | Long | Yes | End timestamp in **seconds** |
```bash
curl -s -H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
'https://api-cloud-v2.bitmart.com/contract/public/markprice-kline?symbol=BTCUSDT&step=60&start_time=1709942400&end_time=1709971200'
```
**Response:**
```json
{
"code": 1000,
"message": "OK",
"data": [{
"timestamp": 1709971200,
"open_price": "67098.5",
"high_price": "67198.0",
"low_price": "67048.2",
"close_price": "67120.5",
"volume": "0"
}]
}
```
| Field | Description |
|-------|-------------|
| timestamp | Candle open time in seconds |
| open_price | Mark price open |
| high_price | Mark price high |
| low_price | Mark price low |
| close_price | Mark price close |
| volume | Volume (typically 0 for mark price klines) |
---
### 8. Open Interest
`GET /contract/public/open-interest`
**Rate Limit:** 2 req/2sec per IP
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| symbol | String | Yes | Contract symbol, e.g. `BTCUSDT` |
```bash
curl -s -H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
'https://api-cloud-v2.bitmart.com/contract/public/open-interest?symbol=BTCUSDT'
```
**Response:**
```json
{
"code": 1000,
"message": "OK",
"data": {
"symbol": "BTCUSDT",
"open_interest": "12345678",
"open_interest_value": "823456789.12",
"timestamp": 1709971200000
}
}
```
| Field | Description |
|-------|-------------|
| symbol | Contract symbol |
| open_interest | Total open interest (contracts) |
| open_interest_value | Total open interest (quote currency) |
| timestamp | Timestamp (ms) |
---
### 9. Leverage Brackets / Risk Limits
`GET /contract/public/leverage-bracket`
**Rate Limit:** 12 req/2sec per IP
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| symbol | String | No | Contract symbol, e.g. `BTCUSDT`. Omit for all |
```bash
curl -s -H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
'https://api-cloud-v2.bitmart.com/contract/public/leverage-bracket?symbol=BTCUSDT'
```
**Response:**
```json
{
"code": 1000,
"message": "OK",
"data": {
"rules": [{
"symbol": "BTCUSDT",
"brackets": [{
"bracket": 1,
"initial_leverage": 100,
"notional_cap": "500000",
"notional_floor": "0",
"maint_margin_ratio": "0.004",
"cum": "0"
}]
}]
}
}
```
| Field | Description |
|-------|-------------|
| symbol | Contract symbol |
| bracket | Bracket tier number |
| initial_leverage | Maximum leverage at this tier |
| notional_cap | Upper bound of notional value |
| notional_floor | Lower bound of notional value |
| maint_margin_ratio | Maintenance margin rate |
| cum | Cumulative value |
**Important:** Higher position sizes require lower leverage. Check brackets before setting leverage to avoid rejection.
---
## Account Endpoints (KEYED auth)
### 10. Futures Account Balance
`GET /contract/private/assets-detail`
**Rate Limit:** 12 req/2sec per KEY
**Parameters:** None
```bash
curl -s -H "X-BM-KEY: $BITMART_API_KEY" \
-H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
'https://api-cloud-v2.bitmart.com/contract/private/assets-detail'
```
**Response:**
```json
{
"code": 1000,
"message": "OK",
"data": [{
"currency": "USDT",
"available_balance": "50000.00",
"frozen_balance": "5000.00",
"position_deposit": "10000.00",
"equity": "65000.00",
"unrealized": "0.00"
}]
}
```
| Field | Description |
|-------|-------------|
| currency | Currency symbol |
| available_balance | Available balance for trading |
| frozen_balance | Frozen balance (in open orders) |
| position_deposit | Margin used by positions |
| equity | Total equity (balance + unrealized PnL) |
| unrealized | Unrealized profit/loss |
---
### 11. Trade Fee Rate
`GET /contract/private/trade-fee-rate`
**Rate Limit:** 2 req/2sec per KEY
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| symbol | String | Yes | Contract symbol, e.g. `BTCUSDT` |
```bash
curl -s -H "X-BM-KEY: $BITMART_API_KEY" \
-H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
'https://api-cloud-v2.bitmart.com/contract/private/trade-fee-rate?symbol=BTCUSDT'
```
**Response:**
```json
{
"code": 1000,
"message": "OK",
"data": {
"symbol": "BTCUSDT",
"taker_fee_rate": "0.0006",
"maker_fee_rate": "0.0002"
}
}
```
---
### 12. Current Positions
`GET /contract/private/position`
**Rate Limit:** 6 req/2sec per KEY
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| symbol | String | No | Contract symbol. Omit for all positions |
| account | String | No | `"futures"` (default) or `"copy_trading"` |
```bash
curl -s -H "X-BM-KEY: $BITMART_API_KEY" \
-H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
'https://api-cloud-v2.bitmart.com/contract/private/position?symbol=BTCUSDT'
```
**Response:**
```json
{
"code": 1000,
"message": "OK",
"data": [{
"symbol": "BTCUSDT",
"leverage": "10",
"timestamp": 1709971200000,
"current_fee": "12.50",
"open_timestamp": 1709942400000,
"current_value": "6712.34",
"mark_price": "67123.4",
"mark_value": "6712.34",
"position_value": "6700.00",
"position_cross": "100",
"maintenance_margin": "26.80",
"margin_type": "Cross",
"position_mode": "hedge_mode",
"close_vol": "0",
"close_avg_price": "0",
"open_avg_price": "67000.0",
"entry_price": "67000.0",
"current_amount": "100",
"unrealized_value": "12.34",
"realized_value": "0",
"position_type": 1,
"account": "futures"
}]
}
```
| Field | Description |
|-------|-------------|
| symbol | Contract symbol |
| leverage | Current leverage |
| mark_price | Current mark price |
| position_value | Position value in quote currency |
| entry_price | Average entry price |
| current_amount | Position size (contracts) |
| unrealized_value | Unrealized PnL |
| realized_value | Realized PnL |
| position_type | 1 = long, 2 = short |
| margin_type | `"Cross"` or `"Isolated"` |
| position_mode | `"hedge_mode"` or `"one_way_mode"` |
| current_fee | Accumulated fees |
| close_vol | Closed volume |
| close_avg_price | Average close price |
| current_value | Current value based on mark price |
| maintenance_margin | Maintenance margin |
| account | Account type |
---
### 13. Positions V2 (Extended Info)
`GET /contract/private/position-v2`
**Rate Limit:** 6 req/2sec per KEY
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| symbol | String | No | Contract symbol. Without symbol returns only positions with holdings; with symbol returns all positions |
| account | String | No | `"futures"` (default) or `"copy_trading"` |
```bash
curl -s -H "X-BM-KEY: $BITMART_API_KEY" \
-H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
'https://api-cloud-v2.bitmart.com/contract/private/position-v2?symbol=BTCUSDT'
```
**Response:**
```json
{
"code": 1000,
"message": "OK",
"data": [{
"symbol": "BTCUSDT",
"leverage": "10",
"position_amount": "100",
"position_side": "long",
"entry_price": "67000.0",
"mark_price": "67123.4",
"liquidation_price": "60500.0",
"unrealized_pnl": "12.34",
"initial_margin": "670.00",
"maintenance_margin": "26.80",
"position_value": "6712.34",
"open_type": "cross",
"max_notional_value": "500000",
"timestamp": 1709971200000,
"current_fee": "12.50",
"open_timestamp": 1709942400000,
"current_value": "6712.34",
"close_vol": "0",
"close_avg_price": "0",
"open_avg_price": "67000.0",
"current_amount": "100",
"realized_value": "0",
"mark_value": "6712.34",
"account": "futures"
}]
}
```
| Field | Description |
|-------|-------------|
| position_side | `"both"`, `"long"`, or `"short"` |
| liquidation_price | Estimated liquidation price |
| unrealized_pnl | Unrealized profit/loss |
| initial_margin | Initial margin used |
| maintenance_margin | Maintenance margin required |
| current_amount | Current position size (contracts, string, always ≥ 0). **Use this field to determine whether a position exists** (parse as number, check ≠ 0) |
| position_amount | Current position direction amount (hedge mode: always positive; one-way mode: positive=long, negative=short) |
| open_type | `"cross"` or `"isolated"` |
| max_notional_value | Maximum notional value for current leverage |
| *(other fields same as endpoint 12)* | |
---
### 14. Position Risk
`GET /contract/private/position-risk`
**Rate Limit:** 24 req/2sec per KEY
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| symbol | String | No | Contract symbol. Omit for all positions |
| account | String | No | `"futures"` (default) or `"copy_trading"` |
```bash
curl -s -H "X-BM-KEY: $BITMART_API_KEY" \
-H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
'https://api-cloud-v2.bitmart.com/contract/private/position-risk?symbol=BTCUSDT'
```
**Response:**
```json
{
"code": 1000,
"message": "OK",
"data": [{
"symbol": "BTCUSDT",
"position_amt": "100",
"mark_price": "67123.4",
"unrealized_profit": "12.34",
"liquidation_price": "60500.0",
"leverage": "10",
"max_notional_value": "500000",
"margin_type": "cross",
"isolated_margin": "0",
"position_side": "Long",
"notional": "6712.34",
"update_time": 1709971200000,
"account": "futures"
}]
}
```
| Field | Description |
|-------|-------------|
| position_amt | Position size |
| mark_price | Current mark price |
| unrealized_profit | Unrealized PnL |
| liquidation_price | Estimated liquidation price |
| leverage | Current leverage |
| max_notional_value | Max notional value |
| margin_type | `"cross"` or `"isolated"` |
| isolated_margin | Isolated margin amount |
| position_side | `"Long"` or `"Short"` |
| notional | Position notional value |
---
### 15. Get Position Mode
`GET /contract/private/get-position-mode`
**Rate Limit:** 2 req/2sec per KEY
**Parameters:** None
```bash
curl -s -H "X-BM-KEY: $BITMART_API_KEY" \
-H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
'https://api-cloud-v2.bitmart.com/contract/private/get-position-mode'
```
**Live verification (2026-03-13):** KEYED request (only `X-BM-KEY`) and SIGNED request both returned `code=1000`. Use KEYED as the minimum required auth level.
**Response:**
```json
{
"code": 1000,
"message": "OK",
"data": {
"position_mode": "hedge_mode"
}
}
```
---
### 16. Transaction History
`GET /contract/private/transaction-history`
**Rate Limit:** 6 req/2sec per KEY | **Default Range:** Last 7 days
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| symbol | String | No | Contract symbol |
| flow_type | Int | No | 0=All, 1=Transfer, 2=Realized PNL, 3=Funding Fee, 4=Commission, 5=Liquidation |
| account | String | No | `"futures"` (default) or `"copy_trading"` |
| start_time | Long | No | Start timestamp in **milliseconds** |
| end_time | Long | No | End timestamp in **milliseconds** |
| page_size | Int | No | Page size, 1-1000, default 100 |
```bash
curl -s -H "X-BM-KEY: $BITMART_API_KEY" \
-H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
'https://api-cloud-v2.bitmart.com/contract/private/transaction-history?symbol=BTCUSDT&flow_type=2&page_size=10'
```
**Response:**
```json
{
"code": 1000,
"message": "OK",
"data": [{
"symbol": "BTCUSDT",
"flow_type": 2,
"type": "Realized PNL",
"amount": "12.34",
"asset": "USDT",
"account": "futures",
"time": 1709971200000,
"tran_id": "123456789"
}]
}
```
| Field | Description |
|-------|-------------|
| symbol | Contract symbol |
| flow_type | Numeric transaction type code: 0=All, 1=Transfer, 2=Realized PNL, 3=Funding Fee, 4=Commission, 5=Liquidation |
| type | Transaction type: Transfer, Realized PNL, Funding Fee, Commission Fee, Liquidation Clearance |
| amount | Transaction amount (negative = outflow) |
| asset | Currency |
| account | Account type |
| time | Transaction timestamp (ms) |
| tran_id | Transaction ID |
---
## Trading Endpoints (SIGNED auth)
### 17. Place Order
`POST /contract/private/submit-order`
**Rate Limit:** 24 req/2sec per KEY
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| symbol | String | Yes | Contract symbol, e.g. `BTCUSDT` |
| side | Int | Yes | 1=buy_open_long, 2=buy_close_short, 3=sell_close_long, 4=sell_open_short |
| type | String | No | `"limit"` (default) or `"market"` |
| price | String | Conditional | Order price. **Required for `type=limit`; do NOT send for `type=market`** (ignored and causes confusion) |
| size | Int | Yes | Order quantity (number of contracts, integer) |
| leverage | String | Conditional | Leverage multiplier (e.g. `"10"`). **Required for opening positions (side=1 or 4)**; ignored for closing (side=2 or 3). If an open position already exists, you MUST use the existing position's leverage (see Step 1.5). |
| open_type | String | Conditional | `"cross"` or `"isolated"`. **Required for opening positions (side=1 or 4)**; ignored for closing (side=2 or 3). If an open position already exists, you MUST use the existing position's `open_type`. |
| mode | Int | No | 1=GTC (default), 2=FOK, 3=IOC, 4=Maker Only. **`mode=4` (Maker Only) is NOT valid with `type=market`** — use only with `type=limit`. |
| client_order_id | String | No | Client-defined order ID (1-32 chars, alphanumeric) |
| stp_mode | Int | No | Self-trade prevention: 1=Cancel Maker (default), 2=Cancel Taker, 3=Cancel Both |
| preset_take_profit_price_type | Int | No | 1=Last Price (default), 2=Mark Price. **Only applies to opening orders (side=1 or 4).** |
| preset_stop_loss_price_type | Int | No | 1=Last Price (default), 2=Mark Price. **Only applies to opening orders (side=1 or 4).** |
| preset_take_profit_price | String | No | Inline preset TP price on this order. **Only applies to opening orders (side=1 or 4).** For TP/SL on an existing position, use `submit-tp-sl-order` instead. |
| preset_stop_loss_price | String | No | Inline preset SL price on this order. **Only applies to opening orders (side=1 or 4).** For TP/SL on an existing position, use `submit-tp-sl-order` instead. |
**Example — Market open long:**
```bash
TIMESTAMP=$(date +%s000)
BODY='{"symbol":"BTCUSDT","side":1,"type":"market","size":1,"leverage":"10","open_type":"cross"}'
SIGN=$(echo -n "TIMESTAMP#BITMART_API_MEMO#BODY" | openssl dgst -sha256 -hmac "$BITMART_API_SECRET" | awk '{print $NF}')
curl -s -X POST 'https://api-cloud-v2.bitmart.com/contract/private/submit-order' \
-H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
-H "Content-Type: application/json" \
-H "X-BM-KEY: $BITMART_API_KEY" \
-H "X-BM-SIGN: $SIGN" \
-H "X-BM-TIMESTAMP: $TIMESTAMP" \
-d "$BODY"
```
**Example — Limit open short with preset TP/SL:**
```bash
TIMESTAMP=$(date +%s000)
BODY='{"symbol":"BTCUSDT","side":4,"type":"limit","price":"70000","size":10,"leverage":"20","open_type":"isolated","mode":1,"preset_take_profit_price":"68000","preset_stop_loss_price":"72000","preset_take_profit_price_type":1,"preset_stop_loss_price_type":1}'
SIGN=$(echo -n "TIMESTAMP#BITMART_API_MEMO#BODY" | openssl dgst -sha256 -hmac "$BITMART_API_SECRET" | awk '{print $NF}')
curl -s -X POST 'https://api-cloud-v2.bitmart.com/contract/private/submit-order' \
-H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
-H "Content-Type: application/json" \
-H "X-BM-KEY: $BITMART_API_KEY" \
-H "X-BM-SIGN: $SIGN" \
-H "X-BM-TIMESTAMP: $TIMESTAMP" \
-d "$BODY"
```
**Response:**
```json
{
"code": 1000,
"message": "OK",
"data": {
"order_id": 23456789012345678,
"price": "70000"
}
}
```
---
### 18. Cancel Order
`POST /contract/private/cancel-order`
**Rate Limit:** 40 req/2sec per KEY
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| symbol | String | Yes | Contract symbol, e.g. `BTCUSDT` |
| order_id | String | No | Order ID (provide order_id or client_order_id) |
| client_order_id | String | No | Client order ID (provide order_id or client_order_id) |
**Note:** If neither `order_id` nor `client_order_id` is provided, cancels all orders for the symbol.
```bash
TIMESTAMP=$(date +%s000)
BODY='{"symbol":"BTCUSDT","order_id":"23456789012345678"}'
SIGN=$(echo -n "TIMESTAMP#BITMART_API_MEMO#BODY" | openssl dgst -sha256 -hmac "$BITMART_API_SECRET" | awk '{print $NF}')
curl -s -X POST 'https://api-cloud-v2.bitmart.com/contract/private/cancel-order' \
-H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
-H "Content-Type: application/json" \
-H "X-BM-KEY: $BITMART_API_KEY" \
-H "X-BM-SIGN: $SIGN" \
-H "X-BM-TIMESTAMP: $TIMESTAMP" \
-d "$BODY"
```
**Response:** `{ "code": 1000, "data": {} }`
---
### 19. Batch Cancel Orders
`POST /contract/private/cancel-orders`
**Rate Limit:** 2 req/2sec per KEY
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| symbol | String | Yes | Contract symbol. Cancels all open orders for this symbol |
```bash
TIMESTAMP=$(date +%s000)
BODY='{"symbol":"BTCUSDT"}'
SIGN=$(echo -n "TIMESTAMP#BITMART_API_MEMO#BODY" | openssl dgst -sha256 -hmac "$BITMART_API_SECRET" | awk '{print $NF}')
curl -s -X POST 'https://api-cloud-v2.bitmart.com/contract/private/cancel-orders' \
-H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
-H "Content-Type: application/json" \
-H "X-BM-KEY: $BITMART_API_KEY" \
-H "X-BM-SIGN: $SIGN" \
-H "X-BM-TIMESTAMP: $TIMESTAMP" \
-d "$BODY"
```
**Response:** `{ "code": 1000, "data": {} }`
---
### 20. Modify Limit Order
`POST /contract/private/modify-limit-order`
**Rate Limit:** 24 req/2sec per UID
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| symbol | String | Yes | Contract symbol |
| order_id | Int | Conditional | Order ID (required if no client_order_id) |
| client_order_id | String | Conditional | Client order ID (required if no order_id) |
| price | String | No | New price (at least one of price/size required) |
| size | Int | No | New size (at least one of price/size required) |
```bash
TIMESTAMP=$(date +%s000)
BODY='{"symbol":"BTCUSDT","order_id":23456789012345678,"price":"66500"}'
SIGN=$(echo -n "TIMESTAMP#BITMART_API_MEMO#BODY" | openssl dgst -sha256 -hmac "$BITMART_API_SECRET" | awk '{print $NF}')
curl -s -X POST 'https://api-cloud-v2.bitmart.com/contract/private/modify-limit-order' \
-H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
-H "Content-Type: application/json" \
-H "X-BM-KEY: $BITMART_API_KEY" \
-H "X-BM-SIGN: $SIGN" \
-H "X-BM-TIMESTAMP: $TIMESTAMP" \
-d "$BODY"
```
**Response:**
```json
{ "code": 1000, "data": { "order_id": 23456789012345678, "client_order_id": "" } }
```
---
### 21. Cancel All After (Timed Cancel)
`POST /contract/private/cancel-all-after`
**Rate Limit:** 4 req/2sec per UID
Sets a countdown timer. When the timer expires, all open orders for the specified symbol are canceled. Useful as a dead-man switch.
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| symbol | String | Yes | Contract symbol |
| timeout | Int | Yes | Timeout in seconds (minimum 5, set to 0 to disable) |
```bash
TIMESTAMP=$(date +%s000)
BODY='{"symbol":"BTCUSDT","timeout":60}'
SIGN=$(echo -n "TIMESTAMP#BITMART_API_MEMO#BODY" | openssl dgst -sha256 -hmac "$BITMART_API_SECRET" | awk '{print $NF}')
curl -s -X POST 'https://api-cloud-v2.bitmart.com/contract/private/cancel-all-after' \
-H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
-H "Content-Type: application/json" \
-H "X-BM-KEY: $BITMART_API_KEY" \
-H "X-BM-SIGN: $SIGN" \
-H "X-BM-TIMESTAMP: $TIMESTAMP" \
-d "$BODY"
```
**Response:**
```json
{ "code": 1000, "data": { "result": true, "set_time": 1709971200, "cancel_time": 1709971260 } }
```
---
### 22. Set Leverage
`POST /contract/private/submit-leverage`
**Rate Limit:** 24 req/2sec per KEY
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| symbol | String | Yes | Contract symbol |
| leverage | String | No | Leverage multiplier (e.g. `"10"`) |
| open_type | String | Yes | `"cross"` or `"isolated"` |
```bash
TIMESTAMP=$(date +%s000)
BODY='{"symbol":"BTCUSDT","leverage":"20","open_type":"cross"}'
SIGN=$(echo -n "TIMESTAMP#BITMART_API_MEMO#BODY" | openssl dgst -sha256 -hmac "$BITMART_API_SECRET" | awk '{print $NF}')
curl -s -X POST 'https://api-cloud-v2.bitmart.com/contract/private/submit-leverage' \
-H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
-H "Content-Type: application/json" \
-H "X-BM-KEY: $BITMART_API_KEY" \
-H "X-BM-SIGN: $SIGN" \
-H "X-BM-TIMESTAMP: $TIMESTAMP" \
-d "$BODY"
```
**Response:**
```json
{ "code": 1000, "data": { "symbol": "BTCUSDT", "leverage": "20", "open_type": "cross", "max_value": "500000" } }
```
**Important:** You cannot change leverage while there is an open position with a different margin type.
---
### 23. Set Position Mode
`POST /contract/private/set-position-mode`
**Rate Limit:** 2 req/2sec per KEY
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| position_mode | String | Yes | `"hedge_mode"` or `"one_way_mode"` |
**Note:** No `symbol` parameter — this applies globally to the account.
```bash
TIMESTAMP=$(date +%s000)
BODY='{"position_mode":"hedge_mode"}'
SIGN=$(echo -n "TIMESTAMP#BITMART_API_MEMO#BODY" | openssl dgst -sha256 -hmac "$BITMART_API_SECRET" | awk '{print $NF}')
curl -s -X POST 'https://api-cloud-v2.bitmart.com/contract/private/set-position-mode' \
-H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
-H "Content-Type: application/json" \
-H "X-BM-KEY: $BITMART_API_KEY" \
-H "X-BM-SIGN: $SIGN" \
-H "X-BM-TIMESTAMP: $TIMESTAMP" \
-d "$BODY"
```
**Response:**
```json
{ "code": 1000, "data": { "position_mode": "hedge_mode" } }
```
**Important:** You cannot change position mode while you have open positions. Close all positions first.
---
### 24. Spot-Futures Transfer
`POST /account/v1/transfer-contract`
**Rate Limit:** 1 req/2sec per KEY
> **Note:** Although the path prefix is `/account/v1/`, this endpoint uses the **futures Base URL** (`https://api-cloud-v2.bitmart.com`), not the spot Base URL.
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| currency | String | Yes | Currency symbol (currently USDT only) |
| amount | String | Yes | Transfer amount [0.01-10000000000] |
| type | String | Yes | `"spot_to_contract"` or `"contract_to_spot"` |
| recvWindow | Long | No | Valid duration (0-60000]ms, default 5000 |
```bash
TIMESTAMP=$(date +%s000)
BODY='{"currency":"USDT","amount":"1000","type":"spot_to_contract"}'
SIGN=$(echo -n "TIMESTAMP#BITMART_API_MEMO#BODY" | openssl dgst -sha256 -hmac "$BITMART_API_SECRET" | awk '{print $NF}')
curl -s -X POST 'https://api-cloud-v2.bitmart.com/account/v1/transfer-contract' \
-H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
-H "Content-Type: application/json" \
-H "X-BM-KEY: $BITMART_API_KEY" \
-H "X-BM-SIGN: $SIGN" \
-H "X-BM-TIMESTAMP: $TIMESTAMP" \
-d "$BODY"
```
**Response:**
```json
{ "code": 1000, "data": { "currency": "USDT", "amount": "1000" } }
```
---
## Plan Order Endpoints (SIGNED auth)
### 25. Submit Plan Order (Conditional/Trigger)
`POST /contract/private/submit-plan-order`
**Rate Limit:** 24 req/2sec per UID
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| symbol | String | Yes | Contract symbol |
| side | Int | Yes | 1=buy_open_long, 2=buy_close_short, 3=sell_close_long, 4=sell_open_short |
| type | String | No | `"limit"` (default), `"market"`, `"take_profit"`, `"stop_loss"` |
| leverage | String | Yes | Leverage multiplier |
| open_type | String | Yes | `"cross"` or `"isolated"` |
| mode | Int | No | 1=GTC (default), 2=FOK, 3=IOC, 4=Maker Only |
| size | Int | Yes | Order quantity (contracts) |
| trigger_price | String | Yes | Price that triggers the order |
| executive_price | String | Conditional | Execution price (required when type=limit) |
| price_way | Int | Yes | 1=Bullish (trigger when price rises above), 2=Bearish (trigger when price drops below) |
| price_type | Int | Yes | 1=Last Price, 2=Mark Price |
| plan_category | Int | No | 1=TP/SL, 2=Position TP/SL |
| preset_take_profit_price_type | Int | No | 1=Last Price (default), 2=Mark Price |
| preset_stop_loss_price_type | Int | No | 1=Last Price (default), 2=Mark Price |
| preset_take_profit_price | String | No | Preset TP price |
| preset_stop_loss_price | String | No | Preset SL price |
**Example — Trigger buy long when price drops to 65000:**
```bash
TIMESTAMP=$(date +%s000)
BODY='{"symbol":"BTCUSDT","side":1,"type":"market","size":10,"leverage":"10","open_type":"cross","trigger_price":"65000","price_way":2,"price_type":1}'
SIGN=$(echo -n "TIMESTAMP#BITMART_API_MEMO#BODY" | openssl dgst -sha256 -hmac "$BITMART_API_SECRET" | awk '{print $NF}')
curl -s -X POST 'https://api-cloud-v2.bitmart.com/contract/private/submit-plan-order' \
-H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
-H "Content-Type: application/json" \
-H "X-BM-KEY: $BITMART_API_KEY" \
-H "X-BM-SIGN: $SIGN" \
-H "X-BM-TIMESTAMP: $TIMESTAMP" \
-d "$BODY"
```
**Response:**
```json
{ "code": 1000, "data": { "order_id": 34567890123456789 } }
```
---
### 26. Cancel Plan Order
`POST /contract/private/cancel-plan-order`
**Rate Limit:** 40 req/2sec per UID
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| symbol | String | Yes | Contract symbol |
| order_id | String | Conditional | Order ID (required if no client_order_id) |
| client_order_id | String | Conditional | Client order ID (required if no order_id) |
```bash
TIMESTAMP=$(date +%s000)
BODY='{"symbol":"BTCUSDT","order_id":"34567890123456789"}'
SIGN=$(echo -n "TIMESTAMP#BITMART_API_MEMO#BODY" | openssl dgst -sha256 -hmac "$BITMART_API_SECRET" | awk '{print $NF}')
curl -s -X POST 'https://api-cloud-v2.bitmart.com/contract/private/cancel-plan-order' \
-H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
-H "Content-Type: application/json" \
-H "X-BM-KEY: $BITMART_API_KEY" \
-H "X-BM-SIGN: $SIGN" \
-H "X-BM-TIMESTAMP: $TIMESTAMP" \
-d "$BODY"
```
**Response:** `{ "code": 1000, "data": {} }`
---
### 27. Modify Plan Order
`POST /contract/private/modify-plan-order`
**Rate Limit:** 24 req/2sec per UID
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| symbol | String | Yes | Contract symbol |
| order_id | String | No | Plan order ID |
| type | String | Yes | `"limit"` or `"market"` |
| trigger_price | String | Yes | New trigger price |
| executive_price | String | Conditional | New execution price (required when type=limit) |
| price_type | Int | Yes | 1=Last Price, 2=Mark Price |
```bash
TIMESTAMP=$(date +%s000)
BODY='{"symbol":"BTCUSDT","order_id":"34567890123456789","type":"market","trigger_price":"64000","price_type":1}'
SIGN=$(echo -n "TIMESTAMP#BITMART_API_MEMO#BODY" | openssl dgst -sha256 -hmac "$BITMART_API_SECRET" | awk '{print $NF}')
curl -s -X POST 'https://api-cloud-v2.bitmart.com/contract/private/modify-plan-order' \
-H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
-H "Content-Type: application/json" \
-H "X-BM-KEY: $BITMART_API_KEY" \
-H "X-BM-SIGN: $SIGN" \
-H "X-BM-TIMESTAMP: $TIMESTAMP" \
-d "$BODY"
```
**Response:**
```json
{ "code": 1000, "data": { "order_id": "34567890123456789" } }
```
---
## TP/SL Endpoints (SIGNED auth)
### 28. Submit TP/SL Order
`POST /contract/private/submit-tp-sl-order`
**Rate Limit:** 24 req/2sec per UID
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| symbol | String | Yes | Contract symbol |
| type | String | Yes | `"take_profit"` or `"stop_loss"` |
| side | Int | Yes | 2=Close Short, 3=Close Long (hedge mode); 2=Reduce Buy, 3=Reduce Sell (one-way) |
| trigger_price | String | Yes | Trigger/activation price |
| executive_price | String | Yes | Execution price |
| price_type | Int | Yes | 1=Last Price, 2=Mark Price |
| size | Int | No | Order quantity (default: full position size) |
| plan_category | Int | No | 1=TP/SL, 2=Position TP/SL (default) |
| client_order_id | String | No | Custom ID (1-32 chars) |
| category | String | No | `"limit"` or `"market"` (default market for full position) |
**Example — Set TP for a long position:**
```bash
TIMESTAMP=$(date +%s000)
BODY='{"symbol":"BTCUSDT","type":"take_profit","side":3,"trigger_price":"72000","executive_price":"71900","price_type":1,"plan_category":2}'
SIGN=$(echo -n "TIMESTAMP#BITMART_API_MEMO#BODY" | openssl dgst -sha256 -hmac "$BITMART_API_SECRET" | awk '{print $NF}')
curl -s -X POST 'https://api-cloud-v2.bitmart.com/contract/private/submit-tp-sl-order' \
-H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
-H "Content-Type: application/json" \
-H "X-BM-KEY: $BITMART_API_KEY" \
-H "X-BM-SIGN: $SIGN" \
-H "X-BM-TIMESTAMP: $TIMESTAMP" \
-d "$BODY"
```
**Example — Set SL for a short position:**
```bash
TIMESTAMP=$(date +%s000)
BODY='{"symbol":"BTCUSDT","type":"stop_loss","side":2,"trigger_price":"72000","executive_price":"72100","price_type":1}'
SIGN=$(echo -n "TIMESTAMP#BITMART_API_MEMO#BODY" | openssl dgst -sha256 -hmac "$BITMART_API_SECRET" | awk '{print $NF}')
curl -s -X POST 'https://api-cloud-v2.bitmart.com/contract/private/submit-tp-sl-order' \
-H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
-H "Content-Type: application/json" \
-H "X-BM-KEY: $BITMART_API_KEY" \
-H "X-BM-SIGN: $SIGN" \
-H "X-BM-TIMESTAMP: $TIMESTAMP" \
-d "$BODY"
```
**Response:**
```json
{ "code": 1000, "data": { "order_id": "45678901234567890", "client_order_id": "" } }
```
**TP/SL Logic:**
- **Long position TP**: type=take_profit, side=3, trigger_price > entry_price
- **Long position SL**: type=stop_loss, side=3, trigger_price < entry_price
- **Short position TP**: type=take_profit, side=2, trigger_price < entry_price
- **Short position SL**: type=stop_loss, side=2, trigger_price > entry_price
---
### 29. Modify TP/SL Order
`POST /contract/private/modify-tp-sl-order`
**Rate Limit:** 24 req/2sec per UID
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| symbol | String | Yes | Contract symbol |
| order_id | String | Conditional | Order ID (required if no client_order_id) |
| client_order_id | String | Conditional | Client order ID |
| trigger_price | String | Yes | New trigger price |
| executive_price | String | No | New execution price (required when plan_category=1) |
| price_type | Int | Yes | 1=Last Price, 2=Mark Price |
| plan_category | Int | No | 1=TP/SL, 2=Position TP/SL |
| category | String | No | `"limit"` or `"market"` |
```bash
TIMESTAMP=$(date +%s000)
BODY='{"symbol":"BTCUSDT","order_id":"45678901234567890","trigger_price":"73000","price_type":1}'
SIGN=$(echo -n "TIMESTAMP#BITMART_API_MEMO#BODY" | openssl dgst -sha256 -hmac "$BITMART_API_SECRET" | awk '{print $NF}')
curl -s -X POST 'https://api-cloud-v2.bitmart.com/contract/private/modify-tp-sl-order' \
-H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
-H "Content-Type: application/json" \
-H "X-BM-KEY: $BITMART_API_KEY" \
-H "X-BM-SIGN: $SIGN" \
-H "X-BM-TIMESTAMP: $TIMESTAMP" \
-d "$BODY"
```
**Response:**
```json
{ "code": 1000, "data": { "order_id": "45678901234567890" } }
```
---
### 30. Modify Preset Plan Order (Preset TP/SL on Order)
`POST /contract/private/modify-preset-plan-order`
**Rate Limit:** 24 req/2sec per UID
Modify the preset take-profit/stop-loss attached to an existing order.
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| symbol | String | Yes | Contract symbol |
| order_id | String | Yes | Order ID |
| preset_take_profit_price_type | Int | No | 1=Last Price (default), 2=Mark Price |
| preset_stop_loss_price_type | Int | No | 1=Last Price (default), 2=Mark Price |
| preset_take_profit_price | String | No | New preset TP price |
| preset_stop_loss_price | String | No | New preset SL price |
```bash
TIMESTAMP=$(date +%s000)
BODY='{"symbol":"BTCUSDT","order_id":"23456789012345678","preset_take_profit_price":"73000","preset_stop_loss_price":"64000","preset_take_profit_price_type":1,"preset_stop_loss_price_type":1}'
SIGN=$(echo -n "TIMESTAMP#BITMART_API_MEMO#BODY" | openssl dgst -sha256 -hmac "$BITMART_API_SECRET" | awk '{print $NF}')
curl -s -X POST 'https://api-cloud-v2.bitmart.com/contract/private/modify-preset-plan-order' \
-H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
-H "Content-Type: application/json" \
-H "X-BM-KEY: $BITMART_API_KEY" \
-H "X-BM-SIGN: $SIGN" \
-H "X-BM-TIMESTAMP: $TIMESTAMP" \
-d "$BODY"
```
**Response:**
```json
{ "code": 1000, "data": { "order_id": "23456789012345678" } }
```
---
## Trailing Order Endpoints (SIGNED auth)
### 31. Submit Trailing Stop Order
`POST /contract/private/submit-trail-order`
**Rate Limit:** 24 req/2sec per UID
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| symbol | String | Yes | Contract symbol |
| side | Int | Yes | 1=buy_open_long, 2=buy_close_short, 3=sell_close_long, 4=sell_open_short |
| leverage | String | Yes | Leverage multiplier |
| open_type | String | Yes | `"cross"` or `"isolated"` |
| size | Int | Yes | Order quantity (contracts) |
| activation_price | String | Yes | Price that activates trailing behavior |
| callback_rate | String | Yes | Callback rate as percentage (0.1-5, where 1=1%) |
| activation_price_type | Int | Yes | 1=Last Price, 2=Mark Price |
**Example — Trailing stop to close long at 2% pullback from 72000:**
```bash
TIMESTAMP=$(date +%s000)
BODY='{"symbol":"BTCUSDT","side":3,"leverage":"10","open_type":"cross","size":10,"activation_price":"72000","callback_rate":"2","activation_price_type":1}'
SIGN=$(echo -n "TIMESTAMP#BITMART_API_MEMO#BODY" | openssl dgst -sha256 -hmac "$BITMART_API_SECRET" | awk '{print $NF}')
curl -s -X POST 'https://api-cloud-v2.bitmart.com/contract/private/submit-trail-order' \
-H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
-H "Content-Type: application/json" \
-H "X-BM-KEY: $BITMART_API_KEY" \
-H "X-BM-SIGN: $SIGN" \
-H "X-BM-TIMESTAMP: $TIMESTAMP" \
-d "$BODY"
```
**Response:**
```json
{ "code": 1000, "data": { "order_id": 56789012345678901 } }
```
**How trailing stops work:**
1. When the market price reaches `activation_price`, the trailing stop activates.
2. It tracks the highest price (for sell/close long) or lowest price (for buy/close short).
3. If the price reverses by `callback_rate` percent from the tracked extreme, a market order is placed.
---
### 32. Cancel Trailing Stop Order
`POST /contract/private/cancel-trail-order`
**Rate Limit:** 24 req/2sec per UID
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| symbol | String | Yes | Contract symbol |
| order_id | String | No | Trailing order ID |
```bash
TIMESTAMP=$(date +%s000)
BODY='{"symbol":"BTCUSDT","order_id":"56789012345678901"}'
SIGN=$(echo -n "TIMESTAMP#BITMART_API_MEMO#BODY" | openssl dgst -sha256 -hmac "$BITMART_API_SECRET" | awk '{print $NF}')
curl -s -X POST 'https://api-cloud-v2.bitmart.com/contract/private/cancel-trail-order' \
-H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
-H "Content-Type: application/json" \
-H "X-BM-KEY: $BITMART_API_KEY" \
-H "X-BM-SIGN: $SIGN" \
-H "X-BM-TIMESTAMP: $TIMESTAMP" \
-d "$BODY"
```
**Response:** `{ "code": 1000, "data": {} }`
---
## Order Query Endpoints (KEYED auth)
### 33. Query Order by ID
`GET /contract/private/order`
**Rate Limit:** 50 req/2sec per KEY
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| symbol | String | Yes | Contract symbol |
| order_id | String | Yes | Order ID |
| account | String | No | `"futures"` or `"copy_trading"` |
```bash
curl -s -H "X-BM-KEY: $BITMART_API_KEY" \
-H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
'https://api-cloud-v2.bitmart.com/contract/private/order?symbol=BTCUSDT&order_id=23456789012345678'
```
**Response:**
```json
{
"code": 1000,
"data": {
"order_id": "23456789012345678",
"client_order_id": "",
"symbol": "BTCUSDT",
"side": 1,
"type": "market",
"leverage": "10",
"open_type": "cross",
"size": "1",
"price": "0",
"deal_avg_price": "67123.4",
"deal_size": "1",
"state": 4,
"create_time": 1709971200000,
"update_time": 1709971200500,
"position_mode": "hedge_mode",
"account": "futures",
"activation_price": "",
"callback_rate": "",
"activation_price_type": 0,
"preset_take_profit_price_type": 0,
"preset_stop_loss_price_type": 0,
"preset_take_profit_price": "",
"preset_stop_loss_price": ""
}
}
```
| Field | Description |
|-------|-------------|
| order_id | BitMart-assigned order ID |
| client_order_id | Client-defined order ID |
| symbol | Contract symbol |
| side | Order side (1-4) |
| type | limit, market, liquidate, bankruptcy, adl |
| leverage | Leverage used |
| open_type | `"cross"` or `"isolated"` |
| size | Order size (contracts) |
| price | Order price (0 for market) |
| deal_avg_price | Average fill price |
| deal_size | Filled size |
| state | 1=Approving, 2=Pending, 4=Closed |
| create_time | Creation timestamp (ms) |
| update_time | Last update timestamp (ms) |
| position_mode | hedge_mode / one_way_mode |
| account | Account type |
| preset_take_profit_price | Preset TP price |
| preset_stop_loss_price | Preset SL price |
---
### 34. Order History
`GET /contract/private/order-history`
**Rate Limit:** 6 req/2sec per KEY | **Default Range:** Last 7 days | **Max Range:** 90 days | **Max Records:** 200
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| symbol | String | Yes | Contract symbol |
| order_id | String | No | Filter by order ID |
| client_order_id | String | No | Filter by client order ID |
| account | String | No | `"futures"` or `"copy_trading"` |
| start_time | Long | No | Start timestamp in **seconds** |
| end_time | Long | No | End timestamp in **seconds** |
```bash
curl -s -H "X-BM-KEY: $BITMART_API_KEY" \
-H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
'https://api-cloud-v2.bitmart.com/contract/private/order-history?symbol=BTCUSDT'
```
**Response:** Array of order objects with the same fields as endpoint 33.
**Additional fields in order history:**
| Field | Description |
|-------|-------------|
| `trigger_price` | Trigger price (for plan/conditional orders) |
| `execution_price` | Execution price (for triggered orders) |
| `executive_order_id` | ID of the order created when plan order triggers |
---
### 35. All Open Orders
`GET /contract/private/get-open-orders`
**Rate Limit:** 50 req/2sec per KEY
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| symbol | String | No | Contract symbol. Omit for all symbols |
| type | String | No | Filter: `"limit"`, `"market"`, `"trailing"` (default all) |
| order_state | String | No | `"all"` (default) or `"partially_filled"` |
| limit | Int | No | Max results, max 100, default 100 |
```bash
curl -s -H "X-BM-KEY: $BITMART_API_KEY" \
-H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
'https://api-cloud-v2.bitmart.com/contract/private/get-open-orders?symbol=BTCUSDT&limit=50'
```
**Response:** Array of order objects with the same fields as endpoint 33.
---
### 36. Active Plan Orders
`GET /contract/private/current-plan-order`
**Rate Limit:** 50 req/2sec per KEY
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| symbol | String | No | Contract symbol. Omit for all symbols |
| type | String | No | Filter: `"limit"` or `"market"` (default all) |
| limit | Int | No | Max results, max 100, default 100 |
| plan_type | String | No | `"plan"` (conditional), `"profit_loss"` (TP/SL) (default all) |
```bash
curl -s -H "X-BM-KEY: $BITMART_API_KEY" \
-H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
'https://api-cloud-v2.bitmart.com/contract/private/current-plan-order?symbol=BTCUSDT&limit=50'
```
**Response:**
```json
{
"code": 1000,
"data": [{
"order_id": "34567890123456789",
"client_order_id": "",
"symbol": "BTCUSDT",
"side": 1,
"type": "market",
"leverage": "10",
"open_type": "cross",
"size": "10",
"executive_price": "0",
"trigger_price": "65000",
"price_way": 2,
"price_type": 1,
"plan_category": 1,
"state": "active",
"mode": 1,
"position_mode": "hedge_mode",
"create_time": 1709971200000,
"update_time": 1709971200000,
"preset_take_profit_price_type": 0,
"preset_stop_loss_price_type": 0,
"preset_take_profit_price": "",
"preset_stop_loss_price": ""
}]
}
```
| Field | Description |
|-------|-------------|
| executive_price | Execution price (0 for market) |
| trigger_price | Trigger price |
| price_way | 1=Bullish, 2=Bearish |
| price_type | 1=Last Price, 2=Mark Price |
| plan_category | 1=TP/SL, 2=Position TP/SL |
| mode | 1=GTC, 2=FOK, 3=IOC, 4=Maker Only |
| *(other fields same as endpoint 33)* | |
---
### 37. Trade / Fill History
`GET /contract/private/trades`
**Rate Limit:** 6 req/2sec per KEY | **Default Range:** Last 7 days | **Max Range:** 90 days | **Max Records:** 200
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| symbol | String | No | Contract symbol (optional) |
| account | String | No | `"futures"` or `"copy_trading"` |
| start_time | Long | No | Start timestamp in **seconds** |
| end_time | Long | No | End timestamp in **seconds** |
| order_id | Long | No | Filter by order ID |
| client_order_id | String | No | Filter by client order ID |
```bash
curl -s -H "X-BM-KEY: $BITMART_API_KEY" \
-H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
'https://api-cloud-v2.bitmart.com/contract/private/trades?symbol=BTCUSDT'
```
**Response:**
```json
{
"code": 1000,
"data": [{
"order_id": "23456789012345678",
"trade_id": "98765432101234568",
"symbol": "BTCUSDT",
"side": 1,
"price": "67123.4",
"vol": "1",
"exec_type": "Taker",
"profit": false,
"realised_profit": "0",
"paid_fees": "0.04027",
"account": "futures",
"create_time": 1709971200500
}]
}
```
| Field | Description |
|-------|-------------|
| order_id | Associated order ID |
| trade_id | Unique trade ID |
| symbol | Contract symbol |
| side | Trade side (1-4) |
| price | Fill price |
| vol | Fill volume (contracts) |
| exec_type | `"Taker"` or `"Maker"` |
| profit | Whether this trade has profit (Boolean) |
| realised_profit | Realized profit |
| paid_fees | Fees paid |
| account | Account type |
| create_time | Trade timestamp (ms) |
---
### 38. Transfer Records
`POST /account/v1/transfer-contract-list`
**Rate Limit:** 1 req/2sec per KEY | **Auth:** SIGNED
> **Note:** Although the path prefix is `/account/v1/`, this endpoint uses the **futures Base URL** (`https://api-cloud-v2.bitmart.com`), not the spot Base URL.
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| currency | String | No | Currency (e.g. USDT) |
| time_start | Long | No | Start time in milliseconds |
| time_end | Long | No | End time in milliseconds |
| page | Int | Yes | Page number [1-1000] |
| limit | Int | Yes | Records per page [10-100] |
| recvWindow | Long | No | Valid duration (0-60000]ms, default 5000 |
```bash
TIMESTAMP=$(date +%s000)
BODY='{"page":1,"limit":20}'
SIGN=$(echo -n "TIMESTAMP#BITMART_API_MEMO#BODY" | openssl dgst -sha256 -hmac "$BITMART_API_SECRET" | awk '{print $NF}')
curl -s -X POST 'https://api-cloud-v2.bitmart.com/account/v1/transfer-contract-list' \
-H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
-H "Content-Type: application/json" \
-H "X-BM-KEY: $BITMART_API_KEY" \
-H "X-BM-SIGN: $SIGN" \
-H "X-BM-TIMESTAMP: $TIMESTAMP" \
-d "$BODY"
```
**Response:**
```json
{
"code": 1000,
"data": {
"records": [{
"transfer_id": "123456",
"currency": "USDT",
"amount": "1000",
"type": "spot_to_contract",
"state": "FINISHED",
"timestamp": 1709971200000
}]
}
}
```
| Field | Description |
|-------|-------------|
| transfer_id | Transfer ID |
| currency | Currency |
| amount | Transfer amount |
| type | `"spot_to_contract"` or `"contract_to_spot"` |
| state | `"PROCESSING"`, `"FINISHED"`, `"FAILED"` |
| timestamp | Transfer timestamp (ms) |
---
## Sub-Account Endpoints (SIGNED / KEYED auth)
> Institutional verification required for all sub-account features.
### 39. Sub-Account to Main-Account Transfer (from Main Account)
`POST /account/contract/sub-account/main/v1/sub-to-main`
**Rate Limit:** 8 req/2sec per KEY | **Auth:** SIGNED
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| requestNo | String | Yes | UUID, unique request identifier (max 64 chars) |
| subAccount | String | Yes | Sub-account username |
| amount | String | Yes | Transfer amount |
| currency | String | Yes | Currency code (currently only `USDT` supported) |
```bash
TIMESTAMP=$(date +%s000)
BODY='{"requestNo":"550e8400-e29b-41d4-a716-446655440000","subAccount":"mysubuser","amount":"100","currency":"USDT"}'
SIGN=$(echo -n "TIMESTAMP#BITMART_API_MEMO#BODY" | openssl dgst -sha256 -hmac "$BITMART_API_SECRET" | awk '{print $NF}')
curl -s -X POST 'https://api-cloud-v2.bitmart.com/account/contract/sub-account/main/v1/sub-to-main' \
-H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
-H "Content-Type: application/json" \
-H "X-BM-KEY: $BITMART_API_KEY" \
-H "X-BM-SIGN: $SIGN" \
-H "X-BM-TIMESTAMP: $TIMESTAMP" \
-d "$BODY"
```
**Response:**
```json
{ "code": 1000, "message": "OK", "data": {} }
```
---
### 40. Main-Account to Sub-Account Transfer (from Main Account)
`POST /account/contract/sub-account/main/v1/main-to-sub`
**Rate Limit:** 8 req/2sec per KEY | **Auth:** SIGNED
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| requestNo | String | Yes | UUID, unique request identifier (max 64 chars) |
| subAccount | String | Yes | Sub-account username |
| amount | String | Yes | Transfer amount |
| currency | String | Yes | Currency code (currently only `USDT` supported) |
```bash
TIMESTAMP=$(date +%s000)
BODY='{"requestNo":"550e8400-e29b-41d4-a716-446655440001","subAccount":"mysubuser","amount":"100","currency":"USDT"}'
SIGN=$(echo -n "TIMESTAMP#BITMART_API_MEMO#BODY" | openssl dgst -sha256 -hmac "$BITMART_API_SECRET" | awk '{print $NF}')
curl -s -X POST 'https://api-cloud-v2.bitmart.com/account/contract/sub-account/main/v1/main-to-sub' \
-H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
-H "Content-Type: application/json" \
-H "X-BM-KEY: $BITMART_API_KEY" \
-H "X-BM-SIGN: $SIGN" \
-H "X-BM-TIMESTAMP: $TIMESTAMP" \
-d "$BODY"
```
**Response:**
```json
{ "code": 1000, "message": "OK", "data": {} }
```
---
### 41. Sub-Account to Main-Account Transfer (from Sub Account)
`POST /account/contract/sub-account/sub/v1/sub-to-main`
**Rate Limit:** 8 req/2sec per KEY | **Auth:** SIGNED
> **Note:** This endpoint must be called using the **sub-account's own API key**. The sub-account is inferred from the auth credentials — no `subAccount` parameter needed.
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| requestNo | String | Yes | UUID, unique request identifier (max 64 chars) |
| amount | String | Yes | Transfer amount |
| currency | String | Yes | Currency code (currently only `USDT` supported) |
```bash
TIMESTAMP=$(date +%s000)
BODY='{"requestNo":"550e8400-e29b-41d4-a716-446655440002","amount":"100","currency":"USDT"}'
SIGN=$(echo -n "TIMESTAMP#BITMART_API_MEMO#BODY" | openssl dgst -sha256 -hmac "$BITMART_API_SECRET" | awk '{print $NF}')
curl -s -X POST 'https://api-cloud-v2.bitmart.com/account/contract/sub-account/sub/v1/sub-to-main' \
-H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
-H "Content-Type: application/json" \
-H "X-BM-KEY: $BITMART_API_KEY" \
-H "X-BM-SIGN: $SIGN" \
-H "X-BM-TIMESTAMP: $TIMESTAMP" \
-d "$BODY"
```
**Response:**
```json
{ "code": 1000, "message": "OK", "data": {} }
```
---
### 42. Get Sub-Account Futures Wallet Balance (from Main Account)
`GET /account/contract/sub-account/main/v1/wallet`
**Rate Limit:** 12 req/2sec per KEY | **Auth:** KEYED
> Returns only assets with balance greater than 0.
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| subAccount | String | Yes | Sub-account username |
| currency | String | No | Currency filter (e.g. `USDT`) |
```bash
curl -s -H "X-BM-KEY: $BITMART_API_KEY" \
-H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
'https://api-cloud-v2.bitmart.com/account/contract/sub-account/main/v1/wallet?subAccount=mysubuser'
```
**Response:**
```json
{
"code": 1000,
"message": "OK",
"data": {
"wallet": [{
"currency": "USDT",
"name": "Tether",
"available": "400.00",
"frozen": "100.00"
}]
}
}
```
| Field | Description |
|-------|-------------|
| data.wallet[] | Wallet item array |
| currency | Token symbol (e.g. `USDT`) |
| name | Token name (e.g. `Tether`) |
| available | Available balance |
| frozen | Frozen balance |
---
### 43. Get Sub-Account Transfer History (from Main Account)
`GET /account/contract/sub-account/main/v1/transfer-list`
**Rate Limit:** 8 req/2sec per KEY | **Auth:** KEYED
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| subAccount | String | Yes | Sub-account username |
| limit | Int | Yes | Number of recent records [1-100] |
```bash
curl -s -H "X-BM-KEY: $BITMART_API_KEY" \
-H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
'https://api-cloud-v2.bitmart.com/account/contract/sub-account/main/v1/transfer-list?subAccount=mysubuser&limit=20'
```
**Response:**
```json
{
"code": 1000,
"message": "OK",
"data": [{
"fromAccount": "main",
"fromWalletType": "future",
"toAccount": "mysubuser",
"toWalletType": "future",
"currency": "USDT",
"amount": "100",
"submissionTime": 1709971200
}]
}
```
| Field | Description |
|-------|-------------|
| fromAccount | Transfer-out account username |
| fromWalletType | Transfer-out wallet type (`future`) |
| toAccount | Transfer-in account username |
| toWalletType | Transfer-in wallet type (`future`) |
| currency | Currency symbol |
| amount | Transfer amount |
| submissionTime | Request timestamp in seconds (UTC) |
---
### 44. Get Account Futures Asset Transfer History (Main/Sub)
`GET /account/contract/sub-account/v1/transfer-history`
**Rate Limit:** 8 req/2sec per KEY | **Auth:** KEYED
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| limit | Int | Yes | Number of recent records [1-100] |
```bash
curl -s -H "X-BM-KEY: $BITMART_API_KEY" \
-H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
'https://api-cloud-v2.bitmart.com/account/contract/sub-account/v1/transfer-history?limit=20'
```
**Response:**
```json
{
"code": 1000,
"message": "OK",
"data": [{
"fromAccount": "main",
"fromWalletType": "future",
"toAccount": "mysubuser",
"toWalletType": "future",
"currency": "USDT",
"amount": "100",
"submissionTime": 1709971200
}]
}
```
| Field | Description |
|-------|-------------|
| fromAccount | Transfer-out account username |
| fromWalletType | Transfer-out wallet type (`future`) |
| toAccount | Transfer-in account username |
| toWalletType | Transfer-in wallet type (`future`) |
| currency | Currency symbol |
| amount | Transfer amount |
| submissionTime | Request timestamp in seconds (UTC) |
---
## Affiliate Endpoints (KEYED auth)
> All affiliate endpoints use **KEYED** authentication with rate limit of **24 req/2sec**. Timestamps in parameters are in **seconds** (not milliseconds). Maximum time range for endpoints 47, 48, 50 is **60 days**.
### 45. Get Futures Rebate List
`GET /contract/private/affiliate/rebate-list`
**Rate Limit:** 24 req/2sec per KEY
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| page | Int | Yes | Page number |
| size | Int | Yes | Records per page |
| currency | String | Yes | Currency to query (e.g. `USDT`) |
| user_id | Long | No | User ID filter |
| rebate_start_time | Long | No | Rebate start timestamp (seconds) |
| rebate_end_time | Long | No | Rebate end timestamp (seconds) |
| register_start_time | Long | No | Registration start timestamp (seconds) |
| register_end_time | Long | No | Registration end timestamp (seconds) |
```bash
curl -s -H "X-BM-KEY: $BITMART_API_KEY" \
-H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
'https://api-cloud-v2.bitmart.com/contract/private/affiliate/rebate-list?page=1&size=10¤cy=USDT'
```
**Response:**
```json
{
"code": "1000",
"message": "OK",
"data": {
"total": 0,
"page": 1,
"size": 10,
"btc_rebate_sum": 0,
"usdt_rebate_sum": 0,
"eth_rebate_sum": 0,
"rebate_detail_page_data": []
}
}
```
| Field | Description |
|-------|-------------|
| total | Total count |
| btc_rebate_sum / usdt_rebate_sum / eth_rebate_sum | Total rebate per currency |
| rebate_detail_page_data | Array of rebate records |
| ↳ rebate_coin | Currency |
| ↳ trade_user_id | Trading user ID |
| ↳ total_rebate_amount | Total commission |
| ↳ user_type | 0=Indirect, 1=Direct |
---
### 46. Get Futures Trade List
`GET /contract/private/affiliate/trade-list`
**Rate Limit:** 24 req/2sec per KEY
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| user_id | Long | Yes | User ID |
| type | Int | Yes | Query type: 1=U-based, 2=Coin-based |
| page | Int | Yes | Page number |
| size | Int | Yes | Records per page |
| start_time | Long | No | Start timestamp (seconds) |
| end_time | Long | No | End timestamp (seconds) |
```bash
curl -s -H "X-BM-KEY: $BITMART_API_KEY" \
-H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
'https://api-cloud-v2.bitmart.com/contract/private/affiliate/trade-list?user_id=123456&type=1&page=1&size=10'
```
**Response:**
```json
{
"code": "1000",
"message": "OK",
"data": {
"total": 0,
"page": 1,
"size": 10,
"list": [{
"user_id": 10048829,
"user_type": 1,
"create_time": 1689933471000,
"symbol": "BTCUSDT",
"leverage": 20,
"open_type": 2,
"way": 1,
"category": 2,
"select_copy_trade": 1,
"deal_price": 29771.9,
"deal_vol": 32,
"fee": 0.57162048,
"realised_profit": 0
}]
}
}
```
| Field | Description |
|-------|-------------|
| list[].user_id | User ID |
| list[].user_type | User type (direct/indirect) |
| list[].create_time | Creation timestamp |
| list[].symbol | Trading symbol |
| list[].leverage | Leverage |
| list[].open_type | Position type: `1`=Isolated, `2`=Cross |
| list[].way | Order direction: `1`=Long, `2`=Close Short, `3`=Close Long, `4`=Short |
| list[].category | Order type: `1`=Limit, `2`=Market |
| list[].select_copy_trade | Type: `1`=Copy Trading, `2`=Non-Copy Trading |
| list[].deal_price | Average deal price |
| list[].deal_vol | Deal volume |
| list[].fee | Fee |
| list[].realised_profit | Realized PnL |
---
### 47. Get Single User Rebate Data
`GET /contract/private/affiliate/rebate-user`
**Rate Limit:** 24 req/2sec per KEY | **Max Range:** 60 days
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| cid | Long | Yes | User CID to query |
| start_time | Long | Yes | Start timestamp (seconds) |
| end_time | Long | Yes | End timestamp (seconds) |
```bash
curl -s -H "X-BM-KEY: $BITMART_API_KEY" \
-H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
'https://api-cloud-v2.bitmart.com/contract/private/affiliate/rebate-user?cid=123456&start_time=1706745600&end_time=1709251200'
```
**Response:**
```json
{
"code": "1000",
"message": "OK",
"data": {
"cid": 123456,
"back_rate": "0.3",
"trading_vol_total": "10000",
"trading_fee_total": "6.0",
"rebate_total": "1.8",
"trading_vol": "10000",
"trading_fee": "6.0",
"rebate": "1.8"
}
}
```
| Field | Description |
|-------|-------------|
| cid | User CID |
| back_rate | Rebate rate |
| trading_vol_total | Total trading volume |
| trading_fee_total | Total trading fee |
| rebate_total | Total rebate |
| trading_vol / trading_fee / rebate | Period-specific values |
---
### 48. Get Single API User Rebate Data
`GET /contract/private/affiliate/rebate-api`
**Rate Limit:** 24 req/2sec per KEY | **Max Range:** 60 days
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| cid | Long | Yes | User CID to query |
| start_time | Long | Yes | Start timestamp (seconds) |
| end_time | Long | Yes | End timestamp (seconds) |
```bash
curl -s -H "X-BM-KEY: $BITMART_API_KEY" \
-H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
'https://api-cloud-v2.bitmart.com/contract/private/affiliate/rebate-api?cid=123456&start_time=1706745600&end_time=1709251200'
```
**Response:**
```json
{
"code": "1000",
"message": "OK",
"data": {
"api_trading_fee_total": "6.0",
"api_rebate_total": "1.8"
}
}
```
| Field | Description |
|-------|-------------|
| api_trading_fee_total | API trading fee rebate |
| api_rebate_total | API rebate amount |
---
### 49. Check If Invited User
`GET /contract/private/affiliate/invite-check`
**Rate Limit:** 24 req/2sec per KEY
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| cid | Long | Yes | User CID to query |
```bash
curl -s -H "X-BM-KEY: $BITMART_API_KEY" \
-H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
'https://api-cloud-v2.bitmart.com/contract/private/affiliate/invite-check?cid=123456'
```
**Response:**
```json
{
"code": "1000",
"message": "OK",
"data": {
"isInviteUser": true
}
}
```
| Field | Description |
|-------|-------------|
| isInviteUser | `true` = invited user, `false` = not invited |
---
### 50. Get Invited Customer List
`GET /contract/private/affiliate/rebate-inviteUser`
**Rate Limit:** 24 req/2sec per KEY | **Max Range:** 60 days
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| start_time | Long | Yes | Start timestamp (seconds) |
| end_time | Long | Yes | End timestamp (seconds) |
| page | Int | Yes | Page number |
| size | Int | Yes | Records per page (max 50) |
| cid | Long | No | User CID filter |
```bash
curl -s -H "X-BM-KEY: $BITMART_API_KEY" \
-H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
'https://api-cloud-v2.bitmart.com/contract/private/affiliate/rebate-inviteUser?start_time=1706745600&end_time=1709251200&page=1&size=10'
```
**Response:**
```json
{
"code": "1000",
"message": "OK",
"data": {
"total": 0,
"page": 1,
"size": 10,
"list": []
}
}
```
| Field | Description |
|-------|-------------|
| total | Total records |
| list[].cid | User CID |
| list[].rebateTotal | Cumulative rebate (USDT) |
| list[].tradingVolTotal | Cumulative trading volume (USDT) |
| list[].cashbackRate | Cashback percentage |
| list[].tradingFeeTotal | Total trading fees (USDT) |
| list[].backRate | Rebate rate |
| list[].status | 1=Rebate issued, 0=Not issued |
---
## Simulated Trading Endpoint (SIGNED auth)
### 51. Demo Account Claim (Demo Only)
`POST /contract/private/claim`
**Auth:** SIGNED
> **IMPORTANT:** This endpoint only works with the demo base URL: `https://demo-api-cloud-v2.bitmart.com`
> It resets/refreshes simulated account balance for continued testing.
| Component | URL |
|-----------|-----|
| Demo REST API | `https://demo-api-cloud-v2.bitmart.com` |
| Demo WebSocket Public | `wss://openapi-wsdemo-v2.bitmart.com/api?protocol=1.1` |
| Demo WebSocket Private | `wss://openapi-wsdemo-v2.bitmart.com/user?protocol=1.1` |
```bash
TIMESTAMP=$(date +%s000)
BODY='{}'
SIGN=$(echo -n "TIMESTAMP#BITMART_API_MEMO#BODY" | openssl dgst -sha256 -hmac "$BITMART_API_SECRET" | awk '{print $NF}')
curl -s -X POST 'https://demo-api-cloud-v2.bitmart.com/contract/private/claim' \
-H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
-H "Content-Type: application/json" \
-H "X-BM-KEY: $BITMART_API_KEY" \
-H "X-BM-SIGN: $SIGN" \
-H "X-BM-TIMESTAMP: $TIMESTAMP" \
-d "$BODY"
```
**Response:**
```json
{
"code": 1000,
"message": "OK",
"data": {
"currency": "USDT",
"amount": "10"
}
}
```
| Field | Description |
|-------|-------------|
| currency | Claimed asset currency |
| amount | Claimed amount |
> All standard futures endpoints work identically in the demo environment. Just use the demo base URL instead of the production URL.
---
# System Endpoints
## 52. Get System Time
`GET /system/time`
**Auth:** NONE | **Rate Limit:** 10 req/sec per IP
No parameters required.
```bash
curl -s -H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
'https://api-cloud.bitmart.com/system/time'
```
**Response:**
```json
{
"code": 1000,
"trace": "886fb6ae-456b-4654-b4e0-d681ac05cea1",
"message": "OK",
"data": {
"server_time": 1527777538000
}
}
```
| Field | Type | Description |
|-------|------|-------------|
| server_time | Long | Current server time (UTC milliseconds) |
---
## 53. Get System Service Status
`GET /system/service`
**Auth:** NONE | **Rate Limit:** 10 req/sec per IP
No parameters required.
```bash
curl -s -H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
'https://api-cloud.bitmart.com/system/service'
```
**Response:**
```json
{
"code": 1000,
"trace": "886fb6ae-456b-4654-b4e0-d681ac05cea1",
"message": "OK",
"data": {
"service": [
{
"title": "Contract API Stop",
"service_type": "contract",
"status": "2",
"start_time": 1527777538000,
"end_time": 1527777538000
}
]
}
}
```
| Field | Type | Description |
|-------|------|-------------|
| title | String | Maintenance description title |
| service_type | String | `spot` / `contract` / `account` |
| status | Long | `0`=Waiting `1`=Working `2`=Completed |
| start_time | Long | Maintenance start time (UTC milliseconds) |
| end_time | Long | Maintenance end time (UTC milliseconds) |
FILE:references/bitmart-close-position.md
# Close Position Workflow
> **Credentials:** Read from `.env` — `BITMART_API_KEY`, `BITMART_API_SECRET`, `BITMART_API_MEMO`.
## Scenario: Close a Long Position
**User prompt:** "Close my BTC long position on BitMart"
### Step 1: Get current positions
```bash
curl -s -H "X-BM-KEY: $BITMART_API_KEY" \
-H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
'https://api-cloud-v2.bitmart.com/contract/private/position-v2?symbol=BTCUSDT'
```
Expected response:
```json
{
"code": 1000,
"data": [
{
"symbol": "BTCUSDT",
"leverage": "10",
"current_amount": "100",
"position_amount": "100",
"position_side": "long",
"entry_price": "67000.0",
"mark_price": "67500.0",
"liquidation_price": "60500.0",
"unrealized_pnl": "50.00",
"initial_margin": "670.00",
"open_type": "cross"
}
]
}
```
If no position is found, inform the user:
```
No open BTCUSDT long position found. Nothing to close.
```
If multiple positions exist (e.g., both long and short in hedge mode), ask the user which one to close.
### Step 2: Determine close side
| Position | Close Side | Description |
|----------|------------|-------------|
| Long | 3 (sell_close_long) | Sell to close the long position |
| Short | 2 (buy_close_short) | Buy to cover the short position |
For a long position: **side = 3**
### Step 3: Present close summary and ask for CONFIRM
```
Close Position Summary:
Symbol: BTCUSDT
Direction: Close Long (sell_close_long, side=3)
Size: 100 contracts (entire position)
Type: Market
Entry Price: 67,000.0 USDT
Current Mark Price: 67,500.0 USDT
Unrealized PnL: +50.00 USDT (profit)
Estimated Realized PnL: ~+50.00 USDT
Please type CONFIRM to proceed.
```
### Step 4: Submit close order (after user confirms)
> **Note:** Close orders only require `symbol`, `side`, `type`, and `size`.
> Do NOT include `leverage` or `open_type` — these fields are ignored for close orders and may cause confusion.
> For limit close, add `price`. For partial close, use a smaller `size`.
```bash
TIMESTAMP=$(date +%s000)
BODY='{"symbol":"BTCUSDT","side":3,"type":"market","size":100}'
SIGN=$(echo -n "TIMESTAMP#BITMART_API_MEMO#BODY" | openssl dgst -sha256 -hmac "$BITMART_API_SECRET" | awk '{print $NF}')
curl -s -X POST 'https://api-cloud-v2.bitmart.com/contract/private/submit-order' \
-H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
-H "Content-Type: application/json" \
-H "X-BM-KEY: $BITMART_API_KEY" \
-H "X-BM-SIGN: $SIGN" \
-H "X-BM-TIMESTAMP: $TIMESTAMP" \
-d "$BODY"
```
Expected response:
```json
{
"code": 1000,
"data": {
"order_id": 23456789012345680
}
}
```
### Step 5: Verify position closed
```bash
curl -s -H "X-BM-KEY: $BITMART_API_KEY" \
-H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
'https://api-cloud-v2.bitmart.com/contract/private/position-v2?symbol=BTCUSDT'
```
Verify the position no longer appears or the parsed `current_amount` is `0`.
Also check the fill details:
```bash
curl -s -H "X-BM-KEY: $BITMART_API_KEY" \
-H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
'https://api-cloud-v2.bitmart.com/contract/private/order?symbol=BTCUSDT&order_id=23456789012345680'
```
### Step 6: Report realized PnL
```
Position Closed Successfully:
Symbol: BTCUSDT
Direction: Closed Long
Size: 100 contracts
Entry Price: 67,000.0 USDT
Exit Price: 67,498.5 USDT
Realized PnL: +49.85 USDT
Fees: 4.05 USDT
Net PnL: +45.80 USDT
Margin Released: 670.00 USDT
Not financial advice. You are solely responsible for your investment decisions.
```
---
## Scenario: Partial Close
**User prompt:** "Close half of my BTC long"
The flow is the same, but with size = 50 (half of 100):
```json
{"symbol":"BTCUSDT","side":3,"type":"market","size":50}
```
After partial close, report both the closed portion and the remaining position.
---
## Scenario: Close a Short Position
**User prompt:** "Close my ETH short position"
- Query positions for ETHUSDT
- Use **side = 2** (buy_close_short)
- For short positions, profit occurs when exit price < entry price
```json
{"symbol":"ETHUSDT","side":2,"type":"market","size":50}
```
---
## Scenario: Limit Close
**User prompt:** "Close my BTC long at 70000"
Use a limit order instead of market:
```json
{"symbol":"BTCUSDT","side":3,"type":"limit","price":"70000","size":100,"mode":1}
```
Inform the user that the order will remain open until the price reaches 70000 or they cancel it.
---
## Error Handling
| Error | Cause | Action |
|-------|-------|--------|
| No position found | Position already closed or does not exist | Inform user |
| Size exceeds position | Trying to close more than held | Use the actual position amount |
| `code != 1000` on submit | Various | Report error message; do not retry automatically |
| Order state `failed` | System rejection | Check order details for failure reason |
| Partially filled close | Slippage or liquidity | Report partial fill; suggest retrying for remainder |
FILE:references/bitmart-futures-skill.md
# BitMart Futures Trading
**Base URL:** `https://api-cloud-v2.bitmart.com` | **Symbol:** `BTCUSDT` (no underscore) | **Success:** `code == 1000`
53 endpoints — full details in `references/bitmart-api-reference.md`
## Authentication
**Credentials** (from `.env`): `BITMART_API_KEY`, `BITMART_API_SECRET`, `BITMART_API_MEMO`
No BitMart account? Register at **[https://www.bitmart.com/invite/cMEArf](https://www.bitmart.com/invite/cMEArf)**
Verify credentials before any private call. If missing — **STOP**.
| Level | Endpoints | Headers |
| ------ | ------------------ | ------------------------------------------- |
| NONE | Public market data | — |
| KEYED | Read-only private | `X-BM-KEY` |
| SIGNED | Write operations | `X-BM-KEY` + `X-BM-SIGN` + `X-BM-TIMESTAMP` |
**Signature:** `HMAC-SHA256(secret, "{timestamp}#{memo}#{body}")` — GET body = `""`
**Always include `X-BM-BROKER-ID: BlaveData666666` on ALL requests.**
**IP Whitelist:** Use **public IP** (`curl https://checkip.amazonaws.com`), not private IP (`10.x`, `172.x`, `192.168.x`).
> Signature Python implementation and common mistakes: `references/bitmart-signature.md`
## Operation Flow
### Step 0: Credential Check
Verify `BITMART_API_KEY`, `BITMART_API_SECRET`, `BITMART_API_MEMO`. If missing — **STOP**.
### Step 1.1: Query Positions (READ)
`GET /contract/private/position-v2` (KEYED, no signature needed)
Filter `current_amount != "0"` → display symbol, position_side, current_amount, entry_price, leverage, open_type, liquidation_price, unrealized_pnl
### Step 1.5: Pre-Trade Check (MANDATORY before open/leverage)
1. Call `GET /contract/private/position-v2?symbol=<SYMBOL>`
2. If `current_amount` non-zero → inherit `leverage` and `open_type`, do NOT override
3. If user wants different values → **STOP**, warn to close position first
### Step 1.55: Pre-Mode-Switch Check
Confirm no positions (Step 1.5) AND no open orders (`GET /contract/private/get-open-orders`). If either exists → **STOP**.
### Step 1.6: TP/SL on Existing Position
`POST /contract/private/submit-tp-sl-order` — submit TP and SL as **two separate calls**
| Param | Value |
| ----------------- | -------------------------------- |
| `type` | `"take_profit"` or `"stop_loss"` |
| `side` | `3` close long / `2` close short |
| `trigger_price` | Activation price |
| `executive_price` | `"0"` for market fill |
| `price_type` | `1` last / `2` mark |
| `plan_category` | `2` |
### Step 2: Execute
- READ → call, parse, display
- WRITE → present summary → ask **"CONFIRM"** → execute
**submit-order rules:**
| Scenario | Send | Omit |
| ------------- | -------------------------------------------------------------- | -------------------------- |
| Open, market | symbol, side, type:`"market"`, size, leverage, open_type | price |
| Open, limit | symbol, side, type:`"limit"`, price, size, leverage, open_type | — |
| Close, market | symbol, side, type:`"market"`, size | price, leverage, open_type |
| Close, limit | symbol, side, type:`"limit"`, price, size | leverage, open_type |
### Step 3: Verify
- After open: `position-v2` → report entry price, size, leverage, liquidation price
- After close: `position-v2` → report realized PnL
- After order: `GET /contract/private/order` → confirm status
## Order Reference
**Side:** `1` Open Long / `2` Close Short / `3` Close Long / `4` Open Short
**Mode:** `1` GTC / `2` FOK / `3` IOC / `4` Maker Only
**Timestamps:** ms — always convert to local time for display.
## Error Handling
| Code | Action |
| ------------------ | --------------------------------------------------------- |
| 30005 | Wrong signature → see `references/bitmart-signature.md` |
| 30007 | Timestamp drift → sync clock |
| 40012/40040 | Leverage/mode conflict → inherit existing position values |
| 40027/42000 | Insufficient balance → transfer from spot or reduce size |
| 429 | Rate limited → wait |
| 403/503 Cloudflare | Wait 30-60s, retry max 3× |
## Spot ↔ Futures Transfer
Present summary → ask **"CONFIRM"** → execute.
**Endpoint:** `POST https://api-cloud-v2.bitmart.com/account/v1/transfer-contract` (SIGNED)
| Param | Value |
| ---------- | -------------------------------------------- |
| `currency` | `USDT` only |
| `amount` | transfer amount |
| `type` | `"spot_to_contract"` or `"contract_to_spot"` |
Rate limit: 1 req/2sec. ⚠️ `/spot/v1/transfer-contract` does NOT exist.
## Security
- WRITE operations require **"CONFIRM"**
- Always show liquidation price before opening leveraged positions
- "Not financial advice. Futures trading carries significant risk of loss."
## References
- `references/bitmart-api-reference.md` — 53 endpoints
- `references/bitmart-signature.md` — Python signature implementation
- `references/bitmart-open-position.md` / `bitmart-close-position.md` / `bitmart-plan-order.md` / `bitmart-tp-sl.md`
---
FILE:references/bitmart-open-position.md
# Open Position Workflow
> **Credentials:** Read from `.env` — `BITMART_API_KEY`, `BITMART_API_SECRET`, `BITMART_API_MEMO`.
## Scenario: Open a Long Position
**User prompt:** "Open a 10x long BTC position with 100 contracts on BitMart"
### Step 1: Pre-flight — Check futures balance
```bash
curl -s -H "X-BM-KEY: $BITMART_API_KEY" \
-H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
'https://api-cloud-v2.bitmart.com/contract/private/assets-detail'
```
Verify the user has sufficient `available_balance` in USDT. If insufficient, suggest transferring from spot:
```
Insufficient futures balance. Available: 50.00 USDT, Required: ~670 USDT (estimated).
Would you like to transfer USDT from your spot wallet?
```
### Step 2: Pre-flight — Check contract details
```bash
curl -s -H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
'https://api-cloud-v2.bitmart.com/contract/public/details?symbol=BTCUSDT'
```
Validate:
- `contract_size` — to calculate margin requirement
- `min_volume` — ensure order size meets minimum
- `max_volume` — ensure order size does not exceed maximum
- `max_leverage` — ensure requested leverage is supported
- `price_precision` — for limit orders
### Step 3: Pre-flight — Check leverage brackets
```bash
curl -s -H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
'https://api-cloud-v2.bitmart.com/contract/public/leverage-bracket?symbol=BTCUSDT'
```
Verify the requested leverage (10x) is available for the position size. If position size exceeds the tier limit, warn the user:
```
Warning: At 10x leverage, maximum position is 500,000 contracts.
Your requested 100 contracts is within limits.
```
### Step 3.5: Check existing position (MANDATORY)
Before setting leverage or submitting an order, check for existing positions:
```bash
curl -s -H "X-BM-KEY: $BITMART_API_KEY" \
-H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
'https://api-cloud-v2.bitmart.com/contract/private/position-v2?symbol=BTCUSDT'
```
`position-v2` returns a `data[]` array. Do **not** assume there is only one row:
- In `one_way_mode`, you will typically see one row with `position_side="both"`
- In some account / mode combinations, you may see multiple rows for the same symbol (for example `position_side="long"` and `position_side="short"`), even when both sides are zero
Use this step to answer two questions only:
- Does a position already exist?
- If yes, which `leverage` and `open_type` must be inherited?
Do **not** rely on `position-v2` for `position_mode`. Query `get-position-mode` later only when the workflow becomes mode-sensitive (for example, deciding whether to switch modes or explaining hedge vs one-way behavior).
Evaluate the **entire** `data[]` array:
Parse each row's `current_amount` as a number before comparing it. The API returns string values such as `"0"`.
**If any row's parsed `current_amount` is non-zero (existing position found):**
- Treat the symbol as having an existing position
- Use the relevant non-zero position row's `leverage` and `open_type` values in the order — do NOT send different values
- If user requested different leverage or margin mode → **STOP** and warn:
> "You have an existing position. Close it first before changing leverage or margin mode."
- Do NOT call `submit-leverage` with different values — API will return 40012/40040
- Do **not** attempt to change `position_mode` while an existing position is present
- Skip Step 4 and continue with the current account mode in Step 5
**If every row's parsed `current_amount` is 0 (no existing position):**
- Proceed with user-requested leverage and margin mode (Step 4)
### Step 4: Set leverage (if needed — skip if existing position found in Step 3.5)
Only execute this step when no existing position was found in Step 3.5:
```bash
TIMESTAMP=$(date +%s000)
BODY='{"symbol":"BTCUSDT","leverage":"10","open_type":"cross"}'
SIGN=$(echo -n "TIMESTAMP#BITMART_API_MEMO#BODY" | openssl dgst -sha256 -hmac "$BITMART_API_SECRET" | awk '{print $NF}')
curl -s -X POST 'https://api-cloud-v2.bitmart.com/contract/private/submit-leverage' \
-H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
-H "Content-Type: application/json" \
-H "X-BM-KEY: $BITMART_API_KEY" \
-H "X-BM-SIGN: $SIGN" \
-H "X-BM-TIMESTAMP: $TIMESTAMP" \
-d "$BODY"
```
### Step 5: Check current position mode and set it only if safe
Query the account-wide `position_mode` only when you need a mode-sensitive decision, such as deciding whether a mode switch is needed or explaining current hedge/one-way behavior:
```bash
curl -s -H "X-BM-KEY: $BITMART_API_KEY" \
-H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
'https://api-cloud-v2.bitmart.com/contract/private/get-position-mode'
```
Read the current account-wide position mode first:
- If Step 3.5 found any existing position (any row's parsed `current_amount` is non-zero), **do not call** `set-position-mode`
- If the user requested a different position mode while an existing position is present, **STOP** and tell them to close the existing position first
- Before any mode switch, check that there are no open orders on the account:
```bash
curl -s -H "X-BM-KEY: $BITMART_API_KEY" \
-H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
'https://api-cloud-v2.bitmart.com/contract/private/get-open-orders'
```
- If any open orders are present, **STOP** and tell the user to cancel them before changing `position_mode`
- Only consider calling `set-position-mode` when Step 3.5 found **no** existing position and `get-open-orders` is empty
If the user wants hedge mode (to hold long and short simultaneously), Step 3.5 found no existing position, and the current mode is not already `hedge_mode`, set it:
```bash
TIMESTAMP=$(date +%s000)
BODY='{"position_mode":"hedge_mode"}'
SIGN=$(echo -n "TIMESTAMP#BITMART_API_MEMO#BODY" | openssl dgst -sha256 -hmac "$BITMART_API_SECRET" | awk '{print $NF}')
curl -s -X POST 'https://api-cloud-v2.bitmart.com/contract/private/set-position-mode' \
-H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
-H "Content-Type: application/json" \
-H "X-BM-KEY: $BITMART_API_KEY" \
-H "X-BM-SIGN: $SIGN" \
-H "X-BM-TIMESTAMP: $TIMESTAMP" \
-d "$BODY"
```
**Note:** BitMart rejects `set-position-mode` when the account is not in a clean state for mode switching. Existing positions will fail, and open orders may also fail with `40059` (`"Some positions exist and the mode switching fails."`). If mode switching fails, stop and ask the user to clear existing positions / open orders before retrying.
### Step 6: Present order summary and ask for CONFIRM
Display to user:
```
Position Summary:
Symbol: BTCUSDT
Direction: Long (buy_open_long, side=1)
Type: Market
Size: 100 contracts
Leverage: 10x
Margin Mode: Cross
Estimated Entry: ~67,123 USDT
Estimated Margin: ~671 USDT
Estimated Liquidation: ~60,500 USDT
WARNING: Futures trading carries significant risk. Higher leverage
amplifies both gains and losses. You could lose your entire margin.
Please type CONFIRM to proceed.
```
### Step 7: Submit order (after user confirms)
**Parameter rules (must match SKILL.md Step 2a):**
- **Market order:** do NOT include `price` field — it is ignored and causes confusion
- **If existing position found in Step 3.5:** use `leverage` and `open_type` from the relevant non-zero position row, not user-requested values
- **`size` must be an integer** (number of contracts) — check min/max via `GET /contract/public/details`
- **Maker Only (`mode=4`):** only valid with `type=limit`, never with `type=market`
```bash
TIMESTAMP=$(date +%s000)
BODY='{"symbol":"BTCUSDT","side":1,"type":"market","size":100,"leverage":"10","open_type":"cross"}'
SIGN=$(echo -n "TIMESTAMP#BITMART_API_MEMO#BODY" | openssl dgst -sha256 -hmac "$BITMART_API_SECRET" | awk '{print $NF}')
curl -s -X POST 'https://api-cloud-v2.bitmart.com/contract/private/submit-order' \
-H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
-H "Content-Type: application/json" \
-H "X-BM-KEY: $BITMART_API_KEY" \
-H "X-BM-SIGN: $SIGN" \
-H "X-BM-TIMESTAMP: $TIMESTAMP" \
-d "$BODY"
```
Expected response:
```json
{
"code": 1000,
"data": {
"order_id": 23456789012345678
}
}
```
### Step 8: Verify position
```bash
curl -s -H "X-BM-KEY: $BITMART_API_KEY" \
-H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
'https://api-cloud-v2.bitmart.com/contract/private/position-v2?symbol=BTCUSDT'
```
### Step 9: Report results
```
Position Opened Successfully:
Symbol: BTCUSDT
Direction: Long
Size: 100 contracts
Entry Price: 67,123.4 USDT
Leverage: 10x
Margin Mode: Cross
Margin Used: 671.23 USDT
Liquidation Price: 60,500.0 USDT
Mark Price: 67,125.0 USDT
Unrealized PnL: +0.16 USDT
Not financial advice. Futures trading carries significant risk of loss.
```
---
## Scenario: Open a Short Position
**User prompt:** "Short ETH with 20x leverage, 50 contracts, isolated margin"
The flow is the same as above, with these differences:
- **Side:** 4 (sell_open_short) instead of 1
- **Leverage:** 20x
- **Open type:** isolated
- **Liquidation direction:** above entry price (for shorts, liquidation is at higher price)
Order body:
```json
{"symbol":"ETHUSDT","side":4,"type":"market","size":50,"leverage":"20","open_type":"isolated"}
```
---
## Error Handling
| Error | Cause | Action |
|-------|-------|--------|
| `code != 1000` on submit | Various | Report error message; do not retry automatically |
| Insufficient balance | Not enough margin | Suggest transferring from spot or reducing position size |
| Invalid leverage | Exceeds bracket limit | Show leverage brackets and suggest a valid leverage |
| Position mode conflict | Has open positions | Cannot change mode; inform user to close positions first |
| Invalid size | Below min or above max | Show contract details with min/max volume |
FILE:references/bitmart-plan-order.md
# Plan (Conditional) Order Workflow
> **Credentials:** Read from `.env` — `BITMART_API_KEY`, `BITMART_API_SECRET`, `BITMART_API_MEMO`.
## What Are Plan Orders?
Plan orders are conditional/trigger orders that remain dormant until a specified trigger price is reached. When the market price hits the trigger, the plan order is automatically submitted as a regular order (market or limit).
**Use cases:**
- **Buy the dip**: Set a trigger to open long when BTC drops to a support level
- **Breakout entry**: Set a trigger to open long when BTC breaks above resistance
- **Automated close**: Set a trigger to close a position at a target or stop price
- **Conditional strategy**: Enter positions based on price conditions without manual monitoring
**`price_way` (required):**
| Value | Name | Trigger Condition | Typical Use |
|-------|------|-------------------|-------------|
| 1 | Bullish | When market price **rises above** trigger_price | Breakout entry, stop loss for short |
| 2 | Bearish | When market price **drops below** trigger_price | Buy the dip, stop loss for long |
---
## Scenario 1: Trigger Buy Long on Dip
**User prompt:** "Open a long BTC position when price drops to 65000"
### Step 1: Validate parameters
```bash
curl -s -H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
'https://api-cloud-v2.bitmart.com/contract/public/details?symbol=BTCUSDT'
```
Check current price and validate trigger price is reasonable. If current price is 67000 and trigger is 65000, the order will wait until price drops to 65000.
### Step 2: Present plan order summary and ask for CONFIRM
```
Plan Order Summary:
Symbol: BTCUSDT
Type: Conditional / Trigger Order
Direction: Open Long (buy_open_long, side=1)
Trigger Price: 65,000.0 USDT (when last price reaches this)
Execution Type: Market
Size: 10 contracts
Leverage: 10x
Margin Mode: Cross
Price Trigger: Last Price
This order will remain pending until BTCUSDT reaches 65,000.0 USDT.
When triggered, a market buy order for 10 contracts will be submitted.
Please type CONFIRM to proceed.
```
### Step 3: Submit plan order (after user confirms)
```bash
TIMESTAMP=$(date +%s000)
BODY='{"symbol":"BTCUSDT","side":1,"trigger_price":"65000","type":"market","size":10,"leverage":"10","open_type":"cross","price_way":2,"price_type":1}'
SIGN=$(echo -n "TIMESTAMP#BITMART_API_MEMO#BODY" | openssl dgst -sha256 -hmac "$BITMART_API_SECRET" | awk '{print $NF}')
curl -s -X POST 'https://api-cloud-v2.bitmart.com/contract/private/submit-plan-order' \
-H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
-H "Content-Type: application/json" \
-H "X-BM-KEY: $BITMART_API_KEY" \
-H "X-BM-SIGN: $SIGN" \
-H "X-BM-TIMESTAMP: $TIMESTAMP" \
-d "$BODY"
```
Expected response:
```json
{
"code": 1000,
"data": {
"order_id": 34567890123456789
}
}
```
### Step 4: Verify plan order is active
```bash
curl -s -H "X-BM-KEY: $BITMART_API_KEY" \
-H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
'https://api-cloud-v2.bitmart.com/contract/private/current-plan-order?symbol=BTCUSDT'
```
Confirm the plan order appears in the active plan orders list.
```
Plan Order Created Successfully:
Order ID: 34567890123456789
Symbol: BTCUSDT
Trigger: Buy Long when price <= 65,000.0 USDT
Execution: Market, 10 contracts, 10x leverage
Status: Active (waiting for trigger)
Not financial advice. Futures trading carries significant risk of loss.
```
---
## Scenario 2: Trigger Limit Order on Breakout
**User prompt:** "Open a limit long at 72100 when BTC breaks above 72000"
### Submit plan order with limit type
```bash
TIMESTAMP=$(date +%s000)
BODY='{"symbol":"BTCUSDT","side":1,"trigger_price":"72000","type":"limit","executive_price":"72100","size":10,"leverage":"10","open_type":"cross","price_way":1,"price_type":1,"mode":1}'
SIGN=$(echo -n "TIMESTAMP#BITMART_API_MEMO#BODY" | openssl dgst -sha256 -hmac "$BITMART_API_SECRET" | awk '{print $NF}')
curl -s -X POST 'https://api-cloud-v2.bitmart.com/contract/private/submit-plan-order' \
-H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
-H "Content-Type: application/json" \
-H "X-BM-KEY: $BITMART_API_KEY" \
-H "X-BM-SIGN: $SIGN" \
-H "X-BM-TIMESTAMP: $TIMESTAMP" \
-d "$BODY"
```
**Key difference:** When triggered, a limit order at 72100 is placed instead of a market order. This gives price control but risks not filling if the price moves too quickly.
---
## Monitoring Plan Orders
### List all active plan orders
```bash
curl -s -H "X-BM-KEY: $BITMART_API_KEY" \
-H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
'https://api-cloud-v2.bitmart.com/contract/private/current-plan-order?symbol=BTCUSDT&plan_type=plan&limit=50'
```
Report active plan orders in a table:
```
Active Plan Orders (BTCUSDT):
┌───────────────────┬──────────┬─────────────┬──────────┬──────┬────────┐
│ Order ID │ Side │ Trigger │ Type │ Size │ Status │
├───────────────────┼──────────┼─────────────┼──────────┼──────┼────────┤
│ 34567890123456789 │ Open Long│ <= 65,000.0 │ Market │ 10 │ Active │
│ 34567890123456790 │ Open Long│ >= 72,000.0 │ Limit │ 10 │ Active │
└───────────────────┴──────────┴─────────────┴──────────┴──────┴────────┘
```
---
## Modifying a Plan Order
**User prompt:** "Change my plan order trigger from 65000 to 64000"
### Step 1: Find the plan order
```bash
curl -s -H "X-BM-KEY: $BITMART_API_KEY" \
-H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
'https://api-cloud-v2.bitmart.com/contract/private/current-plan-order?symbol=BTCUSDT&plan_type=plan'
```
Identify the order by trigger price or ask user for order ID if ambiguous.
### Step 2: Modify the plan order
```bash
TIMESTAMP=$(date +%s000)
BODY='{"symbol":"BTCUSDT","order_id":"34567890123456789","type":"market","trigger_price":"64000","price_type":1}'
SIGN=$(echo -n "TIMESTAMP#BITMART_API_MEMO#BODY" | openssl dgst -sha256 -hmac "$BITMART_API_SECRET" | awk '{print $NF}')
curl -s -X POST 'https://api-cloud-v2.bitmart.com/contract/private/modify-plan-order' \
-H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
-H "Content-Type: application/json" \
-H "X-BM-KEY: $BITMART_API_KEY" \
-H "X-BM-SIGN: $SIGN" \
-H "X-BM-TIMESTAMP: $TIMESTAMP" \
-d "$BODY"
```
For **limit** type orders, also include `executive_price`:
```bash
BODY='{"symbol":"BTCUSDT","order_id":"34567890123456789","type":"limit","trigger_price":"64000","executive_price":"63500","price_type":1}'
```
Report the modification result.
---
## Canceling a Plan Order
**User prompt:** "Cancel my 65000 trigger order on BTC"
### Step 1: Find the plan order
Query active plan orders and identify the target by trigger price.
### Step 2: Cancel
```bash
TIMESTAMP=$(date +%s000)
BODY='{"symbol":"BTCUSDT","order_id":"34567890123456789"}'
SIGN=$(echo -n "TIMESTAMP#BITMART_API_MEMO#BODY" | openssl dgst -sha256 -hmac "$BITMART_API_SECRET" | awk '{print $NF}')
curl -s -X POST 'https://api-cloud-v2.bitmart.com/contract/private/cancel-plan-order' \
-H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
-H "Content-Type: application/json" \
-H "X-BM-KEY: $BITMART_API_KEY" \
-H "X-BM-SIGN: $SIGN" \
-H "X-BM-TIMESTAMP: $TIMESTAMP" \
-d "$BODY"
```
### Step 3: Verify cancellation
```bash
curl -s -H "X-BM-KEY: $BITMART_API_KEY" \
-H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
'https://api-cloud-v2.bitmart.com/contract/private/current-plan-order?symbol=BTCUSDT&plan_type=plan'
```
Confirm the order no longer appears in the active list.
---
## Error Handling
| Error | Cause | Action |
|-------|-------|--------|
| Invalid trigger price | Trigger price is at or past current price | Adjust trigger price to be in the correct direction |
| Plan order not found | Already triggered or canceled | Check order history |
| Insufficient balance | Not enough margin when triggered | Ensure sufficient balance is maintained |
| Rate limit | Too many plan order requests | Wait for rate limit window to reset |
FILE:references/bitmart-signature.md
# BitMart API Signature Guide
> **Credentials:** Read from `.env` — `BITMART_API_KEY`, `BITMART_API_SECRET`, `BITMART_API_MEMO`.
## Formula
```
timestamp = current UTC milliseconds
message = "{timestamp}#{memo}#{body}" # GET: body = ""
signature = HMAC-SHA256(secret, message) → hex
```
## Python Implementation
```python
import time, hmac, hashlib, json, os, requests
api_key = os.environ["BITMART_API_KEY"]
api_secret = os.environ["BITMART_API_SECRET"]
memo = os.environ["BITMART_API_MEMO"]
def sign(body_str: str):
ts = str(int(time.time() * 1000))
msg = f"{ts}#{memo}#{body_str}"
sig = hmac.new(api_secret.encode(), msg.encode(), hashlib.sha256).hexdigest()
return ts, sig
# POST — use data= not json=, and sign the exact same string
body = {"symbol": "ETHUSDT", "side": 1, "type": "market", "size": 1, "leverage": "5", "open_type": "isolated"}
body_str = json.dumps(body, separators=(",", ":"))
ts, sig = sign(body_str)
resp = requests.post(
"https://api-cloud-v2.bitmart.com/contract/private/submit-order",
headers={
"Content-Type": "application/json",
"X-BM-KEY": api_key,
"X-BM-SIGN": sig,
"X-BM-TIMESTAMP": ts,
"User-Agent": "bitmart-skills/futures/v2026.3.23",
"X-BM-BROKER-ID": "BlaveData666666",
},
data=body_str,
)
# GET — body is empty string ""
ts, sig = sign("")
resp = requests.get(
"https://api-cloud-v2.bitmart.com/contract/private/position-v2",
headers={
"X-BM-KEY": api_key,
"X-BM-SIGN": sig,
"X-BM-TIMESTAMP": ts,
"User-Agent": "bitmart-skills/futures/v2026.3.23",
"X-BM-BROKER-ID": "BlaveData666666",
},
)
```
## Common Mistakes
| Mistake | Correct |
|---|---|
| GET body = `"{}"` or `None` | GET body = `""` (empty string) |
| `requests.post(json=body)` | `requests.post(data=body_str)` — sign and send the exact same string |
| Timestamp in seconds | Timestamp in **milliseconds**: `int(time.time() * 1000)` |
| `hmac.new(secret, msg, ...)` without `.encode()` | `hmac.new(secret.encode(), msg.encode(), hashlib.sha256).hexdigest()` |
FILE:references/bitmart-spot-api-reference.md
# BitMart Spot Trading API Reference
> **Credentials:** Read from `.env` — `BITMART_API_KEY`, `BITMART_API_SECRET`, `BITMART_API_MEMO`.
> **Broker ID:** Always include `X-BM-BROKER-ID: BlaveData666666` header on ALL requests.
> **Base URL:** `https://api-cloud.bitmart.com`
>
> **Sources of truth:**
> - Go SDK v1.4.0 for method signatures and parameter names
> - BitMart API docs for HTTP methods and response formats
---
## Parameter Naming Convention
> **IMPORTANT:** Parameter naming varies by API version:
>
> - **v1-v3 endpoints:** `snake_case` (e.g., `client_order_id`, `order_id`, `start_time`)
> - **v4 endpoints:** `camelCase` (e.g., `clientOrderId`, `orderId`, `orderMode`, `startTime`)
> - **Exception:** v2 `submit_order` uses `stpMode` (camelCase) alongside `client_order_id` (snake_case)
> - **Exception:** v1 `margin/submit_order` uses `clientOrderId` (camelCase in a v1 endpoint)
---
## Authentication Levels
| Level | Header | Description |
|-------|--------|-------------|
| **NONE** | None | Public endpoints, no credentials needed |
| **KEYED** | `X-BM-KEY` | Requires API key only |
| **SIGNED** | `X-BM-KEY`, `X-BM-SIGN`, `X-BM-TIMESTAMP` | Requires full HMAC-SHA256 signature |
**Signature construction:**
- GET requests: message = `{timestamp}#{memo}#`
- POST requests: message = `{timestamp}#{memo}#{body}`
- Sign with HMAC-SHA256 using your secret key
---
## Table of Contents
**Public Market Data (1-9)**
1. [Get Single Pair Ticker](#1-get-single-pair-ticker)
2. [Get All Pair Tickers](#2-get-all-pair-tickers)
3. [Order Book Depth](#3-order-book-depth)
4. [Recent Public Trades](#4-recent-public-trades)
5. [Historical K-Line](#5-historical-k-line)
6. [Latest K-Line](#6-latest-k-line)
7. [Trading Pair Details](#7-trading-pair-details)
8. [Trading Pair List](#8-trading-pair-list)
9. [All Supported Currencies](#9-all-supported-currencies)
**Account & Margin Account (10-17)**
10. [Account Balance (All Wallets)](#10-account-balance-all-wallets)
11. [Spot Wallet Balance](#11-spot-wallet-balance)
12. [Actual Trade Fee Rate](#12-actual-trade-fee-rate)
13. [Base Fee Rate](#13-base-fee-rate)
14. [Isolated Margin Account Details](#14-isolated-margin-account-details)
15. [Trading Pair Borrowing Rate & Amount](#15-trading-pair-borrowing-rate--amount)
16. [Borrow Record (Isolated)](#16-borrow-record-isolated)
17. [Repayment Record (Isolated)](#17-repayment-record-isolated)
**Spot Trading (18-23)**
18. [Place Single Order](#18-place-single-order)
19. [Place Margin Order](#19-place-margin-order)
20. [Batch Place Orders](#20-batch-place-orders)
21. [Cancel Single Order](#21-cancel-single-order)
22. [Cancel Multiple Orders](#22-cancel-multiple-orders)
23. [Cancel All Open Orders](#23-cancel-all-open-orders)
**Order Query (24-29)**
24. [Query Order by Order ID](#24-query-order-by-order-id)
25. [Query Order by Client Order ID](#25-query-order-by-client-order-id)
26. [All Open Orders](#26-all-open-orders)
27. [Historical Orders](#27-historical-orders)
28. [Account Trade History](#28-account-trade-history)
29. [Trades for Specific Order](#29-trades-for-specific-order)
**Margin Loan (30-32)**
30. [Margin Borrow (Isolated)](#30-margin-borrow-isolated)
31. [Margin Repay (Isolated)](#31-margin-repay-isolated)
32. [Margin Asset Transfer](#32-margin-asset-transfer)
**System (33-34)**
33. [Get System Time](#33-get-system-time)
34. [Get System Service Status](#34-get-system-service-status)
---
# Public Market Data
## 1. Get Single Pair Ticker
`GET /spot/quotation/v3/ticker`
**Auth:** NONE | **Rate Limit:** 15 req/2sec per IP
**Parameters (query string):**
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `symbol` | String | Yes | Trading pair, e.g., `BTC_USDT` |
**Example:**
```bash
curl -s -H "User-Agent: bitmart-skills/spot/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
"https://api-cloud.bitmart.com/spot/quotation/v3/ticker?symbol=BTC_USDT"
```
**Response:**
```json
{
"code": 1000,
"trace": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"message": "success",
"data": {
"symbol": "BTC_USDT",
"last": "67000.00",
"v_24h": "12345.6789",
"qv_24h": "827045678.90",
"open_24h": "66500.00",
"high_24h": "67500.00",
"low_24h": "66000.00",
"fluctuation": "+0.0075",
"bid_px": "66999.50",
"bid_sz": "0.5000",
"ask_px": "67000.50",
"ask_sz": "0.3200",
"ts": "1700000000000"
}
}
```
**Response Fields:**
| Field | Description |
|-------|-------------|
| `symbol` | Trading pair |
| `last` | Last trade price |
| `v_24h` | 24h volume in base currency |
| `qv_24h` | 24h volume in quote currency |
| `open_24h` | Opening price 24h ago |
| `high_24h` | 24h high price |
| `low_24h` | 24h low price |
| `fluctuation` | 24h price change ratio (signed) |
| `bid_px` | Best bid price |
| `bid_sz` | Best bid size |
| `ask_px` | Best ask price |
| `ask_sz` | Best ask size |
| `ts` | Timestamp in milliseconds (String) |
---
## 2. Get All Pair Tickers
`GET /spot/quotation/v3/tickers`
**Auth:** NONE | **Rate Limit:** 10 req/2sec per IP
**Parameters:** None
**Example:**
```bash
curl -s -H "User-Agent: bitmart-skills/spot/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
"https://api-cloud.bitmart.com/spot/quotation/v3/tickers"
```
**Response:**
```json
{
"code": 1000,
"trace": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"message": "success",
"data": [
["BTC_USDT", "67000.00", "12345.6789", "827045678.90", "66500.00", "67500.00", "66000.00", "+0.0075", "66999.50", "0.5000", "67000.50", "0.3200", "1700000000000"],
["ETH_USDT", "3500.00", "98765.4321", "345678901.23", "3480.00", "3520.00", "3460.00", "+0.0057", "3499.80", "5.0000", "3500.20", "3.2000", "1700000000000"]
]
}
```
**Response Fields (array positions):**
| Index | Field | Description |
|-------|-------|-------------|
| 0 | symbol | Trading pair |
| 1 | last | Last trade price |
| 2 | v_24h | 24h volume in base currency |
| 3 | qv_24h | 24h volume in quote currency |
| 4 | open_24h | Opening price 24h ago |
| 5 | high_24h | 24h high price |
| 6 | low_24h | 24h low price |
| 7 | fluctuation | 24h price change ratio |
| 8 | bid_px | Best bid price |
| 9 | bid_sz | Best bid size |
| 10 | ask_px | Best ask price |
| 11 | ask_sz | Best ask size |
| 12 | timestamp | Timestamp in milliseconds (String) |
---
## 3. Order Book Depth
`GET /spot/quotation/v3/books`
**Auth:** NONE | **Rate Limit:** 15 req/2sec per IP
**Parameters (query string):**
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `symbol` | String | Yes | Trading pair, e.g., `BTC_USDT` |
| `limit` | Integer | No | Number of levels, max 50, default 35 |
**Example:**
```bash
curl -s -H "User-Agent: bitmart-skills/spot/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
"https://api-cloud.bitmart.com/spot/quotation/v3/books?symbol=BTC_USDT&limit=5"
```
**Response:**
```json
{
"code": 1000,
"trace": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"message": "success",
"data": {
"ts": "1700000000000",
"symbol": "BTC_USDT",
"asks": [
["67000.50", "0.3200"],
["67001.00", "1.5000"],
["67002.50", "0.8000"],
["67005.00", "2.1000"],
["67010.00", "0.4500"]
],
"bids": [
["66999.50", "0.5000"],
["66999.00", "1.2000"],
["66998.00", "0.7500"],
["66995.00", "3.0000"],
["66990.00", "1.8000"]
]
}
}
```
**Response Fields:**
| Field | Description |
|-------|-------------|
| `ts` | Timestamp in milliseconds |
| `symbol` | Trading pair |
| `asks` | Ask levels as `[[price, amount], ...]`, sorted ascending by price |
| `bids` | Bid levels as `[[price, amount], ...]`, sorted descending by price |
---
## 4. Recent Public Trades
`GET /spot/quotation/v3/trades`
**Auth:** NONE | **Rate Limit:** 15 req/2sec per IP
**Parameters (query string):**
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `symbol` | String | Yes | Trading pair, e.g., `BTC_USDT` |
| `limit` | Integer | No | Number of trades, max 50, default 50 |
**Example:**
```bash
curl -s -H "User-Agent: bitmart-skills/spot/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
"https://api-cloud.bitmart.com/spot/quotation/v3/trades?symbol=BTC_USDT&limit=3"
```
**Response:**
```json
{
"code": 1000,
"trace": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"message": "success",
"data": [
["BTC_USDT", "1700000000000", "67000.00", "0.1500", "buy"],
["BTC_USDT", "1699999999500", "66999.50", "0.2300", "sell"],
["BTC_USDT", "1699999999000", "67000.50", "0.0800", "buy"]
]
}
```
**Response Fields (array positions):**
| Index | Field | Description |
|-------|-------|-------------|
| 0 | symbol | Trading pair |
| 1 | timestamp | Trade time in milliseconds |
| 2 | price | Trade price |
| 3 | size | Trade size in base currency |
| 4 | side | `"buy"` or `"sell"` |
---
## 5. Historical K-Line
`GET /spot/quotation/v3/klines`
**Auth:** NONE | **Rate Limit:** 10 req/2sec per IP
**Parameters (query string):**
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `symbol` | String | Yes | Trading pair, e.g., `BTC_USDT` |
| `before` | Long | No | Start timestamp in **seconds** |
| `after` | Long | No | End timestamp in **seconds** |
| `step` | Integer | No | K-line interval in minutes. Allowed values: `1`, `5`, `15`, `30`, `60`, `120`, `240`, `1440`, `10080`, `43200` (10 values). Default: `1` |
| `limit` | Integer | No | Number of candles, max 200, default 100 |
**Kline step reference:**
| Step | Interval |
|------|----------|
| 1 | 1 minute |
| 5 | 5 minutes |
| 15 | 15 minutes |
| 30 | 30 minutes |
| 60 | 1 hour |
| 120 | 2 hours |
| 240 | 4 hours |
| 1440 | 1 day |
| 10080 | 1 week |
| 43200 | 1 month |
**Example:**
```bash
curl -s -H "User-Agent: bitmart-skills/spot/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
"https://api-cloud.bitmart.com/spot/quotation/v3/klines?symbol=BTC_USDT&step=60&limit=3"
```
**Response:**
```json
{
"code": 1000,
"trace": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"message": "success",
"data": [
["1700000000", "66800.00", "67100.00", "66750.00", "67000.00", "125.5000", "8398250.00"],
["1699996400", "66600.00", "66900.00", "66550.00", "66800.00", "98.3200", "6557568.00"],
["1699992800", "66700.00", "66750.00", "66500.00", "66600.00", "110.1500", "7336990.50"]
]
}
```
**Response Fields (array positions):**
| Index | Field | Description |
|-------|-------|-------------|
| 0 | t | Candle open time in **seconds** (String) |
| 1 | o | Opening price |
| 2 | h | Highest price |
| 3 | l | Lowest price |
| 4 | c | Closing price |
| 5 | v | Volume in base currency |
| 6 | qv | Volume in quote currency |
---
## 6. Latest K-Line
`GET /spot/quotation/v3/lite-klines`
**Auth:** NONE | **Rate Limit:** 15 req/2sec per IP
**Parameters (query string):**
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `symbol` | String | Yes | Trading pair, e.g., `BTC_USDT` |
| `before` | Long | No | Start timestamp in **seconds** |
| `after` | Long | No | End timestamp in **seconds** |
| `step` | Integer | No | K-line interval in minutes. Allowed values: `1`, `5`, `15`, `30`, `60`, `120`, `240`, `1440`, `10080`, `43200` (10 values). Default: `1` |
| `limit` | Integer | No | Number of candles, max 200, default 100 |
**Example:**
```bash
curl -s -H "User-Agent: bitmart-skills/spot/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
"https://api-cloud.bitmart.com/spot/quotation/v3/lite-klines?symbol=BTC_USDT&step=15&limit=2"
```
**Response:**
```json
{
"code": 1000,
"trace": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"message": "success",
"data": [
["1700000000", "66950.00", "67100.00", "66900.00", "67050.00", "45.2300", "3028906.50"],
["1699999100", "66900.00", "66980.00", "66850.00", "66950.00", "38.1500", "2553427.50"]
]
}
```
**Response Fields (array positions):**
| Index | Field | Description |
|-------|-------|-------------|
| 0 | t | Candle open time in **seconds** (String) |
| 1 | o | Opening price |
| 2 | h | Highest price |
| 3 | l | Lowest price |
| 4 | c | Closing price |
| 5 | v | Volume in base currency |
| 6 | qv | Volume in quote currency |
---
## 7. Trading Pair Details
`GET /spot/v1/symbols/details`
**Auth:** NONE | **Rate Limit:** 12 req/2sec per IP
**Parameters:** None
**Example:**
```bash
curl -s -H "User-Agent: bitmart-skills/spot/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
"https://api-cloud.bitmart.com/spot/v1/symbols/details"
```
**Response:**
```json
{
"code": 1000,
"trace": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"message": "success",
"data": {
"symbols": [
{
"symbol": "BTC_USDT",
"symbol_id": 1234,
"base_currency": "BTC",
"quote_currency": "USDT",
"quote_increment": "0.01",
"base_min_size": "0.00001",
"price_min_precision": 2,
"price_max_precision": 6,
"trade_status": "trading",
"min_buy_amount": "5.00",
"min_sell_amount": "5.00",
"expiration": "",
"planned_down_time": ""
}
]
}
}
```
**Response Fields:**
| Field | Description |
|-------|-------------|
| `symbol` | Trading pair name |
| `symbol_id` | Numeric symbol identifier |
| `base_currency` | Base currency (e.g., `BTC`) |
| `quote_currency` | Quote currency (e.g., `USDT`) |
| `quote_increment` | Minimum price increment |
| `base_min_size` | Minimum order size in base currency |
| `price_min_precision` | Minimum decimal places for price |
| `price_max_precision` | Maximum decimal places for price |
| `trade_status` | `"trading"` if active |
| `min_buy_amount` | Minimum buy order value in quote currency |
| `min_sell_amount` | Minimum sell order value in quote currency |
| `expiration` | Token expiration info (empty for most tokens) |
| `planned_down_time` | Planned downtime info (empty if none) |
---
## 8. Trading Pair List
`GET /spot/v1/symbols`
**Auth:** NONE | **Rate Limit:** 8 req/2sec per IP
**Parameters:** None
**SDK:** `GetSpotSymbol()` -> `requestWithoutParams(GET, API_SPOT_SYMBOLS_URL, NONE)`
**Example:**
```bash
curl -s -H "User-Agent: bitmart-skills/spot/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
"https://api-cloud.bitmart.com/spot/v1/symbols"
```
**Response:**
```json
{
"code": 1000,
"trace": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"message": "success",
"data": {
"symbols": [
"BTC_USDT",
"ETH_USDT",
"ETH_BTC",
"SOL_USDT",
"XRP_USDT"
]
}
}
```
**Response Fields:**
| Field | Description |
|-------|-------------|
| `symbols` | Array of trading pair name strings |
---
## 9. All Supported Currencies
`GET /spot/v1/currencies`
**Auth:** NONE | **Rate Limit:** 8 req/2sec per IP
**Parameters:** None
**Example:**
```bash
curl -s -H "User-Agent: bitmart-skills/spot/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
"https://api-cloud.bitmart.com/spot/v1/currencies"
```
**Response:**
```json
{
"code": 1000,
"trace": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"message": "success",
"data": {
"currencies": [
{
"id": "BTC",
"name": "Bitcoin",
"withdraw_enabled": true,
"deposit_enabled": true
},
{
"id": "USDT",
"name": "Tether",
"withdraw_enabled": true,
"deposit_enabled": true
}
]
}
}
```
**Response Fields:**
| Field | Description |
|-------|-------------|
| `id` | Currency symbol (e.g., `BTC`) |
| `name` | Full currency name |
| `withdraw_enabled` | Whether withdrawals are enabled |
| `deposit_enabled` | Whether deposits are enabled |
---
# Account & Margin Account
## 10. Account Balance (All Wallets)
`GET /account/v1/wallet`
**Auth:** KEYED | **Rate Limit:** 12 req/2sec per KEY
**Parameters (query string):**
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `currency` | String | No | Filter by currency, e.g., `BTC` |
| `needUsdValuation` | Boolean | No | Include USD valuation in response |
**Example:**
```bash
curl -s -H "User-Agent: bitmart-skills/spot/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
"https://api-cloud.bitmart.com/account/v1/wallet" \
-H "X-BM-KEY: $BITMART_API_KEY"
```
**Response:**
```json
{
"code": 1000,
"trace": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"message": "success",
"data": {
"wallet": [
{
"currency": "BTC",
"name": "Bitcoin",
"available": "1.25000000",
"frozen": "0.10000000",
"unAvailable": "0.00000000",
"available_usd_valuation": "83750.00"
},
{
"currency": "USDT",
"name": "Tether",
"available": "50000.00000000",
"frozen": "5000.00000000",
"unAvailable": "0.00000000",
"available_usd_valuation": "50000.00"
}
]
}
}
```
**Response Fields:**
| Field | Description |
|-------|-------------|
| `currency` | Currency symbol |
| `name` | Currency full name |
| `available` | Available balance |
| `frozen` | Frozen balance (in open orders, etc.) |
| `unAvailable` | Unavailable balance |
| `available_usd_valuation` | USD valuation of available balance (only if `needUsdValuation=true`) |
---
## 11. Spot Wallet Balance
`GET /spot/v1/wallet`
**Auth:** KEYED | **Rate Limit:** 12 req/2sec per KEY
**Parameters:** None
**Example:**
```bash
curl -s -H "User-Agent: bitmart-skills/spot/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
"https://api-cloud.bitmart.com/spot/v1/wallet" \
-H "X-BM-KEY: $BITMART_API_KEY"
```
**Response:**
```json
{
"code": 1000,
"trace": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"message": "success",
"data": {
"wallet": [
{
"id": "BTC",
"available": "1.25000000",
"name": "Bitcoin",
"frozen": "0.10000000"
},
{
"id": "USDT",
"available": "50000.00000000",
"name": "Tether",
"frozen": "5000.00000000"
}
]
}
}
```
**Response Fields:**
| Field | Description |
|-------|-------------|
| `id` | Currency symbol |
| `available` | Available balance for trading |
| `name` | Currency full name |
| `frozen` | Balance locked in open orders |
---
## 12. Actual Trade Fee Rate
`GET /spot/v1/trade_fee`
**Auth:** KEYED | **Rate Limit:** 2 req/2sec per KEY
**Parameters (query string):**
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `symbol` | String | Yes | Trading pair, e.g., `BTC_USDT` |
**Example:**
```bash
curl -s -H "User-Agent: bitmart-skills/spot/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
"https://api-cloud.bitmart.com/spot/v1/trade_fee?symbol=BTC_USDT" \
-H "X-BM-KEY: $BITMART_API_KEY"
```
**Response:**
```json
{
"code": 1000,
"trace": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"message": "success",
"data": {
"symbol": "BTC_USDT",
"buy_taker_fee_rate": "0.0025",
"sell_taker_fee_rate": "0.0025",
"buy_maker_fee_rate": "0.0025",
"sell_maker_fee_rate": "0.0025"
}
}
```
**Response Fields:**
| Field | Description |
|-------|-------------|
| `symbol` | Trading pair |
| `buy_taker_fee_rate` | Fee rate for buy taker orders |
| `sell_taker_fee_rate` | Fee rate for sell taker orders |
| `buy_maker_fee_rate` | Fee rate for buy maker orders |
| `sell_maker_fee_rate` | Fee rate for sell maker orders |
---
## 13. Base Fee Rate
`GET /spot/v1/user_fee`
**Auth:** KEYED | **Rate Limit:** 2 req/2sec per KEY
**Parameters:** None
**Example:**
```bash
curl -s -H "User-Agent: bitmart-skills/spot/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
"https://api-cloud.bitmart.com/spot/v1/user_fee" \
-H "X-BM-KEY: $BITMART_API_KEY"
```
**Response:**
```json
{
"code": 1000,
"trace": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"message": "success",
"data": {
"user_rate_type": 1,
"level": "level_1",
"taker_fee_rate_A": "0.0025",
"maker_fee_rate_A": "0.0025",
"taker_fee_rate_B": "0.0025",
"maker_fee_rate_B": "0.0025",
"taker_fee_rate_C": "0.0025",
"maker_fee_rate_C": "0.0025",
"taker_fee_rate_D": "0.0025",
"maker_fee_rate_D": "0.0025"
}
}
```
**Response Fields:**
| Field | Description |
|-------|-------------|
| `user_rate_type` | User rate type identifier |
| `level` | User fee level |
| `taker_fee_rate_A` | Taker fee rate for class A pairs |
| `maker_fee_rate_A` | Maker fee rate for class A pairs |
| `taker_fee_rate_B` | Taker fee rate for class B pairs |
| `maker_fee_rate_B` | Maker fee rate for class B pairs |
| `taker_fee_rate_C` | Taker fee rate for class C pairs |
| `maker_fee_rate_C` | Maker fee rate for class C pairs |
| `taker_fee_rate_D` | Taker fee rate for class D pairs |
| `maker_fee_rate_D` | Maker fee rate for class D pairs |
---
## 14. Isolated Margin Account Details
`GET /spot/v1/margin/isolated/account`
**Auth:** KEYED | **Rate Limit:** 12 req/2sec per KEY
**SDK:** `GetMarginAccountDetailsIsolated(options)` with optional `symbol`
**Parameters (query string):**
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `symbol` | String | No | Trading pair. Omit to get all isolated margin assets |
**Example:**
```bash
curl -s -H "User-Agent: bitmart-skills/spot/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
"https://api-cloud.bitmart.com/spot/v1/margin/isolated/account?symbol=BTC_USDT" \
-H "X-BM-KEY: $BITMART_API_KEY"
```
**Response:**
```json
{
"code": 1000,
"trace": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"message": "success",
"data": {
"symbols": [
{
"symbol": "BTC_USDT",
"risk_rate": "999.00",
"risk_level": "1",
"buy_enabled": true,
"sell_enabled": true,
"liquidate_price": "0.00",
"liquidate_rate": "1.15",
"base": {
"currency": "BTC",
"borrow_enabled": true,
"borrowed": "0.00000000",
"available": "1.00000000",
"frozen": "0.00000000",
"net_asset": "1.00000000",
"net_assetBTC": "1.00000000",
"total_asset": "1.00000000",
"borrow_unpaid": "0.00000000",
"interest_unpaid": "0.00000000"
},
"quote": {
"currency": "USDT",
"borrow_enabled": true,
"borrowed": "0.00000000",
"available": "50000.00000000",
"frozen": "0.00000000",
"net_asset": "50000.00000000",
"net_assetBTC": "0.74626866",
"total_asset": "50000.00000000",
"borrow_unpaid": "0.00000000",
"interest_unpaid": "0.00000000"
}
}
]
}
}
```
**Response Fields:**
| Field | Description |
|-------|-------------|
| `symbol` | Trading pair |
| `risk_rate` | Current risk rate |
| `risk_level` | Risk level as numeric string: `"1"`=low, `"2"`=medium, `"3"`=high |
| `buy_enabled` | Whether buying is enabled |
| `sell_enabled` | Whether selling is enabled |
| `liquidate_price` | Estimated liquidation price |
| `liquidate_rate` | Liquidation rate threshold |
| `base.currency` | Base currency symbol |
| `base.borrow_enabled` | Whether borrowing is enabled for base |
| `base.borrowed` | Amount currently borrowed |
| `base.available` | Available balance |
| `base.frozen` | Frozen balance |
| `base.net_asset` | Net asset value |
| `base.net_assetBTC` | Net asset value in BTC |
| `base.total_asset` | Total asset value |
| `base.borrow_unpaid` | Outstanding borrowed amount |
| `base.interest_unpaid` | Outstanding interest |
| `quote.*` | Same fields as base, for quote currency |
---
## 15. Trading Pair Borrowing Rate & Amount
`GET /spot/v1/margin/isolated/pairs`
**Auth:** KEYED | **Rate Limit:** 2 req/2sec per KEY
**SDK:** `GetTradingPairBorrowingRateAndAmount(symbol)` with optional symbol
**Parameters (query string):**
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `symbol` | String | No | Trading pair. Omit to get all pairs |
**Example:**
```bash
curl -s -H "User-Agent: bitmart-skills/spot/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
"https://api-cloud.bitmart.com/spot/v1/margin/isolated/pairs?symbol=BTC_USDT" \
-H "X-BM-KEY: $BITMART_API_KEY"
```
**Response:**
```json
{
"code": 1000,
"trace": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"message": "success",
"data": {
"symbols": [
{
"symbol": "BTC_USDT",
"max_leverage": "5",
"symbol_enabled": true,
"base": {
"currency": "BTC",
"daily_interest": "0.00100000",
"hourly_interest": "0.00004167",
"max_borrow_amount": "100.00000000",
"min_borrow_amount": "0.00100000",
"borrowable_amount": "50.00000000"
},
"quote": {
"currency": "USDT",
"daily_interest": "0.00100000",
"hourly_interest": "0.00004167",
"max_borrow_amount": "1000000.00000000",
"min_borrow_amount": "10.00000000",
"borrowable_amount": "500000.00000000"
}
}
]
}
}
```
**Response Fields:**
| Field | Description |
|-------|-------------|
| `symbol` | Trading pair |
| `max_leverage` | Maximum leverage allowed |
| `symbol_enabled` | Whether margin trading is enabled for this pair |
| `base.currency` | Base currency symbol |
| `base.daily_interest` | Daily interest rate for borrowing |
| `base.hourly_interest` | Hourly interest rate for borrowing |
| `base.max_borrow_amount` | Maximum borrowable amount |
| `base.min_borrow_amount` | Minimum borrowable amount |
| `base.borrowable_amount` | Currently available amount to borrow |
| `quote.*` | Same fields as base, for quote currency |
---
## 16. Borrow Record (Isolated)
`GET /spot/v1/margin/isolated/borrow_record`
**Auth:** KEYED | **Rate Limit:** 150 req/2sec per KEY
**SDK:** `GetBorrowRecordIsolated(symbol, borrowId, startTime, endTime, N)`
**Parameters (query string):**
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `symbol` | String | Yes | Trading pair, e.g., `BTC_USDT` |
| `borrow_id` | String | No | Filter by specific borrow ID |
| `start_time` | Long | No | Start time in milliseconds |
| `end_time` | Long | No | End time in milliseconds |
| `N` | Integer | No | Number of records, max 100, default 50 |
**Example:**
```bash
curl -s -H "User-Agent: bitmart-skills/spot/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
"https://api-cloud.bitmart.com/spot/v1/margin/isolated/borrow_record?symbol=BTC_USDT&N=10" \
-H "X-BM-KEY: $BITMART_API_KEY"
```
**Response:**
```json
{
"code": 1000,
"trace": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"message": "success",
"data": {
"records": [
{
"borrow_id": "123456789",
"symbol": "BTC_USDT",
"currency": "USDT",
"borrow_amount": "10000.00000000",
"daily_interest": "0.00100000",
"hourly_interest": "0.00004167",
"interest_amount": "1.00000000",
"create_time": 1700000000
}
]
}
}
```
**Response Fields:**
| Field | Description |
|-------|-------------|
| `borrow_id` | Unique borrow record ID |
| `symbol` | Trading pair |
| `currency` | Borrowed currency |
| `borrow_amount` | Amount borrowed |
| `daily_interest` | Daily interest rate at time of borrowing |
| `hourly_interest` | Hourly interest rate at time of borrowing |
| `interest_amount` | Accrued interest amount |
| `create_time` | Borrow time in seconds |
---
## 17. Repayment Record (Isolated)
`GET /spot/v1/margin/isolated/repay_record`
**Auth:** KEYED | **Rate Limit:** 150 req/2sec per KEY
**SDK:** `GetRepaymentRecordIsolated(symbol, repayId, currency, startTime, endTime, N)`
**Parameters (query string):**
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `symbol` | String | Yes | Trading pair, e.g., `BTC_USDT` |
| `repay_id` | String | No | Filter by specific repay ID |
| `currency` | String | No | Filter by currency |
| `start_time` | Long | No | Start time in milliseconds |
| `end_time` | Long | No | End time in milliseconds |
| `N` | Integer | No | Number of records, max 100, default 50 |
**Example:**
```bash
curl -s -H "User-Agent: bitmart-skills/spot/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
"https://api-cloud.bitmart.com/spot/v1/margin/isolated/repay_record?symbol=BTC_USDT&N=10" \
-H "X-BM-KEY: $BITMART_API_KEY"
```
**Response:**
```json
{
"code": 1000,
"trace": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"message": "success",
"data": {
"records": [
{
"repay_id": "987654321",
"symbol": "BTC_USDT",
"currency": "USDT",
"repaid_amount": "10001.00000000",
"repaid_principal": "10000.00000000",
"repaid_interest": "1.00000000",
"repay_time": 1700086400
}
]
}
}
```
**Response Fields:**
| Field | Description |
|-------|-------------|
| `repay_id` | Unique repayment record ID |
| `symbol` | Trading pair |
| `currency` | Repaid currency |
| `repaid_amount` | Total repayment amount (principal + interest) |
| `repaid_principal` | Principal amount repaid |
| `repaid_interest` | Interest amount repaid |
| `repay_time` | Repayment time in seconds |
---
# Spot Trading
## 18. Place Single Order
`POST /spot/v2/submit_order`
**Auth:** SIGNED | **Rate Limit:** 40 req/2sec per UID
> **Note on parameter naming:** This v2 endpoint uses `client_order_id` (snake_case) but `stpMode` (camelCase). This is a known inconsistency.
**Parameters (JSON body):**
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `symbol` | String | Yes | Trading pair, e.g., `BTC_USDT` |
| `side` | String | Yes | `"buy"` or `"sell"` |
| `type` | String | Yes | `"limit"`, `"market"`, `"limit_maker"`, `"ioc"` |
| `client_order_id` | String | No | User-defined order ID (snake_case!) |
| `size` | String | Conditional | Order quantity in base currency. Required for `limit`, `limit_maker`, `ioc`, and `market` sell |
| `price` | String | Conditional | Order price. Required for `limit`, `limit_maker`, `ioc` |
| `notional` | String | Conditional | Order value in quote currency. Required for `market` buy orders |
| `stpMode` | String | No | Self-trade prevention mode (camelCase exception in v2) |
**Order type rules:**
| Type | `price` | `size` | `notional` |
|------|---------|--------|------------|
| `limit` | Required | Required | - |
| `market` buy | - | - | Required |
| `market` sell | - | Required | - |
| `limit_maker` | Required | Required | - |
| `ioc` | Required | Required | - |
**Example (limit buy):**
```bash
TIMESTAMP=$(date +%s000)
BODY='{"symbol":"BTC_USDT","side":"buy","type":"limit","price":"60000.00","size":"0.001"}'
MESSAGE="TIMESTAMP#BITMART_API_MEMO#BODY"
SIGN=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "$BITMART_API_SECRET" | awk '{print $NF}')
curl -s -X POST "https://api-cloud.bitmart.com/spot/v2/submit_order" \
-H "User-Agent: bitmart-skills/spot/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
-H "Content-Type: application/json" \
-H "X-BM-KEY: $BITMART_API_KEY" \
-H "X-BM-SIGN: $SIGN" \
-H "X-BM-TIMESTAMP: $TIMESTAMP" \
-d "$BODY"
```
**Example (market buy — use `notional`, NOT `size`):**
```bash
TIMESTAMP=$(date +%s000)
BODY='{"symbol":"BTC_USDT","side":"buy","type":"market","notional":"50"}'
MESSAGE="TIMESTAMP#BITMART_API_MEMO#BODY"
SIGN=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "$BITMART_API_SECRET" | awk '{print $NF}')
curl -s -X POST "https://api-cloud.bitmart.com/spot/v2/submit_order" \
-H "User-Agent: bitmart-skills/spot/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
-H "Content-Type: application/json" \
-H "X-BM-KEY: $BITMART_API_KEY" \
-H "X-BM-SIGN: $SIGN" \
-H "X-BM-TIMESTAMP: $TIMESTAMP" \
-d "$BODY"
```
**Response:**
```json
{
"code": 1000,
"trace": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"message": "success",
"data": {
"order_id": "1234567890"
}
}
```
**Response Fields:**
| Field | Description |
|-------|-------------|
| `order_id` | Server-assigned order ID |
---
## 19. Place Margin Order
`POST /spot/v1/margin/submit_order`
**Auth:** SIGNED | **Rate Limit:** 20 req/1sec per UID
**SDK:** `PostMarginSubmitOrder(MarginOrder)` -- uses `clientOrderId` (camelCase, exception in v1)
> **Note on parameter naming:** This v1 endpoint uses `clientOrderId` (camelCase), which is an exception to the v1 snake_case convention.
**Parameters (JSON body):**
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `symbol` | String | Yes | Trading pair, e.g., `BTC_USDT` |
| `side` | String | Yes | `"buy"` or `"sell"` |
| `type` | String | Yes | `"limit"`, `"market"`, `"limit_maker"`, `"ioc"` |
| `clientOrderId` | String | No | User-defined order ID (camelCase exception!) |
| `size` | String | Conditional | Order quantity in base currency |
| `price` | String | Conditional | Order price |
| `notional` | String | Conditional | Order value in quote currency. Required for `market` buy orders |
**Example:**
```bash
TIMESTAMP=$(date +%s000)
BODY='{"symbol":"BTC_USDT","side":"buy","type":"limit","price":"60000.00","size":"0.001","clientOrderId":"margin_order_001"}'
MESSAGE="TIMESTAMP#BITMART_API_MEMO#BODY"
SIGN=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "$BITMART_API_SECRET" | awk '{print $NF}')
curl -s -X POST "https://api-cloud.bitmart.com/spot/v1/margin/submit_order" \
-H "User-Agent: bitmart-skills/spot/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
-H "Content-Type: application/json" \
-H "X-BM-KEY: $BITMART_API_KEY" \
-H "X-BM-SIGN: $SIGN" \
-H "X-BM-TIMESTAMP: $TIMESTAMP" \
-d "$BODY"
```
**Response:**
```json
{
"code": 1000,
"trace": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"message": "success",
"data": {
"order_id": "1234567891"
}
}
```
**Response Fields:**
| Field | Description |
|-------|-------------|
| `order_id` | Server-assigned order ID |
---
## 20. Batch Place Orders
`POST /spot/v4/batch_orders`
**Auth:** SIGNED | **Rate Limit:** 40 req/2sec per UID
> **IMPORTANT:** The body format wraps orders inside an `orderParams` array with a top-level `symbol`. Individual orders do NOT repeat the `symbol` field.
**Top-level Parameters (JSON body):**
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `symbol` | String | Yes | Trading pair, e.g., `BTC_USDT` |
| `orderParams` | Array | Yes | Array of order objects, max 10 |
| `recvWindow` | Long | No | Request timeout in ms, range 0-60000, default 5000 |
**Each order in `orderParams`:**
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `side` | String | Yes | `"buy"` or `"sell"` |
| `type` | String | Yes | `"limit"`, `"market"`, `"limit_maker"`, `"ioc"` |
| `clientOrderId` | String | No | User-defined order ID (camelCase, v4) |
| `size` | String | Conditional | Order quantity in base currency |
| `price` | String | Conditional | Order price |
| `notional` | String | Conditional | Order value in quote currency. Required for `market` buy orders |
| `stpMode` | String | No | Self-trade prevention mode |
**Example:**
```bash
TIMESTAMP=$(date +%s000)
BODY='{"symbol":"BTC_USDT","orderParams":[{"side":"buy","type":"limit","price":"60000.00","size":"0.001","clientOrderId":"batch_001"},{"side":"sell","type":"limit","price":"70000.00","size":"0.001","clientOrderId":"batch_002"}],"recvWindow":5000}'
MESSAGE="TIMESTAMP#BITMART_API_MEMO#BODY"
SIGN=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "$BITMART_API_SECRET" | awk '{print $NF}')
curl -s -X POST "https://api-cloud.bitmart.com/spot/v4/batch_orders" \
-H "User-Agent: bitmart-skills/spot/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
-H "Content-Type: application/json" \
-H "X-BM-KEY: $BITMART_API_KEY" \
-H "X-BM-SIGN: $SIGN" \
-H "X-BM-TIMESTAMP: $TIMESTAMP" \
-d "$BODY"
```
**Response:**
```json
{
"code": 1000,
"trace": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"message": "success",
"data": {
"code": 0,
"msg": "success",
"data": {
"orderIds": ["1234567892", "1234567893"]
}
}
}
```
**Response Fields:**
| Field | Description |
|-------|-------------|
| `data.code` | Batch result code (0 = success) |
| `data.msg` | Batch result message |
| `data.data.orderIds` | Array of created order ID strings |
---
## 21. Cancel Single Order
`POST /spot/v3/cancel_order`
**Auth:** SIGNED | **Rate Limit:** 40 req/2sec per UID
> **Note:** Supports cancellation by either `order_id` or `client_order_id`. They are mutually exclusive -- provide one or the other, not both. Both parameters use snake_case (v3 endpoint).
**Parameters (JSON body):**
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `symbol` | String | Yes | Trading pair, e.g., `BTC_USDT` |
| `order_id` | String | Conditional | Server-assigned order ID (mutually exclusive with `client_order_id`) |
| `client_order_id` | String | Conditional | User-defined order ID (mutually exclusive with `order_id`) |
**Example (cancel by order_id):**
```bash
TIMESTAMP=$(date +%s000)
BODY='{"symbol":"BTC_USDT","order_id":"1234567890"}'
MESSAGE="TIMESTAMP#BITMART_API_MEMO#BODY"
SIGN=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "$BITMART_API_SECRET" | awk '{print $NF}')
curl -s -X POST "https://api-cloud.bitmart.com/spot/v3/cancel_order" \
-H "User-Agent: bitmart-skills/spot/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
-H "Content-Type: application/json" \
-H "X-BM-KEY: $BITMART_API_KEY" \
-H "X-BM-SIGN: $SIGN" \
-H "X-BM-TIMESTAMP: $TIMESTAMP" \
-d "$BODY"
```
**Example (cancel by client_order_id):**
```bash
TIMESTAMP=$(date +%s000)
BODY='{"symbol":"BTC_USDT","client_order_id":"my_order_001"}'
MESSAGE="TIMESTAMP#BITMART_API_MEMO#BODY"
SIGN=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "$BITMART_API_SECRET" | awk '{print $NF}')
curl -s -X POST "https://api-cloud.bitmart.com/spot/v3/cancel_order" \
-H "User-Agent: bitmart-skills/spot/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
-H "Content-Type: application/json" \
-H "X-BM-KEY: $BITMART_API_KEY" \
-H "X-BM-SIGN: $SIGN" \
-H "X-BM-TIMESTAMP: $TIMESTAMP" \
-d "$BODY"
```
**Response:**
```json
{
"code": 1000,
"trace": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"message": "success",
"data": {
"result": true
}
}
```
**Response Fields:**
| Field | Description |
|-------|-------------|
| `data.result` | `true` if cancellation request accepted |
---
## 22. Cancel Multiple Orders
`POST /spot/v4/cancel_orders`
**Auth:** SIGNED | **Rate Limit:** 40 req/2sec per UID
**Parameters (JSON body):**
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `symbol` | String | Yes | Trading pair, e.g., `BTC_USDT` |
| `orderIds` | Array[String] | Conditional | Server-assigned order IDs, max 10 (camelCase, v4). Mutually exclusive with `clientOrderIds` |
| `clientOrderIds` | Array[String] | Conditional | User-defined order IDs, max 10 (camelCase, v4). Mutually exclusive with `orderIds` |
| `recvWindow` | Long | No | Request timeout in ms, range 0-60000 |
**Example:**
```bash
TIMESTAMP=$(date +%s000)
BODY='{"symbol":"BTC_USDT","orderIds":["1234567890","1234567891","1234567892"]}'
MESSAGE="TIMESTAMP#BITMART_API_MEMO#BODY"
SIGN=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "$BITMART_API_SECRET" | awk '{print $NF}')
curl -s -X POST "https://api-cloud.bitmart.com/spot/v4/cancel_orders" \
-H "User-Agent: bitmart-skills/spot/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
-H "Content-Type: application/json" \
-H "X-BM-KEY: $BITMART_API_KEY" \
-H "X-BM-SIGN: $SIGN" \
-H "X-BM-TIMESTAMP: $TIMESTAMP" \
-d "$BODY"
```
**Response:**
```json
{
"code": 1000,
"trace": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"message": "success",
"data": {
"successIds": ["1234567890", "1234567891"],
"failIds": ["1234567892"],
"totalCount": 3,
"successCount": 2,
"failedCount": 1
}
}
```
**Response Fields:**
| Field | Description |
|-------|-------------|
| `successIds` | Array of successfully cancelled order IDs |
| `failIds` | Array of order IDs that failed to cancel |
| `totalCount` | Total number of orders in request |
| `successCount` | Number of successfully cancelled orders |
| `failedCount` | Number of orders that failed to cancel |
---
## 23. Cancel All Open Orders
`POST /spot/v4/cancel_all`
**Auth:** SIGNED | **Rate Limit:** 1 req/3sec per UID
**Parameters (JSON body):**
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `symbol` | String | No | Trading pair. Omit to cancel across all pairs |
| `side` | String | No | Cancel only orders on one side: `"buy"` or `"sell"`. Omit to cancel both sides |
**Example:**
```bash
TIMESTAMP=$(date +%s000)
BODY='{"symbol":"BTC_USDT","side":"buy"}'
MESSAGE="TIMESTAMP#BITMART_API_MEMO#BODY"
SIGN=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "$BITMART_API_SECRET" | awk '{print $NF}')
curl -s -X POST "https://api-cloud.bitmart.com/spot/v4/cancel_all" \
-H "User-Agent: bitmart-skills/spot/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
-H "Content-Type: application/json" \
-H "X-BM-KEY: $BITMART_API_KEY" \
-H "X-BM-SIGN: $SIGN" \
-H "X-BM-TIMESTAMP: $TIMESTAMP" \
-d "$BODY"
```
**Response:**
```json
{
"code": 1000,
"trace": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"message": "success",
"data": {}
}
```
**Response Fields:**
| Field | Description |
|-------|-------------|
| `data` | Empty object on success |
---
# Order Query
> **IMPORTANT:** All v4 query endpoints use the **POST** method, NOT GET. Parameters are sent as a JSON body. The signature includes the JSON body: `{timestamp}#{memo}#{body}`.
## 24. Query Order by Order ID
`POST /spot/v4/query/order`
**Auth:** SIGNED | **Rate Limit:** 50 req/2sec per KEY
> **Method is POST.** Parameters go in the JSON body, not the query string. Signature includes the body.
**Parameters (JSON body):**
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `orderId` | String | Yes | Server-assigned order ID (camelCase, v4) |
| `queryState` | String | No | `"open"` for active orders, `"history"` for completed/cancelled. Default searches both |
| `recvWindow` | Long | No | Request timeout in ms, range 0-60000 |
**Example:**
```bash
TIMESTAMP=$(date +%s000)
BODY='{"orderId":"1234567890","queryState":"open","recvWindow":5000}'
MESSAGE="TIMESTAMP#BITMART_API_MEMO#BODY"
SIGN=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "$BITMART_API_SECRET" | awk '{print $NF}')
curl -s -X POST "https://api-cloud.bitmart.com/spot/v4/query/order" \
-H "User-Agent: bitmart-skills/spot/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
-H "Content-Type: application/json" \
-H "X-BM-KEY: $BITMART_API_KEY" \
-H "X-BM-SIGN: $SIGN" \
-H "X-BM-TIMESTAMP: $TIMESTAMP" \
-d "$BODY"
```
**Response:**
```json
{
"code": 1000,
"trace": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"message": "success",
"data": {
"orderId": "1234567890",
"clientOrderId": "my_order_001",
"symbol": "BTC_USDT",
"side": "buy",
"orderMode": "spot",
"type": "limit",
"state": "new",
"cancelSource": "",
"stpMode": "",
"price": "60000.00",
"priceAvg": "0.00",
"size": "0.00100000",
"filledSize": "0.00000000",
"notional": "60.00000000",
"filledNotional": "0.00000000",
"createTime": 1700000000000,
"updateTime": 1700000000000
}
}
```
**Response Fields:**
| Field | Description |
|-------|-------------|
| `orderId` | Server-assigned order ID |
| `clientOrderId` | User-defined order ID (empty string if not set) |
| `symbol` | Trading pair |
| `side` | `"buy"` or `"sell"` |
| `orderMode` | `"spot"` or `"iso_margin"` |
| `type` | Order type: `"limit"`, `"market"`, `"limit_maker"`, `"ioc"` |
| `state` | Order state: `"new"`, `"partially_filled"`, `"filled"`, `"canceled"`, `"partially_canceled"` |
| `cancelSource` | Reason for cancellation (empty if not cancelled) |
| `stpMode` | Self-trade prevention mode |
| `price` | Order price |
| `priceAvg` | Average fill price (**NOT** `avgPrice`) |
| `size` | Order quantity |
| `filledSize` | Filled quantity |
| `notional` | Order value in quote currency |
| `filledNotional` | Filled value in quote currency |
| `createTime` | Order creation time in milliseconds |
| `updateTime` | Last update time in milliseconds |
---
## 25. Query Order by Client Order ID
`POST /spot/v4/query/client-order`
**Auth:** SIGNED | **Rate Limit:** 50 req/2sec per KEY
> **Method is POST.** Parameters go in the JSON body, not the query string. Signature includes the body.
**Parameters (JSON body):**
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `clientOrderId` | String | Yes | User-defined order ID (camelCase, v4) |
| `queryState` | String | No | `"open"` for active orders, `"history"` for completed/cancelled |
| `recvWindow` | Long | No | Request timeout in ms, range 0-60000 |
**Example:**
```bash
TIMESTAMP=$(date +%s000)
BODY='{"clientOrderId":"my_order_001","queryState":"open","recvWindow":5000}'
MESSAGE="TIMESTAMP#BITMART_API_MEMO#BODY"
SIGN=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "$BITMART_API_SECRET" | awk '{print $NF}')
curl -s -X POST "https://api-cloud.bitmart.com/spot/v4/query/client-order" \
-H "User-Agent: bitmart-skills/spot/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
-H "Content-Type: application/json" \
-H "X-BM-KEY: $BITMART_API_KEY" \
-H "X-BM-SIGN: $SIGN" \
-H "X-BM-TIMESTAMP: $TIMESTAMP" \
-d "$BODY"
```
**Response:**
Same format as [endpoint 24](#24-query-order-by-order-id).
```json
{
"code": 1000,
"trace": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"message": "success",
"data": {
"orderId": "1234567890",
"clientOrderId": "my_order_001",
"symbol": "BTC_USDT",
"side": "buy",
"orderMode": "spot",
"type": "limit",
"state": "new",
"cancelSource": "",
"stpMode": "",
"price": "60000.00",
"priceAvg": "0.00",
"size": "0.00100000",
"filledSize": "0.00000000",
"notional": "60.00000000",
"filledNotional": "0.00000000",
"createTime": 1700000000000,
"updateTime": 1700000000000
}
}
```
---
## 26. All Open Orders
`POST /spot/v4/query/open-orders`
**Auth:** SIGNED | **Rate Limit:** 12 req/2sec per KEY
> **Method is POST.** Parameters go in the JSON body, not the query string. Signature includes the body.
**Parameters (JSON body):**
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `symbol` | String | No | Filter by trading pair |
| `orderMode` | String | No | `"spot"` or `"iso_margin"` |
| `startTime` | Long | No | Start time in milliseconds (camelCase, v4) |
| `endTime` | Long | No | End time in milliseconds (camelCase, v4) |
| `limit` | Integer | No | Number of results, range 1-200, default 200 |
| `recvWindow` | Long | No | Request timeout in ms, range 0-60000 |
**Example:**
```bash
TIMESTAMP=$(date +%s000)
BODY='{"symbol":"BTC_USDT","orderMode":"spot","limit":50,"recvWindow":5000}'
MESSAGE="TIMESTAMP#BITMART_API_MEMO#BODY"
SIGN=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "$BITMART_API_SECRET" | awk '{print $NF}')
curl -s -X POST "https://api-cloud.bitmart.com/spot/v4/query/open-orders" \
-H "User-Agent: bitmart-skills/spot/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
-H "Content-Type: application/json" \
-H "X-BM-KEY: $BITMART_API_KEY" \
-H "X-BM-SIGN: $SIGN" \
-H "X-BM-TIMESTAMP: $TIMESTAMP" \
-d "$BODY"
```
**Response:**
```json
{
"code": 1000,
"trace": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"message": "success",
"data": [
{
"orderId": "1234567890",
"clientOrderId": "my_order_001",
"symbol": "BTC_USDT",
"side": "buy",
"orderMode": "spot",
"type": "limit",
"state": "new",
"cancelSource": "",
"stpMode": "",
"price": "60000.00",
"priceAvg": "0.00",
"size": "0.00100000",
"filledSize": "0.00000000",
"notional": "60.00000000",
"filledNotional": "0.00000000",
"createTime": 1700000000000,
"updateTime": 1700000000000
}
]
}
```
**Response Fields:**
Array of order objects. Each order has the same fields as [endpoint 24](#24-query-order-by-order-id).
---
## 27. Historical Orders
`POST /spot/v4/query/history-orders`
**Auth:** SIGNED | **Rate Limit:** 12 req/2sec per KEY
> **Method is POST.** Parameters go in the JSON body, not the query string. Signature includes the body.
**Parameters (JSON body):**
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `symbol` | String | No | Filter by trading pair |
| `orderMode` | String | No | `"spot"` or `"iso_margin"` |
| `startTime` | Long | No | Start time in milliseconds (camelCase, v4) |
| `endTime` | Long | No | End time in milliseconds (camelCase, v4) |
| `limit` | Integer | No | Number of results, range 1-200, default 200 |
| `recvWindow` | Long | No | Request timeout in ms, range 0-60000 |
**Example:**
```bash
TIMESTAMP=$(date +%s000)
BODY='{"symbol":"BTC_USDT","orderMode":"spot","limit":50,"recvWindow":5000}'
MESSAGE="TIMESTAMP#BITMART_API_MEMO#BODY"
SIGN=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "$BITMART_API_SECRET" | awk '{print $NF}')
curl -s -X POST "https://api-cloud.bitmart.com/spot/v4/query/history-orders" \
-H "User-Agent: bitmart-skills/spot/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
-H "Content-Type: application/json" \
-H "X-BM-KEY: $BITMART_API_KEY" \
-H "X-BM-SIGN: $SIGN" \
-H "X-BM-TIMESTAMP: $TIMESTAMP" \
-d "$BODY"
```
**Response:**
```json
{
"code": 1000,
"trace": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"message": "success",
"data": [
{
"orderId": "1234567880",
"clientOrderId": "",
"symbol": "BTC_USDT",
"side": "sell",
"orderMode": "spot",
"type": "limit",
"state": "filled",
"cancelSource": "",
"stpMode": "",
"price": "67000.00",
"priceAvg": "67000.00",
"size": "0.00100000",
"filledSize": "0.00100000",
"notional": "67.00000000",
"filledNotional": "67.00000000",
"createTime": 1699990000000,
"updateTime": 1699990001000
}
]
}
```
**Response Fields:**
Array of order objects. Each order has the same fields as [endpoint 24](#24-query-order-by-order-id).
---
## 28. Account Trade History
`POST /spot/v4/query/trades`
**Auth:** SIGNED | **Rate Limit:** 12 req/2sec per KEY
> **Method is POST.** Parameters go in the JSON body, not the query string. Signature includes the body.
**Parameters (JSON body):**
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `symbol` | String | No | Filter by trading pair |
| `orderMode` | String | No | `"spot"` or `"iso_margin"` |
| `startTime` | Long | No | Start time in milliseconds (camelCase, v4) |
| `endTime` | Long | No | End time in milliseconds (camelCase, v4) |
| `limit` | Integer | No | Number of results, range 1-200, default 200 |
| `recvWindow` | Long | No | Request timeout in ms, range 0-60000 |
**Example:**
```bash
TIMESTAMP=$(date +%s000)
BODY='{"symbol":"BTC_USDT","orderMode":"spot","limit":20,"recvWindow":5000}'
MESSAGE="TIMESTAMP#BITMART_API_MEMO#BODY"
SIGN=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "$BITMART_API_SECRET" | awk '{print $NF}')
curl -s -X POST "https://api-cloud.bitmart.com/spot/v4/query/trades" \
-H "User-Agent: bitmart-skills/spot/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
-H "Content-Type: application/json" \
-H "X-BM-KEY: $BITMART_API_KEY" \
-H "X-BM-SIGN: $SIGN" \
-H "X-BM-TIMESTAMP: $TIMESTAMP" \
-d "$BODY"
```
**Response:**
```json
{
"code": 1000,
"trace": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"message": "success",
"data": [
{
"tradeId": "9876543210",
"orderId": "1234567880",
"clientOrderId": "",
"symbol": "BTC_USDT",
"side": "sell",
"orderMode": "spot",
"type": "limit",
"stpMode": "",
"price": "67000.00",
"size": "0.00100000",
"notional": "67.00000000",
"fee": "0.06700000",
"feeCoinName": "USDT",
"tradeRole": "taker",
"createTime": 1699990001000,
"updateTime": 1699990001000
}
]
}
```
**Response Fields:**
| Field | Description |
|-------|-------------|
| `tradeId` | Unique trade ID |
| `orderId` | Server-assigned order ID |
| `clientOrderId` | User-defined order ID (empty string if not set) |
| `symbol` | Trading pair |
| `side` | `"buy"` or `"sell"` |
| `orderMode` | `"spot"` or `"iso_margin"` |
| `type` | Order type |
| `stpMode` | Self-trade prevention mode |
| `price` | Trade execution price |
| `size` | Trade size in base currency |
| `notional` | Trade value in quote currency |
| `fee` | Trading fee |
| `feeCoinName` | Currency of fee |
| `tradeRole` | `"taker"` or `"maker"` |
| `createTime` | Trade time in milliseconds |
| `updateTime` | Last update time in milliseconds |
---
## 29. Trades for Specific Order
`POST /spot/v4/query/order-trades`
**Auth:** SIGNED | **Rate Limit:** 12 req/2sec per KEY
> **Method is POST.** Parameters go in the JSON body, not the query string. Signature includes the body.
**Parameters (JSON body):**
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `orderId` | String | Yes | Server-assigned order ID (camelCase, v4) |
| `recvWindow` | Long | No | Request timeout in ms, range 0-60000 |
**Example:**
```bash
TIMESTAMP=$(date +%s000)
BODY='{"orderId":"1234567880","recvWindow":5000}'
MESSAGE="TIMESTAMP#BITMART_API_MEMO#BODY"
SIGN=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "$BITMART_API_SECRET" | awk '{print $NF}')
curl -s -X POST "https://api-cloud.bitmart.com/spot/v4/query/order-trades" \
-H "User-Agent: bitmart-skills/spot/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
-H "Content-Type: application/json" \
-H "X-BM-KEY: $BITMART_API_KEY" \
-H "X-BM-SIGN: $SIGN" \
-H "X-BM-TIMESTAMP: $TIMESTAMP" \
-d "$BODY"
```
**Response:**
Same format as [endpoint 28](#28-account-trade-history).
```json
{
"code": 1000,
"trace": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"message": "success",
"data": [
{
"tradeId": "9876543210",
"orderId": "1234567880",
"clientOrderId": "",
"symbol": "BTC_USDT",
"side": "sell",
"orderMode": "spot",
"type": "limit",
"stpMode": "",
"price": "67000.00",
"size": "0.00100000",
"notional": "67.00000000",
"fee": "0.06700000",
"feeCoinName": "USDT",
"tradeRole": "taker",
"createTime": 1699990001000,
"updateTime": 1699990001000
}
]
}
```
---
# Margin Loan
## 30. Margin Borrow (Isolated)
`POST /spot/v1/margin/isolated/borrow`
**Auth:** SIGNED | **Rate Limit:** 2 req/2sec per KEY
**SDK:** `MarginBorrowIsolated(symbol, currency, amount)`
**Parameters (JSON body):**
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `symbol` | String | Yes | Trading pair, e.g., `BTC_USDT` |
| `currency` | String | Yes | Currency to borrow, e.g., `USDT` |
| `amount` | String | Yes | Amount to borrow |
**Example:**
```bash
TIMESTAMP=$(date +%s000)
BODY='{"symbol":"BTC_USDT","currency":"USDT","amount":"10000"}'
MESSAGE="TIMESTAMP#BITMART_API_MEMO#BODY"
SIGN=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "$BITMART_API_SECRET" | awk '{print $NF}')
curl -s -X POST "https://api-cloud.bitmart.com/spot/v1/margin/isolated/borrow" \
-H "User-Agent: bitmart-skills/spot/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
-H "Content-Type: application/json" \
-H "X-BM-KEY: $BITMART_API_KEY" \
-H "X-BM-SIGN: $SIGN" \
-H "X-BM-TIMESTAMP: $TIMESTAMP" \
-d "$BODY"
```
**Response:**
```json
{
"code": 1000,
"trace": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"message": "success",
"data": {
"borrow_id": "123456789"
}
}
```
**Response Fields:**
| Field | Description |
|-------|-------------|
| `borrow_id` | Unique borrow record ID |
---
## 31. Margin Repay (Isolated)
`POST /spot/v1/margin/isolated/repay`
**Auth:** SIGNED | **Rate Limit:** 2 req/2sec per KEY
**SDK:** `MarginRepayIsolated(symbol, currency, amount)`
**Parameters (JSON body):**
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `symbol` | String | Yes | Trading pair, e.g., `BTC_USDT` |
| `currency` | String | Yes | Currency to repay, e.g., `USDT` |
| `amount` | String | Yes | Amount to repay |
**Example:**
```bash
TIMESTAMP=$(date +%s000)
BODY='{"symbol":"BTC_USDT","currency":"USDT","amount":"10001"}'
MESSAGE="TIMESTAMP#BITMART_API_MEMO#BODY"
SIGN=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "$BITMART_API_SECRET" | awk '{print $NF}')
curl -s -X POST "https://api-cloud.bitmart.com/spot/v1/margin/isolated/repay" \
-H "User-Agent: bitmart-skills/spot/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
-H "Content-Type: application/json" \
-H "X-BM-KEY: $BITMART_API_KEY" \
-H "X-BM-SIGN: $SIGN" \
-H "X-BM-TIMESTAMP: $TIMESTAMP" \
-d "$BODY"
```
**Response:**
```json
{
"code": 1000,
"trace": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"message": "success",
"data": {
"repay_id": "987654321"
}
}
```
**Response Fields:**
| Field | Description |
|-------|-------------|
| `repay_id` | Unique repayment record ID |
---
## 32. Margin Asset Transfer
`POST /spot/v1/margin/isolated/transfer`
**Auth:** SIGNED | **Rate Limit:** 2 req/2sec per KEY
**SDK:** `MarginAssetTransfer(transfer MarginAssetTransfer)` with fields: symbol, currency, amount, side
**Parameters (JSON body):**
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `symbol` | String | Yes | Trading pair, e.g., `BTC_USDT` |
| `currency` | String | Yes | Currency to transfer, e.g., `USDT` |
| `amount` | String | Yes | Amount to transfer |
| `side` | String | Yes | `"in"` = spot to margin, `"out"` = margin to spot |
**Example:**
```bash
TIMESTAMP=$(date +%s000)
BODY='{"symbol":"BTC_USDT","currency":"USDT","amount":"5000","side":"in"}'
MESSAGE="TIMESTAMP#BITMART_API_MEMO#BODY"
SIGN=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "$BITMART_API_SECRET" | awk '{print $NF}')
curl -s -X POST "https://api-cloud.bitmart.com/spot/v1/margin/isolated/transfer" \
-H "User-Agent: bitmart-skills/spot/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
-H "Content-Type: application/json" \
-H "X-BM-KEY: $BITMART_API_KEY" \
-H "X-BM-SIGN: $SIGN" \
-H "X-BM-TIMESTAMP: $TIMESTAMP" \
-d "$BODY"
```
**Response:**
```json
{
"code": 1000,
"trace": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"message": "success",
"data": {
"transfer_id": "456789123"
}
}
```
**Response Fields:**
| Field | Description |
|-------|-------------|
| `transfer_id` | Unique transfer record ID |
---
# Quick Reference: Error Codes
Spot business error codes are endpoint/version specific. Do **not** assume the same numeric code has the same meaning across all spot flows. The table below is limited to authentication / transport issues and a small set of common current order-query errors that are useful across this guide. For margin borrow / repay / transfer errors, rely on the endpoint section and the official business-code table rather than a global mapping.
| Code | Meaning |
|------|---------|
| `1000` | Success |
| `30002` | Header X-BM-KEY not found (authentication) |
| `30005` | Header X-BM-SIGN is wrong (authentication) |
| `30006` | Header X-BM-TIMESTAMP is wrong / missing (authentication) |
| `30007` | Timestamp/recvWindow validation failed (authentication) |
| `30010` | IP is forbidden (authentication) |
| `30011` | API key expired (authentication) |
| `30012` | API key has no required permission (authentication) |
| `30013` | Request too many requests |
| `50000` | Bad Request |
| `50001` | Symbol not found |
| `50002` | From Or To format error |
| `50004` | Kline size over 500 |
| `50005` | Order Id not found / query returned no data |
| `50006` | Minimum size is %s |
| `50007` | Maximum size is %s |
| `50008` | Minimum price is %s |
| `50021` | Market buy No size required / param error |
| `51011` | Limit order quantity * price below minimum transaction amount |
| `51012` | Market buy amount below minimum transaction amount |
| `52000` | Unsupported OrderMode Type |
**Margin-only / endpoint-specific business-code examples (non-global):**
| Code | Meaning | Notes |
|------|---------|-------|
| `51003` | Account Limit | Use in margin borrow / repay / transfer context; do not use as a generic v4 order-query error |
| `51006` | Exceeds the amount to be repaid | Isolated margin repay context |
| `51007` | order_mode not found | Official business-code-table entry; current invalid `orderMode` requests on v4 query endpoints return `52000` instead |
---
# System Endpoints
## 33. Get System Time
`GET /system/time`
**Auth:** NONE | **Rate Limit:** 10 req/sec per IP
No parameters required.
```bash
curl -s -H "User-Agent: bitmart-skills/spot/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
'https://api-cloud.bitmart.com/system/time'
```
**Response:**
```json
{
"code": 1000,
"trace": "886fb6ae-456b-4654-b4e0-d681ac05cea1",
"message": "OK",
"data": {
"server_time": 1527777538000
}
}
```
| Field | Type | Description |
|-------|------|-------------|
| server_time | Long | Current server time (UTC milliseconds) |
---
## 34. Get System Service Status
`GET /system/service`
**Auth:** NONE | **Rate Limit:** 10 req/sec per IP
No parameters required.
```bash
curl -s -H "User-Agent: bitmart-skills/spot/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
'https://api-cloud.bitmart.com/system/service'
```
**Response:**
```json
{
"code": 1000,
"trace": "886fb6ae-456b-4654-b4e0-d681ac05cea1",
"message": "OK",
"data": {
"service": [
{
"title": "Spot API Stop",
"service_type": "spot",
"status": "2",
"start_time": 1527777538000,
"end_time": 1527777538000
}
]
}
}
```
| Field | Type | Description |
|-------|------|-------------|
| title | String | Maintenance description title |
| service_type | String | `spot` / `contract` / `account` |
| status | Long | `0`=Waiting `1`=Working `2`=Completed |
| start_time | Long | Maintenance start time (UTC milliseconds) |
| end_time | Long | Maintenance end time (UTC milliseconds) |
---
# Quick Reference: Rate Limits Summary
| Category | Typical Limit | Per |
|----------|--------------|-----|
| Public market data | 10-15 req/2sec | IP |
| Account queries (KEYED) | 12 req/2sec | KEY |
| Trade fee / User fee | 2 req/2sec | KEY |
| Order placement/cancellation | 40 req/2sec | UID |
| Cancel all | 1 req/3sec | UID |
| Margin order placement | 20 req/1sec | UID |
| v4 single order query | 50 req/2sec | KEY |
| v4 list/history queries | 12 req/2sec | KEY |
| v4 order-trades | 12 req/2sec | KEY |
| Margin loan operations | 2 req/2sec | KEY |
| Borrow/repay records | 150 req/2sec | KEY |
| System time / service status | 10 req/sec | IP |
FILE:references/bitmart-spot-authentication.md
# BitMart Spot API Authentication Guide
## 1. Getting API Credentials
### Step-by-Step
1. Log in to [BitMart](https://www.bitmart.com)
2. Navigate to **Account** → **API Management** (or go directly to [https://www.bitmart.com/api-config/en](https://www.bitmart.com/api-config/en))
3. Click **Create API Key**
4. Set a **label** for your API key (e.g., "AI Trading Bot")
5. Enter your **memo** — this is a passphrase you choose (required for signature generation)
6. Set **permissions**:
- **Read-Only** — Required for balance and order queries
- **Spot Trade** — Required for placing and canceling orders
- Do NOT enable **Withdraw** permission unless absolutely necessary
7. (Recommended) Set **IP whitelist** to restrict API access to your server's IP
8. Complete 2FA verification
9. Save your credentials securely:
- **API Key** (also called Access Key)
- **Secret Key**
- **Memo** (the passphrase you entered)
**Important:** The Secret Key is shown only once. Save it immediately. If lost, you must delete and recreate the API key.
---
## 2. Environment Variables Setup
Add the following to your `.env` file:
```
BITMART_API_KEY=your-api-key
BITMART_API_SECRET=your-secret-key
BITMART_API_MEMO=your-memo
```
**Security:** Never display the full secret key or memo.
---
## 3. Signature Generation
BitMart uses HMAC-SHA256 signatures for authenticated (SIGNED) API calls.
### Signature Formula
```
message = "{timestamp}#{memo}#{body}"
signature = HMAC-SHA256(secret_key, message) → hex string
```
Where:
- `timestamp` — Current UTC time in milliseconds (e.g., `1709971200000`)
- `memo` — Your API memo string
- `body` — JSON body string for POST requests, or empty string `""` for GET requests
- The `#` character is a literal separator
### Example: POST Request Signature
For a limit buy order:
```bash
# Step 1: Set timestamp
TIMESTAMP=$(date +%s000)
# Example: 1709971200000
# Step 2: Define JSON body
BODY='{"symbol":"BTC_USDT","side":"buy","type":"limit","size":"0.001","price":"60000"}'
# Step 3: Build message string
# Format: {timestamp}#{memo}#{body}
MESSAGE="TIMESTAMP#BITMART_API_MEMO#BODY"
# Example: 1709971200000#my_memo#{"symbol":"BTC_USDT","side":"buy","type":"limit","size":"0.001","price":"60000"}
# Step 4: Generate HMAC-SHA256 signature
SIGN=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "$BITMART_API_SECRET" | awk '{print $NF}')
# Result: a hex string like "a1b2c3d4e5f6..."
```
### Example: GET Request Signature
For GET requests, the body portion is an empty string:
```bash
TIMESTAMP=$(date +%s000)
# For GET: body is empty, so message is "{timestamp}#{memo}#"
MESSAGE="TIMESTAMP#BITMART_API_MEMO#"
SIGN=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "$BITMART_API_SECRET" | awk '{print $NF}')
```
### Required Headers
| Header | Value | When |
|--------|-------|------|
| `X-BM-KEY` | Your API key | KEYED and SIGNED |
| `X-BM-SIGN` | Hex HMAC-SHA256 signature | SIGNED only |
| `X-BM-TIMESTAMP` | UTC milliseconds timestamp | SIGNED only |
| `Content-Type` | `application/json` | POST requests |
---
## 5. Verification
Test your credentials with a simple balance query.
### KEYED Verification (simplest)
```bash
curl -s -H "X-BM-KEY: $BITMART_API_KEY" \
-H "User-Agent: bitmart-skills/spot/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
'https://api-cloud.bitmart.com/account/v1/wallet' | python3 -m json.tool
```
Expected successful response:
```json
{
"code": 1000,
"message": "OK",
"trace": "...",
"data": {
"wallet": [...]
}
}
```
### SIGNED Verification (full auth test)
```bash
TIMESTAMP=$(date +%s000)
BODY='{}'
SIGN=$(echo -n "TIMESTAMP#BITMART_API_MEMO#BODY" | openssl dgst -sha256 -hmac "$BITMART_API_SECRET" | awk '{print $NF}')
curl -s -X POST 'https://api-cloud.bitmart.com/spot/v4/query/open-orders' \
-H "User-Agent: bitmart-skills/spot/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
-H "Content-Type: application/json" \
-H "X-BM-KEY: $BITMART_API_KEY" \
-H "X-BM-SIGN: $SIGN" \
-H "X-BM-TIMESTAMP: $TIMESTAMP" \
-d "$BODY" | python3 -m json.tool
```
If `code` is `1000`, your credentials are working correctly.
---
## 6. Troubleshooting
| Error Code | Message | Cause | Fix |
|------------|---------|-------|-----|
| 30002 | `X-BM-KEY not found` | API key header missing or empty | Verify `BITMART_API_KEY` env var is set. Check for typos in the header name `X-BM-KEY`. |
| 30005 | `X-BM-SIGN is wrong` | Signature mismatch | 1) Verify memo matches exactly what you set on BitMart. 2) Ensure timestamp is in milliseconds. 3) For POST: body JSON must match exactly (no extra spaces). 4) For GET: body must be empty string. |
| 30006 | `X-BM-TIMESTAMP is wrong` | `X-BM-TIMESTAMP` header missing or empty | Ensure `X-BM-TIMESTAMP` is present on signed requests and uses Unix milliseconds (for example `date +%s000`). |
| 30007 | `Timestamp/recvWindow validation failed` | Timestamp is outside the allowed `recvWindow`, or `recvWindow` is invalid | Sync your system clock, send `X-BM-TIMESTAMP` in Unix milliseconds, and for v4 endpoints ensure `recvWindow` is a Long in `(0,60000]` (default `5000`, max `60000`). Treat `recvWindow` as the effective validity window for current signed v4 requests. |
| 30010 | `IP forbidden` | Request IP not in whitelist | Add your current IP to the API key whitelist on BitMart, or remove IP restriction for testing. |
| 30011 | `No permission` | API key lacks required permission | Enable the required permission (Read-Only, Spot Trade) in API Management. |
### Common Pitfalls
1. **Memo mismatch**: The memo in your signature must match exactly what you entered when creating the API key. It is case-sensitive.
2. **Timestamp drift / recvWindow mismatch**: For signed v4 requests, BitMart validates `X-BM-TIMESTAMP` against `recvWindow` (default `5000ms`, maximum `60000ms`). In practice, use `recvWindow` as the request-validity source of truth. Keep your system clock synchronized, and increase `recvWindow` only when necessary:
```bash
# macOS
sudo sntp -sS time.apple.com
# Linux
sudo ntpdate pool.ntp.org
```
3. **Body formatting**: For POST requests, the JSON body used in the signature must be exactly the same string sent in the request. Do not add whitespace or reorder keys between signature generation and the actual request.
4. **Empty body for GET**: When signing GET requests, the body portion of the message is an empty string (not `"{}"`, not `null` — just `""`). The message format is: `{timestamp}#{memo}#`
5. **URL encoding**: Query parameters in GET requests are NOT included in the signature. Only the body is signed.
FILE:references/bitmart-spot-scenarios.md
# BitMart Spot Trading Scenarios
> **Credentials:** Read from `.env` — `BITMART_API_KEY`, `BITMART_API_SECRET`, `BITMART_API_MEMO`.
## Scenario 1: Market Buy
**User prompt:** "Buy 100 USDT worth of BTC on BitMart"
### Step 1: Get current ticker price
```bash
curl -s -H "User-Agent: bitmart-skills/spot/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
'https://api-cloud.bitmart.com/spot/quotation/v3/ticker?symbol=BTC_USDT'
```
Expected response:
```json
{
"code": 1000,
"data": {
"symbol": "BTC_USDT",
"last": "67123.45",
"ask_px": "67125.00",
"bid_px": "67120.00"
}
}
```
### Step 2: Check available balance
```bash
curl -s -H "X-BM-KEY: $BITMART_API_KEY" \
-H "User-Agent: bitmart-skills/spot/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
'https://api-cloud.bitmart.com/spot/v1/wallet'
```
Verify the user has at least 100 USDT available. If insufficient, inform the user and STOP.
### Step 3: Present order summary and ask for confirmation
Display to user:
```
Order Summary:
Action: Market Buy BTC_USDT
Amount: 100 USDT (notional)
Estimated Price: ~67,125.00
Estimated BTC: ~0.00149
Please type CONFIRM to proceed.
```
### Step 4: Submit market buy order (after user confirms)
```bash
TIMESTAMP=$(date +%s000)
BODY='{"symbol":"BTC_USDT","side":"buy","type":"market","notional":"100"}'
SIGN=$(echo -n "TIMESTAMP#BITMART_API_MEMO#BODY" | openssl dgst -sha256 -hmac "$BITMART_API_SECRET" | awk '{print $NF}')
curl -s -X POST 'https://api-cloud.bitmart.com/spot/v2/submit_order' \
-H "User-Agent: bitmart-skills/spot/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
-H "Content-Type: application/json" \
-H "X-BM-KEY: $BITMART_API_KEY" \
-H "X-BM-SIGN: $SIGN" \
-H "X-BM-TIMESTAMP: $TIMESTAMP" \
-d "$BODY"
```
Expected response:
```json
{
"code": 1000,
"data": {
"order_id": "12345678901234567"
}
}
```
### Step 5: Verify fill
```bash
TIMESTAMP=$(date +%s000)
BODY='{"orderId":"12345678901234567"}'
SIGN=$(echo -n "TIMESTAMP#BITMART_API_MEMO#BODY" | openssl dgst -sha256 -hmac "$BITMART_API_SECRET" | awk '{print $NF}')
curl -s -X POST 'https://api-cloud.bitmart.com/spot/v4/query/order' \
-H "User-Agent: bitmart-skills/spot/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
-H "Content-Type: application/json" \
-H "X-BM-KEY: $BITMART_API_KEY" \
-H "X-BM-SIGN: $SIGN" \
-H "X-BM-TIMESTAMP: $TIMESTAMP" \
-d "$BODY"
```
Verify `state` is `"filled"`. Report to user: filled size, average price, and total cost.
### Error Handling
- If `code != 1000` on submit: report error message to user, do not retry automatically.
- If `state` is `"failed"`: report failure reason.
- If balance is insufficient: inform user before attempting order.
---
## Scenario 2: Limit Sell
**User prompt:** "Sell 0.5 ETH at 4000 USDT on BitMart"
### Step 1: Get current price to validate limit price
```bash
curl -s -H "User-Agent: bitmart-skills/spot/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
'https://api-cloud.bitmart.com/spot/quotation/v3/ticker?symbol=ETH_USDT'
```
Check that the limit price (4000) is reasonable relative to the current market price. Warn the user if it deviates significantly.
### Step 2: Check ETH balance
```bash
curl -s -H "X-BM-KEY: $BITMART_API_KEY" \
-H "User-Agent: bitmart-skills/spot/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
'https://api-cloud.bitmart.com/spot/v1/wallet'
```
Verify the user has at least 0.5 ETH available.
### Step 3: Present order summary and ask for confirmation
```
Order Summary:
Action: Limit Sell ETH_USDT
Size: 0.5 ETH
Price: 4,000.00 USDT
Total: ~2,000.00 USDT (if fully filled)
Please type CONFIRM to proceed.
```
### Step 4: Submit limit sell order
```bash
TIMESTAMP=$(date +%s000)
BODY='{"symbol":"ETH_USDT","side":"sell","type":"limit","size":"0.5","price":"4000"}'
SIGN=$(echo -n "TIMESTAMP#BITMART_API_MEMO#BODY" | openssl dgst -sha256 -hmac "$BITMART_API_SECRET" | awk '{print $NF}')
curl -s -X POST 'https://api-cloud.bitmart.com/spot/v2/submit_order' \
-H "User-Agent: bitmart-skills/spot/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
-H "Content-Type: application/json" \
-H "X-BM-KEY: $BITMART_API_KEY" \
-H "X-BM-SIGN: $SIGN" \
-H "X-BM-TIMESTAMP: $TIMESTAMP" \
-d "$BODY"
```
### Step 5: Monitor order status
```bash
TIMESTAMP=$(date +%s000)
BODY='{"orderId":"ORDER_ID"}'
SIGN=$(echo -n "TIMESTAMP#BITMART_API_MEMO#BODY" | openssl dgst -sha256 -hmac "$BITMART_API_SECRET" | awk '{print $NF}')
curl -s -X POST 'https://api-cloud.bitmart.com/spot/v4/query/order' \
-H "User-Agent: bitmart-skills/spot/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
-H "Content-Type: application/json" \
-H "X-BM-KEY: $BITMART_API_KEY" \
-H "X-BM-SIGN: $SIGN" \
-H "X-BM-TIMESTAMP: $TIMESTAMP" \
-d "$BODY"
```
Report order status: `new` (pending), `partially_filled` (partially matched), or `filled` (complete).
### Error Handling
- `40044`: Invalid order size — check `GET /spot/v1/symbols/details` for min size.
- Price too far from market: warn user the order may not fill.
- Insufficient balance: inform user before submitting.
---
## Scenario 3: Batch Orders
**User prompt:** "Place buy orders for BTC at 60000, 59000, and 58000, each 0.001 BTC"
### Step 1: Check balance
Verify the user has at least `0.001 * 60000 + 0.001 * 59000 + 0.001 * 58000 = 177 USDT` available.
### Step 2: Present order summary
```
Batch Order Summary:
1. Limit Buy 0.001 BTC @ 60,000 USDT = 60.00 USDT
2. Limit Buy 0.001 BTC @ 59,000 USDT = 59.00 USDT
3. Limit Buy 0.001 BTC @ 58,000 USDT = 58.00 USDT
Total: 177.00 USDT
Please type CONFIRM to proceed.
```
### Step 3: Submit batch orders
```bash
TIMESTAMP=$(date +%s000)
BODY='{"symbol":"BTC_USDT","orderParams":[{"side":"buy","type":"limit","size":"0.001","price":"60000"},{"side":"buy","type":"limit","size":"0.001","price":"59000"},{"side":"buy","type":"limit","size":"0.001","price":"58000"}]}'
SIGN=$(echo -n "TIMESTAMP#BITMART_API_MEMO#BODY" | openssl dgst -sha256 -hmac "$BITMART_API_SECRET" | awk '{print $NF}')
curl -s -X POST 'https://api-cloud.bitmart.com/spot/v4/batch_orders' \
-H "User-Agent: bitmart-skills/spot/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
-H "Content-Type: application/json" \
-H "X-BM-KEY: $BITMART_API_KEY" \
-H "X-BM-SIGN: $SIGN" \
-H "X-BM-TIMESTAMP: $TIMESTAMP" \
-d "$BODY"
```
### Step 4: Query each order
For each returned `order_id`, query its status:
```bash
TIMESTAMP=$(date +%s000)
BODY='{"orderId":"ORDER_ID"}'
SIGN=$(echo -n "TIMESTAMP#BITMART_API_MEMO#BODY" | openssl dgst -sha256 -hmac "$BITMART_API_SECRET" | awk '{print $NF}')
curl -s -X POST 'https://api-cloud.bitmart.com/spot/v4/query/order' \
-H "User-Agent: bitmart-skills/spot/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
-H "Content-Type: application/json" \
-H "X-BM-KEY: $BITMART_API_KEY" \
-H "X-BM-SIGN: $SIGN" \
-H "X-BM-TIMESTAMP: $TIMESTAMP" \
-d "$BODY"
```
Report the status of all three orders.
### Error Handling
- If some orders in the batch fail while others succeed, report each individually.
- Partial failures do not affect successful orders.
---
## Scenario 4: Cancel and Replace
**User prompt:** "Change my BTC limit buy from 60000 to 59500"
### Step 1: Query open orders to find the target order
```bash
TIMESTAMP=$(date +%s000)
BODY='{"symbol":"BTC_USDT"}'
SIGN=$(echo -n "TIMESTAMP#BITMART_API_MEMO#BODY" | openssl dgst -sha256 -hmac "$BITMART_API_SECRET" | awk '{print $NF}')
curl -s -X POST 'https://api-cloud.bitmart.com/spot/v4/query/open-orders' \
-H "User-Agent: bitmart-skills/spot/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
-H "Content-Type: application/json" \
-H "X-BM-KEY: $BITMART_API_KEY" \
-H "X-BM-SIGN: $SIGN" \
-H "X-BM-TIMESTAMP: $TIMESTAMP" \
-d "$BODY"
```
Find the order with `price: "60000"` and `side: "buy"`. If multiple matches, ask the user to specify.
### Step 2: Cancel the existing order
```bash
TIMESTAMP=$(date +%s000)
BODY='{"symbol":"BTC_USDT","order_id":"FOUND_ORDER_ID"}'
SIGN=$(echo -n "TIMESTAMP#BITMART_API_MEMO#BODY" | openssl dgst -sha256 -hmac "$BITMART_API_SECRET" | awk '{print $NF}')
curl -s -X POST 'https://api-cloud.bitmart.com/spot/v3/cancel_order' \
-H "User-Agent: bitmart-skills/spot/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
-H "Content-Type: application/json" \
-H "X-BM-KEY: $BITMART_API_KEY" \
-H "X-BM-SIGN: $SIGN" \
-H "X-BM-TIMESTAMP: $TIMESTAMP" \
-d "$BODY"
```
### Step 3: Submit new order at updated price
Present the replacement order for confirmation:
```
Replace Order:
Cancel: Buy 0.001 BTC @ 60,000 (order: FOUND_ORDER_ID)
New: Buy 0.001 BTC @ 59,500
Please type CONFIRM to proceed.
```
After confirmation:
```bash
TIMESTAMP=$(date +%s000)
BODY='{"symbol":"BTC_USDT","side":"buy","type":"limit","size":"0.001","price":"59500"}'
SIGN=$(echo -n "TIMESTAMP#BITMART_API_MEMO#BODY" | openssl dgst -sha256 -hmac "$BITMART_API_SECRET" | awk '{print $NF}')
curl -s -X POST 'https://api-cloud.bitmart.com/spot/v2/submit_order' \
-H "User-Agent: bitmart-skills/spot/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
-H "Content-Type: application/json" \
-H "X-BM-KEY: $BITMART_API_KEY" \
-H "X-BM-SIGN: $SIGN" \
-H "X-BM-TIMESTAMP: $TIMESTAMP" \
-d "$BODY"
```
### Step 4: Verify new order
Query the new order to confirm it was placed successfully.
### Error Handling
- If cancel fails (order already filled): inform user the order has been filled and cannot be modified.
- If the original order was partially filled: report the filled portion and place the new order for the remaining size.
- Always cancel before placing the new order to avoid double exposure.
---
## Scenario 5: Portfolio Check
**User prompt:** "Show me my BitMart spot balance"
### Step 1: Get all balances with USD valuation
```bash
curl -s -H "X-BM-KEY: $BITMART_API_KEY" \
-H "User-Agent: bitmart-skills/spot/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
'https://api-cloud.bitmart.com/account/v1/wallet?needUsdValuation=true'
```
### Step 2: Filter and format
From the response, filter out currencies with zero balances (`available == "0"` and `frozen == "0"`).
### Step 3: Present portfolio summary
```
BitMart Spot Portfolio:
┌──────────┬───────────────┬────────────┬────────────────┐
│ Currency │ Available │ Frozen │ USD Value │
├──────────┼───────────────┼────────────┼────────────────┤
│ BTC │ 1.23456789 │ 0.10000000 │ $82,934.56 │
│ ETH │ 15.50000000 │ 0.00000000 │ $53,580.90 │
│ USDT │ 50,000.00 │ 1,000.00 │ $50,000.00 │
└──────────┴───────────────┴────────────┴────────────────┘
Total Portfolio Value: ~$186,515.46
Note: Not financial advice. Values are approximate based on current market prices.
```
### Error Handling
- If `code == 30002`: API key not configured. Guide user to set up credentials.
- If response contains no wallets: inform user the account has no assets.
---
## Scenario 6: Margin Trade
**User prompt:** "Borrow 1000 USDT on margin and buy ETH on BitMart"
### Step 1: Check margin account
```bash
curl -s -H "X-BM-KEY: $BITMART_API_KEY" \
-H "User-Agent: bitmart-skills/spot/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
'https://api-cloud.bitmart.com/spot/v1/margin/isolated/account?symbol=ETH_USDT'
```
Verify the margin account is enabled and check available borrowing limit.
### Step 2: Borrow assets
Present borrow details for confirmation:
```
Margin Borrow:
Pair: ETH_USDT (isolated)
Borrow: 1,000 USDT
Please type CONFIRM to proceed.
```
After confirmation:
```bash
TIMESTAMP=$(date +%s000)
BODY='{"symbol":"ETH_USDT","currency":"USDT","amount":"1000"}'
SIGN=$(echo -n "TIMESTAMP#BITMART_API_MEMO#BODY" | openssl dgst -sha256 -hmac "$BITMART_API_SECRET" | awk '{print $NF}')
curl -s -X POST 'https://api-cloud.bitmart.com/spot/v1/margin/isolated/borrow' \
-H "User-Agent: bitmart-skills/spot/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
-H "Content-Type: application/json" \
-H "X-BM-KEY: $BITMART_API_KEY" \
-H "X-BM-SIGN: $SIGN" \
-H "X-BM-TIMESTAMP: $TIMESTAMP" \
-d "$BODY"
```
### Step 3: Place margin order
```bash
TIMESTAMP=$(date +%s000)
BODY='{"symbol":"ETH_USDT","side":"buy","type":"market","notional":"1000"}'
SIGN=$(echo -n "TIMESTAMP#BITMART_API_MEMO#BODY" | openssl dgst -sha256 -hmac "$BITMART_API_SECRET" | awk '{print $NF}')
curl -s -X POST 'https://api-cloud.bitmart.com/spot/v1/margin/submit_order' \
-H "User-Agent: bitmart-skills/spot/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
-H "Content-Type: application/json" \
-H "X-BM-KEY: $BITMART_API_KEY" \
-H "X-BM-SIGN: $SIGN" \
-H "X-BM-TIMESTAMP: $TIMESTAMP" \
-d "$BODY"
```
### Step 4: Monitor and repay
After the user is ready to close the margin position and repay:
```bash
TIMESTAMP=$(date +%s000)
BODY='{"symbol":"ETH_USDT","currency":"USDT","amount":"1000"}'
SIGN=$(echo -n "TIMESTAMP#BITMART_API_MEMO#BODY" | openssl dgst -sha256 -hmac "$BITMART_API_SECRET" | awk '{print $NF}')
curl -s -X POST 'https://api-cloud.bitmart.com/spot/v1/margin/isolated/repay' \
-H "User-Agent: bitmart-skills/spot/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
-H "Content-Type: application/json" \
-H "X-BM-KEY: $BITMART_API_KEY" \
-H "X-BM-SIGN: $SIGN" \
-H "X-BM-TIMESTAMP: $TIMESTAMP" \
-d "$BODY"
```
### Error Handling
- If margin account not enabled: guide user to enable isolated margin for the pair on BitMart.
- If borrow limit exceeded: inform user of maximum borrowable amount.
- Always remind user about interest costs and liquidation risk for margin trading.
- Margin trading carries higher risk — always include appropriate warnings.
FILE:references/bitmart-spot-skill.md
# BitMart Spot Trading
**Base URL:** `https://api-cloud.bitmart.com` | **Symbol:** `BTC_USDT` (underscore) | **Success:** `code == 1000`
34 endpoints — full details in `references/bitmart-spot-api-reference.md`
## Authentication
Same signature method as Futures. Credentials from `.env`: `BITMART_API_KEY`, `BITMART_API_SECRET`, `BITMART_API_MEMO`
No BitMart account? Register at **[https://www.bitmart.com/invite/cMEArf](https://www.bitmart.com/invite/cMEArf)**
**Always include `X-BM-BROKER-ID: BlaveData666666` on ALL requests.**
**IP Whitelist:** Use **public IP** (`curl https://checkip.amazonaws.com`), not private IP.
> Signature Python implementation: `references/bitmart-signature.md`
## Operation Flow
### Step 0: Credential Check
Verify credentials. If missing — **STOP**.
### Step 1: Identify Intent
- **READ:** market data, balance, order history
- **WRITE:** submit/cancel orders, withdraw
- **TRANSFER:** spot ↔ futures → see Part 2 **Spot ↔ Futures Transfer**
### Step 2: Execute Orders
- READ → call, parse, display
- WRITE → present summary → ask **"CONFIRM"** → execute
**Endpoint:** `POST /spot/v2/submit_order`
| Scenario | side | type | Key param |
| ------------ | ------ | -------- | --------------------------- |
| Buy, market | `buy` | `market` | `notional` (USDT to spend) |
| Buy, limit | `buy` | `limit` | `size` (base qty) + `price` |
| Sell, market | `sell` | `market` | `size` (base qty) |
| Sell, limit | `sell` | `limit` | `size` + `price` |
> Market buy uses `notional`, NOT `size`.
### Step 3: Verify
After order → query order detail. After cancel → check open orders.
## Order Reference
**Side:** `buy` / `sell` | **Type:** `limit` / `market` / `limit_maker` / `ioc`
**Status:** `new` / `partially_filled` / `filled` / `canceled` / `partially_canceled`
**Timestamps:** ms — always convert to local time.
## Error Handling
| Code | Action |
| ------------------ | ------------------------------------------------------- |
| 30005 | Wrong signature → see `references/bitmart-signature.md` |
| 30007 | Timestamp drift → sync clock |
| 50000 | Insufficient balance |
| 429 | Rate limited → wait |
| 403/503 Cloudflare | Wait 30-60s, retry max 3× |
## Security
- WRITE operations require **"CONFIRM"**
- "Not financial advice. Spot trading carries risk of loss."
## References
- `references/bitmart-spot-api-reference.md` — 34 endpoints
- `references/bitmart-signature.md` — Python signature implementation
- `references/bitmart-spot-authentication.md` / `bitmart-spot-scenarios.md`
---
FILE:references/bitmart-tp-sl.md
# Take-Profit / Stop-Loss Workflow
> **Credentials:** Read from `.env` — `BITMART_API_KEY`, `BITMART_API_SECRET`, `BITMART_API_MEMO`.
## Overview
TP/SL orders automatically close a position when the price reaches a specified take-profit or stop-loss level. They are essential for risk management in futures trading.
**Endpoint:** `POST /contract/private/submit-tp-sl-order`
**Important:** Each API call submits a single TP or SL order. To set both TP and SL, make two separate calls — one with `type:"take_profit"` and one with `type:"stop_loss"`.
**Plan categories:**
| Category | Value | Description |
|----------|-------|-------------|
| TP/SL Order | 1 | Applies to a specific quantity of contracts |
| Position TP/SL | 2 | Applies to the entire position (default, recommended) |
**TP/SL Side mapping:**
| Position | Close Side | TP Trigger | SL Trigger |
|----------|------------|------------|------------|
| Long | 3 (sell_close_long) | Price rises above TP price | Price falls below SL price |
| Short | 2 (buy_close_short) | Price falls below TP price | Price rises above SL price |
---
## Scenario 1: Set TP/SL for a Long Position
**User prompt:** "Set take profit at 72000 and stop loss at 64000 for my BTC long"
### Step 1: Get current position
```bash
curl -s -H "X-BM-KEY: $BITMART_API_KEY" \
-H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
'https://api-cloud-v2.bitmart.com/contract/private/position-v2?symbol=BTCUSDT'
```
Expected response:
```json
{
"code": 1000,
"data": [
{
"symbol": "BTCUSDT",
"current_amount": "100",
"position_amount": "100",
"position_side": "long",
"entry_price": "67000.0",
"mark_price": "67123.4",
"leverage": "10"
}
]
}
```
If no long position exists, inform the user and STOP.
### Step 2: Calculate risk/reward
```
Entry Price: 67,000.0 USDT
Take Profit: 72,000.0 USDT (+5,000.0, +7.46%)
Stop Loss: 64,000.0 USDT (-3,000.0, -4.48%)
Risk/Reward: 1:1.67
Potential Profit: +500.00 USDT (100 contracts x 0.001 BTC x 5000)
Potential Loss: -300.00 USDT (100 contracts x 0.001 BTC x 3000)
```
### Step 3: Present TP/SL summary and ask for CONFIRM
```
TP/SL Order Summary:
Symbol: BTCUSDT
Position: Long, 100 contracts @ 67,000.0 USDT
Take Profit: 72,000.0 USDT (+7.46% from entry)
Stop Loss: 64,000.0 USDT (-4.48% from entry)
Type: Position TP/SL (entire position)
Execution: Market
Price Trigger: Last Price
Risk/Reward Ratio: 1:1.67
When BTCUSDT reaches 72,000 → position closes with ~+500 USDT profit
When BTCUSDT reaches 64,000 → position closes with ~-300 USDT loss
Please type CONFIRM to proceed.
```
### Step 4: Submit TP order (after user confirms)
```bash
TIMESTAMP=$(date +%s000)
BODY='{"symbol":"BTCUSDT","type":"take_profit","side":3,"trigger_price":"72000","executive_price":"0","price_type":1,"plan_category":2}'
SIGN=$(echo -n "TIMESTAMP#BITMART_API_MEMO#BODY" | openssl dgst -sha256 -hmac "$BITMART_API_SECRET" | awk '{print $NF}')
curl -s -X POST 'https://api-cloud-v2.bitmart.com/contract/private/submit-tp-sl-order' \
-H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
-H "Content-Type: application/json" \
-H "X-BM-KEY: $BITMART_API_KEY" \
-H "X-BM-SIGN: $SIGN" \
-H "X-BM-TIMESTAMP: $TIMESTAMP" \
-d "$BODY"
```
### Step 5: Submit SL order
```bash
TIMESTAMP=$(date +%s000)
BODY='{"symbol":"BTCUSDT","type":"stop_loss","side":3,"trigger_price":"64000","executive_price":"0","price_type":1,"plan_category":2}'
SIGN=$(echo -n "TIMESTAMP#BITMART_API_MEMO#BODY" | openssl dgst -sha256 -hmac "$BITMART_API_SECRET" | awk '{print $NF}')
curl -s -X POST 'https://api-cloud-v2.bitmart.com/contract/private/submit-tp-sl-order' \
-H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
-H "Content-Type: application/json" \
-H "X-BM-KEY: $BITMART_API_KEY" \
-H "X-BM-SIGN: $SIGN" \
-H "X-BM-TIMESTAMP: $TIMESTAMP" \
-d "$BODY"
```
### Step 6: Verify and report
```bash
curl -s -H "X-BM-KEY: $BITMART_API_KEY" \
-H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
'https://api-cloud-v2.bitmart.com/contract/private/current-plan-order?symbol=BTCUSDT&plan_type=profit_loss'
```
```
TP/SL Order Set Successfully:
Symbol: BTCUSDT
Position: Long, 100 contracts
Entry Price: 67,000.0 USDT
Take Profit: 72,000.0 USDT (+7.46%)
Stop Loss: 64,000.0 USDT (-4.48%)
Risk/Reward: 1:1.67
Status: Active
Not financial advice. Futures trading carries significant risk of loss.
```
---
## Scenario 2: Set TP/SL for a Short Position
**User prompt:** "Set TP at 62000 and SL at 70000 for my ETH short"
For short positions:
- **Side:** 2 (buy_close_short)
- **TP price** is *below* entry price (profit when price drops)
- **SL price** is *above* entry price (loss when price rises)
Submit TP:
```bash
TIMESTAMP=$(date +%s000)
BODY='{"symbol":"ETHUSDT","type":"take_profit","side":2,"trigger_price":"62000","executive_price":"0","price_type":1,"plan_category":2}'
SIGN=$(echo -n "TIMESTAMP#BITMART_API_MEMO#BODY" | openssl dgst -sha256 -hmac "$BITMART_API_SECRET" | awk '{print $NF}')
curl -s -X POST 'https://api-cloud-v2.bitmart.com/contract/private/submit-tp-sl-order' \
-H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
-H "Content-Type: application/json" \
-H "X-BM-KEY: $BITMART_API_KEY" \
-H "X-BM-SIGN: $SIGN" \
-H "X-BM-TIMESTAMP: $TIMESTAMP" \
-d "$BODY"
```
Submit SL:
```bash
TIMESTAMP=$(date +%s000)
BODY='{"symbol":"ETHUSDT","type":"stop_loss","side":2,"trigger_price":"70000","executive_price":"0","price_type":1,"plan_category":2}'
SIGN=$(echo -n "TIMESTAMP#BITMART_API_MEMO#BODY" | openssl dgst -sha256 -hmac "$BITMART_API_SECRET" | awk '{print $NF}')
curl -s -X POST 'https://api-cloud-v2.bitmart.com/contract/private/submit-tp-sl-order' \
-H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
-H "Content-Type: application/json" \
-H "X-BM-KEY: $BITMART_API_KEY" \
-H "X-BM-SIGN: $SIGN" \
-H "X-BM-TIMESTAMP: $TIMESTAMP" \
-d "$BODY"
```
---
## Scenario 3: TP/SL by Percentage
**User prompt:** "Set 5% take profit and 3% stop loss on my BTC long"
### Calculate TP/SL prices from percentage
Given entry_price = 67000:
- TP = 67000 * (1 + 0.05) = 70350.0
- SL = 67000 * (1 - 0.03) = 64990.0
Present the calculated prices and proceed with the standard flow.
---
## Scenario 4: Take-Profit Only
**User prompt:** "Set take profit at 75000 for BTC, no stop loss"
```json
{"symbol":"BTCUSDT","type":"take_profit","side":3,"trigger_price":"75000","executive_price":"0","price_type":1,"plan_category":2}
```
---
## Scenario 5: Stop-Loss Only
**User prompt:** "Set a stop loss at 63000 for my BTC long"
```json
{"symbol":"BTCUSDT","type":"stop_loss","side":3,"trigger_price":"63000","executive_price":"0","price_type":1,"plan_category":2}
```
---
## Scenario 6: Limit Execution TP/SL (Partial Position)
**User prompt:** "Set a limit take profit at 72000 with execution at 71900 for 50 contracts"
When using `plan_category=1` (partial position) with limit execution, provide `executive_price` and `category:"limit"`:
```json
{"symbol":"BTCUSDT","type":"take_profit","side":3,"trigger_price":"72000","executive_price":"71900","price_type":1,"plan_category":1,"size":50,"category":"limit"}
```
---
## Modifying TP/SL
**User prompt:** "Move my BTC take profit from 72000 to 74000"
### Step 1: Find existing TP/SL order
```bash
curl -s -H "X-BM-KEY: $BITMART_API_KEY" \
-H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
'https://api-cloud-v2.bitmart.com/contract/private/current-plan-order?symbol=BTCUSDT&plan_type=profit_loss'
```
### Step 2: Modify the TP/SL order
```bash
TIMESTAMP=$(date +%s000)
BODY='{"symbol":"BTCUSDT","order_id":"45678901234567890","trigger_price":"74000","price_type":1,"plan_category":2}'
SIGN=$(echo -n "TIMESTAMP#BITMART_API_MEMO#BODY" | openssl dgst -sha256 -hmac "$BITMART_API_SECRET" | awk '{print $NF}')
curl -s -X POST 'https://api-cloud-v2.bitmart.com/contract/private/modify-tp-sl-order' \
-H "User-Agent: bitmart-skills/futures/v2026.3.23" \
-H "X-BM-BROKER-ID: BlaveData666666" \
-H "Content-Type: application/json" \
-H "X-BM-KEY: $BITMART_API_KEY" \
-H "X-BM-SIGN: $SIGN" \
-H "X-BM-TIMESTAMP: $TIMESTAMP" \
-d "$BODY"
```
### Step 3: Report updated TP/SL
```
TP/SL Updated:
Take Profit: 72,000.0 → 74,000.0 USDT (+10.45% from entry)
Stop Loss: 64,000.0 USDT (unchanged)
New Risk/Reward: 1:2.33
```
---
## TP/SL Validation Rules
Before submitting, validate:
**Long positions (side=3):**
- TP `trigger_price` must be > current mark price
- SL `trigger_price` must be < current mark price
- SL `trigger_price` must be > liquidation price (otherwise liquidation triggers first)
**Short positions (side=2):**
- TP `trigger_price` must be < current mark price
- SL `trigger_price` must be > current mark price
- SL `trigger_price` must be < liquidation price
If validation fails, inform the user with a clear explanation:
```
Invalid Stop Loss: 60,000 USDT is below your liquidation price of 60,500 USDT.
Your position would be liquidated before the stop loss triggers.
Suggested minimum stop loss: 61,000 USDT (above liquidation price).
```
---
## Error Handling
| Error | Cause | Action |
|-------|-------|--------|
| No position found | Position was closed or does not exist | Inform user |
| Invalid TP price | TP is on the wrong side of current price | Explain correct TP direction |
| Invalid SL price | SL is on the wrong side of current price | Explain correct SL direction |
| SL below liquidation | Position would liquidate first | Suggest SL above liquidation price |
| Order already exists | Duplicate TP/SL | Suggest modifying the existing order instead |
FILE:references/blave-api.md
# Blave API Examples
Full Python examples for all Blave API endpoints.
## Setup
```python
import requests, os
from dotenv import load_dotenv
load_dotenv()
headers = {
"api-key": os.getenv("blave_api_key"),
"secret-key": os.getenv("blave_secret_key"),
}
BASE_URL = "https://api.blave.org"
```
---
## Price
```python
params = {"symbol": "BTCUSDT"}
response = requests.get(f"{BASE_URL}/price", headers=headers, params=params, timeout=60)
print(response.json())
# {"symbol": "BTCUSDT", "price": 95000.0, "change_24h": 2.5}
```
---
## Alpha Table
```python
response = requests.get(f"{BASE_URL}/alpha_table", headers=headers, timeout=60)
print(response.json())
```
---
## Kline
```python
params = {"symbol": "BTCUSDT", "period": "1h", "start_date": "2025-01-01", "end_date": "2025-03-01"}
response = requests.get(f"{BASE_URL}/kline", headers=headers, params=params, timeout=60)
print(response.json())
```
---
## Market Direction
```python
params = {"period": "1h", "start_date": "2025-01-01", "end_date": "2025-03-01"}
response = requests.get(f"{BASE_URL}/market_direction/get_alpha", headers=headers, params=params, timeout=60)
print(response.json())
```
---
## Market Sentiment
```python
# Get symbols
response = requests.get(f"{BASE_URL}/market_sentiment/get_symbols", headers=headers, timeout=60)
# Get alpha
params = {"symbol": "BTCUSDT", "period": "1h", "start_date": "2025-01-01", "end_date": "2025-03-01"}
response = requests.get(f"{BASE_URL}/market_sentiment/get_alpha", headers=headers, params=params, timeout=60)
print(response.json())
```
---
## Capital Shortage
```python
params = {"period": "1h", "start_date": "2025-01-01", "end_date": "2025-03-01"}
response = requests.get(f"{BASE_URL}/capital_shortage/get_alpha", headers=headers, params=params, timeout=60)
print(response.json())
```
---
## Holder Concentration
```python
# Get symbols
response = requests.get(f"{BASE_URL}/holder_concentration/get_symbols", headers=headers, timeout=60)
# Get alpha
params = {"symbol": "BTCUSDT", "period": "1h", "start_date": "2025-01-01", "end_date": "2025-03-01"}
response = requests.get(f"{BASE_URL}/holder_concentration/get_alpha", headers=headers, params=params, timeout=60)
print(response.json())
```
---
## Taker Intensity
```python
# Get symbols
response = requests.get(f"{BASE_URL}/taker_intensity/get_symbols", headers=headers, timeout=60)
# Get alpha
params = {"symbol": "BTCUSDT", "period": "1h", "timeframe": "24h", "start_date": "2025-01-01", "end_date": "2025-03-01"}
response = requests.get(f"{BASE_URL}/taker_intensity/get_alpha", headers=headers, params=params, timeout=60)
print(response.json())
```
---
## Whale Hunter
```python
# Get symbols
response = requests.get(f"{BASE_URL}/whale_hunter/get_symbols", headers=headers, timeout=60)
# Get alpha
params = {"symbol": "BTCUSDT", "period": "1h", "timeframe": "24h", "score_type": "score_oi"}
response = requests.get(f"{BASE_URL}/whale_hunter/get_alpha", headers=headers, params=params, timeout=60)
print(response.json())
```
---
## Squeeze Momentum
```python
# Get symbols
response = requests.get(f"{BASE_URL}/squeeze_momentum/get_symbols", headers=headers, timeout=60)
# Get alpha (period fixed to 1d)
params = {"symbol": "BTCUSDT", "start_date": "2025-01-01", "end_date": "2025-03-01"}
response = requests.get(f"{BASE_URL}/squeeze_momentum/get_alpha", headers=headers, params=params, timeout=60)
print(response.json())
```
---
## Sector Rotation
```python
response = requests.get(f"{BASE_URL}/sector_rotation/get_history_data", headers=headers, timeout=60)
print(response.json())
```
---
## Blave Top Trader Exposure
```python
params = {"period": "1h", "start_date": "2025-01-01", "end_date": "2025-03-01"}
response = requests.get(f"{BASE_URL}/blave_top_trader/get_exposure", headers=headers, params=params, timeout=60)
print(response.json())
```
---
## alpha_table Field Reference
Each symbol in `/alpha_table` contains:
| Field | Description |
|---|---|
| `statistics` | `up_prob` (prob of 24h upward move), `exp_value` (expected return), `avg_up_return`, `avg_down_return`, `return_ratio`, `is_data_sufficient` |
| `price` | `{"-": 70000}` — current price |
| `price_change` | `{"15min": ..., "1h": ..., "24h": ...}` — % change |
| `market_cap` | `{"-": 1234567890}` — USD market cap |
| `market_cap_percentile` | `{"-": 85.3}` — percentile among all listed coins |
| `funding_rate` | `{"binance": -0.01, ...}` — per exchange |
| `oi_imbalance` | `{"-": 0.12}` — OI imbalance |
`fields` = indicator metadata. `note` = color ranges. `""` = insufficient data.
Use `statistics.up_prob` and `statistics.exp_value` for screening. Always check `is_data_sufficient` before using `statistics`.
FILE:references/blave-indicator-guide.md
# Blave Indicator Interpretation Guide
How to interpret alpha values and explain each indicator to users.
> ⚠️ All indicators are reference data only — not buy/sell signals.
---
## `stat` Field Interpretation
All `get_alpha` responses include a `stat` object. Always present these values with context, not just raw numbers.
| Field | Meaning | Example |
|---|---|---|
| `up_prob` | 24h 後上漲的歷史機率 | `0.65` = 65% 機率在 24h 內上漲 |
| `exp_value` | 24h 預期報酬率 (%) | `0.05` = 預期 +5%;`-0.03` = 預期 -3% |
| `avg_up_return` | 上漲時的平均幅度 (%) | `0.08` = 上漲時平均 +8% |
| `avg_down_return` | 下跌時的平均幅度 (%) | `-0.04` = 下跌時平均 -4% |
| `return_ratio` | avg_up_return / avg_down_return 絕對值比值 | `2.0` = 上漲幅度是下跌幅度的 2 倍 |
| `is_data_sufficient` | 歷史資料是否超過 1 年 | `true` = 統計有效;`false` = 資料不足,stat 參考價值低 |
**How to present stat to users:**
- `is_data_sufficient: false` → 提醒用戶統計樣本不足(資料未滿 1 年),數值參考性有限
- `up_prob > 0.6` → 歷史上多數情況看漲(但非保證)
- `up_prob < 0.4` → 歷史上多數情況看跌
- `exp_value > 0` → 預期正報酬
- `return_ratio > 1` → 上漲空間大於下跌風險(風報比佳)
- `return_ratio < 1` → 下跌風險大於上漲空間(風報比差)
---
## 市場情緒 Market Sentiment (MS)
通過現貨與合約價格差異衡量交易員整體看法。
| Value | Meaning |
|---|---|
| 高正值 | 交易員樂觀,合約做多比現貨多 |
| 高負值 | 交易員悲觀,合約做空比現貨多 |
| 過度樂觀 | 可能接近反轉高點,需留意 |
| 過度悲觀 | 可能接近反轉低點,需留意 |
Labels: 樂觀/悲觀 → 高度樂觀/悲觀 → 過度樂觀/悲觀
適合分析趨勢方向,不適合觀察短期動態。
---
## 資金稀缺 Capital Shortage (CS)
通過穩定幣利用率衡量合約市場熱度。
| Value | Meaning |
|---|---|
| 高 (綠) | 資金高度運用,情緒樂觀,槓桿多 |
| 低 (紅) | 資金運用少,情緒悲觀,槓桿少 |
- 高紅柱出現 → 行情偏空確認
- 高紅柱開始萎縮 → 趨勢仍可能延續下跌,需等回升至零軸或轉綠才趨勢轉強
- 高綠柱出現 → 行情偏多確認
- 高綠柱萎縮 → 可能出現短暫回調或趨勢反轉
---
## 籌碼集中度 Holder Concentration (HC)
合約市場多空持倉分佈,反映機構 vs 散戶角力。
| Value | Meaning |
|---|---|
| 正/綠 | 多頭籌碼集中於少數帳戶(機構/主力看多) |
| 負/紅 | 空頭籌碼集中於少數帳戶(機構/主力看空) |
| 絕對值越大 | 籌碼越集中 |
Labels: 集中 → 高度集中 → 過度集中
**關鍵訊號:**
- 綠柱萎縮 → 機構多單開始獲利了結,行情可能轉空
- 紅柱萎縮 → 機構空單開始回補,行情可能轉多
- 過度集中 → 籌碼隨時可能釋放,可考慮反向策略
**兩種策略:**
1. 順向:跟隨機構方向,多頭集中做多 / 空頭集中做空
2. 反向:過度集中時預判釋放,反向佈局
---
## 多空力道 Taker Intensity (TI)
市價單淨差值(多-空)標準化,反映交易員即時決策。
| Value | Meaning |
|---|---|
| 正 (>0) | 淨市價多單,交易員積極做多 |
| 負 (<0) | 淨市價空單,交易員積極做空 |
**注意:**
- 高正值可能是散戶追漲,也可能是空單被清算(空單平倉 = 市價多單)
- 高負值可能是散戶追跌,也可能是多單被清算
- 搭配籌碼集中度 + 巨鯨警報可判斷是散戶行為還是清算行情
---
## 巨鯨警報 Whale Hunter (WH)
未平倉量/成交量變化標準化,偵測潛在大額交易行為。
`score_oi` = 基於 OI 變化 | `score_volume` = 基於成交量變化
| Value | Meaning |
|---|---|
| 正值大 | OI/成交量大幅增加,可能是巨鯨開倉 |
| 負值大 | OI/成交量大幅減少,可能是巨鯨平倉 |
| 接近 0 | 巨鯨行為不明顯 |
**四象限(OI 變化 × 成交量變化):**
| 象限 | OI | 成交量 | 含意 |
|---|---|---|---|
| 巨鯨進場區 | ↑ | ↑ | 行情可能爆發,具關注價值 |
| 巨鯨佈局區 | ↑ | 平 | 潛伏佈局,後市可期待 |
| 巨鯨洗盤區 | ↓ | ↑ | 可能洗盤,行情不易延續 |
| 巨鯨觀望區 | ↓ | ↓ | 市場觀望,等待信號 |
搭配籌碼集中度判斷巨鯨方向(做多/做空)。
---
## 擠壓動能 Squeeze Momentum (SM)
描繪波動性趨勢,固定使用 1d 週期。
| Signal | Meaning |
|---|---|
| 橘點 (中線) | 動能擠壓中,低波動,趨勢膠著 |
| 綠柱增強 | 上漲動能增強 |
| 紅柱增強 | 下跌動能增強 |
| 脫橘 (橘點消失) | 可能出現單邊行情,觀察脫橘後柱狀方向 |
- `scolor` 欄位表示動能方向標籤
- 脫橘後綠柱 → 做多;脫橘後紅柱 → 做空
- 柱狀出現背離(價格新高但動能未創新高)→ 可能反轉
---
## Blave頂尖交易員 Top Trader Exposure (BT)
Blave 平台績效前 10% 交易員的合約淨曝險,僅 BTC。
| Value | Meaning |
|---|---|
| >0 (綠) | 頂尖交易員多頭曝險 > 空頭,整體看多 |
| <0 (紅) | 頂尖交易員空頭曝險 > 多頭,整體看空 |
有效交易員 = 合約帳戶 > $1,000 USD,按夏普比率、最大回撤等多維度篩選。
---
## 板塊輪動 Sector Rotation (SR)
各加密貨幣板塊在不同時間段的漲跌表現。
| Color | Meaning |
|---|---|
| 深綠 | 該板塊在該時間段強勢上漲 |
| 深紅 | 該板塊在該時間段強勢下跌 |
**三個觀察週期:**
- 短週期:1h、8h、24h → 短線交易參考
- 中週期:3d、7d → 波段交易參考
- 長週期:30d、90d → 趨勢判斷參考
**兩種策略:**
1. 強勢做多、弱勢做空(短週期多綠 = 相對強勢)
2. 觀察輪動規律,提前埋伏下一個輪動板塊
---
## 組合分析建議
| 目的 | 搭配指標 |
|---|---|
| 判斷整體趨勢方向 | 市場情緒 + 資金稀缺 + 擠壓動能 |
| 篩選強勢做多幣種 | 巨鯨警報(進場區)+ 籌碼集中度(多頭集中)+ 多空力道(正值) |
| 捕捉反轉 | 市場情緒(過度樂觀/悲觀)+ 籌碼集中度(過度集中)|
| 選板塊 | 板塊輪動 + 多空力道 |
| 驗證巨鯨方向 | 巨鯨警報 + 籌碼集中度 |
FILE:references/bybit-skill.md
# Bybit Trading
**Base URL (Mainnet):** `https://api.bybit.com` | **Backup:** `https://api.bytick.com` | **Testnet:** `https://api-testnet.bybit.com`
**Spot:** `BTCUSDT` | **Perpetual:** `BTCUSDT` (Linear) | **Success:** `"retCode": 0`
## Authentication
**Credentials** (from `.env`): `BYBIT_API_KEY`, `BYBIT_API_SECRET`
No Bybit account? Register at **[https://partner.bybit.com/b/BLAVE](https://partner.bybit.com/b/BLAVE)**
Verify credentials before any private call. If missing — **STOP**.
**Signature:** `HMAC-SHA256(secret, {timestamp}{apiKey}{recvWindow}{queryString|jsonBody})`
- GET: sign `{timestamp}{apiKey}{recvWindow}{queryString}`
- POST: sign `{timestamp}{apiKey}{recvWindow}{jsonBody}` — use **compact JSON** (no spaces, no newlines)
**Headers (all authenticated requests):**
```
X-BAPI-API-KEY: $BYBIT_API_KEY
X-BAPI-TIMESTAMP: <unix ms>
X-BAPI-SIGN: <hmac signature>
X-BAPI-RECV-WINDOW: 5000
referer: Ue001036
Content-Type: application/json (POST only)
```
**`referer: Ue001036` is MANDATORY on every request — no exceptions.**
## Operation Flow
### Step 0: Credential Check
Verify `BYBIT_API_KEY`, `BYBIT_API_SECRET`. If missing — **STOP**. Default to **Mainnet** unless user explicitly requests Testnet.
### Step 1: Pre-Trade Check
`GET /v5/position/list?category=linear&symbol=<SYMBOL>` → if position exists, inherit side and leverage.
### Step 2: Execute
- READ → call, parse, display
- WRITE → present summary → ask **"CONFIRM"** → execute
### Step 3: Verify
After order → `GET /v5/order/realtime` → confirm status. After close → `GET /v5/position/list`.
## Key Endpoints
| Action | Method | Path |
|---|---|---|
| Market info | GET | `/v5/market/instruments-info` |
| Ticker | GET | `/v5/market/tickers` |
| Wallet balance | GET | `/v5/account/wallet-balance` |
| Place order | POST | `/v5/order/create` |
| Cancel order | POST | `/v5/order/cancel` |
| Open orders | GET | `/v5/order/realtime` |
| Positions | GET | `/v5/position/list` |
| Set leverage | POST | `/v5/position/set-leverage` |
| Set TP/SL | POST | `/v5/position/set-tpsl` |
| Order history | GET | `/v5/order/history` |
## Security
- WRITE operations require **"CONFIRM"**
- Always show liquidation price before opening leveraged positions
- "Not financial advice. Trading carries significant risk of loss."
---
FILE:references/hyperliquid-api.md
# Hyperliquid API Reference
All endpoints are prefixed with `https://api.blave.org/hyperliquid/`. Auth headers required: `api-key`, `secret-key`.
---
## `GET /hyperliquid/leaderboard`
`sort_by` — default `accountValue`; pass any window key (`week`, `month`, `allTime`) to sort by PnL.
Returns top 100 traders:
- `ethAddress` — wallet address
- `accountValue` — USD account size
- `windowPerformances` — list of `[window, {pnl, roi, vlm}]` for day/week/month/allTime
- `displayName` — name if Blave-tracked, otherwise null
Cached 5 min.
---
## `GET /hyperliquid/traders`
No params. Returns Blave-curated dict:
```json
{
"0xABC...": {
"name": {"en": "Machi Big Brother", "zh": "麻吉大哥"},
"description": {"en": "...", "zh": "..."}
}
}
```
---
## `GET /hyperliquid/trader_position`
`address`✓ → `{perp, spot, abstraction, net_equity, trader_name, description}`
- `perp.assetPositions[].position` — `coin`, `szi` (size, negative = short), `entryPx`, `unrealizedPnl`, `token_id`
- `spot.balances` — spot token balances
- `net_equity` — total account value (USD)
Cached 15 s.
---
## `GET /hyperliquid/trader_history`
`address`✓ → list of fills:
| Field | Description |
|---|---|
| `coin` | Asset |
| `px` | Fill price |
| `sz` | Fill size |
| `dir` | `Open Long` / `Close Long` / `Open Short` / `Close Short` |
| `closedPnl` | Realized PnL (non-zero on close fills) |
| `time` | Unix timestamp (seconds) |
| `token_id` | Internal token identifier |
Cached 60 s.
---
## `GET /hyperliquid/trader_performance`
`address`✓ → `{chart: {timestamp: [...], pnl: [...]}}`
- `timestamp` — Unix seconds array
- `pnl` — cumulative PnL in USD (same index as timestamp)
Use `np.diff(pnl)` for daily returns. Cached 60 s.
---
## `GET /hyperliquid/trader_open_order`
`address`✓ → list of open orders:
- `coin`, `sz`, `px`, `side` (`B`=buy / `A`=ask), `token_id`, `oid`
Cached 60 s.
---
## `GET /hyperliquid/top_trader_position`
No params. Aggregates positions across top 100 leaderboard traders.
```json
{
"long": [{"coin": "BTC", "position": 12.5, ...}],
"short": [{"coin": "ETH", "position": -200, ...}]
}
```
Cached 5 min.
---
## `GET /hyperliquid/top_trader_exposure_history`
`symbol`✓, `period`✓, `start_date`, `end_date` → `{data: {...}}`
Time series of net long/short exposure for the given symbol across top traders.
---
## `GET /hyperliquid/bucket_stats`
No params. Returns trader stats grouped by account size:
| Bucket | Range |
|---|---|
| `lt_100` | < $100 |
| `100_to_1k` | $100–$1K |
| `1k_to_10k` | $1K–$10K |
| `10k_to_100k` | $10K–$100K |
| `100k_to_1M` | $100K–$1M |
| `gt_1M` | > $1M |
| `top_traders` | Blave-curated list |
Each bucket: `{stats: {count, profit_ratio, loss_ratio}, positions: {long, short}, long_exposure, short_exposure, net_exposure}`
Returns `{"status": "warming_up"}` with HTTP 202 while cache is building — retry after a few seconds. Cached ~5 min.
FILE:references/okx-api-reference.md
# OKX API Reference
**Base URL:** `https://www.okx.com`
**Success:** HTTP 200 + `"code": "0"` in response body
---
## Authentication
**Algorithm:** `Base64(HMAC-SHA256(secret, prehash))`
**Pre-hash:** `timestamp + METHOD + requestPath + body`
- `timestamp` — ISO 8601 milliseconds UTC: `2024-01-01T00:00:00.000Z`
- `METHOD` — `GET` or `POST` (uppercase)
- `requestPath` — full path + query string, e.g. `/api/v5/account/balance?ccy=BTC`
- `body` — raw JSON string for POST; `""` for GET
**Required headers (all private endpoints):**
| Header | Value |
|---|---|
| `OK-ACCESS-KEY` | API key |
| `OK-ACCESS-SIGN` | Base64(HMAC-SHA256 signature) |
| `OK-ACCESS-TIMESTAMP` | ISO 8601 ms timestamp |
| `OK-ACCESS-PASSPHRASE` | Passphrase set at key creation |
| `Content-Type` | `application/json` (POST only) |
| `User-Agent` | `Mozilla/5.0` — **required on ALL requests**, omitting returns `403 Error code 1010` |
**Credentials from `.env`:** `OKX_API_KEY`, `OKX_SECRET_KEY`, `OKX_PASSPHRASE`
```python
import hmac, hashlib, base64
from datetime import datetime, timezone
def okx_sign(secret, timestamp, method, path, body=""):
prehash = timestamp + method.upper() + path + body
return base64.b64encode(
hmac.new(secret.encode(), prehash.encode(), hashlib.sha256).digest()
).decode()
def okx_timestamp():
return datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%S.') + \
f"{datetime.now(timezone.utc).microsecond // 1000:03d}Z"
```
---
## Broker Code
**`"tag": "96ee7de3fd4bBCDE"` — include in the body of EVERY POST that creates or modifies an order. This is non-negotiable.**
Required on (all POST order-creation endpoints):
- `POST /api/v5/trade/order` — single order
- `POST /api/v5/trade/batch-orders` — batch orders
- `POST /api/v5/trade/order-algo` — algo orders (TP/SL, trailing stop, TWAP, iceberg)
- `POST /api/v5/trade/amend-order` — amend order
- Any other POST endpoint that creates or updates an order
Not applicable on: GET queries, `POST /api/v5/trade/cancel-order`, `POST /api/v5/trade/cancel-batch-orders`, `POST /api/v5/trade/close-position` (these do not accept `tag`).
**Checklist before every POST order call:** Does the body contain `"tag": "96ee7de3fd4bBCDE"`? If not — add it now.
---
## Instrument ID Format
| Type | Format | Example |
|---|---|---|
| Spot | `{BASE}-{QUOTE}` | `BTC-USDT` |
| Perpetual Swap | `{BASE}-{QUOTE}-SWAP` | `BTC-USDT-SWAP` |
| Delivery Futures | `{BASE}-{QUOTE}-{YYMMDD}` | `BTC-USDT-250328` |
---
## Key Endpoints
### Account
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | `/api/v5/account/balance` | SIGNED | Account balance. `?ccy=USDT` to filter |
| GET | `/api/v5/account/positions` | SIGNED | Open positions. `?instType=SWAP` |
### Spot Trading
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | `/api/v5/trade/order` | SIGNED | Place order |
| POST | `/api/v5/trade/cancel-order` | SIGNED | Cancel order |
| GET | `/api/v5/trade/orders-pending` | SIGNED | Active orders |
| GET | `/api/v5/trade/order` | SIGNED | Order detail |
| GET | `/api/v5/trade/orders-history` | SIGNED | Order history (7 days) |
### Perpetual Swap Trading
Same endpoints as spot — differentiate via `instId` (`BTC-USDT-SWAP`) and `tdMode`.
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | `/api/v5/trade/order` | SIGNED | Open/close position |
| POST | `/api/v5/trade/close-position` | SIGNED | Close full position |
| GET | `/api/v5/account/positions` | SIGNED | Open positions |
### Market Data (Public)
| Method | Path | Description |
|---|---|---|
| GET | `/api/v5/market/ticker?instId=BTC-USDT` | Last price, 24h stats |
| GET | `/api/v5/market/candles?instId=BTC-USDT&bar=1H` | OHLCV candles |
| GET | `/api/v5/public/funding-rate?instId=BTC-USDT-SWAP` | Funding rate |
| GET | `/api/v5/public/instruments?instType=SWAP&instId=BTC-USDT-SWAP` | Instrument specs — **query before every swap order** |
---
## Swap Contract Size — Always Query `ctVal`
**Never hardcode contract sizes.** Each instrument has a different `ctVal` (contract value in base currency). Always fetch it before calculating swap order size.
**Step 1 — Query instrument specs:**
```
GET /api/v5/public/instruments?instType=SWAP&instId={instId}
```
Extract from response: `ctVal` (base units per contract), `minSz` (minimum contracts), `lotSz` (contract size step).
**Step 2 — Calculate contracts:**
```
contracts = usd_amount / (current_price * ctVal)
# Round down to lotSz precision, must be >= minSz
```
**Example (do NOT hardcode these — query live):**
- BTC-USDT-SWAP: ctVal=0.01 → 1 contract ≈ 0.01 BTC
- ETH-USDT-SWAP: ctVal=0.1 → 1 contract ≈ 0.1 ETH
**Error 51008 (Insufficient margin):** Recalculate using the actual `ctVal` from the API — do not guess.
---
## Place Order Parameters
**`POST /api/v5/trade/order`**
| Param | Required | Description |
|---|---|---|
| `instId` | ✓ | e.g. `BTC-USDT` or `BTC-USDT-SWAP` |
| `tdMode` | ✓ | `cash` (spot) / `cross` / `isolated` (swap) |
| `side` | ✓ | `buy` / `sell` |
| `posSide` | Swap only | `long` / `short` (hedge mode) |
| `ordType` | ✓ | `market` / `limit` / `post_only` / `fok` / `ioc` |
| `sz` | ✓ | Order size |
| `px` | Limit only | Price |
| `tag` | ✓ **MANDATORY** | `"96ee7de3fd4bBCDE"` — must be in every order POST body |
**Submit-order rules:**
| Scenario | tdMode | posSide | Send | Omit |
|---|---|---|---|---|
| Spot buy, market | `cash` | — | instId, side:`buy`, ordType:`market`, sz (USDT notional) | px |
| Spot buy, limit | `cash` | — | instId, side:`buy`, ordType:`limit`, sz, px | — |
| Spot sell, market | `cash` | — | instId, side:`sell`, ordType:`market`, sz (base qty) | px |
| Swap open long | `cross`/`isolated` | `long` | instId, side:`buy`, ordType, sz, leverage via separate call | — |
| Swap close long | `cross`/`isolated` | `long` | instId, side:`sell`, ordType:`market`, sz | px |
| Swap open short | `cross`/`isolated` | `short` | instId, side:`sell`, ordType, sz | — |
---
## Error Codes
| Code | Meaning | Action |
|---|---|---|
| `0` | Success | — |
| `50011` | Rate limit exceeded | Wait and retry |
| `50013` | Timestamp expired | Sync clock (tolerance ±30s) |
| `50111` | Invalid signature | Check sign algorithm |
| `51000` | Parameter error | Check request params |
| `51008` | Insufficient balance | Reduce size |
FILE:references/okx-skill.md
# OKX Trading
**Base URL:** `https://www.okx.com` | **Spot:** `BTC-USDT` | **Swap:** `BTC-USDT-SWAP` | **Success:** `"code": "0"`
Full details in `references/okx-api-reference.md`
## Authentication
**Credentials** (from `.env`): `OKX_API_KEY`, `OKX_SECRET_KEY`, `OKX_PASSPHRASE`
No OKX account? Register at **[https://okx.com/join/58510434](https://okx.com/join/58510434)**
Verify credentials before any private call. If missing — **STOP**.
**Signature:** `Base64(HMAC-SHA256(secret, timestamp + METHOD + requestPath + body))`
- `timestamp` format: `2024-01-01T00:00:00.000Z` (ISO 8601 ms UTC)
- GET body = `""`
**Headers:** `OK-ACCESS-KEY` + `OK-ACCESS-SIGN` + `OK-ACCESS-TIMESTAMP` + `OK-ACCESS-PASSPHRASE` + `User-Agent: Mozilla/5.0`
**`User-Agent` is required on ALL OKX requests.** Omitting it returns `403 Error code 1010`.
**Broker code: `"tag": "96ee7de3fd4bBCDE"` — MANDATORY on every POST that creates or modifies an order. No exceptions. If you write a POST body and forget `tag`, stop and add it before sending.**
## Operation Flow
### Step 0: Credential Check
Verify `OKX_API_KEY`, `OKX_SECRET_KEY`, `OKX_PASSPHRASE`. If missing — **STOP**.
### Step 1: Pre-Trade Check (Swap only)
`GET /api/v5/account/positions?instId=<SYMBOL>-SWAP` → if position exists, inherit `tdMode` and leverage.
### Step 2: Execute
- READ → call, parse, display
- WRITE → present summary → ask **"CONFIRM"** → execute
### Step 3: Verify
After order → `GET /api/v5/trade/order` → confirm status. After close → `GET /api/v5/account/positions`.
## Security
- WRITE operations require **"CONFIRM"**
- Always show liquidation price before opening leveraged swap positions
- "Not financial advice. Trading carries significant risk of loss."
## References
- `references/okx-api-reference.md` — endpoints, signature, order params
---
FILE:references/tradingview-stream.md
# TradingView Signal Stream
## Architecture
```
TradingView Alert → POST /tradingview/webhook → Redis Stream
↓
GET /sse/tradingview/stream → Agent
```
1. TradingView fires an alert → sends POST to Blave webhook with your channel code
2. Blave stores the payload in a Redis Stream
3. Agent connects via SSE and receives each signal in real time
4. On disconnect, agent can reconnect with `last_id` to resume from the exact point
---
## Stream Connection
> **Webhook & channel activation:** Handled by the Blave team. Contact Blave to set up your TradingView webhook and get your channel name.
**Params:**
| Param | Required | Description |
|---|---|---|
| `channel` | ✓ | Your channel name (mapped from webhook code) |
| `last_id` | — | Redis Stream ID of last received message. Omit (or use `"$"`) to get only new signals. Pass on reconnect to resume from that point. |
**SSE event format:**
```
data: {"id": "1712054400000-0", "action": "buy", "symbol": "BTCUSD", "price": "95000"}
: keepalive
```
---
## Python: Listen for Signals
```python
import requests
import json
import time
from dotenv import dotenv_values
_env = dotenv_values()
HEADERS = {"api-key": _env["blave_api_key"], "secret-key": _env["blave_secret_key"]}
BASE_URL = "https://api.blave.org"
CHANNEL = "your_channel" # mapped name (not the raw code)
def handle_signal(data: dict):
"""Called once per TradingView alert. Put your trading logic here."""
print(f"Signal received: {data}")
action = data.get("action", "").lower()
symbol = data.get("symbol", "")
price = data.get("price", "")
if action == "buy":
print(f"→ BUY {symbol} @ {price}")
# place order here
elif action == "sell":
print(f"→ SELL {symbol} @ {price}")
# place order here
def stream(channel: str, last_id: str = "$"):
"""Open SSE stream and yield (last_id, data) for each signal."""
url = f"{BASE_URL}/sse/tradingview/stream"
params = {"channel": channel, "last_id": last_id}
with requests.get(url, headers=HEADERS, params=params,
stream=True, timeout=None) as resp:
resp.raise_for_status()
buf = ""
for chunk in resp.iter_content(chunk_size=1, decode_unicode=True):
buf += chunk
if buf.endswith("\n"):
line = buf.strip()
buf = ""
if line.startswith("data: "):
data = json.loads(line[6:])
last_id = data.get("id", last_id) # save for reconnect
yield last_id, data
# ": keepalive" lines are ignored automatically
def run():
last_id = "$" # "$" = only new signals
while True:
try:
print(f"Connecting to channel '{CHANNEL}' (last_id={last_id})...")
for last_id, data in stream(CHANNEL, last_id):
handle_signal(data)
except Exception as e:
print(f"Disconnected: {e} — reconnecting in 3 s...")
time.sleep(3) # last_id preserved → resumes from exact point
if __name__ == "__main__":
run()
```
---
## Notes
- **Reconnection:** Always pass `last_id` from the last received message. The Redis Stream buffers the last 1000 messages, so signals are not lost during short disconnections.
- **Keepalive:** The server sends `: keepalive` every 15 s. Your HTTP client ignores these — no special handling needed.
- **One signal per connect (test):** To grab just the next signal without looping, call `next(stream(channel))`.
- **Execution timing:** Act on the signal immediately when received, not on the next bar close — see backtest execution timing note in `examples/backtest-holder-concentration.md`.