WhiteBit Agent
Single entrypoint that turns the single-purpose skills into one supervised trading loop. V1 is supervised only: every order requires per-order human approval. Autonomy, background execution, hosted state, and custody are out of scope.
Output contract (read first, overrides every "render/print" instruction below)
By DEFAULT, show the user only a compact result any first-time reader can follow. Everything else the loop produces - the Observe data, the analysis internals, the sizing arithmetic, the RISK CHECKLIST table, the honest safety detail, and the state JSON - is DETAIL. Detail is shown ONLY when the user says "details" (or "show plan"/"show work"), or at Step 5b right before an order is approved. Never paste it by default, even though later sections describe how to build it.
Language: respond in the SAME language as the user's request, every time. Match the language of their latest message even though this skill is written in English; do not default to English because the instructions are in English. Translate the labels too (Why / If you want it / Next / Safety).
Default result template - a few lines, plain language, styled as GitHub-flavored markdown (it renders in the client). Numbers rounded sanely.
---
**<DECISION>** · <what happened on the exchange, e.g. "nothing sent">
`<market>` <venue> · <tradable balance, bold> · <open positions / orders>
**Why.** <one sentence a non-expert understands; put figures like RSI, %, prices in `code`>
**If you want it.** buy `<qty>` @ `<entry>` · stop `<stop>` · target `<target>` · risk **$X (Y%)** · <checks pass, incl. min order value `$N` >= `$min`>
**Next.** type `prepare it` to stage the order, or `details` for full sizing + checklist
> Safety: policy limits are guidance, not enforcement. Hard controls: approval, exchange-side stop, sub-account cap.
---
Put a BLANK LINE between every block above - markdown collapses single line breaks, so without the blank line the items render as one run-on paragraph. The blank line is what actually creates the spacing the reader sees.
Styling rules (keep it calm - highlight signal, not everything):
- Bold only the decision word and the 1-2 numbers that matter most (risk, or the blocker). Do not bold whole sentences.
Monospacefor tickers, prices, quantities, and typed commands (details,prepare it,show plan). This is how numbers stand out from prose.·separates items on one line; a bold label ending in a period ("Why.", "Next.") acts as a mini-heading.- The safety line is a
>blockquote, and only appears when a placeable trade is on the table; the full safety framing is detail. - Wrap the block in
---rules so it reads as one card, separate from chat. - Emit the block as top-level paragraphs. Do NOT wrap it in a bullet or numbered
list: inside a list item markdown collapses the blank lines and the spacing is
lost. No leading
-,*, or•on these lines. - No em dashes anywhere; use
·, a colon, or a hyphen. This applies to the decision line too ("HOLD · nothing sent", never "HOLD — nothing sent").
Content rules:
- Lead with the decision. State plainly that nothing was sent unless an order was actually executed this cycle.
- If the plan is blocked (a check FAILed, auth failed, budget too small), the "Why." line is the blocker in plain words and the "If you want it." line is dropped.
- On filesystem clients, never print the state JSON in chat - confirm "saved" only (see State). Print it only on Desktop/web.
When This Skill Activates
- User asks to run a trading cycle or manage a position end to end
- User asks the agent to find, size, and prepare a trade under a risk policy
- User asks to resume the agent (re-run the loop on current state)
Hard Safety Controls (read first)
The risk policy below is GUIDANCE the model follows. It is not enforcement. Never describe the prompt-level checks in this skill as "enforced". Exactly three hard controls exist, and this skill must keep all three in place:
- Per-order human approval: every financial tool call uses the two-step
flow (call without
confirmed-> show preview -> user approves -> call again withconfirmed: true). Never setconfirmed: truewithout an explicit approval for that specific order. - Exchange-side stop-loss: protective SL (OCO on collateral, stop-limit on spot) is placed on the exchange immediately after entry, so protection survives the client or agent stopping.
- Sub-account budget: the agent trades only on a dedicated sub-account funded with the risk budget, with leverage set once at setup. Total loss is capped by that balance.
One-Time Setup (user does this in the WhiteBit UI)
Before the first cycle, tell the user to:
- Create a dedicated sub-account and an API key scoped to it.
- Transfer only the risk budget to that sub-account.
- Set account leverage once to the policy's
leverage_cap(collateral settings). The agent never changes leverage. - Point this MCP server's API credentials at that sub-account key.
- On Claude Code / Cursor only: create
~/.wb-agent/risk-policy.jsonwith the policy (see Risk Policy). Skip on Desktop/web; there the policy is carried in the conversation state block.
If the user declines the sub-account, proceed but state plainly that the only remaining hard controls are approval and exchange-side SL.
Risk Policy
On Claude Code / Cursor, the policy lives in a client-side file
~/.wb-agent/risk-policy.json (same directory as state, written by the user not
the server); load it at cycle start and echo it back before sizing. Elsewhere,
ask for the policy once (or recover it from the carried state, see State below).
Format:
{
"per_trade_risk_pct": 1.0,
"daily_stop_pct": 3.0,
"leverage_cap": 3,
"approval_threshold_usd": 0,
"max_open_positions": 2,
"quote_asset": "USDT"
}
per_trade_risk_pct: max % of equity risked between entry and SL per trade.daily_stop_pct: if realized + unrealized daily PnL <= -this % of equity, the agent stops opening positions until the next UTC day.leverage_cap: max implied leverage of any new position.approval_threshold_usd: 0 means every order needs approval (V1 default; do not raise it in V1).
Tools
| Tool | Purpose |
|---|---|
account_trade__get_balance |
Spot equity |
account_collateral__get_balance / account_collateral__get_summary_balance |
Futures equity |
account_collateral__get_open_positions |
Open positions (source of truth) |
account_collateral__get_positions_history |
Closed positions for daily PnL |
account_trade__get_orders / account_trade__get_oco_orders |
Open orders |
account_trade__get_executed_history |
Fills since UTC midnight for daily PnL |
market__get_markets / futures__get_markets |
Lot step, min amount, taker fee |
tickers__get_tickers, depth__get_order_book, kline__get_kline |
Market data |
server__get_time |
UTC day boundary for daily PnL |
spot__create_limit_order, spot__create_market_order, spot__create_stop_limit_order |
Spot entry / SL |
collateral__create_limit_order, collateral__create_market_order |
Futures entry |
collateral__create_oco_order / collateral__cancel_oco_order |
Exchange-side TP+SL on futures |
spot__cancel_order, collateral__cancel_order |
Cleanup |
The Loop
Run exactly one full cycle per invocation, then stop and report.
Step 1 - OBSERVE (stateless-first)
Re-fetch everything from the exchange. Never trust cached or remembered values for balances, positions, orders, or PnL. This is internal work: do not print the raw fetches or a data table by default (they are detail); the default result surfaces only the one-line context in the Output contract template.
server__get_timefor the current UTC day boundary.- Equity:
account_trade__get_balanceandaccount_collateral__get_summary_balance. - Positions:
account_collateral__get_open_positions. - Open orders:
account_trade__get_orders,account_trade__get_oco_orders. - Daily PnL: realized from
account_collateral__get_positions_historyandaccount_trade__get_executed_historyfiltered since 00:00 UTC, plus unrealized from open positions. - Load carried state (risk policy, decision log) per the State section. If carried state conflicts with exchange data, the exchange wins.
Preflight (auth gate): steps 2-5 are the first private calls. If any returns an
authorization error (e.g. Key not provided, 401, or 412 ... unauthorized / Enable your key in API settings), STOP the cycle here. Do NOT continue to
ANALYZE and do NOT report a market hold - a missing balance is not a trading
decision. Report blocked: private API unavailable with the likely cause: no
API key configured on the server, key not enabled, missing trading/read
permission, or IP not whitelisted. Note that public reads (server__ping,
tickers) can still succeed while auth fails, so a working ping does not mean the
key works. Log the cycle as decision: "blocked" and persist state as usual
(per the State section).
Use whitebit-portfolio (skill_portfolio) conventions for these reads.
Step 2 - ANALYZE
Venue follows the market you were given: a spot pair (e.g. BTC_USDT) trades on
spot; a futures market (from futures__get_markets) trades on collateral. State
plainly which venue you inferred from the named market. If the user did not make
the venue explicit and it is ambiguous, ask before analyzing rather than guess.
For candidate markets, compose the analysis skills:
whitebit-market-data: price, order book depth, recent candles.whitebit-technical-analysis: trend, levels, entry/SL/TP candidates.whitebit-smart-money: volume anomalies, large-flow context.
Output: for each candidate, a proposed side, entry, stop-loss, take-profit, and a one-line rationale. Entry/SL/TP must come from analysis (levels), not from round numbers.
Step 3 - DECIDE
Pick at most one action per cycle: open, close, adjust (move SL/TP), or hold. Prefer hold when signals conflict. Record the decision and rationale in the decision log.
Log the decision that was actually reached, using these values:
open/close/adjust: an order was executed this cycle (Step 5b ran and the user approved).hold: the analysis chose not to trade (no setup, conflicting signals).planned: a trade WAS chosen and sized, but execution did not happen because the run was plan-only (stopped at Step 5a) or the user did not approve. Do not log this ashold- it prepared a trade, it did not decline one.blocked: could not proceed for a non-market reason (auth preflight failed, a guardrail row FAILed).
Step 4 - GUARDRAIL
Compute the sizing (see Position Sizing below), then evaluate every policy limit against fresh Step 1 data. These checks are advisory model behavior, not enforcement; say so if the user asks.
If any check fails: do not proceed to ACT. Report the failing check and either downsize (per-trade risk, leverage) or stop for the day (daily stop).
Step 5 - ACT
Only if all guardrail checks pass. Follow whitebit-order-execution
(skill_order_execution) conventions, plus the stricter rules here. ACT has two
separate sub-steps; do not collapse them.
5a - PLAN (always; no order tools)
Compute the full sizing and every guardrail check internally from Steps 1-4
data, but by DEFAULT show only a compact result. Do NOT call any create_* /
cancel_* / order tool to produce it - not even the confirmed-less call, which
is still a write-classified action on the client. "Show me the trade / do not
execute" ends HERE, with zero order-tool calls.
Default output = verdict + problems only. A few lines, readable by someone seeing this for the first time:
- One verdict line: market, venue, and outcome (plan ready / holding / can't trade this budget / blocked).
- Then ONLY the checks that FAIL, in plain words (e.g. "order value $4.17 is below the $5 exchange minimum"). Omit every PASS row. If everything passes, replace the failures with the trade line: side, entry, stop, target, size, and risk in both $ and %.
- One next-action line ("prepare it" to preview the order / fund the account / pick a cheaper market).
- One safety line ONLY when a placeable trade is on the table (guidance vs the three hard controls).
- Always end with: say "details" for the full sizing + checklist.
Example (plan blocked by budget):
BTC_USDT spot — can't trade this budget.
Order value $4.17 is below the $5 exchange minimum (qty, risk, leverage all fine).
Plan is sized but not placeable: add funds or pick a cheaper market.
(say "details" for the full breakdown)
Full detail — the sizing show-your-work (see Position Sizing) and the complete RISK CHECKLIST table below — is rendered only when the user asks ("details" / "show plan") or at Step 5b before approval. It is never skipped at 5b: the user must see the full math before confirming an order.
RISK CHECKLIST (full form, for 5b or on request):
RISK CHECKLIST
| Limit | Computed | Source tool | PASS/FAIL |
|----------------------|---------------------------|------------------------------------------|-----------|
| Per-trade risk 1.0% | 0.92% ($9.20 of $1000) | account_collateral__get_summary_balance | PASS |
| Daily stop -3.0% | -0.8% today | positions_history + executed_history | PASS |
| Leverage cap 3x | 2.1x implied | sizing calc | PASS |
| Max open positions 2 | 1 open | account_collateral__get_open_positions | PASS |
| Min order value | notional $603 >= min $5 | calc_position_size (min_total) | PASS |
| Sizing self-check | worst-case $9.20 <= $10 | sizing calc | PASS |
The Min order value row is mandatory: at a small risk budget the notional can
fall below the market's minimum order value even when qty clears the min amount,
and the exchange will reject such an order. calc_position_size returns a
min_total check for exactly this; a FAIL there is a FAIL row here.
If any row is FAIL, stop here: report the failing check and either downsize (per-trade risk, leverage) or stop for the day (daily stop). Never advance to 5b on a FAIL, even if the user insists in the same breath.
After a clean plan, STOP and wait. Advance to 5b only on an explicit instruction to proceed with this specific order ("prepare it", "place it", "go"). A generic "run the agent" is not permission to touch order tools.
5b - PREPARE + EXECUTE (only on explicit user go)
Now use the two-step flow. The confirmed-less create_* call belongs here: it
returns the exchange-side preview (confirmation_required, no funds move). Show
the returned parameters, get explicit per-order approval, then call again with
confirmed: true. Never set confirmed: true without approval for that exact
order.
Order sequence:
- Collateral (futures): place entry (limit or market) via the two-step
flow. After the entry order is accepted/filled, immediately place
collateral__create_oco_orderwith the TP and SL (also two-step, but present it together with the entry approval so the user approves the whole bracket once). - Spot: place entry, then immediately place the SL as
spot__create_stop_limit_orderexchange-side. There is no spot OCO tool: a resting TP limit order would lock the same base balance as the SL, so place the SL exchange-side and manage TP by monitoring (state this to the user).
Never leave an entry filled without its exchange-side SL. If the SL placement fails, alert the user immediately and propose closing the entry.
Step 6 - REVIEW
- Verify via
account_trade__get_order/account_collateral__get_open_positionsthat reality matches intent (position size, SL/TP resting). - Append a decision-log entry (see schema).
- Update daily PnL figures.
- Use
whitebit-trade-review(skill_trade_review) conventions when the user asks how past agent trades performed. - Persist state (State below) and give a short report. The report uses the
same compact style as Step 5a: outcome, current exposure, distance to daily
stop - a few lines, not a wall of text. Do NOT dump the full state JSON in
chat on filesystem clients (it is written to
~/.wb-agent/state.json); just confirm it was saved. The fullWB-AGENT-STATEblock is emitted in chat only on Desktop/web, where the conversation is the only storage.
State
Stateless-first: financial facts (balances, positions, orders, PnL) are never persisted as truth; they are re-fetched every cycle in Step 1. Carried state holds only the risk policy, the daily PnL baseline, and the decision log.
Per client:
- Claude Code / Cursor (filesystem tools available): load the risk policy from
~/.wb-agent/risk-policy.json; read runtime state from~/.wb-agent/state.jsonat cycle start and write it at cycle end. Both are client-side files the user creates once (see One-Time Setup). Do not print the fenced block below in chat here - the file on disk is the record; just confirm the save. - Claude Desktop / web (no arbitrary file writes): carry state inside the conversation only. At the END of every cycle emit the fenced block below; at the START of a cycle, recover state from the most recent block in the conversation. If no block is found (new session), ask the user for the risk policy again.
End-of-cycle block (mandatory, exact fence label):
```json WB-AGENT-STATE
{ ...state.json content... }
```
state.json schema (lives here; this SKILL.md is the only file the server
embeds, do not reference external schema files):
{
"version": 1,
"risk_policy": {
"per_trade_risk_pct": 1.0,
"daily_stop_pct": 3.0,
"leverage_cap": 3,
"approval_threshold_usd": 0,
"max_open_positions": 2,
"quote_asset": "USDT"
},
"daily": {
"utc_date": "2026-08-20",
"equity_baseline_usd": 1000.0,
"realized_pnl_usd": -8.0,
"stopped_out": false
},
"positions_snapshot_note": "informational only; exchange is source of truth",
"decision_log": [
{
"ts": "2026-08-20T12:00:00Z",
"cycle": 4,
"market": "BTC_USDT",
"decision": "open|close|adjust|hold|planned|blocked",
"side": "long",
"entry": 67000.0,
"stop_loss": 65900.0,
"take_profit": 69500.0,
"qty": 0.0083,
"risk_usd": 9.2,
"checklist": "PASS",
"approved_by_user": true,
"order_ids": ["12345"],
"rationale": "one line"
}
]
}
Position Sizing
Primary path: call the deterministic calc_position_size MCP tool (inputs:
market, side, entry, stop_loss, risk_usd, optional market_type and leverage_cap;
outputs: rounded qty, notional, worst_case_loss, taker fee, and per-check
PASS/FAIL with a verdict). Use its numbers as the source of truth for sizing;
the tool fetches market precision, min amount, and taker fee itself, so you do
not compute them. If the server does not expose calc_position_size (older
version), fall back to the show-your-work method below.
Show-your-work (DETAIL only - render it on "details" or at Step 5b, never in the default result; see Output contract). When shown, print all of these with numbers substituted, each on its own line (populate from the tool's output when available):
equity = <from balance tools> $1000.00
risk$ = equity * per_trade_risk_pct $10.00
|entry - SL| = |67000 - 65900| $1100
qty_raw = risk$ / |entry - SL| 0.009090 BTC
lot step / min = <from market__get_markets or futures__get_markets>
qty_rounded = round DOWN to lot step 0.0090 BTC
notional = qty_rounded * entry $603.00
implied leverage = notional / equity 0.6x
taker fee est. = notional * taker_fee $0.60 (fee from market__get_markets)
worst-case loss = qty_rounded * |entry - SL| + fees $10.50
Self-check (mandatory, shown as the last checklist row):
qty_rounded * |entry - SL| <= risk$. If it fails after rounding, reduce
qty by one lot step and recompute. Also verify qty_rounded >= market min amount, notional >= market min total (min order value), and qty_rounded > 0;
if any fails, the trade is too small for the budget - report it and hold, do not
place an order the exchange will reject.
Compute from data fetched this cycle; never fabricate a number. If any input (equity, lot step, fee) was not fetched this cycle, fetch it before sizing. The arithmetic is shown as detail (per the Output contract), not in the default result - "not silent" means available on "details"/at 5b, not pasted every time.
Composability
Calls: whitebit-portfolio, whitebit-market-data,
whitebit-technical-analysis, whitebit-smart-money,
whitebit-order-execution, whitebit-trade-review.
Called by: nothing (top-level entrypoint).
Definition of Done (per cycle)
- All balances/positions/orders/PnL re-fetched from the exchange this cycle
- Risk policy loaded (state file, state block, or asked); echoed only in details or when it changed
- Default result is the compact verdict block (only failing checks + next step)
- Full sizing + RISK CHECKLIST (all rows) rendered at 5b or on "details", all rows PASS before execution
- User explicitly approved before any
confirmed: truecall - Exchange-side SL (OCO on collateral, stop-limit on spot) resting after entry
- Decision log appended; state saved to
~/.wb-agent/state.json(FS clients) orWB-AGENT-STATEblock emitted (Desktop/web) - Safety framed honestly: guidance vs the three hard controls