Skip to content

feat(trade): route asset commands to US APIs for US accounts - #337

Merged
hogan-yuan merged 19 commits into
mainfrom
feat/us-asset-routing
Oct 26, 2026
Merged

hogan-yuan merged 19 commits into
mainfrom
feat/us-asset-routing

Conversation

@hogan-yuan

@hogan-yuan hogan-yuan commented Oct 22, 2026 •

Copy link
Copy Markdown
Member

Draft: depends on longbridge/openapi#613. Before merging, drop the two chore(deps): TEMP … commits and bump longbridge to the released version.

Why

On LBUS accounts, longbridge portfolio and assets fail with code 12 internal server error. In the US UAT (4.3 / 5.4), the trace shows GET /v1/asset/account → unknown service PortfolioAsset. The same happens for cash-flow, exchange-rate, fund-positions, margin-ratio and the matching serve methods. The generic asset endpoints are AP-only, and only positions already branched to the US API.

What (US accounts only; AP behaviour unchanged)

Command US account
assets us_cash_and_buy_power: buying power, cash per currency, margin status
portfolio us_asset_overview + us_cash_and_buy_power
cash-flow us_transaction_history, paging through all pages
exchange-rate us_exchange_rates
fund-positions, margin-ratio Clear "not available for US accounts" error, no request sent
serve New trade.us_* methods. AP-only methods return a clear error naming the US equivalent

Also:

  • Fixes the empty Symbol column in the US positions table.
  • Updates help text, --schema and READMEs.
  • A separate commit bumps the SDK to the published 5.2.2 and handles the new OrderStatus::DelayedNotReported.

Not included: the TUI portfolio for US accounts, and using us_available_holding in the sell flow.

Testing

  • cargo test: all pass. 13 new tests cover routing, no AP call on US, paging, JSON shape and serve errors.
  • cargo clippy: the only new warnings are 5 double_must_use from async_trait, the same as on every existing trait method.
  • Not run against a live account.

Updated after real-data review

Checked against masked US prod responses. Changes in 93af6fe, with the SDK lock bumped to cab79039 in b56b028:

  • positions / portfolio: crypto entries get a full_symbol (BTCUSD.BKKT) built from the bare symbol plus market, the same way stock entries already have one. The crypto table now shows Qty / Cost / Last / Today P&L, since the SDK now returns them.
  • exchange-rate: a US account gets one row per rate type (am, reference) for the same pair. A rate_type column (spec_key) now tells those rows apart, and --schema explains universal_key (account channel, e.g. us_lb_apex) and spec_key (rate type).
  • cash-flow: the transaction symbol is already user-facing, so the counter_id conversion was removed.
  • cargo test passes (915 unit tests, plus the integration tests).

🤖 Generated with Claude Code

5.2.2 adds OrderStatus::DelayedNotReported (time-conditional orders being
monitored); handle it in the TUI orders view as a pending, cancellable status.
…erge)

Points the longbridge crate at a local checkout of the SDK branch
feat/us-asset-apis, which adds the US cash/buying-power, available-holding,
transaction-history and exchange-rate APIs. Drop this commit and bump the
dependency once that SDK release is published.
…counts

Add us_asset_overview, us_cash_and_buy_power, us_available_holding,
us_transaction_history and us_exchange_rates to the TradeApi seam (and so to
`serve` as trade.us_*).

trade.account_balance, cash_flow, stock_positions, fund_positions and
margin_ratio call AP endpoints that are not deployed in the US data center
(they fail with code 12). A US session now gets a clear error that names the
US counterpart where one exists, instead of a generic internal error; results
keep their SDK shape rather than being silently answered in another one.
The AP asset endpoints (/v1/asset/account, /stock, /fund, /cashflow,
/exchange_rates, /v1/risk/margin-ratio) are not deployed in the US data
center, so these commands failed with code 12 for US accounts. Route them by
the session's data center; AP behaviour is unchanged.

