paper-test

API key required
Dev Tools

Paper trading (simulated) for CN/HK/US equities — place/cancel orders, query balance and positions, order/fill history, market session status. Quotes via Stocki gateway.

Install

openclaw skills install paper-test

stocki-trading-assistant

Paper trading for CN / HK / US equities. The skill wraps a locally-spawned paper-trading service; the service is auto-started on first use. Orders, positions, and fills are simulated — not a live brokerage account.

This file instructs the Agent. Do not quote implementation details (tokens, ports, file paths, script names) to the user unless they are explicitly troubleshooting via INSTALL.md.

Core principle

Use this skill when the user wants simulated trading — place or cancel orders, check balance/positions/P&L, order or fill history, market session status.

Do NOT use for real-money brokerage execution or personalized buy/sell recommendations.

Never fabricate balances, positions, fills, or order states. If a value is missing or the service is down, say so.

Paper only. Remind the user when relevant that fills are simulated and not investment advice.

Local API convention

All API calls go through scripts/_http.py Client() — do not hand-build curl with a fixed port or read auth material for the user.

  • Base URL & port: Client() reads state/port (lifecycle may pick 8000–8010). Do not hardcode :8000 in user-visible text.
  • Auth: Client() reads state/token and sends Bearer automatically. Never ask the user for this token or mention PAPER_TRADING_TOKEN.
  • Stocki quotes — same v3 API; quote upstream auth in config/main.yaml selects credentials:
    • bearer: STOCKI_GATEWAY_URL + STOCKI_API_KEY (export / SkillHub)
    • none: STOCKI_GATEWAY_URL only (private gateway, auth: none) Precedence: process env → optional skill-root .env. Never paste API keys in chat.
  • Content-Type: application/json for POST bodies.
  • Error envelope: {"error": {"code", "message", "recovery_hint", ...}}. _http.py maps recovery_hint — see Error handling.

Routing rules

Decide which reference to consult by user intent. Each rule names the reference file containing the contract, error mapping, and worked examples.

  • R1 — place order. Triggers: "买", "卖", "buy", "sell", "下单", "place order", "market buy", "limit sell". → references/place-order.md. Calls POST /v1/orders via scripts/_http.py.

  • R2 — cancel order. Triggers: "撤单", "取消", "cancel", "撤掉". → references/cancel-order.md. Calls DELETE /v1/orders/{order_id}.

  • R3 — account state. Triggers: "余额", "持仓", "盈亏", "balance", "positions", "P&L". → references/account-state.md. Calls GET /v1/accounts/{id} and GET /v1/accounts/{id}/positions.

  • R4 — order history. Triggers: "历史订单", "查订单 ord_xxx", "order history", "list orders". → references/order-history.md. GET /v1/accounts/{id}/orders, GET /v1/orders/{id}.

  • R5 — fills. Triggers: "成交", "fills", "成交记录". → references/fill-history.md. GET /v1/accounts/{id}/fills.

  • R6 — market status. Triggers: "市场", "开盘", "收盘", "market status", "open?". → references/market-status.md. GET /v1/capabilities and GET /v1/markets/{m}/status.

  • R7 — service troubleshooting. Triggers: "起不来", "没响应", "restart trading", "service status". → references/troubleshooting.md. Calls python3 scripts/lifecycle.py {start|stop|status|logs}.

  • R8 — events / reconnect. Triggers: "重连", "事件", "replay", "events". → references/events.md. GET /v1/events?since=<id>.

  • R9 — symbol metadata / lot-size discipline. Triggers: "一手", "最小买", "tick", "T+1 吗", "lot size", "minimum order"; OR any time the user-provided qty isn't obviously lot-aligned for the market (CN MAIN 100, STAR/CHINEXT 200+, HK varies, US 1); OR after a prior order rejected with INVALID_LOT_SIZE / INVALID_TICK_SIZE / SYMBOL_METADATA_UNAVAILABLE. → references/symbols.md. GET /v1/symbols/{symbol}. Round qty locally and resubmit; do not surface INVALID_LOT_SIZE to the user when you could round and proceed. Report original → rounded if you change it.

Routing decision rules

Apply when intent is ambiguous. Later rules override earlier ones.

  • Cancel without order_id → R4 (status=WORKING) first, then R2.
  • Sell / sellable qty → R3 before R1 (sellable = qty - qty_pending_t1 - qty_locked_sell).
  • Invalid-looking qty or price → R9 before R1; round locally when possible.
  • Poll order state ("filled yet?") → R4, not R1.
  • MARKET rejected (NO_ASK_LIQUIDITY / NO_BID_LIQUIDITY) → explain limit lock; suggest LIMIT (R1).
  • Service / connection errors → R7; run doctor before asking the user to restart.
  • recovery_hint=reconcile → R8, then retry the original action.

