diff --git a/docs/en/docs/cli/forex/_category_.json b/docs/en/docs/cli/forex/_category_.json
new file mode 100644
index 00000000..45ab470d
--- /dev/null
+++ b/docs/en/docs/cli/forex/_category_.json
@@ -0,0 +1 @@
+{ "label": "Forex", "icon": "arrow-left-right", "position": 12, "collapsed": true }
diff --git a/docs/en/docs/cli/forex/exchange.mdx b/docs/en/docs/cli/forex/exchange.mdx
new file mode 100644
index 00000000..e6f79288
--- /dev/null
+++ b/docs/en/docs/cli/forex/exchange.mdx
@@ -0,0 +1,71 @@
+---
+title: 'currency exchange'
+sidebar_label: 'currency exchange'
+sidebar_position: 1
+---
+
+# longbridge forex
+
+Convert cash between two currencies inside your account, right from the terminal. The flow is three steps and asynchronous: **quote** a rate, **submit** the conversion against that quote, then **query** the order until it settles.
+
+These commands act on your account, so they require login; `submit-order` moves real money and prompts for confirmation unless you pass `--yes`.
+
+## Basic Usage
+
+```bash
+# Lock a rate and get a quote_id (give --amount or --target-amount)
+longbridge forex quote USD HKD --amount 1000
+```
+
+```
+| Field | Value |
+|-----------|------------------|
+| quote_id | Q20260925000123 |
+| rate | 7.7812 |
+| ccy_pair | USD/HKD |
+| expire_at | 1790000000000 |
+```
+
+## Examples
+
+### Quote a rate
+
+```bash
+# By the convert-out amount (the USD you spend)
+longbridge forex quote USD HKD --amount 1000
+# …or by the convert-in amount (the HKD you want to receive)
+longbridge forex quote USD HKD --target-amount 7800
+```
+
+`rate` is quoted on the standard currency pair (`1 BASE = rate QUOTE`, e.g. `USD/HKD`), independent of the direction you ask for. The quote is bound to you and valid for a short window (`expire_at` is a Unix-millisecond timestamp).
+
+### Submit a conversion
+
+```bash
+# Submit against the quote_id, with your own unique client order id
+longbridge forex submit-order Q20260925000123 my-order-0001
+# Skip the confirmation prompt (e.g. for scripting)
+longbridge forex submit-order Q20260925000123 my-order-0001 --yes
+```
+
+Submitting only means the order was **accepted** — the conversion completes asynchronously, so the command re-queries and prints the current order state. Reusing the same `client_order_id` never places a second order; on a timeout, query first and only re-quote + resubmit with the **same** id.
+
+### Query an order
+
+```bash
+longbridge forex order my-order-0001
+```
+
+```
+| Field | Value |
+|-------------|---------|
+| state | 1 |
+| rate | 7.7812 |
+| from_amount | 1000 |
+| to_amount | 7781.2 |
+| fail_reason | |
+```
+
+`state` is `0` processing (keep polling), `1` success, or `2` failed (frozen funds are returned). Poll no more often than every 2 seconds; most orders settle within a minute.
+
+> Add `--format json` to any command for the full machine-readable payload.
diff --git a/docs/en/docs/forex/_category_.json b/docs/en/docs/forex/_category_.json
new file mode 100644
index 00000000..3fee7225
--- /dev/null
+++ b/docs/en/docs/forex/_category_.json
@@ -0,0 +1,8 @@
+{
+ "label": "Forex",
+ "collapsible": true,
+ "collapsed": true,
+ "position": 4.6,
+ "link": null,
+ "icon": "arrow-left-right"
+}
diff --git a/docs/en/docs/forex/overview.mdx b/docs/en/docs/forex/overview.mdx
new file mode 100644
index 00000000..921b3bbb
--- /dev/null
+++ b/docs/en/docs/forex/overview.mdx
@@ -0,0 +1,57 @@
+---
+id: forex_overview
+title: Overview
+slug: forex-overview
+sidebar_position: 1
+---
+
+# Forex Overview
+
+The forex (currency exchange) channel lets you convert a balance from one
+currency into another. Conversion is asynchronous and follows three steps:
+
+1. **Get Quote** — call [Get Quote](./quote) to lock in an execution rate and
+ obtain a `quote_id`. The quote is bound to you and expires after a short
+ window.
+2. **Submit Order** — call [Submit Order](./submit) with that `quote_id` and
+ your own `client_order_id` to place the conversion. Acceptance is
+ acknowledged synchronously, but the conversion itself is processed
+ asynchronously.
+3. **Query Order** — poll [Query Order](./query) with the same
+ `client_order_id` until the order reaches a terminal state (success or
+ failed).
+
+
+
+## Error Codes
+
+| Code | Description |
+| ------ | --------------------------------------------------------------------------- |
+| 603001 | Parameter error |
+| 603002 | Unsupported currency pair |
+| 603004 | Quote not found |
+| 603005 | Quote expired (re-quote) |
+| 603006 | Quote already used (re-quote) |
+| 603007 | Insufficient convertible balance |
+| 603008 | Unauthorized / no exchange permission / outside service hours |
+| 603009 | Order not found |
+| 603010 | Service temporarily unavailable, retry later |
diff --git a/docs/en/docs/forex/query.mdx b/docs/en/docs/forex/query.mdx
new file mode 100644
index 00000000..2ee5c864
--- /dev/null
+++ b/docs/en/docs/forex/query.mdx
@@ -0,0 +1,269 @@
+---
+slug: query
+sidebar_position: 4
+title: Query Order
+language_tabs: false
+toc_footers: []
+includes: []
+search: true
+highlight_theme: ''
+headingLevel: 2
+---
+
+This API is used to query a currency-exchange order by your `client_order_id`.
+Poll it until the order reaches a terminal state.
+
+
+longbridge forex order my-order-0001
+
+
+
+
+## Request
+
+
+
+| HTTP Method | GET |
+| HTTP URL | /v1/forex/order |
+
+
+
+### Parameters
+
+> Content-Type: application/json; charset=utf-8
+
+| Name | Type | Required | Description |
+| --------------- | ------ | -------- | --------------- |
+| client_order_id | string | YES | Your order id |
+
+Poll no more often than every 2 seconds. Most orders reach a terminal state
+within 1 minute; it can take longer outside service hours or under manual
+review.
+
+### Request Example
+
+
+
+
+```python
+from longbridge.openapi import ForexContext, Config, OAuthBuilder
+
+oauth = OAuthBuilder("your-client-id").build(lambda url: print("Visit:", url))
+config = Config.from_oauth(oauth)
+
+# Create a context for forex APIs
+ctx = ForexContext(config)
+
+# Query order
+resp = ctx.order("my-order-0001")
+print(resp)
+```
+
+
+
+
+```python
+import asyncio
+from longbridge.openapi import AsyncForexContext, Config, OAuthBuilder
+
+async def main() -> None:
+ oauth = await OAuthBuilder("your-client-id").build_async(lambda url: print("Visit:", url))
+ config = Config.from_oauth(oauth)
+
+ # Create a context for forex APIs
+ ctx = AsyncForexContext.create(config)
+
+ # Query order
+ resp = await ctx.order("my-order-0001")
+ print(resp)
+
+if __name__ == "__main__":
+ asyncio.run(main())
+```
+
+
+
+
+```javascript
+const { Config, ForexContext, OAuth } = require('longbridge')
+
+async function main() {
+ const oauth = await OAuth.build('your-client-id', (_, url) => {
+ console.log('Open this URL to authorize: ' + url)
+ })
+ const config = Config.fromOAuth(oauth)
+ const ctx = new ForexContext(config)
+ const resp = await ctx.order('my-order-0001')
+ console.log(resp)
+}
+main().catch(console.error)
+```
+
+
+
+
+```java
+import com.longbridge.*;
+import com.longbridge.forex.*;
+class Main {
+ public static void main(String[] args) throws Exception {
+ try (OAuth oauth = new OAuthBuilder("your-client-id").build(url -> System.out.println("Open to authorize: " + url)).get();
+ Config config = Config.fromOAuth(oauth);
+ ForexContext ctx = ForexContext.create(config)) {
+ ForexOrderDetail resp = ctx.order("my-order-0001").get();
+ System.out.println(resp);
+ }
+ }
+}
+```
+
+
+
+
+```rust
+use std::sync::Arc;
+use longbridge::{oauth::OAuthBuilder, forex::ForexContext, Config};
+
+#[tokio::main]
+async fn main() -> Result<(), Box> {
+ let oauth = OAuthBuilder::new("your-client-id").build(|url| println!("Open this URL to authorize: {url}")).await?;
+ let config = Arc::new(Config::from_oauth(oauth));
+ let ctx = ForexContext::new(config);
+ let resp = ctx.order("my-order-0001").await?;
+ println!("{:?}", resp);
+ Ok(())
+}
+```
+
+
+
+
+```cpp
+#include
+#include
+
+#ifdef WIN32
+#include
+#endif
+
+using namespace longbridge;
+using namespace longbridge::forex;
+
+static void
+run(const OAuth& oauth)
+{
+ Config config = Config::from_oauth(oauth);
+ ForexContext ctx = ForexContext::create(config);
+
+ ctx.order("my-order-0001", [](auto res) {
+ if (!res) { std::cout << "failed" << std::endl; return; }
+ std::cout << "state: " << res->state << std::endl;
+ });
+}
+
+int main(int argc, char const* argv[]) {
+#ifdef WIN32
+ SetConsoleOutputCP(CP_UTF8);
+#endif
+
+ const std::string client_id = "your-client-id";
+ OAuthBuilder(client_id).build(
+ [](const std::string& url) {
+ std::cout << "Open this URL to authorize: " << url << std::endl;
+ },
+ [](auto res) {
+ if (!res) {
+ std::cout << "authorization failed: " << *res.status().message() << std::endl;
+ return;
+ }
+ run(*res);
+ });
+
+ std::cin.get();
+ return 0;
+}
+```
+
+
+
+
+```go
+package main
+
+import (
+ "context"
+ "fmt"
+ "log"
+
+ "github.com/longbridge/openapi-go/config"
+ "github.com/longbridge/openapi-go/forex"
+ "github.com/longbridge/openapi-go/oauth"
+)
+
+func main() {
+ o := oauth.New("your-client-id").
+ OnOpenURL(func(url string) { fmt.Println("Open this URL to authorize:", url) })
+ if err := o.Build(context.Background()); err != nil {
+ log.Fatal(err)
+ }
+ conf, err := config.New(config.WithOAuthClient(o))
+ if err != nil {
+ log.Fatal(err)
+ }
+ fctx, err := forex.NewFromCfg(conf)
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer fctx.Close()
+ resp, err := fctx.Order(context.Background(), "my-order-0001")
+ if err != nil {
+ log.Fatal(err)
+ }
+ fmt.Println("state:", resp.State)
+}
+```
+
+
+
+
+## Response
+
+### Response Headers
+
+- Content-Type: application/json
+
+### Response Example
+
+```json
+{
+ "code": 0,
+ "message": "success",
+ "data": {
+ "state": 1,
+ "rate": "7.7812",
+ "from_amount": "1000",
+ "to_amount": "7781.2",
+ "fail_reason": ""
+ }
+}
+```
+
+The `data` fields are:
+
+| Name | Type | Description |
+| ----------- | ------ | ------------------------------------------------------------------------------------------------------------------- |
+| state | int32 | `0` processing (incl. manual review — keep querying); `1` success; `2` failed (frozen funds returned) |
+| rate | string | Execution rate (standard currency-pair terms); empty when not filled |
+| from_amount | string | Convert-out amount (actual once filled) |
+| to_amount | string | Convert-in amount (actual once filled) |
+| fail_reason | string | Failure reason |
+
+### Response Status
+
+| Status | Description | Schema |
+| ------ | -------------------------------------------------------- | ------ |
+| 200 | The order was returned successfully. | None |
+| 400 | The query failed with an error in the request parameter. | None |
+
+
diff --git a/docs/en/docs/forex/quote.mdx b/docs/en/docs/forex/quote.mdx
new file mode 100644
index 00000000..75f455af
--- /dev/null
+++ b/docs/en/docs/forex/quote.mdx
@@ -0,0 +1,286 @@
+---
+slug: quote
+sidebar_position: 2
+title: Get Quote
+language_tabs: false
+toc_footers: []
+includes: []
+search: true
+highlight_theme: ''
+headingLevel: 2
+---
+
+This API is used to get a currency-exchange quote. The returned quote locks in
+an execution rate for a short window; submit within the window to fill at that
+rate.
+
+
+longbridge forex quote USD HKD --amount 1000
+
+
+
+
+## Request
+
+
+
+| HTTP Method | POST |
+| HTTP URL | /v1/forex/quote |
+
+
+
+### Parameters
+
+> Content-Type: application/json; charset=utf-8
+
+| Name | Type | Required | Description |
+| ------------- | ------ | ----------- | ------------------------------------------------------------------------------------------- |
+| from | string | YES | Convert-out currency, ISO 4217, example: `USD` |
+| to | string | YES | Convert-in currency, example: `HKD` |
+| amount | string | Conditional | Convert-out amount. One of `amount` / `target_amount` is required. At most 2 decimals, minimum `0.01` |
+| target_amount | string | Conditional | Convert-in amount. One of `amount` / `target_amount` is required. |
+
+Request example:
+
+```json
+{ "from": "USD", "to": "HKD", "amount": "1000.00" }
+```
+
+The quote is bound to you and can only be submitted by you.
+
+### Request Example
+
+
+
+
+```python
+from decimal import Decimal
+from longbridge.openapi import ForexContext, Config, OAuthBuilder
+
+oauth = OAuthBuilder("your-client-id").build(lambda url: print("Visit:", url))
+config = Config.from_oauth(oauth)
+
+# Create a context for forex APIs
+ctx = ForexContext(config)
+
+# Get quote
+resp = ctx.quote("USD", "HKD", amount=Decimal("1000.00"))
+print(resp)
+```
+
+
+
+
+```python
+import asyncio
+from decimal import Decimal
+from longbridge.openapi import AsyncForexContext, Config, OAuthBuilder
+
+async def main() -> None:
+ oauth = await OAuthBuilder("your-client-id").build_async(lambda url: print("Visit:", url))
+ config = Config.from_oauth(oauth)
+
+ # Create a context for forex APIs
+ ctx = AsyncForexContext.create(config)
+
+ # Get quote
+ resp = await ctx.quote("USD", "HKD", amount=Decimal("1000.00"))
+ print(resp)
+
+if __name__ == "__main__":
+ asyncio.run(main())
+```
+
+
+
+
+```javascript
+const { Config, ForexContext, OAuth } = require('longbridge')
+
+async function main() {
+ const oauth = await OAuth.build('your-client-id', (_, url) => {
+ console.log('Open this URL to authorize: ' + url)
+ })
+ const config = Config.fromOAuth(oauth)
+ const ctx = new ForexContext(config)
+ const resp = await ctx.quote('USD', 'HKD', '1000.00')
+ console.log(resp)
+}
+main().catch(console.error)
+```
+
+
+
+
+```java
+import com.longbridge.*;
+import com.longbridge.forex.*;
+import java.math.BigDecimal;
+class Main {
+ public static void main(String[] args) throws Exception {
+ try (OAuth oauth = new OAuthBuilder("your-client-id").build(url -> System.out.println("Open to authorize: " + url)).get();
+ Config config = Config.fromOAuth(oauth);
+ ForexContext ctx = ForexContext.create(config)) {
+ ForexQuote resp = ctx.quote("USD", "HKD", new BigDecimal("1000.00"), null).get();
+ System.out.println(resp);
+ }
+ }
+}
+```
+
+
+
+
+```rust
+use std::sync::Arc;
+use longbridge::{oauth::OAuthBuilder, forex::{ForexContext, GetForexQuoteOptions}, Config};
+use rust_decimal::Decimal;
+
+#[tokio::main]
+async fn main() -> Result<(), Box> {
+ let oauth = OAuthBuilder::new("your-client-id").build(|url| println!("Open this URL to authorize: {url}")).await?;
+ let config = Arc::new(Config::from_oauth(oauth));
+ let ctx = ForexContext::new(config);
+ let resp = ctx.quote("USD", "HKD", Some(GetForexQuoteOptions::new().amount(Decimal::from(1000)))).await?;
+ println!("{:?}", resp);
+ Ok(())
+}
+```
+
+
+
+
+```cpp
+#include
+#include
+
+#ifdef WIN32
+#include
+#endif
+
+using namespace longbridge;
+using namespace longbridge::forex;
+
+static void
+run(const OAuth& oauth)
+{
+ Config config = Config::from_oauth(oauth);
+ ForexContext ctx = ForexContext::create(config);
+
+ ctx.quote("USD", "HKD", Decimal(1000.0), std::nullopt, [](auto res) {
+ if (!res) { std::cout << "failed" << std::endl; return; }
+ std::cout << "rate: " << res->rate << std::endl;
+ });
+}
+
+int main(int argc, char const* argv[]) {
+#ifdef WIN32
+ SetConsoleOutputCP(CP_UTF8);
+#endif
+
+ const std::string client_id = "your-client-id";
+ OAuthBuilder(client_id).build(
+ [](const std::string& url) {
+ std::cout << "Open this URL to authorize: " << url << std::endl;
+ },
+ [](auto res) {
+ if (!res) {
+ std::cout << "authorization failed: " << *res.status().message() << std::endl;
+ return;
+ }
+ run(*res);
+ });
+
+ std::cin.get();
+ return 0;
+}
+```
+
+
+
+
+```go
+package main
+
+import (
+ "context"
+ "fmt"
+ "log"
+
+ "github.com/longbridge/openapi-go/config"
+ "github.com/longbridge/openapi-go/forex"
+ "github.com/longbridge/openapi-go/oauth"
+ "github.com/shopspring/decimal"
+)
+
+func main() {
+ o := oauth.New("your-client-id").
+ OnOpenURL(func(url string) { fmt.Println("Open this URL to authorize:", url) })
+ if err := o.Build(context.Background()); err != nil {
+ log.Fatal(err)
+ }
+ conf, err := config.New(config.WithOAuthClient(o))
+ if err != nil {
+ log.Fatal(err)
+ }
+ fctx, err := forex.NewFromCfg(conf)
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer fctx.Close()
+ amount := decimal.NewFromFloat(1000)
+ resp, err := fctx.Quote(context.Background(), &forex.GetForexQuote{
+ From: "USD",
+ To: "HKD",
+ Amount: &amount,
+ })
+ if err != nil {
+ log.Fatal(err)
+ }
+ fmt.Println("rate:", resp.Rate)
+}
+```
+
+
+
+
+## Response
+
+### Response Headers
+
+- Content-Type: application/json
+
+### Response Example
+
+```json
+{
+ "code": 0,
+ "message": "success",
+ "data": {
+ "ok": true,
+ "quote_id": "Q20260925000123",
+ "rate": "7.7812",
+ "expire_at": 1790000000000,
+ "ccy_pair": "USD/HKD"
+ }
+}
+```
+
+The `data` fields are:
+
+| Name | Type | Description |
+| --------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------- |
+| quote_id | string | Quote id, used when submitting the order |
+| rate | string | Customer execution rate (standard currency-pair terms; 1 BASE = rate QUOTE). In-window orders fill at this rate |
+| expire_at | int64 | Quote expiry, Unix MILLISECOND timestamp |
+| ccy_pair | string | Standard pair for the rate, format BASE/QUOTE; independent of the request `from`/`to` direction (both HKD→USD and USD→HKD return USD/HKD) |
+
+### Response Status
+
+| Status | Description | Schema |
+| ------ | ------------------------------------------------------------ | ------ |
+| 200 | The quote was returned successfully. | None |
+| 400 | The request was rejected with an incorrect request parameter. | None |
+
+
diff --git a/docs/en/docs/forex/submit.mdx b/docs/en/docs/forex/submit.mdx
new file mode 100644
index 00000000..a4bb122f
--- /dev/null
+++ b/docs/en/docs/forex/submit.mdx
@@ -0,0 +1,270 @@
+---
+slug: submit
+sidebar_position: 3
+title: Submit Order
+language_tabs: false
+toc_footers: []
+includes: []
+search: true
+highlight_theme: ''
+headingLevel: 2
+---
+
+This API is used to submit a currency-exchange order from a quote. Acceptance is
+acknowledged synchronously; the conversion itself is processed asynchronously,
+so query the result with your `client_order_id`.
+
+
+longbridge forex submit-order Q20260925000123 my-order-0001
+
+
+
+
+## Request
+
+
+
+| HTTP Method | POST |
+| HTTP URL | /v1/forex/order |
+
+
+
+### Parameters
+
+> Content-Type: application/json; charset=utf-8
+
+| Name | Type | Required | Description |
+| --------------- | ------ | -------- | --------------------------------------------------------------------------------------------------------- |
+| quote_id | string | YES | The quote id from Get Quote; the currency and amount are taken from the quote |
+| client_order_id | string | YES | Your own order id, 1~64 chars of letters/digits/`_`/`-`, must be UNIQUE across all of your accounts |
+
+### Idempotency & retries
+
+The same `client_order_id` yields at most one order (the first created order is
+returned). To avoid duplicate conversions on network timeouts or `5xx` errors,
+do **not** change `client_order_id`:
+
+- First **query** with the `client_order_id`.
+- Only if no order is found, **re-quote** and resubmit with the **same**
+ `client_order_id`.
+- Whatever `quote_id` a repeated `client_order_id` carries, the first created
+ order is returned.
+- Each `quote_id` can be submitted successfully only once (reuse returns
+ `603006` — re-quote).
+
+### Request Example
+
+
+
+
+```python
+from longbridge.openapi import ForexContext, Config, OAuthBuilder
+
+oauth = OAuthBuilder("your-client-id").build(lambda url: print("Visit:", url))
+config = Config.from_oauth(oauth)
+
+# Create a context for forex APIs
+ctx = ForexContext(config)
+
+# Submit order
+ctx.submit_order("Q20260925000123", "my-order-0001")
+```
+
+
+
+
+```python
+import asyncio
+from longbridge.openapi import AsyncForexContext, Config, OAuthBuilder
+
+async def main() -> None:
+ oauth = await OAuthBuilder("your-client-id").build_async(lambda url: print("Visit:", url))
+ config = Config.from_oauth(oauth)
+
+ # Create a context for forex APIs
+ ctx = AsyncForexContext.create(config)
+
+ # Submit order
+ await ctx.submit_order("Q20260925000123", "my-order-0001")
+
+if __name__ == "__main__":
+ asyncio.run(main())
+```
+
+
+
+
+```javascript
+const { Config, ForexContext, OAuth } = require('longbridge')
+
+async function main() {
+ const oauth = await OAuth.build('your-client-id', (_, url) => {
+ console.log('Open this URL to authorize: ' + url)
+ })
+ const config = Config.fromOAuth(oauth)
+ const ctx = new ForexContext(config)
+ await ctx.submitOrder('Q20260925000123', 'my-order-0001')
+}
+main().catch(console.error)
+```
+
+
+
+
+```java
+import com.longbridge.*;
+import com.longbridge.forex.*;
+class Main {
+ public static void main(String[] args) throws Exception {
+ try (OAuth oauth = new OAuthBuilder("your-client-id").build(url -> System.out.println("Open to authorize: " + url)).get();
+ Config config = Config.fromOAuth(oauth);
+ ForexContext ctx = ForexContext.create(config)) {
+ ctx.submitOrder("Q20260925000123", "my-order-0001").get();
+ }
+ }
+}
+```
+
+
+
+
+```rust
+use std::sync::Arc;
+use longbridge::{oauth::OAuthBuilder, forex::{ForexContext, SubmitForexOrderOptions}, Config};
+
+#[tokio::main]
+async fn main() -> Result<(), Box> {
+ let oauth = OAuthBuilder::new("your-client-id").build(|url| println!("Open this URL to authorize: {url}")).await?;
+ let config = Arc::new(Config::from_oauth(oauth));
+ let ctx = ForexContext::new(config);
+ ctx.submit_order(
+ SubmitForexOrderOptions::new("Q20260925000123", "my-order-0001")
+ ).await?;
+ Ok(())
+}
+```
+
+
+
+
+```cpp
+#include
+#include
+
+#ifdef WIN32
+#include
+#endif
+
+using namespace longbridge;
+using namespace longbridge::forex;
+
+static void
+run(const OAuth& oauth)
+{
+ Config config = Config::from_oauth(oauth);
+ ForexContext ctx = ForexContext::create(config);
+
+ ctx.submit_order("Q20260925000123", "my-order-0001", [](auto res) {
+ if (!res) { std::cout << "failed" << std::endl; return; }
+ std::cout << "accepted" << std::endl;
+ });
+}
+
+int main(int argc, char const* argv[]) {
+#ifdef WIN32
+ SetConsoleOutputCP(CP_UTF8);
+#endif
+
+ const std::string client_id = "your-client-id";
+ OAuthBuilder(client_id).build(
+ [](const std::string& url) {
+ std::cout << "Open this URL to authorize: " << url << std::endl;
+ },
+ [](auto res) {
+ if (!res) {
+ std::cout << "authorization failed: " << *res.status().message() << std::endl;
+ return;
+ }
+ run(*res);
+ });
+
+ std::cin.get();
+ return 0;
+}
+```
+
+
+
+
+```go
+package main
+
+import (
+ "context"
+ "fmt"
+ "log"
+
+ "github.com/longbridge/openapi-go/config"
+ "github.com/longbridge/openapi-go/forex"
+ "github.com/longbridge/openapi-go/oauth"
+)
+
+func main() {
+ o := oauth.New("your-client-id").
+ OnOpenURL(func(url string) { fmt.Println("Open this URL to authorize:", url) })
+ if err := o.Build(context.Background()); err != nil {
+ log.Fatal(err)
+ }
+ conf, err := config.New(config.WithOAuthClient(o))
+ if err != nil {
+ log.Fatal(err)
+ }
+ fctx, err := forex.NewFromCfg(conf)
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer fctx.Close()
+ err = fctx.SubmitOrder(context.Background(), &forex.SubmitForexOrder{
+ QuoteID: "Q20260925000123",
+ ClientOrderID: "my-order-0001",
+ })
+ if err != nil {
+ log.Fatal(err)
+ }
+ fmt.Println("accepted")
+}
+```
+
+
+
+
+## Response
+
+### Response Headers
+
+- Content-Type: application/json
+
+### Response Example
+
+```json
+{
+ "code": 0,
+ "message": "success",
+ "data": {
+ "ok": true
+ }
+}
+```
+
+`ok` indicates whether the order was accepted. The conversion is processed
+asynchronously — query the result with your `client_order_id`.
+
+### Response Status
+
+| Status | Description | Schema |
+| ------ | ------------------------------------------------------------ | ------ |
+| 200 | The submission was successful and the order was accepted. | None |
+| 400 | The submit was rejected with an incorrect request parameter. | None |
+
+
diff --git a/docs/zh-CN/docs/cli/forex/_category_.json b/docs/zh-CN/docs/cli/forex/_category_.json
new file mode 100644
index 00000000..740b6ae2
--- /dev/null
+++ b/docs/zh-CN/docs/cli/forex/_category_.json
@@ -0,0 +1 @@
+{ "label": "货币兑换", "icon": "arrow-left-right", "position": 12, "collapsed": true }
diff --git a/docs/zh-CN/docs/cli/forex/exchange.mdx b/docs/zh-CN/docs/cli/forex/exchange.mdx
new file mode 100644
index 00000000..cbea8c99
--- /dev/null
+++ b/docs/zh-CN/docs/cli/forex/exchange.mdx
@@ -0,0 +1,71 @@
+---
+title: '货币兑换'
+sidebar_label: '货币兑换'
+sidebar_position: 1
+---
+
+# longbridge forex
+
+在终端里直接把账户内的一种货币兑换成另一种。流程分三步且为异步:先**询价**拿到汇率,再用该报价**下单**,然后**查询**订单直到进入终态。
+
+这些命令作用于你的账户,需要登录;`submit-order` 会动用真实资金,未加 `--yes` 时会提示确认。
+
+## 基本用法
+
+```bash
+# 锁定汇率并拿到 quote_id(--amount 与 --target-amount 二选一)
+longbridge forex quote USD HKD --amount 1000
+```
+
+```
+| Field | Value |
+|-----------|------------------|
+| quote_id | Q20260925000123 |
+| rate | 7.7812 |
+| ccy_pair | USD/HKD |
+| expire_at | 1790000000000 |
+```
+
+## 示例
+
+### 询价
+
+```bash
+# 按兑出金额(你花出的 USD)
+longbridge forex quote USD HKD --amount 1000
+# …或按兑入金额(你想收到的 HKD)
+longbridge forex quote USD HKD --target-amount 7800
+```
+
+`rate` 按标准币种对表示(`1 BASE = rate QUOTE`,如 `USD/HKD`),与你请求的兑换方向无关。报价与你绑定,且仅在有效期内可用(`expire_at` 为 Unix 毫秒时间戳)。
+
+### 提交兑换
+
+```bash
+# 用 quote_id 下单,并带上你自定义的唯一订单号
+longbridge forex submit-order Q20260925000123 my-order-0001
+# 跳过确认(例如用于脚本)
+longbridge forex submit-order Q20260925000123 my-order-0001 --yes
+```
+
+下单成功只代表订单**已受理**——兑换为异步完成,命令会重新查询并打印当前订单状态。相同 `client_order_id` 不会产生第二笔订单;超时请先查询,确有必要再用**同一个**订单号重新询价并提交。
+
+### 查询订单
+
+```bash
+longbridge forex order my-order-0001
+```
+
+```
+| Field | Value |
+|-------------|---------|
+| state | 1 |
+| rate | 7.7812 |
+| from_amount | 1000 |
+| to_amount | 7781.2 |
+| fail_reason | |
+```
+
+`state` 为 `0` 处理中(请继续查询)、`1` 兑换成功、`2` 兑换失败(冻结资金已退回)。建议轮询间隔不低于 2 秒;多数订单在 1 分钟内进入终态。
+
+> 任意命令加 `--format json` 可获取完整的机器可读返回。
diff --git a/docs/zh-CN/docs/forex/_category_.json b/docs/zh-CN/docs/forex/_category_.json
new file mode 100644
index 00000000..7d3d1aec
--- /dev/null
+++ b/docs/zh-CN/docs/forex/_category_.json
@@ -0,0 +1,8 @@
+{
+ "label": "外汇兑换",
+ "collapsible": true,
+ "collapsed": true,
+ "position": 4.6,
+ "link": null,
+ "icon": "arrow-left-right"
+}
diff --git a/docs/zh-CN/docs/forex/overview.mdx b/docs/zh-CN/docs/forex/overview.mdx
new file mode 100644
index 00000000..4da3601b
--- /dev/null
+++ b/docs/zh-CN/docs/forex/overview.mdx
@@ -0,0 +1,50 @@
+---
+id: forex_overview
+sidebar_label: 概览
+title: 概览
+slug: forex-overview
+sidebar_position: 1
+---
+
+# 外汇兑换接口总览
+
+外汇兑换渠道用于将一种货币的余额兑换为另一种货币。兑换是异步的,分为三个步骤:
+
+1. **获取报价** — 调用[获取报价](./quote)锁定执行汇率并获得 `quote_id`。报价与您绑定,并在一个较短的时间窗口后过期。
+2. **提交兑换订单** — 使用该 `quote_id` 和您自己的 `client_order_id` 调用[提交兑换订单](./submit)发起兑换。是否受理会同步返回,但兑换本身为异步处理。
+3. **查询兑换订单** — 使用相同的 `client_order_id` 轮询[查询兑换订单](./query),直到订单到达终态(成功或失败)。
+
+
+
+## 错误码
+
+| 错误码 | 说明 |
+| ------ | ------------------------------------------------ |
+| 603001 | 参数错误 |
+| 603002 | 不支持的货币对 |
+| 603004 | 报价不存在 |
+| 603005 | 报价已过期(请重新报价) |
+| 603006 | 报价已被使用(请重新报价) |
+| 603007 | 可兑换余额不足 |
+| 603008 | 未授权 / 无兑换权限 / 非服务时段 |
+| 603009 | 订单不存在 |
+| 603010 | 服务暂时不可用,请稍后重试 |
diff --git a/docs/zh-CN/docs/forex/query.mdx b/docs/zh-CN/docs/forex/query.mdx
new file mode 100644
index 00000000..a20a1779
--- /dev/null
+++ b/docs/zh-CN/docs/forex/query.mdx
@@ -0,0 +1,266 @@
+---
+slug: query
+sidebar_position: 4
+title: 查询兑换订单
+language_tabs: false
+toc_footers: []
+includes: []
+search: true
+highlight_theme: ''
+headingLevel: 2
+---
+
+该接口用于通过 `client_order_id` 查询外汇兑换订单。请轮询查询,直到订单到达终态。
+
+
+longbridge forex order my-order-0001
+
+
+
+
+## Request
+
+
+
+| HTTP Method | GET |
+| HTTP URL | /v1/forex/order |
+
+
+
+### Parameters
+
+> Content-Type: application/json; charset=utf-8
+
+| Name | Type | Required | Description |
+| --------------- | ------ | -------- | --------------- |
+| client_order_id | string | YES | 您的订单 id |
+
+轮询间隔不要短于 2 秒。大多数订单会在 1 分钟内到达终态;在非服务时段或人工审核时可能更久。
+
+### Request Example
+
+
+
+
+```python
+from longbridge.openapi import ForexContext, Config, OAuthBuilder
+
+oauth = OAuthBuilder("your-client-id").build(lambda url: print("Visit:", url))
+config = Config.from_oauth(oauth)
+
+# Create a context for forex APIs
+ctx = ForexContext(config)
+
+# Query order
+resp = ctx.order("my-order-0001")
+print(resp)
+```
+
+
+
+
+```python
+import asyncio
+from longbridge.openapi import AsyncForexContext, Config, OAuthBuilder
+
+async def main() -> None:
+ oauth = await OAuthBuilder("your-client-id").build_async(lambda url: print("Visit:", url))
+ config = Config.from_oauth(oauth)
+
+ # Create a context for forex APIs
+ ctx = AsyncForexContext.create(config)
+
+ # Query order
+ resp = await ctx.order("my-order-0001")
+ print(resp)
+
+if __name__ == "__main__":
+ asyncio.run(main())
+```
+
+
+
+
+```javascript
+const { Config, ForexContext, OAuth } = require('longbridge')
+
+async function main() {
+ const oauth = await OAuth.build('your-client-id', (_, url) => {
+ console.log('Open this URL to authorize: ' + url)
+ })
+ const config = Config.fromOAuth(oauth)
+ const ctx = new ForexContext(config)
+ const resp = await ctx.order('my-order-0001')
+ console.log(resp)
+}
+main().catch(console.error)
+```
+
+
+
+
+```java
+import com.longbridge.*;
+import com.longbridge.forex.*;
+class Main {
+ public static void main(String[] args) throws Exception {
+ try (OAuth oauth = new OAuthBuilder("your-client-id").build(url -> System.out.println("Open to authorize: " + url)).get();
+ Config config = Config.fromOAuth(oauth);
+ ForexContext ctx = ForexContext.create(config)) {
+ ForexOrderDetail resp = ctx.order("my-order-0001").get();
+ System.out.println(resp);
+ }
+ }
+}
+```
+
+
+
+
+```rust
+use std::sync::Arc;
+use longbridge::{oauth::OAuthBuilder, forex::ForexContext, Config};
+
+#[tokio::main]
+async fn main() -> Result<(), Box> {
+ let oauth = OAuthBuilder::new("your-client-id").build(|url| println!("Open this URL to authorize: {url}")).await?;
+ let config = Arc::new(Config::from_oauth(oauth));
+ let ctx = ForexContext::new(config);
+ let resp = ctx.order("my-order-0001").await?;
+ println!("{:?}", resp);
+ Ok(())
+}
+```
+
+
+
+
+```cpp
+#include
+#include
+
+#ifdef WIN32
+#include
+#endif
+
+using namespace longbridge;
+using namespace longbridge::forex;
+
+static void
+run(const OAuth& oauth)
+{
+ Config config = Config::from_oauth(oauth);
+ ForexContext ctx = ForexContext::create(config);
+
+ ctx.order("my-order-0001", [](auto res) {
+ if (!res) { std::cout << "failed" << std::endl; return; }
+ std::cout << "state: " << res->state << std::endl;
+ });
+}
+
+int main(int argc, char const* argv[]) {
+#ifdef WIN32
+ SetConsoleOutputCP(CP_UTF8);
+#endif
+
+ const std::string client_id = "your-client-id";
+ OAuthBuilder(client_id).build(
+ [](const std::string& url) {
+ std::cout << "Open this URL to authorize: " << url << std::endl;
+ },
+ [](auto res) {
+ if (!res) {
+ std::cout << "authorization failed: " << *res.status().message() << std::endl;
+ return;
+ }
+ run(*res);
+ });
+
+ std::cin.get();
+ return 0;
+}
+```
+
+
+
+
+```go
+package main
+
+import (
+ "context"
+ "fmt"
+ "log"
+
+ "github.com/longbridge/openapi-go/config"
+ "github.com/longbridge/openapi-go/forex"
+ "github.com/longbridge/openapi-go/oauth"
+)
+
+func main() {
+ o := oauth.New("your-client-id").
+ OnOpenURL(func(url string) { fmt.Println("Open this URL to authorize:", url) })
+ if err := o.Build(context.Background()); err != nil {
+ log.Fatal(err)
+ }
+ conf, err := config.New(config.WithOAuthClient(o))
+ if err != nil {
+ log.Fatal(err)
+ }
+ fctx, err := forex.NewFromCfg(conf)
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer fctx.Close()
+ resp, err := fctx.Order(context.Background(), "my-order-0001")
+ if err != nil {
+ log.Fatal(err)
+ }
+ fmt.Println("state:", resp.State)
+}
+```
+
+
+
+
+## Response
+
+### Response Headers
+
+- Content-Type: application/json
+
+### Response Example
+
+```json
+{
+ "code": 0,
+ "message": "success",
+ "data": {
+ "state": 1,
+ "rate": "7.7812",
+ "from_amount": "1000",
+ "to_amount": "7781.2",
+ "fail_reason": ""
+ }
+}
+```
+
+`data` 字段说明:
+
+| Name | Type | Description |
+| ----------- | ------ | --------------------------------------------------------------------- |
+| state | int32 | `0` 处理中(含人工审核,请继续查询);`1` 成功;`2` 失败(冻结资金已退回) |
+| rate | string | 执行汇率(标准货币对表示);未成交时为空 |
+| from_amount | string | 转出金额(成交后为实际值) |
+| to_amount | string | 转入金额(成交后为实际值) |
+| fail_reason | string | 失败原因 |
+
+### Response Status
+
+| Status | Description | Schema |
+| ------ | ---------------------- | ------ |
+| 200 | 订单查询成功。 | None |
+| 400 | 请求参数错误,查询失败。 | None |
+
+
diff --git a/docs/zh-CN/docs/forex/quote.mdx b/docs/zh-CN/docs/forex/quote.mdx
new file mode 100644
index 00000000..89a9effc
--- /dev/null
+++ b/docs/zh-CN/docs/forex/quote.mdx
@@ -0,0 +1,284 @@
+---
+slug: quote
+sidebar_position: 2
+title: 获取报价
+language_tabs: false
+toc_footers: []
+includes: []
+search: true
+highlight_theme: ''
+headingLevel: 2
+---
+
+该接口用于获取外汇兑换报价。返回的报价会锁定一个短时间窗口内的执行汇率,在窗口内提交即可按该汇率成交。
+
+
+longbridge forex quote USD HKD --amount 1000
+
+
+
+
+## Request
+
+
+
+| HTTP Method | POST |
+| HTTP URL | /v1/forex/quote |
+
+
+
+### Parameters
+
+> Content-Type: application/json; charset=utf-8
+
+| Name | Type | Required | Description |
+| ------------- | ------ | -------- | ------------------------------------------------------------------------- |
+| from | string | YES | 转出货币,ISO 4217,例如:`USD` |
+| to | string | YES | 转入货币,例如:`HKD` |
+| amount | string | 条件必填 | 转出金额。`amount` / `target_amount` 二者必填其一。最多 2 位小数,最小 `0.01` |
+| target_amount | string | 条件必填 | 转入金额。`amount` / `target_amount` 二者必填其一。 |
+
+请求示例:
+
+```json
+{ "from": "USD", "to": "HKD", "amount": "1000.00" }
+```
+
+报价与您绑定,只能由您本人提交。
+
+### Request Example
+
+
+
+
+```python
+from decimal import Decimal
+from longbridge.openapi import ForexContext, Config, OAuthBuilder
+
+oauth = OAuthBuilder("your-client-id").build(lambda url: print("Visit:", url))
+config = Config.from_oauth(oauth)
+
+# Create a context for forex APIs
+ctx = ForexContext(config)
+
+# Get quote
+resp = ctx.quote("USD", "HKD", amount=Decimal("1000.00"))
+print(resp)
+```
+
+
+
+
+```python
+import asyncio
+from decimal import Decimal
+from longbridge.openapi import AsyncForexContext, Config, OAuthBuilder
+
+async def main() -> None:
+ oauth = await OAuthBuilder("your-client-id").build_async(lambda url: print("Visit:", url))
+ config = Config.from_oauth(oauth)
+
+ # Create a context for forex APIs
+ ctx = AsyncForexContext.create(config)
+
+ # Get quote
+ resp = await ctx.quote("USD", "HKD", amount=Decimal("1000.00"))
+ print(resp)
+
+if __name__ == "__main__":
+ asyncio.run(main())
+```
+
+
+
+
+```javascript
+const { Config, ForexContext, OAuth } = require('longbridge')
+
+async function main() {
+ const oauth = await OAuth.build('your-client-id', (_, url) => {
+ console.log('Open this URL to authorize: ' + url)
+ })
+ const config = Config.fromOAuth(oauth)
+ const ctx = new ForexContext(config)
+ const resp = await ctx.quote('USD', 'HKD', '1000.00')
+ console.log(resp)
+}
+main().catch(console.error)
+```
+
+
+
+
+```java
+import com.longbridge.*;
+import com.longbridge.forex.*;
+import java.math.BigDecimal;
+class Main {
+ public static void main(String[] args) throws Exception {
+ try (OAuth oauth = new OAuthBuilder("your-client-id").build(url -> System.out.println("Open to authorize: " + url)).get();
+ Config config = Config.fromOAuth(oauth);
+ ForexContext ctx = ForexContext.create(config)) {
+ ForexQuote resp = ctx.quote("USD", "HKD", new BigDecimal("1000.00"), null).get();
+ System.out.println(resp);
+ }
+ }
+}
+```
+
+
+
+
+```rust
+use std::sync::Arc;
+use longbridge::{oauth::OAuthBuilder, forex::{ForexContext, GetForexQuoteOptions}, Config};
+use rust_decimal::Decimal;
+
+#[tokio::main]
+async fn main() -> Result<(), Box> {
+ let oauth = OAuthBuilder::new("your-client-id").build(|url| println!("Open this URL to authorize: {url}")).await?;
+ let config = Arc::new(Config::from_oauth(oauth));
+ let ctx = ForexContext::new(config);
+ let resp = ctx.quote("USD", "HKD", Some(GetForexQuoteOptions::new().amount(Decimal::from(1000)))).await?;
+ println!("{:?}", resp);
+ Ok(())
+}
+```
+
+
+
+
+```cpp
+#include
+#include
+
+#ifdef WIN32
+#include
+#endif
+
+using namespace longbridge;
+using namespace longbridge::forex;
+
+static void
+run(const OAuth& oauth)
+{
+ Config config = Config::from_oauth(oauth);
+ ForexContext ctx = ForexContext::create(config);
+
+ ctx.quote("USD", "HKD", Decimal(1000.0), std::nullopt, [](auto res) {
+ if (!res) { std::cout << "failed" << std::endl; return; }
+ std::cout << "rate: " << res->rate << std::endl;
+ });
+}
+
+int main(int argc, char const* argv[]) {
+#ifdef WIN32
+ SetConsoleOutputCP(CP_UTF8);
+#endif
+
+ const std::string client_id = "your-client-id";
+ OAuthBuilder(client_id).build(
+ [](const std::string& url) {
+ std::cout << "Open this URL to authorize: " << url << std::endl;
+ },
+ [](auto res) {
+ if (!res) {
+ std::cout << "authorization failed: " << *res.status().message() << std::endl;
+ return;
+ }
+ run(*res);
+ });
+
+ std::cin.get();
+ return 0;
+}
+```
+
+
+
+
+```go
+package main
+
+import (
+ "context"
+ "fmt"
+ "log"
+
+ "github.com/longbridge/openapi-go/config"
+ "github.com/longbridge/openapi-go/forex"
+ "github.com/longbridge/openapi-go/oauth"
+ "github.com/shopspring/decimal"
+)
+
+func main() {
+ o := oauth.New("your-client-id").
+ OnOpenURL(func(url string) { fmt.Println("Open this URL to authorize:", url) })
+ if err := o.Build(context.Background()); err != nil {
+ log.Fatal(err)
+ }
+ conf, err := config.New(config.WithOAuthClient(o))
+ if err != nil {
+ log.Fatal(err)
+ }
+ fctx, err := forex.NewFromCfg(conf)
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer fctx.Close()
+ amount := decimal.NewFromFloat(1000)
+ resp, err := fctx.Quote(context.Background(), &forex.GetForexQuote{
+ From: "USD",
+ To: "HKD",
+ Amount: &amount,
+ })
+ if err != nil {
+ log.Fatal(err)
+ }
+ fmt.Println("rate:", resp.Rate)
+}
+```
+
+
+
+
+## Response
+
+### Response Headers
+
+- Content-Type: application/json
+
+### Response Example
+
+```json
+{
+ "code": 0,
+ "message": "success",
+ "data": {
+ "ok": true,
+ "quote_id": "Q20260925000123",
+ "rate": "7.7812",
+ "expire_at": 1790000000000,
+ "ccy_pair": "USD/HKD"
+ }
+}
+```
+
+`data` 字段说明:
+
+| Name | Type | Description |
+| --------- | ------ | --------------------------------------------------------------------------------------- |
+| quote_id | string | 报价 id,提交订单时使用 |
+| rate | string | 客户执行汇率(标准货币对表示;1 BASE = rate QUOTE)。在窗口内的订单按此汇率成交 |
+| expire_at | int64 | 报价过期时间,Unix 毫秒时间戳 |
+| ccy_pair | string | 汇率对应的标准货币对,格式为 BASE/QUOTE;与请求的 `from`/`to` 方向无关(HKD→USD 与 USD→HKD 均返回 USD/HKD) |
+
+### Response Status
+
+| Status | Description | Schema |
+| ------ | ------------------------ | ------ |
+| 200 | 报价获取成功。 | None |
+| 400 | 请求参数错误,请求被拒绝。 | None |
+
+
diff --git a/docs/zh-CN/docs/forex/submit.mdx b/docs/zh-CN/docs/forex/submit.mdx
new file mode 100644
index 00000000..0a219cb9
--- /dev/null
+++ b/docs/zh-CN/docs/forex/submit.mdx
@@ -0,0 +1,262 @@
+---
+slug: submit
+sidebar_position: 3
+title: 提交兑换订单
+language_tabs: false
+toc_footers: []
+includes: []
+search: true
+highlight_theme: ''
+headingLevel: 2
+---
+
+该接口用于基于报价提交外汇兑换订单。是否受理会同步返回;兑换本身为异步处理,请使用 `client_order_id` 查询结果。
+
+
+longbridge forex submit-order Q20260925000123 my-order-0001
+
+
+
+
+## Request
+
+
+
+| HTTP Method | POST |
+| HTTP URL | /v1/forex/order |
+
+
+
+### Parameters
+
+> Content-Type: application/json; charset=utf-8
+
+| Name | Type | Required | Description |
+| --------------- | ------ | -------- | --------------------------------------------------------------------------- |
+| quote_id | string | YES | 来自获取报价的报价 id;币种和金额取自该报价 |
+| client_order_id | string | YES | 您自己的订单 id,1~64 个字符,由字母/数字/`_`/`-` 组成,在您名下所有账户中必须唯一 |
+
+### 幂等与重试
+
+相同的 `client_order_id` 最多只会生成一笔订单(返回最先创建的订单)。为避免在网络超时或 `5xx` 错误时产生重复兑换,请**不要**更换 `client_order_id`:
+
+- 先用该 `client_order_id` **查询**。
+- 仅当查询不到订单时,再**重新报价**并使用**相同的** `client_order_id` 重新提交。
+- 无论重复的 `client_order_id` 携带哪个 `quote_id`,都会返回最先创建的订单。
+- 每个 `quote_id` 只能成功提交一次(重复使用将返回 `603006`,请重新报价)。
+
+### Request Example
+
+
+
+
+```python
+from longbridge.openapi import ForexContext, Config, OAuthBuilder
+
+oauth = OAuthBuilder("your-client-id").build(lambda url: print("Visit:", url))
+config = Config.from_oauth(oauth)
+
+# Create a context for forex APIs
+ctx = ForexContext(config)
+
+# Submit order
+ctx.submit_order("Q20260925000123", "my-order-0001")
+```
+
+
+
+
+```python
+import asyncio
+from longbridge.openapi import AsyncForexContext, Config, OAuthBuilder
+
+async def main() -> None:
+ oauth = await OAuthBuilder("your-client-id").build_async(lambda url: print("Visit:", url))
+ config = Config.from_oauth(oauth)
+
+ # Create a context for forex APIs
+ ctx = AsyncForexContext.create(config)
+
+ # Submit order
+ await ctx.submit_order("Q20260925000123", "my-order-0001")
+
+if __name__ == "__main__":
+ asyncio.run(main())
+```
+
+
+
+
+```javascript
+const { Config, ForexContext, OAuth } = require('longbridge')
+
+async function main() {
+ const oauth = await OAuth.build('your-client-id', (_, url) => {
+ console.log('Open this URL to authorize: ' + url)
+ })
+ const config = Config.fromOAuth(oauth)
+ const ctx = new ForexContext(config)
+ await ctx.submitOrder('Q20260925000123', 'my-order-0001')
+}
+main().catch(console.error)
+```
+
+
+
+
+```java
+import com.longbridge.*;
+import com.longbridge.forex.*;
+class Main {
+ public static void main(String[] args) throws Exception {
+ try (OAuth oauth = new OAuthBuilder("your-client-id").build(url -> System.out.println("Open to authorize: " + url)).get();
+ Config config = Config.fromOAuth(oauth);
+ ForexContext ctx = ForexContext.create(config)) {
+ ctx.submitOrder("Q20260925000123", "my-order-0001").get();
+ }
+ }
+}
+```
+
+
+
+
+```rust
+use std::sync::Arc;
+use longbridge::{oauth::OAuthBuilder, forex::{ForexContext, SubmitForexOrderOptions}, Config};
+
+#[tokio::main]
+async fn main() -> Result<(), Box> {
+ let oauth = OAuthBuilder::new("your-client-id").build(|url| println!("Open this URL to authorize: {url}")).await?;
+ let config = Arc::new(Config::from_oauth(oauth));
+ let ctx = ForexContext::new(config);
+ ctx.submit_order(
+ SubmitForexOrderOptions::new("Q20260925000123", "my-order-0001")
+ ).await?;
+ Ok(())
+}
+```
+
+
+
+
+```cpp
+#include
+#include
+
+#ifdef WIN32
+#include
+#endif
+
+using namespace longbridge;
+using namespace longbridge::forex;
+
+static void
+run(const OAuth& oauth)
+{
+ Config config = Config::from_oauth(oauth);
+ ForexContext ctx = ForexContext::create(config);
+
+ ctx.submit_order("Q20260925000123", "my-order-0001", [](auto res) {
+ if (!res) { std::cout << "failed" << std::endl; return; }
+ std::cout << "accepted" << std::endl;
+ });
+}
+
+int main(int argc, char const* argv[]) {
+#ifdef WIN32
+ SetConsoleOutputCP(CP_UTF8);
+#endif
+
+ const std::string client_id = "your-client-id";
+ OAuthBuilder(client_id).build(
+ [](const std::string& url) {
+ std::cout << "Open this URL to authorize: " << url << std::endl;
+ },
+ [](auto res) {
+ if (!res) {
+ std::cout << "authorization failed: " << *res.status().message() << std::endl;
+ return;
+ }
+ run(*res);
+ });
+
+ std::cin.get();
+ return 0;
+}
+```
+
+
+
+
+```go
+package main
+
+import (
+ "context"
+ "fmt"
+ "log"
+
+ "github.com/longbridge/openapi-go/config"
+ "github.com/longbridge/openapi-go/forex"
+ "github.com/longbridge/openapi-go/oauth"
+)
+
+func main() {
+ o := oauth.New("your-client-id").
+ OnOpenURL(func(url string) { fmt.Println("Open this URL to authorize:", url) })
+ if err := o.Build(context.Background()); err != nil {
+ log.Fatal(err)
+ }
+ conf, err := config.New(config.WithOAuthClient(o))
+ if err != nil {
+ log.Fatal(err)
+ }
+ fctx, err := forex.NewFromCfg(conf)
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer fctx.Close()
+ err = fctx.SubmitOrder(context.Background(), &forex.SubmitForexOrder{
+ QuoteID: "Q20260925000123",
+ ClientOrderID: "my-order-0001",
+ })
+ if err != nil {
+ log.Fatal(err)
+ }
+ fmt.Println("accepted")
+}
+```
+
+
+
+
+## Response
+
+### Response Headers
+
+- Content-Type: application/json
+
+### Response Example
+
+```json
+{
+ "code": 0,
+ "message": "success",
+ "data": {
+ "ok": true
+ }
+}
+```
+
+`ok` 表示订单是否受理。兑换为异步处理 — 请使用 `client_order_id` 查询结果。
+
+### Response Status
+
+| Status | Description | Schema |
+| ------ | ---------------------- | ------ |
+| 200 | 提交成功,订单已受理。 | None |
+| 400 | 请求参数错误,提交被拒绝。 | None |
+
+
diff --git a/docs/zh-HK/docs/cli/forex/_category_.json b/docs/zh-HK/docs/cli/forex/_category_.json
new file mode 100644
index 00000000..a24b4df2
--- /dev/null
+++ b/docs/zh-HK/docs/cli/forex/_category_.json
@@ -0,0 +1 @@
+{ "label": "貨幣兌換", "icon": "arrow-left-right", "position": 12, "collapsed": true }
diff --git a/docs/zh-HK/docs/cli/forex/exchange.mdx b/docs/zh-HK/docs/cli/forex/exchange.mdx
new file mode 100644
index 00000000..dc738a18
--- /dev/null
+++ b/docs/zh-HK/docs/cli/forex/exchange.mdx
@@ -0,0 +1,71 @@
+---
+title: '貨幣兌換'
+sidebar_label: '貨幣兌換'
+sidebar_position: 1
+---
+
+# longbridge forex
+
+在終端裡直接把賬戶內的一種貨幣兌換成另一種。流程分三步且為非同步:先**詢價**取得匯率,再用該報價**下單**,然後**查詢**訂單直至進入終態。
+
+這些命令作用於你的賬戶,需要登入;`submit-order` 會動用真實資金,未加 `--yes` 時會提示確認。
+
+## 基本用法
+
+```bash
+# 鎖定匯率並取得 quote_id(--amount 與 --target-amount 二選一)
+longbridge forex quote USD HKD --amount 1000
+```
+
+```
+| Field | Value |
+|-----------|------------------|
+| quote_id | Q20260925000123 |
+| rate | 7.7812 |
+| ccy_pair | USD/HKD |
+| expire_at | 1790000000000 |
+```
+
+## 範例
+
+### 詢價
+
+```bash
+# 按兌出金額(你花出的 USD)
+longbridge forex quote USD HKD --amount 1000
+# …或按兌入金額(你想收到的 HKD)
+longbridge forex quote USD HKD --target-amount 7800
+```
+
+`rate` 按標準幣種對表示(`1 BASE = rate QUOTE`,如 `USD/HKD`),與你請求的兌換方向無關。報價與你綁定,且僅在有效期內可用(`expire_at` 為 Unix 毫秒時間戳)。
+
+### 提交兌換
+
+```bash
+# 用 quote_id 下單,並帶上你自定義的唯一訂單號
+longbridge forex submit-order Q20260925000123 my-order-0001
+# 跳過確認(例如用於腳本)
+longbridge forex submit-order Q20260925000123 my-order-0001 --yes
+```
+
+下單成功只代表訂單**已受理**——兌換為非同步完成,命令會重新查詢並打印當前訂單狀態。相同 `client_order_id` 不會產生第二筆訂單;逾時請先查詢,確有必要再用**同一個**訂單號重新詢價並提交。
+
+### 查詢訂單
+
+```bash
+longbridge forex order my-order-0001
+```
+
+```
+| Field | Value |
+|-------------|---------|
+| state | 1 |
+| rate | 7.7812 |
+| from_amount | 1000 |
+| to_amount | 7781.2 |
+| fail_reason | |
+```
+
+`state` 為 `0` 處理中(請繼續查詢)、`1` 兌換成功、`2` 兌換失敗(凍結資金已退回)。建議輪詢間隔不低於 2 秒;多數訂單在 1 分鐘內進入終態。
+
+> 任意命令加 `--format json` 可取得完整的機器可讀返回。
diff --git a/docs/zh-HK/docs/forex/_category_.json b/docs/zh-HK/docs/forex/_category_.json
new file mode 100644
index 00000000..9f69cbad
--- /dev/null
+++ b/docs/zh-HK/docs/forex/_category_.json
@@ -0,0 +1,8 @@
+{
+ "label": "外匯兌換",
+ "collapsible": true,
+ "collapsed": true,
+ "position": 4.6,
+ "link": null,
+ "icon": "arrow-left-right"
+}
diff --git a/docs/zh-HK/docs/forex/overview.mdx b/docs/zh-HK/docs/forex/overview.mdx
new file mode 100644
index 00000000..a243091c
--- /dev/null
+++ b/docs/zh-HK/docs/forex/overview.mdx
@@ -0,0 +1,50 @@
+---
+id: forex_overview
+sidebar_label: 概覽
+title: 概覽
+slug: forex-overview
+sidebar_position: 1
+---
+
+# 外匯兌換接口總覽
+
+外匯兌換渠道用於將一種貨幣的餘額兌換為另一種貨幣。兌換是異步的,分為三個步驟:
+
+1. **獲取報價** — 調用[獲取報價](./quote)鎖定執行匯率並獲得 `quote_id`。報價與您綁定,並在一個較短的時間窗口後過期。
+2. **提交兌換訂單** — 使用該 `quote_id` 和您自己的 `client_order_id` 調用[提交兌換訂單](./submit)發起兌換。是否受理會同步返回,但兌換本身為異步處理。
+3. **查詢兌換訂單** — 使用相同的 `client_order_id` 輪詢[查詢兌換訂單](./query),直到訂單到達終態(成功或失敗)。
+
+
+
+## 錯誤碼
+
+| 錯誤碼 | 說明 |
+| ------ | ------------------------------------------------ |
+| 603001 | 參數錯誤 |
+| 603002 | 不支援的貨幣對 |
+| 603004 | 報價不存在 |
+| 603005 | 報價已過期(請重新報價) |
+| 603006 | 報價已被使用(請重新報價) |
+| 603007 | 可兌換餘額不足 |
+| 603008 | 未授權 / 無兌換權限 / 非服務時段 |
+| 603009 | 訂單不存在 |
+| 603010 | 服務暫時不可用,請稍後重試 |
diff --git a/docs/zh-HK/docs/forex/query.mdx b/docs/zh-HK/docs/forex/query.mdx
new file mode 100644
index 00000000..74b687e8
--- /dev/null
+++ b/docs/zh-HK/docs/forex/query.mdx
@@ -0,0 +1,266 @@
+---
+slug: query
+sidebar_position: 4
+title: 查詢兌換訂單
+language_tabs: false
+toc_footers: []
+includes: []
+search: true
+highlight_theme: ''
+headingLevel: 2
+---
+
+該接口用於通過 `client_order_id` 查詢外匯兌換訂單。請輪詢查詢,直到訂單到達終態。
+
+
+longbridge forex order my-order-0001
+
+
+
+
+## Request
+
+
+
+| HTTP Method | GET |
+| HTTP URL | /v1/forex/order |
+
+
+
+### Parameters
+
+> Content-Type: application/json; charset=utf-8
+
+| Name | Type | Required | Description |
+| --------------- | ------ | -------- | --------------- |
+| client_order_id | string | YES | 您的訂單 id |
+
+輪詢間隔不要短於 2 秒。大多數訂單會在 1 分鐘內到達終態;在非服務時段或人工審核時可能更久。
+
+### Request Example
+
+
+
+
+```python
+from longbridge.openapi import ForexContext, Config, OAuthBuilder
+
+oauth = OAuthBuilder("your-client-id").build(lambda url: print("Visit:", url))
+config = Config.from_oauth(oauth)
+
+# Create a context for forex APIs
+ctx = ForexContext(config)
+
+# Query order
+resp = ctx.order("my-order-0001")
+print(resp)
+```
+
+
+
+
+```python
+import asyncio
+from longbridge.openapi import AsyncForexContext, Config, OAuthBuilder
+
+async def main() -> None:
+ oauth = await OAuthBuilder("your-client-id").build_async(lambda url: print("Visit:", url))
+ config = Config.from_oauth(oauth)
+
+ # Create a context for forex APIs
+ ctx = AsyncForexContext.create(config)
+
+ # Query order
+ resp = await ctx.order("my-order-0001")
+ print(resp)
+
+if __name__ == "__main__":
+ asyncio.run(main())
+```
+
+
+
+
+```javascript
+const { Config, ForexContext, OAuth } = require('longbridge')
+
+async function main() {
+ const oauth = await OAuth.build('your-client-id', (_, url) => {
+ console.log('Open this URL to authorize: ' + url)
+ })
+ const config = Config.fromOAuth(oauth)
+ const ctx = new ForexContext(config)
+ const resp = await ctx.order('my-order-0001')
+ console.log(resp)
+}
+main().catch(console.error)
+```
+
+
+
+
+```java
+import com.longbridge.*;
+import com.longbridge.forex.*;
+class Main {
+ public static void main(String[] args) throws Exception {
+ try (OAuth oauth = new OAuthBuilder("your-client-id").build(url -> System.out.println("Open to authorize: " + url)).get();
+ Config config = Config.fromOAuth(oauth);
+ ForexContext ctx = ForexContext.create(config)) {
+ ForexOrderDetail resp = ctx.order("my-order-0001").get();
+ System.out.println(resp);
+ }
+ }
+}
+```
+
+
+
+
+```rust
+use std::sync::Arc;
+use longbridge::{oauth::OAuthBuilder, forex::ForexContext, Config};
+
+#[tokio::main]
+async fn main() -> Result<(), Box> {
+ let oauth = OAuthBuilder::new("your-client-id").build(|url| println!("Open this URL to authorize: {url}")).await?;
+ let config = Arc::new(Config::from_oauth(oauth));
+ let ctx = ForexContext::new(config);
+ let resp = ctx.order("my-order-0001").await?;
+ println!("{:?}", resp);
+ Ok(())
+}
+```
+
+
+
+
+```cpp
+#include
+#include
+
+#ifdef WIN32
+#include
+#endif
+
+using namespace longbridge;
+using namespace longbridge::forex;
+
+static void
+run(const OAuth& oauth)
+{
+ Config config = Config::from_oauth(oauth);
+ ForexContext ctx = ForexContext::create(config);
+
+ ctx.order("my-order-0001", [](auto res) {
+ if (!res) { std::cout << "failed" << std::endl; return; }
+ std::cout << "state: " << res->state << std::endl;
+ });
+}
+
+int main(int argc, char const* argv[]) {
+#ifdef WIN32
+ SetConsoleOutputCP(CP_UTF8);
+#endif
+
+ const std::string client_id = "your-client-id";
+ OAuthBuilder(client_id).build(
+ [](const std::string& url) {
+ std::cout << "Open this URL to authorize: " << url << std::endl;
+ },
+ [](auto res) {
+ if (!res) {
+ std::cout << "authorization failed: " << *res.status().message() << std::endl;
+ return;
+ }
+ run(*res);
+ });
+
+ std::cin.get();
+ return 0;
+}
+```
+
+
+
+
+```go
+package main
+
+import (
+ "context"
+ "fmt"
+ "log"
+
+ "github.com/longbridge/openapi-go/config"
+ "github.com/longbridge/openapi-go/forex"
+ "github.com/longbridge/openapi-go/oauth"
+)
+
+func main() {
+ o := oauth.New("your-client-id").
+ OnOpenURL(func(url string) { fmt.Println("Open this URL to authorize:", url) })
+ if err := o.Build(context.Background()); err != nil {
+ log.Fatal(err)
+ }
+ conf, err := config.New(config.WithOAuthClient(o))
+ if err != nil {
+ log.Fatal(err)
+ }
+ fctx, err := forex.NewFromCfg(conf)
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer fctx.Close()
+ resp, err := fctx.Order(context.Background(), "my-order-0001")
+ if err != nil {
+ log.Fatal(err)
+ }
+ fmt.Println("state:", resp.State)
+}
+```
+
+
+
+
+## Response
+
+### Response Headers
+
+- Content-Type: application/json
+
+### Response Example
+
+```json
+{
+ "code": 0,
+ "message": "success",
+ "data": {
+ "state": 1,
+ "rate": "7.7812",
+ "from_amount": "1000",
+ "to_amount": "7781.2",
+ "fail_reason": ""
+ }
+}
+```
+
+`data` 欄位說明:
+
+| Name | Type | Description |
+| ----------- | ------ | --------------------------------------------------------------------- |
+| state | int32 | `0` 處理中(含人工審核,請繼續查詢);`1` 成功;`2` 失敗(凍結資金已退回) |
+| rate | string | 執行匯率(標準貨幣對表示);未成交時為空 |
+| from_amount | string | 轉出金額(成交後為實際值) |
+| to_amount | string | 轉入金額(成交後為實際值) |
+| fail_reason | string | 失敗原因 |
+
+### Response Status
+
+| Status | Description | Schema |
+| ------ | ---------------------- | ------ |
+| 200 | 訂單查詢成功。 | None |
+| 400 | 請求參數錯誤,查詢失敗。 | None |
+
+
diff --git a/docs/zh-HK/docs/forex/quote.mdx b/docs/zh-HK/docs/forex/quote.mdx
new file mode 100644
index 00000000..2b753c37
--- /dev/null
+++ b/docs/zh-HK/docs/forex/quote.mdx
@@ -0,0 +1,284 @@
+---
+slug: quote
+sidebar_position: 2
+title: 獲取報價
+language_tabs: false
+toc_footers: []
+includes: []
+search: true
+highlight_theme: ''
+headingLevel: 2
+---
+
+該接口用於獲取外匯兌換報價。返回的報價會鎖定一個短時間窗口內的執行匯率,在窗口內提交即可按該匯率成交。
+
+
+longbridge forex quote USD HKD --amount 1000
+
+
+
+
+## Request
+
+
+
+| HTTP Method | POST |
+| HTTP URL | /v1/forex/quote |
+
+
+
+### Parameters
+
+> Content-Type: application/json; charset=utf-8
+
+| Name | Type | Required | Description |
+| ------------- | ------ | -------- | ------------------------------------------------------------------------- |
+| from | string | YES | 轉出貨幣,ISO 4217,例如:`USD` |
+| to | string | YES | 轉入貨幣,例如:`HKD` |
+| amount | string | 條件必填 | 轉出金額。`amount` / `target_amount` 二者必填其一。最多 2 位小數,最小 `0.01` |
+| target_amount | string | 條件必填 | 轉入金額。`amount` / `target_amount` 二者必填其一。 |
+
+請求示例:
+
+```json
+{ "from": "USD", "to": "HKD", "amount": "1000.00" }
+```
+
+報價與您綁定,只能由您本人提交。
+
+### Request Example
+
+
+
+
+```python
+from decimal import Decimal
+from longbridge.openapi import ForexContext, Config, OAuthBuilder
+
+oauth = OAuthBuilder("your-client-id").build(lambda url: print("Visit:", url))
+config = Config.from_oauth(oauth)
+
+# Create a context for forex APIs
+ctx = ForexContext(config)
+
+# Get quote
+resp = ctx.quote("USD", "HKD", amount=Decimal("1000.00"))
+print(resp)
+```
+
+
+
+
+```python
+import asyncio
+from decimal import Decimal
+from longbridge.openapi import AsyncForexContext, Config, OAuthBuilder
+
+async def main() -> None:
+ oauth = await OAuthBuilder("your-client-id").build_async(lambda url: print("Visit:", url))
+ config = Config.from_oauth(oauth)
+
+ # Create a context for forex APIs
+ ctx = AsyncForexContext.create(config)
+
+ # Get quote
+ resp = await ctx.quote("USD", "HKD", amount=Decimal("1000.00"))
+ print(resp)
+
+if __name__ == "__main__":
+ asyncio.run(main())
+```
+
+
+
+
+```javascript
+const { Config, ForexContext, OAuth } = require('longbridge')
+
+async function main() {
+ const oauth = await OAuth.build('your-client-id', (_, url) => {
+ console.log('Open this URL to authorize: ' + url)
+ })
+ const config = Config.fromOAuth(oauth)
+ const ctx = new ForexContext(config)
+ const resp = await ctx.quote('USD', 'HKD', '1000.00')
+ console.log(resp)
+}
+main().catch(console.error)
+```
+
+
+
+
+```java
+import com.longbridge.*;
+import com.longbridge.forex.*;
+import java.math.BigDecimal;
+class Main {
+ public static void main(String[] args) throws Exception {
+ try (OAuth oauth = new OAuthBuilder("your-client-id").build(url -> System.out.println("Open to authorize: " + url)).get();
+ Config config = Config.fromOAuth(oauth);
+ ForexContext ctx = ForexContext.create(config)) {
+ ForexQuote resp = ctx.quote("USD", "HKD", new BigDecimal("1000.00"), null).get();
+ System.out.println(resp);
+ }
+ }
+}
+```
+
+
+
+
+```rust
+use std::sync::Arc;
+use longbridge::{oauth::OAuthBuilder, forex::{ForexContext, GetForexQuoteOptions}, Config};
+use rust_decimal::Decimal;
+
+#[tokio::main]
+async fn main() -> Result<(), Box> {
+ let oauth = OAuthBuilder::new("your-client-id").build(|url| println!("Open this URL to authorize: {url}")).await?;
+ let config = Arc::new(Config::from_oauth(oauth));
+ let ctx = ForexContext::new(config);
+ let resp = ctx.quote("USD", "HKD", Some(GetForexQuoteOptions::new().amount(Decimal::from(1000)))).await?;
+ println!("{:?}", resp);
+ Ok(())
+}
+```
+
+
+
+
+```cpp
+#include
+#include
+
+#ifdef WIN32
+#include
+#endif
+
+using namespace longbridge;
+using namespace longbridge::forex;
+
+static void
+run(const OAuth& oauth)
+{
+ Config config = Config::from_oauth(oauth);
+ ForexContext ctx = ForexContext::create(config);
+
+ ctx.quote("USD", "HKD", Decimal(1000.0), std::nullopt, [](auto res) {
+ if (!res) { std::cout << "failed" << std::endl; return; }
+ std::cout << "rate: " << res->rate << std::endl;
+ });
+}
+
+int main(int argc, char const* argv[]) {
+#ifdef WIN32
+ SetConsoleOutputCP(CP_UTF8);
+#endif
+
+ const std::string client_id = "your-client-id";
+ OAuthBuilder(client_id).build(
+ [](const std::string& url) {
+ std::cout << "Open this URL to authorize: " << url << std::endl;
+ },
+ [](auto res) {
+ if (!res) {
+ std::cout << "authorization failed: " << *res.status().message() << std::endl;
+ return;
+ }
+ run(*res);
+ });
+
+ std::cin.get();
+ return 0;
+}
+```
+
+
+
+
+```go
+package main
+
+import (
+ "context"
+ "fmt"
+ "log"
+
+ "github.com/longbridge/openapi-go/config"
+ "github.com/longbridge/openapi-go/forex"
+ "github.com/longbridge/openapi-go/oauth"
+ "github.com/shopspring/decimal"
+)
+
+func main() {
+ o := oauth.New("your-client-id").
+ OnOpenURL(func(url string) { fmt.Println("Open this URL to authorize:", url) })
+ if err := o.Build(context.Background()); err != nil {
+ log.Fatal(err)
+ }
+ conf, err := config.New(config.WithOAuthClient(o))
+ if err != nil {
+ log.Fatal(err)
+ }
+ fctx, err := forex.NewFromCfg(conf)
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer fctx.Close()
+ amount := decimal.NewFromFloat(1000)
+ resp, err := fctx.Quote(context.Background(), &forex.GetForexQuote{
+ From: "USD",
+ To: "HKD",
+ Amount: &amount,
+ })
+ if err != nil {
+ log.Fatal(err)
+ }
+ fmt.Println("rate:", resp.Rate)
+}
+```
+
+
+
+
+## Response
+
+### Response Headers
+
+- Content-Type: application/json
+
+### Response Example
+
+```json
+{
+ "code": 0,
+ "message": "success",
+ "data": {
+ "ok": true,
+ "quote_id": "Q20260925000123",
+ "rate": "7.7812",
+ "expire_at": 1790000000000,
+ "ccy_pair": "USD/HKD"
+ }
+}
+```
+
+`data` 欄位說明:
+
+| Name | Type | Description |
+| --------- | ------ | --------------------------------------------------------------------------------------- |
+| quote_id | string | 報價 id,提交訂單時使用 |
+| rate | string | 客戶執行匯率(標準貨幣對表示;1 BASE = rate QUOTE)。在窗口內的訂單按此匯率成交 |
+| expire_at | int64 | 報價過期時間,Unix 毫秒時間戳 |
+| ccy_pair | string | 匯率對應的標準貨幣對,格式為 BASE/QUOTE;與請求的 `from`/`to` 方向無關(HKD→USD 與 USD→HKD 均返回 USD/HKD) |
+
+### Response Status
+
+| Status | Description | Schema |
+| ------ | ------------------------ | ------ |
+| 200 | 報價獲取成功。 | None |
+| 400 | 請求參數錯誤,請求被拒絕。 | None |
+
+
diff --git a/docs/zh-HK/docs/forex/submit.mdx b/docs/zh-HK/docs/forex/submit.mdx
new file mode 100644
index 00000000..36879699
--- /dev/null
+++ b/docs/zh-HK/docs/forex/submit.mdx
@@ -0,0 +1,262 @@
+---
+slug: submit
+sidebar_position: 3
+title: 提交兌換訂單
+language_tabs: false
+toc_footers: []
+includes: []
+search: true
+highlight_theme: ''
+headingLevel: 2
+---
+
+該接口用於基於報價提交外匯兌換訂單。是否受理會同步返回;兌換本身為異步處理,請使用 `client_order_id` 查詢結果。
+
+
+longbridge forex submit-order Q20260925000123 my-order-0001
+
+
+
+
+## Request
+
+
+
+| HTTP Method | POST |
+| HTTP URL | /v1/forex/order |
+
+
+
+### Parameters
+
+> Content-Type: application/json; charset=utf-8
+
+| Name | Type | Required | Description |
+| --------------- | ------ | -------- | --------------------------------------------------------------------------- |
+| quote_id | string | YES | 來自獲取報價的報價 id;幣種和金額取自該報價 |
+| client_order_id | string | YES | 您自己的訂單 id,1~64 個字符,由字母/數字/`_`/`-` 組成,在您名下所有賬戶中必須唯一 |
+
+### 冪等與重試
+
+相同的 `client_order_id` 最多只會生成一筆訂單(返回最先創建的訂單)。為避免在網絡超時或 `5xx` 錯誤時產生重複兌換,請**不要**更換 `client_order_id`:
+
+- 先用該 `client_order_id` **查詢**。
+- 僅當查詢不到訂單時,再**重新報價**並使用**相同的** `client_order_id` 重新提交。
+- 無論重複的 `client_order_id` 攜帶哪個 `quote_id`,都會返回最先創建的訂單。
+- 每個 `quote_id` 只能成功提交一次(重複使用將返回 `603006`,請重新報價)。
+
+### Request Example
+
+
+
+
+```python
+from longbridge.openapi import ForexContext, Config, OAuthBuilder
+
+oauth = OAuthBuilder("your-client-id").build(lambda url: print("Visit:", url))
+config = Config.from_oauth(oauth)
+
+# Create a context for forex APIs
+ctx = ForexContext(config)
+
+# Submit order
+ctx.submit_order("Q20260925000123", "my-order-0001")
+```
+
+
+
+
+```python
+import asyncio
+from longbridge.openapi import AsyncForexContext, Config, OAuthBuilder
+
+async def main() -> None:
+ oauth = await OAuthBuilder("your-client-id").build_async(lambda url: print("Visit:", url))
+ config = Config.from_oauth(oauth)
+
+ # Create a context for forex APIs
+ ctx = AsyncForexContext.create(config)
+
+ # Submit order
+ await ctx.submit_order("Q20260925000123", "my-order-0001")
+
+if __name__ == "__main__":
+ asyncio.run(main())
+```
+
+
+
+
+```javascript
+const { Config, ForexContext, OAuth } = require('longbridge')
+
+async function main() {
+ const oauth = await OAuth.build('your-client-id', (_, url) => {
+ console.log('Open this URL to authorize: ' + url)
+ })
+ const config = Config.fromOAuth(oauth)
+ const ctx = new ForexContext(config)
+ await ctx.submitOrder('Q20260925000123', 'my-order-0001')
+}
+main().catch(console.error)
+```
+
+
+
+
+```java
+import com.longbridge.*;
+import com.longbridge.forex.*;
+class Main {
+ public static void main(String[] args) throws Exception {
+ try (OAuth oauth = new OAuthBuilder("your-client-id").build(url -> System.out.println("Open to authorize: " + url)).get();
+ Config config = Config.fromOAuth(oauth);
+ ForexContext ctx = ForexContext.create(config)) {
+ ctx.submitOrder("Q20260925000123", "my-order-0001").get();
+ }
+ }
+}
+```
+
+
+
+
+```rust
+use std::sync::Arc;
+use longbridge::{oauth::OAuthBuilder, forex::{ForexContext, SubmitForexOrderOptions}, Config};
+
+#[tokio::main]
+async fn main() -> Result<(), Box> {
+ let oauth = OAuthBuilder::new("your-client-id").build(|url| println!("Open this URL to authorize: {url}")).await?;
+ let config = Arc::new(Config::from_oauth(oauth));
+ let ctx = ForexContext::new(config);
+ ctx.submit_order(
+ SubmitForexOrderOptions::new("Q20260925000123", "my-order-0001")
+ ).await?;
+ Ok(())
+}
+```
+
+
+
+
+```cpp
+#include
+#include
+
+#ifdef WIN32
+#include
+#endif
+
+using namespace longbridge;
+using namespace longbridge::forex;
+
+static void
+run(const OAuth& oauth)
+{
+ Config config = Config::from_oauth(oauth);
+ ForexContext ctx = ForexContext::create(config);
+
+ ctx.submit_order("Q20260925000123", "my-order-0001", [](auto res) {
+ if (!res) { std::cout << "failed" << std::endl; return; }
+ std::cout << "accepted" << std::endl;
+ });
+}
+
+int main(int argc, char const* argv[]) {
+#ifdef WIN32
+ SetConsoleOutputCP(CP_UTF8);
+#endif
+
+ const std::string client_id = "your-client-id";
+ OAuthBuilder(client_id).build(
+ [](const std::string& url) {
+ std::cout << "Open this URL to authorize: " << url << std::endl;
+ },
+ [](auto res) {
+ if (!res) {
+ std::cout << "authorization failed: " << *res.status().message() << std::endl;
+ return;
+ }
+ run(*res);
+ });
+
+ std::cin.get();
+ return 0;
+}
+```
+
+
+
+
+```go
+package main
+
+import (
+ "context"
+ "fmt"
+ "log"
+
+ "github.com/longbridge/openapi-go/config"
+ "github.com/longbridge/openapi-go/forex"
+ "github.com/longbridge/openapi-go/oauth"
+)
+
+func main() {
+ o := oauth.New("your-client-id").
+ OnOpenURL(func(url string) { fmt.Println("Open this URL to authorize:", url) })
+ if err := o.Build(context.Background()); err != nil {
+ log.Fatal(err)
+ }
+ conf, err := config.New(config.WithOAuthClient(o))
+ if err != nil {
+ log.Fatal(err)
+ }
+ fctx, err := forex.NewFromCfg(conf)
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer fctx.Close()
+ err = fctx.SubmitOrder(context.Background(), &forex.SubmitForexOrder{
+ QuoteID: "Q20260925000123",
+ ClientOrderID: "my-order-0001",
+ })
+ if err != nil {
+ log.Fatal(err)
+ }
+ fmt.Println("accepted")
+}
+```
+
+
+
+
+## Response
+
+### Response Headers
+
+- Content-Type: application/json
+
+### Response Example
+
+```json
+{
+ "code": 0,
+ "message": "success",
+ "data": {
+ "ok": true
+ }
+}
+```
+
+`ok` 表示訂單是否受理。兌換為異步處理 — 請使用 `client_order_id` 查詢結果。
+
+### Response Status
+
+| Status | Description | Schema |
+| ------ | ---------------------- | ------ |
+| 200 | 提交成功,訂單已受理。 | None |
+| 400 | 請求參數錯誤,提交被拒絕。 | None |
+
+