- assets: cash per currency, buying power and margin status from
  us_cash_and_buy_power.
- portfolio: US asset overview (stocks / options / crypto) plus
  cash_and_buy_power, instead of account_balance + stock_positions.
- cash-flow: us_transaction_history over --start/--end, walking all pages.
- exchange-rate: us_exchange_rates, keeping the {exchanges: [...]} shape.
- fund-positions, margin-ratio: fail early with "not available for US
  accounts" without calling the API.

Also show the symbol column of US positions from full_symbol / symbol (the
overview has no counter_id field, so it printed "-"), and update help text,
--schema descriptions and README for the changed commands.
- Crypto entries in the US asset overview carry the bare pair code
  (BTCUSD) plus market (BKKT): add the user-facing full_symbol
  (BTCUSD.BKKT), as stock entries have it, and print quantity, cost,
  last price and today's P&L in the crypto table now that the SDK
  keeps them.
- exchange-rate: US accounts get one row per rate type (am, reference)
  for the same pair; show spec_key as a rate_type column so the rows
  are told apart, and describe universal_key / spec_key in --schema.
- cash-flow: transaction symbol is already user-facing (BTCUSD.BKKT);
  drop the counter_id conversion.
On US accounts GET /v1/trade/order returns HTTP 500, and the CLI already
reroutes `order` / `order <id>` to the US order APIs and refuses
`order executions`. `serve` exposed neither US method, so a US session
got an opaque upstream failure.

Add us_query_orders and us_order_detail to the TradeApi seam (and so to
serve as trade.us_query_orders / trade.us_order_detail). Mark
today_orders, history_orders, order_detail, today_executions and
history_executions AP-only, with errors naming the US method to call.
Document the US mapping in the README and `serve -h`.
US accounts now print the same --format json keys, value formats and
top-level shape as AP accounts for assets, positions, portfolio,
cash-flow, exchange-rate, order (today / --history) and order detail.
AP-only fields with no US source are omitted; US-only data stays as
extra keys or under `us`.

- orders: array rows keyed order_id/symbol/side/...; symbol taken from
  the response when the gateway sends it, else derived from counter_id;
  status uses the AP enum names; time_in_force/tag kept as
  us_time_in_force/us_tag. Order detail reads the raw body so
  counter_id survives.
- cash-flow: flow_name/balance/time/description plus direction (amount
  sign, or quantity sign for zero-amount crypto transfers).
- exchange-rate: one row per pair (reference, else am), numeric rates.
- check --format json reports dc_region (ap / us).
- AP JSON builders extracted unchanged and covered by golden tests;
  sanitized US golden tests for every command.
US cash-flow balance is now the signed transaction amount, as AP balances are, instead of its absolute value. Crypto transfers and fees keep balance 0; direction still comes from the amount sign, or the quantity sign when the amount is 0.
Symbol <-> counter ID conversion belongs to the gateway. US order, order detail and holding rows now use symbol / underlying_symbol / full_symbol from the response only; when a row has none, symbol is omitted and the raw counter_id (underlying_counter_id) is passed through instead of a CLI-derived symbol.
…reviews and return raw US order detail in serve

- serve trade.us_order_detail returns the untouched GET /v1/us/orders/{order_id} body; order IDs are validated (digits only) before going into the path
- cancel/replace previews on US read the raw detail and show symbol (or the raw counter_id, unconverted)
- --schema marks (US only) / (AP only) fields for order, order detail, assets, cash-flow, portfolio and positions; net_assets / total_asset are documented as absent without a net liquidation value
- US order paging note is printed only when orders remain past the requested page, and help/schema say it goes to stderr
- US order detail always emits stock_name, currency and executed_amount, as AP does
…, and stop deriving symbols

- order executions and trade.today_executions/history_executions now work on
  US accounts (the execution APIs serve them with the AP shape)
- US cancel/replace previews fail with the reason (invalid order ID, order not
  found) instead of issuing a confirmation code without the order