Confirmation discipline

Mutating calls (R1 place-order, R2 cancel-order) require user confirmation by default. Before sending the POST/DELETE, echo a one-line summary that includes side, quantity, symbol, type, price (if LIMIT), and account:

"即将下单:BUY 100 600519.SH MARKET (account: acc_default),确认?"

Read-only calls (R3–R6, R8, R9) never require confirmation.

Skipping confirmation

If the user's trade request itself contains any of these phrases, skip the confirmation for that single call:

"不要确认" "直接下单" "立即买入" "立即卖出" "skip confirm" "no confirm" "just do it"

Skip is per-call, not session-wide. The next trade request without the phrase requires confirmation again.

Output discipline

When relaying API results to the user, translate implementation detail into plain language. Follow the same hygiene as stocki-financial-reader.

CategoryDo not showShow instead
Secretsstate/token, Bearer headers, API key values, .env contentsnothing, or "local auth is configured"
Network127.0.0.1, port numbers, /v1/health URLs"local simulator" / "service unavailable"
StorageSQLite paths, data/, state/, logs/"records stored on this machine"
Configyaml URL fields, risk.max_working_ordersenv vars; limits in plain words
Errorsrecovery_hint, raw JSON, HTTP status alonetranslated message + next step
Field namesqty_pending_t1, qty_locked_sell, floating_pnlsellable qty, T+1 lock, unrealized P&L

Required: label simulated on first balance/trade touch in a session; distinguish open (WORKING) vs filled vs cancelled/rejected/expired; do not invent P&L when quote is missing; no buy/sell/hold advice.

Default account

Unless the user specifies an account, all R1–R5 calls target acc_default. Power users may add accounts in config/accounts.yaml; in that case ask the user which account they mean if it is not obvious from context.

Symbol naming

This skill expects exchange-coded symbols: 600519.SH, 00700.HK, AAPL.US. If the user gives a natural-language name ("茅台"), ask for the exchange code. If another name-resolver skill is installed in the environment, you may use it once, then continue with the returned code — this skill does not require any specific reader skill.

Service lifecycle

The local paper-trading process is auto-started on first _http.py call. The user need not invoke start manually. For diagnostics:

python3 {baseDir}/scripts/doctor.py
python3 {baseDir}/scripts/diagnose.py

R7 covers explicit lifecycle.py start | stop | status | logs. In normal chat, report outcomes only — do not ask the user to run lifecycle commands.

First-run account setup

Before auto-starting for the very first time, check whether accounts have been configured:

python3 -c "from scripts.init import is_first_run; print(is_first_run())"

If True, this is the first run and initial cash has not been set yet. Before proceeding, show the user the defaults and ask for confirmation:

即将初始化模拟账户,默认初始资金:

  • CNY(人民币):10,000,000 元
  • HKD(港币):10,000,000 元
  • USD(美元):50,000,000 元

是否调整?(直接回复"确认"或给出修改后的金额)

Once the user confirms (or provides custom amounts), write accounts.yaml and then proceed with the normal autostart:

python3 -c "
from pathlib import Path; from scripts.init import write_accounts_yaml
write_accounts_yaml(Path('.'), cny=<CNY>, hkd=<HKD>, usd=<USD>)
"

Important constraint: initial cash is a one-time setting. After the service starts for the first time, accounts are seeded from accounts.yaml into the local database. Subsequent edits to accounts.yaml have no effect — changing initial cash after first start requires resetting the simulation, which permanently deletes all trade history. Warn the user clearly if they ask to change initial cash after the service has already run. Do not mention accounts.yaml or SQLite in the user dialog — use the prompt above only.

Error handling

_http.py maps trading-assistant error recovery_hint (agent-internal — do not expose hint names to the user):

  • fix_input — surface translated message; ask the user to correct
  • retry — one retry already attempted; propagate if still failing
  • reconcile — R8: refresh account + events, then retry

DUPLICATE_CLIENT_ORDER_ID returns the existing order — treat as success. SERVICE_UNREACHABLE means autostart already tried; run R7 (doctor / lifecycle) silently, then tell the user the simulator is temporarily unreachable and you are fixing it.

Doctor / diagnose

Run before reporting setup issues:

python3 {baseDir}/scripts/doctor.py
python3 {baseDir}/scripts/diagnose.py

Report exit code 0/1 and the one-line status if failed — do not paste token or path details from the output.

Cross-ref

See INSTALL.md for install and .env setup. See references/*.md for per-endpoint contracts.