Skip to content

feat(symbol)!: send symbols instead of counter_ids, add multi-leg orders - #128

Draft
sunli829 wants to merge 1 commit into
mainfrom
feat/symbol-based-requests
Draft

feat(symbol)!: send symbols instead of counter_ids, add multi-leg orders#128
sunli829 wants to merge 1 commit into
mainfrom
feat/symbol-based-requests

Conversation

@sunli829

Copy link
Copy Markdown
Contributor

Requests become symbol-based end to end, the embedded ETF directory and the
counter_id conversion go away in both directions, and submit_multileg_order
is added.

⚠️ Blocked on the backend and on an SDK branch

Two things must land before this can ship:

  1. Cargo.toml points at longbridge/openapi@feat/remove-counter-id-conversion,
    not main. That branch carries the symbol-based request contract (it is also
    where the 25,590-line US-ETF.csv / US-IX.csv / US-WT.csv directory was
    deleted). Switch back to main once it merges there.
  2. Production does not accept symbol= yet. Verified directly: production
    answers /v1/quote/index-constituents?symbol=… with
    invalid GetIndexConstituentsReq.CounterId, returns an empty record from
    comp-overview, and 500s on etf-dividend-info. Only canary serves the new
    contract today, so merging this before the backend rolls out will break every
    symbol-taking tool.

Request path

src/counter.rs — a re-export of the SDK's conversion helpers backed by the
embedded directory — is deleted. All 55 call sites now pass the caller's symbol
straight through, and the 61 counter_id query parameters become symbol /
symbols.

Parameter names are not guesswork:

  • 44 endpoints take the name the SDK branch uses (uniformly symbol;
    symbols for company-professionals and sharelists/{id}/items;
    comparison_symbols for compare/valuation).
  • 12 raw endpoints the SDK does not wrap were probed individually against
    canary — all accept symbol=.
  • 1 endpoint could not be verified: POST /v1/quant/run_script returns
    code=14 for symbol and counter_id, with several script bodies. The
    failure is symmetric, so it is not a parameter problem, but the endpoint is
    unavailable on canary. It sends symbol for consistency.

Response path

Wherever the backend still returns a counter_id, it returns the matching
symbol alongside it, so counter_id is now passed through untouched. This
fixes a live bug: the old rename emitted the key twice.

before: {"counter_id":"ST/HK/700","symbol":"700.HK"} → {"symbol":"700.HK","symbol":"700.HK"}

looks_like_counter_id is replaced by is_field_name, which keeps any
non-identifier map key out of the snake_case pass. That fixes a second live bug —
the backend's new symbol-keyed map was being mangled:

before: {"symbols":{"AAPL.US":{…}}} → {"symbols":{"a_a_p_l._u_s":{…}}}
after:  {"symbols":{"AAPL.US":{…}}} → unchanged

serialize/counter_id.rs, counter_id_to_symbol and FieldKind::CounterId(s)
are all removed.

No local ETF table, so upstream decides

Nothing distinguishes an ETF from an index or a stock before the call any more,
so is_etf is gone. Its two callers were reworked and both were checked against
live data first:

constituent asks for the ETF asset allocation first and falls back to index
constituents on an empty answer. The existing fallback was already there; only the
is_etf guard was removed. Production data:

symbol class etf-asset-allocation
SPY.US / QQQ.US / 2800.HK ETF info[4]
.DJI.US / .IXIC.US / HSI.HK index info[0]
AAPL.US / 700.HK / MSFT.US stock info[0]

Order matters and is preserved: SPY.US also resolves on index-constituents
(503 rows), so asking that first would shadow the allocation.

dividend drops the ETF branch entirely and always uses company-dividends.
"Try the ETF endpoint, fall back when empty" does not work here — measured on
production, etf-dividend-info answers for plain stocks too (AAPL.US
dividend_ttm=1.06, dividend_frequency=4), so it would never fall back. But
company-dividends turns out to be a strict superset for ETFs:

etf-dividend-info (SPY)  fiscal_year_info[5]   FY2019 → 5.48 / 1.85%
company-dividends (SPY)  dividend_history[5]   FY2019 → 5.48 / 1.85% / growth 9.16%
                       + payout_ratios[5] + dividend_payout_history[135] + recent_dividends