- US preview summary and order detail --attached keep the raw counter_id when
  the response has no symbol; no counter_id-to-symbol derivation
- note past-the-last-page US order pages; size pages by the returned rows
- --schema: underlying_symbol/underlying_counter_id and US position extras;
  with_legend asserts every legend key is a field
- help examples use all-digit order IDs; tests for is_us_order_id and the
  serve order_id param error
…ncel previews

- order executions --history: US accounts read /v1/trade/execution/history
  (the SDK pages has_more); /v3/trade/execution/all answers HTTP 500 there.
  AP keeps the v3 paging loop.
- US cancel/replace previews: report "not found" only for HTTP 404 or a
  missing/null/non-object order; other failures say "could not fetch order".
- --execute on US accounts validates the order ID (digits only) before
  calling cancel/replace.
- Pretty order detail --attached shows counter_id when no symbol is returned.
- --schema: document top-level counter_id for order / order detail, add
  side and trade_done_at to order executions, and put the US_ORDER_LEGEND
  doc comment back on its item.
…rve execution sides

- Side backfill trusts total_count only when positive (the SDK reports a
  missing total_count as 0); otherwise it stops at a short page.
- Orders still unmatched after the range query are read from the order
  detail by order_id (at most 20, 4 at a time); failures only warn.
- An unresolved US execution side is printed as "Unknown", as on AP,
  in JSON and table output; schema, help and READMEs say so.
- Document that serve trade.today_executions / trade.history_executions
  stay raw (side "Unknown" on US) and should be joined with
  trade.us_query_orders or trade.us_order_detail by order_id.
- README.zh-CN: add the US v1 execution history caveat.
Only a first-page order list failure is an error now; a later page failing
prints a warning, stops paging and keeps the sides already found, so the
order-detail fallback only reads the rest. Without total_count, a page ends
the lookup only when shorter than both the requested limit and the previous
page, so a server-capped page size does not stop paging early.
hogan-yuan added a commit to longbridge/openapi that referenced this pull request Oct 23, 2026
## Why

LBUS (US data-center) accounts can't read balances, cash flow or
exchange rates through the SDK: the generic `/v1/asset/*` and
`/v1/risk/margin-ratio` endpoints are AP-only. On US they return `code
12 internal server error`, because the gateway reports `unknown service
PortfolioAsset` (the backend isn't deployed in US). This was found in
the US UAT (items 3.3 / 4.3 / 5.4).

## What

New `TradeContext` methods for US accounts
(`dc_restrict(DcRegion::Us)`):

| Method | Endpoint |
|---|---|
| `us_cash_and_buy_power(currency)` | `GET
/v1/us/assets/cash-and-buy-power` |
| `us_available_holding(opts)` | `GET /v1/us/assets/available-holding` |
| `us_transaction_history(opts)` | `POST
/v1/us/assets/transaction-history` |
| `us_exchange_rates()` | `GET /v1/us/assets/exchange-rates` |

- Response types follow the live `lb-portfolio-liberty` schemas (read
via inner-gateway reflection). The exchange-rate response is a two-level
map keyed by account channel (e.g. `us_lb_apex`) and rate type (`am` /
`reference`). The SDK flattens it into `exchanges[]` and keeps both keys
on each entry.
- `fund_positions` and `margin_ratio` are now AP-only
(`dc_restrict(DcRegion::Ap)`), so US accounts get a clear region error
instead of `code 12`. US accounts have no fund holdings or margin-ratio
data.
- Bindings: Rust (async + blocking), Python (sync + async +
`openapi.pyi`), Node.js (napi; `index.d.ts` regenerated with
`@napi-rs/cli` 3.8.0), Java JNI layer.
- Java classes and C/C++ are not included, following the precedent of
the existing US methods (`us_asset_overview` etc.), which are not
exposed there either.
- CHANGELOG updated.

## Gateway

All four routes are deployed to prod. `transaction-history` (559) and
`exchange-rates` (558) are new. `available-holding` (440) now has
`symbol2CounterID`, so callers pass `AAPL.US`. The routes are also bound
to MCP scopes (`account.read` / `trade.read`) on the HK and US
registries.

## Testing

- `cargo test -p longbridge --lib`: 76 passed. Clippy shows no new
warnings. Python and Node built locally, and the new methods were
checked by importing them.
- Against US prod with an LBUS API key: all four endpoints return data.
The real responses deserialize into the new types. This caught a bug,
fixed in `f8877d2ab`: the gateway returns both `symbol` and
`counter_id`, so the earlier `counter_id` alias caused a duplicate-field
error.

## Related

- Go SDK: longbridge/openapi-go#135
- Docs: longbridge/developers#1297
- Consumers (draft until this is released):
longbridge/longbridge-terminal#337, longbridge/longbridge-mcp#173

## Updated after real-data review

Checked against masked US prod responses (overview, cash-and-buy-power,
available-holding, transaction-history, exchange-rates). The new types
match. One gap fixed in cab7903: `USCryptoEntry` (used by
`us_asset_overview`) kept only 5 of the fields prod returns for a crypto
holding, so it had no `quantity`. It now has the same fields as
`USStockEntry` (`quantity`, `market`, `name`, prices, `today_pl`,
`position_side`, ...) in Rust, Python and Node.js. The `symbol` doc now
says it is the bare pair code (`BTCUSD`), and the user-facing symbol is
`<symbol>.<market>`. Also added a test using the prod response shapes
(`current_page` as a number, `us_lb_apex`/`am`/`reference` keys,
numeric-string timestamps). `cargo test -p longbridge --lib` passes (77
tests). `index.d.ts` was regenerated with `@napi-rs/cli@3.8.0`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
@hogan-yuan
hogan-yuan marked this pull request as ready for review October 26, 2026 01:54
@hogan-yuan
hogan-yuan merged commit 75b7c8d into main Oct 26, 2026
1 check passed
@hogan-yuan
hogan-yuan deleted the feat/us-asset-routing branch October 26, 2026 01:58
hogan-yuan added a commit that referenced this pull request Oct 26, 2026
Bump `0.30.0` → `0.31.0` for release.

## Included since v0.30.0
- **Longbridge US accounts** (#337): `assets`, `portfolio`, `positions`,
`cash-flow`, `exchange-rate`, `order` (today and `--history`) and `order
detail` call the US asset and order APIs for US accounts, built on
longbridge SDK 5.3.0. Their `--format json` output keeps the AP keys and
shape; US-only fields are marked `(US only)` in `--schema`, and `check
--format json` reports `dc_region`.
- **`order executions` on US accounts** (#337): history comes from
`/v1/trade/execution/history`, which is not filtered by execution time
and can lag across days; `side` is filled from the account's orders and
shows `Unknown` when it can't be resolved.
- **AP-only commands and `serve` methods** (#337): `fund-positions` and
`margin-ratio` on a US account fail before sending a request and name
the alternative; `serve` gains `trade.us_query_orders` and
`trade.us_order_detail`.
- **Safer US cancel / replace** (#337): the preview refuses to issue a
confirmation code when the order can't be fetched, and US order ids must
be digits.
- **`rank --count`** (#336) is honored.

Tag `v0.31.0` after merge to trigger the release build.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
hogan-yuan added a commit to longbridge/skills that referenced this pull request Oct 26, 2026
…ount limits (#68)

## Why

Found in the LBUS user acceptance test:

- **5.4 "show my positions" failed for an LBUS account.** The base skill
tells the model to always run `portfolio` for "my portfolio". On LBUS
accounts the current CLI's `portfolio` calls AP-only endpoints and fails
with `internal server error` (code 12). `positions` works.
- **5.5 / 5.x: order confirmation is described wrong.**
`references/order.md` was a stale capture of the old CLI help ("prompts
for confirmation"). The CLI now previews by default and only acts when
the same command is re-run with `--execute <CODE>`. `--execute` appeared
nowhere in this repo.

## What

- `longbridge-portfolio/references/order.md`: refreshed the captured
`longbridge order --help` (CLI 0.30.0) and added the preview → confirm →
`--execute <CODE>` flow, with "never execute before the user confirms".
- `longbridge-portfolio/SKILL.md`:
  - The Auth section explains the dry-run default.
- New "Longbridge US accounts" section: use `positions` for holdings,
cash and buying power; fund positions, margin ratio and executions are
not available for US accounts; fall back to `positions` if `portfolio` /
`assets` / `cash-flow` fail on an older CLI.
- Two new error-handling rows for the AP-only region error and the code
12 case.
- `longbridge/SKILL.md`: one-line LBUS note next to the `portfolio`
example.

Follow-ups, not in this PR:
- Once longbridge/longbridge-terminal#337 ships (US support for
`portfolio`, `assets`, `cash-flow`, `exchange-rate`), the "older CLI"
fallback note can be narrowed to a version.
- Once longbridge/openapi#613 is released, add the new US SDK methods to
`references/*-sdk/trade-context.md`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
hogan-yuan added a commit to longbridge/developers that referenced this pull request Oct 26, 2026
…1297)

## Why

LBUS users follow the asset docs and get `code 12`: the generic asset
endpoints are AP-only, and the docs don't say so or point to the US
endpoints (US UAT 3.3).

## What

- New pages (en / zh-CN / zh-HK) under `trade/asset/`: Cash and Buying
Power, Available Holding, Transaction History, Exchange Rates (US). The
API reference (`openapi.yaml`) gets the 4 endpoints too. Examples are in
Python, Python async, Node.js, Rust and Go. Java is not included because
the Java SDK does not expose these methods yet.
- An "AP accounts only" notice on Account Assets, Stock Positions, Fund
Positions, Cash Flow, Margin Ratio and Exchange Rates, linking to the US
equivalents. Fund positions and margin ratio are marked not available
for US accounts.
- Getting Started: US accounts are pointed to `us_asset_overview` /
`us_cash_and_buy_power` for balances.

SDK: longbridge/openapi#613. The endpoints are already live on the prod
gateway.

## Testing

- `autocorrect --lint` passes. `openapi.yaml` parses. The Node.js and Go
examples type-check against the SDK branches.
- The site build was not run here (`bun run build:canary` should be run
before merge).

## Also in this PR

Doc fixes from the LBUS UAT replies:

- Order Details is marked AP-only and points US accounts to
`us_order_detail`. Today Orders and History Orders recommend
`us_query_orders` for US accounts (UAT 3.4).
- Paper trading: the MCP prerequisites and the general FAQ say LBUS has
no paper accounts, and tell users to test with the paper account's own
credentials (UAT 3.6).
- Error codes: added `403308` (scope not authorized), with the fix and
the no-securities-account/no-permission case. The MCP troubleshooting
section links to it (UAT 3.x / 5.5).
- Getting Started: new OAuth scopes section (`watchlist`,
`account.read`, `trade.read`, `trade.write`, and the production ids
`4/6/10/11` from longbridge-mcp; other ids are treated as opaque) and a
Sign Out section (delete the stored token file, or `longbridge auth
logout`) (UAT 2.3).
- CLI `order` / `grid`: replaced "prompts for confirmation" / `-y` /
`--dry-run` with the actual preview + `--execute <CODE>` flow (UAT 5.5).
- CLI `portfolio` / `assets` / `cash-flow` / `exchange-rate`: one-line
note that LBUS should use `positions` for now, with US support coming in
longbridge/longbridge-terminal#337 (UAT 4.3 / 5.4).

🤖 Generated with [Claude Code](https://claude.com/claude-code)
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