TQX (tqx.trade) HK/US stock quant: factor analysis, strategy backtests, and agent-driven trading (paper or live) via the TQX CLIs. Use when the user wants to run factor IC/IR analysis, backtest a Python trading strategy on Hong Kong or US stocks, or set up agent-automated trading on a paper or live account (e.g. "backtest a moving-average strategy on AAPL", "analyze a momentum factor on HK stocks", "let the agent trade my TQX account", "join the TQX trading competition").

GitHub
安装命令
npx skhub add starchild-ai-agent/tqx
Markdown
SKILL.md

TQX Quant — Factor Analysis & Strategy Backtest

TQX (https://www.tqx.trade) is a HK/US stock quant platform. This skill drives it through the official tqx-cli pip package.

Official TQX skills (source of truth — read these first)

TQX publishes two official skills. Fetch the raw URLs directly (the repo UI is a JS SPA; only /-/git/raw/ URLs return content):

1. tqx-research — factor analysis, strategy backtest, workflow management (Python tqx-cli, email/password login):

  • Main: https://cnb.cool/liangyunzhijing/clis/-/git/raw/main/skills/tqx-research/SKILL.md
  • References (read before generating code — do NOT guess APIs):
    • commands: .../skills/tqx-research/references/commands.md
    • strategy templates: .../skills/tqx-research/references/strategy_templates.md
    • US API: .../skills/tqx-research/references/stock_us_api.md
    • HK API: .../skills/tqx-research/references/stock_hk_api.md
    • tqx_data: .../skills/tqx-research/references/tqx_data_usage.md (replace ... with https://cnb.cool/liangyunzhijing/clis/-/git/raw/main)

2. tqx-trading — account/positions/orders/trades queries + authorized order placement (TypeScript @tqx-ai/cli@0.1.3, API-key auth, pin the version):

  • Main: https://cnb.cool/liangyunzhijing/clis/-/git/raw/main/skills/tqx-trading/SKILL.md
  • OpenAPI: https://www.tqx.trade/openapi/v1/openapi.json (interactive: /openapi/v1/scalar)

Routing rule: research/backtest/factor work → tqx-research (tqx-cli, Python). Live account state & order execution → tqx-trading (tqx, TS CLI). They use DIFFERENT CLIs and DIFFERENT auth (email login vs TQX_API_KEY) — don't mix them. This skill below adds what the official docs don't cover: verified onboarding, failure-mode table, and agent-automation patterns.

User onboarding (first-time setup, ~3 minutes)

  1. Register at https://www.tqx.trade (email signup). A PAPER (simulation) account is provisioned automatically — all workflows below are safe to run on it. Paper and live are the SAME API: tqx trading account reports "mode": "PAPER" or "mode": "LIVE", and every command (positions, orders place|modify|cancel, trades) is identical on both. Switching to real money is an account-level change on TQX, not a different code path — which is exactly why order-size caps and the human-approval rule below matter.

    Trading competition: TQX runs an agent trading competition — entry and rules at https://luma.com/dawwrdxk?tk=adzfZA. Competition accounts are created from the same dashboard (https://www.tqx.trade/hk/competition-manage?catalog=2), which is also where the TQX_API_KEY for the trading CLI is issued.

  2. Collect credentials securely: agents must use request_env_input for TQX_EMAIL and TQX_PASSWORD — never ask for credentials in chat.

  3. Install + login + verify:

pip install tqx-cli
tqx-cli login --email "$TQX_EMAIL" --password "$TQX_PASSWORD"
tqx-cli --json balance        # non-error response = onboarding complete

Token is cached in ~/.tqx/config.yaml.

  1. First quick win (recommended demo): run the "5d momentum" factor analysis from §1 below — completes in ~1–2 min and produces IC/IR/Sharpe numbers you can show immediately.

⚠️ Token expiry gotcha: both accessToken AND refresh_token can expire together. Do NOT only match one specific error string — re-login on ANY response containing LOGIN_REQUIRED, 均已失效, or Please log in to continue. A strict matcher silently fails and every later call returns auth errors.

CLI command map

factor_create / factor_run / factor_result / factor_list / factor_delete
strategy_create / strategy_run / strategy_result / strategy_list / strategy_delete
backtest_result   # per-backtest detail: summary/account/position/profit/trade/log sections
workflow_list / workflow_stop / balance

Add --json for machine-readable output.

1. Factor analysis (cross-sectional IC/IR)

tqx-cli --json factor_create --market us --name "5d momentum" \
  --formula "close/ref(close,5)-1" \
  --start-date 20250101 --end-date 20250701 --group-number 2
tqx-cli --json factor_run <factor_id>          # waits and returns results

Result parsing gotcha: the result JSON has TWO formats depending on backend version — legacy nodes[].result_json and current root-level factor_analysis. Handle both. Key metrics: IC mean, IR, t-stat, annualized group returns, Sharpe.

2. Strategy backtest (panda_backtest engine)

tqx-cli --json strategy_create --market us --name "AAPL SMA cross" \
  --code "$(cat strategy.py)" \
  --start-date 20250101 --end-date 20250701 \
  --start-capital 1000000 --commission-rate 0.0003 --slippage 0.001 --frequency 1d
tqx-cli --json strategy_run <strategy_id>

Strategy code contract

from panda_backtest.api.api import *            # common trading API — MANDATORY
from panda_backtest.api.stock_us_api import *   # US market data API — MANDATORY
# HK market: from panda_backtest.api.stock_hk_api import *
import tqx_data

def initialize(context):
    # Account ID is per-user — discover it once, do NOT hardcode.
    # The '8888' from CN-market docs does NOT exist for HK/US backtests.
    context.account = list(context.stock_account_dict.keys())[0]
    context.symbol = 'AAPL.NB'   # symbol format: US = TICKER.NB (NOT .US!), HK = 00700.HK
    context.closes = []

def handle_data(context, data):
    account = context.stock_account_dict.get(context.account)
    if account is None:
        return
    bar = data.get(context.symbol)
    # bar CAN be None — with a WRONG suffix it is None EVERY day (silent 0-trade run)
    if bar is None or getattr(bar, 'close', None) is None or float(bar.close) <= 0:
        return
    price = float(bar.close)
    context.closes.append(price)
    if len(context.closes) < 20:
        return
    fast = sum(context.closes[-5:]) / 5
    slow = sum(context.closes[-20:]) / 20
    position = account.positions.get(context.symbol)
    quantity = 0 if position is None else position.quantity
    sellable = 0 if position is None else position.sellable
    if fast > slow and quantity == 0:
        buy_qty = int(account.cash * 0.9 // price)
        if buy_qty > 0:
            order_shares(context.account, context.symbol, buy_qty, style=MarketOrderStyle)
            print(f"BUY {buy_qty} @ {price:.2f}")   # print() = strategy log; SRLog is FORBIDDEN
    elif fast < slow and quantity > 0 and sellable > 0:
        order_shares(context.account, context.symbol, -sellable, style=MarketOrderStyle)
        print(f"SELL {sellable} @ {price:.2f}")

Reference run: this exact code on AAPL.NB, 20250101–20251231, produces 18 real fills (check with backtest_result <backtest_id> --section trade --all-pages).

Hard rules learned from real failures:

SymptomRoot causeFix
禁止使用危险函数 dir()Security filter blocks introspectionNever use dir()/eval()/exec; to inspect context, raise Exception(str(...)) and read the error_detail in run logs
访问了不存在的键 on orderWrong account ID (e.g. '8888')Use list(context.stock_account_dict.keys())[0]
order_shares() missing 1 required positional argumentCalled with 2 argsSignature is order_shares(account, symbol, quantity)
股票X不属于当前股票回测市场Wrong symbol suffixUS = TICKER.NB (NOT .US!), HK = XXXXX.HK; .O/.N/bare tickers are rejected
Backtest SUCCESS but 0 trades, 0 log lines, profit = 0.0Symbol suffix .US (or any wrong suffix) → every bar returns None → defensive guard skips all days silentlyUse TICKER.NB for US stocks. ALWAYS verify via backtest_result <id> --section trade — SUCCESS ≠ trades executed
Run FAILED immediately (~0.3s, node failed)SRLog is not a valid API in strategy codeUse plain print() for strategy logging (visible in --section log)
Run status FAILED but NO failed node (all nodes success/pending)Transient TQX queue/scheduler error, not your codeResubmit the same workflow once — typically succeeds in ~30s. Only debug strategy code if a node actually failed
Backtest SUCCESS but 0 trades, 标的不在当前回测数据集内Date range beyond ingested market data (recent months may not be loaded even though the benchmark series exists)Shift the window earlier (e.g. use last year's range); verify trades>0 in the trade section before trusting metrics
frequency rejectedOnly 1d and 1M are valid—

Reading results

strategy_run/strategy_result returns run status + node outputs. For full detail, extract the backtest id from run logs (BacktestNodeIdentifier: line) or node output, then:

tqx-cli --json backtest_result <backtest_id> --section summary   # profit, alpha, beta, sharpe, IR
tqx-cli --json backtest_result <backtest_id> --section trade     # ⚠️ always check trades executed
tqx-cli --json backtest_result <backtest_id> --section log       # per-order rejection reasons

A run can report SUCCESS with zero trades (orders silently rejected day by day). Always confirm the trade section is non-empty before reporting performance numbers.

Daily equity curve (--section profit)

The profit section is the day-by-day NAV series — one row per trading day with gmt_create (YYYYMMDD), strategy_profit (cumulative strategy return, decimal) and csi_stock (cumulative benchmark return, decimal).

# ⚠️ --all-pages does NOT work for the profit section: it silently returns only page 1 (100 rows).
tqx-cli --json backtest_result <backtest_id> --section profit --page-size 1000
# -> pagination {total: 252, page: 1, page_size: 1000, total_pages: 1}   ✅ full year in one call

Two more behaviours to handle (reference: AAPL.NB, full-year 2025 = 252 rows):

  • Rows come back unsorted — always sort by gmt_create ascending before plotting; do not assume the API order.
  • Values are decimals, not percent (-0.1174 = −11.74%) and are already cumulative, so plot them directly; don't compound them again.
  • The last row must match the summary section's total return — use that as your correctness check.

Cache the series locally (one file per run) instead of re-fetching: the call costs ~2–4 s and the data is immutable once the run is done.

Debugging failed runs

Error details are NOT in the top-level status — fetch run logs and read error_detail, which includes the exact strategy line number and exception message:

from tqx_cli.config import load_config
from tqx_cli.auth import require_login
from tqx_cli.workflow import get_run_logs
cfg = load_config(); token, uid, _ = require_login(cfg, cfg.get("_config_path"))
for l in get_run_logs(cfg, token, uid, run_id).get("logs") or []:
    if l.get("error_detail"): print(l["error_detail"])

Cost & pacing

Backtests are billed in TQX compute credits (tqx-cli balance). A 6-month daily-frequency single-stock backtest takes ~2 minutes wall time. Poll strategy_result every 3s; don't fire concurrent runs of the same workflow.

3. Templates (copy-paste starting points)

T1 — Momentum factor (cross-sectional, whole market)

tqx-cli --json factor_create --market us --name "5d momentum" \
  --formula "close/ref(close,5)-1" \
  --start-date 20250101 --end-date 20250701 --group-number 5

Other verified formulas: mean reversion -(close/ref(close,5)-1), volume surge volume/mean(volume,20). Factor mode is whole-market cross-sectional only (--market hk|us) — it CANNOT target one stock; for single-stock questions use a strategy backtest (T2).

T2 — Single-stock backtest (SMA cross on AAPL)

Use the strategy code contract in §2 verbatim — it IS the template. Change context.symbol and the signal logic only. Keep the None-bar guard and dynamic account discovery.

T3 — Agent-driven automated trading loop

Pattern verified over a 10-round live run (~21 min, end-to-end):

loop every N minutes:
  1. fetch live positions + account state
  2. compute signal (factor value or strategy rule)
  3. decide: buy / sell / hold  ← agent reasoning step
  4. place order (paper account, small fixed qty during development)
  5. journal the decision: {ts, reasoning, tool_calls, params, result, position_delta}

Hard rules for automation:

  • PAPER account by default. Live trading uses the identical command surface, so nothing in the code stops a live order — only your gating does. Require an explicit user approval before pointing the loop at a live account, keep a hard order-size cap, and always pass --idempotencyKey so a retry can never double-fill.
  • Journal every decision (JSONL is enough) — users must be able to audit why each trade happened.
  • Re-login on ANY auth-ish error string (see token gotcha above); a mid-loop token expiry must self-heal, not kill the loop.
  • Check compute balance before each backtest-class call to avoid silent overdraft.

T4 — Studio: ready-to-run companion UI (templates/studio/)

A complete, tested web workbench ships with this skill — do NOT build a dashboard from scratch. Copy templates/studio/ into the user's workspace, start it, and adapt.

Files:

FileRole
server.pyStdlib HTTP backend (port 8090, no pip deps). Proxies tqx-cli, auto re-login on token expiry, serves all /api/* routes
index.htmlSingle-file frontend: factor analysis, backtest submit/history, positions, agent decision timeline
agent.pyAgent trading loop (LLM via proxied_post + tool calls), JSONL decision journal
backtests.py / strategies.py / journal.pyDisk persistence modules (see data spec below)

UI language: the template ships English-only. Any Chinese you see at runtime comes from the TQX API itself (metric key names, error strings) — do not translate those, they are matched against.

Run: python3 server.py from the studio dir (background), then preview(action="serve") on it. Credentials come from TQX_EMAIL / TQX_PASSWORD env vars (collect via secure input — never hardcode).

Design spec (keep when restyling):

  • Minimalist quant-tech aesthetic: dark-first, monospace numerals, dense tables, no decorative graphics. Avoid bright or cyberpunk themes.
  • History rows must be loadable: clicking Load on any history record re-displays its full result in the top result area (factor runs → factor metrics/charts via loadBt; strategy runs → dedicated strategy-result card via loadStgBt: return vs benchmark, annualized, Sharpe, max drawdown, trade count, params + code link). A history table that only lists rows without load-back is incomplete.
  • UI exposes only high-frequency params (formula, market, date range, groups, rebalance, direction) + account switcher + NAV curve + decision timeline. Everything complex (custom strategy code, stock pools, commission/slippage) stays in the conversation layer — the agent has the full CLI surface, the UI must not duplicate it.
  • Amounts: format gold/cash values with a single dedicated formatter two competing formatters produce inconsistent output. Check dark-mode contrast on every card.

Persistence & paths (why the two directories):

tqx-cli writes its login config to /root/.tqx/config.yaml, which lives on the container's ephemeral layer and is wiped on every machine restart — the agent would silently lose its session. Only /data/workspace survives restarts, so the studio mirrors the config into /data/workspace/.tqx/ and syncs both ways on startup (sync_tqx_config() in server.py, _sync_tqx_config() in agent.py).

These paths are intentionally fixed, not a portability oversight: every agent has the identical layout — /data/workspace = persistent volume, /root = ephemeral. Keep the rule when extending the studio: anything that must survive a restart is written under the workspace, never under /root or /tmp. In-studio state (data/backtests, data/journal, data/strategies) is already __file__-relative, so it inherits persistence as long as the studio itself is copied into the workspace. If you ever run this outside a Starchild machine, override the persistent root — nothing else in the code assumes an absolute path.

Data storage spec:

  • data/backtests/index.json + one JSON per run — MUST persist the full strategy code string (not a truncated preview) so any run can be reloaded and re-edited.
  • data/journal/*.jsonl — one line per agent decision, tagged source: manual | agent, append-only, never rewritten.
  • data/strategies/ — named saved strategies. All state is plain JSON on disk; no DB.

Testing spec (before declaring the studio 'working'):

  1. curl localhost:8090/api/health (or any GET route) returns JSON — backend alive.
  2. Submit one REAL backtest through the UI and confirm it appears in the history panel with full code + metrics. A rendered page alone is NOT verification.
  3. Verify fills exist via backtest_result <id> --section trade — SUCCESS status with 0 trades means a symbol-suffix bug (see failure table).
  4. Kill/restart server.py and confirm history persists (disk, not memory).
发现
标签

此技能尚未发布标签。

版本
最新版本元数据

版本

v2026.09.24

发布时间

2026年9月24日

分类

未分类

许可证

未指定

源路径

tqx

默认分支

main

最新提交

cce29fd

Tree SHA

baca3ec