Same figures, more fields. This also removes a schema wart: the ETF branch's
response never matched the declared DividendResponse, which only models the
generic and US-company shapes.

Input contract

Callers must pass canonical symbols. The backend matches HK codes exactly, and a
padded code fails silently200 with an empty record, not an error:

symbol=700.HK    → {"name":"腾讯控股", …}
symbol=00700.HK  → {"name":"", …}
symbol=9988.HK   → {"name":"阿里巴巴-W", …}
symbol=09988.HK  → {"name":"", …}

The old conversion used to strip that padding. Normalizing it here was considered
and deliberately rejected: 700.HK is the required input form.

New tool: submit_multileg_order

Wraps the SDK's submit_multileg (POST /v1/trade/order/multileg) — vertical
spreads, straddles, strangles, collars, covered calls/puts. All legs are submitted
as one strategy order. Scoped under trade.write, excluded from /v2 like the
other order-write tools, with zh-CN / zh-HK descriptions. Input validation covers
empty legs, an unknown strategy, and a non-positive ratio_quantity (leg
direction comes from strategy + side, so the sign carries no meaning and the
server rejects it with 602001).

Tool count 163 → 164 across README.md and server.json.

Verification

  • cargo +nightly fmt --check, cargo clippy --all-features --all-targets
    (0 warnings), cargo test (144 passed).
  • Live replay against canary. A mock upstream captured the exact requests the
    server now emits for every migrated tool; those 52 requests were then replayed
    against canary: 48/50 GET OK, 0 endpoints still demanding counter_id, plus
    POST /v1/dailycoins/batch-check-support with symbols OK.
  • The 2 failures are pre-existing canary breakage, not regressions:
    option-volume-stats/daily 500s on every combination of symbol and timestamp
    and 500s identically with counter_id=, while its non-daily sibling succeeds
    with the same symbol; quant/run_script is the unverified endpoint above.
  • Three write endpoints were not replayed against the live paper account
    (POST /v1/notify/reminders, dailycoins/create, sharelists/{id}/items);
    their parameter names come from the SDK branch.
  • submit_multileg_order was driven end to end through the MCP server and
    produced exactly the SDK's request body.

🤖 Generated with Claude Code

The backend now resolves user-facing symbols itself, so this server no
longer converts between `TSLA.US` and `ST/US/TSLA` in either direction.

Request path: `src/counter.rs` (a re-export of the SDK's conversion
helpers, backed by a 25,590-line embedded ETF/index/warrant directory) is
gone. All 55 call sites pass the caller's symbol through, and the 61
`counter_id` query parameters become `symbol` / `symbols`. Parameter names
follow the SDK for the 44 endpoints it wraps; the remaining raw endpoints
were each confirmed against a live environment.

Response path: wherever the backend still returns a `counter_id` it
returns the matching `symbol` alongside it, so `counter_id` is now passed
through untouched. Renaming it would emit the key `symbol` twice — which
the old transform already did on `/v1/quote/short-positions/*`. Also
replaces `looks_like_counter_id` with `is_field_name`, so a map keyed by
symbol (`{"symbols": {"AAPL.US": …}}`) is no longer mangled into
`a_a_p_l._u_s` by the snake_case pass.

Without a local directory nothing distinguishes an ETF from an index or a
stock before the call, so `is_etf` is gone and upstream decides instead:

- `constituent` asks for the ETF asset allocation first and falls back to
  index constituents on an empty answer. Verified live: ETFs return four
  allocation groups, indexes and stocks return none.
- `dividend` always uses `company-dividends`, which covers ETFs and
  returns a superset of `etf-dividend-info` (same TTM figures and
  fiscal-year rows, plus payout ratios and individual payout events).
  This also fixes the ETF branch's response not matching the declared
  `DividendResponse` schema.

Callers must pass canonical symbols: the backend matches HK codes exactly,
so `00700.HK` is not `700.HK` and comes back as an empty record.

Also adds `submit_multileg_order`, exposing the SDK's multi-leg option
combination orders (vertical spreads, straddles, strangles, collars,
covered calls/puts) as the 164th tool, scoped under `trade.write`.

BREAKING CHANGE: tool responses no longer rename `counter_id` to `symbol`
or rewrite its value; `symbol` now comes from the backend directly.
@sunli829
sunli829 marked this pull request as draft August 26, 2026 06:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant