From 39544d3563236b99fdcf80a1705c5b526b3f8303 Mon Sep 17 00:00:00 2001 From: Jason Lee Date: Fri, 27 May 2022 14:22:01 +0800 Subject: [PATCH 001/567] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 409249bfd7..abb2ae27d0 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,10 @@ Longbridge OpenAPI provides programmatic quote trading interfaces for investors 2. Log in to the [longbridgeapp.com](https://longbridgeapp.com) and enter the developer platform, complete the developer verification (OpenAPI permission application), and obtain a token. +## SDK Documenation + +https://longbridgeapp.github.io/openapi-sdk + ## Pricing Longbridge does not charge any additional fees for activating or using interface services. You only need to open a Longbridge Integrated A/C and get OpenAPI service permissions to use it for free. Please refer to [Pricing](https://longbridge.hk/rate) or consult online customer service for the actual commissions or advanced quotes fees incurred by transactions. From f69170775bbc7b442e88766a75cf8930f7268bd3 Mon Sep 17 00:00:00 2001 From: Jason Lee Date: Fri, 27 May 2022 14:23:02 +0800 Subject: [PATCH 002/567] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index abb2ae27d0..700a951b7c 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ Longbridge OpenAPI provides programmatic quote trading interfaces for investors |----------------------------|--------------------------------------------| | [Rust](rust/README.md) | Longbridge OpenAPI for Rust `(>= 1.56.1)` | | [Python](python/README.md) | Longbridge OpenAPI for Python 3 `(>= 3.7)` | +| Go | WIP | ## How to enable Longbridge OpenAPI From eff1923342e8781104808334494fb0c9aab6d407 Mon Sep 17 00:00:00 2001 From: Sunli Date: Fri, 27 May 2022 14:46:16 +0800 Subject: [PATCH 003/567] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 700a951b7c..18dbdee7f4 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Longbridge OpenAPI provides programmatic quote trading interfaces for investors |----------------------------|--------------------------------------------| | [Rust](rust/README.md) | Longbridge OpenAPI for Rust `(>= 1.56.1)` | | [Python](python/README.md) | Longbridge OpenAPI for Python 3 `(>= 3.7)` | -| Go | WIP | +| Go | WIP | ## How to enable Longbridge OpenAPI From 9c55f6ba8b93cb3d588240bab0e156c20ae5da80 Mon Sep 17 00:00:00 2001 From: Sunli Date: Fri, 27 May 2022 15:14:43 +0800 Subject: [PATCH 004/567] Update CI --- .github/workflows/docs.yml | 2 +- .github/workflows/release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index ed227c805a..e11c3b79a2 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -61,7 +61,7 @@ jobs: uses: peaceiris/actions-gh-pages@v2.5.1 with: emptyCommits: true - keepFiles: true + keepFiles: false env: ACTIONS_DEPLOY_KEY: ${{ secrets.ACTIONS_DEPLOY_KEY }} PUBLISH_BRANCH: gh-pages diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0811744b80..b8a57318d7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -254,4 +254,4 @@ jobs: TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} run: | pip install --upgrade twine - twine upload --repository testpypi --skip-existing * + twine upload --skip-existing * From 29195ec5ddf58175a174937bfdae593a01414720 Mon Sep 17 00:00:00 2001 From: Sunli Date: Fri, 27 May 2022 16:15:37 +0800 Subject: [PATCH 005/567] Remove debug print --- rust/crates/httpclient/src/request.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/rust/crates/httpclient/src/request.rs b/rust/crates/httpclient/src/request.rs index 36265716a4..d101c27f4b 100644 --- a/rust/crates/httpclient/src/request.rs +++ b/rust/crates/httpclient/src/request.rs @@ -151,7 +151,6 @@ where .map_err(|_| HttpClientError::RequestTimeout)??; tracing::debug!(body = text.as_str(), "http response"); - println!("{}", text); let resp = serde_json::from_str::>(&text)?; match resp.code { From 933269770f041924b703bcd6c7fd57ad5d53fe69 Mon Sep 17 00:00:00 2001 From: Sunli Date: Fri, 27 May 2022 16:21:08 +0800 Subject: [PATCH 006/567] Release 0.2.3 longbridge@0.2.3 longbridge-httpcli@0.2.3 longbridge-proto@0.2.3 longbridge-python@0.2.3 longbridge-python-macros@0.2.3 longbridge-wscli@0.2.3 Generated by cargo-workspaces --- python/Cargo.toml | 6 +++--- python/crates/macros/Cargo.toml | 2 +- rust/Cargo.toml | 8 ++++---- rust/crates/httpclient/Cargo.toml | 2 +- rust/crates/proto/Cargo.toml | 2 +- rust/crates/wsclient/Cargo.toml | 4 ++-- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/python/Cargo.toml b/python/Cargo.toml index cb71019d36..510724285d 100644 --- a/python/Cargo.toml +++ b/python/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge-python" -version = "0.2.2" +version = "0.2.3" description = "Longbridge OpenAPI SDK for Python" homepage = "https://open.longbridgeapp.com/en/" readme = "README.md" @@ -15,8 +15,8 @@ name = "longbridge" crate-type = ["cdylib"] [dependencies] -longbridge = { path = "../rust", version = "0.2.2", features = ["blocking"] } -longbridge-python-macros = { path = "crates/macros", version = "0.2.2" } +longbridge = { path = "../rust", version = "0.2.3", features = ["blocking"] } +longbridge-python-macros = { path = "crates/macros", version = "0.2.3" } once_cell = "1.11.0" pyo3 = { version = "0.16.4", features = ["anyhow", "extension-module"] } diff --git a/python/crates/macros/Cargo.toml b/python/crates/macros/Cargo.toml index a87f89d6dc..4043ec591d 100644 --- a/python/crates/macros/Cargo.toml +++ b/python/crates/macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "longbridge-python-macros" -version = "0.2.2" +version = "0.2.3" edition = "2021" [lib] diff --git a/rust/Cargo.toml b/rust/Cargo.toml index c2e46ad202..eb3fa7e1cb 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge" -version = "0.2.2" +version = "0.2.3" description = "Longbridge OpenAPI SDK for Rust" homepage = "https://open.longbridgeapp.com/en/" readme = "README.md" @@ -14,9 +14,9 @@ categories = ["api-bindings"] blocking = [] [dependencies] -longbridge-wscli = { path = "crates/wsclient", version = "0.2.2" } -longbridge-httpcli = { path = "crates/httpclient", version = "0.2.2" } -longbridge-proto = { path = "crates/proto", version = "0.2.2" } +longbridge-wscli = { path = "crates/wsclient", version = "0.2.3" } +longbridge-httpcli = { path = "crates/httpclient", version = "0.2.3" } +longbridge-proto = { path = "crates/proto", version = "0.2.3" } tokio = { version = "1.18.2", features = [ "time", diff --git a/rust/crates/httpclient/Cargo.toml b/rust/crates/httpclient/Cargo.toml index f8b72f2f78..125fe3954b 100644 --- a/rust/crates/httpclient/Cargo.toml +++ b/rust/crates/httpclient/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge-httpcli" -version = "0.2.2" +version = "0.2.3" description = "Longbridge HTTP SDK for Rust" license = "MIT OR Apache-2.0" diff --git a/rust/crates/proto/Cargo.toml b/rust/crates/proto/Cargo.toml index 7a39b1f0fb..9c0a4dc231 100644 --- a/rust/crates/proto/Cargo.toml +++ b/rust/crates/proto/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge-proto" -version = "0.2.2" +version = "0.2.3" description = "Longbridge Protocol" license = "MIT OR Apache-2.0" diff --git a/rust/crates/wsclient/Cargo.toml b/rust/crates/wsclient/Cargo.toml index 97d3c28b08..ed690760a9 100644 --- a/rust/crates/wsclient/Cargo.toml +++ b/rust/crates/wsclient/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "longbridge-wscli" -version = "0.2.2" +version = "0.2.3" edition = "2021" description = "Longbridge Websocket SDK for Rust" license = "MIT OR Apache-2.0" [dependencies] -longbridge-proto = { path = "../proto", version = "0.2.2" } +longbridge-proto = { path = "../proto", version = "0.2.3" } tokio = { version = "1.18.2", features = [ "time", From ebbf8456bb9eb0232d7d48d33920fb86efa72ad3 Mon Sep 17 00:00:00 2001 From: Sunli Date: Fri, 27 May 2022 17:00:44 +0800 Subject: [PATCH 007/567] Update docs --- python/docs/index.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/python/docs/index.md b/python/docs/index.md index 1e3df5ff5c..3421a32d9d 100644 --- a/python/docs/index.md +++ b/python/docs/index.md @@ -29,8 +29,7 @@ setx LONGBRIDGE_ACCESS_TOKEN "Access Token get from user center" ## Quote API _(Get basic information of securities)_ ```python -from longbridge import Config -from longbridge.quote import QuoteContext +from longbridge.openapi import Config, QuoteContext # Load configuration from environment variables config = Config.from_env() @@ -47,8 +46,7 @@ print(resp) ```python from time import sleep -from longbridge import Config -from longbridge.quote import QuoteContext, SubType, PushQuote +from longbridge.openapi import Config, QuoteContext, SubType, PushQuote # Load configuration from environment variables config = Config.from_env() From 9ef42ed4f3efe165a274290ed30e417886a24764 Mon Sep 17 00:00:00 2001 From: Sunli Date: Fri, 27 May 2022 18:15:36 +0800 Subject: [PATCH 008/567] Update docs --- python/Makefile.toml | 2 +- python/pysrc/longbridge/openapi.pyi | 57 ++++++++++++++++++++++++----- rust/src/blocking/quote.rs | 3 ++ rust/src/quote/context.rs | 3 ++ rust/src/quote/types.rs | 31 ++++++++++++++++ 5 files changed, 86 insertions(+), 10 deletions(-) diff --git a/python/Makefile.toml b/python/Makefile.toml index 5f1e30a0e9..c6648ce281 100644 --- a/python/Makefile.toml +++ b/python/Makefile.toml @@ -12,7 +12,7 @@ args = ["install", "maturin>=0.12,<0.13"] command = "pip" args = [ "install", - "target/wheels/longbridge-0.2.1-cp310-none-win_amd64.whl", + "target/wheels/longbridge-0.2.3-cp310-none-win_amd64.whl", "-I", ] dependencies = ["build-python-sdk"] diff --git a/python/pysrc/longbridge/openapi.pyi b/python/pysrc/longbridge/openapi.pyi index 23c37af478..512df9f9bd 100644 --- a/python/pysrc/longbridge/openapi.pyi +++ b/python/pysrc/longbridge/openapi.pyi @@ -911,9 +911,9 @@ class TradeSession: """ -class Execution: +class Trade: """ - Execution + Trade """ price: Decimal @@ -934,6 +934,37 @@ class Execution: trade_type: str """ Trade type + + HK + + - `*` - Overseas trade + - `D` - Odd-lot trade + - `M` - Non-direct off-exchange trade + - `P` - Late trade (Off-exchange previous day) + - `U` - Auction trade + - `X` - Direct off-exchange trade + - `Y` - Automatch internalized + - `` - Automatch normal + + US + + - `` - Regular sale + - `A` - Acquisition + - `B` - Bunched trade + - `D` - Distribution + - `F` - Intermarket sweep + - `G` - Bunched sold trades + - `H` - Price variation trade + - `I` - Odd lot trade + - `K` - Rule 155 trde(NYSE MKT) + - `M` - Market center close price + - `P` - Prior reference price + - `Q` - Market center open price + - `S` - Split trade + - `V` - Contingent trade + - `W` - Average price trade + - `X` - Cross trade + - `1` - Stopped stock(Regular trade) """ direction: Type[TradeDirection] @@ -1554,19 +1585,27 @@ class QuoteContext: print(resp) """ - def trading_days(self) -> MarketTradingDays: + def trading_days(self, market: Type[Market], begin: date, end: date) -> MarketTradingDays: """ Get trading session of the day + The interval must be less than one month, and only the most recent year is supported. + + Args: + market: Market + begin: Begin date + end: End date + Examples: :: - from longbridge.openapi import QuoteContext, Config + from datetime import date + from longbridge.openapi import QuoteContext, Config, Market config = Config.from_env() ctx = QuoteContext(config) - resp = ctx.trading_days() + resp = ctx.trading_days(Market.HK, date(2022, 1, 1), date(2022, 2, 1)) print(resp) """ @@ -1908,9 +1947,9 @@ class TriggerStatus: """ -class Trade: +class Execution: """ - Trade + Execution """ order_id: str @@ -2553,7 +2592,7 @@ class TradeContext: topics: Topic list """ - def history_executions(self, symbol: Optional[str] = None, start_at: Optional[date] = None, end_at: Optional[date] = None) -> List[Trade]: + def history_executions(self, symbol: Optional[str] = None, start_at: Optional[date] = None, end_at: Optional[date] = None) -> List[Execution]: """ Get history executions @@ -2579,7 +2618,7 @@ class TradeContext: print(resp) """ - def today_executions(self, symbol: Optional[str] = None, order_id: Optional[str] = None) -> List[Trade]: + def today_executions(self, symbol: Optional[str] = None, order_id: Optional[str] = None) -> List[Execution]: """ Get today executions diff --git a/rust/src/blocking/quote.rs b/rust/src/blocking/quote.rs index 9c5cc6df90..4e01fcc2f7 100644 --- a/rust/src/blocking/quote.rs +++ b/rust/src/blocking/quote.rs @@ -468,6 +468,9 @@ impl QuoteContextSync { /// Get market trading days /// + /// The interval must be less than one month, and only the most recent year + /// is supported. + /// /// # Examples /// /// ```no_run diff --git a/rust/src/quote/context.rs b/rust/src/quote/context.rs index d5b04408fa..b6e90dc8ba 100644 --- a/rust/src/quote/context.rs +++ b/rust/src/quote/context.rs @@ -736,6 +736,9 @@ impl QuoteContext { /// Get market trading days /// + /// The interval must be less than one month, and only the most recent year + /// is supported. + /// /// Reference: /// /// # Examples diff --git a/rust/src/quote/types.rs b/rust/src/quote/types.rs index eb8c60fc16..b033ba246f 100644 --- a/rust/src/quote/types.rs +++ b/rust/src/quote/types.rs @@ -74,6 +74,37 @@ pub struct Trade { /// Time of trading pub timestamp: OffsetDateTime, /// Trade type + /// + /// HK + /// + /// - `*` - Overseas trade + /// - `D` - Odd-lot trade + /// - `M` - Non-direct off-exchange trade + /// - `P` - Late trade (Off-exchange previous day) + /// - `U` - Auction trade + /// - `X` - Direct off-exchange trade + /// - `Y` - Automatch internalized + /// - `` - Automatch normal + /// + /// US + /// + /// - `` - Regular sale + /// - `A` - Acquisition + /// - `B` - Bunched trade + /// - `D` - Distribution + /// - `F` - Intermarket sweep + /// - `G` - Bunched sold trades + /// - `H` - Price variation trade + /// - `I` - Odd lot trade + /// - `K` - Rule 155 trde(NYSE MKT) + /// - `M` - Market center close price + /// - `P` - Prior reference price + /// - `Q` - Market center open price + /// - `S` - Split trade + /// - `V` - Contingent trade + /// - `W` - Average price trade + /// - `X` - Cross trade + /// - `1` - Stopped stock(Regular trade) pub trade_type: String, /// Trade direction pub direction: TradeDirection, From 0d846fc919c796724f1e335967e84b5395cfae08 Mon Sep 17 00:00:00 2001 From: Sunli Date: Fri, 27 May 2022 18:16:13 +0800 Subject: [PATCH 009/567] Release 0.2.4 longbridge@0.2.4 longbridge-httpcli@0.2.4 longbridge-proto@0.2.4 longbridge-python@0.2.4 longbridge-python-macros@0.2.4 longbridge-wscli@0.2.4 Generated by cargo-workspaces --- python/Cargo.toml | 6 +++--- python/crates/macros/Cargo.toml | 2 +- rust/Cargo.toml | 8 ++++---- rust/crates/httpclient/Cargo.toml | 2 +- rust/crates/proto/Cargo.toml | 2 +- rust/crates/wsclient/Cargo.toml | 4 ++-- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/python/Cargo.toml b/python/Cargo.toml index 510724285d..100d254312 100644 --- a/python/Cargo.toml +++ b/python/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge-python" -version = "0.2.3" +version = "0.2.4" description = "Longbridge OpenAPI SDK for Python" homepage = "https://open.longbridgeapp.com/en/" readme = "README.md" @@ -15,8 +15,8 @@ name = "longbridge" crate-type = ["cdylib"] [dependencies] -longbridge = { path = "../rust", version = "0.2.3", features = ["blocking"] } -longbridge-python-macros = { path = "crates/macros", version = "0.2.3" } +longbridge = { path = "../rust", version = "0.2.4", features = ["blocking"] } +longbridge-python-macros = { path = "crates/macros", version = "0.2.4" } once_cell = "1.11.0" pyo3 = { version = "0.16.4", features = ["anyhow", "extension-module"] } diff --git a/python/crates/macros/Cargo.toml b/python/crates/macros/Cargo.toml index 4043ec591d..68721d6cd2 100644 --- a/python/crates/macros/Cargo.toml +++ b/python/crates/macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "longbridge-python-macros" -version = "0.2.3" +version = "0.2.4" edition = "2021" [lib] diff --git a/rust/Cargo.toml b/rust/Cargo.toml index eb3fa7e1cb..5d31192735 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge" -version = "0.2.3" +version = "0.2.4" description = "Longbridge OpenAPI SDK for Rust" homepage = "https://open.longbridgeapp.com/en/" readme = "README.md" @@ -14,9 +14,9 @@ categories = ["api-bindings"] blocking = [] [dependencies] -longbridge-wscli = { path = "crates/wsclient", version = "0.2.3" } -longbridge-httpcli = { path = "crates/httpclient", version = "0.2.3" } -longbridge-proto = { path = "crates/proto", version = "0.2.3" } +longbridge-wscli = { path = "crates/wsclient", version = "0.2.4" } +longbridge-httpcli = { path = "crates/httpclient", version = "0.2.4" } +longbridge-proto = { path = "crates/proto", version = "0.2.4" } tokio = { version = "1.18.2", features = [ "time", diff --git a/rust/crates/httpclient/Cargo.toml b/rust/crates/httpclient/Cargo.toml index 125fe3954b..947925e092 100644 --- a/rust/crates/httpclient/Cargo.toml +++ b/rust/crates/httpclient/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge-httpcli" -version = "0.2.3" +version = "0.2.4" description = "Longbridge HTTP SDK for Rust" license = "MIT OR Apache-2.0" diff --git a/rust/crates/proto/Cargo.toml b/rust/crates/proto/Cargo.toml index 9c0a4dc231..131ee67293 100644 --- a/rust/crates/proto/Cargo.toml +++ b/rust/crates/proto/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge-proto" -version = "0.2.3" +version = "0.2.4" description = "Longbridge Protocol" license = "MIT OR Apache-2.0" diff --git a/rust/crates/wsclient/Cargo.toml b/rust/crates/wsclient/Cargo.toml index ed690760a9..111a5748d2 100644 --- a/rust/crates/wsclient/Cargo.toml +++ b/rust/crates/wsclient/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "longbridge-wscli" -version = "0.2.3" +version = "0.2.4" edition = "2021" description = "Longbridge Websocket SDK for Rust" license = "MIT OR Apache-2.0" [dependencies] -longbridge-proto = { path = "../proto", version = "0.2.3" } +longbridge-proto = { path = "../proto", version = "0.2.4" } tokio = { version = "1.18.2", features = [ "time", From a19675cdced2a447970b8b733d9bad059f0df4fd Mon Sep 17 00:00:00 2001 From: Sunli Date: Fri, 27 May 2022 22:54:56 +0800 Subject: [PATCH 010/567] Update docs --- python/pysrc/longbridge/openapi.pyi | 86 ++++++++++++++++++++++++++++- rust/src/blocking/quote.rs | 2 +- rust/src/quote/context.rs | 2 +- 3 files changed, 87 insertions(+), 3 deletions(-) diff --git a/python/pysrc/longbridge/openapi.pyi b/python/pysrc/longbridge/openapi.pyi index 512df9f9bd..c8e3ccfe72 100644 --- a/python/pysrc/longbridge/openapi.pyi +++ b/python/pysrc/longbridge/openapi.pyi @@ -1329,6 +1329,9 @@ class QuoteContext: Args: symbols: Security codes + Returns: + Security info list + Examples: :: @@ -1348,6 +1351,9 @@ class QuoteContext: Args: symbols: Security codes + Returns: + Security quote list + Examples: :: @@ -1367,6 +1373,9 @@ class QuoteContext: Args: symbols: Security codes + Returns: + Option quote list + Examples: :: @@ -1386,6 +1395,9 @@ class QuoteContext: Args: symbols: Security codes + Returns: + Warrant quote list + Examples: :: @@ -1405,6 +1417,9 @@ class QuoteContext: Args: symbol: Security code + Returns: + Security depth + Examples: :: @@ -1424,6 +1439,9 @@ class QuoteContext: Args: symbol: Security code + Returns: + Security brokers + Examples: :: @@ -1440,6 +1458,9 @@ class QuoteContext: """ Get participants + Returns: + Participants + Examples: :: @@ -1460,6 +1481,9 @@ class QuoteContext: symbol: Security code count: Count of trades (Maximum is `1000`) + Returns: + Trades + Examples: :: @@ -1474,11 +1498,14 @@ class QuoteContext: def intraday(self, symbol: str) -> List[IntradayLine]: """ - Get security intraday + Get security intraday lines Args: symbol: Security code + Returns: + Intraday lines + Examples: :: @@ -1501,6 +1528,9 @@ class QuoteContext: count: Count of cancdlestick (Maximum is `1000`) adjust_type: Adjustment type + Returns: + Candlesticks + Examples: :: @@ -1520,6 +1550,9 @@ class QuoteContext: Args: symbol: Security code + Returns: + Option chain expiry date list + Examples: :: @@ -1540,6 +1573,9 @@ class QuoteContext: symbol: Security code expiry_date: Expiry date + Returns: + Option chain info + Examples: :: @@ -1557,6 +1593,9 @@ class QuoteContext: """ Get warrant issuers + Returns: + Warrant issuers + Examples: :: @@ -1573,6 +1612,9 @@ class QuoteContext: """ Get trading session of the day + Returns: + Trading session of the day + Examples: :: @@ -1596,6 +1638,9 @@ class QuoteContext: begin: Begin date end: End date + Returns: + Trading days + Examples: :: @@ -1618,6 +1663,9 @@ class QuoteContext: Args: symbols: Security codes + Returns: + Quote list + Examples: :: @@ -1642,6 +1690,9 @@ class QuoteContext: Args: symbol: Security code + Returns: + Security depth + Examples: :: @@ -1666,6 +1717,9 @@ class QuoteContext: Args: symbol: Security code + Returns: + Security brokers + Examples: :: @@ -1691,6 +1745,9 @@ class QuoteContext: symbol: Security code count: Count of trades + Returns: + Security trades + Examples: :: @@ -2601,6 +2658,9 @@ class TradeContext: start_at: Start time end_at: End time + Returns: + Execution list + Examples: :: @@ -2626,6 +2686,9 @@ class TradeContext: symbol: Filter by security code order_id: Filter by Order ID + Returns: + Execution list + Examples: :: @@ -2650,6 +2713,9 @@ class TradeContext: start_at: Start time end_at: End time + Returns: + Order list + Examples: :: @@ -2680,6 +2746,9 @@ class TradeContext: side: Filter by order side market: Filter by market type + Returns: + Order list + Examples: :: @@ -2745,6 +2814,9 @@ class TradeContext: outside_rth: Enable or disable outside regular trading hours remark: Remark (Maximum 64 characters) + Returns: + Response + Examples: :: @@ -2788,6 +2860,9 @@ class TradeContext: """ Get account balance + Returns: + Account list + Examples: :: @@ -2812,6 +2887,9 @@ class TradeContext: page: Start page (Default: 1) size: Page size (Default: 50) + Returns: + Cash flow list + Examples: :: @@ -2835,6 +2913,9 @@ class TradeContext: Args: symbols: Filter by fund codes + Returns: + Fund positions + Examples: :: @@ -2854,6 +2935,9 @@ class TradeContext: Args: symbols: Filter by stock codes + Returns: + Stock positions + Examples: :: diff --git a/rust/src/blocking/quote.rs b/rust/src/blocking/quote.rs index 4e01fcc2f7..3da4f921cf 100644 --- a/rust/src/blocking/quote.rs +++ b/rust/src/blocking/quote.rs @@ -305,7 +305,7 @@ impl QuoteContextSync { .call(move |ctx| async move { ctx.trades(symbol, count).await }) } - /// Get security intraday + /// Get security intraday lines /// /// # Examples /// diff --git a/rust/src/quote/context.rs b/rust/src/quote/context.rs index b6e90dc8ba..8ba3ea37f1 100644 --- a/rust/src/quote/context.rs +++ b/rust/src/quote/context.rs @@ -487,7 +487,7 @@ impl QuoteContext { Ok(trades) } - /// Get security intraday + /// Get security intraday lines /// /// Reference: /// From ed503281bf752362a816fad2c608c1ea4a4b46ac Mon Sep 17 00:00:00 2001 From: Sunli Date: Sat, 28 May 2022 11:50:32 +0800 Subject: [PATCH 011/567] Update mkdocs.yml --- python/Makefile.toml | 2 +- python/mkdocs.yml | 12 +++++------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/python/Makefile.toml b/python/Makefile.toml index c6648ce281..0cbf8ccccc 100644 --- a/python/Makefile.toml +++ b/python/Makefile.toml @@ -12,7 +12,7 @@ args = ["install", "maturin>=0.12,<0.13"] command = "pip" args = [ "install", - "target/wheels/longbridge-0.2.3-cp310-none-win_amd64.whl", + "target/wheels/longbridge-0.2.4-cp310-none-win_amd64.whl", "-I", ] dependencies = ["build-python-sdk"] diff --git a/python/mkdocs.yml b/python/mkdocs.yml index 31fbc4ace6..f442c8c5ef 100644 --- a/python/mkdocs.yml +++ b/python/mkdocs.yml @@ -1,8 +1,8 @@ site_name: Longbridge OpenAPI SDK docs_dir: docs -theme: - name: readthedocs +# theme: +# name: readthedocs nav: - Index: index.md @@ -24,13 +24,11 @@ plugins: - mkdocstrings: handlers: python: - import: - - https://docs.python.org/3/objects.inv + # import: + # - https://docs.python.org/3/objects.inv selection: docstring_style: google rendering: show_source: false - show_signature: true - show_if_no_docstring: false - show_signature_annotations: true + show_if_no_docstring: true members_order: source From 0984eaab272a0d47045c62a0b0bc3054a6f88165 Mon Sep 17 00:00:00 2001 From: Sunli Date: Sat, 28 May 2022 23:37:41 +0800 Subject: [PATCH 012/567] Fix typo --- python/pysrc/longbridge/openapi.pyi | 10 +++++----- python/src/quote/context.rs | 4 ++-- python/src/quote/mod.rs | 2 +- python/src/quote/types.rs | 4 ++-- rust/src/blocking/quote.rs | 6 +++--- rust/src/quote/context.rs | 6 +++--- rust/src/quote/mod.rs | 2 +- rust/src/quote/types.rs | 6 +++--- 8 files changed, 20 insertions(+), 20 deletions(-) diff --git a/python/pysrc/longbridge/openapi.pyi b/python/pysrc/longbridge/openapi.pyi index c8e3ccfe72..a868d72631 100644 --- a/python/pysrc/longbridge/openapi.pyi +++ b/python/pysrc/longbridge/openapi.pyi @@ -220,7 +220,7 @@ class DerivativeType: """ -class SecuritiyStaticInfo: +class SecurityStaticInfo: """ The basic information of securities """ @@ -795,7 +795,7 @@ class Depth: """ -class SecuritiyDepth: +class SecurityDepth: """ Security depth """ @@ -1322,7 +1322,7 @@ class QuoteContext: ctx.unsubscribe(["AAPL.US"], [SubType.Quote]) """ - def static_info(self, symbols: List[str]) -> List[SecuritiyStaticInfo]: + def static_info(self, symbols: List[str]) -> List[SecurityStaticInfo]: """ Get basic information of securities @@ -1410,7 +1410,7 @@ class QuoteContext: print(resp) """ - def depth(self, symbol: str) -> SecuritiyDepth: + def depth(self, symbol: str) -> SecurityDepth: """ Get security depth @@ -1681,7 +1681,7 @@ class QuoteContext: print(resp) """ - def realtime_depth(self, symbol: str) -> SecuritiyDepth: + def realtime_depth(self, symbol: str) -> SecurityDepth: """ Get real-time depth diff --git a/python/src/quote/context.rs b/python/src/quote/context.rs index 77a014e996..7a4b579848 100644 --- a/python/src/quote/context.rs +++ b/python/src/quote/context.rs @@ -10,7 +10,7 @@ use crate::{ types::{ AdjustType, Candlestick, IntradayLine, IssuerInfo, MarketTradingDays, MarketTradingSession, OptionQuote, ParticipantInfo, Period, RealtimeQuote, - SecuritiyStaticInfo, SecurityBrokers, SecurityDepth, SecurityQuote, StrikePriceInfo, + SecurityBrokers, SecurityDepth, SecurityQuote, SecurityStaticInfo, StrikePriceInfo, SubType, SubTypes, Trade, WarrantQuote, }, }, @@ -53,7 +53,7 @@ impl QuoteContext { } /// Get basic information of securities - fn static_info(&self, symbols: Vec) -> PyResult> { + fn static_info(&self, symbols: Vec) -> PyResult> { self.0 .static_info(symbols)? .into_iter() diff --git a/python/src/quote/mod.rs b/python/src/quote/mod.rs index 276b01b6ad..0591b15abf 100644 --- a/python/src/quote/mod.rs +++ b/python/src/quote/mod.rs @@ -13,7 +13,7 @@ pub(crate) fn register_types(parent: &PyModule) -> PyResult<()> { parent.add_class::()?; parent.add_class::()?; parent.add_class::()?; - parent.add_class::()?; + parent.add_class::()?; parent.add_class::()?; parent.add_class::()?; parent.add_class::()?; diff --git a/python/src/quote/types.rs b/python/src/quote/types.rs index de915c2f91..82662ca01a 100644 --- a/python/src/quote/types.rs +++ b/python/src/quote/types.rs @@ -221,8 +221,8 @@ impl From for longbridge::quote::AdjustType { /// The basic information of securities #[pyclass] #[derive(Debug, PyObject)] -#[py(from = "longbridge::quote::SecuritiyStaticInfo")] -pub(crate) struct SecuritiyStaticInfo { +#[py(from = "longbridge::quote::SecurityStaticInfo")] +pub(crate) struct SecurityStaticInfo { /// Security code symbol: String, /// Security name (zh-CN) diff --git a/rust/src/blocking/quote.rs b/rust/src/blocking/quote.rs index 3da4f921cf..2c2d97a6e2 100644 --- a/rust/src/blocking/quote.rs +++ b/rust/src/blocking/quote.rs @@ -7,8 +7,8 @@ use crate::{ blocking::runtime::BlockingRuntime, quote::{ AdjustType, Candlestick, IntradayLine, IssuerInfo, MarketTradingDays, MarketTradingSession, - OptionQuote, ParticipantInfo, Period, PushEvent, RealtimeQuote, SecuritiyStaticInfo, - SecurityBrokers, SecurityDepth, SecurityQuote, StrikePriceInfo, SubFlags, Trade, + OptionQuote, ParticipantInfo, Period, PushEvent, RealtimeQuote, SecurityBrokers, + SecurityDepth, SecurityQuote, SecurityStaticInfo, StrikePriceInfo, SubFlags, Trade, WarrantQuote, }, Config, Market, QuoteContext, @@ -115,7 +115,7 @@ impl QuoteContextSync { /// # Ok(()) /// # } /// ``` - pub fn static_info(&self, symbols: I) -> Result> + pub fn static_info(&self, symbols: I) -> Result> where I: IntoIterator + Send + 'static, I::IntoIter: Send + 'static, diff --git a/rust/src/quote/context.rs b/rust/src/quote/context.rs index 8ba3ea37f1..992142e7f2 100644 --- a/rust/src/quote/context.rs +++ b/rust/src/quote/context.rs @@ -14,8 +14,8 @@ use crate::{ sub_flags::SubFlags, utils::{format_date, parse_date}, AdjustType, Candlestick, IntradayLine, IssuerInfo, MarketTradingDays, MarketTradingSession, - OptionQuote, ParticipantInfo, Period, PushEvent, RealtimeQuote, SecuritiyStaticInfo, - SecurityBrokers, SecurityDepth, SecurityQuote, StrikePriceInfo, Trade, WarrantQuote, + OptionQuote, ParticipantInfo, Period, PushEvent, RealtimeQuote, SecurityBrokers, + SecurityDepth, SecurityQuote, SecurityStaticInfo, StrikePriceInfo, Trade, WarrantQuote, }, Config, Market, }; @@ -204,7 +204,7 @@ impl QuoteContext { /// # Ok::<_, anyhow::Error>(()) /// # }); /// ``` - pub async fn static_info(&self, symbols: I) -> Result> + pub async fn static_info(&self, symbols: I) -> Result> where I: IntoIterator, T: Into, diff --git a/rust/src/quote/mod.rs b/rust/src/quote/mod.rs index 3e2dc78234..7df5105542 100644 --- a/rust/src/quote/mod.rs +++ b/rust/src/quote/mod.rs @@ -17,7 +17,7 @@ pub use sub_flags::SubFlags; pub use types::{ Brokers, Candlestick, Depth, DerivativeType, IntradayLine, IssuerInfo, MarketTradingDays, MarketTradingSession, OptionDirection, OptionQuote, OptionType, ParticipantInfo, PrePostQuote, - RealtimeQuote, SecuritiyStaticInfo, SecurityBrokers, SecurityDepth, SecurityQuote, + RealtimeQuote, SecurityBrokers, SecurityDepth, SecurityQuote, SecurityStaticInfo, StrikePriceInfo, Trade, TradeDirection, TradingSessionInfo, WarrantQuote, WarrantType, }; // pub use types::{FilterWarrantExpiryDate, diff --git a/rust/src/quote/types.rs b/rust/src/quote/types.rs index b033ba246f..be3b1db3e6 100644 --- a/rust/src/quote/types.rs +++ b/rust/src/quote/types.rs @@ -139,7 +139,7 @@ pub enum DerivativeType { /// The basic information of securities #[derive(Debug)] -pub struct SecuritiyStaticInfo { +pub struct SecurityStaticInfo { /// Security code pub symbol: String, /// Security name (zh-CN) @@ -172,11 +172,11 @@ pub struct SecuritiyStaticInfo { pub stock_derivatives: Vec, } -impl TryFrom for SecuritiyStaticInfo { +impl TryFrom for SecurityStaticInfo { type Error = Error; fn try_from(resp: quote::StaticInfo) -> Result { - Ok(SecuritiyStaticInfo { + Ok(SecurityStaticInfo { symbol: resp.symbol, name_cn: resp.name_cn, name_en: resp.name_en, From 236f573820bdd66fb9db5ea252631b8c5e45cd3e Mon Sep 17 00:00:00 2001 From: Sunli Date: Sat, 28 May 2022 23:44:40 +0800 Subject: [PATCH 013/567] Update mkdocs.yml --- python/mkdocs.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/mkdocs.yml b/python/mkdocs.yml index f442c8c5ef..76fa523a3e 100644 --- a/python/mkdocs.yml +++ b/python/mkdocs.yml @@ -24,8 +24,8 @@ plugins: - mkdocstrings: handlers: python: - # import: - # - https://docs.python.org/3/objects.inv + import: + - https://docs.python.org/3/objects.inv selection: docstring_style: google rendering: From b535e09cd2e75d98a1d87f502b5623bd978d7aec Mon Sep 17 00:00:00 2001 From: Sunli Date: Sat, 28 May 2022 23:47:31 +0800 Subject: [PATCH 014/567] Release 0.2.5 longbridge@0.2.5 longbridge-httpcli@0.2.5 longbridge-proto@0.2.5 longbridge-python@0.2.5 longbridge-python-macros@0.2.5 longbridge-wscli@0.2.5 Generated by cargo-workspaces --- python/Cargo.toml | 6 +++--- python/Makefile.toml | 2 +- python/crates/macros/Cargo.toml | 2 +- rust/Cargo.toml | 8 ++++---- rust/crates/httpclient/Cargo.toml | 2 +- rust/crates/proto/Cargo.toml | 2 +- rust/crates/wsclient/Cargo.toml | 4 ++-- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/python/Cargo.toml b/python/Cargo.toml index 100d254312..7ad03ab159 100644 --- a/python/Cargo.toml +++ b/python/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge-python" -version = "0.2.4" +version = "0.2.5" description = "Longbridge OpenAPI SDK for Python" homepage = "https://open.longbridgeapp.com/en/" readme = "README.md" @@ -15,8 +15,8 @@ name = "longbridge" crate-type = ["cdylib"] [dependencies] -longbridge = { path = "../rust", version = "0.2.4", features = ["blocking"] } -longbridge-python-macros = { path = "crates/macros", version = "0.2.4" } +longbridge = { path = "../rust", version = "0.2.5", features = ["blocking"] } +longbridge-python-macros = { path = "crates/macros", version = "0.2.5" } once_cell = "1.11.0" pyo3 = { version = "0.16.4", features = ["anyhow", "extension-module"] } diff --git a/python/Makefile.toml b/python/Makefile.toml index 0cbf8ccccc..340b483867 100644 --- a/python/Makefile.toml +++ b/python/Makefile.toml @@ -12,7 +12,7 @@ args = ["install", "maturin>=0.12,<0.13"] command = "pip" args = [ "install", - "target/wheels/longbridge-0.2.4-cp310-none-win_amd64.whl", + "target/wheels/longbridge-0.2.5-cp310-none-win_amd64.whl", "-I", ] dependencies = ["build-python-sdk"] diff --git a/python/crates/macros/Cargo.toml b/python/crates/macros/Cargo.toml index 68721d6cd2..e3392da174 100644 --- a/python/crates/macros/Cargo.toml +++ b/python/crates/macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "longbridge-python-macros" -version = "0.2.4" +version = "0.2.5" edition = "2021" [lib] diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 5d31192735..0df26555f7 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge" -version = "0.2.4" +version = "0.2.5" description = "Longbridge OpenAPI SDK for Rust" homepage = "https://open.longbridgeapp.com/en/" readme = "README.md" @@ -14,9 +14,9 @@ categories = ["api-bindings"] blocking = [] [dependencies] -longbridge-wscli = { path = "crates/wsclient", version = "0.2.4" } -longbridge-httpcli = { path = "crates/httpclient", version = "0.2.4" } -longbridge-proto = { path = "crates/proto", version = "0.2.4" } +longbridge-wscli = { path = "crates/wsclient", version = "0.2.5" } +longbridge-httpcli = { path = "crates/httpclient", version = "0.2.5" } +longbridge-proto = { path = "crates/proto", version = "0.2.5" } tokio = { version = "1.18.2", features = [ "time", diff --git a/rust/crates/httpclient/Cargo.toml b/rust/crates/httpclient/Cargo.toml index 947925e092..b5510f0f05 100644 --- a/rust/crates/httpclient/Cargo.toml +++ b/rust/crates/httpclient/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge-httpcli" -version = "0.2.4" +version = "0.2.5" description = "Longbridge HTTP SDK for Rust" license = "MIT OR Apache-2.0" diff --git a/rust/crates/proto/Cargo.toml b/rust/crates/proto/Cargo.toml index 131ee67293..fe38307c56 100644 --- a/rust/crates/proto/Cargo.toml +++ b/rust/crates/proto/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge-proto" -version = "0.2.4" +version = "0.2.5" description = "Longbridge Protocol" license = "MIT OR Apache-2.0" diff --git a/rust/crates/wsclient/Cargo.toml b/rust/crates/wsclient/Cargo.toml index 111a5748d2..18786d30f0 100644 --- a/rust/crates/wsclient/Cargo.toml +++ b/rust/crates/wsclient/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "longbridge-wscli" -version = "0.2.4" +version = "0.2.5" edition = "2021" description = "Longbridge Websocket SDK for Rust" license = "MIT OR Apache-2.0" [dependencies] -longbridge-proto = { path = "../proto", version = "0.2.4" } +longbridge-proto = { path = "../proto", version = "0.2.5" } tokio = { version = "1.18.2", features = [ "time", From 324f7c68b5dd92347d0bfc1e51d7db843d24ed9f Mon Sep 17 00:00:00 2001 From: sunli829 Date: Sun, 29 May 2022 16:21:31 +0800 Subject: [PATCH 015/567] Fix trade API response parsing error --- python/pysrc/longbridge/openapi.pyi | 37 +++- python/src/trade/types.rs | 24 ++- rust/src/trade/mod.rs | 5 +- rust/src/trade/serde_utils.rs | 12 ++ rust/src/trade/types.rs | 318 +++++++++++++++++++++++++--- 5 files changed, 360 insertions(+), 36 deletions(-) diff --git a/python/pysrc/longbridge/openapi.pyi b/python/pysrc/longbridge/openapi.pyi index a868d72631..1a77d5fdd4 100644 --- a/python/pysrc/longbridge/openapi.pyi +++ b/python/pysrc/longbridge/openapi.pyi @@ -2517,7 +2517,7 @@ class FundPosition: Current equity """ - net_asset_value_day: Decimal + net_asset_value_day: datetime """ Current equity PyDecimal """ @@ -2537,10 +2537,15 @@ class FundPosition: Net cost """ + holding_units: Decimal + """ + Holding units + """ -class FundPositionsResponse: + +class FundPositionChannel: """ - Fund positions response + Fund position channel """ account_channel: str @@ -2554,6 +2559,17 @@ class FundPositionsResponse: """ +class FundPositionsResponse: + """ + Fund positions response + """ + + channels: List[FundPositionChannel] + """ + Channels + """ + + class StockPosition: """ Stock position @@ -2590,9 +2606,9 @@ class StockPosition: """ -class StockPositionsResponse: +class StockPositionChannel: """ - Stock positions response + Stock position channel """ account_channel: str @@ -2606,6 +2622,17 @@ class StockPositionsResponse: """ +class StockPositionsResponse: + """ + Stock positions response + """ + + channels: List[StockPositionChannel] + """ + Channels + """ + + class TopicType: """ Topic type diff --git a/python/src/trade/types.rs b/python/src/trade/types.rs index 33ab42c5fa..9c76d7d755 100644 --- a/python/src/trade/types.rs +++ b/python/src/trade/types.rs @@ -409,6 +409,15 @@ pub(crate) struct CashFlow { #[derive(Debug, PyObject)] #[py(from = "longbridge::trade::FundPositionsResponse")] pub(crate) struct FundPositionsResponse { + #[py(array)] + pub channels: Vec, +} + +/// Fund position channel +#[pyclass] +#[derive(Debug, PyObject, Clone)] +#[py(from = "longbridge::trade::FundPositionChannel")] +pub(crate) struct FundPositionChannel { /// Account type account_channel: String, /// Fund positions @@ -425,14 +434,16 @@ pub(crate) struct FundPosition { symbol: String, /// Current equity current_net_asset_value: PyDecimal, - /// Current equity PyDecimal - net_asset_value_day: PyDecimal, + /// Current equity time + net_asset_value_day: PyOffsetDateTimeWrapper, /// Fund name symbol_name: String, /// Currency currency: String, /// Net cost cost_net_asset_value: PyDecimal, + /// Holding units + pub holding_units: PyDecimal, } /// Stock positions response @@ -440,6 +451,15 @@ pub(crate) struct FundPosition { #[derive(Debug, PyObject, Clone)] #[py(from = "longbridge::trade::StockPositionsResponse")] pub(crate) struct StockPositionsResponse { + #[py(array)] + pub channels: Vec, +} + +/// Stock position channel +#[pyclass] +#[derive(Debug, PyObject, Clone)] +#[py(from = "longbridge::trade::StockPositionChannel")] +pub(crate) struct StockPositionChannel { /// Account type account_channel: String, /// Fund details diff --git a/rust/src/trade/mod.rs b/rust/src/trade/mod.rs index 1bd78db773..797744b707 100644 --- a/rust/src/trade/mod.rs +++ b/rust/src/trade/mod.rs @@ -17,6 +17,7 @@ pub use requests::{ }; pub use types::{ AccountBalance, BalanceType, CashFlow, CashFlowDirection, CashInfo, Execution, FundPosition, - FundPositionsResponse, Order, OrderSide, OrderStatus, OrderTag, OrderType, OutsideRTH, - StockPosition, StockPositionsResponse, TimeInForceType, TriggerPriceType, TriggerStatus, + FundPositionChannel, FundPositionsResponse, Order, OrderSide, OrderStatus, OrderTag, OrderType, + OutsideRTH, StockPosition, StockPositionChannel, StockPositionsResponse, TimeInForceType, + TriggerPriceType, TriggerStatus, }; diff --git a/rust/src/trade/serde_utils.rs b/rust/src/trade/serde_utils.rs index 3e28f8de36..8b266aa45a 100644 --- a/rust/src/trade/serde_utils.rs +++ b/rust/src/trade/serde_utils.rs @@ -61,6 +61,18 @@ pub(crate) mod timestamp_opt { } } +pub(crate) mod timestamp_int { + use super::*; + + pub(crate) fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let value = i64::deserialize(deserializer)?; + OffsetDateTime::from_unix_timestamp(value).map_err(|_| Error::custom("invalid timestamp")) + } +} + pub(crate) mod date_opt { use super::*; diff --git a/rust/src/trade/types.rs b/rust/src/trade/types.rs index 2e8d874163..c75df7b22f 100644 --- a/rust/src/trade/types.rs +++ b/rust/src/trade/types.rs @@ -1,5 +1,6 @@ +use num_enum::{FromPrimitive, IntoPrimitive}; use rust_decimal::Decimal; -use serde::Deserialize; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; use strum_macros::{Display, EnumString}; use time::{Date, OffsetDateTime}; @@ -326,34 +327,52 @@ pub struct AccountBalance { } /// Balance type -#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, EnumString, Display)] +#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, FromPrimitive, IntoPrimitive)] +#[repr(i32)] pub enum BalanceType { /// Unknown - #[strum(disabled)] - Unknown, + #[num_enum(default)] + Unknown = 0, /// Limit Order - #[strum(serialize = "1")] - Cash, + Cash = 1, /// Stock - #[strum(serialize = "2")] - Stock, + Stock = 2, /// Fund - #[strum(serialize = "3")] - Fund, + Fund = 3, +} + +impl Serialize for BalanceType { + fn serialize(&self, serializer: S) -> Result { + let value: i32 = (*self).into(); + value.serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for BalanceType { + fn deserialize>(deserializer: D) -> Result { + let value = i32::deserialize(deserializer)?; + Ok(BalanceType::from(value)) + } } /// Cash flow direction -#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, EnumString, Display)] +#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, FromPrimitive)] +#[repr(i32)] pub enum CashFlowDirection { /// Unknown - #[strum(disabled)] + #[num_enum(default)] Unknown, /// Out - #[strum(serialize = "1")] - Out, + Out = 1, /// Stock - #[strum(serialize = "2")] - In, + In = 2, +} + +impl<'de> Deserialize<'de> for CashFlowDirection { + fn deserialize>(deserializer: D) -> Result { + let value = i32::deserialize(deserializer)?; + Ok(CashFlowDirection::from(value)) + } } /// Cash flow @@ -370,7 +389,7 @@ pub struct CashFlow { /// Cash currency pub currency: String, /// Business time - #[serde(with = "serde_utils::timestamp")] + #[serde(with = "serde_utils::timestamp_int")] pub business_time: OffsetDateTime, /// Associated Stock code information #[serde(with = "serde_utils::cash_flow_symbol")] @@ -382,10 +401,19 @@ pub struct CashFlow { /// Fund positions response #[derive(Debug, Clone, Deserialize)] pub struct FundPositionsResponse { + /// Channels + #[serde(rename = "list")] + pub channels: Vec, +} + +/// Fund position channel +#[derive(Debug, Clone, Deserialize)] +pub struct FundPositionChannel { /// Account type pub account_channel: String, + /// Fund positions - #[serde(default)] + #[serde(default, rename = "fund_info")] pub positions: Vec, } @@ -397,22 +425,34 @@ pub struct FundPosition { /// Current equity pub current_net_asset_value: Decimal, /// Current equity time - pub net_asset_value_day: Decimal, + #[serde(with = "serde_utils::timestamp_int")] + pub net_asset_value_day: OffsetDateTime, /// Fund name pub symbol_name: String, /// Currency pub currency: String, /// Net cost pub cost_net_asset_value: Decimal, + /// Holding units + pub holding_units: Decimal, } /// Stock positions response #[derive(Debug, Clone, Deserialize)] pub struct StockPositionsResponse { + /// Channels + #[serde(rename = "list")] + pub channels: Vec, +} + +/// Stock position channel +#[derive(Debug, Clone, Deserialize)] +pub struct StockPositionChannel { /// Account type pub account_channel: String, - /// Stock positions - #[serde(default)] + + /// Fund positions + #[serde(default, rename = "stock_info")] pub positions: Vec, } @@ -442,9 +482,7 @@ impl_serde_for_enum_string!( OrderTag, TimeInForceType, TriggerStatus, - OutsideRTH, - BalanceType, - CashFlowDirection + OutsideRTH ); impl_default_for_enum_string!( OrderType, @@ -454,7 +492,233 @@ impl_default_for_enum_string!( OrderTag, TimeInForceType, TriggerStatus, - OutsideRTH, - BalanceType, - CashFlowDirection + OutsideRTH ); + +#[cfg(test)] +mod tests { + use time::macros::datetime; + + use super::*; + + #[test] + fn fund_position_response() { + let data = r#" + { + "list": [{ + "account_channel": "lb", + "fund_info": [{ + "symbol": "HK0000447943", + "symbol_name": "高腾亚洲收益基金", + "currency": "USD", + "holding_units": "5.000", + "current_net_asset_value": "0", + "cost_net_asset_value": "0.00", + "net_asset_value_day": 1649865600 + }] + }] + } + "#; + + let resp: FundPositionsResponse = serde_json::from_str(data).unwrap(); + assert_eq!(resp.channels.len(), 1); + + let channel = &resp.channels[0]; + assert_eq!(channel.account_channel, "lb"); + assert_eq!(channel.positions.len(), 1); + + let position = &channel.positions[0]; + assert_eq!(position.symbol, "HK0000447943"); + assert_eq!(position.symbol_name, "高腾亚洲收益基金"); + assert_eq!(position.currency, "USD"); + assert_eq!(position.current_net_asset_value, decimal!(0i32)); + assert_eq!(position.cost_net_asset_value, decimal!(0i32)); + assert_eq!(position.holding_units, decimal!(5i32)); + assert_eq!(position.net_asset_value_day, datetime!(2022-4-14 0:00 +8)); + } + + #[test] + fn stock_position_response() { + let data = r#" + { + "list": [ + { + "account_channel": "lb", + "stock_info": [ + { + "symbol": "700.HK", + "symbol_name": "腾讯控股", + "currency": "HK", + "quality": "650", + "available_quality": "-450", + "cost_price": "457.53" + }, + { + "symbol": "9991.HK", + "symbol_name": "宝尊电商-SW", + "currency": "HK", + "quality": "200", + "available_quality": "0", + "cost_price": "32.25" + } + ] + } + ] + } + "#; + + let resp: StockPositionsResponse = serde_json::from_str(data).unwrap(); + assert_eq!(resp.channels.len(), 1); + + let channel = &resp.channels[0]; + assert_eq!(channel.account_channel, "lb"); + assert_eq!(channel.positions.len(), 2); + + let position = &channel.positions[0]; + assert_eq!(position.symbol, "700.HK"); + assert_eq!(position.symbol_name, "腾讯控股"); + assert_eq!(position.currency, "HK"); + assert_eq!(position.quality, decimal!(650i32)); + assert_eq!(position.available_quality, decimal!(-450i32)); + assert_eq!(position.cost_price, decimal!(457.53f32)); + + let position = &channel.positions[0]; + assert_eq!(position.symbol, "700.HK"); + assert_eq!(position.symbol_name, "腾讯控股"); + assert_eq!(position.currency, "HK"); + assert_eq!(position.quality, decimal!(650i32)); + assert_eq!(position.available_quality, decimal!(-450i32)); + assert_eq!(position.cost_price, decimal!(457.53f32)); + + let position = &channel.positions[1]; + assert_eq!(position.symbol, "9991.HK"); + assert_eq!(position.symbol_name, "宝尊电商-SW"); + assert_eq!(position.currency, "HK"); + assert_eq!(position.quality, decimal!(200i32)); + assert_eq!(position.available_quality, decimal!(0i32)); + assert_eq!(position.cost_price, decimal!(32.25f32)); + } + + #[test] + fn cash_flow() { + let data = r#" + { + "list": [ + { + "transaction_flow_name": "BuyContract-Stocks", + "direction": 1, + "balance": "-248.60", + "currency": "USD", + "business_type": 1, + "business_time": 1621507957, + "symbol": "AAPL.US", + "description": "AAPL" + }, + { + "transaction_flow_name": "BuyContract-Stocks", + "direction": 1, + "balance": "-125.16", + "currency": "USD", + "business_type": 2, + "business_time": 1621504824, + "symbol": "AAPL.US", + "description": "AAPL" + } + ] + } + "#; + + #[derive(Debug, Deserialize)] + struct Response { + list: Vec, + } + + let resp: Response = serde_json::from_str(data).unwrap(); + assert_eq!(resp.list.len(), 2); + + let cashflow = &resp.list[0]; + assert_eq!(cashflow.transaction_flow_name, "BuyContract-Stocks"); + assert_eq!(cashflow.direction, CashFlowDirection::Out); + assert_eq!(cashflow.balance, decimal!(-248.60f32)); + assert_eq!(cashflow.currency, "USD"); + assert_eq!(cashflow.business_type, BalanceType::Cash); + assert_eq!(cashflow.business_time, datetime!(2021-05-20 18:52:37 +8)); + assert_eq!(cashflow.symbol.as_deref(), Some("AAPL.US")); + assert_eq!(cashflow.description, "AAPL"); + + let cashflow = &resp.list[1]; + assert_eq!(cashflow.transaction_flow_name, "BuyContract-Stocks"); + assert_eq!(cashflow.direction, CashFlowDirection::Out); + assert_eq!(cashflow.balance, decimal!(-125.16f32)); + assert_eq!(cashflow.currency, "USD"); + assert_eq!(cashflow.business_type, BalanceType::Stock); + assert_eq!(cashflow.business_time, datetime!(2021-05-20 18:00:24 +8)); + assert_eq!(cashflow.symbol.as_deref(), Some("AAPL.US")); + assert_eq!(cashflow.description, "AAPL"); + } + + #[test] + fn account_balance() { + let data = r#" + { + "list": [ + { + "total_cash": "1759070010.72", + "max_finance_amount": "977582000", + "remaining_finance_amount": "0", + "risk_level": "1", + "margin_call": "2598051051.50", + "currency": "HKD", + "cash_infos": [ + { + "withdraw_cash": "97592.30", + "available_cash": "195902464.37", + "frozen_cash": "11579339.13", + "settling_cash": "207288537.81", + "currency": "HKD" + }, + { + "withdraw_cash": "199893416.74", + "available_cash": "199893416.74", + "frozen_cash": "28723.76", + "settling_cash": "-276806.51", + "currency": "USD" + } + ] + } + ] + }"#; + + #[derive(Debug, Deserialize)] + struct Response { + list: Vec, + } + + let resp: Response = serde_json::from_str(data).unwrap(); + assert_eq!(resp.list.len(), 1); + + let balance = &resp.list[0]; + assert_eq!(balance.total_cash, "1759070010.72".parse().unwrap()); + assert_eq!(balance.max_finance_amount, "977582000".parse().unwrap()); + assert_eq!(balance.remaining_finance_amount, decimal!(0i32)); + assert_eq!(balance.risk_level, Some(1)); + assert_eq!(balance.margin_call, "2598051051.50".parse().unwrap()); + assert_eq!(balance.currency, "HKD"); + + assert_eq!(balance.cash_infos.len(), 2); + + let cash_info = &balance.cash_infos[0]; + assert_eq!(cash_info.withdraw_cash, "97592.30".parse().unwrap()); + assert_eq!(cash_info.available_cash, "195902464.37".parse().unwrap()); + assert_eq!(cash_info.frozen_cash, "11579339.13".parse().unwrap()); + assert_eq!(cash_info.settling_cash, "207288537.81".parse().unwrap()); + assert_eq!(cash_info.currency, "HKD"); + + let cash_info = &balance.cash_infos[1]; + assert_eq!(cash_info.withdraw_cash, "199893416.74".parse().unwrap()); + assert_eq!(cash_info.available_cash, "199893416.74".parse().unwrap()); + assert_eq!(cash_info.frozen_cash, "28723.76".parse().unwrap()); + assert_eq!(cash_info.settling_cash, "-276806.51".parse().unwrap()); + assert_eq!(cash_info.currency, "USD"); + } +} From 742ce5f975f55d78bb03c97024424d6f31ea4d4e Mon Sep 17 00:00:00 2001 From: Sunli Date: Sun, 29 May 2022 17:12:57 +0800 Subject: [PATCH 016/567] Release 0.2.6 longbridge@0.2.6 longbridge-httpcli@0.2.6 longbridge-proto@0.2.6 longbridge-python@0.2.6 longbridge-python-macros@0.2.6 longbridge-wscli@0.2.6 Generated by cargo-workspaces --- python/Cargo.toml | 6 +++--- python/Makefile.toml | 2 +- python/crates/macros/Cargo.toml | 2 +- rust/Cargo.toml | 8 ++++---- rust/crates/httpclient/Cargo.toml | 2 +- rust/crates/proto/Cargo.toml | 2 +- rust/crates/wsclient/Cargo.toml | 4 ++-- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/python/Cargo.toml b/python/Cargo.toml index 7ad03ab159..c68db2a9ab 100644 --- a/python/Cargo.toml +++ b/python/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge-python" -version = "0.2.5" +version = "0.2.6" description = "Longbridge OpenAPI SDK for Python" homepage = "https://open.longbridgeapp.com/en/" readme = "README.md" @@ -15,8 +15,8 @@ name = "longbridge" crate-type = ["cdylib"] [dependencies] -longbridge = { path = "../rust", version = "0.2.5", features = ["blocking"] } -longbridge-python-macros = { path = "crates/macros", version = "0.2.5" } +longbridge = { path = "../rust", version = "0.2.6", features = ["blocking"] } +longbridge-python-macros = { path = "crates/macros", version = "0.2.6" } once_cell = "1.11.0" pyo3 = { version = "0.16.4", features = ["anyhow", "extension-module"] } diff --git a/python/Makefile.toml b/python/Makefile.toml index 340b483867..7cd21a912e 100644 --- a/python/Makefile.toml +++ b/python/Makefile.toml @@ -12,7 +12,7 @@ args = ["install", "maturin>=0.12,<0.13"] command = "pip" args = [ "install", - "target/wheels/longbridge-0.2.5-cp310-none-win_amd64.whl", + "target/wheels/longbridge-0.2.6-cp310-none-win_amd64.whl", "-I", ] dependencies = ["build-python-sdk"] diff --git a/python/crates/macros/Cargo.toml b/python/crates/macros/Cargo.toml index e3392da174..e65c4649f5 100644 --- a/python/crates/macros/Cargo.toml +++ b/python/crates/macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "longbridge-python-macros" -version = "0.2.5" +version = "0.2.6" edition = "2021" [lib] diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 0df26555f7..760dc62e2d 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge" -version = "0.2.5" +version = "0.2.6" description = "Longbridge OpenAPI SDK for Rust" homepage = "https://open.longbridgeapp.com/en/" readme = "README.md" @@ -14,9 +14,9 @@ categories = ["api-bindings"] blocking = [] [dependencies] -longbridge-wscli = { path = "crates/wsclient", version = "0.2.5" } -longbridge-httpcli = { path = "crates/httpclient", version = "0.2.5" } -longbridge-proto = { path = "crates/proto", version = "0.2.5" } +longbridge-wscli = { path = "crates/wsclient", version = "0.2.6" } +longbridge-httpcli = { path = "crates/httpclient", version = "0.2.6" } +longbridge-proto = { path = "crates/proto", version = "0.2.6" } tokio = { version = "1.18.2", features = [ "time", diff --git a/rust/crates/httpclient/Cargo.toml b/rust/crates/httpclient/Cargo.toml index b5510f0f05..70e89a03c6 100644 --- a/rust/crates/httpclient/Cargo.toml +++ b/rust/crates/httpclient/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge-httpcli" -version = "0.2.5" +version = "0.2.6" description = "Longbridge HTTP SDK for Rust" license = "MIT OR Apache-2.0" diff --git a/rust/crates/proto/Cargo.toml b/rust/crates/proto/Cargo.toml index fe38307c56..749e5972f7 100644 --- a/rust/crates/proto/Cargo.toml +++ b/rust/crates/proto/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge-proto" -version = "0.2.5" +version = "0.2.6" description = "Longbridge Protocol" license = "MIT OR Apache-2.0" diff --git a/rust/crates/wsclient/Cargo.toml b/rust/crates/wsclient/Cargo.toml index 18786d30f0..77e797b8b6 100644 --- a/rust/crates/wsclient/Cargo.toml +++ b/rust/crates/wsclient/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "longbridge-wscli" -version = "0.2.5" +version = "0.2.6" edition = "2021" description = "Longbridge Websocket SDK for Rust" license = "MIT OR Apache-2.0" [dependencies] -longbridge-proto = { path = "../proto", version = "0.2.5" } +longbridge-proto = { path = "../proto", version = "0.2.6" } tokio = { version = "1.18.2", features = [ "time", From ed59c39ad959f4d9099854404c8351db5c5bf328 Mon Sep 17 00:00:00 2001 From: Sunli Date: Sun, 29 May 2022 21:04:02 +0800 Subject: [PATCH 017/567] [Python] Fix unable to import `Decimal` class. --- python/src/decimal.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/src/decimal.rs b/python/src/decimal.rs index 506ed389d2..8fde290b13 100644 --- a/python/src/decimal.rs +++ b/python/src/decimal.rs @@ -7,7 +7,7 @@ use rust_decimal::Decimal; static DECIMAL_TYPE: Lazy = Lazy::new(|| { Python::with_gil(|py| { let decimal_module = py.import("decimal")?; - let decimal_type = decimal_module.get_item("Decimal")?; + let decimal_type = decimal_module.getattr("Decimal")?; Ok::<_, PyErr>(decimal_type.to_object(py)) }) .expect("import decimal") From 7ae284df3073cc2714955485459226b2367fa11b Mon Sep 17 00:00:00 2001 From: Sunli Date: Sun, 29 May 2022 21:05:07 +0800 Subject: [PATCH 018/567] Release 0.2.7 longbridge@0.2.7 longbridge-httpcli@0.2.7 longbridge-proto@0.2.7 longbridge-python@0.2.7 longbridge-python-macros@0.2.7 longbridge-wscli@0.2.7 Generated by cargo-workspaces --- python/Cargo.toml | 6 +++--- python/Makefile.toml | 2 +- python/crates/macros/Cargo.toml | 2 +- rust/Cargo.toml | 8 ++++---- rust/crates/httpclient/Cargo.toml | 2 +- rust/crates/proto/Cargo.toml | 2 +- rust/crates/wsclient/Cargo.toml | 4 ++-- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/python/Cargo.toml b/python/Cargo.toml index c68db2a9ab..7a6c9e8d85 100644 --- a/python/Cargo.toml +++ b/python/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge-python" -version = "0.2.6" +version = "0.2.7" description = "Longbridge OpenAPI SDK for Python" homepage = "https://open.longbridgeapp.com/en/" readme = "README.md" @@ -15,8 +15,8 @@ name = "longbridge" crate-type = ["cdylib"] [dependencies] -longbridge = { path = "../rust", version = "0.2.6", features = ["blocking"] } -longbridge-python-macros = { path = "crates/macros", version = "0.2.6" } +longbridge = { path = "../rust", version = "0.2.7", features = ["blocking"] } +longbridge-python-macros = { path = "crates/macros", version = "0.2.7" } once_cell = "1.11.0" pyo3 = { version = "0.16.4", features = ["anyhow", "extension-module"] } diff --git a/python/Makefile.toml b/python/Makefile.toml index 7cd21a912e..d931dcbf58 100644 --- a/python/Makefile.toml +++ b/python/Makefile.toml @@ -12,7 +12,7 @@ args = ["install", "maturin>=0.12,<0.13"] command = "pip" args = [ "install", - "target/wheels/longbridge-0.2.6-cp310-none-win_amd64.whl", + "target/wheels/longbridge-0.2.7-cp310-none-win_amd64.whl", "-I", ] dependencies = ["build-python-sdk"] diff --git a/python/crates/macros/Cargo.toml b/python/crates/macros/Cargo.toml index e65c4649f5..3c84c0fa9a 100644 --- a/python/crates/macros/Cargo.toml +++ b/python/crates/macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "longbridge-python-macros" -version = "0.2.6" +version = "0.2.7" edition = "2021" [lib] diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 760dc62e2d..074e43d03b 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge" -version = "0.2.6" +version = "0.2.7" description = "Longbridge OpenAPI SDK for Rust" homepage = "https://open.longbridgeapp.com/en/" readme = "README.md" @@ -14,9 +14,9 @@ categories = ["api-bindings"] blocking = [] [dependencies] -longbridge-wscli = { path = "crates/wsclient", version = "0.2.6" } -longbridge-httpcli = { path = "crates/httpclient", version = "0.2.6" } -longbridge-proto = { path = "crates/proto", version = "0.2.6" } +longbridge-wscli = { path = "crates/wsclient", version = "0.2.7" } +longbridge-httpcli = { path = "crates/httpclient", version = "0.2.7" } +longbridge-proto = { path = "crates/proto", version = "0.2.7" } tokio = { version = "1.18.2", features = [ "time", diff --git a/rust/crates/httpclient/Cargo.toml b/rust/crates/httpclient/Cargo.toml index 70e89a03c6..0c8e96c1ec 100644 --- a/rust/crates/httpclient/Cargo.toml +++ b/rust/crates/httpclient/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge-httpcli" -version = "0.2.6" +version = "0.2.7" description = "Longbridge HTTP SDK for Rust" license = "MIT OR Apache-2.0" diff --git a/rust/crates/proto/Cargo.toml b/rust/crates/proto/Cargo.toml index 749e5972f7..14d60b1fe9 100644 --- a/rust/crates/proto/Cargo.toml +++ b/rust/crates/proto/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge-proto" -version = "0.2.6" +version = "0.2.7" description = "Longbridge Protocol" license = "MIT OR Apache-2.0" diff --git a/rust/crates/wsclient/Cargo.toml b/rust/crates/wsclient/Cargo.toml index 77e797b8b6..22f5798b2b 100644 --- a/rust/crates/wsclient/Cargo.toml +++ b/rust/crates/wsclient/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "longbridge-wscli" -version = "0.2.6" +version = "0.2.7" edition = "2021" description = "Longbridge Websocket SDK for Rust" license = "MIT OR Apache-2.0" [dependencies] -longbridge-proto = { path = "../proto", version = "0.2.6" } +longbridge-proto = { path = "../proto", version = "0.2.7" } tokio = { version = "1.18.2", features = [ "time", From 542fdc66d292bb4b05967e526ac09437c366185f Mon Sep 17 00:00:00 2001 From: Sunli Date: Mon, 30 May 2022 09:07:13 +0800 Subject: [PATCH 019/567] Add some tests --- python/pysrc/longbridge/openapi.pyi | 2 +- python/src/trade/types.rs | 3 +- rust/src/trade/types.rs | 185 +++++++++++++++++++++++++++- 3 files changed, 184 insertions(+), 6 deletions(-) diff --git a/python/pysrc/longbridge/openapi.pyi b/python/pysrc/longbridge/openapi.pyi index 1a77d5fdd4..466dd918e4 100644 --- a/python/pysrc/longbridge/openapi.pyi +++ b/python/pysrc/longbridge/openapi.pyi @@ -2243,7 +2243,7 @@ class Order: Executed price """ - submitted_at: Optional[datetime] + submitted_at: datetime """ Submitted time """ diff --git a/python/src/trade/types.rs b/python/src/trade/types.rs index 9c76d7d755..faf946923f 100644 --- a/python/src/trade/types.rs +++ b/python/src/trade/types.rs @@ -202,8 +202,7 @@ pub(crate) struct Order { #[py(opt)] executed_price: Option, /// Submitted time - #[py(opt)] - submitted_at: Option, + submitted_at: PyOffsetDateTimeWrapper, /// Order side side: OrderSide, /// Security code diff --git a/rust/src/trade/types.rs b/rust/src/trade/types.rs index c75df7b22f..641872b8cc 100644 --- a/rust/src/trade/types.rs +++ b/rust/src/trade/types.rs @@ -242,8 +242,8 @@ pub struct Order { #[serde(with = "serde_utils::decimal_opt_0_is_none")] pub executed_price: Option, /// Submitted time - #[serde(with = "serde_utils::timestamp_opt")] - pub submitted_at: Option, + #[serde(with = "serde_utils::timestamp")] + pub submitted_at: OffsetDateTime, /// Order side pub side: OrderSide, /// Security code @@ -254,7 +254,7 @@ pub struct Order { #[serde(with = "serde_utils::decimal_opt_empty_is_none")] pub last_done: Option, /// `LIT` / `MIT` Order Trigger Price - #[serde(with = "serde_utils::decimal_opt_0_is_none")] + #[serde(with = "serde_utils::decimal_opt_empty_is_none")] pub trigger_price: Option, /// Rejected Message or remark pub msg: String, @@ -721,4 +721,183 @@ mod tests { assert_eq!(cash_info.settling_cash, "-276806.51".parse().unwrap()); assert_eq!(cash_info.currency, "USD"); } + + #[test] + fn history_orders() { + let data = r#" + { + "orders": [ + { + "currency": "HKD", + "executed_price": "0.000", + "executed_quantity": "0", + "expire_date": "", + "last_done": "", + "limit_offset": "", + "msg": "", + "order_id": "706388312699592704", + "order_type": "ELO", + "outside_rth": "UnknownOutsideRth", + "price": "11.900", + "quantity": "200", + "side": "Buy", + "status": "RejectedStatus", + "stock_name": "Bank of East Asia Ltd/The", + "submitted_at": "1651644897", + "symbol": "23.HK", + "tag": "Normal", + "time_in_force": "Day", + "trailing_amount": "", + "trailing_percent": "", + "trigger_at": "0", + "trigger_price": "", + "trigger_status": "NOT_USED", + "updated_at": "1651644898" + } + ] + } + "#; + + #[derive(Deserialize)] + struct Response { + orders: Vec, + } + + let resp: Response = serde_json::from_str(data).unwrap(); + assert_eq!(resp.orders.len(), 1); + + let order = &resp.orders[0]; + assert_eq!(order.currency, "HKD"); + assert!(order.executed_price.is_none()); + assert!(order.executed_quantity.is_none()); + assert!(order.expire_date.is_none()); + assert!(order.last_done.is_none()); + assert!(order.limit_offset.is_none()); + assert_eq!(order.msg, ""); + assert_eq!(order.order_id, "706388312699592704"); + assert_eq!(order.order_type, OrderType::ELO); + assert!(order.outside_rth.is_none()); + assert_eq!(order.price, Some("11.900".parse().unwrap())); + assert_eq!(order.quantity, "200".parse().unwrap()); + assert_eq!(order.side, OrderSide::Buy); + assert_eq!(order.status, OrderStatus::Rejected); + assert_eq!(order.stock_name, "Bank of East Asia Ltd/The"); + assert_eq!(order.submitted_at, datetime!(2022-05-04 14:14:57 +8)); + assert_eq!(order.symbol, "23.HK"); + assert_eq!(order.tag, OrderTag::Normal); + assert_eq!(order.time_in_force, TimeInForceType::Day); + assert!(order.trailing_amount.is_none()); + assert!(order.trailing_percent.is_none()); + assert!(order.trigger_at.is_none()); + assert!(order.trigger_price.is_none()); + assert!(order.trigger_status.is_none()); + assert_eq!(order.updated_at, Some(datetime!(2022-05-04 14:14:58 +8))); + } + + #[test] + fn today_orders() { + let data = r#" + { + "orders": [ + { + "currency": "HKD", + "executed_price": "0.000", + "executed_quantity": "0", + "expire_date": "", + "last_done": "", + "limit_offset": "", + "msg": "", + "order_id": "706388312699592704", + "order_type": "ELO", + "outside_rth": "UnknownOutsideRth", + "price": "11.900", + "quantity": "200", + "side": "Buy", + "status": "RejectedStatus", + "stock_name": "Bank of East Asia Ltd/The", + "submitted_at": "1651644897", + "symbol": "23.HK", + "tag": "Normal", + "time_in_force": "Day", + "trailing_amount": "", + "trailing_percent": "", + "trigger_at": "0", + "trigger_price": "", + "trigger_status": "NOT_USED", + "updated_at": "1651644898" + } + ] + } + "#; + + #[derive(Deserialize)] + struct Response { + orders: Vec, + } + + let resp: Response = serde_json::from_str(data).unwrap(); + assert_eq!(resp.orders.len(), 1); + + let order = &resp.orders[0]; + assert_eq!(order.currency, "HKD"); + assert!(order.executed_price.is_none()); + assert!(order.executed_quantity.is_none()); + assert!(order.expire_date.is_none()); + assert!(order.last_done.is_none()); + assert!(order.limit_offset.is_none()); + assert_eq!(order.msg, ""); + assert_eq!(order.order_id, "706388312699592704"); + assert_eq!(order.order_type, OrderType::ELO); + assert!(order.outside_rth.is_none()); + assert_eq!(order.price, Some("11.900".parse().unwrap())); + assert_eq!(order.quantity, "200".parse().unwrap()); + assert_eq!(order.side, OrderSide::Buy); + assert_eq!(order.status, OrderStatus::Rejected); + assert_eq!(order.stock_name, "Bank of East Asia Ltd/The"); + assert_eq!(order.submitted_at, datetime!(2022-05-04 14:14:57 +8)); + assert_eq!(order.symbol, "23.HK"); + assert_eq!(order.tag, OrderTag::Normal); + assert_eq!(order.time_in_force, TimeInForceType::Day); + assert!(order.trailing_amount.is_none()); + assert!(order.trailing_percent.is_none()); + assert!(order.trigger_at.is_none()); + assert!(order.trigger_price.is_none()); + assert!(order.trigger_status.is_none()); + assert_eq!(order.updated_at, Some(datetime!(2022-05-04 14:14:58 +8))); + } + + #[test] + fn history_executions() { + let data = r#" + { + "has_more": false, + "trades": [ + { + "order_id": "693664675163312128", + "price": "388", + "quantity": "100", + "symbol": "700.HK", + "trade_done_at": "1648611351", + "trade_id": "693664675163312128-1648611351433741210" + } + ] + } + "#; + + #[derive(Deserialize)] + struct Response { + trades: Vec, + } + + let resp: Response = serde_json::from_str(data).unwrap(); + assert_eq!(resp.trades.len(), 1); + + let execution = &resp.trades[0]; + assert_eq!(execution.order_id, "693664675163312128"); + assert_eq!(execution.price, "388".parse().unwrap()); + assert_eq!(execution.quantity, "100".parse().unwrap()); + assert_eq!(execution.symbol, "700.HK"); + assert_eq!(execution.trade_done_at, datetime!(2022-03-30 11:35:51 +8)); + assert_eq!(execution.trade_id, "693664675163312128-1648611351433741210"); + } } From b2bcb8f3089a09e584bb299980cc284ec7e55e63 Mon Sep 17 00:00:00 2001 From: Sunli Date: Mon, 30 May 2022 09:21:34 +0800 Subject: [PATCH 020/567] Release 0.2.8 longbridge@0.2.8 longbridge-httpcli@0.2.8 longbridge-proto@0.2.8 longbridge-python@0.2.8 longbridge-python-macros@0.2.8 longbridge-wscli@0.2.8 Generated by cargo-workspaces --- python/Cargo.toml | 6 +++--- python/Makefile.toml | 2 +- python/crates/macros/Cargo.toml | 2 +- rust/Cargo.toml | 8 ++++---- rust/crates/httpclient/Cargo.toml | 2 +- rust/crates/proto/Cargo.toml | 2 +- rust/crates/wsclient/Cargo.toml | 4 ++-- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/python/Cargo.toml b/python/Cargo.toml index 7a6c9e8d85..d1a2729976 100644 --- a/python/Cargo.toml +++ b/python/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge-python" -version = "0.2.7" +version = "0.2.8" description = "Longbridge OpenAPI SDK for Python" homepage = "https://open.longbridgeapp.com/en/" readme = "README.md" @@ -15,8 +15,8 @@ name = "longbridge" crate-type = ["cdylib"] [dependencies] -longbridge = { path = "../rust", version = "0.2.7", features = ["blocking"] } -longbridge-python-macros = { path = "crates/macros", version = "0.2.7" } +longbridge = { path = "../rust", version = "0.2.8", features = ["blocking"] } +longbridge-python-macros = { path = "crates/macros", version = "0.2.8" } once_cell = "1.11.0" pyo3 = { version = "0.16.4", features = ["anyhow", "extension-module"] } diff --git a/python/Makefile.toml b/python/Makefile.toml index d931dcbf58..6084c3ed3f 100644 --- a/python/Makefile.toml +++ b/python/Makefile.toml @@ -12,7 +12,7 @@ args = ["install", "maturin>=0.12,<0.13"] command = "pip" args = [ "install", - "target/wheels/longbridge-0.2.7-cp310-none-win_amd64.whl", + "target/wheels/longbridge-0.2.8-cp310-none-win_amd64.whl", "-I", ] dependencies = ["build-python-sdk"] diff --git a/python/crates/macros/Cargo.toml b/python/crates/macros/Cargo.toml index 3c84c0fa9a..7765e73838 100644 --- a/python/crates/macros/Cargo.toml +++ b/python/crates/macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "longbridge-python-macros" -version = "0.2.7" +version = "0.2.8" edition = "2021" [lib] diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 074e43d03b..e7e3e84460 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge" -version = "0.2.7" +version = "0.2.8" description = "Longbridge OpenAPI SDK for Rust" homepage = "https://open.longbridgeapp.com/en/" readme = "README.md" @@ -14,9 +14,9 @@ categories = ["api-bindings"] blocking = [] [dependencies] -longbridge-wscli = { path = "crates/wsclient", version = "0.2.7" } -longbridge-httpcli = { path = "crates/httpclient", version = "0.2.7" } -longbridge-proto = { path = "crates/proto", version = "0.2.7" } +longbridge-wscli = { path = "crates/wsclient", version = "0.2.8" } +longbridge-httpcli = { path = "crates/httpclient", version = "0.2.8" } +longbridge-proto = { path = "crates/proto", version = "0.2.8" } tokio = { version = "1.18.2", features = [ "time", diff --git a/rust/crates/httpclient/Cargo.toml b/rust/crates/httpclient/Cargo.toml index 0c8e96c1ec..0350df330e 100644 --- a/rust/crates/httpclient/Cargo.toml +++ b/rust/crates/httpclient/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge-httpcli" -version = "0.2.7" +version = "0.2.8" description = "Longbridge HTTP SDK for Rust" license = "MIT OR Apache-2.0" diff --git a/rust/crates/proto/Cargo.toml b/rust/crates/proto/Cargo.toml index 14d60b1fe9..64b37b1522 100644 --- a/rust/crates/proto/Cargo.toml +++ b/rust/crates/proto/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge-proto" -version = "0.2.7" +version = "0.2.8" description = "Longbridge Protocol" license = "MIT OR Apache-2.0" diff --git a/rust/crates/wsclient/Cargo.toml b/rust/crates/wsclient/Cargo.toml index 22f5798b2b..96718328c2 100644 --- a/rust/crates/wsclient/Cargo.toml +++ b/rust/crates/wsclient/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "longbridge-wscli" -version = "0.2.7" +version = "0.2.8" edition = "2021" description = "Longbridge Websocket SDK for Rust" license = "MIT OR Apache-2.0" [dependencies] -longbridge-proto = { path = "../proto", version = "0.2.7" } +longbridge-proto = { path = "../proto", version = "0.2.8" } tokio = { version = "1.18.2", features = [ "time", From 93c29fe44fae375df4543476a524a4a4327cd32f Mon Sep 17 00:00:00 2001 From: Sunli Date: Mon, 30 May 2022 16:24:48 +0800 Subject: [PATCH 021/567] Add Node.js SDK --- {misc => .github/misc}/render_docs_index.py | 0 {misc => .github/misc}/templates/docs.html | 0 .github/workflows/ci.yml | 31 +- .github/workflows/docs.yml | 2 +- Cargo.toml | 2 +- README.md | 11 +- nodejs/.gitignore | 1 + nodejs/.npmignore | 10 + nodejs/Cargo.toml | 31 + nodejs/README.md | 3 + nodejs/build.rs | 5 + nodejs/crates/macros/Cargo.toml | 14 + nodejs/crates/macros/src/error.rs | 22 + nodejs/crates/macros/src/jsenum.rs | 108 ++ nodejs/crates/macros/src/jsobject.rs | 126 ++ nodejs/crates/macros/src/lib.rs | 24 + nodejs/index.d.ts | 1106 +++++++++++++++++ nodejs/index.js | 316 +++++ nodejs/npm/darwin-x64/README.md | 3 + nodejs/npm/darwin-x64/package.json | 18 + nodejs/npm/linux-x64-gnu/README.md | 3 + nodejs/npm/linux-x64-gnu/package.json | 21 + nodejs/npm/win32-x64-msvc/README.md | 3 + nodejs/npm/win32-x64-msvc/package.json | 18 + nodejs/package.json | 24 + nodejs/src/config.rs | 54 + nodejs/src/decimal.rs | 31 + nodejs/src/error.rs | 8 + nodejs/src/lib.rs | 10 + nodejs/src/quote/context.rs | 418 +++++++ nodejs/src/quote/mod.rs | 3 + nodejs/src/quote/push.rs | 30 + nodejs/src/quote/types.rs | 677 ++++++++++ nodejs/src/time.rs | 92 ++ nodejs/src/trade/context.rs | 233 ++++ nodejs/src/trade/mod.rs | 13 + nodejs/src/trade/requests/get_cash_flow.rs | 56 + .../trade/requests/get_history_executions.rs | 46 + .../src/trade/requests/get_history_orders.rs | 75 ++ .../trade/requests/get_today_executions.rs | 35 + nodejs/src/trade/requests/get_today_orders.rs | 59 + nodejs/src/trade/requests/mod.rs | 15 + nodejs/src/trade/requests/replace_order.rs | 67 + nodejs/src/trade/requests/submit_order.rs | 95 ++ nodejs/src/trade/types.rs | 491 ++++++++ nodejs/src/types.rs | 18 + nodejs/src/utils.rs | 17 + python/Cargo.toml | 2 +- python/crates/macros/src/pyenum.rs | 61 +- python/crates/macros/src/pyobject.rs | 12 +- python/pysrc/longbridge/openapi.pyi | 6 +- python/src/config.rs | 4 +- python/src/error.rs | 17 + python/src/lib.rs | 7 + python/src/quote/context.rs | 88 +- python/src/quote/types.rs | 110 +- python/src/trade/context.rs | 66 +- python/src/trade/types.rs | 64 +- python/src/types.rs | 2 +- rust/Cargo.toml | 1 - rust/crates/httpclient/src/request.rs | 1 + rust/examples/pull_quotes.rs | 3 +- rust/examples/submit_order.rs | 3 +- rust/examples/subscribe_quote.rs | 3 +- rust/src/blocking/quote.rs | 3 +- rust/src/blocking/runtime.rs | 3 +- rust/src/blocking/trade.rs | 4 +- rust/src/config.rs | 3 +- rust/src/error.rs | 59 + rust/src/lib.rs | 2 + rust/src/quote/context.rs | 23 +- rust/src/quote/core.rs | 3 +- rust/src/quote/push_types.rs | 21 +- rust/src/quote/types.rs | 76 +- rust/src/quote/utils.rs | 8 +- rust/src/trade/context.rs | 3 +- rust/src/trade/core.rs | 6 +- rust/src/trade/push_types.rs | 23 +- rust/src/trade/requests/get_cash_flow.rs | 2 +- .../trade/requests/get_history_executions.rs | 2 +- rust/src/trade/requests/get_history_orders.rs | 2 +- .../trade/requests/get_today_executions.rs | 2 +- rust/src/trade/requests/get_today_orders.rs | 2 +- rust/src/trade/requests/replace_order.rs | 2 +- rust/src/trade/requests/submit_order.rs | 2 +- rust/src/trade/types.rs | 9 +- 86 files changed, 4865 insertions(+), 290 deletions(-) rename {misc => .github/misc}/render_docs_index.py (100%) rename {misc => .github/misc}/templates/docs.html (100%) create mode 100644 nodejs/.gitignore create mode 100644 nodejs/.npmignore create mode 100644 nodejs/Cargo.toml create mode 100644 nodejs/README.md create mode 100644 nodejs/build.rs create mode 100644 nodejs/crates/macros/Cargo.toml create mode 100644 nodejs/crates/macros/src/error.rs create mode 100644 nodejs/crates/macros/src/jsenum.rs create mode 100644 nodejs/crates/macros/src/jsobject.rs create mode 100644 nodejs/crates/macros/src/lib.rs create mode 100644 nodejs/index.d.ts create mode 100644 nodejs/index.js create mode 100644 nodejs/npm/darwin-x64/README.md create mode 100644 nodejs/npm/darwin-x64/package.json create mode 100644 nodejs/npm/linux-x64-gnu/README.md create mode 100644 nodejs/npm/linux-x64-gnu/package.json create mode 100644 nodejs/npm/win32-x64-msvc/README.md create mode 100644 nodejs/npm/win32-x64-msvc/package.json create mode 100644 nodejs/package.json create mode 100644 nodejs/src/config.rs create mode 100644 nodejs/src/decimal.rs create mode 100644 nodejs/src/error.rs create mode 100644 nodejs/src/lib.rs create mode 100644 nodejs/src/quote/context.rs create mode 100644 nodejs/src/quote/mod.rs create mode 100644 nodejs/src/quote/push.rs create mode 100644 nodejs/src/quote/types.rs create mode 100644 nodejs/src/time.rs create mode 100644 nodejs/src/trade/context.rs create mode 100644 nodejs/src/trade/mod.rs create mode 100644 nodejs/src/trade/requests/get_cash_flow.rs create mode 100644 nodejs/src/trade/requests/get_history_executions.rs create mode 100644 nodejs/src/trade/requests/get_history_orders.rs create mode 100644 nodejs/src/trade/requests/get_today_executions.rs create mode 100644 nodejs/src/trade/requests/get_today_orders.rs create mode 100644 nodejs/src/trade/requests/mod.rs create mode 100644 nodejs/src/trade/requests/replace_order.rs create mode 100644 nodejs/src/trade/requests/submit_order.rs create mode 100644 nodejs/src/trade/types.rs create mode 100644 nodejs/src/types.rs create mode 100644 nodejs/src/utils.rs create mode 100644 python/src/error.rs create mode 100644 rust/src/error.rs diff --git a/misc/render_docs_index.py b/.github/misc/render_docs_index.py similarity index 100% rename from misc/render_docs_index.py rename to .github/misc/render_docs_index.py diff --git a/misc/templates/docs.html b/.github/misc/templates/docs.html similarity index 100% rename from misc/templates/docs.html rename to .github/misc/templates/docs.html diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ed63b6eabd..b7265478f7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,7 +7,7 @@ on: pull_request: {} jobs: - check_format: + check-format: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 @@ -86,3 +86,32 @@ jobs: - name: Test run: cargo test -p longbridge-python --all-features + + check-nodejs-sdk: + strategy: + fail-fast: true + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + runs-on: ${{ matrix.os }} + steps: + - name: Setup node + uses: actions/setup-node@v3 + with: + node-version: 16 + check-latest: true + architecture: x86_64 + + - name: Install Protoc + uses: arduino/setup-protoc@v1 + with: + repo-token: ${{ github.token }} + + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: 1.60.0 + override: true + components: rustfmt, clippy + + - name: Build + run: npm run build diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index e11c3b79a2..723c5b4719 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -56,7 +56,7 @@ jobs: mkdir -p gh-pages/python mv -f target/doc gh-pages/rust/$RUST_SDK_VERSION mv -f python/site gh-pages/python/$PYTHON_SDK_VERSION - python misc/render_docs_index.py > gh-pages/index.html + python .github/misc/render_docs_index.py > gh-pages/index.html - name: Deploy uses: peaceiris/actions-gh-pages@v2.5.1 with: diff --git a/Cargo.toml b/Cargo.toml index cedbf0e15c..4061584223 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,2 +1,2 @@ [workspace] -members = ["rust", "python"] +members = ["rust", "python", "nodejs"] diff --git a/README.md b/README.md index 18dbdee7f4..3da0729e64 100644 --- a/README.md +++ b/README.md @@ -9,11 +9,12 @@ Longbridge OpenAPI provides programmatic quote trading interfaces for investors **This repo contains the following main components:** -| Name | Description | -|----------------------------|--------------------------------------------| -| [Rust](rust/README.md) | Longbridge OpenAPI for Rust `(>= 1.56.1)` | -| [Python](python/README.md) | Longbridge OpenAPI for Python 3 `(>= 3.7)` | -| Go | WIP | +| Name | Description | +|-----------------------------|--------------------------------------------| +| [Rust](rust/README.md) | Longbridge OpenAPI for Rust `(>= 1.56.1)` | +| [Python](python/README.md) | Longbridge OpenAPI for Python 3 `(>= 3.7)` | +| [Node.js](nodejs/README.md) | Longbridge OpenAPI for Npde.js `(>= 10)` | +| Go | WIP | ## How to enable Longbridge OpenAPI diff --git a/nodejs/.gitignore b/nodejs/.gitignore new file mode 100644 index 0000000000..e9beb7b3d1 --- /dev/null +++ b/nodejs/.gitignore @@ -0,0 +1 @@ +longbridge.*.node diff --git a/nodejs/.npmignore b/nodejs/.npmignore new file mode 100644 index 0000000000..f96abe0b8c --- /dev/null +++ b/nodejs/.npmignore @@ -0,0 +1,10 @@ +target +Cargo.lock +.cargo +.github +npm +.eslintrc +.prettierignore +rustfmt.toml +yarn.lock +*.node diff --git a/nodejs/Cargo.toml b/nodejs/Cargo.toml new file mode 100644 index 0000000000..735c6ae92e --- /dev/null +++ b/nodejs/Cargo.toml @@ -0,0 +1,31 @@ +[package] +edition = "2021" +name = "longbridge-nodejs" +version = "0.2.8" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +longbridge = { path = "../rust", version = "0.2.8" } +longbridge-nodejs-macros = { path = "crates/macros", version = "0.2.8" } + +# napi = { version = "2.4.3", default-features = false, features = ["napi4"] } +# napi-derive = "2.4.1" +napi = { git = "https://github.com/sunli829/napi-rs", default-features = false, features = [ + "napi4", + "chrono_date", + "async", +] } +napi-derive = { git = "https://github.com/sunli829/napi-rs" } +rust_decimal = { version = "1.23.1", features = ["maths"] } +chrono = "0.4.19" +time = "0.3.9" +tokio = { version = "1.18.2", features = ["rt", "time"] } +parking_lot = "0.12.0" + +[build-dependencies] +napi-build = "2.0.0" + +[profile.release] +lto = true diff --git a/nodejs/README.md b/nodejs/README.md new file mode 100644 index 0000000000..5b91cc22cb --- /dev/null +++ b/nodejs/README.md @@ -0,0 +1,3 @@ +# Longbridge OpenAPI SDK for Node.js + +`longbridge` provides an easy-to-use interface for invokes [`Longbridge OpenAPI`](https://open.longbridgeapp.com/en/). diff --git a/nodejs/build.rs b/nodejs/build.rs new file mode 100644 index 0000000000..9fc2367889 --- /dev/null +++ b/nodejs/build.rs @@ -0,0 +1,5 @@ +extern crate napi_build; + +fn main() { + napi_build::setup(); +} diff --git a/nodejs/crates/macros/Cargo.toml b/nodejs/crates/macros/Cargo.toml new file mode 100644 index 0000000000..daf2c56e74 --- /dev/null +++ b/nodejs/crates/macros/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "longbridge-nodejs-macros" +version = "0.2.8" +edition = "2021" + +[lib] +proc-macro = true + +[dependencies] +darling = "0.13.0" +proc-macro2 = "1.0.29" +quote = "1.0.9" +syn = { version = "1.0.77", features = [] } +thiserror = "1.0.29" diff --git a/nodejs/crates/macros/src/error.rs b/nodejs/crates/macros/src/error.rs new file mode 100644 index 0000000000..a715a210c0 --- /dev/null +++ b/nodejs/crates/macros/src/error.rs @@ -0,0 +1,22 @@ +use proc_macro2::TokenStream; +use thiserror::Error; + +#[derive(Error, Debug)] +pub(crate) enum GeneratorError { + #[error("{0}")] + Syn(#[from] syn::Error), + + #[error("{0}")] + Darling(#[from] darling::Error), +} + +impl GeneratorError { + pub(crate) fn write_errors(self) -> TokenStream { + match self { + GeneratorError::Syn(err) => err.to_compile_error(), + GeneratorError::Darling(err) => err.write_errors(), + } + } +} + +pub(crate) type GeneratorResult = std::result::Result; diff --git a/nodejs/crates/macros/src/jsenum.rs b/nodejs/crates/macros/src/jsenum.rs new file mode 100644 index 0000000000..74bcf74400 --- /dev/null +++ b/nodejs/crates/macros/src/jsenum.rs @@ -0,0 +1,108 @@ +use darling::{ + ast::{Data, Fields}, + util::Ignored, + FromDeriveInput, FromVariant, +}; +use proc_macro2::TokenStream; +use quote::quote; +use syn::{DeriveInput, Error, Ident, TypePath}; + +use crate::error::GeneratorResult; + +#[derive(FromVariant)] +#[darling(attributes(js), forward_attrs(doc))] +struct EnumItem { + ident: Ident, + fields: Fields, + + #[darling(default)] + remote: Option, +} + +#[derive(FromDeriveInput)] +#[darling(attributes(js), forward_attrs(doc))] +struct EnumArgs { + ident: Ident, + data: Data, + + remote: TypePath, + #[darling(default)] + from: Option, + #[darling(default)] + into: Option, +} + +pub(crate) fn generate(args: DeriveInput) -> GeneratorResult { + let EnumArgs { + ident, + data, + remote, + from, + into, + } = EnumArgs::from_derive_input(&args)?; + let from = from.unwrap_or(true); + let into = into.unwrap_or(true); + + let e = match data { + Data::Enum(e) => e, + _ => return Err(Error::new_spanned(ident, "Can only be applied to an enum.").into()), + }; + + let mut from_remote = Vec::new(); + let mut from_local = Vec::new(); + + for variant in e { + if !variant.fields.is_empty() { + return Err(Error::new_spanned( + &variant.ident, + format!("Invalid enum variant {}.", variant.ident), + ) + .into()); + } + + let item_ident = &variant.ident; + let remote_ident = variant.remote.as_ref().unwrap_or(&variant.ident); + + from_remote.push(quote! { + #remote::#remote_ident => #ident::#item_ident, + }); + from_local.push(quote! { + #ident::#item_ident => #remote::#remote_ident, + }); + } + + let impl_from = if from { + Some(quote! { + impl ::std::convert::From<#remote> for #ident { + fn from(value: #remote) -> #ident { + match value { + #(#from_remote)* + } + } + } + }) + } else { + None + }; + + let impl_into = if into { + Some(quote! { + impl ::std::convert::From<#ident> for #remote { + fn from(value: #ident) -> #remote { + match value { + #(#from_local)* + } + } + } + }) + } else { + None + }; + + let expanded = quote! { + #impl_from + #impl_into + }; + + Ok(expanded) +} diff --git a/nodejs/crates/macros/src/jsobject.rs b/nodejs/crates/macros/src/jsobject.rs new file mode 100644 index 0000000000..0bd0d5e3aa --- /dev/null +++ b/nodejs/crates/macros/src/jsobject.rs @@ -0,0 +1,126 @@ +use darling::{ast::Data, util::Ignored, FromDeriveInput, FromField}; +use proc_macro2::TokenStream; +use quote::quote; +use syn::{Attribute, DeriveInput, Error, Ident, Type, TypePath}; + +use crate::error::GeneratorResult; + +#[derive(FromField)] +#[darling(attributes(js), forward_attrs(doc))] +struct ObjectField { + ident: Option, + ty: Type, + attrs: Vec, + + #[darling(default)] + array: bool, + #[darling(default)] + opt: bool, + #[darling(default)] + datetime: bool, +} + +#[derive(FromDeriveInput)] +#[darling(attributes(js), forward_attrs(doc))] +struct ObjectArgs { + ident: Ident, + data: Data, + + remote: TypePath, +} + +pub(crate) fn generate(args: DeriveInput) -> GeneratorResult { + let ObjectArgs { + ident, + data, + remote, + } = ObjectArgs::from_derive_input(&args)?; + + let s = match data { + Data::Struct(s) => s, + _ => return Err(Error::new_spanned(ident, "Can only be applied to an struct.").into()), + }; + + let mut getters = Vec::new(); + let mut from_fields = Vec::new(); + + for field in &s.fields { + let field_ident = field.ident.as_ref().unwrap(); + let field_type = &field.ty; + let attrs = &field.attrs; + + getters.push(quote! { + #[napi(getter)] + #[inline] + #(#attrs)* + pub fn #field_ident(&self) -> #field_type { + self.#field_ident.clone() + } + }); + + match (field.array, field.opt, field.datetime) { + (true, false, false) => { + from_fields.push(quote! { + #field_ident: value.#field_ident + .into_iter() + .map(TryInto::try_into) + .collect::<::std::result::Result<::std::vec::Vec<_>, _>>()?, + }); + } + (false, true, false) => { + from_fields.push(quote! { + #field_ident: match value.#field_ident { + ::std::option::Option::Some(value) => ::std::option::Option::Some(value.try_into()?), + ::std::option::Option::None => ::std::option::Option::None, + }, + }); + } + (false, false, false) => { + from_fields.push(quote! { + #field_ident: value.#field_ident.try_into()?, + }); + } + (true, false, true) => { + from_fields.push(quote! { + #field_ident: value.#field_ident + .into_iter() + .map(crate::utils::to_datetime) + .collect::<::std::result::Result<::std::vec::Vec<_>, _>>()?, + }); + } + (false, true, true) => { + from_fields.push(quote! { + #field_ident: value.#field_ident.map(crate::utils::to_datetime), + }); + } + (false, false, true) => { + from_fields.push(quote! { + #field_ident: crate::utils::to_datetime(value.#field_ident), + }); + } + _ => return Err(Error::new_spanned(ident, "Invalid attribute.").into()), + } + } + + let expanded = quote! { + #[napi_derive::napi] + impl #ident { + #(#getters)* + } + + impl ::std::convert::TryFrom<#remote> for #ident { + type Error = ::napi::Error; + + fn try_from(value: #remote) -> ::std::result::Result { + use ::std::convert::TryInto; + use ::std::iter::Iterator; + + Ok(Self { + #(#from_fields)* + }) + } + } + }; + + Ok(expanded) +} diff --git a/nodejs/crates/macros/src/lib.rs b/nodejs/crates/macros/src/lib.rs new file mode 100644 index 0000000000..10078c3c31 --- /dev/null +++ b/nodejs/crates/macros/src/lib.rs @@ -0,0 +1,24 @@ +mod error; +mod jsenum; +mod jsobject; + +use proc_macro::TokenStream; +use syn::{parse_macro_input, DeriveInput}; + +#[proc_macro_derive(JsObject, attributes(js))] +pub fn derive_pyobject(input: TokenStream) -> TokenStream { + let args = parse_macro_input!(input as DeriveInput); + match jsobject::generate(args) { + Ok(stream) => stream.into(), + Err(err) => err.write_errors().into(), + } +} + +#[proc_macro_derive(JsEnum, attributes(js))] +pub fn derive_jsenum(input: TokenStream) -> TokenStream { + let args = parse_macro_input!(input as DeriveInput); + match jsenum::generate(args) { + Ok(stream) => stream.into(), + Err(err) => err.write_errors().into(), + } +} diff --git a/nodejs/index.d.ts b/nodejs/index.d.ts new file mode 100644 index 0000000000..1431259a5e --- /dev/null +++ b/nodejs/index.d.ts @@ -0,0 +1,1106 @@ +/* tslint:disable */ +/* eslint-disable */ + +/* auto-generated by NAPI-RS */ + +export interface ConfigParams { + /** App Key */ + appKey: string + /** App Secret */ + appSecret: string + /** Access Token */ + accessToken: string + /** HTTP API url (default: "https://openapi.longbridgeapp.com") */ + httpUrl?: string + /** + * Websocket url for quote API (default: + * "wss://openapi-quote.longbridgeapp.com") + */ + quoteWsUrl?: string + /** + * Websocket url for trade API (default: + * "wss://openapi-trade.longbridgeapp.com") + */ + tradeWsUrl?: string +} +/** Derivative type */ +export const enum DerivativeType { + /** US stock options */ + Option = 0, + /** HK warrants */ + Warrant = 1 +} +export const enum TradeStatus { + /** Normal */ + Normal = 0, + /** Suspension */ + Halted = 1, + /** Delisted */ + Delisted = 2, + /** Fuse */ + Fuse = 3, + /** Prepare List */ + PrepareList = 4, + /** Code Moved */ + CodeMoved = 5, + /** To Be Opened */ + ToBeOpened = 6, + /** Split Stock Halts */ + SplitStockHalts = 7, + /** Expired */ + Expired = 8, + /** Warrant To BeListed */ + WarrantPrepareList = 9, + /** Warrant To BeListed */ + Suspend = 10 +} +/** Trade session */ +export const enum TradeSession { + /** Trading */ + Normal = 0, + /** Pre-Trading */ + Pre = 1, + /** Post-Trading */ + Post = 2 +} +/** Quote type of subscription */ +export const enum SubType { + /** Quote */ + Quote = 0, + /** Depth */ + Depth = 1, + /** Brokers */ + Brokers = 2, + /** Trade */ + Trade = 3 +} +/** Trade direction */ +export const enum TradeDirection { + /** Neutral */ + Neutral = 0, + /** Down */ + Down = 1, + /** Up */ + Up = 2 +} +/** Option type */ +export const enum OptionType { + /** Unknown */ + Unknown = 0, + /** American */ + American = 1, + /** Europe */ + Europe = 2 +} +/** Option direction */ +export const enum OptionDirection { + /** Unknown */ + Unknown = 0, + /** Put */ + Put = 1, + /** Call */ + Call = 2 +} +/** Warrant type */ +export const enum WarrantType { + /** Unknown */ + Unknown = 0, + /** Call */ + Call = 1, + /** Put */ + Put = 2, + /** Bull */ + Bull = 3, + /** Bear */ + Bear = 4, + /** Inline */ + Inline = 5 +} +/** Candlestick period */ +export const enum Period { + /** One Minute */ + Min_1 = 0, + /** Five Minutes */ + Min_5 = 1, + /** Fifteen Minutes */ + Min_15 = 2, + /** Thirty Minutes */ + Min_30 = 3, + /** Sixty Minutes */ + Min_60 = 4, + /** One Days */ + Day = 5, + /** One Week */ + Week = 6, + /** One Month */ + Month = 7, + /** One Year */ + Year = 8 +} +/** Candlestick adjustment type */ +export const enum AdjustType { + /** Actual */ + NoAdjust = 0, + /** Adjust forward */ + ForwardAdjust = 1 +} +export function sleep(milliseconds: number): Promise +/** Topic type */ +export const enum TopicType { + /** Private notification for trade */ + Private = 0 +} +export const enum OrderStatus { + /** Unknown */ + Unknown = 0, + /** Not reported */ + NotReported = 1, + /** Not reported (Replaced Order) */ + ReplacedNotReported = 2, + /** Not reported (Protected Order) */ + ProtectedNotReported = 3, + /** Not reported (Conditional Order) */ + VarietiesNotReported = 4, + /** Filled */ + Filled = 5, + /** Wait To New */ + WaitToNew = 6, + /** New */ + New = 7, + /** Wait To Replace */ + WaitToReplace = 8, + /** Pending Replace */ + PendingReplace = 9, + /** Replaced */ + Replaced = 10, + /** Partial Filled */ + PartialFilled = 11, + /** Wait To Cancel */ + WaitToCancel = 12, + /** Pending Cancel */ + PendingCancel = 13, + /** Rejected */ + Rejected = 14, + /** Canceled */ + Canceled = 15, + /** Expired */ + Expired = 16, + /** Partial Withdrawal */ + PartialWithdrawal = 17 +} +export const enum OrderSide { + /** Unknown */ + Unknown = 0, + /** Buy */ + Buy = 1, + /** Sell */ + Sell = 2 +} +export const enum OrderType { + /** Unknown */ + Unknown = 0, + /** Limit Order */ + LO = 1, + /** Enhanced Limit Order */ + ELO = 2, + /** Market Order */ + MO = 3, + /** At-auction Order */ + AO = 4, + /** At-auction Limit Order */ + ALO = 5, + /** Odd Lots */ + ODD = 6, + /** Limit If Touched */ + LIT = 7, + /** Market If Touched */ + MIT = 8, + /** Trailing Limit If Touched (Trailing Amount) */ + TSLPAMT = 9, + /** Trailing Limit If Touched (Trailing Percent) */ + TSLPPCT = 10, + /** Trailing Market If Touched (Trailing Amount) */ + TSMAMT = 11, + /** Trailing Market If Touched (Trailing Percent) */ + TSMPCT = 12 +} +/** Order tag */ +export const enum OrderTag { + /** Unknown */ + Unknown = 0, + /** Normal Order */ + Normal = 1, + /** Long term Order */ + LongTerm = 2, + /** Grey Order */ + Grey = 3 +} +/** Time in force type */ +export const enum TimeInForceType { + /** Unknown */ + Unknown = 0, + /** Day Order */ + Day = 1, + /** Good Til Canceled Order */ + GoodTilCanceled = 2, + /** Good Til Date Order */ + GoodTilDate = 3 +} +/** Trigger status */ +export const enum TriggerStatus { + /** Unknown */ + Unknown = 0, + /** Deactive */ + Deactive = 1, + /** Active */ + Active = 2, + /** Released */ + Released = 3 +} +/** Enable or disable outside regular trading hours */ +export const enum OutsideRTH { + /** Unknown */ + Unknown = 0, + /** Regular trading hour only */ + RTHOnly = 1, + /** Any time */ + AnyTime = 2 +} +export const enum BalanceType { + /** Unknown */ + Unknown = 0, + /** Limit Order */ + Cash = 1, + /** Stock */ + Stock = 2, + /** Fund */ + Fund = 3 +} +export const enum CashFlowDirection { + /** Unknown */ + Unknown = 0, + /** Out */ + Out = 1, + /** Stock */ + In = 2 +} +export const enum Market { + /** Unknown */ + Unknown = 0, + /** US market */ + US = 1, + /** HK market */ + HK = 2, + /** CN market */ + CN = 3, + /** SG market */ + SG = 4 +} +export class Config { + /** Create a new `Config` */ + constructor(params: ConfigParams) + /** Create a new `Config` from the given environment variables */ + static fromEnv(): Config +} +export class Decimal { + static new(value: string): Decimal +} +/** Trade context */ +export class QuoteContext { + static new(config: Config): Promise + set onQuote(callback: (err: null | Error, event: PushQuoteEvent) => void) + set onDepth(callback: (err: null | Error, event: PushDepthEvent) => void) + set onBrokers(callback: (err: null | Error, event: PushBrokersEvent) => void) + set onTrades(callback: (err: null | Error, event: PushTradesEvent) => void) + /** Subscribe */ + subscribe(symbols: Array, subTypes: Array, isFirstPush: boolean): Promise + /** Unsubscribe */ + unsubscribe(symbols: Array, subTypes: Array): Promise + /** Get basic information of securities */ + staticInfo(symbols: Array): Promise> + /** Get quote of securities */ + quote(symbols: Array): Promise> + /** Get quote of option securities */ + optionQuote(symbols: Array): Promise> + /** Get quote of warrant securities */ + warrantQuote(symbols: Array): Promise> + /** Get security depth */ + depth(symbol: string): Promise + /** Get security brokers */ + brokers(symbol: string): Promise + /** Get participants */ + participants(): Promise> + /** Get security trades */ + trades(symbol: string, count: number): Promise> + /** Get security intraday */ + intraday(symbol: string): Promise> + /** Get security candlesticks */ + candlesticks(symbol: string, period: Period, count: number, adjustType: AdjustType): Promise> + /** Get option chain expiry date list */ + optionChainExpiryDateList(symbol: string): Promise> + /** Get option chain info by date */ + optionChainInfoByDate(symbol: string, expiryDate: NaiveDate): Promise> + /** Get warrant issuers */ + warrantIssuers(): Promise> + /** Get trading session of the day */ + tradingSession(): Promise> + /** Get trading session of the day */ + tradingDays(market: Market, begin: NaiveDate, end: NaiveDate): Promise + /** Get real-time quote */ + realtimeQuote(symbols: Array): Promise> + /** Get real-time depth */ + realtimeDepth(symbol: string): Promise + /** Get real-time brokers */ + realtimeBrokers(symbol: string): Promise + /** Get real-time trades */ + realtimeTrades(symbol: string, count: number): Promise> +} +export class PushQuoteEvent { + get symbol(): string + get data(): PushQuote +} +export class PushDepthEvent { + get symbol(): string + get data(): PushDepth +} +export class PushBrokersEvent { + get symbol(): string + get data(): PushBrokers +} +export class PushTradesEvent { + get symbol(): string + get data(): PushTrades +} +/** The basic information of securities */ +export class SecurityStaticInfo { + /** Security code */ + get symbol(): string + /** Security name (zh-CN) */ + get nameCn(): string + /** Security name (en) */ + get nameEn(): string + /** Security name (zh-HK) */ + get nameHk(): string + /** Exchange which the security belongs to */ + get exchange(): string + /** Trading currency */ + get currency(): string + /** Lot size */ + get lotSize(): number + /** Total shares */ + get totalShares(): number + /** Circulating shares */ + get circulatingShares(): number + /** HK shares (only HK stocks) */ + get hkShares(): number + /** Earnings per share */ + get eps(): Decimal + /** Earnings per share (TTM) */ + get epsTtm(): Decimal + /** Net assets per share */ + get bps(): Decimal + /** Dividend yield */ + get dividendYield(): Decimal + /** Types of supported derivatives */ + get stockDerivatives(): Array +} +/** Quote of US pre/post market */ +export class PrePostQuote { + /** Latest price */ + get lastDone(): Decimal + /** Time of latest price */ + get timestamp(): Date + /** Volume */ + get volume(): number + /** Turnover */ + get turnover(): Decimal + /** High */ + get high(): Decimal + /** Low */ + get low(): Decimal + /** Close of the last trade session */ + get prevClose(): Decimal +} +/** Quote of securitity */ +export class SecurityQuote { + /** Security code */ + get symbol(): string + /** Latest price */ + get lastDone(): Decimal + /** Yesterday's close */ + get prevClose(): Decimal + /** Open */ + get open(): Decimal + /** High */ + get high(): Decimal + /** Low */ + get low(): Decimal + /** Time of latest price */ + get timestamp(): Date + /** Volume */ + get volume(): number + /** Turnover */ + get turnover(): Decimal + /** Security trading status */ + get tradeStatus(): TradeStatus + /** Quote of US pre market */ + get preMarketQuote(): PrePostQuote | null + /** Quote of US post market */ + get postMarketQuote(): PrePostQuote | null +} +/** Quote of option */ +export class OptionQuote { + /** Security code */ + get symbol(): string + /** Latest price */ + get lastDone(): Decimal + /** Yesterday's close */ + get prevClose(): Decimal + /** Open */ + get open(): Decimal + /** High */ + get high(): Decimal + /** Low */ + get low(): Decimal + /** Time of latest price */ + get timestamp(): Date + /** Volume */ + get volume(): number + /** Turnover */ + get turnover(): Decimal + /** Security trading status */ + get tradeStatus(): TradeStatus + /** Implied volatility */ + get impliedVolatility(): Decimal + /** Number of open positions */ + get openInterest(): number + /** Exprity date */ + get expiryDate(): NaiveDate + /** Strike price */ + get strikePrice(): Decimal + /** Contract multiplier */ + get contractMultiplier(): Decimal + /** Option type */ + get contractType(): OptionType + /** Contract size */ + get contractSize(): Decimal + /** Option direction */ + get direction(): OptionDirection + /** Underlying security historical volatility of the option */ + get historicalVolatility(): Decimal + /** Underlying security symbol of the option */ + get underlyingSymbol(): string +} +/** Quote of warrant */ +export class WarrantQuote { + /** Security code */ + get symbol(): string + /** Latest price */ + get lastDone(): Decimal + /** Yesterday's close */ + get prevClose(): Decimal + /** Open */ + get open(): Decimal + /** High */ + get high(): Decimal + /** Low */ + get low(): Decimal + /** Time of latest price */ + get timestamp(): Date + /** Volume */ + get volume(): number + /** Turnover */ + get turnover(): Decimal + /** Security trading status */ + get tradeStatus(): TradeStatus + /** Implied volatility */ + get impliedVolatility(): Decimal + /** Exprity date */ + get expiryDate(): NaiveDate + /** Last tradalbe date */ + get lastTradeDate(): NaiveDate + /** Outstanding ratio */ + get outstandingRatio(): Decimal + /** Outstanding quantity */ + get outstandingQty(): number + /** Conversion ratio */ + get conversionRatio(): Decimal + /** Warrant type */ + get category(): WarrantType + /** Strike price */ + get strikePrice(): Decimal + /** Upper bound price */ + get upperStrikePrice(): Decimal + /** Lower bound price */ + get lowerStrikePrice(): Decimal + /** Call price */ + get callPrice(): Decimal + /** Underlying security symbol of the warrant */ + get underlyingSymbol(): string +} +/** Depth */ +export class Depth { + /** Position */ + get position(): number + /** Price */ + get price(): Decimal + /** Volume */ + get volume(): number + /** Number of orders */ + get orderNum(): number +} +/** Security depth */ +export class SecurityDepth { + /** Ask depth */ + get asks(): Array + /** Bid depth */ + get bids(): Array +} +/** Brokers */ +export class Brokers { + /** Position */ + get position(): number + /** Broker IDs */ + get brokerIds(): Array +} +/** Security brokers */ +export class SecurityBrokers { + /** Ask brokers */ + get askBrokers(): Array + /** Bid brokers */ + get bidBrokers(): Array +} +/** Participant info */ +export class ParticipantInfo { + /** Broker IDs */ + get brokerIds(): Array + /** Participant name (zh-CN) */ + get nameCn(): string + /** Participant name (en) */ + get nameEn(): string + /** Participant name (zh-HK) */ + get nameHk(): string +} +/** Trade */ +export class Trade { + /** Price */ + get price(): Decimal + /** Volume */ + get volume(): number + /** Time of trading */ + get timestamp(): Date + /** Trade type */ + get tradeType(): string + /** Trade direction */ + get direction(): TradeDirection + /** Trade session */ + get tradeSession(): TradeSession +} +/** Intraday line */ +export class IntradayLine { + /** Close price of the minute */ + get price(): Decimal + /** Start time of the minute */ + get timestamp(): Date + /** Volume */ + get volume(): number + /** Turnover */ + get turnover(): Decimal + /** Average price */ + get avgPrice(): Decimal +} +/** Candlestick */ +export class Candlestick { + /** Close price */ + get close(): Decimal + /** Open price */ + get open(): Decimal + /** Low price */ + get low(): Decimal + /** High price */ + get high(): Decimal + /** Volume */ + get volume(): number + /** Turnover */ + get turnover(): Decimal + /** Timestamp */ + get timestamp(): Date +} +/** Strike price info */ +export class StrikePriceInfo { + /** Strike price */ + get price(): Decimal + /** Security code of call option */ + get callSymbol(): string + /** Security code of put option */ + get putSymbol(): string + /** Is standard */ + get standard(): boolean +} +/** Issuer info */ +export class IssuerInfo { + /** Issuer ID */ + get issuerId(): number + /** Issuer name (zh-CN) */ + get nameCn(): string + /** Issuer name (en) */ + get nameEn(): string + /** Issuer name (zh-HK) */ + get nameHk(): string +} +/** The information of trading session */ +export class TradingSessionInfo { + /** Being trading time */ + get beginTime(): Time + /** End trading time */ + get endTime(): Time + /** Trading session */ + get tradeSession(): TradeSession +} +/** Market trading session */ +export class MarketTradingSession { + /** Market */ + get market(): Market + /** Trading session */ + get tradeSession(): Array +} +/** Real-time quote */ +export class RealtimeQuote { + /** Security code */ + get symbol(): string + /** Latest price */ + get lastDone(): Decimal + /** Open */ + get open(): Decimal + /** High */ + get high(): Decimal + /** Low */ + get low(): Decimal + /** Time of latest price */ + get timestamp(): Date + /** Volume */ + get volume(): number + /** Turnover */ + get turnover(): Decimal + /** Security trading status */ + get tradeStatus(): TradeStatus +} +/** Push real-time quote */ +export class PushQuote { + /** Latest price */ + get lastDone(): Decimal + /** Open */ + get open(): Decimal + /** High */ + get high(): Decimal + /** Low */ + get low(): Decimal + /** Time of latest price */ + get timestamp(): Date + /** Volume */ + get volume(): number + /** Turnover */ + get turnover(): Decimal + /** Security trading status */ + get tradeStatus(): TradeStatus + /** Trade session, */ + get tradeSession(): TradeSession +} +/** Push real-time depth */ +export class PushDepth { + /** Ask depth */ + get asks(): Array + /** Bid depth */ + get bids(): Array +} +/** Push real-time brokers */ +export class PushBrokers { + /** Ask brokers */ + get askBrokers(): Array + /** Bid brokers */ + get bidBrokers(): Array +} +/** Push real-time trades */ +export class PushTrades { + /** Trades data */ + get trades(): Array +} +/** Market trading days */ +export class MarketTradingDays { + /** Trading days */ + get tradingDays(): Array + /** Half trading days */ + get halfTradingDays(): Array +} +/** Naive date type */ +export class NaiveDate { + constructor(year: number, month: number, day: number) + get year(): number + get month(): number + get day(): number +} +/** Naive date type */ +export class Time { + constructor(hour: number, minute: number, second: number) + get hour(): number + get monute(): number + get second(): number +} +/** Trade context */ +export class TradeContext { + static new(config: Config): Promise + set onOrderChanged(callback: (err: null | Error, event: PushOrderChanged) => void) + /** Subscribe */ + subscribe(topics: Array): Promise + /** Unsubscribe */ + unsubscribe(topics: Array): Promise + /** Get history executions */ + historyExecutions(opts?: GetHistoryExecutionsOptions | undefined | null): Promise> + /** Get today executions */ + todayExecutions(opts?: GetTodayExecutionsOptions | undefined | null): Promise> + /** Get history orders */ + historyOrders(opts?: GetHistoryOrdersOptions | undefined | null): Promise> + /** Get today orders */ + todayOrders(opts?: GetTodayOrdersOptions | undefined | null): Promise> + /** Replace order */ + replaceOrder(opts: ReplaceOrderOptions): Promise + /** Submit order */ + submitOrder(opts: SubmitOrderOptions): Promise + /** Withdraw order */ + withdrawOrder(orderId: string): Promise + /** Get account balance */ + accountBalance(): Promise> + /** Get cash flow */ + cashFlow(opts: GetCashFlowOptions): Promise> + /** Get fund positions */ + fundPositions(symbols?: Array | undefined | null): Promise + /** Get stock positions */ + stockPositions(symbols?: Array | undefined | null): Promise +} +/** Options for submit order request */ +export class GetCashFlowOptions { + /** Create a new `GetCashFlowOptions` */ + constructor(startAt: Date, endAt: Date) + /** Set the business type */ + businessType(businessType: BalanceType): GetCashFlowOptions + /** Set the security symbol */ + symbol(symbol: string): GetCashFlowOptions + /** Set the page number */ + page(page: number): GetCashFlowOptions + /** Set the page size */ + size(size: number): GetCashFlowOptions +} +/** Options for get histroy executions request */ +export class GetHistoryExecutionsOptions { + /** Create a new `GetHistoryExecutionsOptions` */ + constructor() + /** Set the security symbol */ + symbol(symbol: string): GetHistoryExecutionsOptions + /** Set the start time */ + startAt(startAt: Date): GetHistoryExecutionsOptions + /** Set the end time */ + endAt(endAt: Date): GetHistoryExecutionsOptions +} +/** Options for get histroy orders request */ +export class GetHistoryOrdersOptions { + /** Create a new `GetHistoryOrdersOptions` */ + constructor() + /** Set the security symbol */ + symbol(symbol: string): GetHistoryOrdersOptions + /** Set the order status */ + status(status: OrderStatus | Array): GetHistoryOrdersOptions + /** Set the order side */ + side(side: OrderSide): GetHistoryOrdersOptions + /** Set the market */ + market(market: Market): GetHistoryOrdersOptions + /** Set the start time */ + startAt(startAt: Date): GetHistoryOrdersOptions + /** Set the end time */ + endAt(endAt: Date): GetHistoryOrdersOptions +} +/** Options for get histroy executions request */ +export class GetTodayExecutionsOptions { + /** Create a new `GetTodayExecutionsOptions` */ + constructor() + /** Set the security symbol */ + symbol(symbol: string): GetTodayExecutionsOptions + /** Set the order id */ + orderId(orderId: string): GetTodayExecutionsOptions +} +/** Options for get today orders request */ +export class GetTodayOrdersOptions { + /** Create a new `GetTodayOrdersOptions` */ + constructor() + /** Set the security symbol */ + symbol(symbol: string): GetTodayOrdersOptions + /** Set the order status */ + status(status: OrderStatus | Array): GetTodayOrdersOptions + /** Set the order side */ + side(side: OrderSide): GetTodayOrdersOptions + /** Set the market */ + market(market: Market): GetTodayOrdersOptions +} +/** Options for get today orders request */ +export class ReplaceOrderOptions { + /** Create a new `ReplaceOrderOptions` */ + constructor(orderId: string, quantity: Decimal) + /** Set the price */ + price(price: Decimal): ReplaceOrderOptions + /** Set the trigger price */ + triggerPrice(triggerPrice: Decimal): ReplaceOrderOptions + /** Set the limit offset */ + limitOffset(limitOffset: Decimal): ReplaceOrderOptions + /** Set the trailing amount */ + trailingAmount(trailingAmount: Decimal): ReplaceOrderOptions + /** Set the trailing percent */ + trailingPercent(trailingPercent: Decimal): ReplaceOrderOptions + /** Set the remark */ + remark(remark: string): ReplaceOrderOptions +} +/** Options for submit order request */ +export class SubmitOrderOptions { + /** Create a new `SubmitOrderOptions` */ + constructor(symbol: string, orderType: OrderType, side: OrderSide, submittedQuantity: Decimal, timeInForce: TimeInForceType) + /** Set the submitted price */ + submittedPrice(submittedPrice: Decimal): SubmitOrderOptions + /** Set the trigger price */ + triggerPrice(triggerPrice: Decimal): SubmitOrderOptions + /** Set the limit offset */ + limitOffset(limitOffset: Decimal): SubmitOrderOptions + /** Set the trailing amount */ + trailingAmount(trailingAmount: Decimal): SubmitOrderOptions + /** Set the trailing percent */ + trailingPercent(trailingPercent: Decimal): SubmitOrderOptions + /** Set the expire date */ + expireDate(expireDate: NaiveDate): SubmitOrderOptions + /** Enable or disable outside regular trading hours */ + outsideRth(outsideRth: OutsideRTH): SubmitOrderOptions + /** Set the remark */ + remark(remark: string): SubmitOrderOptions +} +/** Trade */ +export class Execution { + /** Order ID */ + get orderId(): string + /** Execution ID */ + get tradeId(): string + /** Security code */ + get symbol(): string + /** Trade done time */ + get tradeDoneAt(): Date + /** Executed quantity */ + get quantity(): Decimal + /** Executed price */ + get price(): Decimal +} +/** Order */ +export class Order { + /** Order ID */ + get orderId(): string + /** Order status */ + get status(): OrderStatus + /** Stock name */ + get stockName(): string + /** Submitted quantity */ + get quantity(): Decimal + /** Executed quantity */ + get executedQuantity(): Decimal | null + /** Submitted price */ + get price(): Decimal | null + /** Executed price */ + get executedPrice(): Decimal | null + /** Submitted time */ + get submittedAt(): Date + /** Order side */ + get side(): OrderSide + /** Security code */ + get symbol(): string + /** Order type */ + get orderType(): OrderType + /** Last done */ + get lastDone(): Decimal | null + /** `LIT` / `MIT` Order Trigger Price */ + get triggerPrice(): Decimal | null + /** Rejected Message or remark */ + get msg(): string + /** Order tag */ + get tag(): OrderTag + /** Time in force type */ + get timeInForce(): TimeInForceType + /** Long term order expire date */ + get expireDate(): NaiveDate | null + /** Last updated time */ + get updatedAt(): Date | null + /** Conditional order trigger time */ + get triggerAt(): Date | null + /** `TSMAMT` / `TSLPAMT` order trailing amount */ + get trailingAmount(): Decimal | null + /** `TSMPCT` / `TSLPPCT` order trailing percent */ + get trailingPercent(): Decimal | null + /** `TSLPAMT` / `TSLPPCT` order limit offset amount */ + get limitOffset(): Decimal | null + /** Conditional order trigger status */ + get triggerStatus(): TriggerStatus | null + /** Currency */ + get currency(): string + /** Enable or disable outside regular trading hours */ + get outsideRth(): OutsideRTH | null +} +/** Order changed message */ +export class PushOrderChanged { + /** Order side */ + get side(): OrderSide + /** Stock name */ + get stockName(): string + /** Submitted quantity */ + get quantity(): string + /** Order symbol */ + get symbol(): string + /** Order type */ + get orderType(): OrderType + /** Submitted price */ + get price(): Decimal + /** Executed quantity */ + get executedQuantity(): number + /** Executed price */ + get executedPrice(): Decimal + /** Order ID */ + get orderId(): string + /** Currency */ + get currency(): string + /** Order status */ + get status(): OrderStatus + /** Submitted time */ + get submittedAt(): Date + /** Last updated time */ + get updatedAt(): Date + /** Order trigger price */ + get triggerPrice(): Decimal | null + /** Rejected message or remark */ + get msg(): string + /** Order tag */ + get tag(): OrderTag + /** Conditional order trigger status */ + get triggerStatus(): TriggerStatus | null + /** Conditional order trigger time */ + get triggerAt(): Date | null + /** Trailing amount */ + get trailingAmount(): Decimal | null + /** Trailing percent */ + get trailingPercent(): Decimal | null + /** Limit offset amount */ + get limitOffset(): Decimal | null + /** Account no */ + get accountNo(): string +} +/** Response for withdraw order request */ +export class SubmitOrderResponse { + /** Order id */ + get orderId(): string +} +/** Account balance */ +export class CashInfo { + /** Withdraw cash */ + get withdrawCash(): Decimal + /** Available cash */ + get availableCash(): Decimal + /** Frozen cash */ + get frozenCash(): Decimal + /** Cash to be settled */ + get settlingCash(): Decimal + /** Currency */ + get currency(): string +} +/** Account balance */ +export class AccountBalance { + /** Total cash */ + get totalCash(): Decimal + /** Maximum financing amount */ + get maxFinanceAmount(): Decimal + /** Remaining financing amount */ + get remainingFinanceAmount(): Decimal + /** Risk control level */ + get riskLevel(): number | null + /** Margin call */ + get marginCall(): Decimal + /** Currency */ + get currency(): string + /** Cash details */ + get cashInfos(): Array +} +/** Account balance */ +export class CashFlow { + /** Cash flow name */ + get transactionFlowName(): string + /** Outflow direction */ + get direction(): CashFlowDirection + /** Balance type */ + get businessType(): BalanceType + /** Cash amount */ + get balance(): Decimal + /** Cash currency */ + get currency(): string + /** Business time */ + get businessTime(): Date + /** Associated Stock code information */ + get symbol(): string | null + /** Cash flow description */ + get description(): string +} +/** Fund positions response */ +export class FundPositionsResponse { + get channels(): Array +} +/** Fund position channel */ +export class FundPositionChannel { + /** Account type */ + get accountChannel(): string + /** Fund positions */ + get positions(): Array +} +/** Fund position */ +export class FundPosition { + /** Fund ISIN code */ + get symbol(): string + /** Current equity */ + get currentNetAssetValue(): Decimal + /** Current equity time */ + get netAssetValueDay(): Date + /** Fund name */ + get symbolName(): string + /** Currency */ + get currency(): string + /** Net cost */ + get costNetAssetValue(): Decimal + /** Holding units */ + get holdingUnits(): Decimal +} +/** Stock positions response */ +export class StockPositionsResponse { + get channels(): Array +} +/** Stock position channel */ +export class StockPositionChannel { + /** Account type */ + get accountChannel(): string + /** Fund details */ + get positions(): Array +} +/** Stock position */ +export class StockPosition { + /** Stock code */ + get symbol(): string + /** Stock name */ + get symbolName(): string + /** The number of holdings */ + get quantity(): Decimal + /** Available quantity */ + get availableQuality(): Decimal + /** Currency */ + get currency(): string + /** + * Cost Price(According to the client's choice of average purchase or + * diluted cost) + */ + get costPrice(): Decimal +} diff --git a/nodejs/index.js b/nodejs/index.js new file mode 100644 index 0000000000..b3b2115fb6 --- /dev/null +++ b/nodejs/index.js @@ -0,0 +1,316 @@ +const { existsSync, readFileSync } = require('fs') +const { join } = require('path') + +const { platform, arch } = process + +let nativeBinding = null +let localFileExisted = false +let loadError = null + +function isMusl() { + // For Node 10 + if (!process.report || typeof process.report.getReport !== 'function') { + try { + return readFileSync('/usr/bin/ldd', 'utf8').includes('musl') + } catch (e) { + return true + } + } else { + const { glibcVersionRuntime } = process.report.getReport().header + return !glibcVersionRuntime + } +} + +switch (platform) { + case 'android': + switch (arch) { + case 'arm64': + localFileExisted = existsSync(join(__dirname, 'longbridge.android-arm64.node')) + try { + if (localFileExisted) { + nativeBinding = require('./longbridge.android-arm64.node') + } else { + nativeBinding = require('longbridge-android-arm64') + } + } catch (e) { + loadError = e + } + break + case 'arm': + localFileExisted = existsSync(join(__dirname, 'longbridge.android-arm-eabi.node')) + try { + if (localFileExisted) { + nativeBinding = require('./longbridge.android-arm-eabi.node') + } else { + nativeBinding = require('longbridge-android-arm-eabi') + } + } catch (e) { + loadError = e + } + break + default: + throw new Error(`Unsupported architecture on Android ${arch}`) + } + break + case 'win32': + switch (arch) { + case 'x64': + localFileExisted = existsSync( + join(__dirname, 'longbridge.win32-x64-msvc.node') + ) + try { + if (localFileExisted) { + nativeBinding = require('./longbridge.win32-x64-msvc.node') + } else { + nativeBinding = require('longbridge-win32-x64-msvc') + } + } catch (e) { + loadError = e + } + break + case 'ia32': + localFileExisted = existsSync( + join(__dirname, 'longbridge.win32-ia32-msvc.node') + ) + try { + if (localFileExisted) { + nativeBinding = require('./longbridge.win32-ia32-msvc.node') + } else { + nativeBinding = require('longbridge-win32-ia32-msvc') + } + } catch (e) { + loadError = e + } + break + case 'arm64': + localFileExisted = existsSync( + join(__dirname, 'longbridge.win32-arm64-msvc.node') + ) + try { + if (localFileExisted) { + nativeBinding = require('./longbridge.win32-arm64-msvc.node') + } else { + nativeBinding = require('longbridge-win32-arm64-msvc') + } + } catch (e) { + loadError = e + } + break + default: + throw new Error(`Unsupported architecture on Windows: ${arch}`) + } + break + case 'darwin': + switch (arch) { + case 'x64': + localFileExisted = existsSync(join(__dirname, 'longbridge.darwin-x64.node')) + try { + if (localFileExisted) { + nativeBinding = require('./longbridge.darwin-x64.node') + } else { + nativeBinding = require('longbridge-darwin-x64') + } + } catch (e) { + loadError = e + } + break + case 'arm64': + localFileExisted = existsSync( + join(__dirname, 'longbridge.darwin-arm64.node') + ) + try { + if (localFileExisted) { + nativeBinding = require('./longbridge.darwin-arm64.node') + } else { + nativeBinding = require('longbridge-darwin-arm64') + } + } catch (e) { + loadError = e + } + break + default: + throw new Error(`Unsupported architecture on macOS: ${arch}`) + } + break + case 'freebsd': + if (arch !== 'x64') { + throw new Error(`Unsupported architecture on FreeBSD: ${arch}`) + } + localFileExisted = existsSync(join(__dirname, 'longbridge.freebsd-x64.node')) + try { + if (localFileExisted) { + nativeBinding = require('./longbridge.freebsd-x64.node') + } else { + nativeBinding = require('longbridge-freebsd-x64') + } + } catch (e) { + loadError = e + } + break + case 'linux': + switch (arch) { + case 'x64': + if (isMusl()) { + localFileExisted = existsSync( + join(__dirname, 'longbridge.linux-x64-musl.node') + ) + try { + if (localFileExisted) { + nativeBinding = require('./longbridge.linux-x64-musl.node') + } else { + nativeBinding = require('longbridge-linux-x64-musl') + } + } catch (e) { + loadError = e + } + } else { + localFileExisted = existsSync( + join(__dirname, 'longbridge.linux-x64-gnu.node') + ) + try { + if (localFileExisted) { + nativeBinding = require('./longbridge.linux-x64-gnu.node') + } else { + nativeBinding = require('longbridge-linux-x64-gnu') + } + } catch (e) { + loadError = e + } + } + break + case 'arm64': + if (isMusl()) { + localFileExisted = existsSync( + join(__dirname, 'longbridge.linux-arm64-musl.node') + ) + try { + if (localFileExisted) { + nativeBinding = require('./longbridge.linux-arm64-musl.node') + } else { + nativeBinding = require('longbridge-linux-arm64-musl') + } + } catch (e) { + loadError = e + } + } else { + localFileExisted = existsSync( + join(__dirname, 'longbridge.linux-arm64-gnu.node') + ) + try { + if (localFileExisted) { + nativeBinding = require('./longbridge.linux-arm64-gnu.node') + } else { + nativeBinding = require('longbridge-linux-arm64-gnu') + } + } catch (e) { + loadError = e + } + } + break + case 'arm': + localFileExisted = existsSync( + join(__dirname, 'longbridge.linux-arm-gnueabihf.node') + ) + try { + if (localFileExisted) { + nativeBinding = require('./longbridge.linux-arm-gnueabihf.node') + } else { + nativeBinding = require('longbridge-linux-arm-gnueabihf') + } + } catch (e) { + loadError = e + } + break + default: + throw new Error(`Unsupported architecture on Linux: ${arch}`) + } + break + default: + throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`) +} + +if (!nativeBinding) { + if (loadError) { + throw loadError + } + throw new Error(`Failed to load native binding`) +} + +const { Config, Decimal, QuoteContext, PushQuoteEvent, PushDepthEvent, PushBrokersEvent, PushTradesEvent, DerivativeType, TradeStatus, TradeSession, SubType, TradeDirection, OptionType, OptionDirection, WarrantType, Period, AdjustType, SecurityStaticInfo, PrePostQuote, SecurityQuote, OptionQuote, WarrantQuote, Depth, SecurityDepth, Brokers, SecurityBrokers, ParticipantInfo, Trade, IntradayLine, Candlestick, StrikePriceInfo, IssuerInfo, TradingSessionInfo, MarketTradingSession, RealtimeQuote, PushQuote, PushDepth, PushBrokers, PushTrades, MarketTradingDays, NaiveDate, Time, sleep, TradeContext, GetCashFlowOptions, GetHistoryExecutionsOptions, GetHistoryOrdersOptions, GetTodayExecutionsOptions, GetTodayOrdersOptions, ReplaceOrderOptions, SubmitOrderOptions, TopicType, Execution, OrderStatus, OrderSide, OrderType, OrderTag, TimeInForceType, TriggerStatus, OutsideRTH, Order, PushOrderChanged, SubmitOrderResponse, CashInfo, AccountBalance, BalanceType, CashFlowDirection, CashFlow, FundPositionsResponse, FundPositionChannel, FundPosition, StockPositionsResponse, StockPositionChannel, StockPosition, Market } = nativeBinding + +module.exports.Config = Config +module.exports.Decimal = Decimal +module.exports.QuoteContext = QuoteContext +module.exports.PushQuoteEvent = PushQuoteEvent +module.exports.PushDepthEvent = PushDepthEvent +module.exports.PushBrokersEvent = PushBrokersEvent +module.exports.PushTradesEvent = PushTradesEvent +module.exports.DerivativeType = DerivativeType +module.exports.TradeStatus = TradeStatus +module.exports.TradeSession = TradeSession +module.exports.SubType = SubType +module.exports.TradeDirection = TradeDirection +module.exports.OptionType = OptionType +module.exports.OptionDirection = OptionDirection +module.exports.WarrantType = WarrantType +module.exports.Period = Period +module.exports.AdjustType = AdjustType +module.exports.SecurityStaticInfo = SecurityStaticInfo +module.exports.PrePostQuote = PrePostQuote +module.exports.SecurityQuote = SecurityQuote +module.exports.OptionQuote = OptionQuote +module.exports.WarrantQuote = WarrantQuote +module.exports.Depth = Depth +module.exports.SecurityDepth = SecurityDepth +module.exports.Brokers = Brokers +module.exports.SecurityBrokers = SecurityBrokers +module.exports.ParticipantInfo = ParticipantInfo +module.exports.Trade = Trade +module.exports.IntradayLine = IntradayLine +module.exports.Candlestick = Candlestick +module.exports.StrikePriceInfo = StrikePriceInfo +module.exports.IssuerInfo = IssuerInfo +module.exports.TradingSessionInfo = TradingSessionInfo +module.exports.MarketTradingSession = MarketTradingSession +module.exports.RealtimeQuote = RealtimeQuote +module.exports.PushQuote = PushQuote +module.exports.PushDepth = PushDepth +module.exports.PushBrokers = PushBrokers +module.exports.PushTrades = PushTrades +module.exports.MarketTradingDays = MarketTradingDays +module.exports.NaiveDate = NaiveDate +module.exports.Time = Time +module.exports.sleep = sleep +module.exports.TradeContext = TradeContext +module.exports.GetCashFlowOptions = GetCashFlowOptions +module.exports.GetHistoryExecutionsOptions = GetHistoryExecutionsOptions +module.exports.GetHistoryOrdersOptions = GetHistoryOrdersOptions +module.exports.GetTodayExecutionsOptions = GetTodayExecutionsOptions +module.exports.GetTodayOrdersOptions = GetTodayOrdersOptions +module.exports.ReplaceOrderOptions = ReplaceOrderOptions +module.exports.SubmitOrderOptions = SubmitOrderOptions +module.exports.TopicType = TopicType +module.exports.Execution = Execution +module.exports.OrderStatus = OrderStatus +module.exports.OrderSide = OrderSide +module.exports.OrderType = OrderType +module.exports.OrderTag = OrderTag +module.exports.TimeInForceType = TimeInForceType +module.exports.TriggerStatus = TriggerStatus +module.exports.OutsideRTH = OutsideRTH +module.exports.Order = Order +module.exports.PushOrderChanged = PushOrderChanged +module.exports.SubmitOrderResponse = SubmitOrderResponse +module.exports.CashInfo = CashInfo +module.exports.AccountBalance = AccountBalance +module.exports.BalanceType = BalanceType +module.exports.CashFlowDirection = CashFlowDirection +module.exports.CashFlow = CashFlow +module.exports.FundPositionsResponse = FundPositionsResponse +module.exports.FundPositionChannel = FundPositionChannel +module.exports.FundPosition = FundPosition +module.exports.StockPositionsResponse = StockPositionsResponse +module.exports.StockPositionChannel = StockPositionChannel +module.exports.StockPosition = StockPosition +module.exports.QuoteContext = QuoteContext +module.exports.Market = Market diff --git a/nodejs/npm/darwin-x64/README.md b/nodejs/npm/darwin-x64/README.md new file mode 100644 index 0000000000..0c95bee2e7 --- /dev/null +++ b/nodejs/npm/darwin-x64/README.md @@ -0,0 +1,3 @@ +# `longbridge-darwin-x64` + +This is the **x86_64-apple-darwin** binary for `longbridge` diff --git a/nodejs/npm/darwin-x64/package.json b/nodejs/npm/darwin-x64/package.json new file mode 100644 index 0000000000..909c8fd124 --- /dev/null +++ b/nodejs/npm/darwin-x64/package.json @@ -0,0 +1,18 @@ +{ + "name": "longbridge-darwin-x64", + "version": "0.0.0", + "os": [ + "darwin" + ], + "cpu": [ + "x64" + ], + "main": "longbridge.darwin-x64.node", + "files": [ + "longbridge.darwin-x64.node" + ], + "license": "MIT", + "engines": { + "node": ">= 10" + } +} \ No newline at end of file diff --git a/nodejs/npm/linux-x64-gnu/README.md b/nodejs/npm/linux-x64-gnu/README.md new file mode 100644 index 0000000000..c299cd500e --- /dev/null +++ b/nodejs/npm/linux-x64-gnu/README.md @@ -0,0 +1,3 @@ +# `longbridge-linux-x64-gnu` + +This is the **x86_64-unknown-linux-gnu** binary for `longbridge` diff --git a/nodejs/npm/linux-x64-gnu/package.json b/nodejs/npm/linux-x64-gnu/package.json new file mode 100644 index 0000000000..cbebc210ff --- /dev/null +++ b/nodejs/npm/linux-x64-gnu/package.json @@ -0,0 +1,21 @@ +{ + "name": "longbridge-linux-x64-gnu", + "version": "0.0.0", + "os": [ + "linux" + ], + "cpu": [ + "x64" + ], + "main": "longbridge.linux-x64-gnu.node", + "files": [ + "longbridge.linux-x64-gnu.node" + ], + "license": "MIT", + "engines": { + "node": ">= 10" + }, + "libc": [ + "glibc" + ] +} \ No newline at end of file diff --git a/nodejs/npm/win32-x64-msvc/README.md b/nodejs/npm/win32-x64-msvc/README.md new file mode 100644 index 0000000000..5c2273b350 --- /dev/null +++ b/nodejs/npm/win32-x64-msvc/README.md @@ -0,0 +1,3 @@ +# `longbridge-win32-x64-msvc` + +This is the **x86_64-pc-windows-msvc** binary for `longbridge` diff --git a/nodejs/npm/win32-x64-msvc/package.json b/nodejs/npm/win32-x64-msvc/package.json new file mode 100644 index 0000000000..f32de61a5d --- /dev/null +++ b/nodejs/npm/win32-x64-msvc/package.json @@ -0,0 +1,18 @@ +{ + "name": "longbridge-win32-x64-msvc", + "version": "0.0.0", + "os": [ + "win32" + ], + "cpu": [ + "x64" + ], + "main": "longbridge.win32-x64-msvc.node", + "files": [ + "longbridge.win32-x64-msvc.node" + ], + "license": "MIT", + "engines": { + "node": ">= 10" + } +} \ No newline at end of file diff --git a/nodejs/package.json b/nodejs/package.json new file mode 100644 index 0000000000..f5fa637075 --- /dev/null +++ b/nodejs/package.json @@ -0,0 +1,24 @@ +{ + "name": "longbridge", + "version": "0.2.8", + "main": "index.js", + "types": "index.d.ts", + "napi": { + "name": "longbridge", + "triples": {} + }, + "license": "MIT OR Apache-2.0", + "devDependencies": { + "@napi-rs/cli": "^2.9.0" + }, + "engines": { + "node": ">= 10" + }, + "scripts": { + "artifacts": "napi artifacts", + "build": "napi build --platform --release", + "build:debug": "napi build --platform", + "prepublishOnly": "napi prepublish -t npm", + "version": "napi version" + } +} \ No newline at end of file diff --git a/nodejs/src/config.rs b/nodejs/src/config.rs new file mode 100644 index 0000000000..ba0e4668bf --- /dev/null +++ b/nodejs/src/config.rs @@ -0,0 +1,54 @@ +use napi::Result; + +use crate::error::ErrorNewType; + +#[napi_derive::napi(object)] +pub struct ConfigParams { + /// App Key + pub app_key: String, + /// App Secret + pub app_secret: String, + /// Access Token + pub access_token: String, + /// HTTP API url (default: "https://openapi.longbridgeapp.com") + pub http_url: Option, + /// Websocket url for quote API (default: + /// "wss://openapi-quote.longbridgeapp.com") + pub quote_ws_url: Option, + /// Websocket url for trade API (default: + /// "wss://openapi-trade.longbridgeapp.com") + pub trade_ws_url: Option, +} + +#[napi_derive::napi] +pub struct Config(pub(crate) longbridge::Config); + +#[napi_derive::napi] +impl Config { + /// Create a new `Config` + #[napi(constructor)] + pub fn new(params: ConfigParams) -> Self { + let mut config = + longbridge::Config::new(params.app_key, params.app_secret, params.access_token); + + if let Some(http_url) = params.http_url { + config = config.http_url(http_url); + } + + if let Some(quote_ws_url) = params.quote_ws_url { + config = config.quote_ws_url(quote_ws_url); + } + + if let Some(trade_ws_url) = params.trade_ws_url { + config = config.trade_ws_url(trade_ws_url); + } + + Self(config) + } + + /// Create a new `Config` from the given environment variables + #[napi(factory)] + pub fn from_env() -> Result { + Ok(Self(longbridge::Config::from_env().map_err(ErrorNewType)?)) + } +} diff --git a/nodejs/src/decimal.rs b/nodejs/src/decimal.rs new file mode 100644 index 0000000000..b746ac0265 --- /dev/null +++ b/nodejs/src/decimal.rs @@ -0,0 +1,31 @@ +use std::fmt::{self, Debug, Formatter}; + +use napi::{Error, Result}; + +#[napi_derive::napi] +#[derive(Copy, Clone)] +pub struct Decimal(pub(crate) rust_decimal::Decimal); + +impl From for Decimal { + #[inline] + fn from(value: rust_decimal::Decimal) -> Self { + Self(value) + } +} + +impl Debug for Decimal { + #[inline] + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +#[napi_derive::napi] +impl Decimal { + #[napi(factory)] + pub fn new(value: String) -> Result { + Ok(Self(value.parse().map_err(|err| { + Error::from_reason(format!("invalid decimal: {}", err)) + })?)) + } +} diff --git a/nodejs/src/error.rs b/nodejs/src/error.rs new file mode 100644 index 0000000000..a54dabd050 --- /dev/null +++ b/nodejs/src/error.rs @@ -0,0 +1,8 @@ +pub(crate) struct ErrorNewType(pub(crate) longbridge::Error); + +impl std::convert::From for napi::Error { + #[inline] + fn from(err: ErrorNewType) -> napi::Error { + napi::Error::from_reason(err.0.to_string()) + } +} diff --git a/nodejs/src/lib.rs b/nodejs/src/lib.rs new file mode 100644 index 0000000000..67de47704f --- /dev/null +++ b/nodejs/src/lib.rs @@ -0,0 +1,10 @@ +#![allow(dead_code)] + +mod config; +mod decimal; +mod error; +mod quote; +mod time; +mod trade; +mod types; +mod utils; diff --git a/nodejs/src/quote/context.rs b/nodejs/src/quote/context.rs new file mode 100644 index 0000000000..cf2dbc4a7a --- /dev/null +++ b/nodejs/src/quote/context.rs @@ -0,0 +1,418 @@ +use std::sync::Arc; + +use longbridge::quote::PushEventDetail; +use napi::{threadsafe_function::ThreadsafeFunctionCallMode, JsFunction, Result}; + +use crate::{ + config::Config, + error::ErrorNewType, + quote::{ + push::{PushBrokersEvent, PushDepthEvent, PushQuoteEvent, PushTradesEvent}, + types::{ + AdjustType, Candlestick, IntradayLine, IssuerInfo, MarketTradingDays, + MarketTradingSession, OptionQuote, ParticipantInfo, Period, RealtimeQuote, + SecurityBrokers, SecurityDepth, SecurityQuote, SecurityStaticInfo, StrikePriceInfo, + SubType, SubTypes, Trade, WarrantQuote, + }, + }, + time::NaiveDate, + types::Market, + utils::JsCallback, +}; + +/// Quote context +#[napi_derive::napi] +#[derive(Clone)] +pub struct QuoteContext { + ctx: longbridge::quote::QuoteContext, + on_quote: JsCallback, + on_depth: JsCallback, + on_brokers: JsCallback, + on_trades: JsCallback, +} + +#[napi_derive::napi] +impl QuoteContext { + #[napi] + pub async fn new(config: &Config) -> Result { + let (ctx, mut receiver) = + longbridge::quote::QuoteContext::try_new(Arc::new(config.0.clone())) + .await + .map_err(ErrorNewType)?; + let js_ctx = Self { + ctx, + on_quote: Default::default(), + on_depth: Default::default(), + on_brokers: Default::default(), + on_trades: Default::default(), + }; + + tokio::spawn({ + let js_ctx = js_ctx.clone(); + async move { + while let Some(msg) = receiver.recv().await { + match msg.detail { + PushEventDetail::Quote(quote) => { + if let Some(handler) = js_ctx.on_quote.lock().clone() { + if let Ok(quote) = quote.try_into() { + handler.call( + Ok(PushQuoteEvent { + symbol: msg.symbol, + data: quote, + }), + ThreadsafeFunctionCallMode::Blocking, + ); + } + } + } + PushEventDetail::Depth(depth) => { + if let Some(handler) = js_ctx.on_depth.lock().clone() { + if let Ok(depth) = depth.try_into() { + handler.call( + Ok(PushDepthEvent { + symbol: msg.symbol, + data: depth, + }), + ThreadsafeFunctionCallMode::Blocking, + ); + } + } + } + PushEventDetail::Brokers(brokers) => { + if let Some(handler) = js_ctx.on_brokers.lock().clone() { + if let Ok(brokers) = brokers.try_into() { + handler.call( + Ok(PushBrokersEvent { + symbol: msg.symbol, + data: brokers, + }), + ThreadsafeFunctionCallMode::Blocking, + ); + } + } + } + PushEventDetail::Trade(trades) => { + if let Some(handler) = js_ctx.on_trades.lock().clone() { + if let Ok(trades) = trades.try_into() { + handler.call( + Ok(PushTradesEvent { + symbol: msg.symbol, + data: trades, + }), + ThreadsafeFunctionCallMode::Blocking, + ); + } + } + } + } + } + } + }); + + Ok(js_ctx) + } + + #[napi( + setter, + ts_args_type = "callback: (err: null | Error, event: PushQuoteEvent) => void" + )] + pub fn on_quote(&mut self, handler: JsFunction) -> Result<()> { + *self.on_quote.lock() = + Some(handler.create_threadsafe_function(32, |ctx| Ok(vec![ctx.value]))?); + Ok(()) + } + + #[napi( + setter, + ts_args_type = "callback: (err: null | Error, event: PushDepthEvent) => void" + )] + pub fn on_depth(&mut self, handler: JsFunction) -> Result<()> { + *self.on_depth.lock() = + Some(handler.create_threadsafe_function(32, |ctx| Ok(vec![ctx.value]))?); + Ok(()) + } + + #[napi( + setter, + ts_args_type = "callback: (err: null | Error, event: PushBrokersEvent) => void" + )] + pub fn on_brokers(&mut self, handler: JsFunction) -> Result<()> { + *self.on_brokers.lock() = + Some(handler.create_threadsafe_function(32, |ctx| Ok(vec![ctx.value]))?); + Ok(()) + } + + #[napi( + setter, + ts_args_type = "callback: (err: null | Error, event: PushTradesEvent) => void" + )] + pub fn on_trades(&mut self, handler: JsFunction) -> Result<()> { + *self.on_trades.lock() = + Some(handler.create_threadsafe_function(32, |ctx| Ok(vec![ctx.value]))?); + Ok(()) + } + + /// Subscribe + #[napi] + pub async fn subscribe( + &self, + symbols: Vec, + sub_types: Vec, + is_first_push: bool, + ) -> Result<()> { + self.ctx + .subscribe(symbols, SubTypes(sub_types), is_first_push) + .await + .map_err(ErrorNewType)?; + Ok(()) + } + + /// Unsubscribe + #[napi] + pub async fn unsubscribe(&self, symbols: Vec, sub_types: Vec) -> Result<()> { + self.ctx + .unsubscribe(symbols, SubTypes(sub_types)) + .await + .map_err(ErrorNewType)?; + Ok(()) + } + + /// Get basic information of securities + #[napi] + pub async fn static_info(&self, symbols: Vec) -> Result> { + self.ctx + .static_info(symbols) + .await + .map_err(ErrorNewType)? + .into_iter() + .map(TryInto::try_into) + .collect() + } + + /// Get quote of securities + #[napi] + pub async fn quote(&self, symbols: Vec) -> Result> { + self.ctx + .quote(symbols) + .await + .map_err(ErrorNewType)? + .into_iter() + .map(TryInto::try_into) + .collect() + } + + /// Get quote of option securities + #[napi] + pub async fn option_quote(&self, symbols: Vec) -> Result> { + self.ctx + .option_quote(symbols) + .await + .map_err(ErrorNewType)? + .into_iter() + .map(TryInto::try_into) + .collect() + } + + /// Get quote of warrant securities + #[napi] + pub async fn warrant_quote(&self, symbols: Vec) -> Result> { + self.ctx + .warrant_quote(symbols) + .await + .map_err(ErrorNewType)? + .into_iter() + .map(TryInto::try_into) + .collect() + } + + /// Get security depth + #[napi] + pub async fn depth(&self, symbol: String) -> Result { + self.ctx + .depth(symbol) + .await + .map_err(ErrorNewType)? + .try_into() + } + + /// Get security brokers + #[napi] + pub async fn brokers(&self, symbol: String) -> Result { + self.ctx + .brokers(symbol) + .await + .map_err(ErrorNewType)? + .try_into() + } + + /// Get participants + #[napi] + pub async fn participants(&self) -> Result> { + self.ctx + .participants() + .await + .map_err(ErrorNewType)? + .into_iter() + .map(TryInto::try_into) + .collect() + } + + /// Get security trades + #[napi] + pub async fn trades(&self, symbol: String, count: i32) -> Result> { + self.ctx + .trades(symbol, count.max(0) as usize) + .await + .map_err(ErrorNewType)? + .into_iter() + .map(TryInto::try_into) + .collect() + } + + /// Get security intraday + #[napi] + pub async fn intraday(&self, symbol: String) -> Result> { + self.ctx + .intraday(symbol) + .await + .map_err(ErrorNewType)? + .into_iter() + .map(TryInto::try_into) + .collect() + } + + /// Get security candlesticks + #[napi] + pub async fn candlesticks( + &self, + symbol: String, + period: Period, + count: i32, + adjust_type: AdjustType, + ) -> Result> { + self.ctx + .candlesticks( + symbol, + period.into(), + count.max(0) as usize, + adjust_type.into(), + ) + .await + .map_err(ErrorNewType)? + .into_iter() + .map(TryInto::try_into) + .collect() + } + + /// Get option chain expiry date list + #[napi] + pub async fn option_chain_expiry_date_list(&self, symbol: String) -> Result> { + Ok(self + .ctx + .option_chain_expiry_date_list(symbol) + .await + .map_err(ErrorNewType)? + .into_iter() + .map(Into::into) + .collect()) + } + + /// Get option chain info by date + #[napi] + pub async fn option_chain_info_by_date( + &self, + symbol: String, + expiry_date: &NaiveDate, + ) -> Result> { + self.ctx + .option_chain_info_by_date(symbol, expiry_date.0) + .await + .map_err(ErrorNewType)? + .into_iter() + .map(TryInto::try_into) + .collect() + } + + /// Get warrant issuers + #[napi] + pub async fn warrant_issuers(&self) -> Result> { + self.ctx + .warrant_issuers() + .await + .map_err(ErrorNewType)? + .into_iter() + .map(TryInto::try_into) + .collect() + } + + /// Get trading session of the day + #[napi] + pub async fn trading_session(&self) -> Result> { + self.ctx + .trading_session() + .await + .map_err(ErrorNewType)? + .into_iter() + .map(TryInto::try_into) + .collect() + } + + /// Get trading session of the day + #[napi] + pub async fn trading_days( + &self, + market: Market, + begin: &NaiveDate, + end: &NaiveDate, + ) -> Result { + self.ctx + .trading_days(market.into(), begin.0, end.0) + .await + .map_err(ErrorNewType)? + .try_into() + } + + /// Get real-time quote + #[napi] + pub async fn realtime_quote(&self, symbols: Vec) -> Result> { + self.ctx + .realtime_quote(symbols) + .await + .map_err(ErrorNewType)? + .into_iter() + .map(TryInto::try_into) + .collect() + } + + /// Get real-time depth + #[napi] + pub async fn realtime_depth(&self, symbol: String) -> Result { + self.ctx + .realtime_depth(symbol) + .await + .map_err(ErrorNewType)? + .try_into() + } + + /// Get real-time brokers + #[napi] + pub async fn realtime_brokers(&self, symbol: String) -> Result { + self.ctx + .realtime_brokers(symbol) + .await + .map_err(ErrorNewType)? + .try_into() + } + + /// Get real-time trades + #[napi] + pub async fn realtime_trades(&self, symbol: String, count: i32) -> Result> { + self.ctx + .realtime_trades(symbol, count.max(0) as usize) + .await + .map_err(ErrorNewType)? + .into_iter() + .map(TryInto::try_into) + .collect() + } +} diff --git a/nodejs/src/quote/mod.rs b/nodejs/src/quote/mod.rs new file mode 100644 index 0000000000..d59c0c5aaa --- /dev/null +++ b/nodejs/src/quote/mod.rs @@ -0,0 +1,3 @@ +mod context; +mod push; +mod types; diff --git a/nodejs/src/quote/push.rs b/nodejs/src/quote/push.rs new file mode 100644 index 0000000000..9b5eeb5823 --- /dev/null +++ b/nodejs/src/quote/push.rs @@ -0,0 +1,30 @@ +use crate::quote::types::{PushBrokers, PushDepth, PushQuote, PushTrades}; + +macro_rules! define_push_event { + ($name:ident, $ty:ty) => { + #[napi_derive::napi] + #[derive(Debug)] + pub struct $name { + pub(crate) symbol: String, + pub(crate) data: $ty, + } + + #[napi_derive::napi] + impl $name { + #[napi(getter)] + pub fn symbol(&self) -> &str { + &self.symbol + } + + #[napi(getter)] + pub fn data(&self) -> &$ty { + &self.data + } + } + }; +} + +define_push_event!(PushQuoteEvent, PushQuote); +define_push_event!(PushDepthEvent, PushDepth); +define_push_event!(PushBrokersEvent, PushBrokers); +define_push_event!(PushTradesEvent, PushTrades); diff --git a/nodejs/src/quote/types.rs b/nodejs/src/quote/types.rs new file mode 100644 index 0000000000..fa63924aa4 --- /dev/null +++ b/nodejs/src/quote/types.rs @@ -0,0 +1,677 @@ +use chrono::{DateTime, Utc}; +use longbridge::quote::SubFlags; +use longbridge_nodejs_macros::{JsEnum, JsObject}; +use napi::bindgen_prelude::*; + +use crate::{ + decimal::Decimal, + time::{NaiveDate, Time}, + types::Market, +}; + +/// Derivative type +#[napi_derive::napi] +#[derive(JsEnum, Debug, Hash, Eq, PartialEq)] +#[js(remote = "longbridge::quote::DerivativeType")] +pub enum DerivativeType { + /// US stock options + Option, + /// HK warrants + Warrant, +} + +#[napi_derive::napi] +#[derive(JsEnum, Debug, Hash, Eq, PartialEq)] +#[js(remote = "longbridge::quote::TradeStatus")] +pub enum TradeStatus { + /// Normal + Normal, + /// Suspension + Halted, + /// Delisted + Delisted, + /// Fuse + Fuse, + /// Prepare List + PrepareList, + /// Code Moved + CodeMoved, + /// To Be Opened + ToBeOpened, + /// Split Stock Halts + SplitStockHalts, + /// Expired + Expired, + /// Warrant To BeListed + WarrantPrepareList, + /// Warrant To BeListed + #[js(remote = "SuspendTrade")] + Suspend, +} + +/// Trade session +#[napi_derive::napi] +#[derive(JsEnum, Debug, Hash, Eq, PartialEq)] +#[js(remote = "longbridge::quote::TradeSession")] +pub enum TradeSession { + /// Trading + #[js(remote = "NormalTrade")] + Normal, + /// Pre-Trading + #[js(remote = "PreTrade")] + Pre, + /// Post-Trading + #[js(remote = "PostTrade")] + Post, +} + +/// Quote type of subscription +#[napi_derive::napi] +#[derive(Debug, Hash, Eq, PartialEq)] +pub enum SubType { + /// Quote + Quote, + /// Depth + Depth, + /// Brokers + Brokers, + /// Trade + Trade, +} + +pub struct SubTypes(pub Vec); + +impl From for SubFlags { + fn from(types: SubTypes) -> Self { + types + .0 + .into_iter() + .map(|ty| match ty { + SubType::Quote => SubFlags::QUOTE, + SubType::Depth => SubFlags::DEPTH, + SubType::Brokers => SubFlags::BROKER, + SubType::Trade => SubFlags::TRADE, + }) + .fold(SubFlags::empty(), |mut acc, flag| { + acc |= flag; + acc + }) + } +} + +/// Trade direction +#[napi_derive::napi] +#[derive(JsEnum, Debug, Hash, Eq, PartialEq)] +#[js(remote = "longbridge::quote::TradeDirection")] +pub enum TradeDirection { + /// Neutral + Neutral, + /// Down + Down, + /// Up + Up, +} + +/// Option type +#[napi_derive::napi] +#[derive(JsEnum, Debug, Hash, Eq, PartialEq)] +#[js(remote = "longbridge::quote::OptionType")] +pub enum OptionType { + /// Unknown + Unknown, + /// American + American, + /// Europe + Europe, +} + +/// Option direction +#[napi_derive::napi] +#[derive(JsEnum, Debug, Hash, Eq, PartialEq)] +#[js(remote = "longbridge::quote::OptionDirection")] +pub enum OptionDirection { + /// Unknown + Unknown, + /// Put + Put, + /// Call + Call, +} + +/// Warrant type +#[napi_derive::napi] +#[derive(JsEnum, Debug, Hash, Eq, PartialEq)] +#[js(remote = "longbridge::quote::WarrantType")] +pub enum WarrantType { + /// Unknown + Unknown, + /// Call + Call, + /// Put + Put, + /// Bull + Bull, + /// Bear + Bear, + /// Inline + Inline, +} + +/// Candlestick period +#[napi_derive::napi] +#[allow(non_camel_case_types)] +#[derive(Debug, JsEnum, Hash, Eq, PartialEq)] +#[js(remote = "longbridge::quote::Period", from = false)] +pub enum Period { + /// One Minute + #[js(remote = "OneMinute")] + Min_1, + /// Five Minutes + #[js(remote = "FiveMinute")] + Min_5, + /// Fifteen Minutes + #[js(remote = "FifteenMinute")] + Min_15, + /// Thirty Minutes + #[js(remote = "ThirtyMinute")] + Min_30, + /// Sixty Minutes + #[js(remote = "SixtyMinute")] + Min_60, + /// One Days + Day, + /// One Week + Week, + /// One Month + Month, + /// One Year + Year, +} + +/// Candlestick adjustment type +#[napi_derive::napi] +#[derive(Debug, JsEnum, Hash, Eq, PartialEq)] +#[js(remote = "longbridge::quote::AdjustType", from = false)] +pub enum AdjustType { + /// Actual + NoAdjust, + /// Adjust forward + ForwardAdjust, +} + +/// The basic information of securities +#[napi_derive::napi] +#[derive(Debug, JsObject)] +#[js(remote = "longbridge::quote::SecurityStaticInfo")] +pub struct SecurityStaticInfo { + /// Security code + symbol: String, + /// Security name (zh-CN) + name_cn: String, + /// Security name (en) + name_en: String, + /// Security name (zh-HK) + name_hk: String, + /// Exchange which the security belongs to + exchange: String, + /// Trading currency + currency: String, + /// Lot size + lot_size: i32, + /// Total shares + total_shares: i64, + /// Circulating shares + circulating_shares: i64, + /// HK shares (only HK stocks) + hk_shares: i64, + /// Earnings per share + eps: Decimal, + /// Earnings per share (TTM) + eps_ttm: Decimal, + /// Net assets per share + bps: Decimal, + /// Dividend yield + dividend_yield: Decimal, + /// Types of supported derivatives + #[js(array)] + stock_derivatives: Vec, +} + +/// Quote of US pre/post market +#[napi_derive::napi] +#[derive(Debug, JsObject, Copy, Clone)] +#[js(remote = "longbridge::quote::PrePostQuote")] +pub struct PrePostQuote { + /// Latest price + last_done: Decimal, + /// Time of latest price + #[js(datetime)] + timestamp: DateTime, + /// Volume + volume: i64, + /// Turnover + turnover: Decimal, + /// High + high: Decimal, + /// Low + low: Decimal, + /// Close of the last trade session + prev_close: Decimal, +} + +/// Quote of securitity +#[napi_derive::napi] +#[derive(Debug, JsObject)] +#[js(remote = "longbridge::quote::SecurityQuote")] +pub struct SecurityQuote { + /// Security code + symbol: String, + /// Latest price + last_done: Decimal, + /// Yesterday's close + prev_close: Decimal, + /// Open + open: Decimal, + /// High + high: Decimal, + /// Low + low: Decimal, + /// Time of latest price + #[js(datetime)] + timestamp: DateTime, + /// Volume + volume: i64, + /// Turnover + turnover: Decimal, + /// Security trading status + trade_status: TradeStatus, + /// Quote of US pre market + #[js(opt)] + pre_market_quote: Option, + /// Quote of US post market + #[js(opt)] + post_market_quote: Option, +} + +/// Quote of option +#[napi_derive::napi] +#[derive(Debug, JsObject)] +#[js(remote = "longbridge::quote::OptionQuote")] +pub struct OptionQuote { + /// Security code + symbol: String, + /// Latest price + last_done: Decimal, + /// Yesterday's close + prev_close: Decimal, + /// Open + open: Decimal, + /// High + high: Decimal, + /// Low + low: Decimal, + /// Time of latest price + #[js(datetime)] + timestamp: DateTime, + /// Volume + volume: i64, + /// Turnover + turnover: Decimal, + /// Security trading status + trade_status: TradeStatus, + /// Implied volatility + implied_volatility: Decimal, + /// Number of open positions + open_interest: i64, + /// Exprity date + expiry_date: NaiveDate, + /// Strike price + strike_price: Decimal, + /// Contract multiplier + contract_multiplier: Decimal, + /// Option type + contract_type: OptionType, + /// Contract size + contract_size: Decimal, + /// Option direction + direction: OptionDirection, + /// Underlying security historical volatility of the option + historical_volatility: Decimal, + /// Underlying security symbol of the option + underlying_symbol: String, +} + +/// Quote of warrant +#[napi_derive::napi] +#[derive(Debug, JsObject)] +#[js(remote = "longbridge::quote::WarrantQuote")] +pub struct WarrantQuote { + /// Security code + symbol: String, + /// Latest price + last_done: Decimal, + /// Yesterday's close + prev_close: Decimal, + /// Open + open: Decimal, + /// High + high: Decimal, + /// Low + low: Decimal, + /// Time of latest price + #[js(datetime)] + timestamp: DateTime, + /// Volume + volume: i64, + /// Turnover + turnover: Decimal, + /// Security trading status + trade_status: TradeStatus, + /// Implied volatility + implied_volatility: Decimal, + /// Exprity date + expiry_date: NaiveDate, + /// Last tradalbe date + last_trade_date: NaiveDate, + /// Outstanding ratio + outstanding_ratio: Decimal, + /// Outstanding quantity + outstanding_qty: i64, + /// Conversion ratio + conversion_ratio: Decimal, + /// Warrant type + category: WarrantType, + /// Strike price + strike_price: Decimal, + /// Upper bound price + upper_strike_price: Decimal, + /// Lower bound price + lower_strike_price: Decimal, + /// Call price + call_price: Decimal, + /// Underlying security symbol of the warrant + underlying_symbol: String, +} + +/// Depth +#[napi_derive::napi] +#[derive(Debug, JsObject, Copy, Clone)] +#[js(remote = "longbridge::quote::Depth")] +pub struct Depth { + /// Position + position: i32, + /// Price + price: Decimal, + /// Volume + volume: i64, + /// Number of orders + order_num: i64, +} + +/// Security depth +#[napi_derive::napi] +#[derive(Debug, JsObject)] +#[js(remote = "longbridge::quote::SecurityDepth")] +pub struct SecurityDepth { + /// Ask depth + #[js(array)] + asks: Vec, + /// Bid depth + #[js(array)] + bids: Vec, +} + +/// Brokers +#[napi_derive::napi] +#[derive(Debug, JsObject, Clone)] +#[js(remote = "longbridge::quote::Brokers")] +pub struct Brokers { + /// Position + position: i32, + /// Broker IDs + broker_ids: Vec, +} + +/// Security brokers +#[napi_derive::napi] +#[derive(Debug, JsObject)] +#[js(remote = "longbridge::quote::SecurityBrokers")] +pub struct SecurityBrokers { + /// Ask brokers + #[js(array)] + ask_brokers: Vec, + /// Bid brokers + #[js(array)] + bid_brokers: Vec, +} + +/// Participant info +#[napi_derive::napi] +#[derive(Debug, JsObject)] +#[js(remote = "longbridge::quote::ParticipantInfo")] +pub struct ParticipantInfo { + /// Broker IDs + broker_ids: Vec, + /// Participant name (zh-CN) + name_cn: String, + /// Participant name (en) + name_en: String, + /// Participant name (zh-HK) + name_hk: String, +} + +/// Trade +#[napi_derive::napi] +#[derive(Debug, JsObject, Clone)] +#[js(remote = "longbridge::quote::Trade")] +pub struct Trade { + /// Price + price: Decimal, + /// Volume + volume: i64, + /// Time of trading + #[js(datetime)] + timestamp: DateTime, + /// Trade type + trade_type: String, + /// Trade direction + direction: TradeDirection, + /// Trade session + trade_session: TradeSession, +} + +/// Intraday line +#[napi_derive::napi] +#[derive(Debug, JsObject)] +#[js(remote = "longbridge::quote::IntradayLine")] +pub struct IntradayLine { + /// Close price of the minute + price: Decimal, + /// Start time of the minute + #[js(datetime)] + timestamp: DateTime, + /// Volume + volume: i64, + /// Turnover + turnover: Decimal, + /// Average price + avg_price: Decimal, +} + +/// Candlestick +#[napi_derive::napi] +#[derive(Debug, JsObject)] +#[js(remote = "longbridge::quote::Candlestick")] +pub struct Candlestick { + /// Close price + close: Decimal, + /// Open price + open: Decimal, + /// Low price + low: Decimal, + /// High price + high: Decimal, + /// Volume + volume: i64, + /// Turnover + turnover: Decimal, + /// Timestamp + #[js(datetime)] + timestamp: DateTime, +} + +/// Strike price info +#[napi_derive::napi] +#[derive(Debug, JsObject)] +#[js(remote = "longbridge::quote::StrikePriceInfo")] +pub struct StrikePriceInfo { + /// Strike price + price: Decimal, + /// Security code of call option + call_symbol: String, + /// Security code of put option + put_symbol: String, + /// Is standard + standard: bool, +} + +/// Issuer info +#[napi_derive::napi] +#[derive(Debug, JsObject)] +#[js(remote = "longbridge::quote::IssuerInfo")] +pub struct IssuerInfo { + /// Issuer ID + issuer_id: i32, + /// Issuer name (zh-CN) + name_cn: String, + /// Issuer name (en) + name_en: String, + /// Issuer name (zh-HK) + name_hk: String, +} + +/// The information of trading session +#[napi_derive::napi] +#[derive(Debug, JsObject, Copy, Clone)] +#[js(remote = "longbridge::quote::TradingSessionInfo")] +pub struct TradingSessionInfo { + /// Being trading time + begin_time: Time, + /// End trading time + end_time: Time, + /// Trading session + trade_session: TradeSession, +} + +/// Market trading session +#[napi_derive::napi] +#[derive(Debug, JsObject)] +#[js(remote = "longbridge::quote::MarketTradingSession")] +pub struct MarketTradingSession { + /// Market + market: Market, + /// Trading session + #[js(array)] + trade_session: Vec, +} + +/// Real-time quote +#[napi_derive::napi] +#[derive(JsObject, Debug)] +#[js(remote = "longbridge::quote::RealtimeQuote")] +pub struct RealtimeQuote { + /// Security code + symbol: String, + /// Latest price + last_done: Decimal, + /// Open + open: Decimal, + /// High + high: Decimal, + /// Low + low: Decimal, + /// Time of latest price + #[js(datetime)] + timestamp: DateTime, + /// Volume + volume: i64, + /// Turnover + turnover: Decimal, + /// Security trading status + trade_status: TradeStatus, +} + +/// Push real-time quote +#[napi_derive::napi] +#[derive(Debug, JsObject)] +#[js(remote = "longbridge::quote::PushQuote")] +pub struct PushQuote { + /// Latest price + last_done: Decimal, + /// Open + open: Decimal, + /// High + high: Decimal, + /// Low + low: Decimal, + /// Time of latest price + #[js(datetime)] + timestamp: DateTime, + /// Volume + volume: i64, + /// Turnover + turnover: Decimal, + /// Security trading status + trade_status: TradeStatus, + /// Trade session, + trade_session: TradeSession, +} + +/// Push real-time depth +#[napi_derive::napi] +#[derive(Debug, JsObject)] +#[js(remote = "longbridge::quote::PushDepth")] +pub struct PushDepth { + /// Ask depth + #[js(array)] + asks: Vec, + /// Bid depth + #[js(array)] + bids: Vec, +} + +/// Push real-time brokers +#[napi_derive::napi] +#[derive(Debug, JsObject)] +#[js(remote = "longbridge::quote::PushBrokers")] +pub struct PushBrokers { + /// Ask brokers + #[js(array)] + ask_brokers: Vec, + /// Bid brokers + #[js(array)] + bid_brokers: Vec, +} + +/// Push real-time trades +#[napi_derive::napi] +#[derive(Debug, JsObject)] +#[js(remote = "longbridge::quote::PushTrades")] +pub struct PushTrades { + /// Trades data + #[js(array)] + trades: Vec, +} + +/// Market trading days +#[napi_derive::napi] +#[derive(Debug, JsObject)] +#[js(remote = "longbridge::quote::MarketTradingDays")] +pub struct MarketTradingDays { + /// Trading days + #[js(array)] + trading_days: Vec, + /// Half trading days + #[js(array)] + half_trading_days: Vec, +} diff --git a/nodejs/src/time.rs b/nodejs/src/time.rs new file mode 100644 index 0000000000..2f4bc90e57 --- /dev/null +++ b/nodejs/src/time.rs @@ -0,0 +1,92 @@ +use std::time::Duration; + +use napi::{Error, Result}; + +/// Naive date type +#[derive(Debug, Copy, Clone)] +#[napi_derive::napi] +pub struct NaiveDate(pub(crate) time::Date); + +impl From for NaiveDate { + #[inline] + fn from(date: time::Date) -> Self { + Self(date) + } +} + +#[napi_derive::napi] +impl NaiveDate { + #[napi(constructor)] + pub fn new(year: i32, month: u8, day: u8) -> Result { + let month = + time::Month::try_from(month).map_err(|err| Error::from_reason(err.to_string()))?; + Ok(Self( + time::Date::from_calendar_date(year, month, day) + .map_err(|err| Error::from_reason(err.to_string()))?, + )) + } + + #[napi(getter)] + #[inline] + pub fn year(&self) -> i32 { + self.0.year() + } + + #[napi(getter)] + #[inline] + pub fn month(&self) -> u8 { + self.0.month() as u8 + } + + #[napi(getter)] + #[inline] + pub fn day(&self) -> u8 { + self.0.day() + } +} + +/// Naive date type +#[derive(Debug, Copy, Clone)] +#[napi_derive::napi] +pub struct Time(pub(crate) time::Time); + +impl From for Time { + #[inline] + fn from(time: time::Time) -> Self { + Self(time) + } +} + +#[napi_derive::napi] +impl Time { + #[napi(constructor)] + pub fn new(hour: u8, minute: u8, second: u8) -> Result { + Ok(Self( + time::Time::from_hms(hour, minute, second) + .map_err(|err| Error::from_reason(err.to_string()))?, + )) + } + + #[napi(getter)] + #[inline] + pub fn hour(&self) -> u8 { + self.0.hour() + } + + #[napi(getter)] + #[inline] + pub fn monute(&self) -> u8 { + self.0.minute() as u8 + } + + #[napi(getter)] + #[inline] + pub fn second(&self) -> u8 { + self.0.second() + } +} + +#[napi_derive::napi] +async fn sleep(milliseconds: i64) { + tokio::time::sleep(Duration::from_millis(milliseconds as u64)).await +} diff --git a/nodejs/src/trade/context.rs b/nodejs/src/trade/context.rs new file mode 100644 index 0000000000..252127ca6f --- /dev/null +++ b/nodejs/src/trade/context.rs @@ -0,0 +1,233 @@ +use std::sync::Arc; + +use longbridge::trade::{GetFundPositionsOptions, GetStockPositionsOptions, PushEvent}; +use napi::{threadsafe_function::ThreadsafeFunctionCallMode, JsFunction, Result}; + +use crate::{ + config::Config, + error::ErrorNewType, + trade::{ + requests::{ + GetCashFlowOptions, GetHistoryExecutionsOptions, GetHistoryOrdersOptions, + GetTodayExecutionsOptions, GetTodayOrdersOptions, ReplaceOrderOptions, + SubmitOrderOptions, + }, + types::{ + AccountBalance, CashFlow, Execution, FundPositionsResponse, Order, PushOrderChanged, + StockPositionsResponse, SubmitOrderResponse, TopicType, + }, + }, + utils::JsCallback, +}; + +/// Trade context +#[napi_derive::napi] +#[derive(Clone)] +pub struct TradeContext { + ctx: longbridge::trade::TradeContext, + on_order_changed: JsCallback, +} + +#[napi_derive::napi] +impl TradeContext { + #[napi] + pub async fn new(config: &Config) -> Result { + let (ctx, mut receiver) = + longbridge::trade::TradeContext::try_new(Arc::new(config.0.clone())) + .await + .map_err(ErrorNewType)?; + let js_ctx = Self { + ctx, + on_order_changed: Default::default(), + }; + + tokio::spawn({ + let js_ctx = js_ctx.clone(); + async move { + while let Some(msg) = receiver.recv().await { + match msg { + PushEvent::OrderChanged(order_changed) => { + if let Some(handler) = js_ctx.on_order_changed.lock().clone() { + if let Ok(order_changed) = order_changed.try_into() { + handler.call( + Ok(order_changed), + ThreadsafeFunctionCallMode::Blocking, + ); + } + } + } + } + } + } + }); + + Ok(js_ctx) + } + + #[napi( + setter, + ts_args_type = "callback: (err: null | Error, event: PushOrderChanged) => void" + )] + pub fn on_order_changed(&mut self, handler: JsFunction) -> Result<()> { + *self.on_order_changed.lock() = + Some(handler.create_threadsafe_function(32, |ctx| Ok(vec![ctx.value]))?); + Ok(()) + } + + /// Subscribe + #[napi] + pub async fn subscribe(&self, topics: Vec) -> Result<()> { + self.ctx + .subscribe(topics.into_iter().map(Into::into)) + .await + .map_err(ErrorNewType)?; + Ok(()) + } + + /// Unsubscribe + #[napi] + pub async fn unsubscribe(&self, topics: Vec) -> Result<()> { + self.ctx + .unsubscribe(topics.into_iter().map(Into::into)) + .await + .map_err(ErrorNewType)?; + Ok(()) + } + + /// Get history executions + #[napi] + pub async fn history_executions( + &self, + opts: Option<&GetHistoryExecutionsOptions>, + ) -> Result> { + self.ctx + .history_executions(opts.cloned().map(Into::into)) + .await + .map_err(ErrorNewType)? + .into_iter() + .map(TryInto::try_into) + .collect() + } + + /// Get today executions + #[napi] + pub async fn today_executions( + &self, + opts: Option<&GetTodayExecutionsOptions>, + ) -> Result> { + self.ctx + .today_executions(opts.cloned().map(Into::into)) + .await + .map_err(ErrorNewType)? + .into_iter() + .map(TryInto::try_into) + .collect() + } + + /// Get history orders + #[napi] + pub async fn history_orders( + &self, + opts: Option<&GetHistoryOrdersOptions>, + ) -> Result> { + self.ctx + .history_orders(opts.cloned().map(Into::into)) + .await + .map_err(ErrorNewType)? + .into_iter() + .map(TryInto::try_into) + .collect() + } + + /// Get today orders + #[napi] + pub async fn today_orders(&self, opts: Option<&GetTodayOrdersOptions>) -> Result> { + self.ctx + .today_orders(opts.cloned().map(Into::into)) + .await + .map_err(ErrorNewType)? + .into_iter() + .map(TryInto::try_into) + .collect() + } + + /// Replace order + #[napi] + pub async fn replace_order(&self, opts: &ReplaceOrderOptions) -> Result<()> { + self.ctx + .replace_order(opts.clone().into()) + .await + .map_err(ErrorNewType)?; + Ok(()) + } + + /// Submit order + #[napi] + pub async fn submit_order(&self, opts: &SubmitOrderOptions) -> Result { + self.ctx + .submit_order(opts.clone().into()) + .await + .map_err(ErrorNewType)? + .try_into() + } + + /// Withdraw order + #[napi] + pub async fn withdraw_order(&self, order_id: String) -> Result<()> { + self.ctx + .withdraw_order(order_id) + .await + .map_err(ErrorNewType)?; + Ok(()) + } + + /// Get account balance + #[napi] + pub async fn account_balance(&self) -> Result> { + self.ctx + .account_balance() + .await + .map_err(ErrorNewType)? + .into_iter() + .map(TryInto::try_into) + .collect::>>() + } + + /// Get cash flow + #[napi] + pub async fn cash_flow(&self, opts: &GetCashFlowOptions) -> Result> { + self.ctx + .cash_flow(opts.clone().into()) + .await + .map_err(ErrorNewType)? + .into_iter() + .map(TryInto::try_into) + .collect::>>() + } + + /// Get fund positions + #[napi] + pub async fn fund_positions( + &self, + symbols: Option>, + ) -> Result { + self.ctx + .fund_positions(GetFundPositionsOptions::new().symbols(symbols.unwrap_or_default())) + .await + .map_err(ErrorNewType)? + .try_into() + } + + /// Get stock positions + #[napi] + pub async fn stock_positions( + &self, + symbols: Option>, + ) -> Result { + self.ctx + .stock_positions(GetStockPositionsOptions::new().symbols(symbols.unwrap_or_default())) + .await + .map_err(ErrorNewType)? + .try_into() + } +} diff --git a/nodejs/src/trade/mod.rs b/nodejs/src/trade/mod.rs new file mode 100644 index 0000000000..a461159ca9 --- /dev/null +++ b/nodejs/src/trade/mod.rs @@ -0,0 +1,13 @@ +use crate::{trade::types::PushOrderChanged, utils::JsCallback}; + +mod context; +mod requests; +mod types; + +/// Trade context +#[napi_derive::napi] +#[derive(Clone)] +pub struct QuoteContext { + ctx: longbridge::quote::QuoteContext, + on_order_changed: JsCallback, +} diff --git a/nodejs/src/trade/requests/get_cash_flow.rs b/nodejs/src/trade/requests/get_cash_flow.rs new file mode 100644 index 0000000000..148ec0ef8a --- /dev/null +++ b/nodejs/src/trade/requests/get_cash_flow.rs @@ -0,0 +1,56 @@ +use chrono::{DateTime, Utc}; + +use crate::{trade::types::BalanceType, utils::from_datetime}; + +/// Options for submit order request +#[napi_derive::napi] +#[derive(Clone)] +pub struct GetCashFlowOptions(longbridge::trade::GetCashFlowOptions); + +#[napi_derive::napi(object)] +impl GetCashFlowOptions { + /// Create a new `GetCashFlowOptions` + #[napi(constructor)] + #[inline] + pub fn new(start_at: DateTime, end_at: DateTime) -> GetCashFlowOptions { + Self(longbridge::trade::GetCashFlowOptions::new( + from_datetime(start_at), + from_datetime(end_at), + )) + } + + /// Set the business type + #[napi] + #[inline] + pub fn business_type(&self, business_type: BalanceType) -> GetCashFlowOptions { + Self(self.0.clone().business_type(business_type.into())) + } + + /// Set the security symbol + #[napi] + #[inline] + pub fn symbol(&self, symbol: String) -> GetCashFlowOptions { + Self(self.0.clone().symbol(symbol)) + } + + /// Set the page number + #[napi] + #[inline] + pub fn page(&self, page: i32) -> GetCashFlowOptions { + Self(self.0.clone().page(page.max(0) as usize)) + } + + /// Set the page size + #[napi] + #[inline] + pub fn size(&self, size: i32) -> GetCashFlowOptions { + Self(self.0.clone().size(size.max(0) as usize)) + } +} + +impl From for longbridge::trade::GetCashFlowOptions { + #[inline] + fn from(opts: GetCashFlowOptions) -> Self { + opts.0 + } +} diff --git a/nodejs/src/trade/requests/get_history_executions.rs b/nodejs/src/trade/requests/get_history_executions.rs new file mode 100644 index 0000000000..06a7bc8813 --- /dev/null +++ b/nodejs/src/trade/requests/get_history_executions.rs @@ -0,0 +1,46 @@ +use chrono::{DateTime, Utc}; + +use crate::utils::from_datetime; + +/// Options for get histroy executions request +#[napi_derive::napi] +#[derive(Clone, Default)] +pub struct GetHistoryExecutionsOptions(longbridge::trade::GetHistoryExecutionsOptions); + +#[napi_derive::napi(object)] +impl GetHistoryExecutionsOptions { + /// Create a new `GetHistoryExecutionsOptions` + #[napi(constructor)] + #[inline] + pub fn new() -> GetHistoryExecutionsOptions { + Default::default() + } + + /// Set the security symbol + #[napi] + #[inline] + pub fn symbol(&self, symbol: String) -> GetHistoryExecutionsOptions { + Self(self.0.clone().symbol(symbol)) + } + + /// Set the start time + #[napi] + #[inline] + pub fn start_at(&self, start_at: DateTime) -> GetHistoryExecutionsOptions { + Self(self.0.clone().start_at(from_datetime(start_at))) + } + + /// Set the end time + #[napi] + #[inline] + pub fn end_at(&self, end_at: DateTime) -> GetHistoryExecutionsOptions { + Self(self.0.clone().end_at(from_datetime(end_at))) + } +} + +impl From for longbridge::trade::GetHistoryExecutionsOptions { + #[inline] + fn from(opts: GetHistoryExecutionsOptions) -> Self { + opts.0 + } +} diff --git a/nodejs/src/trade/requests/get_history_orders.rs b/nodejs/src/trade/requests/get_history_orders.rs new file mode 100644 index 0000000000..034850e4e1 --- /dev/null +++ b/nodejs/src/trade/requests/get_history_orders.rs @@ -0,0 +1,75 @@ +use chrono::{DateTime, Utc}; +use napi::Either; + +use crate::{ + trade::types::{OrderSide, OrderStatus}, + types::Market, + utils::from_datetime, +}; + +/// Options for get histroy orders request +#[napi_derive::napi] +#[derive(Clone, Default)] +pub struct GetHistoryOrdersOptions(longbridge::trade::GetHistoryOrdersOptions); + +#[napi_derive::napi(object)] +impl GetHistoryOrdersOptions { + /// Create a new `GetHistoryOrdersOptions` + #[napi(constructor)] + #[inline] + pub fn new() -> GetHistoryOrdersOptions { + Default::default() + } + + /// Set the security symbol + #[napi] + #[inline] + pub fn symbol(&self, symbol: String) -> GetHistoryOrdersOptions { + Self(self.0.clone().symbol(symbol)) + } + + /// Set the order status + #[napi] + #[inline] + pub fn status(&self, status: Either>) -> GetHistoryOrdersOptions { + Self(self.0.clone().status(match status { + Either::A(status) => vec![status.into()], + Either::B(status) => status.into_iter().map(Into::into).collect(), + })) + } + + /// Set the order side + #[napi] + #[inline] + pub fn side(&self, side: OrderSide) -> GetHistoryOrdersOptions { + Self(self.0.clone().side(side.into())) + } + + /// Set the market + #[napi] + #[inline] + pub fn market(&self, market: Market) -> GetHistoryOrdersOptions { + Self(self.0.clone().market(market.into())) + } + + /// Set the start time + #[napi] + #[inline] + pub fn start_at(&self, start_at: DateTime) -> GetHistoryOrdersOptions { + Self(self.0.clone().start_at(from_datetime(start_at))) + } + + /// Set the end time + #[napi] + #[inline] + pub fn end_at(&self, end_at: DateTime) -> GetHistoryOrdersOptions { + Self(self.0.clone().end_at(from_datetime(end_at))) + } +} + +impl From for longbridge::trade::GetHistoryOrdersOptions { + #[inline] + fn from(opts: GetHistoryOrdersOptions) -> Self { + opts.0 + } +} diff --git a/nodejs/src/trade/requests/get_today_executions.rs b/nodejs/src/trade/requests/get_today_executions.rs new file mode 100644 index 0000000000..610407244d --- /dev/null +++ b/nodejs/src/trade/requests/get_today_executions.rs @@ -0,0 +1,35 @@ +/// Options for get histroy executions request +#[napi_derive::napi] +#[derive(Clone, Default)] +pub struct GetTodayExecutionsOptions(longbridge::trade::GetTodayExecutionsOptions); + +#[napi_derive::napi(object)] +impl GetTodayExecutionsOptions { + /// Create a new `GetTodayExecutionsOptions` + #[napi(constructor)] + #[inline] + pub fn new() -> GetTodayExecutionsOptions { + Default::default() + } + + /// Set the security symbol + #[napi] + #[inline] + pub fn symbol(&self, symbol: String) -> GetTodayExecutionsOptions { + Self(self.0.clone().symbol(symbol)) + } + + /// Set the order id + #[napi] + #[inline] + pub fn order_id(&self, order_id: String) -> GetTodayExecutionsOptions { + Self(self.0.clone().order_id(order_id)) + } +} + +impl From for longbridge::trade::GetTodayExecutionsOptions { + #[inline] + fn from(opts: GetTodayExecutionsOptions) -> Self { + opts.0 + } +} diff --git a/nodejs/src/trade/requests/get_today_orders.rs b/nodejs/src/trade/requests/get_today_orders.rs new file mode 100644 index 0000000000..3cea874599 --- /dev/null +++ b/nodejs/src/trade/requests/get_today_orders.rs @@ -0,0 +1,59 @@ +use napi::Either; + +use crate::{ + trade::types::{OrderSide, OrderStatus}, + types::Market, +}; + +/// Options for get today orders request +#[napi_derive::napi] +#[derive(Clone, Default)] +pub struct GetTodayOrdersOptions(longbridge::trade::GetTodayOrdersOptions); + +#[napi_derive::napi(object)] +impl GetTodayOrdersOptions { + /// Create a new `GetTodayOrdersOptions` + #[napi(constructor)] + #[inline] + pub fn new() -> GetTodayOrdersOptions { + Default::default() + } + + /// Set the security symbol + #[napi] + #[inline] + pub fn symbol(&self, symbol: String) -> GetTodayOrdersOptions { + Self(self.0.clone().symbol(symbol)) + } + + /// Set the order status + #[napi] + #[inline] + pub fn status(&self, status: Either>) -> GetTodayOrdersOptions { + Self(self.0.clone().status(match status { + Either::A(status) => vec![status.into()], + Either::B(status) => status.into_iter().map(Into::into).collect(), + })) + } + + /// Set the order side + #[napi] + #[inline] + pub fn side(&self, side: OrderSide) -> GetTodayOrdersOptions { + Self(self.0.clone().side(side.into())) + } + + /// Set the market + #[napi] + #[inline] + pub fn market(&self, market: Market) -> GetTodayOrdersOptions { + Self(self.0.clone().market(market.into())) + } +} + +impl From for longbridge::trade::GetTodayOrdersOptions { + #[inline] + fn from(opts: GetTodayOrdersOptions) -> Self { + opts.0 + } +} diff --git a/nodejs/src/trade/requests/mod.rs b/nodejs/src/trade/requests/mod.rs new file mode 100644 index 0000000000..a73a7ba2f0 --- /dev/null +++ b/nodejs/src/trade/requests/mod.rs @@ -0,0 +1,15 @@ +mod get_cash_flow; +mod get_history_executions; +mod get_history_orders; +mod get_today_executions; +mod get_today_orders; +mod replace_order; +mod submit_order; + +pub use get_cash_flow::GetCashFlowOptions; +pub use get_history_executions::GetHistoryExecutionsOptions; +pub use get_history_orders::GetHistoryOrdersOptions; +pub use get_today_executions::GetTodayExecutionsOptions; +pub use get_today_orders::GetTodayOrdersOptions; +pub use replace_order::ReplaceOrderOptions; +pub use submit_order::SubmitOrderOptions; diff --git a/nodejs/src/trade/requests/replace_order.rs b/nodejs/src/trade/requests/replace_order.rs new file mode 100644 index 0000000000..3157bc85ed --- /dev/null +++ b/nodejs/src/trade/requests/replace_order.rs @@ -0,0 +1,67 @@ +use crate::decimal::Decimal; + +/// Options for get today orders request +#[napi_derive::napi] +#[derive(Clone)] +pub struct ReplaceOrderOptions(longbridge::trade::ReplaceOrderOptions); + +#[napi_derive::napi(object)] +impl ReplaceOrderOptions { + /// Create a new `ReplaceOrderOptions` + #[napi(constructor)] + #[inline] + pub fn new(order_id: String, quantity: &Decimal) -> ReplaceOrderOptions { + Self(longbridge::trade::ReplaceOrderOptions::new( + order_id, quantity.0, + )) + } + + /// Set the price + #[napi] + #[inline] + pub fn price(&self, price: &Decimal) -> ReplaceOrderOptions { + Self(self.0.clone().price(price.0)) + } + + /// Set the trigger price + #[napi] + #[inline] + pub fn trigger_price(&self, trigger_price: &Decimal) -> Self { + Self(self.0.clone().trigger_price(trigger_price.0)) + } + + /// Set the limit offset + #[napi] + #[inline] + pub fn limit_offset(&self, limit_offset: &Decimal) -> Self { + Self(self.0.clone().limit_offset(limit_offset.0)) + } + + /// Set the trailing amount + #[napi] + #[inline] + pub fn trailing_amount(&self, trailing_amount: &Decimal) -> Self { + Self(self.0.clone().trailing_amount(trailing_amount.0)) + } + + /// Set the trailing percent + #[napi] + #[inline] + pub fn trailing_percent(&self, trailing_percent: &Decimal) -> Self { + Self(self.0.clone().trailing_percent(trailing_percent.0)) + } + + /// Set the remark + #[napi] + #[inline] + pub fn remark(&self, remark: String) -> Self { + Self(self.0.clone().remark(remark)) + } +} + +impl From for longbridge::trade::ReplaceOrderOptions { + #[inline] + fn from(opts: ReplaceOrderOptions) -> Self { + opts.0 + } +} diff --git a/nodejs/src/trade/requests/submit_order.rs b/nodejs/src/trade/requests/submit_order.rs new file mode 100644 index 0000000000..6d5df077d0 --- /dev/null +++ b/nodejs/src/trade/requests/submit_order.rs @@ -0,0 +1,95 @@ +use crate::{ + decimal::Decimal, + time::NaiveDate, + trade::types::{OrderSide, OrderType, OutsideRTH, TimeInForceType}, +}; + +/// Options for submit order request +#[napi_derive::napi] +#[derive(Clone)] +pub struct SubmitOrderOptions(longbridge::trade::SubmitOrderOptions); + +#[napi_derive::napi] +impl SubmitOrderOptions { + /// Create a new `SubmitOrderOptions` + #[napi(constructor)] + #[inline] + pub fn new( + symbol: String, + order_type: OrderType, + side: OrderSide, + submitted_quantity: &Decimal, + time_in_force: TimeInForceType, + ) -> SubmitOrderOptions { + Self(longbridge::trade::SubmitOrderOptions::new( + symbol, + order_type.into(), + side.into(), + submitted_quantity.0, + time_in_force.into(), + )) + } + + /// Set the submitted price + #[napi] + #[inline] + pub fn submitted_price(&self, submitted_price: &Decimal) -> SubmitOrderOptions { + Self(self.0.clone().submitted_price(submitted_price.0)) + } + + /// Set the trigger price + #[napi] + #[inline] + pub fn trigger_price(&self, trigger_price: &Decimal) -> SubmitOrderOptions { + Self(self.0.clone().trigger_price(trigger_price.0)) + } + + /// Set the limit offset + #[napi] + #[inline] + pub fn limit_offset(&self, limit_offset: &Decimal) -> SubmitOrderOptions { + Self(self.0.clone().limit_offset(limit_offset.0)) + } + + /// Set the trailing amount + #[napi] + #[inline] + pub fn trailing_amount(&self, trailing_amount: &Decimal) -> SubmitOrderOptions { + Self(self.0.clone().trailing_amount(trailing_amount.0)) + } + + /// Set the trailing percent + #[napi] + #[inline] + pub fn trailing_percent(&self, trailing_percent: &Decimal) -> SubmitOrderOptions { + Self(self.0.clone().trailing_percent(trailing_percent.0)) + } + + /// Set the expire date + #[napi] + #[inline] + pub fn expire_date(&self, expire_date: &NaiveDate) -> SubmitOrderOptions { + Self(self.0.clone().expire_date(expire_date.0)) + } + + /// Enable or disable outside regular trading hours + #[napi] + #[inline] + pub fn outside_rth(&self, outside_rth: OutsideRTH) -> SubmitOrderOptions { + Self(self.0.clone().outside_rth(outside_rth.into())) + } + + /// Set the remark + #[napi] + #[inline] + pub fn remark(&self, remark: String) -> SubmitOrderOptions { + Self(self.0.clone().remark(remark)) + } +} + +impl From for longbridge::trade::SubmitOrderOptions { + #[inline] + fn from(opts: SubmitOrderOptions) -> Self { + opts.0 + } +} diff --git a/nodejs/src/trade/types.rs b/nodejs/src/trade/types.rs new file mode 100644 index 0000000000..df6b8441fb --- /dev/null +++ b/nodejs/src/trade/types.rs @@ -0,0 +1,491 @@ +use chrono::{DateTime, Utc}; +use longbridge_nodejs_macros::{JsEnum, JsObject}; +use napi::bindgen_prelude::*; + +use crate::{decimal::Decimal, time::NaiveDate}; + +/// Topic type +#[napi_derive::napi] +#[derive(Debug, JsEnum, Hash, Eq, PartialEq)] +#[js(remote = "longbridge::trade::TopicType")] +pub enum TopicType { + /// Private notification for trade + Private, +} + +/// Trade +#[napi_derive::napi] +#[derive(Debug, JsObject)] +#[js(remote = "longbridge::trade::Execution")] +pub struct Execution { + /// Order ID + order_id: String, + /// Execution ID + trade_id: String, + /// Security code + symbol: String, + /// Trade done time + #[js(datetime)] + trade_done_at: DateTime, + /// Executed quantity + quantity: Decimal, + /// Executed price + price: Decimal, +} + +#[napi_derive::napi] +#[derive(Debug, JsEnum, Hash, Eq, PartialEq)] +#[js(remote = "longbridge::trade::OrderStatus")] +pub enum OrderStatus { + /// Unknown + Unknown, + /// Not reported + NotReported, + /// Not reported (Replaced Order) + ReplacedNotReported, + /// Not reported (Protected Order) + ProtectedNotReported, + /// Not reported (Conditional Order) + VarietiesNotReported, + /// Filled + Filled, + /// Wait To New + WaitToNew, + /// New + New, + /// Wait To Replace + WaitToReplace, + /// Pending Replace + PendingReplace, + /// Replaced + Replaced, + /// Partial Filled + PartialFilled, + /// Wait To Cancel + WaitToCancel, + /// Pending Cancel + PendingCancel, + /// Rejected + Rejected, + /// Canceled + Canceled, + /// Expired + Expired, + /// Partial Withdrawal + PartialWithdrawal, +} + +#[napi_derive::napi] +#[derive(Debug, JsEnum, Hash, Eq, PartialEq)] +#[js(remote = "longbridge::trade::OrderSide")] +pub enum OrderSide { + /// Unknown + Unknown, + /// Buy + Buy, + /// Sell + Sell, +} + +#[napi_derive::napi] +#[derive(Debug, JsEnum, Hash, Eq, PartialEq)] +#[js(remote = "longbridge::trade::OrderType")] +#[allow(clippy::upper_case_acronyms)] +pub enum OrderType { + /// Unknown + Unknown, + /// Limit Order + LO, + /// Enhanced Limit Order + ELO, + /// Market Order + MO, + /// At-auction Order + AO, + /// At-auction Limit Order + ALO, + /// Odd Lots + ODD, + /// Limit If Touched + LIT, + /// Market If Touched + MIT, + /// Trailing Limit If Touched (Trailing Amount) + TSLPAMT, + /// Trailing Limit If Touched (Trailing Percent) + TSLPPCT, + /// Trailing Market If Touched (Trailing Amount) + TSMAMT, + /// Trailing Market If Touched (Trailing Percent) + TSMPCT, +} + +/// Order tag +#[napi_derive::napi] +#[derive(Debug, JsEnum, Hash, Eq, PartialEq)] +#[js(remote = "longbridge::trade::OrderTag")] +pub enum OrderTag { + /// Unknown + Unknown, + /// Normal Order + Normal, + /// Long term Order + LongTerm, + /// Grey Order + Grey, +} + +/// Time in force type +#[napi_derive::napi] +#[derive(Debug, JsEnum, Hash, Eq, PartialEq)] +#[js(remote = "longbridge::trade::TimeInForceType")] +pub enum TimeInForceType { + /// Unknown + Unknown, + /// Day Order + Day, + /// Good Til Canceled Order + GoodTilCanceled, + /// Good Til Date Order + GoodTilDate, +} + +/// Trigger status +#[napi_derive::napi] +#[derive(Debug, JsEnum, Hash, Eq, PartialEq)] +#[js(remote = "longbridge::trade::TriggerStatus")] +pub enum TriggerStatus { + /// Unknown + Unknown, + /// Deactive + Deactive, + /// Active + Active, + /// Released + Released, +} + +/// Enable or disable outside regular trading hours +#[napi_derive::napi] +#[derive(Debug, JsEnum, Hash, Eq, PartialEq)] +#[js(remote = "longbridge::trade::OutsideRTH")] +pub enum OutsideRTH { + /// Unknown + Unknown, + /// Regular trading hour only + RTHOnly, + /// Any time + AnyTime, +} + +/// Order +#[napi_derive::napi] +#[derive(Debug, JsObject)] +#[js(remote = "longbridge::trade::Order")] +pub struct Order { + /// Order ID + order_id: String, + /// Order status + status: OrderStatus, + /// Stock name + stock_name: String, + /// Submitted quantity + quantity: Decimal, + /// Executed quantity + #[js(opt)] + executed_quantity: Option, + /// Submitted price + #[js(opt)] + price: Option, + /// Executed price + #[js(opt)] + executed_price: Option, + /// Submitted time + #[js(datetime)] + submitted_at: DateTime, + /// Order side + side: OrderSide, + /// Security code + symbol: String, + /// Order type + order_type: OrderType, + /// Last done + #[js(opt)] + last_done: Option, + /// `LIT` / `MIT` Order Trigger Price + #[js(opt)] + trigger_price: Option, + /// Rejected Message or remark + msg: String, + /// Order tag + tag: OrderTag, + /// Time in force type + time_in_force: TimeInForceType, + /// Long term order expire date + #[js(opt)] + expire_date: Option, + /// Last updated time + #[js(opt, datetime)] + updated_at: Option>, + /// Conditional order trigger time + #[js(opt, datetime)] + trigger_at: Option>, + /// `TSMAMT` / `TSLPAMT` order trailing amount + #[js(opt)] + trailing_amount: Option, + /// `TSMPCT` / `TSLPPCT` order trailing percent + #[js(opt)] + trailing_percent: Option, + /// `TSLPAMT` / `TSLPPCT` order limit offset amount + #[js(opt)] + limit_offset: Option, + /// Conditional order trigger status + #[js(opt)] + trigger_status: Option, + /// Currency + currency: String, + /// Enable or disable outside regular trading hours + #[js(opt)] + outside_rth: Option, +} + +/// Order changed message +#[napi_derive::napi] +#[derive(Debug, JsObject)] +#[js(remote = "longbridge::trade::PushOrderChanged")] +pub struct PushOrderChanged { + /// Order side + side: OrderSide, + /// Stock name + stock_name: String, + /// Submitted quantity + quantity: String, + /// Order symbol + symbol: String, + /// Order type + order_type: OrderType, + /// Submitted price + price: Decimal, + /// Executed quantity + executed_quantity: i64, + /// Executed price + executed_price: Decimal, + /// Order ID + order_id: String, + /// Currency + currency: String, + /// Order status + status: OrderStatus, + /// Submitted time + #[js(datetime)] + submitted_at: DateTime, + /// Last updated time + #[js(datetime)] + updated_at: DateTime, + /// Order trigger price + #[js(opt)] + trigger_price: Option, + /// Rejected message or remark + msg: String, + /// Order tag + tag: OrderTag, + /// Conditional order trigger status + #[js(opt)] + trigger_status: Option, + /// Conditional order trigger time + #[js(opt, datetime)] + trigger_at: Option>, + /// Trailing amount + #[js(opt)] + trailing_amount: Option, + /// Trailing percent + #[js(opt)] + trailing_percent: Option, + /// Limit offset amount + #[js(opt)] + limit_offset: Option, + /// Account no + account_no: String, +} + +/// Response for withdraw order request +#[napi_derive::napi] +#[derive(Debug, JsObject)] +#[js(remote = "longbridge::trade::SubmitOrderResponse")] +pub struct SubmitOrderResponse { + /// Order id + order_id: String, +} + +/// Account balance +#[napi_derive::napi] +#[derive(Debug, JsObject, Clone)] +#[js(remote = "longbridge::trade::CashInfo")] +pub struct CashInfo { + /// Withdraw cash + withdraw_cash: Decimal, + /// Available cash + available_cash: Decimal, + /// Frozen cash + frozen_cash: Decimal, + /// Cash to be settled + settling_cash: Decimal, + /// Currency + currency: String, +} + +/// Account balance +#[napi_derive::napi] +#[derive(Debug, JsObject)] +#[js(remote = "longbridge::trade::AccountBalance")] +pub struct AccountBalance { + /// Total cash + total_cash: Decimal, + /// Maximum financing amount + max_finance_amount: Decimal, + /// Remaining financing amount + remaining_finance_amount: Decimal, + /// Risk control level + risk_level: Option, + /// Margin call + margin_call: Decimal, + /// Currency + currency: String, + /// Cash details + #[js(array)] + cash_infos: Vec, +} + +#[napi_derive::napi] +#[derive(Debug, JsEnum, Hash, Eq, PartialEq)] +#[js(remote = "longbridge::trade::BalanceType")] +pub enum BalanceType { + /// Unknown + Unknown, + /// Limit Order + Cash, + /// Stock + Stock, + /// Fund + Fund, +} + +#[napi_derive::napi] +#[derive(Debug, JsEnum, Hash, Eq, PartialEq)] +#[js(remote = "longbridge::trade::CashFlowDirection")] +pub enum CashFlowDirection { + /// Unknown + Unknown, + /// Out + Out, + /// Stock + In, +} + +/// Account balance +#[napi_derive::napi] +#[derive(Debug, JsObject)] +#[js(remote = "longbridge::trade::CashFlow")] +pub struct CashFlow { + /// Cash flow name + transaction_flow_name: String, + /// Outflow direction + direction: CashFlowDirection, + /// Balance type + business_type: BalanceType, + /// Cash amount + balance: Decimal, + /// Cash currency + currency: String, + /// Business time + #[js(datetime)] + business_time: DateTime, + /// Associated Stock code information + symbol: Option, + /// Cash flow description + description: String, +} + +/// Fund positions response +#[napi_derive::napi] +#[derive(Debug, JsObject)] +#[js(remote = "longbridge::trade::FundPositionsResponse")] +pub struct FundPositionsResponse { + #[js(array)] + channels: Vec, +} + +/// Fund position channel +#[napi_derive::napi] +#[derive(Debug, JsObject, Clone)] +#[js(remote = "longbridge::trade::FundPositionChannel")] +pub struct FundPositionChannel { + /// Account type + account_channel: String, + /// Fund positions + #[js(array)] + positions: Vec, +} + +/// Fund position +#[napi_derive::napi] +#[derive(Debug, JsObject, Clone)] +#[js(remote = "longbridge::trade::FundPosition")] +pub struct FundPosition { + /// Fund ISIN code + symbol: String, + /// Current equity + current_net_asset_value: Decimal, + /// Current equity time + #[js(datetime)] + net_asset_value_day: DateTime, + /// Fund name + symbol_name: String, + /// Currency + currency: String, + /// Net cost + cost_net_asset_value: Decimal, + /// Holding units + holding_units: Decimal, +} + +/// Stock positions response +#[napi_derive::napi] +#[derive(Debug, JsObject, Clone)] +#[js(remote = "longbridge::trade::StockPositionsResponse")] +pub struct StockPositionsResponse { + #[js(array)] + channels: Vec, +} + +/// Stock position channel +#[napi_derive::napi] +#[derive(Debug, JsObject, Clone)] +#[js(remote = "longbridge::trade::StockPositionChannel")] +pub struct StockPositionChannel { + /// Account type + account_channel: String, + /// Fund details + #[js(array)] + positions: Vec, +} + +/// Stock position +#[napi_derive::napi] +#[derive(Debug, JsObject, Clone)] +#[js(remote = "longbridge::trade::StockPosition")] +pub struct StockPosition { + /// Stock code + symbol: String, + /// Stock name + symbol_name: String, + /// The number of holdings + quantity: Decimal, + /// Available quantity + available_quality: Decimal, + /// Currency + currency: String, + /// Cost Price(According to the client's choice of average purchase or + /// diluted cost) + cost_price: Decimal, +} diff --git a/nodejs/src/types.rs b/nodejs/src/types.rs new file mode 100644 index 0000000000..6260ae6021 --- /dev/null +++ b/nodejs/src/types.rs @@ -0,0 +1,18 @@ +use longbridge_nodejs_macros::JsEnum; +use napi::bindgen_prelude::*; + +#[napi_derive::napi] +#[derive(Debug, JsEnum, Hash, Eq, PartialEq)] +#[js(remote = "longbridge::Market")] +pub enum Market { + /// Unknown + Unknown, + /// US market + US, + /// HK market + HK, + /// CN market + CN, + /// SG market + SG, +} diff --git a/nodejs/src/utils.rs b/nodejs/src/utils.rs new file mode 100644 index 0000000000..66a437f07c --- /dev/null +++ b/nodejs/src/utils.rs @@ -0,0 +1,17 @@ +use std::sync::Arc; + +use chrono::{DateTime, NaiveDateTime, Utc}; +use napi::threadsafe_function::{ErrorStrategy, ThreadsafeFunction}; +use parking_lot::Mutex; +use time::OffsetDateTime; + +pub(crate) type JsCallback = + Arc>>>; + +pub(crate) fn to_datetime(time: OffsetDateTime) -> DateTime { + DateTime::from_utc(NaiveDateTime::from_timestamp(time.unix_timestamp(), 0), Utc) +} + +pub(crate) fn from_datetime(time: DateTime) -> OffsetDateTime { + OffsetDateTime::from_unix_timestamp(time.timestamp()).expect("invalid timestamp") +} diff --git a/python/Cargo.toml b/python/Cargo.toml index d1a2729976..4afd03f47c 100644 --- a/python/Cargo.toml +++ b/python/Cargo.toml @@ -19,7 +19,7 @@ longbridge = { path = "../rust", version = "0.2.8", features = ["blocking"] } longbridge-python-macros = { path = "crates/macros", version = "0.2.8" } once_cell = "1.11.0" -pyo3 = { version = "0.16.4", features = ["anyhow", "extension-module"] } +pyo3 = { version = "0.16.4", features = ["extension-module"] } rust_decimal = "1.23.1" time = "0.3.9" diff --git a/python/crates/macros/src/pyenum.rs b/python/crates/macros/src/pyenum.rs index bbf8b9061b..fe4e7eb19b 100644 --- a/python/crates/macros/src/pyenum.rs +++ b/python/crates/macros/src/pyenum.rs @@ -16,7 +16,7 @@ struct EnumItem { fields: Fields, #[darling(default)] - from: Option, + remote: Option, } #[derive(FromDeriveInput)] @@ -25,11 +25,23 @@ struct EnumArgs { ident: Ident, data: Data, - from: TypePath, + remote: TypePath, + #[darling(default)] + from: Option, + #[darling(default)] + into: Option, } pub(crate) fn generate(args: DeriveInput) -> GeneratorResult { - let EnumArgs { ident, data, from } = EnumArgs::from_derive_input(&args)?; + let EnumArgs { + ident, + data, + remote, + from, + into, + } = EnumArgs::from_derive_input(&args)?; + let from = from.unwrap_or(true); + let into = into.unwrap_or(true); let e = match data { Data::Enum(e) => e, @@ -49,32 +61,47 @@ pub(crate) fn generate(args: DeriveInput) -> GeneratorResult { } let item_ident = &variant.ident; - let remote_ident = variant.from.as_ref().unwrap_or(&variant.ident); + let remote_ident = variant.remote.as_ref().unwrap_or(&variant.ident); from_remote.push(quote! { - #from::#remote_ident => #ident::#item_ident, + #remote::#remote_ident => #ident::#item_ident, }); from_local.push(quote! { - #ident::#item_ident => #from::#remote_ident, + #ident::#item_ident => #remote::#remote_ident, }); } - let expanded = quote! { - impl ::std::convert::From<#from> for #ident { - fn from(value: #from) -> #ident { - match value { - #(#from_remote)* + let impl_from = if from { + Some(quote! { + impl ::std::convert::From<#remote> for #ident { + fn from(value: #remote) -> #ident { + match value { + #(#from_remote)* + } } } - } + }) + } else { + None + }; - impl ::std::convert::From<#ident> for #from { - fn from(value: #ident) -> #from { - match value { - #(#from_local)* + let impl_into = if into { + Some(quote! { + impl ::std::convert::From<#ident> for #remote { + fn from(value: #ident) -> #remote { + match value { + #(#from_local)* + } } } - } + }) + } else { + None + }; + + let expanded = quote! { + #impl_from + #impl_into }; Ok(expanded) diff --git a/python/crates/macros/src/pyobject.rs b/python/crates/macros/src/pyobject.rs index 09d0fcb9eb..6f42ebf4b2 100644 --- a/python/crates/macros/src/pyobject.rs +++ b/python/crates/macros/src/pyobject.rs @@ -23,11 +23,15 @@ struct ObjectArgs { ident: Ident, data: Data, - from: TypePath, + remote: TypePath, } pub(crate) fn generate(args: DeriveInput) -> GeneratorResult { - let ObjectArgs { ident, data, from } = ObjectArgs::from_derive_input(&args)?; + let ObjectArgs { + ident, + data, + remote, + } = ObjectArgs::from_derive_input(&args)?; let s = match data { Data::Struct(s) => s, @@ -84,10 +88,10 @@ pub(crate) fn generate(args: DeriveInput) -> GeneratorResult { #(#getters)* } - impl ::std::convert::TryFrom<#from> for #ident { + impl ::std::convert::TryFrom<#remote> for #ident { type Error = ::pyo3::PyErr; - fn try_from(value: #from) -> ::std::result::Result { + fn try_from(value: #remote) -> ::std::result::Result { use ::std::convert::TryInto; use ::std::iter::Iterator; diff --git a/python/pysrc/longbridge/openapi.pyi b/python/pysrc/longbridge/openapi.pyi index 466dd918e4..1d70edeeff 100644 --- a/python/pysrc/longbridge/openapi.pyi +++ b/python/pysrc/longbridge/openapi.pyi @@ -874,9 +874,9 @@ class TradeDirection: Trade direction """ - class Nature(TradeDirection): + class Neutral(TradeDirection): """ - Nature + Neutral """ class Down(TradeDirection): @@ -2585,7 +2585,7 @@ class StockPosition: Stock name """ - quality: Decimal + quantity: Decimal """ The number of holdings """ diff --git a/python/src/config.rs b/python/src/config.rs index 9b681efd89..638aabe80b 100644 --- a/python/src/config.rs +++ b/python/src/config.rs @@ -1,5 +1,7 @@ use pyo3::{prelude::*, types::PyType}; +use crate::error::ErrorNewType; + #[pyclass(name = "Config")] pub(crate) struct Config(pub(crate) longbridge::Config); @@ -29,6 +31,6 @@ impl Config { #[classmethod] fn from_env(_cls: &PyType) -> PyResult { - Ok(Self(longbridge::Config::from_env()?)) + Ok(Self(longbridge::Config::from_env().map_err(ErrorNewType)?)) } } diff --git a/python/src/error.rs b/python/src/error.rs new file mode 100644 index 0000000000..995f432ea7 --- /dev/null +++ b/python/src/error.rs @@ -0,0 +1,17 @@ +use pyo3::{exceptions::PyException, PyErr}; + +pyo3::create_exception!( + longbridge, + LongbridgeSDKException, + PyException, + "Some description." +); + +pub(crate) struct ErrorNewType(pub(crate) longbridge::Error); + +impl std::convert::From for PyErr { + #[inline] + fn from(err: ErrorNewType) -> PyErr { + LongbridgeSDKException::new_err(err.0.to_string()) + } +} diff --git a/python/src/lib.rs b/python/src/lib.rs index 3353a3708a..33dbd85bd9 100644 --- a/python/src/lib.rs +++ b/python/src/lib.rs @@ -1,5 +1,6 @@ mod config; mod decimal; +mod error; mod quote; mod time; mod trade; @@ -7,10 +8,16 @@ mod types; use pyo3::prelude::*; +use crate::error::LongbridgeSDKException; + #[pymodule] fn longbridge(py: Python<'_>, m: &PyModule) -> PyResult<()> { let openapi = PyModule::new(py, "openapi")?; + openapi.add( + "LongbridgeSDKException", + py.get_type::(), + )?; openapi.add_class::()?; openapi.add_class::()?; quote::register_types(openapi)?; diff --git a/python/src/quote/context.rs b/python/src/quote/context.rs index 7a4b579848..970d25000a 100644 --- a/python/src/quote/context.rs +++ b/python/src/quote/context.rs @@ -5,6 +5,7 @@ use pyo3::prelude::*; use crate::{ config::Config, + error::ErrorNewType, quote::{ push::handle_push_event, types::{ @@ -29,7 +30,8 @@ impl QuoteContext { if let Some(handler) = &handler { handle_push_event(handler, event); } - })?; + }) + .map_err(ErrorNewType)?; Ok(Self(ctx)) } @@ -42,20 +44,24 @@ impl QuoteContext { is_first_push: bool, ) -> PyResult<()> { self.0 - .subscribe(symbols, SubTypes(sub_types), is_first_push)?; + .subscribe(symbols, SubTypes(sub_types), is_first_push) + .map_err(ErrorNewType)?; Ok(()) } /// Unsubscribe fn unsubscribe(&self, symbols: Vec, sub_types: Vec) -> PyResult<()> { - self.0.unsubscribe(symbols, SubTypes(sub_types))?; + self.0 + .unsubscribe(symbols, SubTypes(sub_types)) + .map_err(ErrorNewType)?; Ok(()) } /// Get basic information of securities fn static_info(&self, symbols: Vec) -> PyResult> { self.0 - .static_info(symbols)? + .static_info(symbols) + .map_err(ErrorNewType)? .into_iter() .map(TryInto::try_into) .collect() @@ -64,7 +70,8 @@ impl QuoteContext { /// Get quote of securities fn quote(&self, symbols: Vec) -> PyResult> { self.0 - .quote(symbols)? + .quote(symbols) + .map_err(ErrorNewType)? .into_iter() .map(TryInto::try_into) .collect() @@ -73,7 +80,8 @@ impl QuoteContext { /// Get quote of option securities fn option_quote(&self, symbols: Vec) -> PyResult> { self.0 - .option_quote(symbols)? + .option_quote(symbols) + .map_err(ErrorNewType)? .into_iter() .map(TryInto::try_into) .collect() @@ -82,7 +90,8 @@ impl QuoteContext { /// Get quote of warrant securities fn warrant_quote(&self, symbols: Vec) -> PyResult> { self.0 - .warrant_quote(symbols)? + .warrant_quote(symbols) + .map_err(ErrorNewType)? .into_iter() .map(TryInto::try_into) .collect() @@ -90,43 +99,46 @@ impl QuoteContext { /// Get security depth fn depth(&self, symbol: String) -> PyResult { - self.0.depth(symbol)?.try_into() + self.0.depth(symbol).map_err(ErrorNewType)?.try_into() } /// Get security brokers fn brokers(&self, symbol: String) -> PyResult { - self.0.brokers(symbol)?.try_into() + self.0.brokers(symbol).map_err(ErrorNewType)?.try_into() } /// Get participants - pub fn participants(&self) -> PyResult> { + fn participants(&self) -> PyResult> { self.0 - .participants()? + .participants() + .map_err(ErrorNewType)? .into_iter() .map(TryInto::try_into) .collect() } /// Get security trades - pub fn trades(&self, symbol: String, count: usize) -> PyResult> { + fn trades(&self, symbol: String, count: usize) -> PyResult> { self.0 - .trades(symbol, count)? + .trades(symbol, count) + .map_err(ErrorNewType)? .into_iter() .map(TryInto::try_into) .collect() } /// Get security intraday - pub fn intraday(&self, symbol: String) -> PyResult> { + fn intraday(&self, symbol: String) -> PyResult> { self.0 - .intraday(symbol)? + .intraday(symbol) + .map_err(ErrorNewType)? .into_iter() .map(TryInto::try_into) .collect() } /// Get security candlesticks - pub fn candlesticks( + fn candlesticks( &self, symbol: String, period: Period, @@ -134,69 +146,76 @@ impl QuoteContext { adjust_type: AdjustType, ) -> PyResult> { self.0 - .candlesticks(symbol, period.into(), count, adjust_type.into())? + .candlesticks(symbol, period.into(), count, adjust_type.into()) + .map_err(ErrorNewType)? .into_iter() .map(TryInto::try_into) .collect() } /// Get option chain expiry date list - pub fn option_chain_expiry_date_list(&self, symbol: String) -> PyResult> { + fn option_chain_expiry_date_list(&self, symbol: String) -> PyResult> { Ok(self .0 - .option_chain_expiry_date_list(symbol)? + .option_chain_expiry_date_list(symbol) + .map_err(ErrorNewType)? .into_iter() .map(Into::into) .collect()) } /// Get option chain info by date - pub fn option_chain_info_by_date( + fn option_chain_info_by_date( &self, symbol: String, expiry_date: PyDateWrapper, ) -> PyResult> { self.0 - .option_chain_info_by_date(symbol, expiry_date.0)? + .option_chain_info_by_date(symbol, expiry_date.0) + .map_err(ErrorNewType)? .into_iter() .map(TryInto::try_into) .collect() } /// Get warrant issuers - pub fn warrant_issuers(&self) -> PyResult> { + fn warrant_issuers(&self) -> PyResult> { self.0 - .warrant_issuers()? + .warrant_issuers() + .map_err(ErrorNewType)? .into_iter() .map(TryInto::try_into) .collect() } /// Get trading session of the day - pub fn trading_session(&self) -> PyResult> { + fn trading_session(&self) -> PyResult> { self.0 - .trading_session()? + .trading_session() + .map_err(ErrorNewType)? .into_iter() .map(TryInto::try_into) .collect() } /// Get trading session of the day - pub fn trading_days( + fn trading_days( &self, market: Market, begin: PyDateWrapper, end: PyDateWrapper, ) -> PyResult { self.0 - .trading_days(market.into(), begin.0, end.0)? + .trading_days(market.into(), begin.0, end.0) + .map_err(ErrorNewType)? .try_into() } /// Get real-time quote fn realtime_quote(&self, symbols: Vec) -> PyResult> { self.0 - .realtime_quote(symbols)? + .realtime_quote(symbols) + .map_err(ErrorNewType)? .into_iter() .map(TryInto::try_into) .collect() @@ -204,19 +223,26 @@ impl QuoteContext { /// Get real-time depth fn realtime_depth(&self, symbol: String) -> PyResult { - self.0.realtime_depth(symbol)?.try_into() + self.0 + .realtime_depth(symbol) + .map_err(ErrorNewType)? + .try_into() } /// Get real-time brokers fn realtime_brokers(&self, symbol: String) -> PyResult { - self.0.realtime_brokers(symbol)?.try_into() + self.0 + .realtime_brokers(symbol) + .map_err(ErrorNewType)? + .try_into() } /// Get real-time trades #[args(count = 500)] fn realtime_trades(&self, symbol: String, count: usize) -> PyResult> { self.0 - .realtime_trades(symbol, count)? + .realtime_trades(symbol, count) + .map_err(ErrorNewType)? .into_iter() .map(TryInto::try_into) .collect() diff --git a/python/src/quote/types.rs b/python/src/quote/types.rs index 82662ca01a..a6457ce3bf 100644 --- a/python/src/quote/types.rs +++ b/python/src/quote/types.rs @@ -11,7 +11,7 @@ use crate::{ /// Derivative type #[pyclass] #[derive(PyEnum, Debug, Copy, Clone, Hash, Eq, PartialEq)] -#[py(from = "longbridge::quote::DerivativeType")] +#[py(remote = "longbridge::quote::DerivativeType")] pub(crate) enum DerivativeType { /// US stock options Option, @@ -21,7 +21,7 @@ pub(crate) enum DerivativeType { #[pyclass] #[derive(PyEnum, Debug, Copy, Clone, Hash, Eq, PartialEq)] -#[py(from = "longbridge::quote::TradeStatus")] +#[py(remote = "longbridge::quote::TradeStatus")] pub(crate) enum TradeStatus { /// Normal Normal, @@ -44,23 +44,23 @@ pub(crate) enum TradeStatus { /// Warrant To BeListed WarrantPrepareList, /// Warrant To BeListed - #[py(from = "SuspendTrade")] + #[py(remote = "SuspendTrade")] Suspend, } /// Trade session #[pyclass] #[derive(PyEnum, Debug, Copy, Clone, Hash, Eq, PartialEq)] -#[py(from = "longbridge::quote::TradeSession")] +#[py(remote = "longbridge::quote::TradeSession")] pub(crate) enum TradeSession { /// Trading - #[py(from = "NormalTrade")] + #[py(remote = "NormalTrade")] Normal, /// Pre-Trading - #[py(from = "PreTrade")] + #[py(remote = "PreTrade")] Pre, /// Post-Trading - #[py(from = "PostTrade")] + #[py(remote = "PostTrade")] Post, } @@ -101,10 +101,10 @@ impl From for SubFlags { /// Trade direction #[pyclass] #[derive(Debug, PyEnum, Copy, Clone, Hash, Eq, PartialEq)] -#[py(from = "longbridge::quote::TradeDirection")] +#[py(remote = "longbridge::quote::TradeDirection")] pub(crate) enum TradeDirection { - /// Nature - Nature, + /// Neutral + Neutral, /// Down Down, /// Up @@ -114,7 +114,7 @@ pub(crate) enum TradeDirection { /// Option type #[pyclass] #[derive(Debug, PyEnum, Copy, Clone, Hash, Eq, PartialEq)] -#[py(from = "longbridge::quote::OptionType")] +#[py(remote = "longbridge::quote::OptionType")] pub(crate) enum OptionType { /// Unknown Unknown, @@ -127,7 +127,7 @@ pub(crate) enum OptionType { /// Option direction #[pyclass] #[derive(Debug, PyEnum, Copy, Clone, Hash, Eq, PartialEq)] -#[py(from = "longbridge::quote::OptionDirection")] +#[py(remote = "longbridge::quote::OptionDirection")] pub(crate) enum OptionDirection { /// Unknown Unknown, @@ -140,7 +140,7 @@ pub(crate) enum OptionDirection { /// Warrant type #[pyclass] #[derive(Debug, PyEnum, Copy, Clone, Hash, Eq, PartialEq)] -#[py(from = "longbridge::quote::WarrantType")] +#[py(remote = "longbridge::quote::WarrantType")] pub(crate) enum WarrantType { /// Unknown Unknown, @@ -159,17 +159,23 @@ pub(crate) enum WarrantType { /// Candlestick period #[pyclass] #[allow(non_camel_case_types)] -#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)] +#[derive(Debug, PyEnum, Copy, Clone, Hash, Eq, PartialEq)] +#[py(remote = "longbridge::quote::Period", from = false)] pub(crate) enum Period { /// One Minute + #[py(remote = "OneMinute")] Min_1, /// Five Minutes + #[py(remote = "FiveMinute")] Min_5, /// Fifteen Minutes + #[py(remote = "FifteenMinute")] Min_15, /// Thirty Minutes + #[py(remote = "ThirtyMinute")] Min_30, /// Sixty Minutes + #[py(remote = "SixtyMinute")] Min_60, /// One Days Day, @@ -181,26 +187,10 @@ pub(crate) enum Period { Year, } -impl From for longbridge::quote::Period { - #[inline] - fn from(period: Period) -> Self { - match period { - Period::Min_1 => longbridge::quote::Period::OneMinute, - Period::Min_5 => longbridge::quote::Period::FiveMinute, - Period::Min_15 => longbridge::quote::Period::FifteenMinute, - Period::Min_30 => longbridge::quote::Period::ThirtyMinute, - Period::Min_60 => longbridge::quote::Period::SixtyMinute, - Period::Day => longbridge::quote::Period::Day, - Period::Week => longbridge::quote::Period::Week, - Period::Month => longbridge::quote::Period::Month, - Period::Year => longbridge::quote::Period::Year, - } - } -} - /// Candlestick adjustment type #[pyclass] -#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)] +#[derive(Debug, PyEnum, Copy, Clone, Hash, Eq, PartialEq)] +#[py(remote = "longbridge::quote::AdjustType")] pub(crate) enum AdjustType { /// Actual NoAdjust, @@ -208,20 +198,10 @@ pub(crate) enum AdjustType { ForwardAdjust, } -impl From for longbridge::quote::AdjustType { - #[inline] - fn from(ty: AdjustType) -> Self { - match ty { - AdjustType::NoAdjust => longbridge::quote::AdjustType::NoAdjust, - AdjustType::ForwardAdjust => longbridge::quote::AdjustType::ForwardAdjust, - } - } -} - /// The basic information of securities #[pyclass] #[derive(Debug, PyObject)] -#[py(from = "longbridge::quote::SecurityStaticInfo")] +#[py(remote = "longbridge::quote::SecurityStaticInfo")] pub(crate) struct SecurityStaticInfo { /// Security code symbol: String, @@ -259,7 +239,7 @@ pub(crate) struct SecurityStaticInfo { /// Quote of US pre/post market #[pyclass] #[derive(Debug, PyObject, Copy, Clone)] -#[py(from = "longbridge::quote::PrePostQuote")] +#[py(remote = "longbridge::quote::PrePostQuote")] pub(crate) struct PrePostQuote { /// Latest price last_done: PyDecimal, @@ -280,7 +260,7 @@ pub(crate) struct PrePostQuote { /// Quote of securitity #[pyclass] #[derive(Debug, PyObject)] -#[py(from = "longbridge::quote::SecurityQuote")] +#[py(remote = "longbridge::quote::SecurityQuote")] pub(crate) struct SecurityQuote { /// Security code symbol: String, @@ -313,7 +293,7 @@ pub(crate) struct SecurityQuote { /// Quote of option #[pyclass] #[derive(Debug, PyObject)] -#[py(from = "longbridge::quote::OptionQuote")] +#[py(remote = "longbridge::quote::OptionQuote")] pub(crate) struct OptionQuote { /// Security code symbol: String, @@ -360,7 +340,7 @@ pub(crate) struct OptionQuote { /// Quote of warrant #[pyclass] #[derive(Debug, PyObject)] -#[py(from = "longbridge::quote::WarrantQuote")] +#[py(remote = "longbridge::quote::WarrantQuote")] pub(crate) struct WarrantQuote { /// Security code symbol: String, @@ -411,7 +391,7 @@ pub(crate) struct WarrantQuote { /// Depth #[pyclass] #[derive(Debug, PyObject, Copy, Clone)] -#[py(from = "longbridge::quote::Depth")] +#[py(remote = "longbridge::quote::Depth")] pub(crate) struct Depth { /// Position position: i32, @@ -426,7 +406,7 @@ pub(crate) struct Depth { /// Security depth #[pyclass] #[derive(Debug, PyObject)] -#[py(from = "longbridge::quote::SecurityDepth")] +#[py(remote = "longbridge::quote::SecurityDepth")] pub(crate) struct SecurityDepth { /// Ask depth #[py(array)] @@ -439,7 +419,7 @@ pub(crate) struct SecurityDepth { /// Brokers #[pyclass] #[derive(Debug, PyObject, Clone)] -#[py(from = "longbridge::quote::Brokers")] +#[py(remote = "longbridge::quote::Brokers")] pub(crate) struct Brokers { /// Position position: i32, @@ -450,7 +430,7 @@ pub(crate) struct Brokers { /// Security brokers #[pyclass] #[derive(Debug, PyObject)] -#[py(from = "longbridge::quote::SecurityBrokers")] +#[py(remote = "longbridge::quote::SecurityBrokers")] pub(crate) struct SecurityBrokers { /// Ask brokers #[py(array)] @@ -463,7 +443,7 @@ pub(crate) struct SecurityBrokers { /// Participant info #[pyclass] #[derive(Debug, PyObject)] -#[py(from = "longbridge::quote::ParticipantInfo")] +#[py(remote = "longbridge::quote::ParticipantInfo")] pub(crate) struct ParticipantInfo { /// Broker IDs broker_ids: Vec, @@ -478,7 +458,7 @@ pub(crate) struct ParticipantInfo { /// Trade #[pyclass] #[derive(Debug, PyObject, Clone)] -#[py(from = "longbridge::quote::Trade")] +#[py(remote = "longbridge::quote::Trade")] pub(crate) struct Trade { /// Price price: PyDecimal, @@ -497,7 +477,7 @@ pub(crate) struct Trade { /// Intraday line #[pyclass] #[derive(Debug, PyObject)] -#[py(from = "longbridge::quote::IntradayLine")] +#[py(remote = "longbridge::quote::IntradayLine")] pub(crate) struct IntradayLine { /// Close price of the minute price: PyDecimal, @@ -514,7 +494,7 @@ pub(crate) struct IntradayLine { /// Candlestick #[pyclass] #[derive(Debug, PyObject)] -#[py(from = "longbridge::quote::Candlestick")] +#[py(remote = "longbridge::quote::Candlestick")] pub(crate) struct Candlestick { /// Close price close: PyDecimal, @@ -535,7 +515,7 @@ pub(crate) struct Candlestick { /// Strike price info #[pyclass] #[derive(Debug, PyObject)] -#[py(from = "longbridge::quote::StrikePriceInfo")] +#[py(remote = "longbridge::quote::StrikePriceInfo")] pub(crate) struct StrikePriceInfo { /// Strike price price: PyDecimal, @@ -550,7 +530,7 @@ pub(crate) struct StrikePriceInfo { /// Issuer info #[pyclass] #[derive(Debug, PyObject)] -#[py(from = "longbridge::quote::IssuerInfo")] +#[py(remote = "longbridge::quote::IssuerInfo")] pub(crate) struct IssuerInfo { /// Issuer ID issuer_id: i32, @@ -565,7 +545,7 @@ pub(crate) struct IssuerInfo { /// The information of trading session #[pyclass] #[derive(Debug, PyObject, Copy, Clone)] -#[py(from = "longbridge::quote::TradingSessionInfo")] +#[py(remote = "longbridge::quote::TradingSessionInfo")] pub(crate) struct TradingSessionInfo { /// Being trading time begin_time: PyTimeWrapper, @@ -578,7 +558,7 @@ pub(crate) struct TradingSessionInfo { /// Market trading session #[pyclass] #[derive(Debug, PyObject)] -#[py(from = "longbridge::quote::MarketTradingSession")] +#[py(remote = "longbridge::quote::MarketTradingSession")] pub(crate) struct MarketTradingSession { /// Market market: Market, @@ -590,7 +570,7 @@ pub(crate) struct MarketTradingSession { /// Real-time quote #[pyclass] #[derive(PyObject, Debug, Clone)] -#[py(from = "longbridge::quote::RealtimeQuote")] +#[py(remote = "longbridge::quote::RealtimeQuote")] pub struct RealtimeQuote { /// Security code symbol: String, @@ -615,7 +595,7 @@ pub struct RealtimeQuote { /// Push real-time quote #[pyclass] #[derive(PyObject)] -#[py(from = "longbridge::quote::PushQuote")] +#[py(remote = "longbridge::quote::PushQuote")] #[derive(Debug, Clone)] pub struct PushQuote { /// Latest price @@ -641,7 +621,7 @@ pub struct PushQuote { /// Push real-time depth #[pyclass] #[derive(Debug, PyObject)] -#[py(from = "longbridge::quote::PushDepth")] +#[py(remote = "longbridge::quote::PushDepth")] pub(crate) struct PushDepth { /// Ask depth #[py(array)] @@ -654,7 +634,7 @@ pub(crate) struct PushDepth { /// Push real-time brokers #[pyclass] #[derive(Debug, PyObject)] -#[py(from = "longbridge::quote::PushBrokers")] +#[py(remote = "longbridge::quote::PushBrokers")] pub(crate) struct PushBrokers { /// Ask brokers #[py(array)] @@ -667,7 +647,7 @@ pub(crate) struct PushBrokers { /// Push real-time trades #[pyclass] #[derive(Debug, PyObject)] -#[py(from = "longbridge::quote::PushTrades")] +#[py(remote = "longbridge::quote::PushTrades")] pub struct PushTrades { /// Trades data #[py(array)] @@ -677,7 +657,7 @@ pub struct PushTrades { /// Market trading days #[pyclass] #[derive(Debug, PyObject)] -#[py(from = "longbridge::quote::MarketTradingDays")] +#[py(remote = "longbridge::quote::MarketTradingDays")] pub struct MarketTradingDays { /// Trading days #[py(array)] diff --git a/python/src/trade/context.rs b/python/src/trade/context.rs index 6ff314d4e4..822e119000 100644 --- a/python/src/trade/context.rs +++ b/python/src/trade/context.rs @@ -13,6 +13,7 @@ use pyo3::{pyclass, pymethods, PyObject, PyResult}; use crate::{ config::Config, decimal::PyDecimal, + error::ErrorNewType, time::{PyDateWrapper, PyOffsetDateTimeWrapper}, trade::{ push::handle_push_event, @@ -36,24 +37,29 @@ impl TradeContext { if let Some(handler) = &handler { handle_push_event(handler, event); } - })?; + }) + .map_err(ErrorNewType)?; Ok(Self(ctx)) } /// Subscribe fn subscribe(&self, topics: Vec) -> PyResult<()> { - self.0.subscribe(topics.into_iter().map(Into::into))?; + self.0 + .subscribe(topics.into_iter().map(Into::into)) + .map_err(ErrorNewType)?; Ok(()) } /// Unsubscribe fn unsubscribe(&self, topics: Vec) -> PyResult<()> { - self.0.unsubscribe(topics.into_iter().map(Into::into))?; + self.0 + .unsubscribe(topics.into_iter().map(Into::into)) + .map_err(ErrorNewType)?; Ok(()) } /// Get history executions - pub fn history_executions( + fn history_executions( &self, symbol: Option, start_at: Option, @@ -72,14 +78,15 @@ impl TradeContext { } self.0 - .history_executions(Some(opts))? + .history_executions(Some(opts)) + .map_err(ErrorNewType)? .into_iter() .map(TryInto::try_into) .collect() } /// Get today executions - pub fn today_executions( + fn today_executions( &self, symbol: Option, order_id: Option, @@ -94,15 +101,16 @@ impl TradeContext { } self.0 - .today_executions(Some(opts))? + .today_executions(Some(opts)) + .map_err(ErrorNewType)? .into_iter() .map(TryInto::try_into) .collect() } /// Get history orders - #[args(default = "[]")] - pub fn history_orders( + #[args(status = "vec![]")] + fn history_orders( &self, symbol: Option, status: Vec, @@ -131,14 +139,15 @@ impl TradeContext { } self.0 - .history_orders(Some(opts))? + .history_orders(Some(opts)) + .map_err(ErrorNewType)? .into_iter() .map(TryInto::try_into) .collect() } /// Get today orders - pub fn today_orders( + fn today_orders( &self, symbol: Option, status: Vec, @@ -159,7 +168,8 @@ impl TradeContext { } self.0 - .today_orders(Some(opts))? + .today_orders(Some(opts)) + .map_err(ErrorNewType)? .into_iter() .map(TryInto::try_into) .collect() @@ -167,7 +177,7 @@ impl TradeContext { /// Replace order #[allow(clippy::too_many_arguments)] - pub fn replace_order( + fn replace_order( &self, order_id: String, quantity: PyDecimal, @@ -199,13 +209,13 @@ impl TradeContext { opts = opts.remark(remark); } - self.0.replace_order(opts)?; + self.0.replace_order(opts).map_err(ErrorNewType)?; Ok(()) } /// Submit order #[allow(clippy::too_many_arguments)] - pub fn submit_order( + fn submit_order( &self, symbol: String, order_type: OrderType, @@ -254,26 +264,27 @@ impl TradeContext { opts = opts.remark(remark); } - self.0.submit_order(opts)?.try_into() + self.0.submit_order(opts).map_err(ErrorNewType)?.try_into() } /// Withdraw order - pub fn withdraw_order(&self, order_id: String) -> PyResult<()> { - self.0.withdraw_order(order_id)?; + fn withdraw_order(&self, order_id: String) -> PyResult<()> { + self.0.withdraw_order(order_id).map_err(ErrorNewType)?; Ok(()) } /// Get account balance - pub fn account_balance(&self) -> PyResult> { + fn account_balance(&self) -> PyResult> { self.0 - .account_balance()? + .account_balance() + .map_err(ErrorNewType)? .into_iter() .map(TryInto::try_into) .collect::>>() } /// Get cash flow - pub fn cash_flow( + fn cash_flow( &self, start_at: PyOffsetDateTimeWrapper, end_at: PyOffsetDateTimeWrapper, @@ -298,7 +309,8 @@ impl TradeContext { } self.0 - .cash_flow(opts)? + .cash_flow(opts) + .map_err(ErrorNewType)? .into_iter() .map(TryInto::try_into) .collect::>>() @@ -306,17 +318,19 @@ impl TradeContext { /// Get fund positions #[args(symbols = "vec![]")] - pub fn fund_positions(&self, symbols: Vec) -> PyResult { + fn fund_positions(&self, symbols: Vec) -> PyResult { self.0 - .fund_positions(GetFundPositionsOptions::new().symbols(symbols))? + .fund_positions(GetFundPositionsOptions::new().symbols(symbols)) + .map_err(ErrorNewType)? .try_into() } /// Get stock positions #[args(symbols = "vec![]")] - pub fn stock_positions(&self, symbols: Vec) -> PyResult { + fn stock_positions(&self, symbols: Vec) -> PyResult { self.0 - .stock_positions(GetStockPositionsOptions::new().symbols(symbols))? + .stock_positions(GetStockPositionsOptions::new().symbols(symbols)) + .map_err(ErrorNewType)? .try_into() } } diff --git a/python/src/trade/types.rs b/python/src/trade/types.rs index faf946923f..3b769d559e 100644 --- a/python/src/trade/types.rs +++ b/python/src/trade/types.rs @@ -9,8 +9,8 @@ use crate::{ /// Topic type #[pyclass] #[derive(Debug, PyEnum, Copy, Clone, Hash, Eq, PartialEq)] -#[py(from = "longbridge::trade::TopicType")] -pub enum TopicType { +#[py(remote = "longbridge::trade::TopicType")] +pub(crate) enum TopicType { /// Private notification for trade Private, } @@ -18,7 +18,7 @@ pub enum TopicType { /// Trade #[pyclass] #[derive(Debug, PyObject)] -#[py(from = "longbridge::trade::Execution")] +#[py(remote = "longbridge::trade::Execution")] pub(crate) struct Execution { /// Order ID order_id: String, @@ -36,7 +36,7 @@ pub(crate) struct Execution { #[pyclass] #[derive(Debug, PyEnum, Copy, Clone, Hash, Eq, PartialEq)] -#[py(from = "longbridge::trade::OrderStatus")] +#[py(remote = "longbridge::trade::OrderStatus")] pub(crate) enum OrderStatus { /// Unknown Unknown, @@ -78,7 +78,7 @@ pub(crate) enum OrderStatus { #[pyclass] #[derive(Debug, PyEnum, Copy, Clone, Hash, Eq, PartialEq)] -#[py(from = "longbridge::trade::OrderSide")] +#[py(remote = "longbridge::trade::OrderSide")] pub(crate) enum OrderSide { /// Unknown Unknown, @@ -90,7 +90,7 @@ pub(crate) enum OrderSide { #[pyclass] #[derive(Debug, PyEnum, Copy, Clone, Hash, Eq, PartialEq)] -#[py(from = "longbridge::trade::OrderType")] +#[py(remote = "longbridge::trade::OrderType")] #[allow(clippy::upper_case_acronyms)] pub(crate) enum OrderType { /// Unknown @@ -124,7 +124,7 @@ pub(crate) enum OrderType { /// Order tag #[pyclass] #[derive(Debug, PyEnum, Copy, Clone, Hash, Eq, PartialEq)] -#[py(from = "longbridge::trade::OrderTag")] +#[py(remote = "longbridge::trade::OrderTag")] pub(crate) enum OrderTag { /// Unknown Unknown, @@ -139,7 +139,7 @@ pub(crate) enum OrderTag { /// Time in force type #[pyclass] #[derive(Debug, PyEnum, Copy, Clone, Hash, Eq, PartialEq)] -#[py(from = "longbridge::trade::TimeInForceType")] +#[py(remote = "longbridge::trade::TimeInForceType")] pub(crate) enum TimeInForceType { /// Unknown Unknown, @@ -154,8 +154,8 @@ pub(crate) enum TimeInForceType { /// Trigger status #[pyclass] #[derive(Debug, PyEnum, Copy, Clone, Hash, Eq, PartialEq)] -#[py(from = "longbridge::trade::TriggerStatus")] -pub enum TriggerStatus { +#[py(remote = "longbridge::trade::TriggerStatus")] +pub(crate) enum TriggerStatus { /// Unknown Unknown, /// Deactive @@ -169,8 +169,8 @@ pub enum TriggerStatus { /// Enable or disable outside regular trading hours #[pyclass] #[derive(Debug, PyEnum, Copy, Clone, Hash, Eq, PartialEq)] -#[py(from = "longbridge::trade::OutsideRTH")] -pub enum OutsideRTH { +#[py(remote = "longbridge::trade::OutsideRTH")] +pub(crate) enum OutsideRTH { /// Unknown Unknown, /// Regular trading hour only @@ -182,7 +182,7 @@ pub enum OutsideRTH { /// Order #[pyclass] #[derive(Debug, PyObject)] -#[py(from = "longbridge::trade::Order")] +#[py(remote = "longbridge::trade::Order")] pub(crate) struct Order { /// Order ID order_id: String, @@ -252,7 +252,7 @@ pub(crate) struct Order { /// Order changed message #[pyclass] #[derive(Debug, PyObject)] -#[py(from = "longbridge::trade::PushOrderChanged")] +#[py(remote = "longbridge::trade::PushOrderChanged")] pub(crate) struct PushOrderChanged { /// Order side side: OrderSide, @@ -309,7 +309,7 @@ pub(crate) struct PushOrderChanged { /// Response for withdraw order request #[pyclass] #[derive(Debug, PyObject)] -#[py(from = "longbridge::trade::SubmitOrderResponse")] +#[py(remote = "longbridge::trade::SubmitOrderResponse")] pub(crate) struct SubmitOrderResponse { /// Order id order_id: String, @@ -318,7 +318,7 @@ pub(crate) struct SubmitOrderResponse { /// Account balance #[pyclass] #[derive(Debug, PyObject, Clone)] -#[py(from = "longbridge::trade::CashInfo")] +#[py(remote = "longbridge::trade::CashInfo")] pub(crate) struct CashInfo { /// Withdraw cash withdraw_cash: PyDecimal, @@ -335,7 +335,7 @@ pub(crate) struct CashInfo { /// Account balance #[pyclass] #[derive(Debug, PyObject)] -#[py(from = "longbridge::trade::AccountBalance")] +#[py(remote = "longbridge::trade::AccountBalance")] pub(crate) struct AccountBalance { /// Total cash total_cash: PyDecimal, @@ -356,8 +356,8 @@ pub(crate) struct AccountBalance { #[pyclass] #[derive(Debug, PyEnum, Copy, Clone, Hash, Eq, PartialEq)] -#[py(from = "longbridge::trade::BalanceType")] -pub enum BalanceType { +#[py(remote = "longbridge::trade::BalanceType")] +pub(crate) enum BalanceType { /// Unknown Unknown, /// Limit Order @@ -370,8 +370,8 @@ pub enum BalanceType { #[pyclass] #[derive(Debug, PyEnum, Copy, Clone, Hash, Eq, PartialEq)] -#[py(from = "longbridge::trade::CashFlowDirection")] -pub enum CashFlowDirection { +#[py(remote = "longbridge::trade::CashFlowDirection")] +pub(crate) enum CashFlowDirection { /// Unknown Unknown, /// Out @@ -383,7 +383,7 @@ pub enum CashFlowDirection { /// Account balance #[pyclass] #[derive(Debug, PyObject)] -#[py(from = "longbridge::trade::CashFlow")] +#[py(remote = "longbridge::trade::CashFlow")] pub(crate) struct CashFlow { /// Cash flow name transaction_flow_name: String, @@ -406,16 +406,16 @@ pub(crate) struct CashFlow { /// Fund positions response #[pyclass] #[derive(Debug, PyObject)] -#[py(from = "longbridge::trade::FundPositionsResponse")] +#[py(remote = "longbridge::trade::FundPositionsResponse")] pub(crate) struct FundPositionsResponse { #[py(array)] - pub channels: Vec, + channels: Vec, } /// Fund position channel #[pyclass] #[derive(Debug, PyObject, Clone)] -#[py(from = "longbridge::trade::FundPositionChannel")] +#[py(remote = "longbridge::trade::FundPositionChannel")] pub(crate) struct FundPositionChannel { /// Account type account_channel: String, @@ -427,7 +427,7 @@ pub(crate) struct FundPositionChannel { /// Fund position #[pyclass] #[derive(Debug, PyObject, Clone)] -#[py(from = "longbridge::trade::FundPosition")] +#[py(remote = "longbridge::trade::FundPosition")] pub(crate) struct FundPosition { /// Fund ISIN code symbol: String, @@ -442,22 +442,22 @@ pub(crate) struct FundPosition { /// Net cost cost_net_asset_value: PyDecimal, /// Holding units - pub holding_units: PyDecimal, + holding_units: PyDecimal, } /// Stock positions response #[pyclass] #[derive(Debug, PyObject, Clone)] -#[py(from = "longbridge::trade::StockPositionsResponse")] +#[py(remote = "longbridge::trade::StockPositionsResponse")] pub(crate) struct StockPositionsResponse { #[py(array)] - pub channels: Vec, + channels: Vec, } /// Stock position channel #[pyclass] #[derive(Debug, PyObject, Clone)] -#[py(from = "longbridge::trade::StockPositionChannel")] +#[py(remote = "longbridge::trade::StockPositionChannel")] pub(crate) struct StockPositionChannel { /// Account type account_channel: String, @@ -469,14 +469,14 @@ pub(crate) struct StockPositionChannel { /// Stock position #[pyclass] #[derive(Debug, PyObject, Clone)] -#[py(from = "longbridge::trade::StockPosition")] +#[py(remote = "longbridge::trade::StockPosition")] pub(crate) struct StockPosition { /// Stock code symbol: String, /// Stock name symbol_name: String, /// The number of holdings - quality: PyDecimal, + quantity: PyDecimal, /// Available quantity available_quality: PyDecimal, /// Currency diff --git a/python/src/types.rs b/python/src/types.rs index 6c864ba97e..77cfc6684d 100644 --- a/python/src/types.rs +++ b/python/src/types.rs @@ -3,7 +3,7 @@ use pyo3::prelude::*; #[pyclass] #[derive(Debug, PyEnum, Copy, Clone, Hash, Eq, PartialEq)] -#[py(from = "longbridge::Market")] +#[py(remote = "longbridge::Market")] pub(crate) enum Market { /// Unknown Unknown, diff --git a/rust/Cargo.toml b/rust/Cargo.toml index e7e3e84460..493732de2b 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -27,7 +27,6 @@ tokio = { version = "1.18.2", features = [ ] } rust_decimal = { version = "1.23.1", features = ["serde-with-str"] } num_enum = "0.5.7" -anyhow = "1.0.57" prost = "0.10.3" tracing = "0.1.34" bitflags = "1.3.2" diff --git a/rust/crates/httpclient/src/request.rs b/rust/crates/httpclient/src/request.rs index d101c27f4b..6102bc7981 100644 --- a/rust/crates/httpclient/src/request.rs +++ b/rust/crates/httpclient/src/request.rs @@ -143,6 +143,7 @@ where http_cli .execute(request) .await? + .error_for_status()? .text() .await .map_err(HttpClientError::from) diff --git a/rust/examples/pull_quotes.rs b/rust/examples/pull_quotes.rs index b9ed2e221f..887f1a9556 100644 --- a/rust/examples/pull_quotes.rs +++ b/rust/examples/pull_quotes.rs @@ -1,7 +1,6 @@ use std::sync::Arc; -use anyhow::Result; -use longbridge::{Config, QuoteContext}; +use longbridge::{Config, QuoteContext, Result}; #[tokio::main] async fn main() -> Result<()> { diff --git a/rust/examples/submit_order.rs b/rust/examples/submit_order.rs index 0adb07b81b..c5b2f835ec 100644 --- a/rust/examples/submit_order.rs +++ b/rust/examples/submit_order.rs @@ -1,10 +1,9 @@ use std::sync::Arc; -use anyhow::Result; use longbridge::{ decimal, trade::{OrderSide, OrderType, SubmitOrderOptions, TimeInForceType}, - Config, TradeContext, + Config, Result, TradeContext, }; #[tokio::main] diff --git a/rust/examples/subscribe_quote.rs b/rust/examples/subscribe_quote.rs index 1aec978de3..ecdd5faa6b 100644 --- a/rust/examples/subscribe_quote.rs +++ b/rust/examples/subscribe_quote.rs @@ -1,7 +1,6 @@ use std::sync::Arc; -use anyhow::Result; -use longbridge::{quote::SubFlags, Config, QuoteContext}; +use longbridge::{quote::SubFlags, Config, QuoteContext, Result}; #[tokio::main] async fn main() -> Result<()> { diff --git a/rust/src/blocking/quote.rs b/rust/src/blocking/quote.rs index 2c2d97a6e2..ffdbc27370 100644 --- a/rust/src/blocking/quote.rs +++ b/rust/src/blocking/quote.rs @@ -1,6 +1,5 @@ use std::sync::Arc; -use anyhow::Result; use time::Date; use crate::{ @@ -11,7 +10,7 @@ use crate::{ SecurityDepth, SecurityQuote, SecurityStaticInfo, StrikePriceInfo, SubFlags, Trade, WarrantQuote, }, - Config, Market, QuoteContext, + Config, Market, QuoteContext, Result, }; /// Quote context diff --git a/rust/src/blocking/runtime.rs b/rust/src/blocking/runtime.rs index 3ccc21d25e..336dfecb5c 100644 --- a/rust/src/blocking/runtime.rs +++ b/rust/src/blocking/runtime.rs @@ -1,10 +1,9 @@ use std::{sync::Arc, thread}; -use anyhow::Result; use futures_util::{future::BoxFuture, Future}; use tokio::sync::mpsc; -use crate::blocking::BlockingError; +use crate::{blocking::BlockingError, Result}; const THREAD_NAME: &str = "longbridge-sync-runtime"; diff --git a/rust/src/blocking/trade.rs b/rust/src/blocking/trade.rs index e4407d5ba9..36cae76d7d 100644 --- a/rust/src/blocking/trade.rs +++ b/rust/src/blocking/trade.rs @@ -1,7 +1,5 @@ use std::sync::Arc; -use anyhow::Result; - use crate::{ blocking::runtime::BlockingRuntime, trade::{ @@ -11,7 +9,7 @@ use crate::{ PushEvent, ReplaceOrderOptions, StockPositionsResponse, SubmitOrderOptions, SubmitOrderResponse, TopicType, TradeContext, }, - Config, + Config, Result, }; /// Trade context diff --git a/rust/src/config.rs b/rust/src/config.rs index b71822313f..a6b9263e06 100644 --- a/rust/src/config.rs +++ b/rust/src/config.rs @@ -1,6 +1,7 @@ -use anyhow::Result; use longbridge_httpcli::{HttpClient, HttpClientConfig}; +use crate::error::Result; + const QUOTE_WS_URL: &str = "wss://openapi-quote.longbridgeapp.com"; const TRADE_WS_URL: &str = "wss://openapi-trade.longbridgeapp.com"; diff --git a/rust/src/error.rs b/rust/src/error.rs new file mode 100644 index 0000000000..fa81d6947f --- /dev/null +++ b/rust/src/error.rs @@ -0,0 +1,59 @@ +use std::fmt::Display; + +use longbridge_httpcli::HttpClientError; +use longbridge_wscli::WsClientError; + +/// Longbridge OpenAPI SDK error type +#[derive(Debug, thiserror::Error)] +pub enum Error { + /// Decode Protobuf error + #[error(transparent)] + DecodeProtobuf(#[from] prost::DecodeError), + + /// Decode JSON error + #[error(transparent)] + DecodeJSON(#[from] serde_json::Error), + + /// Parse field + #[error("parse field: {name}: {error}")] + ParseField { + /// Field name + name: &'static str, + + /// Error detail + error: String, + }, + + /// Unknown command + #[error("unknown command: {0}")] + UnknownCommand( + /// Command code + u8, + ), + + /// HTTP client error + #[error(transparent)] + HttpClient(#[from] HttpClientError), + + /// Websocket client error + #[error(transparent)] + WsClient(#[from] WsClientError), + + /// Blocking error + #[cfg(feature = "blocking")] + #[error(transparent)] + Blocking(#[from] crate::blocking::BlockingError), +} + +impl Error { + #[inline] + pub(crate) fn parse_field_error(name: &'static str, error: impl Display) -> Self { + Self::ParseField { + name, + error: error.to_string(), + } + } +} + +/// Longbridge OpenAPI SDK result type +pub type Result = ::std::result::Result; diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 23bcc56bfb..b17baf2f4e 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -8,6 +8,7 @@ mod macros; mod config; +mod error; mod types; #[cfg(feature = "blocking")] @@ -17,6 +18,7 @@ pub mod quote; pub mod trade; pub use config::Config; +pub use error::{Error, Result}; pub use quote::QuoteContext; pub use rust_decimal::Decimal; pub use trade::TradeContext; diff --git a/rust/src/quote/context.rs b/rust/src/quote/context.rs index 992142e7f2..66df85f235 100644 --- a/rust/src/quote/context.rs +++ b/rust/src/quote/context.rs @@ -1,6 +1,5 @@ use std::{sync::Arc, time::Duration}; -use anyhow::Result; use longbridge_proto::quote; use longbridge_wscli::WsClientError; use time::Date; @@ -17,7 +16,7 @@ use crate::{ OptionQuote, ParticipantInfo, Period, PushEvent, RealtimeQuote, SecurityBrokers, SecurityDepth, SecurityQuote, SecurityStaticInfo, StrikePriceInfo, Trade, WarrantQuote, }, - Config, Market, + Config, Error, Market, Result, }; const PARTICIPANT_INFO_CACHE_TIMEOUT: Duration = Duration::from_secs(30 * 60); @@ -123,7 +122,7 @@ impl QuoteContext { pub async fn subscribe( &self, symbols: I, - sub_types: SubFlags, + sub_types: impl Into, is_first_push: bool, ) -> Result<()> where @@ -134,7 +133,7 @@ impl QuoteContext { self.command_tx .send(Command::Subscribe { symbols: symbols.into_iter().map(Into::into).collect(), - sub_types, + sub_types: sub_types.into(), is_first_push, reply_tx, }) @@ -166,7 +165,7 @@ impl QuoteContext { /// # Ok::<_, anyhow::Error>(()) /// # }); /// ``` - pub async fn unsubscribe(&self, symbols: I, sub_types: SubFlags) -> Result<()> + pub async fn unsubscribe(&self, symbols: I, sub_types: impl Into) -> Result<()> where I: IntoIterator, T: Into, @@ -175,7 +174,7 @@ impl QuoteContext { self.command_tx .send(Command::Unsubscribe { symbols: symbols.into_iter().map(Into::into).collect(), - sub_types, + sub_types: sub_types.into(), reply_tx, }) .map_err(|_| WsClientError::ClientClosed)?; @@ -609,7 +608,9 @@ impl QuoteContext { .await?; resp.expiry_date .iter() - .map(|value| parse_date(value)) + .map(|value| { + parse_date(value).map_err(|err| Error::parse_field_error("date", err)) + }) .collect::>>() }) .await @@ -779,12 +780,16 @@ impl QuoteContext { let trading_days = resp .trade_day .iter() - .map(|value| parse_date(value)) + .map(|value| { + parse_date(value).map_err(|err| Error::parse_field_error("trade_day", err)) + }) .collect::>>()?; let half_trading_days = resp .half_trade_day .iter() - .map(|value| parse_date(value)) + .map(|value| { + parse_date(value).map_err(|err| Error::parse_field_error("half_trade_day", err)) + }) .collect::>>()?; Ok(MarketTradingDays { trading_days, diff --git a/rust/src/quote/core.rs b/rust/src/quote/core.rs index b392a66e13..567c875ff4 100644 --- a/rust/src/quote/core.rs +++ b/rust/src/quote/core.rs @@ -1,6 +1,5 @@ use std::{collections::HashMap, sync::Arc, time::Duration}; -use anyhow::Result; use longbridge_proto::quote::{SubscribeRequest, SubscriptionResponse, UnsubscribeRequest}; use longbridge_wscli::{CodecType, Platform, ProtocolVersion, WsClient, WsEvent, WsSession}; use tokio::sync::{mpsc, oneshot}; @@ -10,7 +9,7 @@ use crate::{ cmd_code, store::Store, sub_flags::SubFlags, PushEvent, RealtimeQuote, SecurityBrokers, SecurityDepth, Trade, }, - Config, + Config, Result, }; const RECONNECT_DELAY: Duration = Duration::from_secs(2); diff --git a/rust/src/quote/push_types.rs b/rust/src/quote/push_types.rs index 4933f52f55..542b1ba3bc 100644 --- a/rust/src/quote/push_types.rs +++ b/rust/src/quote/push_types.rs @@ -1,10 +1,12 @@ -use anyhow::{Context, Result}; use longbridge_proto::quote::{self, TradeSession, TradeStatus}; use prost::Message; use rust_decimal::Decimal; use time::OffsetDateTime; -use crate::quote::{cmd_code, Brokers, Depth, Trade}; +use crate::{ + quote::{cmd_code, Brokers, Depth, Trade}, + Error, Result, +}; /// Quote message #[derive(Debug, Clone)] @@ -96,13 +98,11 @@ pub struct PushEvent { impl PushEvent { pub(crate) fn parse(command_code: u8, data: &[u8]) -> Result { match command_code { - cmd_code::PUSH_REALTIME_QUOTE => parse_push_quote(data).context("decode push quote"), - cmd_code::PUSH_REALTIME_DEPTH => parse_push_depth(data).context("decode push depth"), - cmd_code::PUSH_REALTIME_BROKERS => { - parse_push_brokers(data).context("decode push brokers") - } - cmd_code::PUSH_REALTIME_TRADES => parse_push_trade(data).context("decode push trades"), - _ => anyhow::bail!("unknown command: {}", command_code), + cmd_code::PUSH_REALTIME_QUOTE => parse_push_quote(data), + cmd_code::PUSH_REALTIME_DEPTH => parse_push_depth(data), + cmd_code::PUSH_REALTIME_BROKERS => parse_push_brokers(data), + cmd_code::PUSH_REALTIME_TRADES => parse_push_trade(data), + _ => Err(Error::UnknownCommand(command_code)), } } } @@ -117,7 +117,8 @@ fn parse_push_quote(data: &[u8]) -> Result { open: push_quote.open.parse().unwrap_or_default(), high: push_quote.high.parse().unwrap_or_default(), low: push_quote.low.parse().unwrap_or_default(), - timestamp: OffsetDateTime::from_unix_timestamp(push_quote.timestamp)?, + timestamp: OffsetDateTime::from_unix_timestamp(push_quote.timestamp) + .map_err(|err| Error::parse_field_error("timestamp", err))?, volume: push_quote.volume, turnover: push_quote.turnover.parse().unwrap_or_default(), trade_status: TradeStatus::from_i32(push_quote.trade_status).unwrap_or_default(), diff --git a/rust/src/quote/types.rs b/rust/src/quote/types.rs index be3b1db3e6..efedbf6e4b 100644 --- a/rust/src/quote/types.rs +++ b/rust/src/quote/types.rs @@ -1,11 +1,10 @@ -use anyhow::{Error, Result}; use longbridge_proto::quote::{self, TradeSession, TradeStatus}; use num_enum::{FromPrimitive, IntoPrimitive, TryFromPrimitive}; use rust_decimal::Decimal; use strum_macros::EnumString; use time::{Date, OffsetDateTime, Time}; -use crate::{quote::utils::parse_date, Market}; +use crate::{quote::utils::parse_date, Error, Market, Result}; /// Depth #[derive(Debug, Clone)] @@ -23,7 +22,7 @@ pub struct Depth { impl TryFrom for Depth { type Error = Error; - fn try_from(depth: quote::Depth) -> Result { + fn try_from(depth: quote::Depth) -> Result { Ok(Self { position: depth.position, price: depth.price.parse().unwrap_or_default(), @@ -55,9 +54,9 @@ impl From for Brokers { #[derive(Debug, FromPrimitive, Copy, Clone, Hash, Eq, PartialEq)] #[repr(i32)] pub enum TradeDirection { - /// Nature + /// Neutral #[num_enum(default)] - Nature = 0, + Neutral = 0, /// Down Down = 1, /// Up @@ -115,11 +114,12 @@ pub struct Trade { impl TryFrom for Trade { type Error = Error; - fn try_from(trade: quote::Trade) -> Result { + fn try_from(trade: quote::Trade) -> Result { Ok(Self { price: trade.price.parse().unwrap_or_default(), volume: trade.volume, - timestamp: OffsetDateTime::from_unix_timestamp(trade.timestamp)?, + timestamp: OffsetDateTime::from_unix_timestamp(trade.timestamp) + .map_err(|err| Error::parse_field_error("timestamp", err))?, trade_type: trade.trade_type, direction: trade.direction.into(), trade_session: TradeSession::from_i32(trade.trade_session).unwrap_or_default(), @@ -175,7 +175,7 @@ pub struct SecurityStaticInfo { impl TryFrom for SecurityStaticInfo { type Error = Error; - fn try_from(resp: quote::StaticInfo) -> Result { + fn try_from(resp: quote::StaticInfo) -> Result { Ok(SecurityStaticInfo { symbol: resp.symbol, name_cn: resp.name_cn, @@ -245,10 +245,11 @@ pub struct PrePostQuote { impl TryFrom for PrePostQuote { type Error = Error; - fn try_from(quote: quote::PrePostQuote) -> Result { + fn try_from(quote: quote::PrePostQuote) -> Result { Ok(Self { last_done: quote.last_done.parse().unwrap_or_default(), - timestamp: OffsetDateTime::from_unix_timestamp(quote.timestamp)?, + timestamp: OffsetDateTime::from_unix_timestamp(quote.timestamp) + .map_err(|err| Error::parse_field_error("timestamp", err))?, volume: quote.volume, turnover: quote.turnover.parse().unwrap_or_default(), high: quote.high.parse().unwrap_or_default(), @@ -290,7 +291,7 @@ pub struct SecurityQuote { impl TryFrom for SecurityQuote { type Error = Error; - fn try_from(quote: quote::SecurityQuote) -> Result { + fn try_from(quote: quote::SecurityQuote) -> Result { Ok(Self { symbol: quote.symbol, last_done: quote.last_done.parse().unwrap_or_default(), @@ -298,7 +299,8 @@ impl TryFrom for SecurityQuote { open: quote.open.parse().unwrap_or_default(), high: quote.high.parse().unwrap_or_default(), low: quote.low.parse().unwrap_or_default(), - timestamp: OffsetDateTime::from_unix_timestamp(quote.timestamp)?, + timestamp: OffsetDateTime::from_unix_timestamp(quote.timestamp) + .map_err(|err| Error::parse_field_error("timestamp", err))?, volume: quote.volume, turnover: quote.turnover.parse().unwrap_or_default(), trade_status: TradeStatus::from_i32(quote.trade_status).unwrap_or_default(), @@ -384,7 +386,7 @@ pub struct OptionQuote { impl TryFrom for OptionQuote { type Error = Error; - fn try_from(quote: quote::OptionQuote) -> Result { + fn try_from(quote: quote::OptionQuote) -> Result { let option_extend = quote.option_extend.unwrap_or_default(); Ok(Self { @@ -394,13 +396,15 @@ impl TryFrom for OptionQuote { open: quote.open.parse().unwrap_or_default(), high: quote.high.parse().unwrap_or_default(), low: quote.low.parse().unwrap_or_default(), - timestamp: OffsetDateTime::from_unix_timestamp(quote.timestamp)?, + timestamp: OffsetDateTime::from_unix_timestamp(quote.timestamp) + .map_err(|err| Error::parse_field_error("timestamp", err))?, volume: quote.volume, turnover: quote.turnover.parse().unwrap_or_default(), trade_status: TradeStatus::from_i32(quote.trade_status).unwrap_or_default(), implied_volatility: option_extend.implied_volatility.parse().unwrap_or_default(), open_interest: option_extend.open_interest, - expiry_date: parse_date(&option_extend.expiry_date)?, + expiry_date: parse_date(&option_extend.expiry_date) + .map_err(|err| Error::parse_field_error("expiry_date", err))?, strike_price: option_extend.strike_price.parse().unwrap_or_default(), contract_multiplier: option_extend .contract_multiplier @@ -489,7 +493,7 @@ pub struct WarrantQuote { impl TryFrom for WarrantQuote { type Error = Error; - fn try_from(quote: quote::WarrantQuote) -> Result { + fn try_from(quote: quote::WarrantQuote) -> Result { let warrant_extend = quote.warrant_extend.unwrap_or_default(); Ok(Self { @@ -499,7 +503,8 @@ impl TryFrom for WarrantQuote { open: quote.open.parse().unwrap_or_default(), high: quote.high.parse().unwrap_or_default(), low: quote.low.parse().unwrap_or_default(), - timestamp: OffsetDateTime::from_unix_timestamp(quote.timestamp)?, + timestamp: OffsetDateTime::from_unix_timestamp(quote.timestamp) + .map_err(|err| Error::parse_field_error("timestamp", err))?, volume: quote.volume, turnover: quote.turnover.parse().unwrap_or_default(), trade_status: TradeStatus::from_i32(quote.trade_status).unwrap_or_default(), @@ -507,8 +512,10 @@ impl TryFrom for WarrantQuote { .implied_volatility .parse() .unwrap_or_default(), - expiry_date: parse_date(&warrant_extend.expiry_date)?, - last_trade_date: parse_date(&warrant_extend.last_trade_date)?, + expiry_date: parse_date(&warrant_extend.expiry_date) + .map_err(|err| Error::parse_field_error("expiry_date", err))?, + last_trade_date: parse_date(&warrant_extend.last_trade_date) + .map_err(|err| Error::parse_field_error("last_trade_date", err))?, outstanding_ratio: warrant_extend.outstanding_ratio.parse().unwrap_or_default(), outstanding_qty: warrant_extend.outstanding_qty, conversion_ratio: warrant_extend.conversion_ratio.parse().unwrap_or_default(), @@ -588,10 +595,11 @@ pub struct IntradayLine { impl TryFrom for IntradayLine { type Error = Error; - fn try_from(value: quote::Line) -> Result { + fn try_from(value: quote::Line) -> Result { Ok(Self { price: value.price.parse().unwrap_or_default(), - timestamp: OffsetDateTime::from_unix_timestamp(value.timestamp)?, + timestamp: OffsetDateTime::from_unix_timestamp(value.timestamp) + .map_err(|err| Error::parse_field_error("timestamp", err))?, volume: value.volume, turnover: value.turnover.parse().unwrap_or_default(), avg_price: value.avg_price.parse().unwrap_or_default(), @@ -621,7 +629,7 @@ pub struct Candlestick { impl TryFrom for Candlestick { type Error = Error; - fn try_from(value: quote::Candlestick) -> Result { + fn try_from(value: quote::Candlestick) -> Result { Ok(Self { close: value.close.parse().unwrap_or_default(), open: value.open.parse().unwrap_or_default(), @@ -629,7 +637,8 @@ impl TryFrom for Candlestick { high: value.high.parse().unwrap_or_default(), volume: value.volume, turnover: value.turnover.parse().unwrap_or_default(), - timestamp: OffsetDateTime::from_unix_timestamp(value.timestamp)?, + timestamp: OffsetDateTime::from_unix_timestamp(value.timestamp) + .map_err(|err| Error::parse_field_error("timestamp", err))?, }) } } @@ -650,7 +659,7 @@ pub struct StrikePriceInfo { impl TryFrom for StrikePriceInfo { type Error = Error; - fn try_from(value: quote::StrikePriceInfo) -> Result { + fn try_from(value: quote::StrikePriceInfo) -> Result { Ok(Self { price: value.price.parse().unwrap_or_default(), call_symbol: value.call_symbol, @@ -748,18 +757,17 @@ pub struct TradingSessionInfo { impl TryFrom for TradingSessionInfo { type Error = Error; - fn try_from(value: quote::TradePeriod) -> Result { - fn parse_time(value: i32) -> Result + @@ -47,6 +101,34 @@ + + + release + + + + org.apache.maven.plugins + maven-gpg-plugin + 1.5 + + + sign-artifacts + verify + + sign + + + 0x200DE2EF + 0x200DE2EF + + + + + + + + + 1.8 8 From e572b62010bd69d20ea55269809e2f456cc982e6 Mon Sep 17 00:00:00 2001 From: Sunli Date: Thu, 16 Jun 2022 09:13:17 +0800 Subject: [PATCH 067/567] Update docs --- .github/workflows/ci.yml | 2 +- java/javasrc/pom.xml | 24 +- .../java/com/longbridge/AsyncCallback.java | 3 + .../src/main/java/com/longbridge/Config.java | 19 ++ .../java/com/longbridge/ConfigBuilder.java | 43 ++++ .../src/main/java/com/longbridge/Market.java | 18 ++ .../main/java/com/longbridge/SdkNative.java | 145 ++++++----- .../com/longbridge/quote/QuoteContext.java | 243 ++++++++++++++++-- .../com/longbridge/trade/TradeContext.java | 127 ++++++++- 9 files changed, 507 insertions(+), 117 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5f3687a82c..f5c8713302 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -170,7 +170,7 @@ jobs: fail-fast: true matrix: os: [ubuntu-latest, windows-latest, macos-latest] - java-version: ["8"] + java-version: ["11"] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v3 diff --git a/java/javasrc/pom.xml b/java/javasrc/pom.xml index edda250b11..5610cf5627 100644 --- a/java/javasrc/pom.xml +++ b/java/javasrc/pom.xml @@ -58,6 +58,10 @@ + + maven-compiler-plugin + 3.10.1 + org.sonatype.plugins nexus-staging-maven-plugin @@ -79,19 +83,6 @@ maven-compiler-plugin - - maven-javadoc-plugin - - - attach-javadocs - package - - jar - - - - - @@ -130,8 +121,9 @@ - 1.8 - 8 - 8 + 1.11 + 9 + 9 + utf-8 \ No newline at end of file diff --git a/java/javasrc/src/main/java/com/longbridge/AsyncCallback.java b/java/javasrc/src/main/java/com/longbridge/AsyncCallback.java index 35258f35e4..14cd1d8b6a 100644 --- a/java/javasrc/src/main/java/com/longbridge/AsyncCallback.java +++ b/java/javasrc/src/main/java/com/longbridge/AsyncCallback.java @@ -2,6 +2,9 @@ import java.util.concurrent.CompletableFuture; +/** + * @hidden + */ public interface AsyncCallback { public void callback(Object err, Object obj); diff --git a/java/javasrc/src/main/java/com/longbridge/Config.java b/java/javasrc/src/main/java/com/longbridge/Config.java index 36dcb80d9c..c4cfb3ef4a 100644 --- a/java/javasrc/src/main/java/com/longbridge/Config.java +++ b/java/javasrc/src/main/java/com/longbridge/Config.java @@ -1,16 +1,35 @@ package com.longbridge; +/** + * Configuration options for Longbridge sdk + */ public class Config implements AutoCloseable { private long raw; + /** + * @hidden + */ Config(long config) { this.raw = config; } + /** + * Create a new `Config` from the given environment variables + *

+ * It first gets the environment variables from the .env file in the current + * directory. + * + * @return Config object + * @throws OpenApiException If an error occurs + */ public static Config fromEnv() throws OpenApiException { return new Config(SdkNative.newConfigFromEnv()); } + /** + * @hidden + * @return Context pointer + */ public long getRaw() { return this.raw; } diff --git a/java/javasrc/src/main/java/com/longbridge/ConfigBuilder.java b/java/javasrc/src/main/java/com/longbridge/ConfigBuilder.java index ed518cecca..016bc4d4c0 100644 --- a/java/javasrc/src/main/java/com/longbridge/ConfigBuilder.java +++ b/java/javasrc/src/main/java/com/longbridge/ConfigBuilder.java @@ -1,5 +1,8 @@ package com.longbridge; +/** + * A Config object builder + */ public class ConfigBuilder { private String appKey; private String appSecret; @@ -8,27 +11,67 @@ public class ConfigBuilder { private String quoteWsUrl; private String tradeWsUrl; + /** + * Create a `Config` object builder + * + * @param appKey App Key + * @param appSecret App Secret + * @param accessToken Access Token + */ public ConfigBuilder(String appKey, String appSecret, String accessToken) { this.appKey = appKey; this.appSecret = appSecret; this.accessToken = accessToken; } + /** + * Specifies the url of the OpenAPI server. + *

+ * NOTE: Usually you don’t need to change it. + * + * @param httpUrl OpenAPI endpoint (Default: + * `https://openapi.longbridgeapp.com`) + * @return this object + */ public ConfigBuilder HttpUrl(String httpUrl) { this.httpUrl = httpUrl; return this; } + /** + * Specifies the url of the OpenAPI quote websocket server. + *

+ * NOTE: Usually you don’t need to change it. + * + * @param quoteWsUrl OpenAPI quote websocket endpoint (Default: + * `wss://openapi-quote.longbridgeapp.com`) + * @return this object + */ public ConfigBuilder quoteWebsocketUrl(String quoteWsUrl) { this.quoteWsUrl = quoteWsUrl; return this; } + /** + * Specifies the url of the OpenAPI trade websocket server. + *

+ * NOTE: Usually you don’t need to change it. + * + * @param tradeWsUrl OpenAPI trade websocket endpoint (Default: + * `wss://openapi-quote.longbridgeapp.com`) + * @return this object + */ public ConfigBuilder tradeWebsocketUrl(String tradeWsUrl) { this.tradeWsUrl = tradeWsUrl; return this; } + /** + * Build a Config object + * + * @return Config object + * @throws OpenApiException If an error occurs + */ public Config build() throws OpenApiException { return new Config(SdkNative.newConfig(appKey, appSecret, accessToken, httpUrl, quoteWsUrl, tradeWsUrl)); } diff --git a/java/javasrc/src/main/java/com/longbridge/Market.java b/java/javasrc/src/main/java/com/longbridge/Market.java index d8badd1552..3315fe7287 100644 --- a/java/javasrc/src/main/java/com/longbridge/Market.java +++ b/java/javasrc/src/main/java/com/longbridge/Market.java @@ -1,9 +1,27 @@ package com.longbridge; +/** + * Market + */ public enum Market { + /** + * Unknown + */ Unknown, + /** + * US market + */ US, + /** + * HK market + */ HK, + /** + * CN market + */ CN, + /** + * SG market + */ SG, } diff --git a/java/javasrc/src/main/java/com/longbridge/SdkNative.java b/java/javasrc/src/main/java/com/longbridge/SdkNative.java index acac74d510..a541a3ef0f 100644 --- a/java/javasrc/src/main/java/com/longbridge/SdkNative.java +++ b/java/javasrc/src/main/java/com/longbridge/SdkNative.java @@ -7,125 +7,128 @@ import com.longbridge.quote.*; import com.longbridge.trade.*; +/** + * @hidden + */ public class SdkNative { - static native void init(); + static native void init(); - public static native long newConfig(String appKey, String appSecret, String accessToken, String httpUrl, - String quoteWsUrl, String tradeWsUrl); + public static native long newConfig(String appKey, String appSecret, String accessToken, String httpUrl, + String quoteWsUrl, String tradeWsUrl); - public static native long newConfigFromEnv(); + public static native long newConfigFromEnv(); - public static native void freeConfig(long config); + public static native void freeConfig(long config); - public static native void newQuoteContext(long config, AsyncCallback callback); + public static native void newQuoteContext(long config, AsyncCallback callback); - public static native void freeQuoteContext(long config); + public static native void freeQuoteContext(long config); - public static native void quoteContextSetOnQuote(long context, QuoteHandler handler); + public static native void quoteContextSetOnQuote(long context, QuoteHandler handler); - public static native void quoteContextSetOnDepth(long context, DepthHandler handler); + public static native void quoteContextSetOnDepth(long context, DepthHandler handler); - public static native void quoteContextSetOnBrokers(long context, BrokersHandler handler); + public static native void quoteContextSetOnBrokers(long context, BrokersHandler handler); - public static native void quoteContextSetOnTrades(long context, TradesHandler handler); + public static native void quoteContextSetOnTrades(long context, TradesHandler handler); - public static native void quoteContextSubscribe(long context, String[] symbols, int flags, boolean isFirstPush, - AsyncCallback callback); + public static native void quoteContextSubscribe(long context, String[] symbols, int flags, boolean isFirstPush, + AsyncCallback callback); - public static native void quoteContextUnsubscribe(long context, String[] symbols, int flags, - AsyncCallback callback); + public static native void quoteContextUnsubscribe(long context, String[] symbols, int flags, + AsyncCallback callback); - public static native void quoteContextSubscriptions(long context, AsyncCallback callback); + public static native void quoteContextSubscriptions(long context, AsyncCallback callback); - public static native void quoteContextStaticInfo(long context, String[] symbols, AsyncCallback callback); + public static native void quoteContextStaticInfo(long context, String[] symbols, AsyncCallback callback); - public static native void quoteContextQuote(long context, String[] symbols, AsyncCallback callback); + public static native void quoteContextQuote(long context, String[] symbols, AsyncCallback callback); - public static native void quoteContextOptionQuote(long context, String[] symbols, AsyncCallback callback); + public static native void quoteContextOptionQuote(long context, String[] symbols, AsyncCallback callback); - public static native void quoteContextWarrantQuote(long context, String[] symbols, AsyncCallback callback); + public static native void quoteContextWarrantQuote(long context, String[] symbols, AsyncCallback callback); - public static native void quoteContextDepth(long context, String symbol, AsyncCallback callback); + public static native void quoteContextDepth(long context, String symbol, AsyncCallback callback); - public static native void quoteContextBrokers(long context, String symbol, AsyncCallback callback); + public static native void quoteContextBrokers(long context, String symbol, AsyncCallback callback); - public static native void quoteContextParticipants(long context, AsyncCallback callback); + public static native void quoteContextParticipants(long context, AsyncCallback callback); - public static native void quoteContextTrades(long context, String symbol, int count, AsyncCallback callback); + public static native void quoteContextTrades(long context, String symbol, int count, AsyncCallback callback); - public static native void quoteContextIntraday(long context, String symbol, AsyncCallback callback); + public static native void quoteContextIntraday(long context, String symbol, AsyncCallback callback); - public static native void quoteContextCandlesticks(long context, String symbol, Period period, int count, - AdjustType adjustType, AsyncCallback callback); + public static native void quoteContextCandlesticks(long context, String symbol, Period period, int count, + AdjustType adjustType, AsyncCallback callback); - public static native void quoteContextOptionChainExpiryDateList(long context, String symbol, - AsyncCallback callback); + public static native void quoteContextOptionChainExpiryDateList(long context, String symbol, + AsyncCallback callback); - public static native void quoteContextOptionChainInfoByDate(long context, String symbol, LocalDate expiryDate, - AsyncCallback callback); + public static native void quoteContextOptionChainInfoByDate(long context, String symbol, LocalDate expiryDate, + AsyncCallback callback); - public static native void quoteContextWarrantIssuers(long context, AsyncCallback callback); + public static native void quoteContextWarrantIssuers(long context, AsyncCallback callback); - public static native void quoteContextTradingSession(long context, AsyncCallback callback); + public static native void quoteContextTradingSession(long context, AsyncCallback callback); - public static native void quoteContextTradingDays(long context, Market market, LocalDate begin, LocalDate end, - AsyncCallback callback); + public static native void quoteContextTradingDays(long context, Market market, LocalDate begin, LocalDate end, + AsyncCallback callback); - public static native void quoteContextRealtimeQuote(long context, String[] symbols, AsyncCallback callback); + public static native void quoteContextRealtimeQuote(long context, String[] symbols, AsyncCallback callback); - public static native void quoteContextRealtimeDepth(long context, String symbol, AsyncCallback callback); + public static native void quoteContextRealtimeDepth(long context, String symbol, AsyncCallback callback); - public static native void quoteContextRealtimeBrokers(long context, String symbol, AsyncCallback callback); + public static native void quoteContextRealtimeBrokers(long context, String symbol, AsyncCallback callback); - public static native void quoteContextRealtimeTrades(long context, String symbol, int count, - AsyncCallback callback); + public static native void quoteContextRealtimeTrades(long context, String symbol, int count, + AsyncCallback callback); - public static native void newTradeContext(long config, AsyncCallback callback); + public static native void newTradeContext(long config, AsyncCallback callback); - public static native void freeTradeContext(long config); + public static native void freeTradeContext(long config); - public static native void tradeContextSetOnOrderChanged(long context, OrderChangedHandler handler); + public static native void tradeContextSetOnOrderChanged(long context, OrderChangedHandler handler); - public static native void tradeContextSubscribe(long context, TopicType[] topics, AsyncCallback callback); + public static native void tradeContextSubscribe(long context, TopicType[] topics, AsyncCallback callback); - public static native void tradeContextUnsubscribe(long context, TopicType[] topics, AsyncCallback callback); + public static native void tradeContextUnsubscribe(long context, TopicType[] topics, AsyncCallback callback); - public static native void tradeContextHistoryExecutions(long context, GetHistoryExecutionsOptions opts, - AsyncCallback callback); + public static native void tradeContextHistoryExecutions(long context, GetHistoryExecutionsOptions opts, + AsyncCallback callback); - public static native void tradeContextTodayExecutions(long context, GetTodayExecutionsOptions opts, - AsyncCallback callback); + public static native void tradeContextTodayExecutions(long context, GetTodayExecutionsOptions opts, + AsyncCallback callback); - public static native void tradeContextHistoryOrders(long context, GetHistoryOrdersOptions opts, - AsyncCallback callback); + public static native void tradeContextHistoryOrders(long context, GetHistoryOrdersOptions opts, + AsyncCallback callback); - public static native void tradeContextTodayOrders(long context, GetTodayOrdersOptions opts, - AsyncCallback callback); + public static native void tradeContextTodayOrders(long context, GetTodayOrdersOptions opts, + AsyncCallback callback); - public static native void tradeContextReplaceOrder(long context, ReplaceOrderOptions opts, - AsyncCallback callback); + public static native void tradeContextReplaceOrder(long context, ReplaceOrderOptions opts, + AsyncCallback callback); - public static native void tradeContextSubmitOrder(long context, SubmitOrderOptions opts, - AsyncCallback callback); + public static native void tradeContextSubmitOrder(long context, SubmitOrderOptions opts, + AsyncCallback callback); - public static native void tradeContextCancelOrder(long context, String orderId, AsyncCallback callback); + public static native void tradeContextCancelOrder(long context, String orderId, AsyncCallback callback); - public static native void tradeContextAccountBalance(long context, AsyncCallback callback); + public static native void tradeContextAccountBalance(long context, AsyncCallback callback); - public static native void tradeContextCashFlow(long context, GetCashFlowOptions opts, AsyncCallback callback); + public static native void tradeContextCashFlow(long context, GetCashFlowOptions opts, AsyncCallback callback); - public static native void tradeContextFundPositions(long context, GetFundPositionsOptions opts, - AsyncCallback callback); + public static native void tradeContextFundPositions(long context, GetFundPositionsOptions opts, + AsyncCallback callback); - public static native void tradeContextStockPositions(long context, GetStockPositionsOptions opts, - AsyncCallback callback); + public static native void tradeContextStockPositions(long context, GetStockPositionsOptions opts, + AsyncCallback callback); - static { - try { - NativeLoader.loadLibrary("longbridge_java"); - SdkNative.init(); - } catch (IOException e) { - e.printStackTrace(); - } + static { + try { + NativeLoader.loadLibrary("longbridge_java"); + SdkNative.init(); + } catch (IOException e) { + e.printStackTrace(); } + } } diff --git a/java/javasrc/src/main/java/com/longbridge/quote/QuoteContext.java b/java/javasrc/src/main/java/com/longbridge/quote/QuoteContext.java index c9924145d8..823cc48eee 100644 --- a/java/javasrc/src/main/java/com/longbridge/quote/QuoteContext.java +++ b/java/javasrc/src/main/java/com/longbridge/quote/QuoteContext.java @@ -4,9 +4,19 @@ import java.util.concurrent.CompletableFuture; import com.longbridge.*; +/** + * Quote context + */ public class QuoteContext implements AutoCloseable { private long raw; + /** + * Create a QuoteContext object + * + * @param config Config object + * @return A Future representing the result of the operation + * @throws OpenApiException If an error occurs + */ public static CompletableFuture create(Config config) throws OpenApiException { return AsyncCallback.executeTask((callback) -> { @@ -19,151 +29,346 @@ public void close() throws Exception { SdkNative.freeQuoteContext(raw); } + /** + * Set quote callback, after receiving the quote data push, it will call back to + * this handler. + * + * @param handler A quote handler + */ public void setOnQuote(QuoteHandler handler) { SdkNative.quoteContextSetOnQuote(this.raw, handler); } + /** + * Set quote callback, after receiving the depth data push, it will call back to + * this handler. + * + * @param handler A depth handler + */ public void setOnDepth(DepthHandler handler) { SdkNative.quoteContextSetOnDepth(this.raw, handler); } + /** + * Set quote callback, after receiving the brokers data push, it will call back + * to this handler. + * + * @param handler A brokers handler + */ public void setOnBrokers(BrokersHandler handler) { SdkNative.quoteContextSetOnBrokers(this.raw, handler); } + /** + * Set quote callback, after receiving the trades data push, it will call backto + * this handler. + * + * @param handler A trades handler + */ public void setOnTrades(TradesHandler handler) { SdkNative.quoteContextSetOnTrades(this.raw, handler); } + /** + * Subscribe + * + * @param symbols Security symbols + * @param flags Subscription flags + * @param isFirstPush Whether to perform a data push immediately after + * subscribing. + * @return A Future representing the result of the operation + * @throws OpenApiException If an error occurs + */ public CompletableFuture subscribe(String[] symbols, int flags, boolean isFirstPush) throws OpenApiException { return AsyncCallback.executeTask((callback) -> { SdkNative.quoteContextSubscribe(this.raw, symbols, flags, isFirstPush, callback); }); } + /** + * Unsubscribe + * + * @param symbols Security symbols + * @param flags Subscription flags + * @return A Future representing the result of the operation + * @throws OpenApiException If an error occurs + */ public CompletableFuture unsubscribe(String[] symbols, int flags) throws OpenApiException { return AsyncCallback.executeTask((callback) -> { SdkNative.quoteContextUnsubscribe(this.raw, symbols, flags, callback); }); } - public CompletableFuture subscrptions(String[] symbols, int flags) throws OpenApiException { + /** + * Get subscription information + * + * @return A Future representing the result of the operation + * @throws OpenApiException If an error occurs + */ + public CompletableFuture getSubscrptions() throws OpenApiException { return AsyncCallback.executeTask((callback) -> { SdkNative.quoteContextSubscriptions(this.raw, callback); }); } - public CompletableFuture staticInfo(String[] symbols) throws OpenApiException { + /** + * Get basic information of securities + * + * @param symbols Security symbols + * @return A Future representing the result of the operation + * @throws OpenApiException If an error occurs + */ + public CompletableFuture getStaticInfo(String[] symbols) throws OpenApiException { return AsyncCallback.executeTask((callback) -> { SdkNative.quoteContextStaticInfo(this.raw, symbols, callback); }); } - public CompletableFuture quote(String[] symbols) throws OpenApiException { + /** + * Get quote of securities + * + * @param symbols Security symbols + * @return A Future representing the result of the operation + * @throws OpenApiException If an error occurs + */ + public CompletableFuture getQuote(String[] symbols) throws OpenApiException { return AsyncCallback.executeTask((callback) -> { SdkNative.quoteContextQuote(this.raw, symbols, callback); }); } - public CompletableFuture optionQuote(String[] symbols) throws OpenApiException { + /** + * Get quote of option securities + * + * @param symbols Security symbols + * @return A Future representing the result of the operation + * @throws OpenApiException If an error occurs + */ + public CompletableFuture getOptionQuote(String[] symbols) throws OpenApiException { return AsyncCallback.executeTask((callback) -> { SdkNative.quoteContextOptionQuote(this.raw, symbols, callback); }); } - public CompletableFuture depth(String symbol) throws OpenApiException { + /** + * Get security depth + * + * @param symbol Security symbol + * @return A Future representing the result of the operation + * @throws OpenApiException If an error occurs + */ + public CompletableFuture getDepth(String symbol) throws OpenApiException { return AsyncCallback.executeTask((callback) -> { SdkNative.quoteContextDepth(this.raw, symbol, callback); }); } - public CompletableFuture brokers(String symbol) throws OpenApiException { + /** + * Get security brokers + * + * @param symbol Security symbol + * @return A Future representing the result of the operation + * @throws OpenApiException If an error occurs + */ + public CompletableFuture getBrokers(String symbol) throws OpenApiException { return AsyncCallback.executeTask((callback) -> { SdkNative.quoteContextBrokers(this.raw, symbol, callback); }); } - public CompletableFuture participants() throws OpenApiException { + /** + * Get participants + * + * @return A Future representing the result of the operation + * @throws OpenApiException If an error occurs + */ + public CompletableFuture getParticipants() throws OpenApiException { return AsyncCallback.executeTask((callback) -> { SdkNative.quoteContextParticipants(this.raw, callback); }); } - public CompletableFuture trades(String symbol, int count) throws OpenApiException { + /** + * Get security trades + * + * @param symbol Security symbol + * @param count Count of trades + * @return A Future representing the result of the operation + * @throws OpenApiException If an error occurs + */ + public CompletableFuture getTrades(String symbol, int count) throws OpenApiException { return AsyncCallback.executeTask((callback) -> { SdkNative.quoteContextTrades(this.raw, symbol, count, callback); }); } - public CompletableFuture intraday(String symbol) throws OpenApiException { + /** + * Get security intraday lines + * + * @param symbol Security symbol + * @return A Future representing the result of the operation + * @throws OpenApiException If an error occurs + */ + public CompletableFuture getIntraday(String symbol) throws OpenApiException { return AsyncCallback.executeTask((callback) -> { SdkNative.quoteContextIntraday(this.raw, symbol, callback); }); } - public CompletableFuture candlesticks(String symbol, Period period, int count, + /** + * Get security candlesticks + * + * @param symbol Security symbol + * @param period Candlestick period + * @param count Count of candlesticks + * @param adjustType Adjustment type + * @return A Future representing the result of the operation + * @throws OpenApiException If an error occurs + */ + public CompletableFuture getCandlesticks(String symbol, Period period, int count, AdjustType adjustType) throws OpenApiException { return AsyncCallback.executeTask((callback) -> { SdkNative.quoteContextCandlesticks(this.raw, symbol, period, count, adjustType, callback); }); } - public CompletableFuture optionChainExpiryDateList(String symbol) throws OpenApiException { + /** + * Get option chain expiry date list + * + * @param symbol Security symbol + * @return A Future representing the result of the operation + * @throws OpenApiException If an error occurs + */ + public CompletableFuture getOptionChainExpiryDateList(String symbol) throws OpenApiException { return AsyncCallback.executeTask((callback) -> { SdkNative.quoteContextOptionChainExpiryDateList(this.raw, symbol, callback); }); } - public CompletableFuture optionChainExpiryDateList(String symbol, LocalDate expiryDate) + /** + * Get option chain info by date + * + * @param symbol Security symbol + * @param expiryDate Option expiry date + * @return A Future representing the result of the operation + * @throws OpenApiException If an error occurs + */ + public CompletableFuture getOptionChainInfoByDate(String symbol, LocalDate expiryDate) throws OpenApiException { return AsyncCallback.executeTask((callback) -> { SdkNative.quoteContextOptionChainInfoByDate(this.raw, symbol, expiryDate, callback); }); } - public CompletableFuture warrantIssuers(String symbol, LocalDate expiryDate) + /** + * Get warrant issuers + * + * @param symbol Security symbol + * @return A Future representing the result of the operation + * @throws OpenApiException If an error occurs + */ + public CompletableFuture getWarrantIssuers(String symbol) throws OpenApiException { return AsyncCallback.executeTask((callback) -> { SdkNative.quoteContextWarrantIssuers(this.raw, callback); }); } - public CompletableFuture tradingSession(String symbol, LocalDate expiryDate) + /** + * Get trading session of the day + * + * @return A Future representing the result of the operation + * @throws OpenApiException If an error occurs + */ + public CompletableFuture getTradingSession() throws OpenApiException { return AsyncCallback.executeTask((callback) -> { SdkNative.quoteContextTradingSession(this.raw, callback); }); } - public CompletableFuture tradingDays(Market market, LocalDate begin, LocalDate end) + /** + * Get market trading days + *

+ * The interval must be less than one month, and only the most recent year is + * supported. + * + * @param market Market + * @param begin Begin date + * @param end End date + * @return A Future representing the result of the operation + * @throws OpenApiException If an error occurs + */ + public CompletableFuture getTradingDays(Market market, LocalDate begin, LocalDate end) throws OpenApiException { return AsyncCallback.executeTask((callback) -> { SdkNative.quoteContextTradingDays(this.raw, market, begin, end, callback); }); } - public CompletableFuture realtimeQuote(String[] symbols) + /** + * Get real-time quotes + *

+ * Get real-time quotes of the subscribed symbols, it always returns the data in + * the local storage. + * + * @param symbols Security symbols + * @return A Future representing the result of the operation + * @throws OpenApiException If an error occurs + */ + public CompletableFuture getRealtimeQuote(String[] symbols) throws OpenApiException { return AsyncCallback.executeTask((callback) -> { SdkNative.quoteContextRealtimeQuote(this.raw, symbols, callback); }); } - public CompletableFuture realtimeDepth(String symbol) + /** + * Get real-time depth + *

+ * Get real-time depth of the subscribed symbols, it always returns the data in + * the local storage. + * + * @param symbol Security symbol + * @return A Future representing the result of the operation + * @throws OpenApiException If an error occurs + */ + public CompletableFuture getRealtimeDepth(String symbol) throws OpenApiException { return AsyncCallback.executeTask((callback) -> { SdkNative.quoteContextRealtimeDepth(this.raw, symbol, callback); }); } - public CompletableFuture realtimeBrokers(String symbol) + /** + * Get real-time broker queue + *

+ * Get real-time broker queue of the subscribed symbols, it always returns the + * data in the local storage. + * + * @param symbol Security symbol + * @return A Future representing the result of the operation + * @throws OpenApiException If an error occurs + */ + public CompletableFuture getRealtimeBrokers(String symbol) throws OpenApiException { return AsyncCallback.executeTask((callback) -> { SdkNative.quoteContextRealtimeBrokers(this.raw, symbol, callback); }); } - public CompletableFuture realtimeBrokers(String symbol, int count) + /** + * Get real-time trades + *

+ * Get real-time trades of the subscribed symbols, it always returns the data in + * the local storage. + * + * @param symbol Security symbol + * @param count Count of trades + * @return A Future representing the result of the operation + * @throws OpenApiException If an error occurs + */ + public CompletableFuture getRealtimeBrokers(String symbol, int count) throws OpenApiException { return AsyncCallback.executeTask((callback) -> { SdkNative.quoteContextRealtimeTrades(this.raw, symbol, count, callback); diff --git a/java/javasrc/src/main/java/com/longbridge/trade/TradeContext.java b/java/javasrc/src/main/java/com/longbridge/trade/TradeContext.java index 54f789c75f..178685b760 100644 --- a/java/javasrc/src/main/java/com/longbridge/trade/TradeContext.java +++ b/java/javasrc/src/main/java/com/longbridge/trade/TradeContext.java @@ -4,9 +4,19 @@ import com.longbridge.*; +/** + * Trade context + */ public class TradeContext implements AutoCloseable { private long raw; + /** + * Create a TradeContext object + * + * @param config Config object + * @return A Future representing the result of the operation + * @throws OpenApiException If an error occurs + */ public static CompletableFuture create(Config config) throws OpenApiException { return AsyncCallback.executeTask((callback) -> { @@ -19,84 +29,181 @@ public void close() throws Exception { SdkNative.freeTradeContext(raw); } + /** + * Set order changed event callback, after receiving the order changed event, it + * will call back to this handler. + * + * @param handler A order changed handler + */ public void setOnOrderChange(OrderChangedHandler handler) { SdkNative.tradeContextSetOnOrderChanged(this.raw, handler); } + /** + * Subscribe + * + * @param topics Topics + * @return A Future representing the result of the operation + * @throws OpenApiException If an error occurs + */ public CompletableFuture subscribe(TopicType[] topics) throws OpenApiException { return AsyncCallback.executeTask((callback) -> { SdkNative.tradeContextSubscribe(this.raw, topics, callback); }); } + /** + * Unsubscribe + * + * @param topics Topics + * @return A Future representing the result of the operation + * @throws OpenApiException If an error occurs + */ public CompletableFuture unsubscribe(TopicType[] topics) throws OpenApiException { return AsyncCallback.executeTask((callback) -> { SdkNative.tradeContextUnsubscribe(this.raw, topics, callback); }); } - public CompletableFuture historyExecutions(GetHistoryExecutionsOptions opts) throws OpenApiException { + /** + * Get history executions + * + * @param opts Options for this request + * @return A Future representing the result of the operation + * @throws OpenApiException If an error occurs + */ + public CompletableFuture getHistoryExecutions(GetHistoryExecutionsOptions opts) + throws OpenApiException { return AsyncCallback.executeTask((callback) -> { SdkNative.tradeContextHistoryExecutions(this.raw, opts, callback); }); } - public CompletableFuture todayExecutions(GetTodayExecutionsOptions opts) throws OpenApiException { + /** + * Get today executions + * + * @param opts Options for this request + * @return A Future representing the result of the operation + * @throws OpenApiException If an error occurs + */ + public CompletableFuture getTodayExecutions(GetTodayExecutionsOptions opts) throws OpenApiException { return AsyncCallback.executeTask((callback) -> { SdkNative.tradeContextTodayExecutions(this.raw, opts, callback); }); } - public CompletableFuture historyOrders(GetHistoryOrdersOptions opts) throws OpenApiException { + /** + * Get history orders + * + * @param opts Options for this request + * @return A Future representing the result of the operation + * @throws OpenApiException If an error occurs + */ + public CompletableFuture getHistoryOrders(GetHistoryOrdersOptions opts) throws OpenApiException { return AsyncCallback.executeTask((callback) -> { SdkNative.tradeContextHistoryOrders(this.raw, opts, callback); }); } - public CompletableFuture todayOrders(GetTodayOrdersOptions opts) throws OpenApiException { + /** + * Get today orders + * + * @param opts Options for this request + * @return A Future representing the result of the operation + * @throws OpenApiException If an error occurs + */ + public CompletableFuture getTodayOrders(GetTodayOrdersOptions opts) throws OpenApiException { return AsyncCallback.executeTask((callback) -> { SdkNative.tradeContextTodayOrders(this.raw, opts, callback); }); } + /** + * Replace order + * + * @param opts Options for this request + * @return A Future representing the result of the operation + * @throws OpenApiException If an error occurs + */ public CompletableFuture replaceOrder(ReplaceOrderOptions opts) throws OpenApiException { return AsyncCallback.executeTask((callback) -> { SdkNative.tradeContextReplaceOrder(this.raw, opts, callback); }); } + /** + * Submit order + * + * @param opts Options for this request + * @return A Future representing the result of the operation + * @throws OpenApiException If an error occurs + */ public CompletableFuture submitOrder(SubmitOrderOptions opts) throws OpenApiException { return AsyncCallback.executeTask((callback) -> { SdkNative.tradeContextSubmitOrder(this.raw, opts, callback); }); } - public CompletableFuture cancelOrder(String orderIdId) throws OpenApiException { + /** + * Cancel order + * + * @param orderId Order ID + * @return A Future representing the result of the operation + * @throws OpenApiException If an error occurs + */ + public CompletableFuture cancelOrder(String orderId) throws OpenApiException { return AsyncCallback.executeTask((callback) -> { - SdkNative.tradeContextCancelOrder(this.raw, orderIdId, callback); + SdkNative.tradeContextCancelOrder(this.raw, orderId, callback); }); } - public CompletableFuture accountBalance() throws OpenApiException { + /** + * Get account balance + * + * @return A Future representing the result of the operation + * @throws OpenApiException If an error occurs + */ + public CompletableFuture getAccountBalance() throws OpenApiException { return AsyncCallback.executeTask((callback) -> { SdkNative.tradeContextAccountBalance(this.raw, callback); }); } - public CompletableFuture cashFlow(GetCashFlowOptions opts) throws OpenApiException { + /** + * Get cash flow + * + * @param opts Options for this request + * @return A Future representing the result of the operation + * @throws OpenApiException If an error occurs + */ + public CompletableFuture getCashFlow(GetCashFlowOptions opts) throws OpenApiException { return AsyncCallback.executeTask((callback) -> { SdkNative.tradeContextCashFlow(this.raw, opts, callback); }); } - public CompletableFuture fundPositions(GetFundPositionsOptions opts) + /** + * Get fund positions + * + * @param opts Options for this request + * @return A Future representing the result of the operation + * @throws OpenApiException If an error occurs + */ + public CompletableFuture getFundPositions(GetFundPositionsOptions opts) throws OpenApiException { return AsyncCallback.executeTask((callback) -> { SdkNative.tradeContextFundPositions(this.raw, opts, callback); }); } - public CompletableFuture stockPositions(GetStockPositionsOptions opts) + /** + * Get stock positions + * + * @param opts Options for this request + * @return A Future representing the result of the operation + * @throws OpenApiException If an error occurs + */ + public CompletableFuture getStockPositions(GetStockPositionsOptions opts) throws OpenApiException { return AsyncCallback.executeTask((callback) -> { SdkNative.tradeContextStockPositions(this.raw, opts, callback); From 7af4f984f1e88256d266bae24a23e19b2e7bad4a Mon Sep 17 00:00:00 2001 From: Sunli Date: Thu, 16 Jun 2022 09:39:15 +0800 Subject: [PATCH 068/567] Update CI --- .github/misc/render_docs_index.py | 6 + .github/misc/templates/docs.html | 5 + .github/workflows/docs.yml | 13 ++ .github/workflows/release.yml | 101 +++++++++++ java/javasrc/pom.xml | 218 +++++++++++------------ java/javasrc/src/test/java/TestMain.java | 19 -- 6 files changed, 227 insertions(+), 135 deletions(-) delete mode 100644 java/javasrc/src/test/java/TestMain.java diff --git a/.github/misc/render_docs_index.py b/.github/misc/render_docs_index.py index 51a9ebdded..cdde6bc911 100644 --- a/.github/misc/render_docs_index.py +++ b/.github/misc/render_docs_index.py @@ -10,6 +10,7 @@ rust_versions = [] python_versions = [] nodejs_versions = [] +java_versions = [] for item in (root / "rust").iterdir(): rust_versions.append(item.name) @@ -17,13 +18,18 @@ python_versions.append(item.name) for item in (root / "nodejs").iterdir(): nodejs_versions.append(item.name) +for item in (root / "java").iterdir(): + java_versions.append(item.name) rust_versions.sort(key=lambda x: VersionInfo.parse(x), reverse=True) python_versions.sort(key=lambda x: VersionInfo.parse(x), reverse=True) +nodejs_versions.sort(key=lambda x: VersionInfo.parse(x), reverse=True) +java_versions.sort(key=lambda x: VersionInfo.parse(x), reverse=True) result = template.render( rust_versions=rust_versions, python_versions=python_versions, nodejs_versions=nodejs_versions, + java_versions=java_versions, ) print(result) diff --git a/.github/misc/templates/docs.html b/.github/misc/templates/docs.html index 44e961129e..697c71138c 100644 --- a/.github/misc/templates/docs.html +++ b/.github/misc/templates/docs.html @@ -32,6 +32,11 @@

Node.js

{% for version in nodejs_versions %} {{ version }} {% endfor %} + +

Java

+ {% for version in java_versions %} + {{ version }} + {% endfor %} \ No newline at end of file diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index f6590d9e9e..25d4dbd7d3 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -30,6 +30,12 @@ jobs: node-version: 16 check-latest: true + - name: Install JDK + uses: actions/setup-java@v3 + with: + java-version: "11" + distribution: 'temurin' + - name: Build Rust docs run: cargo doc -p longbridge --no-deps - name: Build Python wheels @@ -55,11 +61,16 @@ jobs: typedoc working-directory: ./nodejs + - name: Build Java docs + working-directory: ./java + run: mvn javadoc:javadoc + - name: Get SDK Version run: | echo RUST_SDK_VERSION=$(sed -nE 's/^\s*version = "(.*?)"/\1/p' rust/Cargo.toml) >> $GITHUB_ENV echo PYTHON_SDK_VERSION=$(sed -nE 's/^\s*version = "(.*?)"/\1/p' python/Cargo.toml) >> $GITHUB_ENV echo NODEJS_SDK_VERSION=$(sed -nE 's/^\s*version = "(.*?)"/\1/p' nodejs/Cargo.toml) >> $GITHUB_ENV + echo JAVA_SDK_VERSION=$(sed -nE 's/^\s*version = "(.*?)"/\1/p' java/Cargo.toml) >> $GITHUB_ENV - name: Copy to gh-pages run: | @@ -67,9 +78,11 @@ jobs: mkdir -p gh-pages/rust mkdir -p gh-pages/python mkdir -p gh-pages/nodejs + mkdir -p gh-pages/java mv -f target/doc gh-pages/rust/$RUST_SDK_VERSION mv -f python/site gh-pages/python/$PYTHON_SDK_VERSION mv -f nodejs/docs gh-pages/nodejs/$NODEJS_SDK_VERSION + mv -f java/javasrc/target/site gh-pages/java/$JAVA_SDK_VERSION python .github/misc/render_docs_index.py > gh-pages/index.html - name: Deploy uses: peaceiris/actions-gh-pages@v2.5.1 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d8bb11acb6..3c925d93d4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -367,3 +367,104 @@ jobs: working-directory: ./nodejs env: NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + + build-java-jni: + strategy: + fail-fast: true + matrix: + settings: + - host: ubuntu-latest + lib_prefix: "lib" + lib_suffix: ".so" + target: x86_64-unknown-linux-gnu + - host: windows-latest + lib_suffix: ".dll" + target: x86_64-pc-windows-msvc + - host: macos-latest + lib_prefix: "lib" + lib_suffix: ".dylib" + target: x86_64-apple-darwin + - host: macos-latest + lib_prefix: "lib" + lib_suffix: ".dylib" + target: aarch64-apple-darwin + runs-on: ${{ matrix.settings.host }} + steps: + - uses: actions/checkout@v3 + with: + submodules: true + + - name: Install Protoc + uses: arduino/setup-protoc@v1 + with: + repo-token: ${{ github.token }} + + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: 1.60.0 + override: true + components: rustfmt, clippy + target: ${{ matrix.settings.target }} + + - name: Build + run: | + cargo build -p longbridge-java --release --target ${{ matrix.settings.target }} + mv target/${{ matrix.settings.target }}/release/${{ matrix.settings.lib_prefix }}longbridge_java${{ matrix.settings.lib_suffix }} longbridge_java-${{ matrix.settings.target }}${{ matrix.settings.lib_suffix }} + + - name: Upload artifact + uses: actions/upload-artifact@v2 + with: + name: jnis + path: longbridge_java-${{ matrix.settings.target }}${{ matrix.settings.lib_suffix }} + + publish-java-sdk: + runs-on: ubuntu-latest + needs: build-java-jni + steps: + - uses: actions/checkout@v3 + with: + submodules: true + + - name: Install JDK + uses: actions/setup-java@v3 + with: + java-version: "11" + distribution: "temurin" + + - name: Download all artifacts + uses: actions/download-artifact@v2 + with: + name: jnis + path: ./java/jnis + + - name: Copy jnis + working-directory: java + run: | + mkdir -p javasrc/target/natives/linux_64 + mkdir -p javasrc/target/natives/windows_64 + mkdir -p javasrc/target/natives/osx_64 + mkdir -p javasrc/target/natives/osx_arm64 + mv jnis/longbridge_java-x86_64-unknown-linux-gnu.so javasrc/target/natives/linux_64/liblongbridge_java.so + mv jnis/longbridge_java-x86_64-pc-windows-msvc.dll javasrc/target/natives/windows_64/longbridge_java.dll + mv jnis/longbridge_java-x86_64-apple-darwin.dylib javasrc/target/natives/osx_64/liblongbridge_java.dylib + mv jnis/longbridge_java-aarch64-apple-darwin.dylib javasrc/target/natives/osx_arm64/liblongbridge_java.dylib + + - name: Get version + working-directory: java + run: echo PACKAGE_VERSION=$(sed -nE 's/^\s*version = "(.*?)"/\1/p' Cargo.toml) >> $GITHUB_ENV + + - name: Update version + working-directory: java/javasrc + run: mvn versions:set -DnewVersion=${PACKAGE_VERSION} + + - name: Publish maven package + uses: samuelmeuli/action-maven-publish@v1 + with: + directory: java/javasrc + maven_profiles: release + maven_goals_phases: deploy + gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} + gpg_passphrase: ${{ secrets.GPG_PASSPHRASE }} + nexus_username: ${{ secrets.OSSRH_USERNAME }} + nexus_password: ${{ secrets.OSSRH_PASSWORD }} diff --git a/java/javasrc/pom.xml b/java/javasrc/pom.xml index 5610cf5627..da23c3c51c 100644 --- a/java/javasrc/pom.xml +++ b/java/javasrc/pom.xml @@ -1,129 +1,115 @@ - 4.0.0 + 4.0.0 - io.github.longbridgeapp - openapi-sdk - 0.0.1 - jar + io.github.longbridgeapp + openapi-sdk + 0.0.1 + jar - - scm:git:git@github.com:longbridgeapp/openapi-sdk.git - scm:git:git@github.com:longbridgeapp/openapi-sdk.git - https://github.com/longbridgeapp/openapi-sdk - + + scm:git:git@github.com:longbridgeapp/openapi-sdk.git + scm:git:git@github.com:longbridgeapp/openapi-sdk.git + https://github.com/longbridgeapp/openapi-sdk + - - - sunli829 - scott_s829@163.com - https://github.com/longbridgeapp - +8 - - + + + sunli829 + scott_s829@163.com + https://github.com/longbridgeapp + +8 + + - - - ossrh - https://s01.oss.sonatype.org/content/repositories/snapshots - - - ossrh - https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/ - - + + + ossrh + https://s01.oss.sonatype.org/content/repositories/snapshots + + + ossrh + https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/ + + - - - The Apache Software License, Version 2.0 - http://www.apache.org/licenses/LICENSE-2.0.txt - repo - - + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + - - - org.scijava - native-lib-loader - 2.4.0 - - - junit - junit - 4.12 - test - - + + + org.scijava + native-lib-loader + 2.4.0 + + - - - - - maven-compiler-plugin - 3.10.1 - - - org.sonatype.plugins - nexus-staging-maven-plugin - 1.6.7 - true - - ossrh - https://s01.oss.sonatype.org/ - true - - - - + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.7 + true + + ossrh + https://s01.oss.sonatype.org/ + true + + + + maven-compiler-plugin + 3.10.1 + + + + + target/natives + natives + + + + + + + release + - - org.sonatype.plugins - nexus-staging-maven-plugin - - - maven-compiler-plugin - + + org.apache.maven.plugins + maven-gpg-plugin + 1.6 + + + sign-artifacts + verify + + sign + + + + + --pinentry-mode + loopback + + + + + - - - target/natives - natives - - - - - - - release - - - - org.apache.maven.plugins - maven-gpg-plugin - 1.5 - - - sign-artifacts - verify - - sign - - - 0x200DE2EF - 0x200DE2EF - - - - - - - - + + + - - 1.11 - 9 - 9 - utf-8 - + + 1.11 + 11 + 11 + utf-8 + \ No newline at end of file diff --git a/java/javasrc/src/test/java/TestMain.java b/java/javasrc/src/test/java/TestMain.java deleted file mode 100644 index f21ecfe36c..0000000000 --- a/java/javasrc/src/test/java/TestMain.java +++ /dev/null @@ -1,19 +0,0 @@ -import com.longbridge.*; -import com.longbridge.quote.*; -import org.junit.Test; - -public class TestMain { - @Test - public void main() { - try { - Config config = Config.fromEnv(); - QuoteContext ctx = QuoteContext.create(config).get(); - ctx.setOnQuote((symbol, quote) -> { - System.out.printf("%s %s\n", symbol, quote.getLastDone()); - }); - ctx.subscribe(new String[] { "700.HK", "AAPL.US" }, SubFlags.Quote, true).get(); - } catch (Exception e) { - System.out.println(e); - } - } -} From 46033efb990e042a1611e05e1ba087a1bc68b303 Mon Sep 17 00:00:00 2001 From: Sunli Date: Thu, 16 Jun 2022 16:40:31 +0800 Subject: [PATCH 069/567] Improve parsing http response --- .github/workflows/docs.yml | 2 +- examples/rust/Cargo.lock | 8 ++++---- rust/crates/httpclient/src/error.rs | 18 ++++++++++++++---- rust/crates/httpclient/src/request.rs | 23 +++++++++++++---------- 4 files changed, 32 insertions(+), 19 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 25d4dbd7d3..36ca57a3ff 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -62,7 +62,7 @@ jobs: working-directory: ./nodejs - name: Build Java docs - working-directory: ./java + working-directory: ./java/javasrc run: mvn javadoc:javadoc - name: Get SDK Version diff --git a/examples/rust/Cargo.lock b/examples/rust/Cargo.lock index b6baf5dcd0..4754c2ebe3 100644 --- a/examples/rust/Cargo.lock +++ b/examples/rust/Cargo.lock @@ -482,7 +482,7 @@ dependencies = [ [[package]] name = "longbridge" -version = "0.2.16" +version = "0.2.17" dependencies = [ "bitflags", "dotenv", @@ -505,7 +505,7 @@ dependencies = [ [[package]] name = "longbridge-httpcli" -version = "0.2.16" +version = "0.2.17" dependencies = [ "futures-util", "hmac", @@ -523,7 +523,7 @@ dependencies = [ [[package]] name = "longbridge-proto" -version = "0.2.16" +version = "0.2.17" dependencies = [ "prost", "prost-build", @@ -531,7 +531,7 @@ dependencies = [ [[package]] name = "longbridge-wscli" -version = "0.2.16" +version = "0.2.17" dependencies = [ "byteorder", "flate2", diff --git a/rust/crates/httpclient/src/error.rs b/rust/crates/httpclient/src/error.rs index 2fef8808b8..a8f1796dfb 100644 --- a/rust/crates/httpclient/src/error.rs +++ b/rust/crates/httpclient/src/error.rs @@ -1,3 +1,5 @@ +use reqwest::StatusCode; + use crate::qs::QsError; /// Http client error type @@ -35,14 +37,22 @@ pub enum HttpClientError { message: String, }, - /// JSON error - #[error(transparent)] - Json(#[from] serde_json::Error), + /// Deserialize response body + #[error("deserialize response body error: {0}")] + DeserializeResponseBody(serde_json::Error), + + /// Serialize request body + #[error("serialize request body error: {0}")] + SerializeRequestBody(serde_json::Error), /// Serialize query string error - #[error("serialize body: {0}")] + #[error("serialize query string error: {0}")] SerializeQueryString(#[from] QsError), + /// Bad status + #[error("status error: {0}")] + BadStatus(StatusCode), + /// Http error #[error(transparent)] Http(#[from] reqwest::Error), diff --git a/rust/crates/httpclient/src/request.rs b/rust/crates/httpclient/src/request.rs index 07b88117a8..07587303e0 100644 --- a/rust/crates/httpclient/src/request.rs +++ b/rust/crates/httpclient/src/request.rs @@ -116,7 +116,8 @@ where // set the request body if let Some(body) = self.body { - request_builder = request_builder.body(serde_json::to_string(&body)?); + request_builder = request_builder + .body(serde_json::to_string(&body).map_err(HttpClientError::SerializeRequestBody)?); } let mut request = request_builder.build().expect("invalid request"); @@ -143,25 +144,27 @@ where tracing::debug!(method = %request.method(), url = %request.url(), "http request"); // send request - let text = tokio::time::timeout(REQUEST_TIMEOUT, async move { + let (status, text) = tokio::time::timeout(REQUEST_TIMEOUT, async move { let resp = http_cli.execute(request).await?; - if !resp.status().is_success() && resp.status() != StatusCode::BAD_REQUEST { - resp.error_for_status_ref()?; - } - resp.text().await.map_err(HttpClientError::from) + let status = resp.status(); + let text = resp.text().await.map_err(HttpClientError::from)?; + Ok::<_, HttpClientError>((status, text)) }) .await .map_err(|_| HttpClientError::RequestTimeout)??; tracing::debug!(body = text.as_str(), "http response"); - let resp = serde_json::from_str::>(&text)?; - match resp.code { - 0 => resp.data.ok_or(HttpClientError::UnexpectedResponse), - _ => Err(HttpClientError::OpenApi { + match serde_json::from_str::>(&text) { + Ok(resp) if resp.code == 0 => resp.data.ok_or(HttpClientError::UnexpectedResponse), + Ok(resp) => Err(HttpClientError::OpenApi { code: resp.code, message: resp.message, }), + Err(err) if status == StatusCode::OK => { + Err(HttpClientError::DeserializeResponseBody(err)) + } + Err(_) => Err(HttpClientError::BadStatus(status)), } } } From 98673b0bdbec45d0270d962e4939b64c1ea8d222 Mon Sep 17 00:00:00 2001 From: Sunli Date: Thu, 16 Jun 2022 17:17:54 +0800 Subject: [PATCH 070/567] Update docs.yml --- .github/workflows/docs.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 36ca57a3ff..bbf18933ff 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -34,7 +34,7 @@ jobs: uses: actions/setup-java@v3 with: java-version: "11" - distribution: 'temurin' + distribution: "temurin" - name: Build Rust docs run: cargo doc -p longbridge --no-deps @@ -82,7 +82,7 @@ jobs: mv -f target/doc gh-pages/rust/$RUST_SDK_VERSION mv -f python/site gh-pages/python/$PYTHON_SDK_VERSION mv -f nodejs/docs gh-pages/nodejs/$NODEJS_SDK_VERSION - mv -f java/javasrc/target/site gh-pages/java/$JAVA_SDK_VERSION + mv -f java/javasrc/target/site/apidocs gh-pages/java/$JAVA_SDK_VERSION python .github/misc/render_docs_index.py > gh-pages/index.html - name: Deploy uses: peaceiris/actions-gh-pages@v2.5.1 From 62b46a2bbd6a33bab973a42a322d1f11310936dc Mon Sep 17 00:00:00 2001 From: Sunli Date: Thu, 16 Jun 2022 20:04:02 +0800 Subject: [PATCH 071/567] Update pom.xml --- java/javasrc/pom.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/java/javasrc/pom.xml b/java/javasrc/pom.xml index da23c3c51c..158b4c2298 100644 --- a/java/javasrc/pom.xml +++ b/java/javasrc/pom.xml @@ -5,6 +5,9 @@ io.github.longbridgeapp openapi-sdk 0.0.1 + longbridge-openapi + Longbridge OpenAPI SDK for Java + https://github.com/longbridgeapp/openapi-sdk jar From e57b1f4d11e438a55cd86c419e898a9a65bdde0d Mon Sep 17 00:00:00 2001 From: Sunli Date: Thu, 16 Jun 2022 20:39:23 +0800 Subject: [PATCH 072/567] Update pom.xml --- java/javasrc/pom.xml | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/java/javasrc/pom.xml b/java/javasrc/pom.xml index 158b4c2298..ac6519928e 100644 --- a/java/javasrc/pom.xml +++ b/java/javasrc/pom.xml @@ -83,6 +83,47 @@ release + + org.apache.maven.plugins + maven-source-plugin + 3.1.0 + true + + + attach-sources + + jar + + + + + true + true + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.0.0 + true + + + bundle-sources + package + + jar + + + + + 1024 + UTF-8 + protected + true + false + none + + org.apache.maven.plugins maven-gpg-plugin From dbf5d86cf396a35a300131aa2323210b0f54461f Mon Sep 17 00:00:00 2001 From: Sunli Date: Thu, 16 Jun 2022 23:49:37 +0800 Subject: [PATCH 073/567] Add examples for Java SDK --- examples/java/account_asset/pom.xml | 48 ++++++++++++++++++ .../examples/account_asset/main.java | 14 +++++ examples/java/submit_order/pom.xml | 48 ++++++++++++++++++ .../examples/submit_order/main.java | 16 ++++++ examples/java/subscribe_quote/pom.xml | 48 ++++++++++++++++++ .../examples/subscribe_quote/main.java | 19 +++++++ examples/java/today_orders/pom.xml | 48 ++++++++++++++++++ .../examples/today_orders/main.java | 15 ++++++ java/Makefile.toml | 39 +++++++------- .../main/java/com/longbridge/SdkNative.java | 6 ++- .../java/com/longbridge/quote/Brokers.java | 7 +++ .../com/longbridge/quote/Candlestick.java | 6 +++ .../main/java/com/longbridge/quote/Depth.java | 6 +++ .../com/longbridge/quote/IntradayLine.java | 6 +++ .../java/com/longbridge/quote/IssuerInfo.java | 6 +++ .../longbridge/quote/MarketTradingDays.java | 7 +++ .../quote/MarketTradingSession.java | 7 +++ .../com/longbridge/quote/OptionQuote.java | 11 ++++ .../com/longbridge/quote/ParticipantInfo.java | 8 +++ .../com/longbridge/quote/PrePostQuote.java | 6 +++ .../com/longbridge/quote/PushBrokers.java | 8 +++ .../java/com/longbridge/quote/PushDepth.java | 7 +++ .../java/com/longbridge/quote/PushQuote.java | 7 +++ .../java/com/longbridge/quote/PushTrades.java | 7 +++ .../com/longbridge/quote/RealtimeQuote.java | 7 +++ .../com/longbridge/quote/SecurityBrokers.java | 8 +++ .../com/longbridge/quote/SecurityDepth.java | 7 +++ .../com/longbridge/quote/SecurityQuote.java | 8 +++ .../longbridge/quote/SecurityStaticInfo.java | 10 ++++ .../com/longbridge/quote/StrikePriceInfo.java | 6 +++ .../com/longbridge/quote/Subscription.java | 5 ++ .../main/java/com/longbridge/quote/Trade.java | 6 +++ .../longbridge/quote/TradingSessionInfo.java | 6 +++ .../com/longbridge/quote/WarrantQuote.java | 12 +++++ .../com/longbridge/trade/AccountBalance.java | 9 ++++ .../java/com/longbridge/trade/CashFlow.java | 7 +++ .../java/com/longbridge/trade/CashInfo.java | 6 +++ .../java/com/longbridge/trade/Execution.java | 6 +++ .../com/longbridge/trade/FundPosition.java | 7 +++ .../longbridge/trade/FundPositionChannel.java | 8 +++ .../trade/FundPositionsResponse.java | 7 +++ .../longbridge/trade/GetCashFlowOptions.java | 12 +++-- .../trade/GetFundPositionsOptions.java | 3 +- .../trade/GetHistoryExecutionsOptions.java | 9 ++-- .../trade/GetHistoryOrdersOptions.java | 18 ++++--- .../trade/GetStockPositionsOptions.java | 3 +- .../trade/GetTodayExecutionsOptions.java | 6 ++- .../trade/GetTodayOrdersOptions.java | 12 +++-- .../main/java/com/longbridge/trade/Order.java | 12 +++++ .../longbridge/trade/PushOrderChanged.java | 12 +++++ .../longbridge/trade/ReplaceOrderOptions.java | 18 ++++--- .../com/longbridge/trade/StockPosition.java | 7 +++ .../trade/StockPositionChannel.java | 8 +++ .../trade/StockPositionsResponse.java | 7 +++ .../longbridge/trade/SubmitOrderOptions.java | 24 ++++++--- .../longbridge/trade/SubmitOrderResponse.java | 5 ++ java/libs/native-lib-loader-2.4.0.jar | Bin 0 -> 20592 bytes java/libs/slf4j-api-1.7.30.jar | Bin 0 -> 41472 bytes java/src/quote_context.rs | 4 +- java/src/trade_context.rs | 13 ++++- java/test/Main.java | 15 ++++++ 61 files changed, 653 insertions(+), 60 deletions(-) create mode 100644 examples/java/account_asset/pom.xml create mode 100644 examples/java/account_asset/src/main/java/com/longbridge/examples/account_asset/main.java create mode 100644 examples/java/submit_order/pom.xml create mode 100644 examples/java/submit_order/src/main/java/com/longbridge/examples/submit_order/main.java create mode 100644 examples/java/subscribe_quote/pom.xml create mode 100644 examples/java/subscribe_quote/src/main/java/com/longbridge/examples/subscribe_quote/main.java create mode 100644 examples/java/today_orders/pom.xml create mode 100644 examples/java/today_orders/src/main/java/com/longbridge/examples/today_orders/main.java create mode 100644 java/libs/native-lib-loader-2.4.0.jar create mode 100644 java/libs/slf4j-api-1.7.30.jar create mode 100644 java/test/Main.java diff --git a/examples/java/account_asset/pom.xml b/examples/java/account_asset/pom.xml new file mode 100644 index 0000000000..ef1d38bfca --- /dev/null +++ b/examples/java/account_asset/pom.xml @@ -0,0 +1,48 @@ + + + 4.0.0 + + io.github.longbridgeapp + subscribe_quote + 0.0.1 + + + + io.github.longbridgeapp + openapi-sdk + 0.2.17 + + + + + + + maven-compiler-plugin + 3.10.1 + + + org.codehaus.mojo + exec-maven-plugin + 1.1.1 + + + test + + java + + + com.longbridge.examples.subscribe_quote.Main + + + + + + + + + 1.11 + 11 + 11 + utf-8 + + \ No newline at end of file diff --git a/examples/java/account_asset/src/main/java/com/longbridge/examples/account_asset/main.java b/examples/java/account_asset/src/main/java/com/longbridge/examples/account_asset/main.java new file mode 100644 index 0000000000..97b219400e --- /dev/null +++ b/examples/java/account_asset/src/main/java/com/longbridge/examples/account_asset/main.java @@ -0,0 +1,14 @@ +import com.longbridge.*; +import com.longbridge.trade.*; + +class Main { + public static void main(String[] args) throws Exception { + try (Config config = Config.fromEnv()) { + try (TradeContext ctx = TradeContext.create(config).get()) { + for (AccountBalance obj : ctx.getAccountBalance().get()) { + System.out.println(obj); + } + } + } + } +} \ No newline at end of file diff --git a/examples/java/submit_order/pom.xml b/examples/java/submit_order/pom.xml new file mode 100644 index 0000000000..ef1d38bfca --- /dev/null +++ b/examples/java/submit_order/pom.xml @@ -0,0 +1,48 @@ + + + 4.0.0 + + io.github.longbridgeapp + subscribe_quote + 0.0.1 + + + + io.github.longbridgeapp + openapi-sdk + 0.2.17 + + + + + + + maven-compiler-plugin + 3.10.1 + + + org.codehaus.mojo + exec-maven-plugin + 1.1.1 + + + test + + java + + + com.longbridge.examples.subscribe_quote.Main + + + + + + + + + 1.11 + 11 + 11 + utf-8 + + \ No newline at end of file diff --git a/examples/java/submit_order/src/main/java/com/longbridge/examples/submit_order/main.java b/examples/java/submit_order/src/main/java/com/longbridge/examples/submit_order/main.java new file mode 100644 index 0000000000..d4ffc3ba73 --- /dev/null +++ b/examples/java/submit_order/src/main/java/com/longbridge/examples/submit_order/main.java @@ -0,0 +1,16 @@ +import com.longbridge.*; +import com.longbridge.quote.*; + +class Main { + public static void main(String[] args) throws Exception { + try (Config config = Config.fromEnv()) { + try (QuoteContext ctx = QuoteContext.create(config).get()) { + ctx.setOnQuote((symbol, quote) -> { + System.out.println("%s\t%s\n", symbol, quote); + }); + ctx.subscribe(new String[] { "700.HK", "AAPL.US", "TSLA.US", "NFLX.US" }, SubFlags.Quote, true).get(); + Thread.sleep(30000); + } + } + } +} \ No newline at end of file diff --git a/examples/java/subscribe_quote/pom.xml b/examples/java/subscribe_quote/pom.xml new file mode 100644 index 0000000000..ef1d38bfca --- /dev/null +++ b/examples/java/subscribe_quote/pom.xml @@ -0,0 +1,48 @@ + + + 4.0.0 + + io.github.longbridgeapp + subscribe_quote + 0.0.1 + + + + io.github.longbridgeapp + openapi-sdk + 0.2.17 + + + + + + + maven-compiler-plugin + 3.10.1 + + + org.codehaus.mojo + exec-maven-plugin + 1.1.1 + + + test + + java + + + com.longbridge.examples.subscribe_quote.Main + + + + + + + + + 1.11 + 11 + 11 + utf-8 + + \ No newline at end of file diff --git a/examples/java/subscribe_quote/src/main/java/com/longbridge/examples/subscribe_quote/main.java b/examples/java/subscribe_quote/src/main/java/com/longbridge/examples/subscribe_quote/main.java new file mode 100644 index 0000000000..78470a127e --- /dev/null +++ b/examples/java/subscribe_quote/src/main/java/com/longbridge/examples/subscribe_quote/main.java @@ -0,0 +1,19 @@ +import com.longbridge.*; +import com.longbridge.trade.*; +import java.math.BigDecimal; + +class Main { + public static void main(String[] args) throws Exception { + try (Config config = Config.fromEnv()) { + try (TradeContext ctx = TradeContext.create(config).get()) { + SubmitOrderOptions opts = new SubmitOrderOptions("700.HK", + OrderType.LO, + OrderSide.Buy, + 200, + TimeInForceType.Day).setSubmittedPrice(new BigDecimal(50)); + SubmitOrderResponse resp = ctx.submitOrder(opts).join(); + System.out.println(resp); + } + } + } +} \ No newline at end of file diff --git a/examples/java/today_orders/pom.xml b/examples/java/today_orders/pom.xml new file mode 100644 index 0000000000..ef1d38bfca --- /dev/null +++ b/examples/java/today_orders/pom.xml @@ -0,0 +1,48 @@ + + + 4.0.0 + + io.github.longbridgeapp + subscribe_quote + 0.0.1 + + + + io.github.longbridgeapp + openapi-sdk + 0.2.17 + + + + + + + maven-compiler-plugin + 3.10.1 + + + org.codehaus.mojo + exec-maven-plugin + 1.1.1 + + + test + + java + + + com.longbridge.examples.subscribe_quote.Main + + + + + + + + + 1.11 + 11 + 11 + utf-8 + + \ No newline at end of file diff --git a/examples/java/today_orders/src/main/java/com/longbridge/examples/today_orders/main.java b/examples/java/today_orders/src/main/java/com/longbridge/examples/today_orders/main.java new file mode 100644 index 0000000000..1e2e519923 --- /dev/null +++ b/examples/java/today_orders/src/main/java/com/longbridge/examples/today_orders/main.java @@ -0,0 +1,15 @@ +import com.longbridge.*; +import com.longbridge.trade.*; + +class Main { + public static void main(String[] args) throws Exception { + try (Config config = Config.fromEnv()) { + try (TradeContext ctx = TradeContext.create(config).get()) { + Order[] orders = ctx.getTodayOrders(null).join(); + for (Order order : orders) { + System.out.println(order); + } + } + } + } +} \ No newline at end of file diff --git a/java/Makefile.toml b/java/Makefile.toml index d8d13c8545..21436b2a72 100644 --- a/java/Makefile.toml +++ b/java/Makefile.toml @@ -59,27 +59,28 @@ fn main() { } ''' -[tasks.mvn-package.windows] -command = "mvn.cmd" -args = ["package", "-Dmaven.test.skip=true"] -cwd = "java/javasrc" - -[tasks.mvn-package] -command = "mvn" -args = ["package", "-Dmaven.test.skip=true"] -cwd = "java/javasrc" - [tasks.package-debug-jar] dependencies = ["java", "move-jni-target", "mvn-package"] -[tasks.test-java.windows] -command = "mvn.cmd" -args = ["test"] -cwd = "java/javasrc" -dependencies = ["package-debug-jar"] +[tasks.compile-java-test] +command = "javac" +cwd = "java" +args = [ + "-cp", + "libs/native-lib-loader-2.4.0.jar;libs/slf4j-api-1.7.30.jar;", + "-sourcepath", + "javasrc/src/main/java", + "-d", + "classes", + "test/Main.java", +] [tasks.test-java] -command = "mvn" -args = ["test"] -cwd = "java/javasrc" -dependencies = ["package-debug-jar"] +command = "java" +args = [ + "-Djava.library.path=target/debug", + "-cp", + "java/classes;java/libs/native-lib-loader-2.4.0.jar;java/libs/slf4j-api-1.7.30.jar;", + "Main", +] +dependencies = ["java", "compile-java-test"] diff --git a/java/javasrc/src/main/java/com/longbridge/SdkNative.java b/java/javasrc/src/main/java/com/longbridge/SdkNative.java index a541a3ef0f..da026f37f4 100644 --- a/java/javasrc/src/main/java/com/longbridge/SdkNative.java +++ b/java/javasrc/src/main/java/com/longbridge/SdkNative.java @@ -126,9 +126,11 @@ public static native void tradeContextStockPositions(long context, GetStockPosit static { try { NativeLoader.loadLibrary("longbridge_java"); - SdkNative.init(); } catch (IOException e) { - e.printStackTrace(); + System.load("longbridge_java"); + } finally { + SdkNative.init(); } + } } diff --git a/java/javasrc/src/main/java/com/longbridge/quote/Brokers.java b/java/javasrc/src/main/java/com/longbridge/quote/Brokers.java index 4f6a477cd6..24de0bb7ee 100644 --- a/java/javasrc/src/main/java/com/longbridge/quote/Brokers.java +++ b/java/javasrc/src/main/java/com/longbridge/quote/Brokers.java @@ -1,5 +1,7 @@ package com.longbridge.quote; +import java.util.Arrays; + public class Brokers { private int position; private int[] brokerIds; @@ -11,4 +13,9 @@ public int getPosition() { public int[] getBrokerIds() { return brokerIds; } + + @Override + public String toString() { + return "Brokers [brokerIds=" + Arrays.toString(brokerIds) + ", position=" + position + "]"; + } } diff --git a/java/javasrc/src/main/java/com/longbridge/quote/Candlestick.java b/java/javasrc/src/main/java/com/longbridge/quote/Candlestick.java index 88cb8d6921..a978d84640 100644 --- a/java/javasrc/src/main/java/com/longbridge/quote/Candlestick.java +++ b/java/javasrc/src/main/java/com/longbridge/quote/Candlestick.java @@ -39,4 +39,10 @@ public BigDecimal getTurnover() { public OffsetDateTime getTimestamp() { return timestamp; } + + @Override + public String toString() { + return "Candlestick [close=" + close + ", high=" + high + ", low=" + low + ", open=" + open + ", timestamp=" + + timestamp + ", turnover=" + turnover + ", volume=" + volume + "]"; + } } diff --git a/java/javasrc/src/main/java/com/longbridge/quote/Depth.java b/java/javasrc/src/main/java/com/longbridge/quote/Depth.java index aa2a4bd10f..a53939b61e 100644 --- a/java/javasrc/src/main/java/com/longbridge/quote/Depth.java +++ b/java/javasrc/src/main/java/com/longbridge/quote/Depth.java @@ -23,4 +23,10 @@ public long getVolume() { public long getOrderNum() { return orderNum; } + + @Override + public String toString() { + return "Depth [orderNum=" + orderNum + ", position=" + position + ", price=" + price + ", volume=" + volume + + "]"; + } } diff --git a/java/javasrc/src/main/java/com/longbridge/quote/IntradayLine.java b/java/javasrc/src/main/java/com/longbridge/quote/IntradayLine.java index f393a1fac0..a4bd3c175c 100644 --- a/java/javasrc/src/main/java/com/longbridge/quote/IntradayLine.java +++ b/java/javasrc/src/main/java/com/longbridge/quote/IntradayLine.java @@ -29,4 +29,10 @@ public BigDecimal getTurnover() { public BigDecimal getAvgPrice() { return avgPrice; } + + @Override + public String toString() { + return "IntradayLine [avgPrice=" + avgPrice + ", price=" + price + ", timestamp=" + timestamp + ", turnover=" + + turnover + ", volume=" + volume + "]"; + } } diff --git a/java/javasrc/src/main/java/com/longbridge/quote/IssuerInfo.java b/java/javasrc/src/main/java/com/longbridge/quote/IssuerInfo.java index e6e6197f5a..7018fc8597 100644 --- a/java/javasrc/src/main/java/com/longbridge/quote/IssuerInfo.java +++ b/java/javasrc/src/main/java/com/longbridge/quote/IssuerInfo.java @@ -21,4 +21,10 @@ public String getNameEn() { public String getNameHk() { return nameHk; } + + @Override + public String toString() { + return "IssuerInfo [issuerId=" + issuerId + ", nameCn=" + nameCn + ", nameEn=" + nameEn + ", nameHk=" + nameHk + + "]"; + } } diff --git a/java/javasrc/src/main/java/com/longbridge/quote/MarketTradingDays.java b/java/javasrc/src/main/java/com/longbridge/quote/MarketTradingDays.java index 1d2580f399..1536d0520c 100644 --- a/java/javasrc/src/main/java/com/longbridge/quote/MarketTradingDays.java +++ b/java/javasrc/src/main/java/com/longbridge/quote/MarketTradingDays.java @@ -1,6 +1,7 @@ package com.longbridge.quote; import java.time.LocalDate; +import java.util.Arrays; public class MarketTradingDays { private LocalDate[] tradingDays; @@ -13,4 +14,10 @@ public LocalDate[] getTradingDays() { public LocalDate[] getHalfTradingDays() { return halfTradingDays; } + + @Override + public String toString() { + return "MarketTradingDays [halfTradingDays=" + Arrays.toString(halfTradingDays) + ", tradingDays=" + + Arrays.toString(tradingDays) + "]"; + } } diff --git a/java/javasrc/src/main/java/com/longbridge/quote/MarketTradingSession.java b/java/javasrc/src/main/java/com/longbridge/quote/MarketTradingSession.java index 1e784154e8..1d15b5ec21 100644 --- a/java/javasrc/src/main/java/com/longbridge/quote/MarketTradingSession.java +++ b/java/javasrc/src/main/java/com/longbridge/quote/MarketTradingSession.java @@ -1,5 +1,7 @@ package com.longbridge.quote; +import java.util.Arrays; + import com.longbridge.Market; public class MarketTradingSession { @@ -13,4 +15,9 @@ public Market getMarket() { public TradingSessionInfo[] getTradeSession() { return tradeSession; } + + @Override + public String toString() { + return "MarketTradingSession [market=" + market + ", tradeSession=" + Arrays.toString(tradeSession) + "]"; + } } diff --git a/java/javasrc/src/main/java/com/longbridge/quote/OptionQuote.java b/java/javasrc/src/main/java/com/longbridge/quote/OptionQuote.java index fc4000f792..bae27df171 100644 --- a/java/javasrc/src/main/java/com/longbridge/quote/OptionQuote.java +++ b/java/javasrc/src/main/java/com/longbridge/quote/OptionQuote.java @@ -105,4 +105,15 @@ public BigDecimal getHistoricalVolatility() { public String getUnderlyingSymbol() { return underlyingSymbol; } + + @Override + public String toString() { + return "OptionQuote [contractMultiplier=" + contractMultiplier + ", contractSize=" + contractSize + + ", contractType=" + contractType + ", direction=" + direction + ", expiryDate=" + expiryDate + + ", high=" + high + ", historicalVolatility=" + historicalVolatility + ", impliedVolatility=" + + impliedVolatility + ", lastDone=" + lastDone + ", low=" + low + ", open=" + open + ", openInterest=" + + openInterest + ", prevClose=" + prevClose + ", strikePrice=" + strikePrice + ", symbol=" + symbol + + ", timestamp=" + timestamp + ", tradeStatus=" + tradeStatus + ", turnover=" + turnover + + ", underlyingSymbol=" + underlyingSymbol + ", volume=" + volume + "]"; + } } \ No newline at end of file diff --git a/java/javasrc/src/main/java/com/longbridge/quote/ParticipantInfo.java b/java/javasrc/src/main/java/com/longbridge/quote/ParticipantInfo.java index 1a9822f2e5..96b63534a4 100644 --- a/java/javasrc/src/main/java/com/longbridge/quote/ParticipantInfo.java +++ b/java/javasrc/src/main/java/com/longbridge/quote/ParticipantInfo.java @@ -1,5 +1,7 @@ package com.longbridge.quote; +import java.util.Arrays; + public class ParticipantInfo { private int[] brokerIds; private String nameCn; @@ -21,4 +23,10 @@ public String getNameEn() { public String getNameHk() { return nameHk; } + + @Override + public String toString() { + return "ParticipantInfo [brokerIds=" + Arrays.toString(brokerIds) + ", nameCn=" + nameCn + ", nameEn=" + nameEn + + ", nameHk=" + nameHk + "]"; + } } diff --git a/java/javasrc/src/main/java/com/longbridge/quote/PrePostQuote.java b/java/javasrc/src/main/java/com/longbridge/quote/PrePostQuote.java index bd888b83f4..13c1df1441 100644 --- a/java/javasrc/src/main/java/com/longbridge/quote/PrePostQuote.java +++ b/java/javasrc/src/main/java/com/longbridge/quote/PrePostQuote.java @@ -39,4 +39,10 @@ public BigDecimal getLow() { public BigDecimal getPrevClose() { return prevClose; } + + @Override + public String toString() { + return "PrePostQuote [high=" + high + ", lastDone=" + lastDone + ", low=" + low + ", prevClose=" + prevClose + + ", timestamp=" + timestamp + ", turnover=" + turnover + ", volume=" + volume + "]"; + } } diff --git a/java/javasrc/src/main/java/com/longbridge/quote/PushBrokers.java b/java/javasrc/src/main/java/com/longbridge/quote/PushBrokers.java index ce121f5805..8025e54ecb 100644 --- a/java/javasrc/src/main/java/com/longbridge/quote/PushBrokers.java +++ b/java/javasrc/src/main/java/com/longbridge/quote/PushBrokers.java @@ -1,5 +1,7 @@ package com.longbridge.quote; +import java.util.Arrays; + public class PushBrokers { private Brokers[] askBrokers; private Brokers[] bidBrokers; @@ -11,4 +13,10 @@ public Brokers[] getAskBrokers() { public Brokers[] getBidBrokers() { return bidBrokers; } + + @Override + public String toString() { + return "PushBrokers [askBrokers=" + Arrays.toString(askBrokers) + ", bidBrokers=" + Arrays.toString(bidBrokers) + + "]"; + } } diff --git a/java/javasrc/src/main/java/com/longbridge/quote/PushDepth.java b/java/javasrc/src/main/java/com/longbridge/quote/PushDepth.java index 244b1d8f90..afe73297c3 100644 --- a/java/javasrc/src/main/java/com/longbridge/quote/PushDepth.java +++ b/java/javasrc/src/main/java/com/longbridge/quote/PushDepth.java @@ -1,5 +1,7 @@ package com.longbridge.quote; +import java.util.Arrays; + public class PushDepth { private Depth[] asks; private Depth[] bids; @@ -11,4 +13,9 @@ public Depth[] getAsks() { public Depth[] getBids() { return bids; } + + @Override + public String toString() { + return "PushDepth [asks=" + Arrays.toString(asks) + ", bids=" + Arrays.toString(bids) + "]"; + } } diff --git a/java/javasrc/src/main/java/com/longbridge/quote/PushQuote.java b/java/javasrc/src/main/java/com/longbridge/quote/PushQuote.java index 90f76bdcc1..dc978ade55 100644 --- a/java/javasrc/src/main/java/com/longbridge/quote/PushQuote.java +++ b/java/javasrc/src/main/java/com/longbridge/quote/PushQuote.java @@ -50,4 +50,11 @@ public TradeSession getTradeSession() { return tradeSession; } + @Override + public String toString() { + return "PushQuote [high=" + high + ", lastDone=" + lastDone + ", low=" + low + ", open=" + open + ", timestamp=" + + timestamp + ", tradeSession=" + tradeSession + ", tradeStatus=" + tradeStatus + ", turnover=" + + turnover + ", volume=" + volume + "]"; + } + } diff --git a/java/javasrc/src/main/java/com/longbridge/quote/PushTrades.java b/java/javasrc/src/main/java/com/longbridge/quote/PushTrades.java index 8701fe63f9..3963457899 100644 --- a/java/javasrc/src/main/java/com/longbridge/quote/PushTrades.java +++ b/java/javasrc/src/main/java/com/longbridge/quote/PushTrades.java @@ -1,9 +1,16 @@ package com.longbridge.quote; +import java.util.Arrays; + public class PushTrades { private Trade[] trades; public Trade[] getTrades() { return trades; } + + @Override + public String toString() { + return "PushTrades [trades=" + Arrays.toString(trades) + "]"; + } } diff --git a/java/javasrc/src/main/java/com/longbridge/quote/RealtimeQuote.java b/java/javasrc/src/main/java/com/longbridge/quote/RealtimeQuote.java index a4d12e9139..0e8381f8b2 100644 --- a/java/javasrc/src/main/java/com/longbridge/quote/RealtimeQuote.java +++ b/java/javasrc/src/main/java/com/longbridge/quote/RealtimeQuote.java @@ -49,4 +49,11 @@ public BigDecimal getTurnover() { public TradeStatus getTradeStatus() { return tradeStatus; } + + @Override + public String toString() { + return "RealtimeQuote [high=" + high + ", lastDone=" + lastDone + ", low=" + low + ", open=" + open + + ", symbol=" + symbol + ", timestamp=" + timestamp + ", tradeStatus=" + tradeStatus + ", turnover=" + + turnover + ", volume=" + volume + "]"; + } } diff --git a/java/javasrc/src/main/java/com/longbridge/quote/SecurityBrokers.java b/java/javasrc/src/main/java/com/longbridge/quote/SecurityBrokers.java index e50fcbe17e..df25296f4b 100644 --- a/java/javasrc/src/main/java/com/longbridge/quote/SecurityBrokers.java +++ b/java/javasrc/src/main/java/com/longbridge/quote/SecurityBrokers.java @@ -1,5 +1,7 @@ package com.longbridge.quote; +import java.util.Arrays; + public class SecurityBrokers { private Brokers[] askBrokers; private Brokers[] bidBrokers; @@ -11,4 +13,10 @@ public Brokers[] getAskBrokers() { public Brokers[] getBidBrokers() { return bidBrokers; } + + @Override + public String toString() { + return "SecurityBrokers [askBrokers=" + Arrays.toString(askBrokers) + ", bidBrokers=" + + Arrays.toString(bidBrokers) + "]"; + } } diff --git a/java/javasrc/src/main/java/com/longbridge/quote/SecurityDepth.java b/java/javasrc/src/main/java/com/longbridge/quote/SecurityDepth.java index b31f5357ad..04575efbf9 100644 --- a/java/javasrc/src/main/java/com/longbridge/quote/SecurityDepth.java +++ b/java/javasrc/src/main/java/com/longbridge/quote/SecurityDepth.java @@ -1,5 +1,7 @@ package com.longbridge.quote; +import java.util.Arrays; + public class SecurityDepth { private Depth[] asks; private Depth[] bids; @@ -11,4 +13,9 @@ public Depth[] getAsks() { public Depth[] getBids() { return bids; } + + @Override + public String toString() { + return "SecurityDepth [asks=" + Arrays.toString(asks) + ", bids=" + Arrays.toString(bids) + "]"; + } } diff --git a/java/javasrc/src/main/java/com/longbridge/quote/SecurityQuote.java b/java/javasrc/src/main/java/com/longbridge/quote/SecurityQuote.java index 9c010bf240..fb6d71b4ae 100644 --- a/java/javasrc/src/main/java/com/longbridge/quote/SecurityQuote.java +++ b/java/javasrc/src/main/java/com/longbridge/quote/SecurityQuote.java @@ -64,4 +64,12 @@ public PrePostQuote getPreMarketQuote() { public PrePostQuote getPostMarketQuote() { return postMarketQuote; } + + @Override + public String toString() { + return "SecurityQuote [high=" + high + ", lastDone=" + lastDone + ", low=" + low + ", open=" + open + + ", postMarketQuote=" + postMarketQuote + ", preMarketQuote=" + preMarketQuote + ", prevClose=" + + prevClose + ", symbol=" + symbol + ", timestamp=" + timestamp + ", tradeStatus=" + tradeStatus + + ", turnover=" + turnover + ", volume=" + volume + "]"; + } } \ No newline at end of file diff --git a/java/javasrc/src/main/java/com/longbridge/quote/SecurityStaticInfo.java b/java/javasrc/src/main/java/com/longbridge/quote/SecurityStaticInfo.java index 2a40d98141..8a20fb3989 100644 --- a/java/javasrc/src/main/java/com/longbridge/quote/SecurityStaticInfo.java +++ b/java/javasrc/src/main/java/com/longbridge/quote/SecurityStaticInfo.java @@ -1,6 +1,7 @@ package com.longbridge.quote; import java.math.BigDecimal; +import java.util.Arrays; public class SecurityStaticInfo { private String symbol; @@ -78,4 +79,13 @@ public BigDecimal getDividendYield() { public DerivativeType[] getStockDerivatives() { return stockDerivatives; } + + @Override + public String toString() { + return "SecurityStaticInfo [bps=" + bps + ", circulatingShares=" + circulatingShares + ", currency=" + currency + + ", dividendYield=" + dividendYield + ", eps=" + eps + ", epsTtm=" + epsTtm + ", exchange=" + exchange + + ", hkShares=" + hkShares + ", lotSize=" + lotSize + ", nameCn=" + nameCn + ", nameEn=" + nameEn + + ", nameHk=" + nameHk + ", stockDerivatives=" + Arrays.toString(stockDerivatives) + ", symbol=" + + symbol + ", totalShares=" + totalShares + "]"; + } } diff --git a/java/javasrc/src/main/java/com/longbridge/quote/StrikePriceInfo.java b/java/javasrc/src/main/java/com/longbridge/quote/StrikePriceInfo.java index 80efbd861b..b35dbc1352 100644 --- a/java/javasrc/src/main/java/com/longbridge/quote/StrikePriceInfo.java +++ b/java/javasrc/src/main/java/com/longbridge/quote/StrikePriceInfo.java @@ -23,4 +23,10 @@ public String getPutSymbol() { public boolean isStandard() { return standard; } + + @Override + public String toString() { + return "StrikePriceInfo [callSymbol=" + callSymbol + ", price=" + price + ", putSymbol=" + putSymbol + + ", standard=" + standard + "]"; + } } diff --git a/java/javasrc/src/main/java/com/longbridge/quote/Subscription.java b/java/javasrc/src/main/java/com/longbridge/quote/Subscription.java index bc5db690be..fe24e3aa6f 100644 --- a/java/javasrc/src/main/java/com/longbridge/quote/Subscription.java +++ b/java/javasrc/src/main/java/com/longbridge/quote/Subscription.java @@ -11,4 +11,9 @@ public String getSymbol() { public int getSubTypes() { return subTypes; } + + @Override + public String toString() { + return "Subscription [subTypes=" + subTypes + ", symbol=" + symbol + "]"; + } } diff --git a/java/javasrc/src/main/java/com/longbridge/quote/Trade.java b/java/javasrc/src/main/java/com/longbridge/quote/Trade.java index d2a1eb385a..8c56aadc38 100644 --- a/java/javasrc/src/main/java/com/longbridge/quote/Trade.java +++ b/java/javasrc/src/main/java/com/longbridge/quote/Trade.java @@ -34,4 +34,10 @@ public TradeDirection getDirection() { public TradeSession getTrade_sessions() { return trade_sessions; } + + @Override + public String toString() { + return "Trade [direction=" + direction + ", price=" + price + ", timestamp=" + timestamp + ", tradeType=" + + tradeType + ", trade_sessions=" + trade_sessions + ", volume=" + volume + "]"; + } } diff --git a/java/javasrc/src/main/java/com/longbridge/quote/TradingSessionInfo.java b/java/javasrc/src/main/java/com/longbridge/quote/TradingSessionInfo.java index 6930d37c2b..bdc66dd9e1 100644 --- a/java/javasrc/src/main/java/com/longbridge/quote/TradingSessionInfo.java +++ b/java/javasrc/src/main/java/com/longbridge/quote/TradingSessionInfo.java @@ -18,4 +18,10 @@ public LocalTime getEndTime() { public TradeSession getTradeSession() { return tradeSession; } + + @Override + public String toString() { + return "TradingSessionInfo [beginTime=" + beginTime + ", endTime=" + endTime + ", tradeSession=" + tradeSession + + "]"; + } } diff --git a/java/javasrc/src/main/java/com/longbridge/quote/WarrantQuote.java b/java/javasrc/src/main/java/com/longbridge/quote/WarrantQuote.java index f3aa306f05..da77316f6b 100644 --- a/java/javasrc/src/main/java/com/longbridge/quote/WarrantQuote.java +++ b/java/javasrc/src/main/java/com/longbridge/quote/WarrantQuote.java @@ -120,4 +120,16 @@ public BigDecimal getCall_price() { public String getUnderlyingSymbol() { return underlyingSymbol; } + + @Override + public String toString() { + return "WarrantQuote [call_price=" + call_price + ", category=" + category + ", conversionRatio=" + + conversionRatio + ", expiryDate=" + expiryDate + ", high=" + high + ", impliedVolatility=" + + impliedVolatility + ", lastDone=" + lastDone + ", lastTradeDate=" + lastTradeDate + ", low=" + low + + ", lowerStrikePrice=" + lowerStrikePrice + ", open=" + open + ", openInterest=" + openInterest + + ", outstandingQuantity=" + outstandingQuantity + ", outstandingRatio=" + outstandingRatio + + ", prevClose=" + prevClose + ", strikePrice=" + strikePrice + ", symbol=" + symbol + ", timestamp=" + + timestamp + ", tradeStatus=" + tradeStatus + ", turnover=" + turnover + ", underlyingSymbol=" + + underlyingSymbol + ", upperStrikePrice=" + upperStrikePrice + ", volume=" + volume + "]"; + } } diff --git a/java/javasrc/src/main/java/com/longbridge/trade/AccountBalance.java b/java/javasrc/src/main/java/com/longbridge/trade/AccountBalance.java index 3fb9833c68..eda21be4bc 100644 --- a/java/javasrc/src/main/java/com/longbridge/trade/AccountBalance.java +++ b/java/javasrc/src/main/java/com/longbridge/trade/AccountBalance.java @@ -1,6 +1,7 @@ package com.longbridge.trade; import java.math.BigDecimal; +import java.util.Arrays; public class AccountBalance { private BigDecimal totalCash; @@ -53,4 +54,12 @@ public BigDecimal getInitMargin() { public BigDecimal getMaintenanceMargin() { return maintenanceMargin; } + + @Override + public String toString() { + return "AccountBalance [cashInfos=" + Arrays.toString(cashInfos) + ", currency=" + currency + ", initMargin=" + + initMargin + ", maintenanceMargin=" + maintenanceMargin + ", marginCall=" + marginCall + + ", maxFinanceAmount=" + maxFinanceAmount + ", netAssets=" + netAssets + ", remainingFinanceAmount=" + + remainingFinanceAmount + ", riskLevel=" + riskLevel + ", totalCash=" + totalCash + "]"; + } } diff --git a/java/javasrc/src/main/java/com/longbridge/trade/CashFlow.java b/java/javasrc/src/main/java/com/longbridge/trade/CashFlow.java index 2aaffb7d96..995331246d 100644 --- a/java/javasrc/src/main/java/com/longbridge/trade/CashFlow.java +++ b/java/javasrc/src/main/java/com/longbridge/trade/CashFlow.java @@ -44,4 +44,11 @@ public String getSymbol() { public String getDescription() { return description; } + + @Override + public String toString() { + return "CashFlow [balance=" + balance + ", businessTime=" + businessTime + ", businessType=" + businessType + + ", currency=" + currency + ", description=" + description + ", direction=" + direction + ", symbol=" + + symbol + ", transactionFlowName=" + transactionFlowName + "]"; + } } diff --git a/java/javasrc/src/main/java/com/longbridge/trade/CashInfo.java b/java/javasrc/src/main/java/com/longbridge/trade/CashInfo.java index 07ad51e9d5..5129a858ba 100644 --- a/java/javasrc/src/main/java/com/longbridge/trade/CashInfo.java +++ b/java/javasrc/src/main/java/com/longbridge/trade/CashInfo.java @@ -28,4 +28,10 @@ public BigDecimal getSettlingCash() { public String getCurrency() { return currency; } + + @Override + public String toString() { + return "CashInfo [availableCash=" + availableCash + ", currency=" + currency + ", frozenCash=" + frozenCash + + ", settlingCash=" + settlingCash + ", withdrawCash=" + withdrawCash + "]"; + } } diff --git a/java/javasrc/src/main/java/com/longbridge/trade/Execution.java b/java/javasrc/src/main/java/com/longbridge/trade/Execution.java index 98ab5b4a34..d9ec1f5ce8 100644 --- a/java/javasrc/src/main/java/com/longbridge/trade/Execution.java +++ b/java/javasrc/src/main/java/com/longbridge/trade/Execution.java @@ -34,4 +34,10 @@ public long getQuantity() { public BigDecimal getPrice() { return price; } + + @Override + public String toString() { + return "Execution [orderId=" + orderId + ", price=" + price + ", quantity=" + quantity + ", symbol=" + symbol + + ", tradeDoneAt=" + tradeDoneAt + ", tradeId=" + tradeId + "]"; + } } diff --git a/java/javasrc/src/main/java/com/longbridge/trade/FundPosition.java b/java/javasrc/src/main/java/com/longbridge/trade/FundPosition.java index 30cdb9eee7..756aebd245 100644 --- a/java/javasrc/src/main/java/com/longbridge/trade/FundPosition.java +++ b/java/javasrc/src/main/java/com/longbridge/trade/FundPosition.java @@ -38,4 +38,11 @@ public BigDecimal getCostNetAssetValue() { public BigDecimal getHoldingUnits() { return holdingUnits; } + + @Override + public String toString() { + return "FundPosition [costNetAssetValue=" + costNetAssetValue + ", currency=" + currency + + ", currentNetAssetValue=" + currentNetAssetValue + ", holdingUnits=" + holdingUnits + + ", netAssetValueDay=" + netAssetValueDay + ", symbol=" + symbol + ", symbolName=" + symbolName + "]"; + } } diff --git a/java/javasrc/src/main/java/com/longbridge/trade/FundPositionChannel.java b/java/javasrc/src/main/java/com/longbridge/trade/FundPositionChannel.java index f934a448d7..e0c73d12b1 100644 --- a/java/javasrc/src/main/java/com/longbridge/trade/FundPositionChannel.java +++ b/java/javasrc/src/main/java/com/longbridge/trade/FundPositionChannel.java @@ -1,5 +1,7 @@ package com.longbridge.trade; +import java.util.Arrays; + public class FundPositionChannel { private String accountChannel; private FundPosition[] positions; @@ -11,4 +13,10 @@ public String getAccountChannel() { public FundPosition[] getPositions() { return positions; } + + @Override + public String toString() { + return "FundPositionChannel [accountChannel=" + accountChannel + ", positions=" + Arrays.toString(positions) + + "]"; + } } diff --git a/java/javasrc/src/main/java/com/longbridge/trade/FundPositionsResponse.java b/java/javasrc/src/main/java/com/longbridge/trade/FundPositionsResponse.java index 470513178b..0cc76ed3e9 100644 --- a/java/javasrc/src/main/java/com/longbridge/trade/FundPositionsResponse.java +++ b/java/javasrc/src/main/java/com/longbridge/trade/FundPositionsResponse.java @@ -1,9 +1,16 @@ package com.longbridge.trade; +import java.util.Arrays; + public class FundPositionsResponse { private FundPositionChannel[] channels; public FundPositionChannel[] getChannels() { return channels; } + + @Override + public String toString() { + return "FundPositionsResponse [channels=" + Arrays.toString(channels) + "]"; + } } diff --git a/java/javasrc/src/main/java/com/longbridge/trade/GetCashFlowOptions.java b/java/javasrc/src/main/java/com/longbridge/trade/GetCashFlowOptions.java index 13a7a96007..a0911116ca 100644 --- a/java/javasrc/src/main/java/com/longbridge/trade/GetCashFlowOptions.java +++ b/java/javasrc/src/main/java/com/longbridge/trade/GetCashFlowOptions.java @@ -20,20 +20,24 @@ public BalanceType getBusinessType() { return businessType; } - public void setBusinessType(BalanceType businessType) { + public GetCashFlowOptions setBusinessType(BalanceType businessType) { this.businessType = businessType; + return this; } - public void setSymbol(String symbol) { + public GetCashFlowOptions setSymbol(String symbol) { this.symbol = symbol; + return this; } - public void setPage(int page) { + public GetCashFlowOptions setPage(int page) { this.page = page; + return this; } - public void setSize(int size) { + public GetCashFlowOptions setSize(int size) { this.size = size; + return this; } } diff --git a/java/javasrc/src/main/java/com/longbridge/trade/GetFundPositionsOptions.java b/java/javasrc/src/main/java/com/longbridge/trade/GetFundPositionsOptions.java index 74dc187029..a3619259e3 100644 --- a/java/javasrc/src/main/java/com/longbridge/trade/GetFundPositionsOptions.java +++ b/java/javasrc/src/main/java/com/longbridge/trade/GetFundPositionsOptions.java @@ -4,7 +4,8 @@ public class GetFundPositionsOptions { private String[] symbols; - public void setSymbols(String[] symbols) { + public GetFundPositionsOptions setSymbols(String[] symbols) { this.symbols = symbols; + return this; } } diff --git a/java/javasrc/src/main/java/com/longbridge/trade/GetHistoryExecutionsOptions.java b/java/javasrc/src/main/java/com/longbridge/trade/GetHistoryExecutionsOptions.java index 498661671a..949187d42f 100644 --- a/java/javasrc/src/main/java/com/longbridge/trade/GetHistoryExecutionsOptions.java +++ b/java/javasrc/src/main/java/com/longbridge/trade/GetHistoryExecutionsOptions.java @@ -8,15 +8,18 @@ public class GetHistoryExecutionsOptions { private OffsetDateTime startAt; private OffsetDateTime endAt; - public void setSymbol(String symbol) { + public GetHistoryExecutionsOptions setSymbol(String symbol) { this.symbol = symbol; + return this; } - public void setStartAt(OffsetDateTime startAt) { + public GetHistoryExecutionsOptions setStartAt(OffsetDateTime startAt) { this.startAt = startAt; + return this; } - public void setEndAt(OffsetDateTime endAt) { + public GetHistoryExecutionsOptions setEndAt(OffsetDateTime endAt) { this.endAt = endAt; + return this; } } diff --git a/java/javasrc/src/main/java/com/longbridge/trade/GetHistoryOrdersOptions.java b/java/javasrc/src/main/java/com/longbridge/trade/GetHistoryOrdersOptions.java index ef23ad1503..f89dfe88e4 100644 --- a/java/javasrc/src/main/java/com/longbridge/trade/GetHistoryOrdersOptions.java +++ b/java/javasrc/src/main/java/com/longbridge/trade/GetHistoryOrdersOptions.java @@ -13,27 +13,33 @@ public class GetHistoryOrdersOptions { private OffsetDateTime startAt; private OffsetDateTime endAt; - public void setSymbol(String symbol) { + public GetHistoryOrdersOptions setSymbol(String symbol) { this.symbol = symbol; + return this; } - public void setStatus(OrderStatus[] status) { + public GetHistoryOrdersOptions setStatus(OrderStatus[] status) { this.status = status; + return this; } - public void setSide(OrderSide side) { + public GetHistoryOrdersOptions setSide(OrderSide side) { this.side = side; + return this; } - public void setMarket(Market market) { + public GetHistoryOrdersOptions setMarket(Market market) { this.market = market; + return this; } - public void setStartAt(OffsetDateTime startAt) { + public GetHistoryOrdersOptions setStartAt(OffsetDateTime startAt) { this.startAt = startAt; + return this; } - public void setEndAt(OffsetDateTime endAt) { + public GetHistoryOrdersOptions setEndAt(OffsetDateTime endAt) { this.endAt = endAt; + return this; } } diff --git a/java/javasrc/src/main/java/com/longbridge/trade/GetStockPositionsOptions.java b/java/javasrc/src/main/java/com/longbridge/trade/GetStockPositionsOptions.java index f2601ee65c..26819a8016 100644 --- a/java/javasrc/src/main/java/com/longbridge/trade/GetStockPositionsOptions.java +++ b/java/javasrc/src/main/java/com/longbridge/trade/GetStockPositionsOptions.java @@ -4,7 +4,8 @@ public class GetStockPositionsOptions { private String[] symbols; - public void setSymbols(String[] symbols) { + public GetStockPositionsOptions setSymbols(String[] symbols) { this.symbols = symbols; + return this; } } diff --git a/java/javasrc/src/main/java/com/longbridge/trade/GetTodayExecutionsOptions.java b/java/javasrc/src/main/java/com/longbridge/trade/GetTodayExecutionsOptions.java index 9f7d5ed618..82c5b787bd 100644 --- a/java/javasrc/src/main/java/com/longbridge/trade/GetTodayExecutionsOptions.java +++ b/java/javasrc/src/main/java/com/longbridge/trade/GetTodayExecutionsOptions.java @@ -5,11 +5,13 @@ public class GetTodayExecutionsOptions { private String symbol; private String orderId; - public void setSymbol(String symbol) { + public GetTodayExecutionsOptions setSymbol(String symbol) { this.symbol = symbol; + return this; } - public void setOrderId(String orderId) { + public GetTodayExecutionsOptions setOrderId(String orderId) { this.orderId = orderId; + return this; } } diff --git a/java/javasrc/src/main/java/com/longbridge/trade/GetTodayOrdersOptions.java b/java/javasrc/src/main/java/com/longbridge/trade/GetTodayOrdersOptions.java index af5c810e7c..64a2adaf51 100644 --- a/java/javasrc/src/main/java/com/longbridge/trade/GetTodayOrdersOptions.java +++ b/java/javasrc/src/main/java/com/longbridge/trade/GetTodayOrdersOptions.java @@ -9,20 +9,24 @@ public class GetTodayOrdersOptions { private OrderSide side; private Market market; - public void setSymbol(String symbol) { + public GetTodayOrdersOptions setSymbol(String symbol) { this.symbol = symbol; + return this; } - public void setStatus(OrderStatus[] status) { + public GetTodayOrdersOptions setStatus(OrderStatus[] status) { this.status = status; + return this; } - public void setSide(OrderSide side) { + public GetTodayOrdersOptions setSide(OrderSide side) { this.side = side; + return this; } - public void setMarket(Market market) { + public GetTodayOrdersOptions setMarket(Market market) { this.market = market; + return this; } } diff --git a/java/javasrc/src/main/java/com/longbridge/trade/Order.java b/java/javasrc/src/main/java/com/longbridge/trade/Order.java index 18e5fa44b1..cdb70f5c8e 100644 --- a/java/javasrc/src/main/java/com/longbridge/trade/Order.java +++ b/java/javasrc/src/main/java/com/longbridge/trade/Order.java @@ -130,4 +130,16 @@ public String getCurrency() { public OutsideRTH getOutsideRth() { return outsideRth; } + + @Override + public String toString() { + return "Order [currency=" + currency + ", executedPrice=" + executedPrice + ", executedQuantity=" + + executedQuantity + ", expireDate=" + expireDate + ", lastDone=" + lastDone + ", limitOffset=" + + limitOffset + ", msg=" + msg + ", orderId=" + orderId + ", orderType=" + orderType + ", outsideRth=" + + outsideRth + ", price=" + price + ", quantity=" + quantity + ", side=" + side + ", status=" + status + + ", stockName=" + stockName + ", submittedAt=" + submittedAt + ", symbol=" + symbol + ", tag=" + tag + + ", timeInForce=" + timeInForce + ", trailingAmount=" + trailingAmount + ", trailingPercent=" + + trailingPercent + ", triggerAt=" + triggerAt + ", triggerPrice=" + triggerPrice + ", triggerStatus=" + + triggerStatus + ", updatedAt=" + updatedAt + "]"; + } } \ No newline at end of file diff --git a/java/javasrc/src/main/java/com/longbridge/trade/PushOrderChanged.java b/java/javasrc/src/main/java/com/longbridge/trade/PushOrderChanged.java index bfa7faad46..e22561bd36 100644 --- a/java/javasrc/src/main/java/com/longbridge/trade/PushOrderChanged.java +++ b/java/javasrc/src/main/java/com/longbridge/trade/PushOrderChanged.java @@ -114,4 +114,16 @@ public BigDecimal getLimitOffset() { public String getAccountNo() { return accountNo; } + + @Override + public String toString() { + return "PushOrderChanged [accountNo=" + accountNo + ", currency=" + currency + ", executedPrice=" + + executedPrice + ", executedQuantity=" + executedQuantity + ", limitOffset=" + limitOffset + ", msg=" + + msg + ", orderId=" + orderId + ", orderType=" + orderType + ", side=" + side + ", status=" + status + + ", stockName=" + stockName + ", submittedAt=" + submittedAt + ", submittedPrice=" + submittedPrice + + ", submittedQuantity=" + submittedQuantity + ", symbol=" + symbol + ", tag=" + tag + + ", trailingAmount=" + trailingAmount + ", trailingPercent=" + trailingPercent + ", triggerAt=" + + triggerAt + ", triggerPrice=" + triggerPrice + ", triggerStatus=" + triggerStatus + ", updatedAt=" + + updatedAt + "]"; + } } diff --git a/java/javasrc/src/main/java/com/longbridge/trade/ReplaceOrderOptions.java b/java/javasrc/src/main/java/com/longbridge/trade/ReplaceOrderOptions.java index 6d35c3dca6..9e71f57e8c 100644 --- a/java/javasrc/src/main/java/com/longbridge/trade/ReplaceOrderOptions.java +++ b/java/javasrc/src/main/java/com/longbridge/trade/ReplaceOrderOptions.java @@ -18,28 +18,34 @@ public ReplaceOrderOptions(String orderId, long quantity) { this.quantity = quantity; } - public void setPrice(BigDecimal price) { + public ReplaceOrderOptions setPrice(BigDecimal price) { this.price = price; + return this; } - public void setTriggerPrice(BigDecimal triggerPrice) { + public ReplaceOrderOptions setTriggerPrice(BigDecimal triggerPrice) { this.triggerPrice = triggerPrice; + return this; } - public void setLimitOffset(BigDecimal limitOffset) { + public ReplaceOrderOptions setLimitOffset(BigDecimal limitOffset) { this.limitOffset = limitOffset; + return this; } - public void setTrailingAmount(BigDecimal trailingAmount) { + public ReplaceOrderOptions setTrailingAmount(BigDecimal trailingAmount) { this.trailingAmount = trailingAmount; + return this; } - public void setTrailingPercent(BigDecimal trailingPercent) { + public ReplaceOrderOptions setTrailingPercent(BigDecimal trailingPercent) { this.trailingPercent = trailingPercent; + return this; } - public void setRemark(String remark) { + public ReplaceOrderOptions setRemark(String remark) { this.remark = remark; + return this; } } diff --git a/java/javasrc/src/main/java/com/longbridge/trade/StockPosition.java b/java/javasrc/src/main/java/com/longbridge/trade/StockPosition.java index d3cc02a60b..54f385a2ef 100644 --- a/java/javasrc/src/main/java/com/longbridge/trade/StockPosition.java +++ b/java/javasrc/src/main/java/com/longbridge/trade/StockPosition.java @@ -40,4 +40,11 @@ public BigDecimal getCostPrice() { public Market getMarket() { return market; } + + @Override + public String toString() { + return "StockPosition [availableQuantity=" + availableQuantity + ", costPrice=" + costPrice + ", currency=" + + currency + ", market=" + market + ", quantity=" + quantity + ", symbol=" + symbol + ", symbolName=" + + symbolName + "]"; + } } diff --git a/java/javasrc/src/main/java/com/longbridge/trade/StockPositionChannel.java b/java/javasrc/src/main/java/com/longbridge/trade/StockPositionChannel.java index b6c928c059..9f83762344 100644 --- a/java/javasrc/src/main/java/com/longbridge/trade/StockPositionChannel.java +++ b/java/javasrc/src/main/java/com/longbridge/trade/StockPositionChannel.java @@ -1,5 +1,7 @@ package com.longbridge.trade; +import java.util.Arrays; + public class StockPositionChannel { private String accountChannel; private StockPosition[] positions; @@ -11,4 +13,10 @@ public String getAccountChannel() { public StockPosition[] getPositions() { return positions; } + + @Override + public String toString() { + return "StockPositionChannel [accountChannel=" + accountChannel + ", positions=" + Arrays.toString(positions) + + "]"; + } } diff --git a/java/javasrc/src/main/java/com/longbridge/trade/StockPositionsResponse.java b/java/javasrc/src/main/java/com/longbridge/trade/StockPositionsResponse.java index 934440acc7..9e78ca3f83 100644 --- a/java/javasrc/src/main/java/com/longbridge/trade/StockPositionsResponse.java +++ b/java/javasrc/src/main/java/com/longbridge/trade/StockPositionsResponse.java @@ -1,9 +1,16 @@ package com.longbridge.trade; +import java.util.Arrays; + public class StockPositionsResponse { private StockPositionChannel[] channels; public StockPositionChannel[] getChannels() { return channels; } + + @Override + public String toString() { + return "StockPositionsResponse [channels=" + Arrays.toString(channels) + "]"; + } } diff --git a/java/javasrc/src/main/java/com/longbridge/trade/SubmitOrderOptions.java b/java/javasrc/src/main/java/com/longbridge/trade/SubmitOrderOptions.java index 12711d7089..932078cedd 100644 --- a/java/javasrc/src/main/java/com/longbridge/trade/SubmitOrderOptions.java +++ b/java/javasrc/src/main/java/com/longbridge/trade/SubmitOrderOptions.java @@ -32,35 +32,43 @@ public SubmitOrderOptions( this.timeInForce = timeInForce; } - public void setSubmittedPrice(BigDecimal submittedPrice) { + public SubmitOrderOptions setSubmittedPrice(BigDecimal submittedPrice) { this.submittedPrice = submittedPrice; + return this; } - public void setTriggerPrice(BigDecimal triggerPrice) { + public SubmitOrderOptions setTriggerPrice(BigDecimal triggerPrice) { this.triggerPrice = triggerPrice; + return this; } - public void setLimitOffset(BigDecimal limitOffset) { + public SubmitOrderOptions setLimitOffset(BigDecimal limitOffset) { this.limitOffset = limitOffset; + return this; } - public void setTrailingAmount(BigDecimal trailingAmount) { + public SubmitOrderOptions setTrailingAmount(BigDecimal trailingAmount) { this.trailingAmount = trailingAmount; + return this; } - public void setTrailingPercent(BigDecimal trailingPercent) { + public SubmitOrderOptions setTrailingPercent(BigDecimal trailingPercent) { this.trailingPercent = trailingPercent; + return this; } - public void setExpireDate(LocalDate expireDate) { + public SubmitOrderOptions setExpireDate(LocalDate expireDate) { this.expireDate = expireDate; + return this; } - public void setOutsideRth(OutsideRTH outsideRth) { + public SubmitOrderOptions setOutsideRth(OutsideRTH outsideRth) { this.outsideRth = outsideRth; + return this; } - public void setRemark(String remark) { + public SubmitOrderOptions setRemark(String remark) { this.remark = remark; + return this; } } diff --git a/java/javasrc/src/main/java/com/longbridge/trade/SubmitOrderResponse.java b/java/javasrc/src/main/java/com/longbridge/trade/SubmitOrderResponse.java index c7bd026955..476f40a69e 100644 --- a/java/javasrc/src/main/java/com/longbridge/trade/SubmitOrderResponse.java +++ b/java/javasrc/src/main/java/com/longbridge/trade/SubmitOrderResponse.java @@ -6,4 +6,9 @@ public class SubmitOrderResponse { public String getOrderId() { return orderId; } + + @Override + public String toString() { + return "SubmitOrderResponse [orderId=" + orderId + "]"; + } } diff --git a/java/libs/native-lib-loader-2.4.0.jar b/java/libs/native-lib-loader-2.4.0.jar new file mode 100644 index 0000000000000000000000000000000000000000..0716898129e101f78a616ac816b588b061c5cd43 GIT binary patch literal 20592 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

G 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 literal 0 HcmV?d00001 diff --git a/java/src/quote_context.rs b/java/src/quote_context.rs index 158697a8e4..bfbf1e19c1 100644 --- a/java/src/quote_context.rs +++ b/java/src/quote_context.rs @@ -594,7 +594,7 @@ pub unsafe extern "system" fn Java_com_longbridge_SdkNative_quoteContextRealtime } #[no_mangle] -pub unsafe extern "system" fn quoteContextRealtimeDepth( +pub unsafe extern "system" fn Java_com_longbridge_SdkNative_quoteContextRealtimeDepth( env: JNIEnv, _class: JClass, context: i64, @@ -612,7 +612,7 @@ pub unsafe extern "system" fn quoteContextRealtimeDepth( } #[no_mangle] -pub unsafe extern "system" fn quoteContextRealtimeBrokers( +pub unsafe extern "system" fn Java_com_longbridge_SdkNative_quoteContextRealtimeBrokers( env: JNIEnv, _class: JClass, context: i64, diff --git a/java/src/trade_context.rs b/java/src/trade_context.rs index edfa956713..4f2b712e04 100644 --- a/java/src/trade_context.rs +++ b/java/src/trade_context.rs @@ -99,6 +99,15 @@ pub unsafe extern "system" fn Java_com_longbridge_SdkNative_newTradeContext( }) } +#[no_mangle] +pub unsafe extern "system" fn Java_com_longbridge_SdkNative_freeTradeContext( + _env: JNIEnv, + _class: JClass, + ctx: i64, +) { + let _ = Box::from_raw(ctx as *mut ContextObj); +} + #[no_mangle] pub unsafe extern "system" fn Java_com_longbridge_SdkNative_tradeContextSetOnOrderChanged( env: JNIEnv, @@ -422,7 +431,7 @@ pub unsafe extern "system" fn Java_com_longbridge_SdkNative_tradeContextCancelOr } #[no_mangle] -pub unsafe extern "system" fn tradeContextAccountBalance( +pub unsafe extern "system" fn Java_com_longbridge_SdkNative_tradeContextAccountBalance( env: JNIEnv, _class: JClass, context: i64, @@ -438,7 +447,7 @@ pub unsafe extern "system" fn tradeContextAccountBalance( } #[no_mangle] -pub unsafe extern "system" fn tradeContextCashFlow( +pub unsafe extern "system" fn Java_com_longbridge_SdkNative_tradeContextCashFlow( env: JNIEnv, _class: JClass, context: i64, diff --git a/java/test/Main.java b/java/test/Main.java new file mode 100644 index 0000000000..1e2e519923 --- /dev/null +++ b/java/test/Main.java @@ -0,0 +1,15 @@ +import com.longbridge.*; +import com.longbridge.trade.*; + +class Main { + public static void main(String[] args) throws Exception { + try (Config config = Config.fromEnv()) { + try (TradeContext ctx = TradeContext.create(config).get()) { + Order[] orders = ctx.getTodayOrders(null).join(); + for (Order order : orders) { + System.out.println(order); + } + } + } + } +} \ No newline at end of file From 57b35cf210837e59fa2a4a14463da5d838643815 Mon Sep 17 00:00:00 2001 From: Sunli Date: Fri, 17 Jun 2022 10:21:05 +0800 Subject: [PATCH 074/567] Fixes java examples --- examples/java/.gitignore | 1 + .../examples/submit_order/main.java | 19 +++++----- .../examples/subscribe_quote/main.java | 17 ++++----- java/crates/macros/src/lib.rs | 35 ++++++++++++++++--- .../java/com/longbridge/OpenApiException.java | 5 +++ java/src/types/classes.rs | 2 +- java/test/Main.java | 14 +++++--- 7 files changed, 64 insertions(+), 29 deletions(-) create mode 100644 examples/java/.gitignore diff --git a/examples/java/.gitignore b/examples/java/.gitignore new file mode 100644 index 0000000000..1de565933b --- /dev/null +++ b/examples/java/.gitignore @@ -0,0 +1 @@ +target \ No newline at end of file diff --git a/examples/java/submit_order/src/main/java/com/longbridge/examples/submit_order/main.java b/examples/java/submit_order/src/main/java/com/longbridge/examples/submit_order/main.java index d4ffc3ba73..0e64eac1e0 100644 --- a/examples/java/submit_order/src/main/java/com/longbridge/examples/submit_order/main.java +++ b/examples/java/submit_order/src/main/java/com/longbridge/examples/submit_order/main.java @@ -1,16 +1,19 @@ import com.longbridge.*; -import com.longbridge.quote.*; +import com.longbridge.trade.*; +import java.math.BigDecimal; class Main { public static void main(String[] args) throws Exception { try (Config config = Config.fromEnv()) { - try (QuoteContext ctx = QuoteContext.create(config).get()) { - ctx.setOnQuote((symbol, quote) -> { - System.out.println("%s\t%s\n", symbol, quote); - }); - ctx.subscribe(new String[] { "700.HK", "AAPL.US", "TSLA.US", "NFLX.US" }, SubFlags.Quote, true).get(); - Thread.sleep(30000); + try (TradeContext ctx = TradeContext.create(config).get()) { + SubmitOrderOptions opts = new SubmitOrderOptions("700.HK", + OrderType.LO, + OrderSide.Buy, + 200, + TimeInForceType.Day).setSubmittedPrice(new BigDecimal(50)); + SubmitOrderResponse resp = ctx.submitOrder(opts).join(); + System.out.println(resp); } } } -} \ No newline at end of file +} diff --git a/examples/java/subscribe_quote/src/main/java/com/longbridge/examples/subscribe_quote/main.java b/examples/java/subscribe_quote/src/main/java/com/longbridge/examples/subscribe_quote/main.java index 78470a127e..adcbcaaee3 100644 --- a/examples/java/subscribe_quote/src/main/java/com/longbridge/examples/subscribe_quote/main.java +++ b/examples/java/subscribe_quote/src/main/java/com/longbridge/examples/subscribe_quote/main.java @@ -1,18 +1,15 @@ import com.longbridge.*; -import com.longbridge.trade.*; -import java.math.BigDecimal; +import com.longbridge.quote.*; class Main { public static void main(String[] args) throws Exception { try (Config config = Config.fromEnv()) { - try (TradeContext ctx = TradeContext.create(config).get()) { - SubmitOrderOptions opts = new SubmitOrderOptions("700.HK", - OrderType.LO, - OrderSide.Buy, - 200, - TimeInForceType.Day).setSubmittedPrice(new BigDecimal(50)); - SubmitOrderResponse resp = ctx.submitOrder(opts).join(); - System.out.println(resp); + try (QuoteContext ctx = QuoteContext.create(config).get()) { + ctx.setOnQuote((symbol, quote) -> { + System.out.println("%s\t%s\n", symbol, quote); + }); + ctx.subscribe(new String[] { "700.HK1", "AAPL.US", "TSLA.US", "NFLX.US" }, SubFlags.Quote, true).get(); + Thread.sleep(30000); } } } diff --git a/java/crates/macros/src/lib.rs b/java/crates/macros/src/lib.rs index b7212f22c0..3b84d315b7 100644 --- a/java/crates/macros/src/lib.rs +++ b/java/crates/macros/src/lib.rs @@ -1,10 +1,11 @@ use darling::FromMeta; use inflector::Inflector; use proc_macro::TokenStream; +use proc_macro2::Span; use quote::quote; use syn::{ parse::Parser, punctuated::Punctuated, token::Comma, Error, Expr, ExprArray, ExprLit, ExprPath, - Lit, Path, + Ident, Lit, Path, }; #[derive(FromMeta, Debug, Default)] @@ -120,8 +121,20 @@ pub fn impl_java_class(input: TokenStream) -> TokenStream { } let field_names = fields.iter().map(|(field, _)| field).collect::>(); + let class_ref_name = Ident::new(&classname.replace('/', "_"), Span::call_site()); + let def_class_ref = quote! { + static #class_ref_name: once_cell::sync::OnceCell = once_cell::sync::OnceCell::new(); + }; + let get_class_ref = quote! { + #class_ref_name.get_or_try_init(|| { + let cls: jni::objects::JClass = #classname.lookup(env)?; + env.new_global_ref(cls) + })? + }; let expanded = quote! { + #def_class_ref + impl crate::types::JClassName for #type_path { const CLASSNAME: &'static str = #classname; } @@ -137,7 +150,7 @@ pub fn impl_java_class(input: TokenStream) -> TokenStream { fn into_jvalue<'a>(self, env: &jni::JNIEnv<'a>) -> jni::errors::Result> { use jni::descriptors::Desc; let #type_path { #(#field_names),* } = self; - let cls: jni::objects::JClass = #classname.lookup(env)?; + let cls = #get_class_ref; let obj = env.new_object(cls, "()V", &[])?; #(#set_fields)* Ok(obj.into()) @@ -236,7 +249,20 @@ pub fn impl_java_enum(input: TokenStream) -> TokenStream { }); } + let class_ref_name = Ident::new(&classname.replace('/', "_"), Span::call_site()); + let def_class_ref = quote! { + static #class_ref_name: once_cell::sync::OnceCell = once_cell::sync::OnceCell::new(); + }; + let get_class_ref = quote! { + #class_ref_name.get_or_try_init(|| { + let cls: jni::objects::JClass = #classname.lookup(env)?; + env.new_global_ref(cls) + })? + }; + let expanded = quote! { + #def_class_ref + impl crate::types::JClassName for #type_path { const CLASSNAME: &'static str = #classname; } @@ -256,7 +282,7 @@ pub fn impl_java_enum(input: TokenStream) -> TokenStream { use #type_path::*; use jni::descriptors::Desc; - let cls: jni::objects::JClass = #classname.lookup(env).expect(concat!(#classname, " exists")); + let cls = #get_class_ref; let value = value.l()?; #(#from_jsvalue)* panic!("invalid enum value") @@ -271,8 +297,7 @@ pub fn impl_java_enum(input: TokenStream) -> TokenStream { use jni::descriptors::Desc; use #type_path::*; - let cls: jni::objects::JClass = #classname.lookup(env).expect(concat!(#classname, " exists")); - + let cls = #get_class_ref; match self { #(#into_jsvalue)* } diff --git a/java/javasrc/src/main/java/com/longbridge/OpenApiException.java b/java/javasrc/src/main/java/com/longbridge/OpenApiException.java index 94a7e24b62..eecd103f64 100644 --- a/java/javasrc/src/main/java/com/longbridge/OpenApiException.java +++ b/java/javasrc/src/main/java/com/longbridge/OpenApiException.java @@ -16,4 +16,9 @@ public Long getCode() { public String getMessage() { return message; } + + @Override + public String toString() { + return "OpenApiException [code=" + code + ", message=" + message + "]"; + } } diff --git a/java/src/types/classes.rs b/java/src/types/classes.rs index 8fbc0d6f43..94f5f6237c 100644 --- a/java/src/types/classes.rs +++ b/java/src/types/classes.rs @@ -255,7 +255,7 @@ impl_java_class!( ); impl_java_class!( - "com/longbridge/quote/MarketTradingSession", + "com/longbridge/quote/TradingSessionInfo", longbridge::quote::TradingSessionInfo, [begin_time, end_time, trade_session] ); diff --git a/java/test/Main.java b/java/test/Main.java index 1e2e519923..0e64eac1e0 100644 --- a/java/test/Main.java +++ b/java/test/Main.java @@ -1,15 +1,19 @@ import com.longbridge.*; import com.longbridge.trade.*; +import java.math.BigDecimal; class Main { public static void main(String[] args) throws Exception { try (Config config = Config.fromEnv()) { try (TradeContext ctx = TradeContext.create(config).get()) { - Order[] orders = ctx.getTodayOrders(null).join(); - for (Order order : orders) { - System.out.println(order); - } + SubmitOrderOptions opts = new SubmitOrderOptions("700.HK", + OrderType.LO, + OrderSide.Buy, + 200, + TimeInForceType.Day).setSubmittedPrice(new BigDecimal(50)); + SubmitOrderResponse resp = ctx.submitOrder(opts).join(); + System.out.println(resp); } } } -} \ No newline at end of file +} From 29a059dce6776dfb5e8829f3cc0bfb06523a07dc Mon Sep 17 00:00:00 2001 From: Sunli Date: Fri, 17 Jun 2022 12:20:51 +0800 Subject: [PATCH 075/567] Add more examples for Java SDK --- .../examples/account_asset/main.java | 8 +- .../examples/submit_order/main.java | 18 +- .../examples/subscribe_quote/main.java | 14 +- .../examples/today_orders/main.java | 10 +- .../com/longbridge/quote/QuoteContext.java | 423 +++++++++++++++++- .../main/java/com/longbridge/quote/Trade.java | 8 +- .../com/longbridge/quote/WarrantQuote.java | 8 +- .../com/longbridge/trade/TradeContext.java | 251 ++++++++++- java/src/quote_context.rs | 6 +- java/src/trade_context.rs | 18 +- java/test/Main.java | 14 +- 11 files changed, 710 insertions(+), 68 deletions(-) diff --git a/examples/java/account_asset/src/main/java/com/longbridge/examples/account_asset/main.java b/examples/java/account_asset/src/main/java/com/longbridge/examples/account_asset/main.java index 97b219400e..5c933ad58b 100644 --- a/examples/java/account_asset/src/main/java/com/longbridge/examples/account_asset/main.java +++ b/examples/java/account_asset/src/main/java/com/longbridge/examples/account_asset/main.java @@ -3,11 +3,9 @@ class Main { public static void main(String[] args) throws Exception { - try (Config config = Config.fromEnv()) { - try (TradeContext ctx = TradeContext.create(config).get()) { - for (AccountBalance obj : ctx.getAccountBalance().get()) { - System.out.println(obj); - } + try (Config config = Config.fromEnv(); TradeContext ctx = TradeContext.create(config).get()) { + for (AccountBalance obj : ctx.getAccountBalance().get()) { + System.out.println(obj); } } } diff --git a/examples/java/submit_order/src/main/java/com/longbridge/examples/submit_order/main.java b/examples/java/submit_order/src/main/java/com/longbridge/examples/submit_order/main.java index 0e64eac1e0..d5465824ca 100644 --- a/examples/java/submit_order/src/main/java/com/longbridge/examples/submit_order/main.java +++ b/examples/java/submit_order/src/main/java/com/longbridge/examples/submit_order/main.java @@ -4,16 +4,14 @@ class Main { public static void main(String[] args) throws Exception { - try (Config config = Config.fromEnv()) { - try (TradeContext ctx = TradeContext.create(config).get()) { - SubmitOrderOptions opts = new SubmitOrderOptions("700.HK", - OrderType.LO, - OrderSide.Buy, - 200, - TimeInForceType.Day).setSubmittedPrice(new BigDecimal(50)); - SubmitOrderResponse resp = ctx.submitOrder(opts).join(); - System.out.println(resp); - } + try (Config config = Config.fromEnv(); TradeContext ctx = TradeContext.create(config).get()) { + SubmitOrderOptions opts = new SubmitOrderOptions("700.HK", + OrderType.LO, + OrderSide.Buy, + 200, + TimeInForceType.Day).setSubmittedPrice(new BigDecimal(50)); + SubmitOrderResponse resp = ctx.submitOrder(opts).get(); + System.out.println(resp); } } } diff --git a/examples/java/subscribe_quote/src/main/java/com/longbridge/examples/subscribe_quote/main.java b/examples/java/subscribe_quote/src/main/java/com/longbridge/examples/subscribe_quote/main.java index adcbcaaee3..42cfb00113 100644 --- a/examples/java/subscribe_quote/src/main/java/com/longbridge/examples/subscribe_quote/main.java +++ b/examples/java/subscribe_quote/src/main/java/com/longbridge/examples/subscribe_quote/main.java @@ -3,14 +3,12 @@ class Main { public static void main(String[] args) throws Exception { - try (Config config = Config.fromEnv()) { - try (QuoteContext ctx = QuoteContext.create(config).get()) { - ctx.setOnQuote((symbol, quote) -> { - System.out.println("%s\t%s\n", symbol, quote); - }); - ctx.subscribe(new String[] { "700.HK1", "AAPL.US", "TSLA.US", "NFLX.US" }, SubFlags.Quote, true).get(); - Thread.sleep(30000); - } + try (Config config = Config.fromEnv(); QuoteContext ctx = QuoteContext.create(config).get()) { + ctx.setOnQuote((symbol, quote) -> { + System.out.printf("%s\t%s\n", symbol, quote); + }); + ctx.subscribe(new String[] { "700.HK", "AAPL.US", "TSLA.US", "NFLX.US" }, SubFlags.Quote, true).get(); + Thread.sleep(30000); } } } \ No newline at end of file diff --git a/examples/java/today_orders/src/main/java/com/longbridge/examples/today_orders/main.java b/examples/java/today_orders/src/main/java/com/longbridge/examples/today_orders/main.java index 1e2e519923..ef293198c7 100644 --- a/examples/java/today_orders/src/main/java/com/longbridge/examples/today_orders/main.java +++ b/examples/java/today_orders/src/main/java/com/longbridge/examples/today_orders/main.java @@ -3,12 +3,10 @@ class Main { public static void main(String[] args) throws Exception { - try (Config config = Config.fromEnv()) { - try (TradeContext ctx = TradeContext.create(config).get()) { - Order[] orders = ctx.getTodayOrders(null).join(); - for (Order order : orders) { - System.out.println(order); - } + try (Config config = Config.fromEnv(); TradeContext ctx = TradeContext.create(config).get()) { + Order[] orders = ctx.getTodayOrders(null).get(); + for (Order order : orders) { + System.out.println(order); } } } diff --git a/java/javasrc/src/main/java/com/longbridge/quote/QuoteContext.java b/java/javasrc/src/main/java/com/longbridge/quote/QuoteContext.java index 823cc48eee..42c675e921 100644 --- a/java/javasrc/src/main/java/com/longbridge/quote/QuoteContext.java +++ b/java/javasrc/src/main/java/com/longbridge/quote/QuoteContext.java @@ -72,6 +72,25 @@ public void setOnTrades(TradesHandler handler) { /** * Subscribe * + *
+     * {@code
+     * import com.longbridge.*;
+     * import com.longbridge.quote.*;
+     * 
+     * class Main {
+     *     public static void main(String[] args) throws Exception {
+     *         try (Config config = Config.fromEnv(); QuoteContext ctx = QuoteContext.create(config).get()) {
+     *             ctx.setOnQuote((symbol, quote) -> {
+     *                 System.out.printf("%s\t%s\n", symbol, quote);
+     *             });
+     *             ctx.subscribe(new String[] { "700.HK", "AAPL.US" }, SubFlags.Quote, true).get();
+     *             Thread.sleep(30000);
+     *         }
+     *     }
+     * }
+     * }
+     * 
+ * * @param symbols Security symbols * @param flags Subscription flags * @param isFirstPush Whether to perform a data push immediately after @@ -88,6 +107,26 @@ public CompletableFuture subscribe(String[] symbols, int flags, boolean is /** * Unsubscribe * + *
+     * {@code
+     * import com.longbridge.*;
+     * import com.longbridge.quote.*;
+     * 
+     * class Main {
+     *     public static void main(String[] args) throws Exception {
+     *         try (Config config = Config.fromEnv(); QuoteContext ctx = QuoteContext.create(config).get()) {
+     *             ctx.setOnQuote((symbol, quote) -> {
+     *                 System.out.printf("%s\t%s\n", symbol, quote);
+     *             });
+     *             ctx.subscribe(new String[] { "700.HK", "AAPL.US" }, SubFlags.Quote, true).get();
+     *             ctx.unsubscribe(new String[] { "AAPL.US" }, SubFlags.Quote).get();
+     *         }
+     *     }
+     * }
+     * }
+     * 
+ * + * * @param symbols Security symbols * @param flags Subscription flags * @return A Future representing the result of the operation @@ -102,6 +141,25 @@ public CompletableFuture unsubscribe(String[] symbols, int flags) throws O /** * Get subscription information * + *
+     * {@code
+     * import com.longbridge.*;
+     * import com.longbridge.quote.*;
+     * 
+     * class Main {
+     *     public static void main(String[] args) throws Exception {
+     *         try (Config config = Config.fromEnv(); QuoteContext ctx = QuoteContext.create(config).get()) {
+     *             ctx.subscribe(new String[] { "700.HK", "AAPL.US" }, SubFlags.Quote, true);
+     *             Subscription[] subscriptions = ctx.getSubscrptions().get();
+     *             for (Subscription obj : subscriptions) {
+     *                 System.out.println(obj);
+     *             }
+     *         }
+     *     }
+     * }
+     * }
+     * 
+ * * @return A Future representing the result of the operation * @throws OpenApiException If an error occurs */ @@ -114,6 +172,26 @@ public CompletableFuture getSubscrptions() throws OpenApiExcepti /** * Get basic information of securities * + *
+     * {@code
+     * import com.longbridge.*;
+     * import com.longbridge.quote.*;
+     * 
+     * class Main {
+     *     public static void main(String[] args) throws Exception {
+     *         try (Config config = Config.fromEnv(); QuoteContext ctx = QuoteContext.create(config).get()) {
+     *             SecurityStaticInfo[] resp = ctx
+     *                     .getStaticInfo(new String[] { "700.HK", "AAPL.US", "TSLA.US", "NFLX.US" })
+     *                     .get();
+     *             for (SecurityStaticInfo obj : resp) {
+     *                 System.out.println(obj);
+     *             }
+     *         }
+     *     }
+     * }
+     * }
+     * 
+ * * @param symbols Security symbols * @return A Future representing the result of the operation * @throws OpenApiException If an error occurs @@ -127,6 +205,25 @@ public CompletableFuture getStaticInfo(String[] symbols) t /** * Get quote of securities * + *
+     * {@code
+     * import com.longbridge.*;
+     * import com.longbridge.quote.*;
+     * 
+     * class Main {
+     *     public static void main(String[] args) throws Exception {
+     *         try (Config config = Config.fromEnv(); QuoteContext ctx = QuoteContext.create(config).get()) {
+     *             SecurityQuote[] resp = ctx.getQuote(new String[] { "700.HK", "AAPL.US", "TSLA.US", "NFLX.US" })
+     *                     .get();
+     *             for (SecurityQuote obj : resp) {
+     *                 System.out.println(obj);
+     *             }
+     *         }
+     *     }
+     * }
+     * }
+     * 
+ * * @param symbols Security symbols * @return A Future representing the result of the operation * @throws OpenApiException If an error occurs @@ -140,6 +237,24 @@ public CompletableFuture getQuote(String[] symbols) throws Open /** * Get quote of option securities * + *
+     * {@code
+     * import com.longbridge.*;
+     * import com.longbridge.quote.*;
+     * 
+     * class Main {
+     *     public static void main(String[] args) throws Exception {
+     *         try (Config config = Config.fromEnv(); QuoteContext ctx = QuoteContext.create(config).get()) {
+     *             OptionQuote[] resp = ctx.getOptionQuote(new String[] { "AAPL230317P160000.US" }).get();
+     *             for (OptionQuote obj : resp) {
+     *                 System.out.println(obj);
+     *             }
+     *         }
+     *     }
+     * }
+     * }
+     * 
+ * * @param symbols Security symbols * @return A Future representing the result of the operation * @throws OpenApiException If an error occurs @@ -150,9 +265,56 @@ public CompletableFuture getOptionQuote(String[] symbols) throws }); } + /** + * Get quote of warrant securities + * + *
+     * {@code
+     * import com.longbridge.*;
+     * import com.longbridge.quote.*;
+     * 
+     * class Main {
+     *     public static void main(String[] args) throws Exception {
+     *         try (Config config = Config.fromEnv(); QuoteContext ctx = QuoteContext.create(config).get()) {
+     *             WarrantQuote[] resp = ctx.getWarrantQuote(new String[] { "21125.HK" }).get();
+     *             for (WarrantQuote obj : resp) {
+     *                 System.out.println(obj);
+     *             }
+     *         }
+     *     }
+     * }
+     * }
+     * 
+ * + * @param symbols Security symbols + * @return A Future representing the result of the operation + * @throws OpenApiException If an error occurs + */ + public CompletableFuture getWarrantQuote(String[] symbols) throws OpenApiException { + return AsyncCallback.executeTask((callback) -> { + SdkNative.quoteContextWarrantQuote(this.raw, symbols, callback); + }); + } + /** * Get security depth * + *
+     * {@code
+     * import com.longbridge.*;
+     * import com.longbridge.quote.*;
+     * 
+     * class Main {
+     *     public static void main(String[] args) throws Exception {
+     *         try (Config config = Config.fromEnv(); QuoteContext ctx = QuoteContext.create(config).get()) {
+     *             SecurityDepth resp = ctx.getDepth("700.HK").get();
+     *             System.out.println(resp);
+     *         }
+     *     }
+     * }
+     * }
+     * 
+ * * @param symbol Security symbol * @return A Future representing the result of the operation * @throws OpenApiException If an error occurs @@ -166,6 +328,22 @@ public CompletableFuture getDepth(String symbol) throws OpenApiEx /** * Get security brokers * + *
+     * {@code
+     * import com.longbridge.*;
+     * import com.longbridge.quote.*;
+     * 
+     * class Main {
+     *     public static void main(String[] args) throws Exception {
+     *         try (Config config = Config.fromEnv(); QuoteContext ctx = QuoteContext.create(config).get()) {
+     *             SecurityBrokers resp = ctx.getBrokers("700.HK").get();
+     *             System.out.println(resp);
+     *         }
+     *     }
+     * }
+     * }
+     * 
+ * * @param symbol Security symbol * @return A Future representing the result of the operation * @throws OpenApiException If an error occurs @@ -179,6 +357,24 @@ public CompletableFuture getBrokers(String symbol) throws OpenA /** * Get participants * + *
+     * {@code
+     * import com.longbridge.*;
+     * import com.longbridge.quote.*;
+     * 
+     * class Main {
+     *     public static void main(String[] args) throws Exception {
+     *         try (Config config = Config.fromEnv(); QuoteContext ctx = QuoteContext.create(config).get()) {
+     *             ParticipantInfo[] resp = ctx.getParticipants().get();
+     *             for (ParticipantInfo obj : resp) {
+     *                 System.out.println(obj);
+     *             }
+     *         }
+     *     }
+     * }
+     * }
+     * 
+ * * @return A Future representing the result of the operation * @throws OpenApiException If an error occurs */ @@ -191,6 +387,24 @@ public CompletableFuture getParticipants() throws OpenApiExce /** * Get security trades * + *
+     * {@code
+     * import com.longbridge.*;
+     * import com.longbridge.quote.*;
+     * 
+     * class Main {
+     *     public static void main(String[] args) throws Exception {
+     *         try (Config config = Config.fromEnv(); QuoteContext ctx = QuoteContext.create(config).get()) {
+     *             Trade[] resp = ctx.getTrades("700.HK", 10).get();
+     *             for (Trade obj : resp) {
+     *                 System.out.println(obj);
+     *             }
+     *         }
+     *     }
+     * }
+     * }
+     * 
+ * * @param symbol Security symbol * @param count Count of trades * @return A Future representing the result of the operation @@ -205,6 +419,24 @@ public CompletableFuture getTrades(String symbol, int count) throws Ope /** * Get security intraday lines * + *
+     * {@code
+     * import com.longbridge.*;
+     * import com.longbridge.quote.*;
+     * 
+     * class Main {
+     *     public static void main(String[] args) throws Exception {
+     *         try (Config config = Config.fromEnv(); QuoteContext ctx = QuoteContext.create(config).get()) {
+     *             IntradayLine[] resp = ctx.getIntraday("700.HK").get();
+     *             for (IntradayLine obj : resp) {
+     *                 System.out.println(obj);
+     *             }
+     *         }
+     *     }
+     * }
+     * }
+     * 
+ * * @param symbol Security symbol * @return A Future representing the result of the operation * @throws OpenApiException If an error occurs @@ -218,6 +450,24 @@ public CompletableFuture getIntraday(String symbol) throws OpenA /** * Get security candlesticks * + *
+     * {@code
+     * import com.longbridge.*;
+     * import com.longbridge.quote.*;
+     * 
+     * class Main {
+     *     public static void main(String[] args) throws Exception {
+     *         try (Config config = Config.fromEnv(); QuoteContext ctx = QuoteContext.create(config).get()) {
+     *             Candlestick[] resp = ctx.getCandlesticks("700.HK", Period.Day, 10, AdjustType.NoAdjust).get();
+     *             for (Candlestick obj : resp) {
+     *                 System.out.println(obj);
+     *             }
+     *         }
+     *     }
+     * }
+     * }
+     * 
+ * * @param symbol Security symbol * @param period Candlestick period * @param count Count of candlesticks @@ -235,6 +485,25 @@ public CompletableFuture getCandlesticks(String symbol, Period pe /** * Get option chain expiry date list * + *
+     * {@code
+     * import com.longbridge.*;
+     * import com.longbridge.quote.*;
+     * import java.time.LocalDate;
+     * 
+     * class Main {
+     *     public static void main(String[] args) throws Exception {
+     *         try (Config config = Config.fromEnv(); QuoteContext ctx = QuoteContext.create(config).get()) {
+     *             LocalDate[] resp = ctx.getOptionChainExpiryDateList("AAPL.US").get();
+     *             for (LocalDate obj : resp) {
+     *                 System.out.println(obj);
+     *             }
+     *         }
+     *     }
+     * }
+     * }
+     * 
+ * * @param symbol Security symbol * @return A Future representing the result of the operation * @throws OpenApiException If an error occurs @@ -248,6 +517,25 @@ public CompletableFuture getOptionChainExpiryDateList(String symbol /** * Get option chain info by date * + *
+     * {@code
+     * import com.longbridge.*;
+     * import com.longbridge.quote.*;
+     * import java.time.LocalDate;
+     * 
+     * class Main {
+     *     public static void main(String[] args) throws Exception {
+     *         try (Config config = Config.fromEnv(); QuoteContext ctx = QuoteContext.create(config).get()) {
+     *             StrikePriceInfo[] resp = ctx.getOptionChainInfoByDate("AAPL.US", LocalDate.of(2023, 1, 20)).get();
+     *             for (StrikePriceInfo obj : resp) {
+     *                 System.out.println(obj);
+     *             }
+     *         }
+     *     }
+     * }
+     * }
+     * 
+ * * @param symbol Security symbol * @param expiryDate Option expiry date * @return A Future representing the result of the operation @@ -263,11 +551,28 @@ public CompletableFuture getOptionChainInfoByDate(String symb /** * Get warrant issuers * - * @param symbol Security symbol + *
+     * {@code
+     * import com.longbridge.*;
+     * import com.longbridge.quote.*;
+     * 
+     * class Main {
+     *     public static void main(String[] args) throws Exception {
+     *         try (Config config = Config.fromEnv(); QuoteContext ctx = QuoteContext.create(config).get()) {
+     *             IssuerInfo[] resp = ctx.getWarrantIssuers().get();
+     *             for (IssuerInfo obj : resp) {
+     *                 System.out.println(obj);
+     *             }
+     *         }
+     *     }
+     * }
+     * }
+     * 
+ * * @return A Future representing the result of the operation * @throws OpenApiException If an error occurs */ - public CompletableFuture getWarrantIssuers(String symbol) + public CompletableFuture getWarrantIssuers() throws OpenApiException { return AsyncCallback.executeTask((callback) -> { SdkNative.quoteContextWarrantIssuers(this.raw, callback); @@ -277,6 +582,24 @@ public CompletableFuture getWarrantIssuers(String symbol) /** * Get trading session of the day * + *
+     * {@code
+     * import com.longbridge.*;
+     * import com.longbridge.quote.*;
+     * 
+     * class Main {
+     *     public static void main(String[] args) throws Exception {
+     *         try (Config config = Config.fromEnv(); QuoteContext ctx = QuoteContext.create(config).get()) {
+     *             MarketTradingSession[] resp = ctx.getTradingSession().get();
+     *             for (MarketTradingSession obj : resp) {
+     *                 System.out.println(obj);
+     *             }
+     *         }
+     *     }
+     * }
+     * }
+     * 
+ * * @return A Future representing the result of the operation * @throws OpenApiException If an error occurs */ @@ -293,6 +616,24 @@ public CompletableFuture getTradingSession() * The interval must be less than one month, and only the most recent year is * supported. * + *
+     * {@code
+     * import com.longbridge.*;
+     * import com.longbridge.quote.*;
+     * import java.time.LocalDate;
+     * 
+     * class Main {
+     *     public static void main(String[] args) throws Exception {
+     *         try (Config config = Config.fromEnv(); QuoteContext ctx = QuoteContext.create(config).get()) {
+     *             MarketTradingDays resp = ctx
+     *                     .getTradingDays(Market.HK, LocalDate.of(2022, 1, 20), LocalDate.of(2022, 2, 20)).get();
+     *             System.out.println(resp);
+     *         }
+     *     }
+     * }
+     * }
+     * 
+ * * @param market Market * @param begin Begin date * @param end End date @@ -312,6 +653,26 @@ public CompletableFuture getTradingDays(Market market, LocalD * Get real-time quotes of the subscribed symbols, it always returns the data in * the local storage. * + *
+     * {@code
+     * import com.longbridge.*;
+     * import com.longbridge.quote.*;
+     * 
+     * class Main {
+     *     public static void main(String[] args) throws Exception {
+     *         try (Config config = Config.fromEnv(); QuoteContext ctx = QuoteContext.create(config).get()) {
+     *             ctx.subscribe(new String[] { "700.HK", "AAPL.US" }, SubFlags.Quote, true).get();
+     *             Thread.sleep(5000);
+     *             RealtimeQuote[] resp = ctx.getRealtimeQuote(new String[] { "700.HK", "AAPL.US" }).get();
+     *             for (RealtimeQuote obj : resp) {
+     *                 System.out.println(obj);
+     *             }
+     *         }
+     *     }
+     * }
+     * }
+     * 
+ * * @param symbols Security symbols * @return A Future representing the result of the operation * @throws OpenApiException If an error occurs @@ -329,6 +690,24 @@ public CompletableFuture getRealtimeQuote(String[] symbols) * Get real-time depth of the subscribed symbols, it always returns the data in * the local storage. * + *
+     * {@code
+     * import com.longbridge.*;
+     * import com.longbridge.quote.*;
+     * 
+     * class Main {
+     *     public static void main(String[] args) throws Exception {
+     *         try (Config config = Config.fromEnv(); QuoteContext ctx = QuoteContext.create(config).get()) {
+     *             ctx.subscribe(new String[] { "700.HK", "AAPL.US" }, SubFlags.Depth, true).get();
+     *             Thread.sleep(5000);
+     *             SecurityDepth resp = ctx.getRealtimeDepth("700.HK").get();
+     *             System.out.println(resp);
+     *         }
+     *     }
+     * }
+     * }
+     * 
+ * * @param symbol Security symbol * @return A Future representing the result of the operation * @throws OpenApiException If an error occurs @@ -346,6 +725,24 @@ public CompletableFuture getRealtimeDepth(String symbol) * Get real-time broker queue of the subscribed symbols, it always returns the * data in the local storage. * + *
+     * {@code
+     * import com.longbridge.*;
+     * import com.longbridge.quote.*;
+     * 
+     * class Main {
+     *     public static void main(String[] args) throws Exception {
+     *         try (Config config = Config.fromEnv(); QuoteContext ctx = QuoteContext.create(config).get()) {
+     *             ctx.subscribe(new String[] { "700.HK", "AAPL.US" }, SubFlags.Brokers, true).get();
+     *             Thread.sleep(5000);
+     *             SecurityBrokers resp = ctx.getRealtimeBrokers("700.HK").get();
+     *             System.out.println(resp);
+     *         }
+     *     }
+     * }
+     * }
+     * 
+ * * @param symbol Security symbol * @return A Future representing the result of the operation * @throws OpenApiException If an error occurs @@ -363,12 +760,32 @@ public CompletableFuture getRealtimeBrokers(String symbol) * Get real-time trades of the subscribed symbols, it always returns the data in * the local storage. * + *
+     * {@code
+     * import com.longbridge.*;
+     * import com.longbridge.quote.*;
+     * 
+     * class Main {
+     *     public static void main(String[] args) throws Exception {
+     *         try (Config config = Config.fromEnv(); QuoteContext ctx = QuoteContext.create(config).get()) {
+     *             ctx.subscribe(new String[] { "700.HK", "AAPL.US" }, SubFlags.Trade, false).get();
+     *             Thread.sleep(5000);
+     *             Trade[] resp = ctx.getRealtimeTrades("700.HK", 10).get();
+     *             for (Trade obj : resp) {
+     *                 System.out.println(obj);
+     *             }
+     *         }
+     *     }
+     * }
+     * }
+     * 
+ * * @param symbol Security symbol * @param count Count of trades * @return A Future representing the result of the operation * @throws OpenApiException If an error occurs */ - public CompletableFuture getRealtimeBrokers(String symbol, int count) + public CompletableFuture getRealtimeTrades(String symbol, int count) throws OpenApiException { return AsyncCallback.executeTask((callback) -> { SdkNative.quoteContextRealtimeTrades(this.raw, symbol, count, callback); diff --git a/java/javasrc/src/main/java/com/longbridge/quote/Trade.java b/java/javasrc/src/main/java/com/longbridge/quote/Trade.java index 8c56aadc38..54f04609cf 100644 --- a/java/javasrc/src/main/java/com/longbridge/quote/Trade.java +++ b/java/javasrc/src/main/java/com/longbridge/quote/Trade.java @@ -9,7 +9,7 @@ public class Trade { private OffsetDateTime timestamp; private String tradeType; private TradeDirection direction; - private TradeSession trade_sessions; + private TradeSession tradeSession; public BigDecimal getPrice() { return price; @@ -31,13 +31,13 @@ public TradeDirection getDirection() { return direction; } - public TradeSession getTrade_sessions() { - return trade_sessions; + public TradeSession getTradeSession() { + return tradeSession; } @Override public String toString() { return "Trade [direction=" + direction + ", price=" + price + ", timestamp=" + timestamp + ", tradeType=" - + tradeType + ", trade_sessions=" + trade_sessions + ", volume=" + volume + "]"; + + tradeType + ", tradeSession=" + tradeSession + ", volume=" + volume + "]"; } } diff --git a/java/javasrc/src/main/java/com/longbridge/quote/WarrantQuote.java b/java/javasrc/src/main/java/com/longbridge/quote/WarrantQuote.java index da77316f6b..bb4a34c3ca 100644 --- a/java/javasrc/src/main/java/com/longbridge/quote/WarrantQuote.java +++ b/java/javasrc/src/main/java/com/longbridge/quote/WarrantQuote.java @@ -26,7 +26,7 @@ public class WarrantQuote { private BigDecimal strikePrice; private BigDecimal upperStrikePrice; private BigDecimal lowerStrikePrice; - private BigDecimal call_price; + private BigDecimal callPrice; private String underlyingSymbol; public String getSymbol() { @@ -113,8 +113,8 @@ public BigDecimal getLowerStrikePrice() { return lowerStrikePrice; } - public BigDecimal getCall_price() { - return call_price; + public BigDecimal getCallPrice() { + return callPrice; } public String getUnderlyingSymbol() { @@ -123,7 +123,7 @@ public String getUnderlyingSymbol() { @Override public String toString() { - return "WarrantQuote [call_price=" + call_price + ", category=" + category + ", conversionRatio=" + return "WarrantQuote [callPrice=" + callPrice + ", category=" + category + ", conversionRatio=" + conversionRatio + ", expiryDate=" + expiryDate + ", high=" + high + ", impliedVolatility=" + impliedVolatility + ", lastDone=" + lastDone + ", lastTradeDate=" + lastTradeDate + ", low=" + low + ", lowerStrikePrice=" + lowerStrikePrice + ", open=" + open + ", openInterest=" + openInterest diff --git a/java/javasrc/src/main/java/com/longbridge/trade/TradeContext.java b/java/javasrc/src/main/java/com/longbridge/trade/TradeContext.java index 178685b760..049a696c5a 100644 --- a/java/javasrc/src/main/java/com/longbridge/trade/TradeContext.java +++ b/java/javasrc/src/main/java/com/longbridge/trade/TradeContext.java @@ -42,6 +42,34 @@ public void setOnOrderChange(OrderChangedHandler handler) { /** * Subscribe * + *
+     * {@code
+     * import com.longbridge.*;
+     * import com.longbridge.trade.*;
+     * import java.math.BigDecimal;
+     * 
+     * class Main {
+     *     public static void main(String[] args) throws Exception {
+     *         try (Config config = Config.fromEnv(); TradeContext ctx = TradeContext.create(config).get()) {
+     *             ctx.setOnOrderChange((order_changed) -> {
+     *                 System.out.println(order_changed);
+     *             });
+     *             ctx.subscribe(new TopicType[] { TopicType.Private }).get();
+     * 
+     *             SubmitOrderOptions opts = new SubmitOrderOptions("700.HK",
+     *                     OrderType.LO,
+     *                     OrderSide.Buy,
+     *                     200,
+     *                     TimeInForceType.Day).setSubmittedPrice(new BigDecimal(50));
+     *             SubmitOrderResponse resp = ctx.submitOrder(opts).get();
+     *             System.out.println(resp);
+     *             Thread.sleep(3000);
+     *         }
+     *     }
+     * }
+     * }
+     * 
+ * * @param topics Topics * @return A Future representing the result of the operation * @throws OpenApiException If an error occurs @@ -68,6 +96,28 @@ public CompletableFuture unsubscribe(TopicType[] topics) throws OpenApiExc /** * Get history executions * + *
+     * {@code
+     * import com.longbridge.*;
+     * import com.longbridge.trade.*;
+     * import java.time.*;
+     * 
+     * class Main {
+     *     public static void main(String[] args) throws Exception {
+     *         try (Config config = Config.fromEnv(); TradeContext ctx = TradeContext.create(config).get()) {
+     *             GetHistoryExecutionsOptions opts = new GetHistoryExecutionsOptions().setSymbol("700.HK")
+     *                     .setStartAt(OffsetDateTime.of(2022, 5, 9, 0, 0, 0, 0, ZoneOffset.UTC))
+     *                     .setEndAt(OffsetDateTime.of(2022, 5, 12, 0, 0, 0, 0, ZoneOffset.UTC));
+     *             Execution[] resp = ctx.getHistoryExecutions(opts).get();
+     *             for (Execution obj : resp) {
+     *                 System.out.println(obj);
+     *             }
+     *         }
+     *     }
+     * }
+     * }
+     * 
+ * * @param opts Options for this request * @return A Future representing the result of the operation * @throws OpenApiException If an error occurs @@ -82,6 +132,26 @@ public CompletableFuture getHistoryExecutions(GetHistoryExecutionsO /** * Get today executions * + *
+     * {@code
+     * import com.longbridge.*;
+     * import com.longbridge.trade.*;
+     * 
+     * class Main {
+     *     public static void main(String[] args) throws Exception {
+     *         try (Config config = Config.fromEnv(); TradeContext ctx = TradeContext.create(config).get()) {
+     *             GetTodayExecutionsOptions opts = new GetTodayExecutionsOptions().setSymbol("700.HK");
+     *             Execution[] resp = ctx.getTodayExecutions(opts).get();
+     *             for (Execution obj : resp) {
+     *                 System.out.println(obj);
+     *             }
+     *         }
+     *     }
+     * }
+     * 
+     * }
+     * 
+ * * @param opts Options for this request * @return A Future representing the result of the operation * @throws OpenApiException If an error occurs @@ -95,6 +165,31 @@ public CompletableFuture getTodayExecutions(GetTodayExecutionsOptio /** * Get history orders * + *
+     * {@code
+     * import com.longbridge.*;
+     * import com.longbridge.trade.*;
+     * import java.time.*;
+     * 
+     * class Main {
+     *     public static void main(String[] args) throws Exception {
+     *         try (Config config = Config.fromEnv(); TradeContext ctx = TradeContext.create(config).get()) {
+     *             GetHistoryOrdersOptions opts = new GetHistoryOrdersOptions().setSymbol("700.HK")
+     *                     .setStatus(new OrderStatus[] { OrderStatus.Filled, OrderStatus.New })
+     *                     .setSide(OrderSide.Buy)
+     *                     .setMarket(Market.HK)
+     *                     .setStartAt(OffsetDateTime.of(2022, 5, 9, 0, 0, 0, 0, ZoneOffset.UTC))
+     *                     .setStartAt(OffsetDateTime.of(2022, 5, 12, 0, 0, 0, 0, ZoneOffset.UTC));
+     *             Order[] resp = ctx.getHistoryOrders(opts).get();
+     *             for (Order obj : resp) {
+     *                 System.out.println(obj);
+     *             }
+     *         }
+     *     }
+     * }
+     * }
+     * 
+ * * @param opts Options for this request * @return A Future representing the result of the operation * @throws OpenApiException If an error occurs @@ -108,6 +203,28 @@ public CompletableFuture getHistoryOrders(GetHistoryOrdersOptions opts) /** * Get today orders * + *
+     * {@code
+     * import com.longbridge.*;
+     * import com.longbridge.trade.*;
+     * 
+     * class Main {
+     *     public static void main(String[] args) throws Exception {
+     *         try (Config config = Config.fromEnv(); TradeContext ctx = TradeContext.create(config).get()) {
+     *             GetTodayOrdersOptions opts = new GetTodayOrdersOptions().setSymbol("700.HK")
+     *                     .setStatus(new OrderStatus[] { OrderStatus.Filled, OrderStatus.New })
+     *                     .setSide(OrderSide.Buy)
+     *                     .setMarket(Market.HK);
+     *             Order[] resp = ctx.getTodayOrders(opts).get();
+     *             for (Order obj : resp) {
+     *                 System.out.println(obj);
+     *             }
+     *         }
+     *     }
+     * }
+     * }
+     * 
+ * * @param opts Options for this request * @return A Future representing the result of the operation * @throws OpenApiException If an error occurs @@ -121,7 +238,25 @@ public CompletableFuture getTodayOrders(GetTodayOrdersOptions opts) thr /** * Replace order * - * @param opts Options for this request + *
+     * {@code
+     * import com.longbridge.*;
+     * import com.longbridge.trade.*;
+     * import java.math.BigDecimal;
+     * 
+     * class Main {
+     *     public static void main(String[] args) throws Exception {
+     *         try (Config config = Config.fromEnv(); TradeContext ctx = TradeContext.create(config).get()) {
+     *             ReplaceOrderOptions opts = new ReplaceOrderOptions("709043056541253632", 100)
+     *                     .setPrice(new BigDecimal(300));
+     *             ctx.replaceOrder(opts).get();
+     *         }
+     *     }
+     * }
+     * }
+     * 
+ * + * @param opts Options for this request, not null * @return A Future representing the result of the operation * @throws OpenApiException If an error occurs */ @@ -134,7 +269,30 @@ public CompletableFuture replaceOrder(ReplaceOrderOptions opts) throws Ope /** * Submit order * - * @param opts Options for this request + *
+     * {@code
+     * import com.longbridge.*;
+     * import com.longbridge.trade.*;
+     * import java.math.BigDecimal;
+     * 
+     * class Main {
+     *     public static void main(String[] args) throws Exception {
+     *         try (Config config = Config.fromEnv(); TradeContext ctx = TradeContext.create(config).get()) {
+     *             SubmitOrderOptions opts = new SubmitOrderOptions(
+     *                     "700.HK",
+     *                     OrderType.LO,
+     *                     OrderSide.Buy,
+     *                     200,
+     *                     TimeInForceType.Day).setSubmittedPrice(new BigDecimal(50));
+     *             SubmitOrderResponse resp = ctx.submitOrder(opts).get();
+     *             System.out.println(resp);
+     *         }
+     *     }
+     * }
+     * }
+     * 
+ * + * @param opts Options for this request, not null * @return A Future representing the result of the operation * @throws OpenApiException If an error occurs */ @@ -147,6 +305,21 @@ public CompletableFuture submitOrder(SubmitOrderOptions opt /** * Cancel order * + *
+     * {@code
+     * import com.longbridge.*;
+     * import com.longbridge.trade.*;
+     * 
+     * class Main {
+     *     public static void main(String[] args) throws Exception {
+     *         try (Config config = Config.fromEnv(); TradeContext ctx = TradeContext.create(config).get()) {
+     *             ctx.cancelOrder("709043056541253632").get();
+     *         }
+     *     }
+     * }
+     * }
+     * 
+ * * @param orderId Order ID * @return A Future representing the result of the operation * @throws OpenApiException If an error occurs @@ -160,6 +333,24 @@ public CompletableFuture cancelOrder(String orderId) throws OpenApiExcepti /** * Get account balance * + *
+     * {@code
+     * import com.longbridge.*;
+     * import com.longbridge.trade.*;
+     * 
+     * class Main {
+     *     public static void main(String[] args) throws Exception {
+     *         try (Config config = Config.fromEnv(); TradeContext ctx = TradeContext.create(config).get()) {
+     *             AccountBalance[] resp = ctx.getAccountBalance().get();
+     *             for (AccountBalance obj : resp) {
+     *                 System.out.println(obj);
+     *             }
+     *         }
+     *     }
+     * }
+     * }
+     * 
+ * * @return A Future representing the result of the operation * @throws OpenApiException If an error occurs */ @@ -172,7 +363,29 @@ public CompletableFuture getAccountBalance() throws OpenApiExc /** * Get cash flow * - * @param opts Options for this request + *
+     * {@code
+     * import com.longbridge.*;
+     * import com.longbridge.trade.*;
+     * import java.time.*;
+     * 
+     * class Main {
+     *     public static void main(String[] args) throws Exception {
+     *         try (Config config = Config.fromEnv(); TradeContext ctx = TradeContext.create(config).get()) {
+     *             GetCashFlowOptions opts = new GetCashFlowOptions(
+     *                     OffsetDateTime.of(2022, 5, 9, 0, 0, 0, 0, ZoneOffset.UTC),
+     *                     OffsetDateTime.of(2022, 5, 12, 0, 0, 0, 0, ZoneOffset.UTC));
+     *             CashFlow[] resp = ctx.getCashFlow(opts).get();
+     *             for (CashFlow obj : resp) {
+     *                 System.out.println(obj);
+     *             }
+     *         }
+     *     }
+     * }
+     * }
+     * 
+ * + * @param opts Options for this request, not null * @return A Future representing the result of the operation * @throws OpenApiException If an error occurs */ @@ -185,6 +398,22 @@ public CompletableFuture getCashFlow(GetCashFlowOptions opts) throws /** * Get fund positions * + *
+     * {@code
+     * import com.longbridge.*;
+     * import com.longbridge.trade.*;
+     * 
+     * class Main {
+     *     public static void main(String[] args) throws Exception {
+     *         try (Config config = Config.fromEnv(); TradeContext ctx = TradeContext.create(config).get()) {
+     *             FundPositionsResponse resp = ctx.getFundPositions(null).get();
+     *             System.out.println(resp);
+     *         }
+     *     }
+     * }
+     * }
+     * 
+ * * @param opts Options for this request * @return A Future representing the result of the operation * @throws OpenApiException If an error occurs @@ -199,6 +428,22 @@ public CompletableFuture getFundPositions(GetFundPosition /** * Get stock positions * + *
+     * {@code
+     * import com.longbridge.*;
+     * import com.longbridge.trade.*;
+     * 
+     * class Main {
+     *     public static void main(String[] args) throws Exception {
+     *         try (Config config = Config.fromEnv(); TradeContext ctx = TradeContext.create(config).get()) {
+     *             StockPositionsResponse resp = ctx.getStockPositions(null).get();
+     *             System.out.println(resp);
+     *         }
+     *     }
+     * }
+     * }
+     * 
+ * * @param opts Options for this request * @return A Future representing the result of the operation * @throws OpenApiException If an error occurs diff --git a/java/src/quote_context.rs b/java/src/quote_context.rs index bfbf1e19c1..99a68920b1 100644 --- a/java/src/quote_context.rs +++ b/java/src/quote_context.rs @@ -51,7 +51,7 @@ fn send_push_event(jvm: &JavaVM, callbacks: &Callbacks, event: PushEvent) -> Res } } PushEventDetail::Depth(push_depth) => { - if let Some(handler) = &callbacks.quote { + if let Some(handler) = &callbacks.depth { env.call_method( handler, "onDepth", @@ -64,7 +64,7 @@ fn send_push_event(jvm: &JavaVM, callbacks: &Callbacks, event: PushEvent) -> Res } } PushEventDetail::Brokers(push_brokers) => { - if let Some(handler) = &callbacks.quote { + if let Some(handler) = &callbacks.brokers { env.call_method( handler, "onBrokers", @@ -77,7 +77,7 @@ fn send_push_event(jvm: &JavaVM, callbacks: &Callbacks, event: PushEvent) -> Res } } PushEventDetail::Trade(push_trades) => { - if let Some(handler) = &callbacks.quote { + if let Some(handler) = &callbacks.trades { env.call_method( handler, "onTrades", diff --git a/java/src/trade_context.rs b/java/src/trade_context.rs index 4f2b712e04..5c34e10011 100644 --- a/java/src/trade_context.rs +++ b/java/src/trade_context.rs @@ -44,7 +44,7 @@ fn send_push_event(jvm: &JavaVM, callbacks: &Callbacks, event: PushEvent) -> Res env.call_method( handler, "onOrderChanged", - "(Ljava/lang/String;Lcom/longbridge/trade/PushOrderChanged;)V", + "(Lcom/longbridge/trade/PushOrderChanged;)V", &[order_changed.into_jvalue(&env)?], )?; } @@ -463,17 +463,13 @@ pub unsafe extern "system" fn Java_com_longbridge_SdkNative_tradeContextCashFlow if let Some(business_type) = business_type { new_opts = new_opts.business_type(business_type); } - let page: Option = get_field(&env, opts, "page")?; - if let Some(page) = page { - if page > 0 { - new_opts = new_opts.page(page as usize); - } + let page: i32 = get_field(&env, opts, "page")?; + if page > 0 { + new_opts = new_opts.page(page as usize); } - let size: Option = get_field(&env, opts, "size")?; - if let Some(size) = size { - if size > 0 { - new_opts = new_opts.size(size as usize); - } + let size: i32 = get_field(&env, opts, "size")?; + if size > 0 { + new_opts = new_opts.size(size as usize); } async_util::execute(&env, callback, async move { diff --git a/java/test/Main.java b/java/test/Main.java index 0e64eac1e0..cbfe47e07c 100644 --- a/java/test/Main.java +++ b/java/test/Main.java @@ -1,19 +1,11 @@ import com.longbridge.*; import com.longbridge.trade.*; -import java.math.BigDecimal; class Main { public static void main(String[] args) throws Exception { - try (Config config = Config.fromEnv()) { - try (TradeContext ctx = TradeContext.create(config).get()) { - SubmitOrderOptions opts = new SubmitOrderOptions("700.HK", - OrderType.LO, - OrderSide.Buy, - 200, - TimeInForceType.Day).setSubmittedPrice(new BigDecimal(50)); - SubmitOrderResponse resp = ctx.submitOrder(opts).join(); - System.out.println(resp); - } + try (Config config = Config.fromEnv(); TradeContext ctx = TradeContext.create(config).get()) { + StockPositionsResponse resp = ctx.getStockPositions(null).get(); + System.out.println(resp); } } } From ffaf686ca3171eb22234279382bffade42f70ced Mon Sep 17 00:00:00 2001 From: Sunli Date: Fri, 17 Jun 2022 13:52:41 +0800 Subject: [PATCH 076/567] Release 0.2.18 longbridge@0.2.18 longbridge-httpcli@0.2.18 longbridge-java@0.2.18 longbridge-java-macros@0.2.18 longbridge-nodejs@0.2.18 longbridge-nodejs-macros@0.2.18 longbridge-proto@0.2.18 longbridge-python@0.2.18 longbridge-python-macros@0.2.18 longbridge-wscli@0.2.18 Generated by cargo-workspaces --- examples/java/account_asset/pom.xml | 2 +- examples/java/submit_order/pom.xml | 2 +- examples/java/subscribe_quote/pom.xml | 2 +- examples/java/today_orders/pom.xml | 2 +- examples/rust/Cargo.lock | 8 ++++---- java/Cargo.toml | 2 +- java/crates/macros/Cargo.toml | 2 +- nodejs/Cargo.toml | 6 +++--- nodejs/crates/macros/Cargo.toml | 2 +- python/Cargo.toml | 6 +++--- python/Makefile.toml | 2 +- python/crates/macros/Cargo.toml | 2 +- rust/Cargo.toml | 8 ++++---- rust/crates/httpclient/Cargo.toml | 2 +- rust/crates/proto/Cargo.toml | 2 +- rust/crates/wsclient/Cargo.toml | 4 ++-- 16 files changed, 27 insertions(+), 27 deletions(-) diff --git a/examples/java/account_asset/pom.xml b/examples/java/account_asset/pom.xml index ef1d38bfca..218786db4d 100644 --- a/examples/java/account_asset/pom.xml +++ b/examples/java/account_asset/pom.xml @@ -10,7 +10,7 @@ io.github.longbridgeapp openapi-sdk - 0.2.17 + 0.2.18 diff --git a/examples/java/submit_order/pom.xml b/examples/java/submit_order/pom.xml index ef1d38bfca..218786db4d 100644 --- a/examples/java/submit_order/pom.xml +++ b/examples/java/submit_order/pom.xml @@ -10,7 +10,7 @@ io.github.longbridgeapp openapi-sdk - 0.2.17 + 0.2.18 diff --git a/examples/java/subscribe_quote/pom.xml b/examples/java/subscribe_quote/pom.xml index ef1d38bfca..218786db4d 100644 --- a/examples/java/subscribe_quote/pom.xml +++ b/examples/java/subscribe_quote/pom.xml @@ -10,7 +10,7 @@ io.github.longbridgeapp openapi-sdk - 0.2.17 + 0.2.18 diff --git a/examples/java/today_orders/pom.xml b/examples/java/today_orders/pom.xml index ef1d38bfca..218786db4d 100644 --- a/examples/java/today_orders/pom.xml +++ b/examples/java/today_orders/pom.xml @@ -10,7 +10,7 @@ io.github.longbridgeapp openapi-sdk - 0.2.17 + 0.2.18 diff --git a/examples/rust/Cargo.lock b/examples/rust/Cargo.lock index 4754c2ebe3..6ec2ab3356 100644 --- a/examples/rust/Cargo.lock +++ b/examples/rust/Cargo.lock @@ -482,7 +482,7 @@ dependencies = [ [[package]] name = "longbridge" -version = "0.2.17" +version = "0.2.18" dependencies = [ "bitflags", "dotenv", @@ -505,7 +505,7 @@ dependencies = [ [[package]] name = "longbridge-httpcli" -version = "0.2.17" +version = "0.2.18" dependencies = [ "futures-util", "hmac", @@ -523,7 +523,7 @@ dependencies = [ [[package]] name = "longbridge-proto" -version = "0.2.17" +version = "0.2.18" dependencies = [ "prost", "prost-build", @@ -531,7 +531,7 @@ dependencies = [ [[package]] name = "longbridge-wscli" -version = "0.2.17" +version = "0.2.18" dependencies = [ "byteorder", "flate2", diff --git a/java/Cargo.toml b/java/Cargo.toml index 976ab47f42..01c898f3ad 100644 --- a/java/Cargo.toml +++ b/java/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge-java" -version = "0.2.17" +version = "0.2.18" [lib] crate-type = ["cdylib"] diff --git a/java/crates/macros/Cargo.toml b/java/crates/macros/Cargo.toml index c535b800e4..05bf116221 100644 --- a/java/crates/macros/Cargo.toml +++ b/java/crates/macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "longbridge-java-macros" -version = "0.2.17" +version = "0.2.18" edition = "2021" [lib] diff --git a/nodejs/Cargo.toml b/nodejs/Cargo.toml index 2731906d9b..dab4b313c1 100644 --- a/nodejs/Cargo.toml +++ b/nodejs/Cargo.toml @@ -1,14 +1,14 @@ [package] edition = "2021" name = "longbridge-nodejs" -version = "0.2.17" +version = "0.2.18" [lib] crate-type = ["cdylib"] [dependencies] -longbridge = { path = "../rust", version = "0.2.17" } -longbridge-nodejs-macros = { path = "crates/macros", version = "0.2.17" } +longbridge = { path = "../rust", version = "0.2.18" } +longbridge-nodejs-macros = { path = "crates/macros", version = "0.2.18" } napi = { version = "2.5.0", default-features = false, features = [ "napi4", diff --git a/nodejs/crates/macros/Cargo.toml b/nodejs/crates/macros/Cargo.toml index ef378817b4..fba7e17650 100644 --- a/nodejs/crates/macros/Cargo.toml +++ b/nodejs/crates/macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "longbridge-nodejs-macros" -version = "0.2.17" +version = "0.2.18" edition = "2021" [lib] diff --git a/python/Cargo.toml b/python/Cargo.toml index 22f9cd3226..3dc2c6cf4d 100644 --- a/python/Cargo.toml +++ b/python/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge-python" -version = "0.2.17" +version = "0.2.18" description = "Longbridge OpenAPI SDK for Python" homepage = "https://open.longbridgeapp.com/en/" readme = "README.md" @@ -15,8 +15,8 @@ name = "longbridge" crate-type = ["cdylib"] [dependencies] -longbridge = { path = "../rust", version = "0.2.17", features = ["blocking"] } -longbridge-python-macros = { path = "crates/macros", version = "0.2.17" } +longbridge = { path = "../rust", version = "0.2.18", features = ["blocking"] } +longbridge-python-macros = { path = "crates/macros", version = "0.2.18" } once_cell = "1.11.0" parking_lot = "0.12.1" diff --git a/python/Makefile.toml b/python/Makefile.toml index e0b5f367e4..b34fdaea35 100644 --- a/python/Makefile.toml +++ b/python/Makefile.toml @@ -12,7 +12,7 @@ args = ["install", "maturin>=0.12,<0.13"] command = "pip" args = [ "install", - "target/wheels/longbridge-0.2.17-cp310-none-win_amd64.whl", + "target/wheels/longbridge-0.2.18-cp310-none-win_amd64.whl", "-I", ] dependencies = ["build-python-sdk"] diff --git a/python/crates/macros/Cargo.toml b/python/crates/macros/Cargo.toml index 38adda71ca..efd1a46606 100644 --- a/python/crates/macros/Cargo.toml +++ b/python/crates/macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "longbridge-python-macros" -version = "0.2.17" +version = "0.2.18" edition = "2021" [lib] diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 3d2fce4a41..6d34f7f76b 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge" -version = "0.2.17" +version = "0.2.18" description = "Longbridge OpenAPI SDK for Rust" homepage = "https://open.longbridgeapp.com/en/" readme = "README.md" @@ -14,9 +14,9 @@ categories = ["api-bindings"] blocking = ["flume"] [dependencies] -longbridge-wscli = { path = "crates/wsclient", version = "0.2.17" } -longbridge-httpcli = { path = "crates/httpclient", version = "0.2.17" } -longbridge-proto = { path = "crates/proto", version = "0.2.17" } +longbridge-wscli = { path = "crates/wsclient", version = "0.2.18" } +longbridge-httpcli = { path = "crates/httpclient", version = "0.2.18" } +longbridge-proto = { path = "crates/proto", version = "0.2.18" } tokio = { version = "1.18.2", features = [ "time", diff --git a/rust/crates/httpclient/Cargo.toml b/rust/crates/httpclient/Cargo.toml index 1d72520d27..fc49ebaaf7 100644 --- a/rust/crates/httpclient/Cargo.toml +++ b/rust/crates/httpclient/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge-httpcli" -version = "0.2.17" +version = "0.2.18" description = "Longbridge HTTP SDK for Rust" license = "MIT OR Apache-2.0" diff --git a/rust/crates/proto/Cargo.toml b/rust/crates/proto/Cargo.toml index f60c790adf..98a4e5791f 100644 --- a/rust/crates/proto/Cargo.toml +++ b/rust/crates/proto/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge-proto" -version = "0.2.17" +version = "0.2.18" description = "Longbridge Protocol" license = "MIT OR Apache-2.0" diff --git a/rust/crates/wsclient/Cargo.toml b/rust/crates/wsclient/Cargo.toml index 858ad97cbb..1e1e4837ec 100644 --- a/rust/crates/wsclient/Cargo.toml +++ b/rust/crates/wsclient/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "longbridge-wscli" -version = "0.2.17" +version = "0.2.18" edition = "2021" description = "Longbridge Websocket SDK for Rust" license = "MIT OR Apache-2.0" [dependencies] -longbridge-proto = { path = "../proto", version = "0.2.17" } +longbridge-proto = { path = "../proto", version = "0.2.18" } tokio = { version = "1.18.2", features = [ "time", From 70c091cbd707fa5346a798d6293d37247dcf84d2 Mon Sep 17 00:00:00 2001 From: Sunli Date: Fri, 17 Jun 2022 14:08:52 +0800 Subject: [PATCH 077/567] Update ci.yml --- .github/workflows/ci.yml | 3 ++- java/Makefile.toml | 47 ---------------------------------------- 2 files changed, 2 insertions(+), 48 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f5c8713302..cc81f28212 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -205,4 +205,5 @@ jobs: run: cargo test -p longbridge-java --all-features - name: Compile java sources - run: cargo make mvn-package + working-directory: java/javasrc + run: mvn package diff --git a/java/Makefile.toml b/java/Makefile.toml index 21436b2a72..94f843c97d 100644 --- a/java/Makefile.toml +++ b/java/Makefile.toml @@ -15,53 +15,6 @@ args = [ ] cwd = "java" -[tasks.move-jni-target] -script_runner = "@rust" -script = ''' -fn main() { - let _ = std::fs::remove_dir_all("java/javasrc/target/natives"); - - #[cfg(all(target_os = "linux", target_arch = "x86_64"))] - { - std::fs::create_dir_all("java/javasrc/target/natives/linux_64").unwrap(); - std::fs::copy( - "target/debug/longbridge_java.so", - "java/javasrc/target/natives/linux_64/longbridge_java.so" - ).unwrap(); - } - - #[cfg(all(target_os = "macos", target_arch = "x86_64"))] - { - std::fs::create_dir_all("java/javasrc/target/natives/osx_64").unwrap(); - std::fs::copy( - "target/debug/longbridge_java.so", - "java/javasrc/target/natives/osx_64/longbridge_java.so" - ).unwrap(); - } - - #[cfg(all(target_os = "macos", target_arch = "aarch64"))] - { - std::fs::create_dir_all("java/javasrc/target/natives/osx_arm64").unwrap(); - std::fs::copy( - "target/debug/longbridge_java.dylib", - "java/javasrc/target/natives/osx_arm64/longbridge_java.dylib" - ).unwrap(); - } - - #[cfg(all(target_os = "windows", target_arch = "x86_64"))] - { - std::fs::create_dir_all("java/javasrc/target/natives/windows_64").unwrap(); - std::fs::copy( - "target/debug/longbridge_java.dll", - "java/javasrc/target/natives/windows_64/longbridge_java.dll" - ).unwrap(); - } -} -''' - -[tasks.package-debug-jar] -dependencies = ["java", "move-jni-target", "mvn-package"] - [tasks.compile-java-test] command = "javac" cwd = "java" From 715bb6e6576c84e86fca63187d9f04243ca6470c Mon Sep 17 00:00:00 2001 From: Jason Lee Date: Sat, 18 Jun 2022 20:26:17 +0800 Subject: [PATCH 078/567] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 63b30a6ebd..bb6f025bc6 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Longbridge OpenAPI provides programmatic quote trading interfaces for investors | [Rust](rust/README.md) | Longbridge OpenAPI for Rust `(>= 1.56.1)` | | [Python](python/README.md) | Longbridge OpenAPI for Python 3 `(>= 3.7)` | | [Node.js](nodejs/README.md) | Longbridge OpenAPI for Node.js `(>= 10)` | -| Go | WIP | +| Go | https://github.com/longbridgeapp/openapi-go | ## How to enable Longbridge OpenAPI From da188615e51098ddbb074ba5575a468a02a33ebf Mon Sep 17 00:00:00 2001 From: Sunli Date: Sat, 18 Jun 2022 21:08:51 +0800 Subject: [PATCH 079/567] Fixed java examples --- examples/java/account_asset/pom.xml | 18 ++++++---- .../examples/account_asset => }/main.java | 0 examples/java/submit_order/pom.xml | 18 ++++++---- .../submit_order/main.java => Main.java} | 2 +- examples/java/subscribe_quote/pom.xml | 16 +++++---- .../subscribe_quote/main.java => Main.java} | 0 examples/java/today_orders/pom.xml | 18 ++++++---- .../examples/today_orders => }/main.java | 0 java/Makefile.toml | 36 +++++++++---------- java/test/Main.java | 8 ++++- 10 files changed, 69 insertions(+), 47 deletions(-) rename examples/java/account_asset/src/main/java/{com/longbridge/examples/account_asset => }/main.java (100%) rename examples/java/submit_order/src/main/java/{com/longbridge/examples/submit_order/main.java => Main.java} (96%) rename examples/java/subscribe_quote/src/main/java/{com/longbridge/examples/subscribe_quote/main.java => Main.java} (100%) rename examples/java/today_orders/src/main/java/{com/longbridge/examples/today_orders => }/main.java (100%) diff --git a/examples/java/account_asset/pom.xml b/examples/java/account_asset/pom.xml index 218786db4d..1b406ba2bb 100644 --- a/examples/java/account_asset/pom.xml +++ b/examples/java/account_asset/pom.xml @@ -3,7 +3,7 @@ 4.0.0 io.github.longbridgeapp - subscribe_quote + account_asset 0.0.1 @@ -23,18 +23,22 @@ org.codehaus.mojo exec-maven-plugin - 1.1.1 + 3.0.0 - test - java + exec - - com.longbridge.examples.subscribe_quote.Main - + + java + + -classpath + + Main + + diff --git a/examples/java/account_asset/src/main/java/com/longbridge/examples/account_asset/main.java b/examples/java/account_asset/src/main/java/main.java similarity index 100% rename from examples/java/account_asset/src/main/java/com/longbridge/examples/account_asset/main.java rename to examples/java/account_asset/src/main/java/main.java diff --git a/examples/java/submit_order/pom.xml b/examples/java/submit_order/pom.xml index 218786db4d..32c9b3e5a2 100644 --- a/examples/java/submit_order/pom.xml +++ b/examples/java/submit_order/pom.xml @@ -3,7 +3,7 @@ 4.0.0 io.github.longbridgeapp - subscribe_quote + submit_order 0.0.1 @@ -23,18 +23,22 @@ org.codehaus.mojo exec-maven-plugin - 1.1.1 + 3.0.0 - test - java + exec - - com.longbridge.examples.subscribe_quote.Main - + + java + + -classpath + + Main + + diff --git a/examples/java/submit_order/src/main/java/com/longbridge/examples/submit_order/main.java b/examples/java/submit_order/src/main/java/Main.java similarity index 96% rename from examples/java/submit_order/src/main/java/com/longbridge/examples/submit_order/main.java rename to examples/java/submit_order/src/main/java/Main.java index d5465824ca..18fd183c6b 100644 --- a/examples/java/submit_order/src/main/java/com/longbridge/examples/submit_order/main.java +++ b/examples/java/submit_order/src/main/java/Main.java @@ -2,7 +2,7 @@ import com.longbridge.trade.*; import java.math.BigDecimal; -class Main { +public class Main { public static void main(String[] args) throws Exception { try (Config config = Config.fromEnv(); TradeContext ctx = TradeContext.create(config).get()) { SubmitOrderOptions opts = new SubmitOrderOptions("700.HK", diff --git a/examples/java/subscribe_quote/pom.xml b/examples/java/subscribe_quote/pom.xml index 218786db4d..bf4a3d530e 100644 --- a/examples/java/subscribe_quote/pom.xml +++ b/examples/java/subscribe_quote/pom.xml @@ -23,18 +23,22 @@ org.codehaus.mojo exec-maven-plugin - 1.1.1 + 3.0.0 - test - java + exec - - com.longbridge.examples.subscribe_quote.Main - + + java + + -classpath + + Main + + diff --git a/examples/java/subscribe_quote/src/main/java/com/longbridge/examples/subscribe_quote/main.java b/examples/java/subscribe_quote/src/main/java/Main.java similarity index 100% rename from examples/java/subscribe_quote/src/main/java/com/longbridge/examples/subscribe_quote/main.java rename to examples/java/subscribe_quote/src/main/java/Main.java diff --git a/examples/java/today_orders/pom.xml b/examples/java/today_orders/pom.xml index 218786db4d..030910ece9 100644 --- a/examples/java/today_orders/pom.xml +++ b/examples/java/today_orders/pom.xml @@ -3,7 +3,7 @@ 4.0.0 io.github.longbridgeapp - subscribe_quote + today_orders 0.0.1 @@ -23,18 +23,22 @@ org.codehaus.mojo exec-maven-plugin - 1.1.1 + 3.0.0 - test - java + exec - - com.longbridge.examples.subscribe_quote.Main - + + java + + -classpath + + Main + + diff --git a/examples/java/today_orders/src/main/java/com/longbridge/examples/today_orders/main.java b/examples/java/today_orders/src/main/java/main.java similarity index 100% rename from examples/java/today_orders/src/main/java/com/longbridge/examples/today_orders/main.java rename to examples/java/today_orders/src/main/java/main.java diff --git a/java/Makefile.toml b/java/Makefile.toml index 94f843c97d..ce06be5541 100644 --- a/java/Makefile.toml +++ b/java/Makefile.toml @@ -5,13 +5,13 @@ args = ["build", "-p", "longbridge-java"] [tasks.javah] command = "javac" args = [ - "-h", - "c", - "-sourcepath", - "javasrc", - "-d", - "classes", - "javasrc/com/longbridge/SdkNative.java", + "-h", + "c", + "-sourcepath", + "javasrc", + "-d", + "classes", + "javasrc/com/longbridge/SdkNative.java", ] cwd = "java" @@ -19,21 +19,21 @@ cwd = "java" command = "javac" cwd = "java" args = [ - "-cp", - "libs/native-lib-loader-2.4.0.jar;libs/slf4j-api-1.7.30.jar;", - "-sourcepath", - "javasrc/src/main/java", - "-d", - "classes", - "test/Main.java", + "-cp", + "libs/native-lib-loader-2.4.0.jar;libs/slf4j-api-1.7.30.jar;", + "-sourcepath", + "javasrc/src/main/java", + "-d", + "classes", + "test/Main.java", ] [tasks.test-java] command = "java" args = [ - "-Djava.library.path=target/debug", - "-cp", - "java/classes;java/libs/native-lib-loader-2.4.0.jar;java/libs/slf4j-api-1.7.30.jar;", - "Main", + "-Djava.library.path=target/debug", + "-cp", + "java/classes;java/libs/native-lib-loader-2.4.0.jar;java/libs/slf4j-api-1.7.30.jar;", + "Main", ] dependencies = ["java", "compile-java-test"] diff --git a/java/test/Main.java b/java/test/Main.java index cbfe47e07c..d5465824ca 100644 --- a/java/test/Main.java +++ b/java/test/Main.java @@ -1,10 +1,16 @@ import com.longbridge.*; import com.longbridge.trade.*; +import java.math.BigDecimal; class Main { public static void main(String[] args) throws Exception { try (Config config = Config.fromEnv(); TradeContext ctx = TradeContext.create(config).get()) { - StockPositionsResponse resp = ctx.getStockPositions(null).get(); + SubmitOrderOptions opts = new SubmitOrderOptions("700.HK", + OrderType.LO, + OrderSide.Buy, + 200, + TimeInForceType.Day).setSubmittedPrice(new BigDecimal(50)); + SubmitOrderResponse resp = ctx.submitOrder(opts).get(); System.out.println(resp); } } From 24335bcfbabf54b86b217e11cf5a9329897a2763 Mon Sep 17 00:00:00 2001 From: Sunli Date: Sat, 18 Jun 2022 21:16:56 +0800 Subject: [PATCH 080/567] Add README.md for Java SDK --- README.md | 13 ++++--- java/README.md | 102 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+), 6 deletions(-) create mode 100644 java/README.md diff --git a/README.md b/README.md index bb6f025bc6..c94ae3588b 100644 --- a/README.md +++ b/README.md @@ -9,12 +9,13 @@ Longbridge OpenAPI provides programmatic quote trading interfaces for investors **This repo contains the following main components:** -| Name | Description | -|-----------------------------|--------------------------------------------| -| [Rust](rust/README.md) | Longbridge OpenAPI for Rust `(>= 1.56.1)` | -| [Python](python/README.md) | Longbridge OpenAPI for Python 3 `(>= 3.7)` | -| [Node.js](nodejs/README.md) | Longbridge OpenAPI for Node.js `(>= 10)` | -| Go | https://github.com/longbridgeapp/openapi-go | +| Name | Description | +|-----------------------------|---------------------------------------------| +| [Rust](rust/README.md) | Longbridge OpenAPI for Rust `(>= 1.56.1)` | +| [Python](python/README.md) | Longbridge OpenAPI for Python 3 `(>= 3.7)` | +| [Node.js](nodejs/README.md) | Longbridge OpenAPI for Node.js `(>= 10)` | +| [Java](java/README.md) | Longbridge OpenAPI for Java `(>= 1.8)` | +| Go | https://github.com/longbridgeapp/openapi-go | ## How to enable Longbridge OpenAPI diff --git a/java/README.md b/java/README.md new file mode 100644 index 0000000000..e26b211a97 --- /dev/null +++ b/java/README.md @@ -0,0 +1,102 @@ +# Longbridge OpenAPI SDK for Java + +`longbridge` provides an easy-to-use interface for invokes [`Longbridge OpenAPI`](https://open.longbridgeapp.com/en/). + +## Quickstart + +_Install Longbridge OpenAPI SDK_ + +Add `io.github.longbridgeapp:openapi-sdk` to `pom.xml` + +```xml + + + io.github.longbridgeapp + openapi-sdk + 0.2.18 + + +``` + +_Setting environment variables(MacOS/Linux)_ + +```bash +export LONGBRIDGE_APP_KEY="App Key get from user center" +export LONGBRIDGE_APP_SECRET="App Secret get from user center" +export LONGBRIDGE_ACCESS_TOKEN="Access Token get from user center" +``` + +_Setting environment variables(Windows)_ + +```powershell +setx LONGBRIDGE_APP_KEY "App Key get from user center" +setx LONGBRIDGE_APP_SECRET "App Secret get from user center" +setx LONGBRIDGE_ACCESS_TOKEN "Access Token get from user center" +``` + +## Quote API _(Get basic information of securities)_ + +```java +import com.longbridge.*; +import com.longbridge.quote.*; +import java.math.BigDecimal; + +class Main { + public static void main(String[] args) throws Exception { + try (Config config = Config.fromEnv(); QuoteContext ctx = QuoteContext.create(config).get()) { + SecurityQuote[] resp = ctx.getQuote(new String[] { "700.HK", "AAPL.US", "TSLA.US", "NFLX.US" }).get(); + for (SecurityQuote obj : resp) { + System.out.println(obj); + } + } + } +} +``` + +## Quote API _(Subscribe quotes)_ + +```java +import com.longbridge.*; +import com.longbridge.quote.*; + +class Main { + public static void main(String[] args) throws Exception { + try (Config config = Config.fromEnv(); QuoteContext ctx = QuoteContext.create(config).get()) { + ctx.setOnQuote((symbol, quote) -> { + System.out.printf("%s\t%s\n", symbol, quote); + }); + ctx.subscribe(new String[] { "700.HK", "AAPL.US", "TSLA.US", "NFLX.US" }, SubFlags.Quote, true).get(); + Thread.sleep(30000); + } + } +} +``` + +## Trade API _(Submit order)_ + +```java +import com.longbridge.*; +import com.longbridge.trade.*; +import java.math.BigDecimal; + +public class Main { + public static void main(String[] args) throws Exception { + try (Config config = Config.fromEnv(); TradeContext ctx = TradeContext.create(config).get()) { + SubmitOrderOptions opts = new SubmitOrderOptions("700.HK", + OrderType.LO, + OrderSide.Buy, + 200, + TimeInForceType.Day).setSubmittedPrice(new BigDecimal(50)); + SubmitOrderResponse resp = ctx.submitOrder(opts).get(); + System.out.println(resp); + } + } +} +``` + +## License + +Licensed under either of + +* Apache License, Version 2.0,([LICENSE-APACHE](./LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) +* MIT license ([LICENSE-MIT](./LICENSE-MIT) or http://opensource.org/licenses/MIT) at your option. From cd44bf0b6e3fa18475384642b472faeeeae27fd3 Mon Sep 17 00:00:00 2001 From: Sunli Date: Mon, 20 Jun 2022 13:11:41 +0800 Subject: [PATCH 081/567] Add Capital APIs --- java/Makefile.toml | 6 +- java/c/com_longbridge_SdkNative.h | 16 ++ .../main/java/com/longbridge/SdkNative.java | 4 + .../longbridge/quote/CapitalDistribution.java | 26 +++ .../quote/CapitalDistributionResponse.java | 27 +++ .../com/longbridge/quote/CapitalFlowLine.java | 22 ++ .../com/longbridge/quote/QuoteContext.java | 61 ++++++ java/src/quote_context.rs | 36 ++++ java/src/types/classes.rs | 18 ++ nodejs/src/quote/context.rs | 60 +++++- nodejs/src/quote/types.rs | 39 ++++ python/pysrc/longbridge/openapi.pyi | 102 +++++++++ python/src/quote/context.rs | 27 ++- python/src/quote/types.rs | 37 ++++ rust/crates/proto/build.rs | 1 + rust/crates/proto/openapi-protobufs | 2 +- .../proto/src/longbridgeapp.control.v1.rs | 2 + .../proto/src/longbridgeapp.quote.v1.rs | 197 ++++++++++++++++++ rust/src/blocking/quote.rs | 62 +++++- rust/src/quote/cmd_code.rs | 6 + rust/src/quote/context.rs | 74 ++++++- rust/src/quote/mod.rs | 10 +- rust/src/quote/types.rs | 76 +++++++ 23 files changed, 887 insertions(+), 24 deletions(-) create mode 100644 java/javasrc/src/main/java/com/longbridge/quote/CapitalDistribution.java create mode 100644 java/javasrc/src/main/java/com/longbridge/quote/CapitalDistributionResponse.java create mode 100644 java/javasrc/src/main/java/com/longbridge/quote/CapitalFlowLine.java diff --git a/java/Makefile.toml b/java/Makefile.toml index ce06be5541..03b7ba889b 100644 --- a/java/Makefile.toml +++ b/java/Makefile.toml @@ -7,11 +7,13 @@ command = "javac" args = [ "-h", "c", + "-cp", + "libs/native-lib-loader-2.4.0.jar;libs/slf4j-api-1.7.30.jar;", "-sourcepath", - "javasrc", + "javasrc/src/main/java", "-d", "classes", - "javasrc/com/longbridge/SdkNative.java", + "javasrc/src/main/java/com/longbridge/SdkNative.java", ] cwd = "java" diff --git a/java/c/com_longbridge_SdkNative.h b/java/c/com_longbridge_SdkNative.h index 02ee878fd5..08abfd162d 100644 --- a/java/c/com_longbridge_SdkNative.h +++ b/java/c/com_longbridge_SdkNative.h @@ -231,6 +231,22 @@ JNIEXPORT void JNICALL Java_com_longbridge_SdkNative_quoteContextTradingSession JNIEXPORT void JNICALL Java_com_longbridge_SdkNative_quoteContextTradingDays (JNIEnv *, jclass, jlong, jobject, jobject, jobject, jobject); +/* + * Class: com_longbridge_SdkNative + * Method: quoteContextCapitalFlow + * Signature: (JLjava/lang/String;Lcom/longbridge/AsyncCallback;)V + */ +JNIEXPORT void JNICALL Java_com_longbridge_SdkNative_quoteContextCapitalFlow + (JNIEnv *, jclass, jlong, jstring, jobject); + +/* + * Class: com_longbridge_SdkNative + * Method: quoteContextCapitalDistribution + * Signature: (JLjava/lang/String;Lcom/longbridge/AsyncCallback;)V + */ +JNIEXPORT void JNICALL Java_com_longbridge_SdkNative_quoteContextCapitalDistribution + (JNIEnv *, jclass, jlong, jstring, jobject); + /* * Class: com_longbridge_SdkNative * Method: quoteContextRealtimeQuote diff --git a/java/javasrc/src/main/java/com/longbridge/SdkNative.java b/java/javasrc/src/main/java/com/longbridge/SdkNative.java index da026f37f4..7a7bd2d8b8 100644 --- a/java/javasrc/src/main/java/com/longbridge/SdkNative.java +++ b/java/javasrc/src/main/java/com/longbridge/SdkNative.java @@ -74,6 +74,10 @@ public static native void quoteContextOptionChainInfoByDate(long context, String public static native void quoteContextTradingDays(long context, Market market, LocalDate begin, LocalDate end, AsyncCallback callback); + public static native void quoteContextCapitalFlow(long context, String symbol, AsyncCallback callback); + + public static native void quoteContextCapitalDistribution(long context, String symbol, AsyncCallback callback); + public static native void quoteContextRealtimeQuote(long context, String[] symbols, AsyncCallback callback); public static native void quoteContextRealtimeDepth(long context, String symbol, AsyncCallback callback); diff --git a/java/javasrc/src/main/java/com/longbridge/quote/CapitalDistribution.java b/java/javasrc/src/main/java/com/longbridge/quote/CapitalDistribution.java new file mode 100644 index 0000000000..292fb493cf --- /dev/null +++ b/java/javasrc/src/main/java/com/longbridge/quote/CapitalDistribution.java @@ -0,0 +1,26 @@ +package com.longbridge.quote; + +import java.math.BigDecimal; + +public class CapitalDistribution { + private BigDecimal large; + private BigDecimal medium; + private BigDecimal small; + + public BigDecimal getLarge() { + return large; + } + + public BigDecimal getMedium() { + return medium; + } + + public BigDecimal getSmall() { + return small; + } + + @Override + public String toString() { + return "CapitalDistribution [large=" + large + ", medium=" + medium + ", small=" + small + "]"; + } +} diff --git a/java/javasrc/src/main/java/com/longbridge/quote/CapitalDistributionResponse.java b/java/javasrc/src/main/java/com/longbridge/quote/CapitalDistributionResponse.java new file mode 100644 index 0000000000..2232e77568 --- /dev/null +++ b/java/javasrc/src/main/java/com/longbridge/quote/CapitalDistributionResponse.java @@ -0,0 +1,27 @@ +package com.longbridge.quote; + +import java.time.OffsetDateTime; + +public class CapitalDistributionResponse { + private OffsetDateTime timestamp; + private CapitalDistribution capitalIn; + private CapitalDistribution capitalOut; + + public OffsetDateTime getTimestamp() { + return timestamp; + } + + public CapitalDistribution getCapitalIn() { + return capitalIn; + } + + public CapitalDistribution getCapitalOut() { + return capitalOut; + } + + @Override + public String toString() { + return "CapitalDistributionResponse [capitalIn=" + capitalIn + ", capitalOut=" + capitalOut + ", timestamp=" + + timestamp + "]"; + } +} diff --git a/java/javasrc/src/main/java/com/longbridge/quote/CapitalFlowLine.java b/java/javasrc/src/main/java/com/longbridge/quote/CapitalFlowLine.java new file mode 100644 index 0000000000..24a0821c75 --- /dev/null +++ b/java/javasrc/src/main/java/com/longbridge/quote/CapitalFlowLine.java @@ -0,0 +1,22 @@ +package com.longbridge.quote; + +import java.math.BigDecimal; +import java.time.OffsetDateTime; + +public class CapitalFlowLine { + private BigDecimal inflow; + private OffsetDateTime timestamp; + + public BigDecimal getInflow() { + return inflow; + } + + public OffsetDateTime getTimestamp() { + return timestamp; + } + + @Override + public String toString() { + return "CapitalFlowLine [inflow=" + inflow + ", timestamp=" + timestamp + "]"; + } +} diff --git a/java/javasrc/src/main/java/com/longbridge/quote/QuoteContext.java b/java/javasrc/src/main/java/com/longbridge/quote/QuoteContext.java index 42c675e921..b4e2341726 100644 --- a/java/javasrc/src/main/java/com/longbridge/quote/QuoteContext.java +++ b/java/javasrc/src/main/java/com/longbridge/quote/QuoteContext.java @@ -647,6 +647,67 @@ public CompletableFuture getTradingDays(Market market, LocalD }); } + /** + * Get capital flow intraday + * + *
+     * {@code
+     * import com.longbridge.*;
+     * import com.longbridge.quote.*;
+     * 
+     * class Main {
+     *     public static void main(String[] args) throws Exception {
+     *         try (Config config = Config.fromEnv(); QuoteContext ctx = QuoteContext.create(config).get()) {
+     *             CapitalFlowLine[] resp = ctx.getCapitalFlow("700.HK").get();
+     *             for (CapitalFlowLine obj : resp) {
+     *                 System.out.println(obj);
+     *             }
+     *         }
+     *     }
+     * }
+     * }
+     * 
+ * + * @param symbol Security code + * @return A Future representing the result of the operation + * @throws OpenApiException If an error occurs + */ + public CompletableFuture getCapitalFlow(String symbol) throws OpenApiException { + return AsyncCallback.executeTask((callback) -> { + SdkNative.quoteContextCapitalFlow(this.raw, symbol, callback); + }); + } + + /** + * Get capital distribution + * + *
+     * {@code
+     * import com.longbridge.*;
+     * import com.longbridge.quote.*;
+     * 
+     * class Main {
+     *     public static void main(String[] args) throws Exception {
+     *         try (Config config = Config.fromEnv(); QuoteContext ctx = QuoteContext.create(config).get()) {
+     *             CapitalDistributionResponse resp = ctx.getCapitalDistribution("700.HK").get();
+     *             System.out.println(resp);
+     *         }
+     *     }
+     * }
+     * }
+     * 
+ * + * @param symbol Security code + * @return A Future representing the result of the operation + * @throws OpenApiException If an error occurs + */ + public CompletableFuture getCapitalDistribution(String symbol) + throws OpenApiException { + return AsyncCallback.executeTask((callback) -> { + SdkNative.quoteContextCapitalDistribution(this.raw, symbol, callback); + }); + } + /** * Get real-time quotes *

diff --git a/java/src/quote_context.rs b/java/src/quote_context.rs index 99a68920b1..e6dddf7521 100644 --- a/java/src/quote_context.rs +++ b/java/src/quote_context.rs @@ -575,6 +575,42 @@ pub unsafe extern "system" fn Java_com_longbridge_SdkNative_quoteContextTradingD }) } +#[no_mangle] +pub unsafe extern "system" fn Java_com_longbridge_SdkNative_quoteContextCapitalFlow( + env: JNIEnv, + _class: JClass, + context: i64, + symbol: JString, + callback: JObject, +) { + jni_result(&env, (), || { + let context = &*(context as *const ContextObj); + let symbol: String = FromJValue::from_jvalue(&env, symbol.into())?; + async_util::execute(&env, callback, async move { + Ok(ObjectArray(context.ctx.capital_flow(symbol).await?)) + })?; + Ok(()) + }) +} + +#[no_mangle] +pub unsafe extern "system" fn Java_com_longbridge_SdkNative_quoteContextCapitalDistribution( + env: JNIEnv, + _class: JClass, + context: i64, + symbol: JString, + callback: JObject, +) { + jni_result(&env, (), || { + let context = &*(context as *const ContextObj); + let symbol: String = FromJValue::from_jvalue(&env, symbol.into())?; + async_util::execute(&env, callback, async move { + Ok(context.ctx.capital_distribution(symbol).await?) + })?; + Ok(()) + }) +} + #[no_mangle] pub unsafe extern "system" fn Java_com_longbridge_SdkNative_quoteContextRealtimeQuote( env: JNIEnv, diff --git a/java/src/types/classes.rs b/java/src/types/classes.rs index 94f5f6237c..5d42f92c82 100644 --- a/java/src/types/classes.rs +++ b/java/src/types/classes.rs @@ -271,6 +271,24 @@ impl_java_class!( ] ); +impl_java_class!( + "com/longbridge/quote/CapitalFlowLine", + longbridge::quote::CapitalFlowLine, + [inflow, timestamp] +); + +impl_java_class!( + "com/longbridge/quote/CapitalDistribution", + longbridge::quote::CapitalDistribution, + [large, medium, small] +); + +impl_java_class!( + "com/longbridge/quote/CapitalDistributionResponse", + longbridge::quote::CapitalDistributionResponse, + [timestamp, capital_in, capital_out] +); + impl_java_class!( "com/longbridge/quote/RealtimeQuote", longbridge::quote::RealtimeQuote, diff --git a/nodejs/src/quote/context.rs b/nodejs/src/quote/context.rs index d4b757fcbc..0c62a7bcaf 100644 --- a/nodejs/src/quote/context.rs +++ b/nodejs/src/quote/context.rs @@ -10,10 +10,11 @@ use crate::{ quote::{ push::{PushBrokersEvent, PushDepthEvent, PushQuoteEvent, PushTradesEvent}, types::{ - AdjustType, Candlestick, IntradayLine, IssuerInfo, MarketTradingDays, - MarketTradingSession, OptionQuote, ParticipantInfo, Period, RealtimeQuote, - SecurityBrokers, SecurityDepth, SecurityQuote, SecurityStaticInfo, StrikePriceInfo, - SubType, SubTypes, Subscription, Trade, WarrantQuote, + AdjustType, Candlestick, CapitalDistributionResponse, CapitalFlowLine, IntradayLine, + IssuerInfo, MarketTradingDays, MarketTradingSession, OptionQuote, ParticipantInfo, + Period, RealtimeQuote, SecurityBrokers, SecurityDepth, SecurityQuote, + SecurityStaticInfo, StrikePriceInfo, SubType, SubTypes, Subscription, Trade, + WarrantQuote, }, }, time::NaiveDate, @@ -635,6 +636,57 @@ impl QuoteContext { .try_into() } + /// Get capital flow intraday + /// + /// #### Example + /// + /// ```javascript + /// const { Config, QuoteContext } = require("longbridge") + /// + /// let config = Config.fromEnv() + /// QuoteContext.new(config) + /// .then((ctx) => ctx.capitalFlow("700.HK")) + /// .then((resp) => { + /// for (let obj of resp) { + /// console.log(obj.toString()) + /// } + /// }) + /// ``` + #[napi] + pub async fn capital_flow(&self, symbol: String) -> Result> { + self.ctx + .capital_flow(symbol) + .await + .map_err(ErrorNewType)? + .into_iter() + .map(TryInto::try_into) + .collect() + } + + /// Get capital distribution + /// + /// #### Example + /// + /// ```javascript + /// const { Config, QuoteContext } = require("longbridge") + /// + /// let config = Config.fromEnv() + /// QuoteContext.new(config) + /// .then((ctx) => ctx.capitalDistribution("700.HK")) + /// .then((resp) => console.log(resp.toString())) + /// ``` + #[napi] + pub async fn capital_distribution( + &self, + symbol: String, + ) -> Result { + self.ctx + .capital_distribution(symbol) + .await + .map_err(ErrorNewType)? + .try_into() + } + /// Get real-time quote /// /// #### Example diff --git a/nodejs/src/quote/types.rs b/nodejs/src/quote/types.rs index ddb0d40747..7c918be8e0 100644 --- a/nodejs/src/quote/types.rs +++ b/nodejs/src/quote/types.rs @@ -704,3 +704,42 @@ pub struct MarketTradingDays { #[js(array)] half_trading_days: Vec, } + +/// Capital flow line +#[napi_derive::napi] +#[derive(Debug, JsObject)] +#[js(remote = "longbridge::quote::CapitalFlowLine")] +pub struct CapitalFlowLine { + /// Inflow capital data + inflow: Decimal, + /// Time + #[js(datetime)] + timestamp: DateTime, +} + +/// Capital distribution +#[napi_derive::napi] +#[derive(Debug, JsObject, Clone)] +#[js(remote = "longbridge::quote::CapitalDistribution")] +pub struct CapitalDistribution { + /// Large order + large: Decimal, + /// Medium order + medium: Decimal, + /// Small order + small: Decimal, +} + +/// Capital distribution response +#[napi_derive::napi] +#[derive(Debug, JsObject)] +#[js(remote = "longbridge::quote::CapitalDistributionResponse")] +pub struct CapitalDistributionResponse { + /// Time + #[js(datetime)] + timestamp: DateTime, + /// Inflow capital data + capital_in: CapitalDistribution, + /// Outflow capital data + capital_out: CapitalDistribution, +} diff --git a/python/pysrc/longbridge/openapi.pyi b/python/pysrc/longbridge/openapi.pyi index 7d1d45e8f1..ad60a5b374 100644 --- a/python/pysrc/longbridge/openapi.pyi +++ b/python/pysrc/longbridge/openapi.pyi @@ -1221,6 +1221,64 @@ class MarketTradingDays: half_trading_days: List[date] +class CapitalFlowLine: + """ + Capital flow line + """ + + inflow: Decimal + """ + Inflow capital data + """ + + timestamp: datetime + """ + Time + """ + + +class CapitalDistribution: + """ + Capital distribution + """ + + large: Decimal + """ + Large order + """ + + medium: Decimal + """ + Medium order + """ + + small: Decimal + """ + Small order + """ + + +class CapitalDistributionResponse: + """ + Capital distribution response + """ + + timestamp: datetime + """ + Time + """ + + capital_in: CapitalDistribution + """ + Inflow capital data + """ + + capital_out: CapitalDistribution + """ + Outflow capital data + """ + + class RealtimeQuote: """ Real-time quote @@ -1711,6 +1769,50 @@ class QuoteContext: print(resp) """ + def capital_flow(self, symbol: str) -> List[CapitalFlowLine]: + """ + Get capital flow intraday + + Args: + symbol: Security code + + Returns: + Capital flow list + + Examples: + :: + + from longbridge.openapi import QuoteContext, Config + + config = Config.from_env() + ctx = QuoteContext(config) + + resp = ctx.capital_flow("700.HK") + print(resp) + """ + + def capital_distribution(self, symbol: str) -> CapitalDistributionResponse: + """ + Get capital distribution + + Args: + symbol: Security code + + Returns: + Capital distribution + + Examples: + :: + + from longbridge.openapi import QuoteContext, Config + + config = Config.from_env() + ctx = QuoteContext(config) + + resp = ctx.capital_distribution("700.HK") + print(resp) + """ + def realtime_quote(self, symbols: List[str]) -> List[RealtimeQuote]: """ Get real-time quote diff --git a/python/src/quote/context.rs b/python/src/quote/context.rs index 7b35a0ca95..8c843fa3e0 100644 --- a/python/src/quote/context.rs +++ b/python/src/quote/context.rs @@ -10,10 +10,11 @@ use crate::{ quote::{ push::handle_push_event, types::{ - AdjustType, Candlestick, IntradayLine, IssuerInfo, MarketTradingDays, - MarketTradingSession, OptionQuote, ParticipantInfo, Period, RealtimeQuote, - SecurityBrokers, SecurityDepth, SecurityQuote, SecurityStaticInfo, StrikePriceInfo, - SubType, SubTypes, Subscription, Trade, WarrantQuote, + AdjustType, Candlestick, CapitalDistributionResponse, CapitalFlowLine, IntradayLine, + IssuerInfo, MarketTradingDays, MarketTradingSession, OptionQuote, ParticipantInfo, + Period, RealtimeQuote, SecurityBrokers, SecurityDepth, SecurityQuote, + SecurityStaticInfo, StrikePriceInfo, SubType, SubTypes, Subscription, Trade, + WarrantQuote, }, }, time::PyDateWrapper, @@ -275,6 +276,24 @@ impl QuoteContext { .try_into() } + /// Get capital flow intraday + fn capital_flow(&self, symbol: String) -> PyResult> { + self.ctx + .capital_flow(symbol) + .map_err(ErrorNewType)? + .into_iter() + .map(TryInto::try_into) + .collect() + } + + /// Get capital distribution + fn capital_distribution(&self, symbol: String) -> PyResult { + self.ctx + .capital_distribution(symbol) + .map_err(ErrorNewType)? + .try_into() + } + /// Get real-time quote fn realtime_quote(&self, symbols: Vec) -> PyResult> { self.ctx diff --git a/python/src/quote/types.rs b/python/src/quote/types.rs index 78e9d8167a..4a45d89a67 100644 --- a/python/src/quote/types.rs +++ b/python/src/quote/types.rs @@ -695,3 +695,40 @@ pub struct MarketTradingDays { #[py(array)] half_trading_days: Vec, } + +/// Capital flow line +#[pyclass] +#[derive(Debug, PyObject)] +#[py(remote = "longbridge::quote::CapitalFlowLine")] +pub struct CapitalFlowLine { + /// Inflow capital data + inflow: PyDecimal, + /// Time + timestamp: PyOffsetDateTimeWrapper, +} + +/// Capital distribution +#[pyclass] +#[derive(Debug, PyObject, Clone)] +#[py(remote = "longbridge::quote::CapitalDistribution")] +pub struct CapitalDistribution { + /// Large order + large: PyDecimal, + /// Medium order + medium: PyDecimal, + /// Small order + small: PyDecimal, +} + +/// Capital distribution response +#[pyclass] +#[derive(Debug, PyObject, Clone)] +#[py(remote = "longbridge::quote::CapitalDistributionResponse")] +pub struct CapitalDistributionResponse { + /// Time + timestamp: PyOffsetDateTimeWrapper, + /// Inflow capital data + capital_in: CapitalDistribution, + /// Outflow capital data + capital_out: CapitalDistribution, +} diff --git a/rust/crates/proto/build.rs b/rust/crates/proto/build.rs index 257130ffad..0fe06ab8f3 100644 --- a/rust/crates/proto/build.rs +++ b/rust/crates/proto/build.rs @@ -3,6 +3,7 @@ use prost_build::Config; fn main() { Config::new() .out_dir("./src") + .protoc_arg("--experimental_allow_proto3_optional") .compile_protos( &[ "./openapi-protobufs/control/control.proto", diff --git a/rust/crates/proto/openapi-protobufs b/rust/crates/proto/openapi-protobufs index 434871eb5a..4fb15e74b9 160000 --- a/rust/crates/proto/openapi-protobufs +++ b/rust/crates/proto/openapi-protobufs @@ -1 +1 @@ -Subproject commit 434871eb5a94d7026388d691e6f1c847247e6982 +Subproject commit 4fb15e74b9eb57be071f35abfbae3d76f5eb1a0b diff --git a/rust/crates/proto/src/longbridgeapp.control.v1.rs b/rust/crates/proto/src/longbridgeapp.control.v1.rs index e95be8b51f..6605f5714c 100644 --- a/rust/crates/proto/src/longbridgeapp.control.v1.rs +++ b/rust/crates/proto/src/longbridgeapp.control.v1.rs @@ -30,6 +30,8 @@ pub mod close { pub struct Heartbeat { #[prost(int64, tag="1")] pub timestamp: i64, + #[prost(int32, optional, tag="2")] + pub heartbeat_id: ::core::option::Option, } #[derive(Clone, PartialEq, ::prost::Message)] pub struct AuthRequest { diff --git a/rust/crates/proto/src/longbridgeapp.quote.v1.rs b/rust/crates/proto/src/longbridgeapp.quote.v1.rs index bd939502fb..81bfaa0a1c 100644 --- a/rust/crates/proto/src/longbridgeapp.quote.v1.rs +++ b/rust/crates/proto/src/longbridgeapp.quote.v1.rs @@ -561,6 +561,10 @@ pub struct PushQuote { pub trade_status: i32, #[prost(enumeration="TradeSession", tag="11")] pub trade_session: i32, + #[prost(int64, tag="12")] + pub current_volume: i64, + #[prost(string, tag="13")] + pub current_turnover: ::prost::alloc::string::String, } #[derive(Clone, PartialEq, ::prost::Message)] pub struct PushDepth { @@ -609,6 +613,148 @@ pub struct MarketTradeDayResponse { #[prost(string, repeated, tag="2")] pub half_trade_day: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, } +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct CapitalFlowIntradayRequest { + #[prost(string, tag="1")] + pub symbol: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct CapitalFlowIntradayResponse { + #[prost(string, tag="1")] + pub symbol: ::prost::alloc::string::String, + #[prost(message, repeated, tag="2")] + pub capital_flow_lines: ::prost::alloc::vec::Vec, +} +/// Nested message and enum types in `CapitalFlowIntradayResponse`. +pub mod capital_flow_intraday_response { + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct CapitalFlowLine { + #[prost(string, tag="1")] + pub inflow: ::prost::alloc::string::String, + #[prost(int64, tag="2")] + pub timestamp: i64, + } +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct CapitalDistributionResponse { + #[prost(string, tag="1")] + pub symbol: ::prost::alloc::string::String, + #[prost(int64, tag="2")] + pub timestamp: i64, + #[prost(message, optional, tag="3")] + pub capital_in: ::core::option::Option, + #[prost(message, optional, tag="4")] + pub capital_out: ::core::option::Option, +} +/// Nested message and enum types in `CapitalDistributionResponse`. +pub mod capital_distribution_response { + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct CapitalDistribution { + #[prost(string, tag="1")] + pub large: ::prost::alloc::string::String, + #[prost(string, tag="2")] + pub medium: ::prost::alloc::string::String, + #[prost(string, tag="3")] + pub small: ::prost::alloc::string::String, + } +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SecurityCalcQuoteRequest { + #[prost(string, repeated, tag="1")] + pub symbols: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + #[prost(enumeration="CalcIndex", repeated, tag="2")] + pub calc_index: ::prost::alloc::vec::Vec, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SecurityCalcIndex { + #[prost(string, tag="1")] + pub symbol: ::prost::alloc::string::String, + #[prost(string, tag="2")] + pub last_done: ::prost::alloc::string::String, + #[prost(string, tag="3")] + pub change_val: ::prost::alloc::string::String, + #[prost(string, tag="4")] + pub change_rate: ::prost::alloc::string::String, + #[prost(int64, tag="5")] + pub volume: i64, + #[prost(string, tag="6")] + pub turnover: ::prost::alloc::string::String, + #[prost(string, tag="7")] + pub ytd_change_rate: ::prost::alloc::string::String, + #[prost(string, tag="8")] + pub turnover_rate: ::prost::alloc::string::String, + #[prost(string, tag="9")] + pub total_market_value: ::prost::alloc::string::String, + #[prost(string, tag="10")] + pub capital_flow: ::prost::alloc::string::String, + #[prost(string, tag="11")] + pub amplitude: ::prost::alloc::string::String, + #[prost(string, tag="12")] + pub volume_ratio: ::prost::alloc::string::String, + #[prost(string, tag="13")] + pub pe_ttm_ratio: ::prost::alloc::string::String, + #[prost(string, tag="14")] + pub pb_ratio: ::prost::alloc::string::String, + #[prost(string, tag="15")] + pub dividend_ratio_ttm: ::prost::alloc::string::String, + #[prost(string, tag="16")] + pub five_day_change_rate: ::prost::alloc::string::String, + #[prost(string, tag="17")] + pub ten_day_change_rate: ::prost::alloc::string::String, + #[prost(string, tag="18")] + pub half_year_change_rate: ::prost::alloc::string::String, + #[prost(string, tag="19")] + pub five_minutes_change_rate: ::prost::alloc::string::String, + #[prost(string, tag="20")] + pub expiry_date: ::prost::alloc::string::String, + #[prost(string, tag="21")] + pub strike_price: ::prost::alloc::string::String, + #[prost(string, tag="22")] + pub upper_strike_price: ::prost::alloc::string::String, + #[prost(string, tag="23")] + pub lower_strike_price: ::prost::alloc::string::String, + #[prost(int64, tag="24")] + pub outstanding_qty: i64, + #[prost(string, tag="25")] + pub outstanding_ratio: ::prost::alloc::string::String, + #[prost(string, tag="26")] + pub premium: ::prost::alloc::string::String, + #[prost(string, tag="27")] + pub itm_otm: ::prost::alloc::string::String, + #[prost(string, tag="28")] + pub implied_volatility: ::prost::alloc::string::String, + #[prost(string, tag="29")] + pub warrant_delta: ::prost::alloc::string::String, + #[prost(string, tag="30")] + pub call_price: ::prost::alloc::string::String, + #[prost(string, tag="31")] + pub to_call_price: ::prost::alloc::string::String, + #[prost(string, tag="32")] + pub effective_leverage: ::prost::alloc::string::String, + #[prost(string, tag="33")] + pub leverage_ratio: ::prost::alloc::string::String, + #[prost(string, tag="34")] + pub conversion_ratio: ::prost::alloc::string::String, + #[prost(string, tag="35")] + pub balance_point: ::prost::alloc::string::String, + #[prost(int64, tag="36")] + pub open_interest: i64, + #[prost(string, tag="37")] + pub delta: ::prost::alloc::string::String, + #[prost(string, tag="38")] + pub gamma: ::prost::alloc::string::String, + #[prost(string, tag="39")] + pub theta: ::prost::alloc::string::String, + #[prost(string, tag="40")] + pub vega: ::prost::alloc::string::String, + #[prost(string, tag="41")] + pub rho: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SecurityCalcQuoteResponse { + #[prost(message, repeated, tag="1")] + pub security_calc_index: ::prost::alloc::vec::Vec, +} /// 协议指令定义 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] #[repr(i32)] @@ -658,6 +804,12 @@ pub enum Command { QueryWarrantIssuerInfo = 22, ///查询轮证筛选列表 QueryWarrantFilterList = 23, + ///查询标的的资金流分时 + QueryCapitalFlowIntraday = 24, + ///查询标的资金流大小单 + QueryCapitalFlowDistribution = 25, + ///查询标的指标数据 + QuerySecurityCalcIndex = 26, ///推送行情 PushQuoteData = 101, ///推送盘口 @@ -718,3 +870,48 @@ pub enum SubType { Brokers = 3, Trade = 4, } +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum CalcIndex { + CalcindexUnknown = 0, + CalcindexLastDone = 1, + CalcindexChangeVal = 2, + CalcindexChangeRate = 3, + CalcindexVolume = 4, + CalcindexTurnover = 5, + CalcindexYtdChangeRate = 6, + CalcindexTurnoverRate = 7, + CalcindexTotalMarketValue = 8, + CalcindexCapitalFlow = 9, + CalcindexAmplitude = 10, + CalcindexVolumeRatio = 11, + CalcindexPeTtmRatio = 12, + CalcindexPbRatio = 13, + CalcindexDividendRatioTtm = 14, + CalcindexFiveDayChangeRate = 15, + CalcindexTenDayChangeRate = 16, + CalcindexHalfYearChangeRate = 17, + CalcindexFiveMinutesChangeRate = 18, + CalcindexExpiryDate = 19, + CalcindexStrikePrice = 20, + CalcindexUpperStrikePrice = 21, + CalcindexLowerStrikePrice = 22, + CalcindexOutstandingQty = 23, + CalcindexOutstandingRatio = 24, + CalcindexPremium = 25, + CalcindexItmOtm = 26, + CalcindexImpliedVolatility = 27, + CalcindexWarrantDelta = 28, + CalcindexCallPrice = 29, + CalcindexToCallPrice = 30, + CalcindexEffectiveLeverage = 31, + CalcindexLeverageRatio = 32, + CalcindexConversionRatio = 33, + CalcindexBalancePoint = 34, + CalcindexOpenInterest = 35, + CalcindexDelta = 36, + CalcindexGamma = 37, + CalcindexTheta = 38, + CalcindexVega = 39, + CalcindexRho = 40, +} diff --git a/rust/src/blocking/quote.rs b/rust/src/blocking/quote.rs index 901aeb1ee1..9a80d58c0a 100644 --- a/rust/src/blocking/quote.rs +++ b/rust/src/blocking/quote.rs @@ -5,10 +5,10 @@ use time::Date; use crate::{ blocking::runtime::BlockingRuntime, quote::{ - AdjustType, Candlestick, IntradayLine, IssuerInfo, MarketTradingDays, MarketTradingSession, - OptionQuote, ParticipantInfo, Period, PushEvent, RealtimeQuote, SecurityBrokers, - SecurityDepth, SecurityQuote, SecurityStaticInfo, StrikePriceInfo, SubFlags, Subscription, - Trade, WarrantQuote, + AdjustType, Candlestick, CapitalDistributionResponse, CapitalFlowLine, IntradayLine, + IssuerInfo, MarketTradingDays, MarketTradingSession, OptionQuote, ParticipantInfo, Period, + PushEvent, RealtimeQuote, SecurityBrokers, SecurityDepth, SecurityQuote, + SecurityStaticInfo, StrikePriceInfo, SubFlags, Subscription, Trade, WarrantQuote, }, Config, Market, QuoteContext, Result, }; @@ -521,6 +521,60 @@ impl QuoteContextSync { .call(move |ctx| async move { ctx.trading_days(market, begin, end).await }) } + /// Get capital flow intraday + /// + /// # Examples + /// + /// ```no_run + /// use std::sync::Arc; + /// + /// use longbridge::{blocking::QuoteContextSync, Config}; + /// use time::macros::date; + /// + /// # fn main() -> Result<(), Box> { + /// let config = Arc::new(Config::from_env()?); + /// let ctx = QuoteContextSync::try_new(config, |_| ())?; + /// + /// let resp = ctx.capital_flow("700.HK")?; + /// println!("{:?}", resp); + /// # Ok(()) + /// # } + /// ``` + pub fn capital_flow( + &self, + symbol: impl Into + Send + 'static, + ) -> Result> { + self.rt + .call(move |ctx| async move { ctx.capital_flow(symbol).await }) + } + + /// Get capital distribution + /// + /// # Examples + /// + /// ```no_run + /// use std::sync::Arc; + /// + /// use longbridge::{blocking::QuoteContextSync, Config}; + /// use time::macros::date; + /// + /// # fn main() -> Result<(), Box> { + /// let config = Arc::new(Config::from_env()?); + /// let ctx = QuoteContextSync::try_new(config, |_| ())?; + /// + /// let resp = ctx.capital_distribution("700.HK")?; + /// println!("{:?}", resp); + /// # Ok(()) + /// # } + /// ``` + pub fn capital_distribution( + &self, + symbol: impl Into + Send + 'static, + ) -> Result { + self.rt + .call(move |ctx| async move { ctx.capital_distribution(symbol).await }) + } + /// Get real-time quotes /// /// Get real-time quotes of the subscribed symbols, it always returns the diff --git a/rust/src/quote/cmd_code.rs b/rust/src/quote/cmd_code.rs index 7015ccbff0..16b6413f2d 100644 --- a/rust/src/quote/cmd_code.rs +++ b/rust/src/quote/cmd_code.rs @@ -49,6 +49,12 @@ pub(crate) const GET_OPTION_CHAIN_INFO_BY_DATE: u8 = 21; /// Get Warrant Issuer IDs pub(crate) const GET_WARRANT_ISSUER_IDS: u8 = 22; +/// Get Security Capital Flow Intraday +pub(crate) const GET_CAPITAL_FLOW_INTRADAY: u8 = 24; + +/// Get Security Capital Distribution +pub(crate) const GET_SECURITY_CAPITAL_DISTRIBUTION: u8 = 25; + /// Push Real-time Quote pub(crate) const PUSH_REALTIME_QUOTE: u8 = 101; diff --git a/rust/src/quote/context.rs b/rust/src/quote/context.rs index 247f0f137f..abb4eed5fc 100644 --- a/rust/src/quote/context.rs +++ b/rust/src/quote/context.rs @@ -12,10 +12,10 @@ use crate::{ core::{Command, Core}, sub_flags::SubFlags, utils::{format_date, parse_date}, - AdjustType, Candlestick, IntradayLine, IssuerInfo, MarketTradingDays, MarketTradingSession, - OptionQuote, ParticipantInfo, Period, PushEvent, RealtimeQuote, SecurityBrokers, - SecurityDepth, SecurityQuote, SecurityStaticInfo, StrikePriceInfo, Subscription, Trade, - WarrantQuote, + AdjustType, Candlestick, CapitalDistributionResponse, CapitalFlowLine, IntradayLine, + IssuerInfo, MarketTradingDays, MarketTradingSession, OptionQuote, ParticipantInfo, Period, + PushEvent, RealtimeQuote, SecurityBrokers, SecurityDepth, SecurityQuote, + SecurityStaticInfo, StrikePriceInfo, Subscription, Trade, WarrantQuote, }, Config, Error, Market, Result, }; @@ -829,6 +829,72 @@ impl QuoteContext { }) } + /// Get capital flow intraday + /// + /// Reference: + /// + /// # Examples + /// + /// ```no_run + /// use std::sync::Arc; + /// + /// use longbridge::{quote::QuoteContext, Config}; + /// + /// # tokio::runtime::Runtime::new().unwrap().block_on(async { + /// let config = Arc::new(Config::from_env()?); + /// let (ctx, _) = QuoteContext::try_new(config).await?; + /// + /// let resp = ctx.capital_flow("700.HK").await?; + /// println!("{:?}", resp); + /// # Ok::<_, Box>(()) + /// # }); + pub async fn capital_flow(&self, symbol: impl Into) -> Result> { + self.request::<_, quote::CapitalFlowIntradayResponse>( + cmd_code::GET_CAPITAL_FLOW_INTRADAY, + quote::CapitalFlowIntradayRequest { + symbol: symbol.into(), + }, + ) + .await? + .capital_flow_lines + .into_iter() + .map(TryInto::try_into) + .collect() + } + + /// Get capital distribution + /// + /// Reference: + /// + /// # Examples + /// + /// ```no_run + /// use std::sync::Arc; + /// + /// use longbridge::{quote::QuoteContext, Config}; + /// + /// # tokio::runtime::Runtime::new().unwrap().block_on(async { + /// let config = Arc::new(Config::from_env()?); + /// let (ctx, _) = QuoteContext::try_new(config).await?; + /// + /// let resp = ctx.capital_distribution("700.HK").await?; + /// println!("{:?}", resp); + /// # Ok::<_, Box>(()) + /// # }); + pub async fn capital_distribution( + &self, + symbol: impl Into, + ) -> Result { + self.request::<_, quote::CapitalDistributionResponse>( + cmd_code::GET_SECURITY_CAPITAL_DISTRIBUTION, + quote::SecurityRequest { + symbol: symbol.into(), + }, + ) + .await? + .try_into() + } + /// Get real-time quotes /// /// Get real-time quotes of the subscribed symbols, it always returns the diff --git a/rust/src/quote/mod.rs b/rust/src/quote/mod.rs index 582f83d387..e7487c1bfb 100644 --- a/rust/src/quote/mod.rs +++ b/rust/src/quote/mod.rs @@ -15,11 +15,11 @@ pub use longbridge_proto::quote::{AdjustType, Period, TradeSession, TradeStatus} pub use push_types::{PushBrokers, PushDepth, PushEvent, PushEventDetail, PushQuote, PushTrades}; pub use sub_flags::SubFlags; pub use types::{ - Brokers, Candlestick, Depth, DerivativeType, IntradayLine, IssuerInfo, MarketTradingDays, - MarketTradingSession, OptionDirection, OptionQuote, OptionType, ParticipantInfo, PrePostQuote, - RealtimeQuote, SecurityBrokers, SecurityDepth, SecurityQuote, SecurityStaticInfo, - StrikePriceInfo, Subscription, Trade, TradeDirection, TradingSessionInfo, WarrantQuote, - WarrantType, + Brokers, Candlestick, CapitalDistribution, CapitalDistributionResponse, CapitalFlowLine, Depth, + DerivativeType, IntradayLine, IssuerInfo, MarketTradingDays, MarketTradingSession, + OptionDirection, OptionQuote, OptionType, ParticipantInfo, PrePostQuote, RealtimeQuote, + SecurityBrokers, SecurityDepth, SecurityQuote, SecurityStaticInfo, StrikePriceInfo, + Subscription, Trade, TradeDirection, TradingSessionInfo, WarrantQuote, WarrantType, }; // pub use types::{FilterWarrantExpiryDate, // FilterWarrantStatus,Language,SortType}; diff --git a/rust/src/quote/types.rs b/rust/src/quote/types.rs index b637760aa5..8c2a9c4512 100644 --- a/rust/src/quote/types.rs +++ b/rust/src/quote/types.rs @@ -818,4 +818,80 @@ pub struct MarketTradingDays { pub half_trading_days: Vec, } +/// Capital flow line +#[derive(Debug, Clone)] +pub struct CapitalFlowLine { + /// Inflow capital data + pub inflow: Decimal, + /// Time + pub timestamp: OffsetDateTime, +} + +impl TryFrom for CapitalFlowLine { + type Error = Error; + + fn try_from(value: quote::capital_flow_intraday_response::CapitalFlowLine) -> Result { + Ok(Self { + inflow: value.inflow.parse().unwrap_or_default(), + timestamp: OffsetDateTime::from_unix_timestamp(value.timestamp) + .map_err(|err| Error::parse_field_error("timestamp", err))?, + }) + } +} + +/// Capital distribution +#[derive(Debug, Clone, Default)] +pub struct CapitalDistribution { + /// Large order + pub large: Decimal, + /// Medium order + pub medium: Decimal, + /// Small order + pub small: Decimal, +} + +impl TryFrom for CapitalDistribution { + type Error = Error; + + fn try_from(value: quote::capital_distribution_response::CapitalDistribution) -> Result { + Ok(Self { + large: value.large.parse().unwrap_or_default(), + medium: value.medium.parse().unwrap_or_default(), + small: value.small.parse().unwrap_or_default(), + }) + } +} + +/// Capital distribution response +#[derive(Debug, Clone)] +pub struct CapitalDistributionResponse { + /// Time + pub timestamp: OffsetDateTime, + /// Inflow capital data + pub capital_in: CapitalDistribution, + /// Outflow capital data + pub capital_out: CapitalDistribution, +} + +impl TryFrom for CapitalDistributionResponse { + type Error = Error; + + fn try_from(value: quote::CapitalDistributionResponse) -> Result { + Ok(Self { + timestamp: OffsetDateTime::from_unix_timestamp(value.timestamp) + .map_err(|err| Error::parse_field_error("timestamp", err))?, + capital_in: value + .capital_in + .map(TryInto::try_into) + .transpose()? + .unwrap_or_default(), + capital_out: value + .capital_out + .map(TryInto::try_into) + .transpose()? + .unwrap_or_default(), + }) + } +} + impl_default_for_enum_string!(OptionType, OptionDirection, WarrantType); From c346f665b5f0f647ee85a924e520d4b845fcd05a Mon Sep 17 00:00:00 2001 From: Sunli Date: Mon, 20 Jun 2022 13:35:15 +0800 Subject: [PATCH 082/567] Release 0.2.19 longbridge@0.2.19 longbridge-httpcli@0.2.19 longbridge-java@0.2.19 longbridge-java-macros@0.2.19 longbridge-nodejs@0.2.19 longbridge-nodejs-macros@0.2.19 longbridge-proto@0.2.19 longbridge-python@0.2.19 longbridge-python-macros@0.2.19 longbridge-wscli@0.2.19 Generated by cargo-workspaces --- examples/java/account_asset/pom.xml | 2 +- examples/java/submit_order/pom.xml | 2 +- examples/java/subscribe_quote/pom.xml | 2 +- examples/java/today_orders/pom.xml | 2 +- java/Cargo.toml | 2 +- java/README.md | 2 +- java/crates/macros/Cargo.toml | 2 +- nodejs/Cargo.toml | 6 +++--- nodejs/crates/macros/Cargo.toml | 2 +- python/Cargo.toml | 6 +++--- python/Makefile.toml | 2 +- python/crates/macros/Cargo.toml | 2 +- rust/Cargo.toml | 8 ++++---- rust/crates/httpclient/Cargo.toml | 2 +- rust/crates/proto/Cargo.toml | 2 +- rust/crates/wsclient/Cargo.toml | 16 ++++++++-------- 16 files changed, 30 insertions(+), 30 deletions(-) diff --git a/examples/java/account_asset/pom.xml b/examples/java/account_asset/pom.xml index 1b406ba2bb..e823160982 100644 --- a/examples/java/account_asset/pom.xml +++ b/examples/java/account_asset/pom.xml @@ -10,7 +10,7 @@ io.github.longbridgeapp openapi-sdk - 0.2.18 + 0.2.19 diff --git a/examples/java/submit_order/pom.xml b/examples/java/submit_order/pom.xml index 32c9b3e5a2..11a51fa85a 100644 --- a/examples/java/submit_order/pom.xml +++ b/examples/java/submit_order/pom.xml @@ -10,7 +10,7 @@ io.github.longbridgeapp openapi-sdk - 0.2.18 + 0.2.19 diff --git a/examples/java/subscribe_quote/pom.xml b/examples/java/subscribe_quote/pom.xml index bf4a3d530e..f7e26f43c1 100644 --- a/examples/java/subscribe_quote/pom.xml +++ b/examples/java/subscribe_quote/pom.xml @@ -10,7 +10,7 @@ io.github.longbridgeapp openapi-sdk - 0.2.18 + 0.2.19 diff --git a/examples/java/today_orders/pom.xml b/examples/java/today_orders/pom.xml index 030910ece9..23a1b0c5fe 100644 --- a/examples/java/today_orders/pom.xml +++ b/examples/java/today_orders/pom.xml @@ -10,7 +10,7 @@ io.github.longbridgeapp openapi-sdk - 0.2.18 + 0.2.19 diff --git a/java/Cargo.toml b/java/Cargo.toml index 01c898f3ad..bfcf7c8f1c 100644 --- a/java/Cargo.toml +++ b/java/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge-java" -version = "0.2.18" +version = "0.2.19" [lib] crate-type = ["cdylib"] diff --git a/java/README.md b/java/README.md index e26b211a97..7503806d62 100644 --- a/java/README.md +++ b/java/README.md @@ -13,7 +13,7 @@ Add `io.github.longbridgeapp:openapi-sdk` to `pom.xml` io.github.longbridgeapp openapi-sdk - 0.2.18 + 0.2.19 ``` diff --git a/java/crates/macros/Cargo.toml b/java/crates/macros/Cargo.toml index 05bf116221..11cdcf9896 100644 --- a/java/crates/macros/Cargo.toml +++ b/java/crates/macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "longbridge-java-macros" -version = "0.2.18" +version = "0.2.19" edition = "2021" [lib] diff --git a/nodejs/Cargo.toml b/nodejs/Cargo.toml index dab4b313c1..c2f0908542 100644 --- a/nodejs/Cargo.toml +++ b/nodejs/Cargo.toml @@ -1,14 +1,14 @@ [package] edition = "2021" name = "longbridge-nodejs" -version = "0.2.18" +version = "0.2.19" [lib] crate-type = ["cdylib"] [dependencies] -longbridge = { path = "../rust", version = "0.2.18" } -longbridge-nodejs-macros = { path = "crates/macros", version = "0.2.18" } +longbridge = { path = "../rust", version = "0.2.19" } +longbridge-nodejs-macros = { path = "crates/macros", version = "0.2.19" } napi = { version = "2.5.0", default-features = false, features = [ "napi4", diff --git a/nodejs/crates/macros/Cargo.toml b/nodejs/crates/macros/Cargo.toml index fba7e17650..e76db53e7c 100644 --- a/nodejs/crates/macros/Cargo.toml +++ b/nodejs/crates/macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "longbridge-nodejs-macros" -version = "0.2.18" +version = "0.2.19" edition = "2021" [lib] diff --git a/python/Cargo.toml b/python/Cargo.toml index 3dc2c6cf4d..f243446d4c 100644 --- a/python/Cargo.toml +++ b/python/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge-python" -version = "0.2.18" +version = "0.2.19" description = "Longbridge OpenAPI SDK for Python" homepage = "https://open.longbridgeapp.com/en/" readme = "README.md" @@ -15,8 +15,8 @@ name = "longbridge" crate-type = ["cdylib"] [dependencies] -longbridge = { path = "../rust", version = "0.2.18", features = ["blocking"] } -longbridge-python-macros = { path = "crates/macros", version = "0.2.18" } +longbridge = { path = "../rust", version = "0.2.19", features = ["blocking"] } +longbridge-python-macros = { path = "crates/macros", version = "0.2.19" } once_cell = "1.11.0" parking_lot = "0.12.1" diff --git a/python/Makefile.toml b/python/Makefile.toml index b34fdaea35..7db8bbe311 100644 --- a/python/Makefile.toml +++ b/python/Makefile.toml @@ -12,7 +12,7 @@ args = ["install", "maturin>=0.12,<0.13"] command = "pip" args = [ "install", - "target/wheels/longbridge-0.2.18-cp310-none-win_amd64.whl", + "target/wheels/longbridge-0.2.19-cp310-none-win_amd64.whl", "-I", ] dependencies = ["build-python-sdk"] diff --git a/python/crates/macros/Cargo.toml b/python/crates/macros/Cargo.toml index efd1a46606..bbcf8b3e39 100644 --- a/python/crates/macros/Cargo.toml +++ b/python/crates/macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "longbridge-python-macros" -version = "0.2.18" +version = "0.2.19" edition = "2021" [lib] diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 6d34f7f76b..c76ab4ecb9 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge" -version = "0.2.18" +version = "0.2.19" description = "Longbridge OpenAPI SDK for Rust" homepage = "https://open.longbridgeapp.com/en/" readme = "README.md" @@ -14,9 +14,9 @@ categories = ["api-bindings"] blocking = ["flume"] [dependencies] -longbridge-wscli = { path = "crates/wsclient", version = "0.2.18" } -longbridge-httpcli = { path = "crates/httpclient", version = "0.2.18" } -longbridge-proto = { path = "crates/proto", version = "0.2.18" } +longbridge-wscli = { path = "crates/wsclient", version = "0.2.19" } +longbridge-httpcli = { path = "crates/httpclient", version = "0.2.19" } +longbridge-proto = { path = "crates/proto", version = "0.2.19" } tokio = { version = "1.18.2", features = [ "time", diff --git a/rust/crates/httpclient/Cargo.toml b/rust/crates/httpclient/Cargo.toml index fc49ebaaf7..e09af548b6 100644 --- a/rust/crates/httpclient/Cargo.toml +++ b/rust/crates/httpclient/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge-httpcli" -version = "0.2.18" +version = "0.2.19" description = "Longbridge HTTP SDK for Rust" license = "MIT OR Apache-2.0" diff --git a/rust/crates/proto/Cargo.toml b/rust/crates/proto/Cargo.toml index 98a4e5791f..e496f29741 100644 --- a/rust/crates/proto/Cargo.toml +++ b/rust/crates/proto/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge-proto" -version = "0.2.18" +version = "0.2.19" description = "Longbridge Protocol" license = "MIT OR Apache-2.0" diff --git a/rust/crates/wsclient/Cargo.toml b/rust/crates/wsclient/Cargo.toml index 1e1e4837ec..10560b7827 100644 --- a/rust/crates/wsclient/Cargo.toml +++ b/rust/crates/wsclient/Cargo.toml @@ -1,22 +1,22 @@ [package] name = "longbridge-wscli" -version = "0.2.18" +version = "0.2.19" edition = "2021" description = "Longbridge Websocket SDK for Rust" license = "MIT OR Apache-2.0" [dependencies] -longbridge-proto = { path = "../proto", version = "0.2.18" } +longbridge-proto = { path = "../proto", version = "0.2.19" } tokio = { version = "1.18.2", features = [ - "time", - "rt", - "macros", - "sync", - "net", + "time", + "rt", + "macros", + "sync", + "net", ] } tokio-tungstenite = { version = "0.17.1", features = [ - "rustls-tls-webpki-roots", + "rustls-tls-webpki-roots", ] } thiserror = "1.0.31" futures-util = "0.3.21" From b9f2e56b106953edb8e53ddcbd39b1bfce9224e2 Mon Sep 17 00:00:00 2001 From: Sunli Date: Thu, 23 Jun 2022 16:04:26 +0800 Subject: [PATCH 083/567] Fixes replace_order/cancel_order --- examples/rust/.gitignore | 3 +- examples/rust/Cargo.lock | 1596 ---------------------- examples/rust/account_asset/Cargo.toml | 2 +- examples/rust/submit_order/Cargo.toml | 2 +- examples/rust/subscribe_quote/Cargo.toml | 2 +- examples/rust/today_orders/Cargo.toml | 2 +- rust/src/trade/context.rs | 13 +- 7 files changed, 16 insertions(+), 1604 deletions(-) delete mode 100644 examples/rust/Cargo.lock diff --git a/examples/rust/.gitignore b/examples/rust/.gitignore index 1de565933b..a9d37c560c 100644 --- a/examples/rust/.gitignore +++ b/examples/rust/.gitignore @@ -1 +1,2 @@ -target \ No newline at end of file +target +Cargo.lock diff --git a/examples/rust/Cargo.lock b/examples/rust/Cargo.lock deleted file mode 100644 index 6ec2ab3356..0000000000 --- a/examples/rust/Cargo.lock +++ /dev/null @@ -1,1596 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "account_asset" -version = "0.1.0" -dependencies = [ - "longbridge", - "tokio", -] - -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - -[[package]] -name = "anyhow" -version = "1.0.57" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc" - -[[package]] -name = "arrayvec" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" - -[[package]] -name = "autocfg" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - -[[package]] -name = "base64" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "block-buffer" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" -dependencies = [ - "generic-array", -] - -[[package]] -name = "bumpalo" -version = "3.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" - -[[package]] -name = "byteorder" -version = "1.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" - -[[package]] -name = "bytes" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" - -[[package]] -name = "cc" -version = "1.0.73" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "cmake" -version = "0.1.48" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8ad8cef104ac57b68b89df3208164d228503abbdce70f6880ffa3d970e7443a" -dependencies = [ - "cc", -] - -[[package]] -name = "cpufeatures" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" -dependencies = [ - "libc", -] - -[[package]] -name = "crc32fast" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "crypto-common" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "digest" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" -dependencies = [ - "block-buffer", - "crypto-common", - "subtle", -] - -[[package]] -name = "dotenv" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" - -[[package]] -name = "either" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" - -[[package]] -name = "encoding_rs" -version = "0.8.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "fastrand" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" -dependencies = [ - "instant", -] - -[[package]] -name = "fixedbitset" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "279fb028e20b3c4c320317955b77c5e0c9701f05a1d309905d6fc702cdc5053e" - -[[package]] -name = "flate2" -version = "1.0.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" -dependencies = [ - "crc32fast", - "miniz_oxide", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "form_urlencoded" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" -dependencies = [ - "matches", - "percent-encoding", -] - -[[package]] -name = "futures-channel" -version = "0.3.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" -dependencies = [ - "futures-core", -] - -[[package]] -name = "futures-core" -version = "0.3.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" - -[[package]] -name = "futures-macro" -version = "0.3.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "futures-sink" -version = "0.3.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" - -[[package]] -name = "futures-task" -version = "0.3.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" - -[[package]] -name = "futures-util" -version = "0.3.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" -dependencies = [ - "futures-core", - "futures-macro", - "futures-sink", - "futures-task", - "pin-project-lite", - "pin-utils", - "slab", -] - -[[package]] -name = "generic-array" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "getrandom" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad" -dependencies = [ - "cfg-if", - "libc", - "wasi 0.10.2+wasi-snapshot-preview1", -] - -[[package]] -name = "h2" -version = "0.3.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37a82c6d637fc9515a4694bbf1cb2457b79d81ce52b3108bdeea58b07dd34a57" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "hashbrown" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" - -[[package]] -name = "heck" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" - -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - -[[package]] -name = "hmac" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" -dependencies = [ - "digest", -] - -[[package]] -name = "http" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http-body" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" -dependencies = [ - "bytes", - "http", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c" - -[[package]] -name = "httpdate" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" - -[[package]] -name = "hyper" -version = "0.14.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42dc3c131584288d375f2d07f822b0cb012d8c6fb899a5b9fdb3cb7eb9b6004f" -dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", - "want", -] - -[[package]] -name = "hyper-rustls" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d87c48c02e0dc5e3b849a2041db3029fd066650f8f717c07bf8ed78ccb895cac" -dependencies = [ - "http", - "hyper", - "rustls", - "tokio", - "tokio-rustls", -] - -[[package]] -name = "idna" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" -dependencies = [ - "matches", - "unicode-bidi", - "unicode-normalization", -] - -[[package]] -name = "indexmap" -version = "1.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6012d540c5baa3589337a98ce73408de9b5a25ec9fc2c6fd6be8f0d39e0ca5a" -dependencies = [ - "autocfg", - "hashbrown", -] - -[[package]] -name = "instant" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "ipnet" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" - -[[package]] -name = "itertools" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" -dependencies = [ - "either", -] - -[[package]] -name = "itoa" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" - -[[package]] -name = "js-sys" -version = "0.3.57" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "671a26f820db17c2a2750743f1dd03bafd15b98c9f30c7c2628c024c05d73397" -dependencies = [ - "wasm-bindgen", -] - -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - -[[package]] -name = "libc" -version = "0.2.126" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" - -[[package]] -name = "lock_api" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" -dependencies = [ - "autocfg", - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "longbridge" -version = "0.2.18" -dependencies = [ - "bitflags", - "dotenv", - "futures-util", - "longbridge-httpcli", - "longbridge-proto", - "longbridge-wscli", - "num_enum", - "prost", - "rust_decimal", - "serde", - "serde_json", - "strum", - "strum_macros", - "thiserror", - "time", - "tokio", - "tracing", -] - -[[package]] -name = "longbridge-httpcli" -version = "0.2.18" -dependencies = [ - "futures-util", - "hmac", - "parking_lot", - "percent-encoding", - "reqwest", - "serde", - "serde_json", - "sha1", - "sha2", - "thiserror", - "tokio", - "tracing", -] - -[[package]] -name = "longbridge-proto" -version = "0.2.18" -dependencies = [ - "prost", - "prost-build", -] - -[[package]] -name = "longbridge-wscli" -version = "0.2.18" -dependencies = [ - "byteorder", - "flate2", - "futures-util", - "longbridge-proto", - "num_enum", - "prost", - "thiserror", - "tokio", - "tokio-tungstenite", - "url", -] - -[[package]] -name = "matches" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" - -[[package]] -name = "memchr" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" - -[[package]] -name = "mime" -version = "0.3.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" - -[[package]] -name = "miniz_oxide" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc" -dependencies = [ - "adler", -] - -[[package]] -name = "mio" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "713d550d9b44d89174e066b7a6217ae06234c10cb47819a88290d2b353c31799" -dependencies = [ - "libc", - "log", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys", -] - -[[package]] -name = "multimap" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" - -[[package]] -name = "num-traits" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" -dependencies = [ - "autocfg", -] - -[[package]] -name = "num_cpus" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" -dependencies = [ - "hermit-abi", - "libc", -] - -[[package]] -name = "num_enum" -version = "0.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf5395665662ef45796a4ff5486c5d41d29e0c09640af4c5f17fd94ee2c119c9" -dependencies = [ - "num_enum_derive", -] - -[[package]] -name = "num_enum_derive" -version = "0.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b0498641e53dd6ac1a4f22547548caa6864cc4933784319cd1775271c5a46ce" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "num_threads" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" -dependencies = [ - "libc", -] - -[[package]] -name = "once_cell" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" - -[[package]] -name = "parking_lot" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-sys", -] - -[[package]] -name = "percent-encoding" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" - -[[package]] -name = "petgraph" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d5014253a1331579ce62aa67443b4a658c5e7dd03d4bc6d302b94474888143" -dependencies = [ - "fixedbitset", - "indexmap", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "ppv-lite86" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" - -[[package]] -name = "proc-macro-crate" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" -dependencies = [ - "thiserror", - "toml", -] - -[[package]] -name = "proc-macro2" -version = "1.0.39" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "prost" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71adf41db68aa0daaefc69bb30bcd68ded9b9abaad5d1fbb6304c4fb390e083e" -dependencies = [ - "bytes", - "prost-derive", -] - -[[package]] -name = "prost-build" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae5a4388762d5815a9fc0dea33c56b021cdc8dde0c55e0c9ca57197254b0cab" -dependencies = [ - "bytes", - "cfg-if", - "cmake", - "heck", - "itertools", - "lazy_static", - "log", - "multimap", - "petgraph", - "prost", - "prost-types", - "regex", - "tempfile", - "which", -] - -[[package]] -name = "prost-derive" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b670f45da57fb8542ebdbb6105a925fe571b67f9e7ed9f47a06a84e72b4e7cc" -dependencies = [ - "anyhow", - "itertools", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "prost-types" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d0a014229361011dc8e69c8a1ec6c2e8d0f2af7c91e3ea3f5b2170298461e68" -dependencies = [ - "bytes", - "prost", -] - -[[package]] -name = "quote" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha", - "rand_core", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core", -] - -[[package]] -name = "rand_core" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" -dependencies = [ - "getrandom", -] - -[[package]] -name = "redox_syscall" -version = "0.2.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" -dependencies = [ - "bitflags", -] - -[[package]] -name = "regex" -version = "1.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1" -dependencies = [ - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.6.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64" - -[[package]] -name = "remove_dir_all" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" -dependencies = [ - "winapi", -] - -[[package]] -name = "reqwest" -version = "0.11.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46a1f7aa4f35e5e8b4160449f51afc758f0ce6454315a9fa7d0d113e958c41eb" -dependencies = [ - "base64", - "bytes", - "encoding_rs", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "hyper", - "hyper-rustls", - "ipnet", - "js-sys", - "lazy_static", - "log", - "mime", - "percent-encoding", - "pin-project-lite", - "rustls", - "rustls-pemfile", - "serde", - "serde_json", - "serde_urlencoded", - "tokio", - "tokio-rustls", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "webpki-roots", - "winreg", -] - -[[package]] -name = "ring" -version = "0.16.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" -dependencies = [ - "cc", - "libc", - "once_cell", - "spin", - "untrusted", - "web-sys", - "winapi", -] - -[[package]] -name = "rust_decimal" -version = "1.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2ee7337df68898256ad0d4af4aad178210d9e44d2ff900ce44064a97cd86530" -dependencies = [ - "arrayvec", - "num-traits", - "serde", -] - -[[package]] -name = "rustls" -version = "0.20.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aab8ee6c7097ed6057f43c187a62418d0c05a4bd5f18b3571db50ee0f9ce033" -dependencies = [ - "log", - "ring", - "sct", - "webpki", -] - -[[package]] -name = "rustls-pemfile" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ee86d63972a7c661d1536fefe8c3c8407321c3df668891286de28abcd087360" -dependencies = [ - "base64", -] - -[[package]] -name = "rustversion" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" - -[[package]] -name = "ryu" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" - -[[package]] -name = "scopeguard" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" - -[[package]] -name = "sct" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "serde" -version = "1.0.137" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.137" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.81" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" -dependencies = [ - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "sha-1" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "sha1" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c77f4e7f65455545c2153c1253d25056825e77ee2533f0e41deb65a93a34852f" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "sha2" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "slab" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" - -[[package]] -name = "smallvec" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" - -[[package]] -name = "socket2" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "spin" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" - -[[package]] -name = "strum" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e96acfc1b70604b8b2f1ffa4c57e59176c7dbb05d556c71ecd2f5498a1dee7f8" - -[[package]] -name = "strum_macros" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6878079b17446e4d3eba6192bb0a2950d5b14f0ed8424b852310e5a94345d0ef" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "rustversion", - "syn", -] - -[[package]] -name = "submit_order" -version = "0.1.0" -dependencies = [ - "longbridge", - "tokio", -] - -[[package]] -name = "subscribe_quote" -version = "0.1.0" -dependencies = [ - "longbridge", - "tokio", -] - -[[package]] -name = "subtle" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" - -[[package]] -name = "syn" -version = "1.0.96" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0748dd251e24453cb8717f0354206b91557e4ec8703673a4b30208f2abaf1ebf" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "tempfile" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" -dependencies = [ - "cfg-if", - "fastrand", - "libc", - "redox_syscall", - "remove_dir_all", - "winapi", -] - -[[package]] -name = "thiserror" -version = "1.0.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "time" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2702e08a7a860f005826c6815dcac101b19b5eb330c27fe4a5928fec1d20ddd" -dependencies = [ - "itoa", - "libc", - "num_threads", - "time-macros", -] - -[[package]] -name = "time-macros" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" - -[[package]] -name = "tinyvec" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" - -[[package]] -name = "today_orders" -version = "0.1.0" -dependencies = [ - "longbridge", - "tokio", -] - -[[package]] -name = "tokio" -version = "1.19.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c51a52ed6686dd62c320f9b89299e9dfb46f730c7a48e635c19f21d116cb1439" -dependencies = [ - "bytes", - "libc", - "memchr", - "mio", - "num_cpus", - "once_cell", - "pin-project-lite", - "socket2", - "tokio-macros", - "winapi", -] - -[[package]] -name = "tokio-macros" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tokio-rustls" -version = "0.23.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" -dependencies = [ - "rustls", - "tokio", - "webpki", -] - -[[package]] -name = "tokio-tungstenite" -version = "0.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06cda1232a49558c46f8a504d5b93101d42c0bf7f911f12a105ba48168f821ae" -dependencies = [ - "futures-util", - "log", - "rustls", - "tokio", - "tokio-rustls", - "tungstenite", - "webpki", - "webpki-roots", -] - -[[package]] -name = "tokio-util" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc463cd8deddc3770d20f9852143d50bf6094e640b485cb2e189a2099085ff45" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", - "tracing", -] - -[[package]] -name = "toml" -version = "0.5.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" -dependencies = [ - "serde", -] - -[[package]] -name = "tower-service" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" - -[[package]] -name = "tracing" -version = "0.1.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0ecdcb44a79f0fe9844f0c4f33a342cbcbb5117de8001e6ba0dc2351327d09" -dependencies = [ - "cfg-if", - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc6b8ad3567499f98a1db7a752b07a7c8c7c7c34c332ec00effb2b0027974b7c" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tracing-core" -version = "0.1.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f54c8ca710e81886d498c2fd3331b56c93aa248d49de2222ad2742247c60072f" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "try-lock" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" - -[[package]] -name = "tungstenite" -version = "0.17.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d96a2dea40e7570482f28eb57afbe42d97551905da6a9400acc5c328d24004f5" -dependencies = [ - "base64", - "byteorder", - "bytes", - "http", - "httparse", - "log", - "rand", - "rustls", - "sha-1", - "thiserror", - "url", - "utf-8", - "webpki", -] - -[[package]] -name = "typenum" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" - -[[package]] -name = "unicode-bidi" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" - -[[package]] -name = "unicode-ident" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" - -[[package]] -name = "unicode-normalization" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "untrusted" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" - -[[package]] -name = "url" -version = "2.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" -dependencies = [ - "form_urlencoded", - "idna", - "matches", - "percent-encoding", -] - -[[package]] -name = "utf-8" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" - -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - -[[package]] -name = "want" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" -dependencies = [ - "log", - "try-lock", -] - -[[package]] -name = "wasi" -version = "0.10.2+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "wasm-bindgen" -version = "0.2.80" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27370197c907c55e3f1a9fbe26f44e937fe6451368324e009cba39e139dc08ad" -dependencies = [ - "cfg-if", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.80" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53e04185bfa3a779273da532f5025e33398409573f348985af9a1cbf3774d3f4" -dependencies = [ - "bumpalo", - "lazy_static", - "log", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f741de44b75e14c35df886aff5f1eb73aa114fa5d4d00dcd37b5e01259bf3b2" -dependencies = [ - "cfg-if", - "js-sys", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.80" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17cae7ff784d7e83a2fe7611cfe766ecf034111b49deb850a3dc7699c08251f5" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.80" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99ec0dc7a4756fffc231aab1b9f2f578d23cd391390ab27f952ae0c9b3ece20b" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.80" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744" - -[[package]] -name = "web-sys" -version = "0.3.57" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b17e741662c70c8bd24ac5c5b18de314a2c26c32bf8346ee1e6f53de919c283" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "webpki" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "webpki-roots" -version = "0.22.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d8de8415c823c8abd270ad483c6feeac771fad964890779f9a8cb24fbbc1bf" -dependencies = [ - "webpki", -] - -[[package]] -name = "which" -version = "4.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c4fb54e6113b6a8772ee41c3404fb0301ac79604489467e0a9ce1f3e97c24ae" -dependencies = [ - "either", - "lazy_static", - "libc", -] - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows-sys" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" -dependencies = [ - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" - -[[package]] -name = "windows_i686_gnu" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" - -[[package]] -name = "windows_i686_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" - -[[package]] -name = "winreg" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" -dependencies = [ - "winapi", -] diff --git a/examples/rust/account_asset/Cargo.toml b/examples/rust/account_asset/Cargo.toml index 3c783517d3..a8168f9634 100644 --- a/examples/rust/account_asset/Cargo.toml +++ b/examples/rust/account_asset/Cargo.toml @@ -5,4 +5,4 @@ edition = "2021" [dependencies] longbridge = { path = "../../../rust" } -tokio = { version = "1.0", features = ["rt-multi-thread"] } +tokio = { version = "1.19", features = ["rt-multi-thread", "macros"] } diff --git a/examples/rust/submit_order/Cargo.toml b/examples/rust/submit_order/Cargo.toml index fae6364eee..c082060b5e 100644 --- a/examples/rust/submit_order/Cargo.toml +++ b/examples/rust/submit_order/Cargo.toml @@ -5,4 +5,4 @@ edition = "2021" [dependencies] longbridge = { path = "../../../rust" } -tokio = { version = "1.0", features = ["rt-multi-thread"] } +tokio = { version = "1.19", features = ["rt-multi-thread", "macros"] } diff --git a/examples/rust/subscribe_quote/Cargo.toml b/examples/rust/subscribe_quote/Cargo.toml index c29c0538a6..9c7fa9fa57 100644 --- a/examples/rust/subscribe_quote/Cargo.toml +++ b/examples/rust/subscribe_quote/Cargo.toml @@ -5,4 +5,4 @@ edition = "2021" [dependencies] longbridge = { path = "../../../rust" } -tokio = { version = "1.0", features = ["rt-multi-thread"] } +tokio = { version = "1.19", features = ["rt-multi-thread", "macros"] } diff --git a/examples/rust/today_orders/Cargo.toml b/examples/rust/today_orders/Cargo.toml index 73d52d082f..af856ed1a7 100644 --- a/examples/rust/today_orders/Cargo.toml +++ b/examples/rust/today_orders/Cargo.toml @@ -5,4 +5,4 @@ edition = "2021" [dependencies] longbridge = { path = "../../../rust" } -tokio = { version = "1.0", features = ["rt-multi-thread"] } +tokio = { version = "1.19", features = ["rt-multi-thread", "macros"] } diff --git a/rust/src/trade/context.rs b/rust/src/trade/context.rs index 33d0f857cf..e319e994d3 100644 --- a/rust/src/trade/context.rs +++ b/rust/src/trade/context.rs @@ -1,4 +1,4 @@ -use std::sync::Arc; +use std::{collections::HashMap, sync::Arc}; use longbridge_httpcli::{HttpClient, Method}; use longbridge_wscli::WsClientError; @@ -16,6 +16,9 @@ use crate::{ Config, Result, }; +#[derive(Debug, Deserialize)] +struct EmptyResponse {} + /// Response for submit order request #[derive(Debug, Deserialize)] pub struct SubmitOrderResponse { @@ -333,8 +336,10 @@ impl TradeContext { .http_cli .request(Method::PUT, "/v1/trade/order") .body(options) + .response::() .send() - .await?) + .await + .map(|_| ())?) } /// Submit order @@ -407,11 +412,13 @@ impl TradeContext { Ok(self .http_cli .request(Method::DELETE, "/v1/trade/order") + .response::() .query_params(Request { order_id: order_id.into(), }) .send() - .await?) + .await + .map(|_| ())?) } /// Get account balance From 12e7483c3c12f7c4c1df19dfbb8cb2e35e5e0644 Mon Sep 17 00:00:00 2001 From: Sunli Date: Thu, 23 Jun 2022 16:19:19 +0800 Subject: [PATCH 084/567] Release 0.2.20 longbridge@0.2.20 longbridge-httpcli@0.2.20 longbridge-java@0.2.20 longbridge-java-macros@0.2.20 longbridge-nodejs@0.2.20 longbridge-nodejs-macros@0.2.20 longbridge-proto@0.2.20 longbridge-python@0.2.20 longbridge-python-macros@0.2.20 longbridge-wscli@0.2.20 Generated by cargo-workspaces --- java/Cargo.toml | 6 +++--- java/crates/macros/Cargo.toml | 2 +- nodejs/Cargo.toml | 6 +++--- nodejs/crates/macros/Cargo.toml | 2 +- python/Cargo.toml | 6 +++--- python/crates/macros/Cargo.toml | 2 +- rust/Cargo.toml | 8 ++++---- rust/crates/httpclient/Cargo.toml | 2 +- rust/crates/proto/Cargo.toml | 2 +- rust/crates/wsclient/Cargo.toml | 4 ++-- rust/src/trade/context.rs | 2 +- 11 files changed, 21 insertions(+), 21 deletions(-) diff --git a/java/Cargo.toml b/java/Cargo.toml index bfcf7c8f1c..f22185b65c 100644 --- a/java/Cargo.toml +++ b/java/Cargo.toml @@ -1,14 +1,14 @@ [package] edition = "2021" name = "longbridge-java" -version = "0.2.19" +version = "0.2.20" [lib] crate-type = ["cdylib"] [dependencies] -longbridge = { path = "../rust", version = "0.2.12" } -longbridge-java-macros = { path = "./crates/macros", version = "0.2.12" } +longbridge = { path = "../rust" } +longbridge-java-macros = { path = "./crates/macros" } jni = "0.19.0" thiserror = "1.0.31" diff --git a/java/crates/macros/Cargo.toml b/java/crates/macros/Cargo.toml index 11cdcf9896..fb0f5a8767 100644 --- a/java/crates/macros/Cargo.toml +++ b/java/crates/macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "longbridge-java-macros" -version = "0.2.19" +version = "0.2.20" edition = "2021" [lib] diff --git a/nodejs/Cargo.toml b/nodejs/Cargo.toml index c2f0908542..4f3dca0b0e 100644 --- a/nodejs/Cargo.toml +++ b/nodejs/Cargo.toml @@ -1,14 +1,14 @@ [package] edition = "2021" name = "longbridge-nodejs" -version = "0.2.19" +version = "0.2.20" [lib] crate-type = ["cdylib"] [dependencies] -longbridge = { path = "../rust", version = "0.2.19" } -longbridge-nodejs-macros = { path = "crates/macros", version = "0.2.19" } +longbridge = { path = "../rust" } +longbridge-nodejs-macros = { path = "crates/macros" } napi = { version = "2.5.0", default-features = false, features = [ "napi4", diff --git a/nodejs/crates/macros/Cargo.toml b/nodejs/crates/macros/Cargo.toml index e76db53e7c..7325209b83 100644 --- a/nodejs/crates/macros/Cargo.toml +++ b/nodejs/crates/macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "longbridge-nodejs-macros" -version = "0.2.19" +version = "0.2.20" edition = "2021" [lib] diff --git a/python/Cargo.toml b/python/Cargo.toml index f243446d4c..50b313d4d4 100644 --- a/python/Cargo.toml +++ b/python/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge-python" -version = "0.2.19" +version = "0.2.20" description = "Longbridge OpenAPI SDK for Python" homepage = "https://open.longbridgeapp.com/en/" readme = "README.md" @@ -15,8 +15,8 @@ name = "longbridge" crate-type = ["cdylib"] [dependencies] -longbridge = { path = "../rust", version = "0.2.19", features = ["blocking"] } -longbridge-python-macros = { path = "crates/macros", version = "0.2.19" } +longbridge = { path = "../rust", features = ["blocking"] } +longbridge-python-macros = { path = "crates/macros" } once_cell = "1.11.0" parking_lot = "0.12.1" diff --git a/python/crates/macros/Cargo.toml b/python/crates/macros/Cargo.toml index bbcf8b3e39..c09809fb70 100644 --- a/python/crates/macros/Cargo.toml +++ b/python/crates/macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "longbridge-python-macros" -version = "0.2.19" +version = "0.2.20" edition = "2021" [lib] diff --git a/rust/Cargo.toml b/rust/Cargo.toml index c76ab4ecb9..4fc66cda16 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge" -version = "0.2.19" +version = "0.2.20" description = "Longbridge OpenAPI SDK for Rust" homepage = "https://open.longbridgeapp.com/en/" readme = "README.md" @@ -14,9 +14,9 @@ categories = ["api-bindings"] blocking = ["flume"] [dependencies] -longbridge-wscli = { path = "crates/wsclient", version = "0.2.19" } -longbridge-httpcli = { path = "crates/httpclient", version = "0.2.19" } -longbridge-proto = { path = "crates/proto", version = "0.2.19" } +longbridge-wscli = { path = "crates/wsclient", version = "0.2.20" } +longbridge-httpcli = { path = "crates/httpclient", version = "0.2.20" } +longbridge-proto = { path = "crates/proto", version = "0.2.20" } tokio = { version = "1.18.2", features = [ "time", diff --git a/rust/crates/httpclient/Cargo.toml b/rust/crates/httpclient/Cargo.toml index e09af548b6..2b27a1fcca 100644 --- a/rust/crates/httpclient/Cargo.toml +++ b/rust/crates/httpclient/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge-httpcli" -version = "0.2.19" +version = "0.2.20" description = "Longbridge HTTP SDK for Rust" license = "MIT OR Apache-2.0" diff --git a/rust/crates/proto/Cargo.toml b/rust/crates/proto/Cargo.toml index e496f29741..f0f23adbec 100644 --- a/rust/crates/proto/Cargo.toml +++ b/rust/crates/proto/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge-proto" -version = "0.2.19" +version = "0.2.20" description = "Longbridge Protocol" license = "MIT OR Apache-2.0" diff --git a/rust/crates/wsclient/Cargo.toml b/rust/crates/wsclient/Cargo.toml index 10560b7827..36df1c6a68 100644 --- a/rust/crates/wsclient/Cargo.toml +++ b/rust/crates/wsclient/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "longbridge-wscli" -version = "0.2.19" +version = "0.2.20" edition = "2021" description = "Longbridge Websocket SDK for Rust" license = "MIT OR Apache-2.0" [dependencies] -longbridge-proto = { path = "../proto", version = "0.2.19" } +longbridge-proto = { path = "../proto", version = "0.2.20" } tokio = { version = "1.18.2", features = [ "time", diff --git a/rust/src/trade/context.rs b/rust/src/trade/context.rs index e319e994d3..7f8727a1c6 100644 --- a/rust/src/trade/context.rs +++ b/rust/src/trade/context.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, sync::Arc}; +use std::sync::Arc; use longbridge_httpcli::{HttpClient, Method}; use longbridge_wscli::WsClientError; From ee0920c03d9785abb52b333465b46e841620c691 Mon Sep 17 00:00:00 2001 From: Sunli Date: Tue, 21 Jun 2022 16:45:10 +0800 Subject: [PATCH 085/567] Handling WebSocket SessionId timeout --- rust/src/quote/core.rs | 42 ++++++++++++++++++++++++++++++++---------- rust/src/trade/core.rs | 42 ++++++++++++++++++++++++++++++++---------- 2 files changed, 64 insertions(+), 20 deletions(-) diff --git a/rust/src/quote/core.rs b/rust/src/quote/core.rs index 8516a9d163..d5acd078d1 100644 --- a/rust/src/quote/core.rs +++ b/rust/src/quote/core.rs @@ -1,5 +1,6 @@ use std::{collections::HashMap, sync::Arc, time::Duration}; +use longbridge_httpcli::HttpClient; use longbridge_proto::quote::{SubscribeRequest, SubscriptionResponse, UnsubscribeRequest}; use longbridge_wscli::{CodecType, Platform, ProtocolVersion, WsClient, WsEvent, WsSession}; use tokio::sync::{mpsc, oneshot}; @@ -59,6 +60,7 @@ pub(crate) struct Core { push_tx: mpsc::UnboundedSender, event_tx: mpsc::UnboundedSender, event_rx: mpsc::UnboundedReceiver, + http_cli: HttpClient, ws_cli: WsClient, session: WsSession, close: bool, @@ -72,7 +74,8 @@ impl Core { command_rx: mpsc::UnboundedReceiver, push_tx: mpsc::UnboundedSender, ) -> Result { - let otp = config.create_http_client().get_otp().await?; + let http_cli = config.create_http_client(); + let otp = http_cli.get_otp().await?; let (event_tx, event_rx) = mpsc::unbounded_channel(); @@ -99,6 +102,7 @@ impl Core { push_tx, event_tx, event_rx, + http_cli, ws_cli, session, close: false, @@ -145,15 +149,33 @@ impl Core { ); // request new session - match self - .ws_cli - .request_reconnect(&self.session.session_id) - .await - { - Ok(new_session) => self.session = new_session, - Err(err) => { - tracing::error!(error = %err, "failed to request session id"); - continue; + if self.session.is_expired() { + let otp = match self.http_cli.get_otp().await { + Ok(otp) => otp, + Err(err) => { + tracing::error!(error = %err, "failed to request otp"); + continue; + } + }; + + match self.ws_cli.request_auth(otp).await { + Ok(new_session) => self.session = new_session, + Err(err) => { + tracing::error!(error = %err, "failed to request session id"); + continue; + } + } + } else { + match self + .ws_cli + .request_reconnect(&self.session.session_id) + .await + { + Ok(new_session) => self.session = new_session, + Err(err) => { + tracing::error!(error = %err, "failed to request session id"); + continue; + } } } diff --git a/rust/src/trade/core.rs b/rust/src/trade/core.rs index 50fb6e13b5..2a33b72a65 100644 --- a/rust/src/trade/core.rs +++ b/rust/src/trade/core.rs @@ -1,5 +1,6 @@ use std::{collections::HashSet, sync::Arc, time::Duration}; +use longbridge_httpcli::HttpClient; use longbridge_proto::trade::{Sub, SubResponse, Unsub, UnsubResponse}; use longbridge_wscli::{CodecType, Platform, ProtocolVersion, WsClient, WsEvent, WsSession}; use tokio::sync::{mpsc, oneshot}; @@ -28,6 +29,7 @@ pub(crate) struct Core { push_tx: mpsc::UnboundedSender, event_tx: mpsc::UnboundedSender, event_rx: mpsc::UnboundedReceiver, + http_cli: HttpClient, ws_cli: WsClient, session: WsSession, close: bool, @@ -40,7 +42,8 @@ impl Core { command_rx: mpsc::UnboundedReceiver, push_tx: mpsc::UnboundedSender, ) -> Result { - let otp = config.create_http_client().get_otp().await?; + let http_cli = config.create_http_client(); + let otp = http_cli.get_otp().await?; let (event_tx, event_rx) = mpsc::unbounded_channel(); @@ -67,6 +70,7 @@ impl Core { push_tx, event_tx, event_rx, + http_cli, ws_cli, session, close: false, @@ -112,15 +116,33 @@ impl Core { ); // request new session - match self - .ws_cli - .request_reconnect(&self.session.session_id) - .await - { - Ok(new_session) => self.session = new_session, - Err(err) => { - tracing::error!(error = %err, "failed to request session id"); - continue; + if self.session.is_expired() { + let otp = match self.http_cli.get_otp().await { + Ok(otp) => otp, + Err(err) => { + tracing::error!(error = %err, "failed to request otp"); + continue; + } + }; + + match self.ws_cli.request_auth(otp).await { + Ok(new_session) => self.session = new_session, + Err(err) => { + tracing::error!(error = %err, "failed to request session id"); + continue; + } + } + } else { + match self + .ws_cli + .request_reconnect(&self.session.session_id) + .await + { + Ok(new_session) => self.session = new_session, + Err(err) => { + tracing::error!(error = %err, "failed to request session id"); + continue; + } } } From cc99088a0984476ce7ea85545777f86b34b5f14a Mon Sep 17 00:00:00 2001 From: Sunli Date: Tue, 21 Jun 2022 19:09:53 +0800 Subject: [PATCH 086/567] Add support for subscribe candlesticks --- examples/java/subscribe_candlesticks/pom.xml | 52 + .../src/main/java/Main.java | 14 + .../subscribe_quote/src/main/java/Main.java | 4 +- examples/nodejs/subscribe_candlesticks.js | 7 + examples/python/subscribe_candlesticks.py | 14 + examples/python/subscribe_quote.py | 4 +- examples/rust/Cargo.lock | 1701 +++++++++++++++++ examples/rust/Cargo.toml | 8 +- .../rust/subscribe_candlesticks/Cargo.toml | 8 + .../rust/subscribe_candlesticks/src/main.rs | 18 + examples/rust/subscribe_quote/src/main.rs | 2 - java/c/com_longbridge_SdkNative.h | 32 + .../main/java/com/longbridge/SdkNative.java | 11 + .../com/longbridge/quote/BrokersHandler.java | 2 +- .../longbridge/quote/CandlestickHandler.java | 5 + .../com/longbridge/quote/DepthHandler.java | 2 +- .../com/longbridge/quote/PushCandlestick.java | 19 + .../com/longbridge/quote/QuoteContext.java | 108 +- .../com/longbridge/quote/QuoteHandler.java | 2 +- .../com/longbridge/quote/Subscription.java | 10 +- .../com/longbridge/quote/TradesHandler.java | 2 +- .../trade/GetTodayOrdersOptions.java | 6 + java/src/quote_context.rs | 98 + java/src/trade_context.rs | 4 + java/src/types/classes.rs | 13 +- nodejs/index.d.ts | 319 +++- nodejs/index.js | 7 +- nodejs/src/quote/context.rs | 91 +- nodejs/src/quote/push.rs | 3 +- nodejs/src/quote/types.rs | 20 +- nodejs/src/trade/requests/get_today_orders.rs | 7 + python/Makefile.toml | 4 +- python/README.md | 4 +- python/docs/index.md | 4 +- python/pysrc/longbridge/openapi.pyi | 106 +- python/src/quote/context.rs | 43 + python/src/quote/mod.rs | 1 + python/src/quote/push.rs | 20 +- python/src/quote/types.rs | 20 +- python/src/trade/context.rs | 4 + rust/Cargo.toml | 11 +- rust/crates/candlesticks/Cargo.toml | 12 + rust/crates/candlesticks/src/lib.rs | 7 + rust/crates/candlesticks/src/market.rs | 87 + rust/crates/candlesticks/src/merger.rs | 573 ++++++ rust/crates/candlesticks/src/types.rs | 31 + rust/crates/wsclient/src/client.rs | 8 + rust/src/blocking/quote.rs | 77 + rust/src/quote/context.rs | 103 + rust/src/quote/core.rs | 448 ++++- rust/src/quote/mod.rs | 4 +- rust/src/quote/push_types.rs | 17 +- rust/src/quote/store.rs | 7 +- rust/src/quote/types.rs | 36 +- rust/src/quote/utils.rs | 5 + rust/src/trade/requests/get_today_orders.rs | 12 + 56 files changed, 4035 insertions(+), 202 deletions(-) create mode 100644 examples/java/subscribe_candlesticks/pom.xml create mode 100644 examples/java/subscribe_candlesticks/src/main/java/Main.java create mode 100644 examples/nodejs/subscribe_candlesticks.js create mode 100644 examples/python/subscribe_candlesticks.py create mode 100644 examples/rust/Cargo.lock create mode 100644 examples/rust/subscribe_candlesticks/Cargo.toml create mode 100644 examples/rust/subscribe_candlesticks/src/main.rs create mode 100644 java/javasrc/src/main/java/com/longbridge/quote/CandlestickHandler.java create mode 100644 java/javasrc/src/main/java/com/longbridge/quote/PushCandlestick.java create mode 100644 rust/crates/candlesticks/Cargo.toml create mode 100644 rust/crates/candlesticks/src/lib.rs create mode 100644 rust/crates/candlesticks/src/market.rs create mode 100644 rust/crates/candlesticks/src/merger.rs create mode 100644 rust/crates/candlesticks/src/types.rs diff --git a/examples/java/subscribe_candlesticks/pom.xml b/examples/java/subscribe_candlesticks/pom.xml new file mode 100644 index 0000000000..4d4a94476d --- /dev/null +++ b/examples/java/subscribe_candlesticks/pom.xml @@ -0,0 +1,52 @@ + + + 4.0.0 + + io.github.longbridgeapp + subscribe_candlesticks + 0.0.1 + + + + io.github.longbridgeapp + openapi-sdk + 0.2.19 + + + + + + + maven-compiler-plugin + 3.10.1 + + + org.codehaus.mojo + exec-maven-plugin + 3.0.0 + + + + exec + + + + + java + + -classpath + + Main + + + + + + + + 1.11 + 11 + 11 + utf-8 + + \ No newline at end of file diff --git a/examples/java/subscribe_candlesticks/src/main/java/Main.java b/examples/java/subscribe_candlesticks/src/main/java/Main.java new file mode 100644 index 0000000000..256b2ab4ec --- /dev/null +++ b/examples/java/subscribe_candlesticks/src/main/java/Main.java @@ -0,0 +1,14 @@ +import com.longbridge.*; +import com.longbridge.quote.*; + +class Main { + public static void main(String[] args) throws Exception { + try (Config config = Config.fromEnv(); QuoteContext ctx = QuoteContext.create(config).get()) { + ctx.setOnCandlestick((symbol, event) -> { + System.out.printf("%s\t%s\n", symbol, event); + }); + ctx.subscribeCandlesticks("AAPL.US", Period.Min_1).get(); + Thread.sleep(30000); + } + } +} \ No newline at end of file diff --git a/examples/java/subscribe_quote/src/main/java/Main.java b/examples/java/subscribe_quote/src/main/java/Main.java index 42cfb00113..509fc37dd8 100644 --- a/examples/java/subscribe_quote/src/main/java/Main.java +++ b/examples/java/subscribe_quote/src/main/java/Main.java @@ -4,8 +4,8 @@ class Main { public static void main(String[] args) throws Exception { try (Config config = Config.fromEnv(); QuoteContext ctx = QuoteContext.create(config).get()) { - ctx.setOnQuote((symbol, quote) -> { - System.out.printf("%s\t%s\n", symbol, quote); + ctx.setOnQuote((symbol, event) -> { + System.out.printf("%s\t%s\n", symbol, event); }); ctx.subscribe(new String[] { "700.HK", "AAPL.US", "TSLA.US", "NFLX.US" }, SubFlags.Quote, true).get(); Thread.sleep(30000); diff --git a/examples/nodejs/subscribe_candlesticks.js b/examples/nodejs/subscribe_candlesticks.js new file mode 100644 index 0000000000..3f809b1842 --- /dev/null +++ b/examples/nodejs/subscribe_candlesticks.js @@ -0,0 +1,7 @@ +const { Config, QuoteContext, Period } = require("longbridge"); + +let config = Config.fromEnv(); +QuoteContext.new(config).then((ctx) => { + ctx.setOnCandlestick((_, event) => console.log(event.toString())); + ctx.subscribeCandlesticks("AAPL.US", Period.Min_1); +}); diff --git a/examples/python/subscribe_candlesticks.py b/examples/python/subscribe_candlesticks.py new file mode 100644 index 0000000000..0a742f27f7 --- /dev/null +++ b/examples/python/subscribe_candlesticks.py @@ -0,0 +1,14 @@ +from time import sleep +from longbridge.openapi import QuoteContext, Config, Period, PushCandlestick + + +def on_candlestick(symbol: str, event: PushCandlestick): + print(symbol, event) + + +config = Config.from_env() +ctx = QuoteContext(config) +ctx.set_on_candlestick(on_candlestick) + +ctx.subscribe_candlesticks("AAPL.US", Period.Min_1) +sleep(30) diff --git a/examples/python/subscribe_quote.py b/examples/python/subscribe_quote.py index 0ed0ca8795..a3409a4c8c 100644 --- a/examples/python/subscribe_quote.py +++ b/examples/python/subscribe_quote.py @@ -2,8 +2,8 @@ from longbridge.openapi import QuoteContext, Config, SubType, PushQuote -def on_quote(symbol: str, quote: PushQuote): - print(symbol, quote) +def on_quote(symbol: str, event: PushQuote): + print(symbol, event) config = Config.from_env() diff --git a/examples/rust/Cargo.lock b/examples/rust/Cargo.lock new file mode 100644 index 0000000000..7413ff98b8 --- /dev/null +++ b/examples/rust/Cargo.lock @@ -0,0 +1,1701 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "account_asset" +version = "0.1.0" +dependencies = [ + "longbridge", + "tokio", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "anyhow" +version = "1.0.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc" + +[[package]] +name = "arrayvec" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block-buffer" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" + +[[package]] +name = "cc" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cmake" +version = "0.1.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8ad8cef104ac57b68b89df3208164d228503abbdce70f6880ffa3d970e7443a" +dependencies = [ + "cc", +] + +[[package]] +name = "cpufeatures" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crypto-common" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "dotenv" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" + +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + +[[package]] +name = "encoding_rs" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "fastrand" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" +dependencies = [ + "instant", +] + +[[package]] +name = "fixedbitset" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "279fb028e20b3c4c320317955b77c5e0c9701f05a1d309905d6fc702cdc5053e" + +[[package]] +name = "flate2" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +dependencies = [ + "matches", + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" + +[[package]] +name = "futures-macro" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" + +[[package]] +name = "futures-task" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" + +[[package]] +name = "futures-util" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" +dependencies = [ + "futures-core", + "futures-macro", + "futures-sink", + "futures-task", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.10.2+wasi-snapshot-preview1", +] + +[[package]] +name = "h2" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37a82c6d637fc9515a4694bbf1cb2457b79d81ce52b3108bdeea58b07dd34a57" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" + +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "http" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c" + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + +[[package]] +name = "hyper" +version = "0.14.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42dc3c131584288d375f2d07f822b0cb012d8c6fb899a5b9fdb3cb7eb9b6004f" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87c48c02e0dc5e3b849a2041db3029fd066650f8f717c07bf8ed78ccb895cac" +dependencies = [ + "http", + "hyper", + "rustls", + "tokio", + "tokio-rustls", +] + +[[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6012d540c5baa3589337a98ce73408de9b5a25ec9fc2c6fd6be8f0d39e0ca5a" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "ipnet" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" + +[[package]] +name = "itertools" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" + +[[package]] +name = "js-sys" +version = "0.3.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "671a26f820db17c2a2750743f1dd03bafd15b98c9f30c7c2628c024c05d73397" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" + +[[package]] +name = "lock_api" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "longbridge" +version = "0.2.20" +dependencies = [ + "bitflags", + "dotenv", + "futures-util", + "longbridge-candlesticks", + "longbridge-httpcli", + "longbridge-proto", + "longbridge-wscli", + "num_enum", + "prost", + "rust_decimal", + "serde", + "serde_json", + "strum", + "strum_macros", + "thiserror", + "time", + "tokio", + "tracing", +] + +[[package]] +name = "longbridge-candlesticks" +version = "0.2.20" +dependencies = [ + "bitflags", + "rust_decimal", + "time", + "time-tz", +] + +[[package]] +name = "longbridge-httpcli" +version = "0.2.20" +dependencies = [ + "futures-util", + "hmac", + "parking_lot", + "percent-encoding", + "reqwest", + "serde", + "serde_json", + "sha1", + "sha2", + "thiserror", + "tokio", + "tracing", +] + +[[package]] +name = "longbridge-proto" +version = "0.2.20" +dependencies = [ + "prost", + "prost-build", +] + +[[package]] +name = "longbridge-wscli" +version = "0.2.20" +dependencies = [ + "byteorder", + "flate2", + "futures-util", + "longbridge-proto", + "num_enum", + "prost", + "thiserror", + "tokio", + "tokio-tungstenite", + "url", +] + +[[package]] +name = "matches" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + +[[package]] +name = "miniz_oxide" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "713d550d9b44d89174e066b7a6217ae06234c10cb47819a88290d2b353c31799" +dependencies = [ + "libc", + "log", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys", +] + +[[package]] +name = "multimap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_enum" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf5395665662ef45796a4ff5486c5d41d29e0c09640af4c5f17fd94ee2c119c9" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0498641e53dd6ac1a4f22547548caa6864cc4933784319cd1775271c5a46ce" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num_threads" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +dependencies = [ + "libc", +] + +[[package]] +name = "once_cell" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-sys", +] + +[[package]] +name = "parse-zoneinfo" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c705f256449c60da65e11ff6626e0c16a0a0b96aaa348de61376b249bc340f41" +dependencies = [ + "regex", +] + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + +[[package]] +name = "petgraph" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5014253a1331579ce62aa67443b4a658c5e7dd03d4bc6d302b94474888143" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "phf" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_codegen" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "ppv-lite86" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" + +[[package]] +name = "proc-macro-crate" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" +dependencies = [ + "thiserror", + "toml", +] + +[[package]] +name = "proc-macro2" +version = "1.0.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prost" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71adf41db68aa0daaefc69bb30bcd68ded9b9abaad5d1fbb6304c4fb390e083e" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-build" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae5a4388762d5815a9fc0dea33c56b021cdc8dde0c55e0c9ca57197254b0cab" +dependencies = [ + "bytes", + "cfg-if", + "cmake", + "heck", + "itertools", + "lazy_static", + "log", + "multimap", + "petgraph", + "prost", + "prost-types", + "regex", + "tempfile", + "which", +] + +[[package]] +name = "prost-derive" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b670f45da57fb8542ebdbb6105a925fe571b67f9e7ed9f47a06a84e72b4e7cc" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "prost-types" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d0a014229361011dc8e69c8a1ec6c2e8d0f2af7c91e3ea3f5b2170298461e68" +dependencies = [ + "bytes", + "prost", +] + +[[package]] +name = "quote" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1" +dependencies = [ + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64" + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + +[[package]] +name = "reqwest" +version = "0.11.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46a1f7aa4f35e5e8b4160449f51afc758f0ce6454315a9fa7d0d113e958c41eb" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-rustls", + "ipnet", + "js-sys", + "lazy_static", + "log", + "mime", + "percent-encoding", + "pin-project-lite", + "rustls", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-rustls", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots", + "winreg", +] + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi", +] + +[[package]] +name = "rust_decimal" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34a3bb58e85333f1ab191bf979104b586ebd77475bc6681882825f4532dfe87c" +dependencies = [ + "arrayvec", + "num-traits", + "serde", +] + +[[package]] +name = "rustls" +version = "0.20.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aab8ee6c7097ed6057f43c187a62418d0c05a4bd5f18b3571db50ee0f9ce033" +dependencies = [ + "log", + "ring", + "sct", + "webpki", +] + +[[package]] +name = "rustls-pemfile" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ee86d63972a7c661d1536fefe8c3c8407321c3df668891286de28abcd087360" +dependencies = [ + "base64", +] + +[[package]] +name = "rustversion" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" + +[[package]] +name = "ryu" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "sct" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "serde" +version = "1.0.137" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-xml-rs" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65162e9059be2f6a3421ebbb4fef3e74b7d9e7c60c50a0e292c6239f19f1edfa" +dependencies = [ + "log", + "serde", + "thiserror", + "xml-rs", +] + +[[package]] +name = "serde_derive" +version = "1.0.137" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha-1" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha1" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c77f4e7f65455545c2153c1253d25056825e77ee2533f0e41deb65a93a34852f" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "siphasher" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" + +[[package]] +name = "slab" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" + +[[package]] +name = "smallvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" + +[[package]] +name = "socket2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "strum" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e96acfc1b70604b8b2f1ffa4c57e59176c7dbb05d556c71ecd2f5498a1dee7f8" + +[[package]] +name = "strum_macros" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6878079b17446e4d3eba6192bb0a2950d5b14f0ed8424b852310e5a94345d0ef" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + +[[package]] +name = "submit_order" +version = "0.1.0" +dependencies = [ + "longbridge", + "tokio", +] + +[[package]] +name = "subscribe_candlesticks" +version = "0.1.0" +dependencies = [ + "longbridge", + "tokio", +] + +[[package]] +name = "subscribe_quote" +version = "0.1.0" +dependencies = [ + "longbridge", + "tokio", +] + +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + +[[package]] +name = "syn" +version = "1.0.96" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0748dd251e24453cb8717f0354206b91557e4ec8703673a4b30208f2abaf1ebf" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +dependencies = [ + "cfg-if", + "fastrand", + "libc", + "redox_syscall", + "remove_dir_all", + "winapi", +] + +[[package]] +name = "thiserror" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "time" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72c91f41dcb2f096c05f0873d667dceec1087ce5bcf984ec8ffb19acddbb3217" +dependencies = [ + "itoa", + "libc", + "num_threads", + "time-macros", +] + +[[package]] +name = "time-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" + +[[package]] +name = "time-tz" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "980bd5c9ec52d862803a716b6f3913881910e96124a618a9a0679a984d5c361d" +dependencies = [ + "cfg-if", + "parse-zoneinfo", + "phf", + "phf_codegen", + "serde", + "serde-xml-rs", + "time", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "today_orders" +version = "0.1.0" +dependencies = [ + "longbridge", + "tokio", +] + +[[package]] +name = "tokio" +version = "1.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c51a52ed6686dd62c320f9b89299e9dfb46f730c7a48e635c19f21d116cb1439" +dependencies = [ + "bytes", + "libc", + "memchr", + "mio", + "num_cpus", + "once_cell", + "pin-project-lite", + "socket2", + "tokio-macros", + "winapi", +] + +[[package]] +name = "tokio-macros" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-rustls" +version = "0.23.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" +dependencies = [ + "rustls", + "tokio", + "webpki", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06cda1232a49558c46f8a504d5b93101d42c0bf7f911f12a105ba48168f821ae" +dependencies = [ + "futures-util", + "log", + "rustls", + "tokio", + "tokio-rustls", + "tungstenite", + "webpki", + "webpki-roots", +] + +[[package]] +name = "tokio-util" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc463cd8deddc3770d20f9852143d50bf6094e640b485cb2e189a2099085ff45" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "toml" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" +dependencies = [ + "serde", +] + +[[package]] +name = "tower-service" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" + +[[package]] +name = "tracing" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0ecdcb44a79f0fe9844f0c4f33a342cbcbb5117de8001e6ba0dc2351327d09" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc6b8ad3567499f98a1db7a752b07a7c8c7c7c34c332ec00effb2b0027974b7c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f54c8ca710e81886d498c2fd3331b56c93aa248d49de2222ad2742247c60072f" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "try-lock" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" + +[[package]] +name = "tungstenite" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d96a2dea40e7570482f28eb57afbe42d97551905da6a9400acc5c328d24004f5" +dependencies = [ + "base64", + "byteorder", + "bytes", + "http", + "httparse", + "log", + "rand", + "rustls", + "sha-1", + "thiserror", + "url", + "utf-8", + "webpki", +] + +[[package]] +name = "typenum" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" + +[[package]] +name = "unicode-bidi" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" + +[[package]] +name = "unicode-ident" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" + +[[package]] +name = "unicode-normalization" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "url" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +dependencies = [ + "form_urlencoded", + "idna", + "matches", + "percent-encoding", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27370197c907c55e3f1a9fbe26f44e937fe6451368324e009cba39e139dc08ad" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e04185bfa3a779273da532f5025e33398409573f348985af9a1cbf3774d3f4" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f741de44b75e14c35df886aff5f1eb73aa114fa5d4d00dcd37b5e01259bf3b2" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17cae7ff784d7e83a2fe7611cfe766ecf034111b49deb850a3dc7699c08251f5" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99ec0dc7a4756fffc231aab1b9f2f578d23cd391390ab27f952ae0c9b3ece20b" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744" + +[[package]] +name = "web-sys" +version = "0.3.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b17e741662c70c8bd24ac5c5b18de314a2c26c32bf8346ee1e6f53de919c283" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "webpki-roots" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d8de8415c823c8abd270ad483c6feeac771fad964890779f9a8cb24fbbc1bf" +dependencies = [ + "webpki", +] + +[[package]] +name = "which" +version = "4.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c4fb54e6113b6a8772ee41c3404fb0301ac79604489467e0a9ce1f3e97c24ae" +dependencies = [ + "either", + "lazy_static", + "libc", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +dependencies = [ + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + +[[package]] +name = "windows_i686_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + +[[package]] +name = "windows_i686_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" + +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi", +] + +[[package]] +name = "xml-rs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" diff --git a/examples/rust/Cargo.toml b/examples/rust/Cargo.toml index 0090dc1613..9c8f400d16 100644 --- a/examples/rust/Cargo.toml +++ b/examples/rust/Cargo.toml @@ -1,2 +1,8 @@ [workspace] -members = ["account_asset", "subscribe_quote", "submit_order","today_orders"] +members = [ + "account_asset", + "subscribe_quote", + "submit_order", + "today_orders", + "subscribe_candlesticks", +] diff --git a/examples/rust/subscribe_candlesticks/Cargo.toml b/examples/rust/subscribe_candlesticks/Cargo.toml new file mode 100644 index 0000000000..0a63daa132 --- /dev/null +++ b/examples/rust/subscribe_candlesticks/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "subscribe_candlesticks" +version = "0.1.0" +edition = "2021" + +[dependencies] +longbridge = { path = "../../../rust" } +tokio = { version = "1.19", features = ["rt-multi-thread", "macros"] } diff --git a/examples/rust/subscribe_candlesticks/src/main.rs b/examples/rust/subscribe_candlesticks/src/main.rs new file mode 100644 index 0000000000..26d7d9fa51 --- /dev/null +++ b/examples/rust/subscribe_candlesticks/src/main.rs @@ -0,0 +1,18 @@ +use std::sync::Arc; + +use longbridge::{ + quote::{Period, QuoteContext}, + Config, +}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let config = Arc::new(Config::from_env()?); + let (ctx, mut receiver) = QuoteContext::try_new(config).await?; + ctx.subscribe_candlesticks("AAPL.US", Period::OneMinute) + .await?; + while let Some(event) = receiver.recv().await { + println!("{:?}", event); + } + Ok(()) +} diff --git a/examples/rust/subscribe_quote/src/main.rs b/examples/rust/subscribe_quote/src/main.rs index cb24be695f..b402490620 100644 --- a/examples/rust/subscribe_quote/src/main.rs +++ b/examples/rust/subscribe_quote/src/main.rs @@ -9,14 +9,12 @@ use longbridge::{ async fn main() -> Result<(), Box> { let config = Arc::new(Config::from_env()?); let (ctx, mut receiver) = QuoteContext::try_new(config).await?; - ctx.subscribe( ["700.HK", "AAPL.US", "TSLA.US", "NFLX.US"], SubFlags::QUOTE, true, ) .await?; - while let Some(event) = receiver.recv().await { println!("{:?}", event); } diff --git a/java/c/com_longbridge_SdkNative.h b/java/c/com_longbridge_SdkNative.h index 08abfd162d..44cc5e7da1 100644 --- a/java/c/com_longbridge_SdkNative.h +++ b/java/c/com_longbridge_SdkNative.h @@ -87,6 +87,14 @@ JNIEXPORT void JNICALL Java_com_longbridge_SdkNative_quoteContextSetOnBrokers JNIEXPORT void JNICALL Java_com_longbridge_SdkNative_quoteContextSetOnTrades (JNIEnv *, jclass, jlong, jobject); +/* + * Class: com_longbridge_SdkNative + * Method: quoteContextSetOnCandlestick + * Signature: (JLcom/longbridge/quote/CandlestickHandler;)V + */ +JNIEXPORT void JNICALL Java_com_longbridge_SdkNative_quoteContextSetOnCandlestick + (JNIEnv *, jclass, jlong, jobject); + /* * Class: com_longbridge_SdkNative * Method: quoteContextSubscribe @@ -103,6 +111,22 @@ JNIEXPORT void JNICALL Java_com_longbridge_SdkNative_quoteContextSubscribe JNIEXPORT void JNICALL Java_com_longbridge_SdkNative_quoteContextUnsubscribe (JNIEnv *, jclass, jlong, jobjectArray, jint, jobject); +/* + * Class: com_longbridge_SdkNative + * Method: quoteContextSubscribeCandlesticks + * Signature: (JLjava/lang/String;Lcom/longbridge/quote/Period;Lcom/longbridge/AsyncCallback;)V + */ +JNIEXPORT void JNICALL Java_com_longbridge_SdkNative_quoteContextSubscribeCandlesticks + (JNIEnv *, jclass, jlong, jstring, jobject, jobject); + +/* + * Class: com_longbridge_SdkNative + * Method: quoteContextUnsubscribeCandlesticks + * Signature: (JLjava/lang/String;Lcom/longbridge/quote/Period;Lcom/longbridge/AsyncCallback;)V + */ +JNIEXPORT void JNICALL Java_com_longbridge_SdkNative_quoteContextUnsubscribeCandlesticks + (JNIEnv *, jclass, jlong, jstring, jobject, jobject); + /* * Class: com_longbridge_SdkNative * Method: quoteContextSubscriptions @@ -279,6 +303,14 @@ JNIEXPORT void JNICALL Java_com_longbridge_SdkNative_quoteContextRealtimeBrokers JNIEXPORT void JNICALL Java_com_longbridge_SdkNative_quoteContextRealtimeTrades (JNIEnv *, jclass, jlong, jstring, jint, jobject); +/* + * Class: com_longbridge_SdkNative + * Method: quoteContextRealtimeCandlesticks + * Signature: (JLjava/lang/String;Lcom/longbridge/quote/Period;ILcom/longbridge/AsyncCallback;)V + */ +JNIEXPORT void JNICALL Java_com_longbridge_SdkNative_quoteContextRealtimeCandlesticks + (JNIEnv *, jclass, jlong, jstring, jobject, jint, jobject); + /* * Class: com_longbridge_SdkNative * Method: newTradeContext diff --git a/java/javasrc/src/main/java/com/longbridge/SdkNative.java b/java/javasrc/src/main/java/com/longbridge/SdkNative.java index 7a7bd2d8b8..33c300deef 100644 --- a/java/javasrc/src/main/java/com/longbridge/SdkNative.java +++ b/java/javasrc/src/main/java/com/longbridge/SdkNative.java @@ -32,12 +32,20 @@ public static native long newConfig(String appKey, String appSecret, String acce public static native void quoteContextSetOnTrades(long context, TradesHandler handler); + public static native void quoteContextSetOnCandlestick(long context, CandlestickHandler handler); + public static native void quoteContextSubscribe(long context, String[] symbols, int flags, boolean isFirstPush, AsyncCallback callback); public static native void quoteContextUnsubscribe(long context, String[] symbols, int flags, AsyncCallback callback); + public static native void quoteContextSubscribeCandlesticks(long context, String symbol, Period period, + AsyncCallback callback); + + public static native void quoteContextUnsubscribeCandlesticks(long context, String symbol, Period period, + AsyncCallback callback); + public static native void quoteContextSubscriptions(long context, AsyncCallback callback); public static native void quoteContextStaticInfo(long context, String[] symbols, AsyncCallback callback); @@ -87,6 +95,9 @@ public static native void quoteContextTradingDays(long context, Market market, L public static native void quoteContextRealtimeTrades(long context, String symbol, int count, AsyncCallback callback); + public static native void quoteContextRealtimeCandlesticks(long context, String symbol, Period period, int count, + AsyncCallback callback); + public static native void newTradeContext(long config, AsyncCallback callback); public static native void freeTradeContext(long config); diff --git a/java/javasrc/src/main/java/com/longbridge/quote/BrokersHandler.java b/java/javasrc/src/main/java/com/longbridge/quote/BrokersHandler.java index 9f9e9b533c..c2bf518b0a 100644 --- a/java/javasrc/src/main/java/com/longbridge/quote/BrokersHandler.java +++ b/java/javasrc/src/main/java/com/longbridge/quote/BrokersHandler.java @@ -1,5 +1,5 @@ package com.longbridge.quote; public interface BrokersHandler { - void onBrokers(String symbol, PushBrokers brokers); + void onBrokers(String symbol, PushBrokers event); } diff --git a/java/javasrc/src/main/java/com/longbridge/quote/CandlestickHandler.java b/java/javasrc/src/main/java/com/longbridge/quote/CandlestickHandler.java new file mode 100644 index 0000000000..d48333a1ee --- /dev/null +++ b/java/javasrc/src/main/java/com/longbridge/quote/CandlestickHandler.java @@ -0,0 +1,5 @@ +package com.longbridge.quote; + +public interface CandlestickHandler { + void onCandlestick(String symbol, PushCandlestick event); +} diff --git a/java/javasrc/src/main/java/com/longbridge/quote/DepthHandler.java b/java/javasrc/src/main/java/com/longbridge/quote/DepthHandler.java index 41df07f0c7..07ff50f644 100644 --- a/java/javasrc/src/main/java/com/longbridge/quote/DepthHandler.java +++ b/java/javasrc/src/main/java/com/longbridge/quote/DepthHandler.java @@ -2,5 +2,5 @@ package com.longbridge.quote; public interface DepthHandler { - void onDepth(String symbol, PushDepth depth); + void onDepth(String symbol, PushDepth event); } diff --git a/java/javasrc/src/main/java/com/longbridge/quote/PushCandlestick.java b/java/javasrc/src/main/java/com/longbridge/quote/PushCandlestick.java new file mode 100644 index 0000000000..053813284c --- /dev/null +++ b/java/javasrc/src/main/java/com/longbridge/quote/PushCandlestick.java @@ -0,0 +1,19 @@ +package com.longbridge.quote; + +public class PushCandlestick { + private Period period; + private Candlestick candlestick; + + public Period getPeriod() { + return period; + } + + public Candlestick getCandlestick() { + return candlestick; + } + + @Override + public String toString() { + return "PushCandlestick [candlestick=" + candlestick + ", period=" + period + "]"; + } +} diff --git a/java/javasrc/src/main/java/com/longbridge/quote/QuoteContext.java b/java/javasrc/src/main/java/com/longbridge/quote/QuoteContext.java index b4e2341726..4daa78a83d 100644 --- a/java/javasrc/src/main/java/com/longbridge/quote/QuoteContext.java +++ b/java/javasrc/src/main/java/com/longbridge/quote/QuoteContext.java @@ -40,7 +40,7 @@ public void setOnQuote(QuoteHandler handler) { } /** - * Set quote callback, after receiving the depth data push, it will call back to + * Set depth callback, after receiving the depth data push, it will call back to * this handler. * * @param handler A depth handler @@ -50,7 +50,8 @@ public void setOnDepth(DepthHandler handler) { } /** - * Set quote callback, after receiving the brokers data push, it will call back + * Set brokers callback, after receiving the brokers data push, it will call + * back * to this handler. * * @param handler A brokers handler @@ -60,7 +61,8 @@ public void setOnBrokers(BrokersHandler handler) { } /** - * Set quote callback, after receiving the trades data push, it will call backto + * Set trades callback, after receiving the trades data push, it will call + * backto * this handler. * * @param handler A trades handler @@ -69,6 +71,16 @@ public void setOnTrades(TradesHandler handler) { SdkNative.quoteContextSetOnTrades(this.raw, handler); } + /** + * Set candlestick callback, after receiving the trades data push, it will call + * back to this function. + * + * @param handler A candlestick handler + */ + public void setOnCandlestick(CandlestickHandler handler) { + SdkNative.quoteContextSetOnCandlestick(this.raw, handler); + } + /** * Subscribe * @@ -80,8 +92,8 @@ public void setOnTrades(TradesHandler handler) { * class Main { * public static void main(String[] args) throws Exception { * try (Config config = Config.fromEnv(); QuoteContext ctx = QuoteContext.create(config).get()) { - * ctx.setOnQuote((symbol, quote) -> { - * System.out.printf("%s\t%s\n", symbol, quote); + * ctx.setOnQuote((symbol, event) -> { + * System.out.printf("%s\t%s\n", symbol, event); * }); * ctx.subscribe(new String[] { "700.HK", "AAPL.US" }, SubFlags.Quote, true).get(); * Thread.sleep(30000); @@ -138,6 +150,53 @@ public CompletableFuture unsubscribe(String[] symbols, int flags) throws O }); } + /** + * Subscribe security candlesticks + * + *

+     * {@code
+     * import com.longbridge.*;
+     * import com.longbridge.quote.*;
+     * 
+     * class Main {
+     *     public static void main(String[] args) throws Exception {
+     *         try (Config config = Config.fromEnv(); QuoteContext ctx = QuoteContext.create(config).get()) {
+     *             ctx.setOnCandlestick((symbol, event) -> {
+     *                 System.out.printf("%s\t%s\n", symbol, event);
+     *             });
+     *             ctx.subscribeCandlesticks("700.HK", Period.Min_1).get();
+     *             Thread.sleep(30000);
+     *         }
+     *     }
+     * }
+     * }
+     * 
+ * + * @param symbol Security symbol + * @param period Period type + * @return A Future representing the result of the operation + * @throws OpenApiException If an error occurs + */ + public CompletableFuture subscribeCandlesticks(String symbol, Period period) throws OpenApiException { + return AsyncCallback.executeTask((callback) -> { + SdkNative.quoteContextSubscribeCandlesticks(this.raw, symbol, period, callback); + }); + } + + /** + * Unsubscribe security candlesticks + * + * @param symbol Security symbol + * @param period Period type + * @return A Future representing the result of the operation + * @throws OpenApiException If an error occurs + */ + public CompletableFuture unsubscribeCandlesticks(String symbol, Period period) throws OpenApiException { + return AsyncCallback.executeTask((callback) -> { + SdkNative.quoteContextUnsubscribeCandlesticks(this.raw, symbol, period, callback); + }); + } + /** * Get subscription information * @@ -852,4 +911,43 @@ public CompletableFuture getRealtimeTrades(String symbol, int count) SdkNative.quoteContextRealtimeTrades(this.raw, symbol, count, callback); }); } + + /** + * Get real-time candlesticks + *

+ * Get real-time candlesticks of the subscribed symbols, it always returns the + * data in the local storage. + * + *

+     * {@code
+     * import com.longbridge.*;
+     * import com.longbridge.quote.*;
+     * 
+     * class Main {
+     *     public static void main(String[] args) throws Exception {
+     *         try (Config config = Config.fromEnv(); QuoteContext ctx = QuoteContext.create(config).get()) {
+     *             ctx.subscribeCandlesticks("AAPL.US", Period.Min_1).get();
+     *             Thread.sleep(5000);
+     *             Candlestick[] resp = ctx.getRealtimeCandlesticks("AAPL.US", Period.Min_1, 10).get();
+     *             for (Candlestick obj : resp) {
+     *                 System.out.println(obj);
+     *             }
+     *         }
+     *     }
+     * }
+     * }
+     * 
+ * + * @param symbol Security symbol + * @param period Period type + * @param count Count of trades + * @return A Future representing the result of the operation + * @throws OpenApiException If an error occurs + */ + public CompletableFuture getRealtimeCandlesticks(String symbol, Period period, int count) + throws OpenApiException { + return AsyncCallback.executeTask((callback) -> { + SdkNative.quoteContextRealtimeCandlesticks(this.raw, symbol, period, count, callback); + }); + } } diff --git a/java/javasrc/src/main/java/com/longbridge/quote/QuoteHandler.java b/java/javasrc/src/main/java/com/longbridge/quote/QuoteHandler.java index 90dc2c6cfc..40150f6733 100644 --- a/java/javasrc/src/main/java/com/longbridge/quote/QuoteHandler.java +++ b/java/javasrc/src/main/java/com/longbridge/quote/QuoteHandler.java @@ -1,5 +1,5 @@ package com.longbridge.quote; public interface QuoteHandler { - void onQuote(String symbol, PushQuote quote); + void onQuote(String symbol, PushQuote event); } diff --git a/java/javasrc/src/main/java/com/longbridge/quote/Subscription.java b/java/javasrc/src/main/java/com/longbridge/quote/Subscription.java index fe24e3aa6f..3fdd81adac 100644 --- a/java/javasrc/src/main/java/com/longbridge/quote/Subscription.java +++ b/java/javasrc/src/main/java/com/longbridge/quote/Subscription.java @@ -1,8 +1,11 @@ package com.longbridge.quote; +import java.util.Arrays; + public class Subscription { private String symbol; private int subTypes; + private Period[] candlesticks; public String getSymbol() { return symbol; @@ -12,8 +15,13 @@ public int getSubTypes() { return subTypes; } + public Period[] getCandlesticks() { + return candlesticks; + } + @Override public String toString() { - return "Subscription [subTypes=" + subTypes + ", symbol=" + symbol + "]"; + return "Subscription [candlesticks=" + Arrays.toString(candlesticks) + ", subTypes=" + subTypes + ", symbol=" + + symbol + "]"; } } diff --git a/java/javasrc/src/main/java/com/longbridge/quote/TradesHandler.java b/java/javasrc/src/main/java/com/longbridge/quote/TradesHandler.java index 0265a9b115..22d184c7a0 100644 --- a/java/javasrc/src/main/java/com/longbridge/quote/TradesHandler.java +++ b/java/javasrc/src/main/java/com/longbridge/quote/TradesHandler.java @@ -1,5 +1,5 @@ package com.longbridge.quote; public interface TradesHandler { - void onTrades(String symbol, PushTrades trades); + void onTrades(String symbol, PushTrades event); } diff --git a/java/javasrc/src/main/java/com/longbridge/trade/GetTodayOrdersOptions.java b/java/javasrc/src/main/java/com/longbridge/trade/GetTodayOrdersOptions.java index 64a2adaf51..276b8456dc 100644 --- a/java/javasrc/src/main/java/com/longbridge/trade/GetTodayOrdersOptions.java +++ b/java/javasrc/src/main/java/com/longbridge/trade/GetTodayOrdersOptions.java @@ -8,6 +8,7 @@ public class GetTodayOrdersOptions { private OrderStatus[] status; private OrderSide side; private Market market; + private String orderId; public GetTodayOrdersOptions setSymbol(String symbol) { this.symbol = symbol; @@ -29,4 +30,9 @@ public GetTodayOrdersOptions setMarket(Market market) { return this; } + public GetTodayOrdersOptions setOrderId(String orderId) { + this.orderId = orderId; + return this; + } + } diff --git a/java/src/quote_context.rs b/java/src/quote_context.rs index e6dddf7521..998c0ce4fc 100644 --- a/java/src/quote_context.rs +++ b/java/src/quote_context.rs @@ -26,6 +26,7 @@ struct Callbacks { depth: Option, brokers: Option, trades: Option, + candlestick: Option, } struct ContextObj { @@ -89,6 +90,19 @@ fn send_push_event(jvm: &JavaVM, callbacks: &Callbacks, event: PushEvent) -> Res )?; } } + PushEventDetail::Candlestick(push_candlestick) => { + if let Some(handler) = &callbacks.candlestick { + env.call_method( + handler, + "onCandlestick", + "(Ljava/lang/String;Lcom/longbridge/quote/PushCandlestick;)V", + &[ + event.symbol.into_jvalue(&env)?, + push_candlestick.into_jvalue(&env)?, + ], + )?; + } + } } Ok(()) @@ -220,6 +234,24 @@ pub unsafe extern "system" fn Java_com_longbridge_SdkNative_quoteContextSetOnTra }) } +#[no_mangle] +pub unsafe extern "system" fn Java_com_longbridge_SdkNative_quoteContextSetOnCandlestick( + env: JNIEnv, + _class: JClass, + ctx: i64, + handler: JObject, +) { + let context = &*(ctx as *const ContextObj); + jni_result(&env, (), || { + if !handler.is_null() { + context.callbacks.lock().candlestick = Some(env.new_global_ref(handler)?); + } else { + context.callbacks.lock().candlestick = None; + } + Ok(()) + }) +} + #[no_mangle] pub unsafe extern "system" fn Java_com_longbridge_SdkNative_quoteContextSubscribe( env: JNIEnv, @@ -264,6 +296,46 @@ pub unsafe extern "system" fn Java_com_longbridge_SdkNative_quoteContextUnsubscr }) } +#[no_mangle] +pub unsafe extern "system" fn Java_com_longbridge_SdkNative_quoteContextSubscribeCandlesticks( + env: JNIEnv, + _class: JClass, + context: i64, + symbol: JString, + period: JObject, + callback: JObject, +) { + jni_result(&env, (), || { + let context = &*(context as *const ContextObj); + let symbol: String = FromJValue::from_jvalue(&env, symbol.into())?; + let period: Period = FromJValue::from_jvalue(&env, period.into())?; + async_util::execute(&env, callback, async move { + Ok(context.ctx.subscribe_candlesticks(symbol, period).await?) + })?; + Ok(()) + }) +} + +#[no_mangle] +pub unsafe extern "system" fn Java_com_longbridge_SdkNative_quoteContextUnsubscribeCandlesticks( + env: JNIEnv, + _class: JClass, + context: i64, + symbol: JString, + period: JObject, + callback: JObject, +) { + jni_result(&env, (), || { + let context = &*(context as *const ContextObj); + let symbol: String = FromJValue::from_jvalue(&env, symbol.into())?; + let period: Period = FromJValue::from_jvalue(&env, period.into())?; + async_util::execute(&env, callback, async move { + Ok(context.ctx.unsubscribe_candlesticks(symbol, period).await?) + })?; + Ok(()) + }) +} + #[no_mangle] pub unsafe extern "system" fn Java_com_longbridge_SdkNative_quoteContextSubscriptions( env: JNIEnv, @@ -688,3 +760,29 @@ pub unsafe extern "system" fn Java_com_longbridge_SdkNative_quoteContextRealtime Ok(()) }) } + +#[no_mangle] +pub unsafe extern "system" fn Java_com_longbridge_SdkNative_quoteContextRealtimeCandlesticks( + env: JNIEnv, + _class: JClass, + context: i64, + symbol: JString, + period: JObject, + count: i32, + callback: JObject, +) { + jni_result(&env, (), || { + let context = &*(context as *const ContextObj); + let symbol: String = FromJValue::from_jvalue(&env, symbol.into())?; + let period: Period = FromJValue::from_jvalue(&env, period.into())?; + async_util::execute(&env, callback, async move { + Ok(ObjectArray( + context + .ctx + .realtime_candlesticks(symbol, period, count.max(0) as usize) + .await?, + )) + })?; + Ok(()) + }) +} diff --git a/java/src/trade_context.rs b/java/src/trade_context.rs index 5c34e10011..a7f21d3e7a 100644 --- a/java/src/trade_context.rs +++ b/java/src/trade_context.rs @@ -299,6 +299,10 @@ pub unsafe extern "system" fn Java_com_longbridge_SdkNative_tradeContextTodayOrd if let Some(market) = market { new_opts = new_opts.market(market); } + let order_id: Option = get_field(&env, opts, "orderId")?; + if let Some(order_id) = order_id { + new_opts = new_opts.order_id(order_id); + } Some(new_opts) } else { None diff --git a/java/src/types/classes.rs b/java/src/types/classes.rs index 5d42f92c82..6326377aea 100644 --- a/java/src/types/classes.rs +++ b/java/src/types/classes.rs @@ -32,7 +32,12 @@ impl_java_class!( impl_java_class!( "com/longbridge/quote/Subscription", longbridge::quote::Subscription, - [symbol, sub_types] + [ + symbol, + sub_types, + #[java(objarray)] + candlesticks + ] ); impl_java_class!( @@ -82,6 +87,12 @@ impl_java_class!( ] ); +impl_java_class!( + "com/longbridge/quote/PushCandlestick", + longbridge::quote::PushCandlestick, + [period, candlestick] +); + impl_java_class!( "com/longbridge/quote/SecurityStaticInfo", longbridge::quote::SecurityStaticInfo, diff --git a/nodejs/index.d.ts b/nodejs/index.d.ts index e75b852f17..c6bd251f53 100644 --- a/nodejs/index.d.ts +++ b/nodejs/index.d.ts @@ -120,23 +120,25 @@ export const enum WarrantType { /** Candlestick period */ export const enum Period { /** One Minute */ - Min_1 = 0, + Unknown = 0, + /** One Minute */ + Min_1 = 1, /** Five Minutes */ - Min_5 = 1, + Min_5 = 2, /** Fifteen Minutes */ - Min_15 = 2, + Min_15 = 3, /** Thirty Minutes */ - Min_30 = 3, + Min_30 = 4, /** Sixty Minutes */ - Min_60 = 4, + Min_60 = 5, /** One Days */ - Day = 5, + Day = 6, /** One Week */ - Week = 6, + Week = 7, /** One Month */ - Month = 7, + Month = 8, /** One Year */ - Year = 8 + Year = 9 } /** Candlestick adjustment type */ export const enum AdjustType { @@ -467,20 +469,25 @@ export class QuoteContext { */ setOnQuote(callback: (err: null | Error, event: PushQuoteEvent) => void): void /** - * Set quote callback, after receiving the depth data push, it will call + * Set depth callback, after receiving the depth data push, it will call * back to this function. */ setOnDepth(callback: (err: null | Error, event: PushDepthEvent) => void): void /** - * Set quote callback, after receiving the brokers data push, it will call - * back to this function. + * Set brokers callback, after receiving the brokers data push, it will + * call back to this function. */ setOnBrokers(callback: (err: null | Error, event: PushBrokersEvent) => void): void /** - * Set quote callback, after receiving the trades data push, it will call + * Set trades callback, after receiving the trades data push, it will call * back to this function. */ setOnTrades(callback: (err: null | Error, event: PushTradesEvent) => void): void + /** + * Set candlestick callback, after receiving the trades data push, it will + * call back to this function. + */ + setOnCandlestick(callback: (err: null | Error, event: PushCandlestickEvent) => void): void /** * Subscribe * @@ -515,6 +522,10 @@ export class QuoteContext { * ``` */ unsubscribe(symbols: Array, subTypes: Array): Promise + /** Subscribe security candlesticks */ + subscribeCandlesticks(symbol: string, period: Period): Promise + /** Unsubscribe security candlesticks */ + unsubscribeCandlesticks(symbol: string, period: Period): Promise /** * Get subscription information * @@ -807,6 +818,40 @@ export class QuoteContext { * ``` */ tradingDays(market: Market, begin: NaiveDate, end: NaiveDate): Promise + /** + * Get capital flow intraday + * + * #### Example + * + * ```javascript + * const { Config, QuoteContext } = require("longbridge") + * + * let config = Config.fromEnv() + * QuoteContext.new(config) + * .then((ctx) => ctx.capitalFlow("700.HK")) + * .then((resp) => { + * for (let obj of resp) { + * console.log(obj.toString()) + * } + * }) + * ``` + */ + capitalFlow(symbol: string): Promise> + /** + * Get capital distribution + * + * #### Example + * + * ```javascript + * const { Config, QuoteContext } = require("longbridge") + * + * let config = Config.fromEnv() + * QuoteContext.new(config) + * .then((ctx) => ctx.capitalDistribution("700.HK")) + * .then((resp) => console.log(resp.toString())) + * ``` + */ + capitalDistribution(symbol: string): Promise /** * Get real-time quote * @@ -896,6 +941,29 @@ export class QuoteContext { * ``` */ realtimeTrades(symbol: string, count: number): Promise> + /** + * Get real-time candlesticks + * + * #### Example + * + * ```javascript + * const { Config, QuoteContext, Period } = require("longbridge") + * + * let config = Config.fromEnv(); + * QuoteContext.new(config).then((ctx) => { + * ctx.subscribeCandlesticks("700.HK", Period.Min_1).then(() => { + * setTimeout(() => { + * ctx.realtimeCandlesticks("700.HK", Period.Min_1, 10).then((resp) => { + * for (let obj of resp) { + * console.log(obj.toString()); + * } + * }); + * }, 5000); + * }); + * }); + * ``` + */ + realtimeCandlesticks(symbol: string, period: Period, count: number): Promise> } export class PushQuoteEvent { get symbol(): string @@ -917,11 +985,17 @@ export class PushTradesEvent { get data(): PushTrades toString(): string } +export class PushCandlestickEvent { + get symbol(): string + get data(): PushCandlestick + toString(): string +} /** Subscription */ export class Subscription { toString(): string get symbol(): string get subTypes(): Array + get candlesticks(): Array } /** The basic information of securities */ export class SecurityStaticInfo { @@ -1299,6 +1373,14 @@ export class PushTrades { /** Trades data */ get trades(): Array } +/** Candlestick updated event */ +export class PushCandlestick { + toString(): string + /** Period type */ + get period(): Period + /** Candlestick */ + get candlestick(): Candlestick +} /** Market trading days */ export class MarketTradingDays { toString(): string @@ -1307,6 +1389,34 @@ export class MarketTradingDays { /** Half trading days */ get halfTradingDays(): Array } +/** Capital flow line */ +export class CapitalFlowLine { + toString(): string + /** Inflow capital data */ + get inflow(): Decimal + /** Time */ + get timestamp(): Date +} +/** Capital distribution */ +export class CapitalDistribution { + toString(): string + /** Large order */ + get large(): Decimal + /** Medium order */ + get medium(): Decimal + /** Small order */ + get small(): Decimal +} +/** Capital distribution response */ +export class CapitalDistributionResponse { + toString(): string + /** Time */ + get timestamp(): Date + /** Inflow capital data */ + get capitalIn(): CapitalDistribution + /** Outflow capital data */ + get capitalOut(): CapitalDistribution +} /** Naive date type */ export class NaiveDate { constructor(year: number, month: number, day: number) @@ -1337,32 +1447,32 @@ export class TradeContext { * * ```javascript * const { - * Config, - * TradeContext, - * SubmitOrderOptions, - * Decimal, - * OrderSide, - * TimeInForceType, - * OrderType, - * TopicType, - * } = require("longbridge"); - * - * let config = Config.fromEnv(); - * TradeContext.new(config) - * .then((ctx) => { - * ctx.setOnQuote((_, event) => console.log(event.toString())); - * ctx.subscribe([TopicType.Private]); - * return ctx.submitOrder( - * new SubmitOrderOptions( - * "700.HK", - * OrderType.LO, - * OrderSide.Buy, - * 200, - * TimeInForceType.Day - * ).submittedPrice(new Decimal("50")) - * ); - * }) - * .then((resp) => console.log(resp.toString())); + * Config, + * TradeContext, + * SubmitOrderOptions, + * Decimal, + * OrderSide, + * TimeInForceType, + * OrderType, + * TopicType, + * } = require("longbridge"); + * + * let config = Config.fromEnv(); + * TradeContext.new(config) + * .then((ctx) => { + * ctx.setOnQuote((_, event) => console.log(event.toString())); + * ctx.subscribe([TopicType.Private]); + * return ctx.submitOrder( + * new SubmitOrderOptions( + * "700.HK", + * OrderType.LO, + * OrderSide.Buy, + * 200, + * TimeInForceType.Day + * ).submittedPrice(new Decimal("50")) + * ); + * }) + * .then((resp) => console.log(resp.toString())); * ``` */ subscribe(topics: Array): Promise @@ -1377,18 +1487,17 @@ export class TradeContext { * const { Config, TradeContext, GetHistoryExecutionsOptions } = require("longbridge") * * let config = Config.fromEnv() - * * let opts = new GetHistoryExecutionsOptions() - * .symbol("700.HK") - * .startAt(new Date(2022, 5, 9)) - * .endAt(new Date(2022, 5, 12)) + * .symbol("700.HK") + * .startAt(new Date(2022, 5, 9)) + * .endAt(new Date(2022, 5, 12)) * TradeContext.new(config) - * .then((ctx) => ctx.historyExecutions(opts)) - * .then((resp) => { - * for (let obj of resp) { - * console.log(obj.toString()) - * } - * }) + * .then((ctx) => ctx.historyExecutions(opts)) + * .then((resp) => { + * for (let obj of resp) { + * console.log(obj.toString()) + * } + * }) * ``` */ historyExecutions(opts?: GetHistoryExecutionsOptions | undefined | null): Promise> @@ -1402,12 +1511,12 @@ export class TradeContext { * * let config = Config.fromEnv() * TradeContext.new(config) - * .then((ctx) => ctx.todayExecutions(new GetTodayExecutionsOptions().symbol("700.HK"))) - * .then((resp) => { - * for (let obj of resp) { - * console.log(obj.toString()) - * } - * }) + * .then((ctx) => ctx.todayExecutions(new GetTodayExecutionsOptions().symbol("700.HK"))) + * .then((resp) => { + * for (let obj of resp) { + * console.log(obj.toString()) + * } + * }) * ``` */ todayExecutions(opts?: GetTodayExecutionsOptions | undefined | null): Promise> @@ -1420,21 +1529,20 @@ export class TradeContext { * const { Config, TradeContext, GetHistoryOrdersOptions, OrderStatus, OrderSide, Market } = require("longbridge") * * let config = Config.fromEnv() - * * let opts = new GetHistoryOrdersOptions() - * .symbol("700.HK") - * .status([OrderStatus.Filled, OrderStatus.New]) - * .side(OrderSide.Buy) - * .market(Market.HK) - * .startAt(2022, 5, 9) - * .endAt(2022, 5, 12) + * .symbol("700.HK") + * .status([OrderStatus.Filled, OrderStatus.New]) + * .side(OrderSide.Buy) + * .market(Market.HK) + * .startAt(2022, 5, 9) + * .endAt(2022, 5, 12) * TradeContext.new(config) - * .then((ctx) => ctx.historyOrders(opts)) - * .then((resp) => { - * for (let obj of resp) { - * console.log(obj.toString()) - * } - * }) + * .then((ctx) => ctx.historyOrders(opts)) + * .then((resp) => { + * for (let obj of resp) { + * console.log(obj.toString()) + * } + * }) * ``` */ historyOrders(opts?: GetHistoryOrdersOptions | undefined | null): Promise> @@ -1449,17 +1557,17 @@ export class TradeContext { * let config = Config.fromEnv() * * let opts = new GetTodayOrdersOptions() - * .symbol("700.HK") - * .status([OrderStatus.Filled, OrderStatus.New]) - * .side(OrderSide.Buy) - * .market(Market.HK) + * .symbol("700.HK") + * .status([OrderStatus.Filled, OrderStatus.New]) + * .side(OrderSide.Buy) + * .market(Market.HK) * TradeContext.new(config) - * .then((ctx) => ctx.todayOrders(opts)) - * .then((resp) => { - * for (let obj of resp) { - * console.log(obj.toString()) - * } - * }) + * .then((ctx) => ctx.todayOrders(opts)) + * .then((resp) => { + * for (let obj of resp) { + * console.log(obj.toString()) + * } + * }) * ) * ``` */ @@ -1474,12 +1582,12 @@ export class TradeContext { * * let config = Config.fromEnv() * TradeContext.new(config) - * .then((ctx) => ctx.replaceOrder(new ReplaceOrderOptions("700.HK", 100).price(new Decimal("300")))) - * .then((resp) => { - * for (let obj of resp) { - * console.log(obj.toString()) - * } - * }) + * .then((ctx) => ctx.replaceOrder(new ReplaceOrderOptions("700.HK", 100).price(new Decimal("300")))) + * .then((resp) => { + * for (let obj of resp) { + * console.log(obj.toString()) + * } + * }) * ``` */ replaceOrder(opts: ReplaceOrderOptions): Promise @@ -1492,12 +1600,11 @@ export class TradeContext { * const { Config, TradeContext, SubmitOrderOptions, OrderType, OrderSide, Decimal, TimeInForceType } = require("longbridge") * * let config = Config.fromEnv() - * * let opts = new SubmitOrderOptions("700.HK", OrderType.LO, OrderSide.Buy, 200, TimeInForceType.Day) - * .submittedPrice(new Decimal("300")); + * .submittedPrice(new Decimal("300")); * TradeContext.new(config) - * .then((ctx) => ctx.submitOrder(opts)) - * .then((resp) => console.log(resp)) + * .then((ctx) => ctx.submitOrder(opts)) + * .then((resp) => console.log(resp)) * ``` */ submitOrder(opts: SubmitOrderOptions): Promise @@ -1511,7 +1618,7 @@ export class TradeContext { * * let config = Config.fromEnv() * TradeContext.new(config) - * .then((ctx) => ctx.cancelOrder("709043056541253632")) + * .then((ctx) => ctx.cancelOrder("709043056541253632")) * ``` */ cancelOrder(orderId: string): Promise @@ -1525,12 +1632,12 @@ export class TradeContext { * * let config = Config.fromEnv() * TradeContext.new(config) - * .then((ctx) => ctx.accountBalance()) - * .then((resp) => { - * for (let obj of resp) { - * console.log(obj.toString()) - * } - * }) + * .then((ctx) => ctx.accountBalance()) + * .then((resp) => { + * for (let obj of resp) { + * console.log(obj.toString()) + * } + * }) * ``` */ accountBalance(): Promise> @@ -1544,12 +1651,12 @@ export class TradeContext { * * let config = Config.fromEnv() * TradeContext.new(config) - * .then((ctx) => ctx.cashFlow(new GetCashFlowOptions(new Date(2022, 5, 9), new Date(2022, 5, 12)))) - * .then((resp) => { - * for (let obj of resp) { - * console.log(obj.toString()) - * } - * }) + * .then((ctx) => ctx.cashFlow(new GetCashFlowOptions(new Date(2022, 5, 9), new Date(2022, 5, 12)))) + * .then((resp) => { + * for (let obj of resp) { + * console.log(obj.toString()) + * } + * }) * ``` */ cashFlow(opts: GetCashFlowOptions): Promise> @@ -1563,8 +1670,8 @@ export class TradeContext { * * let config = Config.fromEnv() * TradeContext.new(config) - * .then((ctx) => ctx.fundPositions()) - * .then((resp) => console.log(resp)) + * .then((ctx) => ctx.fundPositions()) + * .then((resp) => console.log(resp)) * ``` */ fundPositions(symbols?: Array | undefined | null): Promise @@ -1578,8 +1685,8 @@ export class TradeContext { * * let config = Config.fromEnv() * TradeContext.new(config) - * .then((ctx) => ctx.stockPositions()) - * .then((resp) => console.log(resp)) + * .then((ctx) => ctx.stockPositions()) + * .then((resp) => console.log(resp)) * ``` */ stockPositions(symbols?: Array | undefined | null): Promise @@ -1646,6 +1753,8 @@ export class GetTodayOrdersOptions { side(side: OrderSide): GetTodayOrdersOptions /** Set the market */ market(market: Market): GetTodayOrdersOptions + /** Set the order id */ + orderId(orderId: string): GetTodayOrdersOptions } /** Options for get today orders request */ export class ReplaceOrderOptions { diff --git a/nodejs/index.js b/nodejs/index.js index 6e600ffbf5..3328c384ab 100644 --- a/nodejs/index.js +++ b/nodejs/index.js @@ -236,7 +236,7 @@ if (!nativeBinding) { throw new Error(`Failed to load native binding`) } -const { Config, Decimal, QuoteContext, PushQuoteEvent, PushDepthEvent, PushBrokersEvent, PushTradesEvent, Subscription, DerivativeType, TradeStatus, TradeSession, SubType, TradeDirection, OptionType, OptionDirection, WarrantType, Period, AdjustType, SecurityStaticInfo, PrePostQuote, SecurityQuote, OptionQuote, WarrantQuote, Depth, SecurityDepth, Brokers, SecurityBrokers, ParticipantInfo, Trade, IntradayLine, Candlestick, StrikePriceInfo, IssuerInfo, TradingSessionInfo, MarketTradingSession, RealtimeQuote, PushQuote, PushDepth, PushBrokers, PushTrades, MarketTradingDays, NaiveDate, Time, TradeContext, GetCashFlowOptions, GetHistoryExecutionsOptions, GetHistoryOrdersOptions, GetTodayExecutionsOptions, GetTodayOrdersOptions, ReplaceOrderOptions, SubmitOrderOptions, TopicType, Execution, OrderStatus, OrderSide, OrderType, OrderTag, TimeInForceType, TriggerStatus, OutsideRTH, Order, PushOrderChanged, SubmitOrderResponse, CashInfo, AccountBalance, BalanceType, CashFlowDirection, CashFlow, FundPositionsResponse, FundPositionChannel, FundPosition, StockPositionsResponse, StockPositionChannel, StockPosition, Market } = nativeBinding +const { Config, Decimal, QuoteContext, PushQuoteEvent, PushDepthEvent, PushBrokersEvent, PushTradesEvent, PushCandlestickEvent, Subscription, DerivativeType, TradeStatus, TradeSession, SubType, TradeDirection, OptionType, OptionDirection, WarrantType, Period, AdjustType, SecurityStaticInfo, PrePostQuote, SecurityQuote, OptionQuote, WarrantQuote, Depth, SecurityDepth, Brokers, SecurityBrokers, ParticipantInfo, Trade, IntradayLine, Candlestick, StrikePriceInfo, IssuerInfo, TradingSessionInfo, MarketTradingSession, RealtimeQuote, PushQuote, PushDepth, PushBrokers, PushTrades, PushCandlestick, MarketTradingDays, CapitalFlowLine, CapitalDistribution, CapitalDistributionResponse, NaiveDate, Time, TradeContext, GetCashFlowOptions, GetHistoryExecutionsOptions, GetHistoryOrdersOptions, GetTodayExecutionsOptions, GetTodayOrdersOptions, ReplaceOrderOptions, SubmitOrderOptions, TopicType, Execution, OrderStatus, OrderSide, OrderType, OrderTag, TimeInForceType, TriggerStatus, OutsideRTH, Order, PushOrderChanged, SubmitOrderResponse, CashInfo, AccountBalance, BalanceType, CashFlowDirection, CashFlow, FundPositionsResponse, FundPositionChannel, FundPosition, StockPositionsResponse, StockPositionChannel, StockPosition, Market } = nativeBinding module.exports.Config = Config module.exports.Decimal = Decimal @@ -245,6 +245,7 @@ module.exports.PushQuoteEvent = PushQuoteEvent module.exports.PushDepthEvent = PushDepthEvent module.exports.PushBrokersEvent = PushBrokersEvent module.exports.PushTradesEvent = PushTradesEvent +module.exports.PushCandlestickEvent = PushCandlestickEvent module.exports.Subscription = Subscription module.exports.DerivativeType = DerivativeType module.exports.TradeStatus = TradeStatus @@ -278,7 +279,11 @@ module.exports.PushQuote = PushQuote module.exports.PushDepth = PushDepth module.exports.PushBrokers = PushBrokers module.exports.PushTrades = PushTrades +module.exports.PushCandlestick = PushCandlestick module.exports.MarketTradingDays = MarketTradingDays +module.exports.CapitalFlowLine = CapitalFlowLine +module.exports.CapitalDistribution = CapitalDistribution +module.exports.CapitalDistributionResponse = CapitalDistributionResponse module.exports.NaiveDate = NaiveDate module.exports.Time = Time module.exports.TradeContext = TradeContext diff --git a/nodejs/src/quote/context.rs b/nodejs/src/quote/context.rs index 0c62a7bcaf..80561dcde3 100644 --- a/nodejs/src/quote/context.rs +++ b/nodejs/src/quote/context.rs @@ -8,7 +8,9 @@ use crate::{ config::Config, error::ErrorNewType, quote::{ - push::{PushBrokersEvent, PushDepthEvent, PushQuoteEvent, PushTradesEvent}, + push::{ + PushBrokersEvent, PushCandlestickEvent, PushDepthEvent, PushQuoteEvent, PushTradesEvent, + }, types::{ AdjustType, Candlestick, CapitalDistributionResponse, CapitalFlowLine, IntradayLine, IssuerInfo, MarketTradingDays, MarketTradingSession, OptionQuote, ParticipantInfo, @@ -28,6 +30,7 @@ struct Callbacks { depth: Option>, brokers: Option>, trades: Option>, + candlestick: Option>, } /// Quote context @@ -106,6 +109,19 @@ impl QuoteContext { } } } + PushEventDetail::Candlestick(candlestick) => { + if let Some(callback) = &callbacks.candlestick { + if let Ok(candlestick) = candlestick.try_into() { + callback.call( + Ok(PushCandlestickEvent { + symbol: msg.symbol, + data: candlestick, + }), + ThreadsafeFunctionCallMode::Blocking, + ); + } + } + } } } } @@ -123,7 +139,7 @@ impl QuoteContext { Ok(()) } - /// Set quote callback, after receiving the depth data push, it will call + /// Set depth callback, after receiving the depth data push, it will call /// back to this function. #[napi(ts_args_type = "callback: (err: null | Error, event: PushDepthEvent) => void")] pub fn set_on_depth(&self, callback: JsFunction) -> Result<()> { @@ -132,8 +148,8 @@ impl QuoteContext { Ok(()) } - /// Set quote callback, after receiving the brokers data push, it will call - /// back to this function. + /// Set brokers callback, after receiving the brokers data push, it will + /// call back to this function. #[napi(ts_args_type = "callback: (err: null | Error, event: PushBrokersEvent) => void")] pub fn set_on_brokers(&self, callback: JsFunction) -> Result<()> { self.callbacks.lock().brokers = @@ -141,7 +157,7 @@ impl QuoteContext { Ok(()) } - /// Set quote callback, after receiving the trades data push, it will call + /// Set trades callback, after receiving the trades data push, it will call /// back to this function. #[napi(ts_args_type = "callback: (err: null | Error, event: PushTradesEvent) => void")] pub fn set_on_trades(&self, callback: JsFunction) -> Result<()> { @@ -150,6 +166,15 @@ impl QuoteContext { Ok(()) } + /// Set candlestick callback, after receiving the trades data push, it will + /// call back to this function. + #[napi(ts_args_type = "callback: (err: null | Error, event: PushCandlestickEvent) => void")] + pub fn set_on_candlestick(&self, callback: JsFunction) -> Result<()> { + self.callbacks.lock().brokers = + Some(callback.create_threadsafe_function(32, |ctx| Ok(vec![ctx.value]))?); + Ok(()) + } + /// Subscribe /// /// #### Example @@ -201,6 +226,26 @@ impl QuoteContext { Ok(()) } + /// Subscribe security candlesticks + #[napi] + pub async fn subscribe_candlesticks(&self, symbol: String, period: Period) -> Result<()> { + self.ctx + .subscribe_candlesticks(symbol, period.into()) + .await + .map_err(ErrorNewType)?; + Ok(()) + } + + /// Unsubscribe security candlesticks + #[napi] + pub async fn unsubscribe_candlesticks(&self, symbol: String, period: Period) -> Result<()> { + self.ctx + .unsubscribe_candlesticks(symbol, period.into()) + .await + .map_err(ErrorNewType)?; + Ok(()) + } + /// Get subscription information /// /// #### Example @@ -803,4 +848,40 @@ impl QuoteContext { .map(TryInto::try_into) .collect() } + + /// Get real-time candlesticks + /// + /// #### Example + /// + /// ```javascript + /// const { Config, QuoteContext, Period } = require("longbridge") + /// + /// let config = Config.fromEnv(); + /// QuoteContext.new(config).then((ctx) => { + /// ctx.subscribeCandlesticks("700.HK", Period.Min_1).then(() => { + /// setTimeout(() => { + /// ctx.realtimeCandlesticks("700.HK", Period.Min_1, 10).then((resp) => { + /// for (let obj of resp) { + /// console.log(obj.toString()); + /// } + /// }); + /// }, 5000); + /// }); + /// }); + /// ``` + #[napi] + pub async fn realtime_candlesticks( + &self, + symbol: String, + period: Period, + count: i32, + ) -> Result> { + self.ctx + .realtime_candlesticks(symbol, period.into(), count.max(0) as usize) + .await + .map_err(ErrorNewType)? + .into_iter() + .map(TryInto::try_into) + .collect() + } } diff --git a/nodejs/src/quote/push.rs b/nodejs/src/quote/push.rs index 017ef29cc6..a41fadffa9 100644 --- a/nodejs/src/quote/push.rs +++ b/nodejs/src/quote/push.rs @@ -1,4 +1,4 @@ -use crate::quote::types::{PushBrokers, PushDepth, PushQuote, PushTrades}; +use crate::quote::types::{PushBrokers, PushCandlestick, PushDepth, PushQuote, PushTrades}; macro_rules! define_push_event { ($name:ident, $ty:ty) => { @@ -35,3 +35,4 @@ define_push_event!(PushQuoteEvent, PushQuote); define_push_event!(PushDepthEvent, PushDepth); define_push_event!(PushBrokersEvent, PushBrokers); define_push_event!(PushTradesEvent, PushTrades); +define_push_event!(PushCandlestickEvent, PushCandlestick); diff --git a/nodejs/src/quote/types.rs b/nodejs/src/quote/types.rs index 7c918be8e0..e8fc7bc82a 100644 --- a/nodejs/src/quote/types.rs +++ b/nodejs/src/quote/types.rs @@ -17,6 +17,8 @@ pub struct Subscription { symbol: String, #[js(sub_types)] sub_types: Vec, + #[js(array)] + candlesticks: Vec, } /// Derivative type @@ -190,8 +192,11 @@ pub enum WarrantType { #[napi_derive::napi] #[allow(non_camel_case_types)] #[derive(Debug, JsEnum, Hash, Eq, PartialEq)] -#[js(remote = "longbridge::quote::Period", from = false)] +#[js(remote = "longbridge::quote::Period")] pub enum Period { + /// One Minute + #[js(remote = "UnknownPeriod")] + Unknown, /// One Minute #[js(remote = "OneMinute")] Min_1, @@ -529,7 +534,7 @@ pub struct IntradayLine { /// Candlestick #[napi_derive::napi] -#[derive(Debug, JsObject)] +#[derive(Debug, JsObject, Copy, Clone)] #[js(remote = "longbridge::quote::Candlestick")] pub struct Candlestick { /// Close price @@ -692,6 +697,17 @@ pub struct PushTrades { trades: Vec, } +/// Candlestick updated event +#[napi_derive::napi] +#[derive(Debug, JsObject, Clone)] +#[js(remote = "longbridge::quote::PushCandlestick")] +pub struct PushCandlestick { + /// Period type + period: Period, + /// Candlestick + candlestick: Candlestick, +} + /// Market trading days #[napi_derive::napi] #[derive(Debug, JsObject)] diff --git a/nodejs/src/trade/requests/get_today_orders.rs b/nodejs/src/trade/requests/get_today_orders.rs index 3cea874599..20b31d124c 100644 --- a/nodejs/src/trade/requests/get_today_orders.rs +++ b/nodejs/src/trade/requests/get_today_orders.rs @@ -49,6 +49,13 @@ impl GetTodayOrdersOptions { pub fn market(&self, market: Market) -> GetTodayOrdersOptions { Self(self.0.clone().market(market.into())) } + + /// Set the order id + #[napi] + #[inline] + pub fn order_id(&self, order_id: String) -> GetTodayOrdersOptions { + Self(self.0.clone().order_id(order_id)) + } } impl From for longbridge::trade::GetTodayOrdersOptions { diff --git a/python/Makefile.toml b/python/Makefile.toml index 7db8bbe311..293bba1836 100644 --- a/python/Makefile.toml +++ b/python/Makefile.toml @@ -12,7 +12,7 @@ args = ["install", "maturin>=0.12,<0.13"] command = "pip" args = [ "install", - "target/wheels/longbridge-0.2.19-cp310-none-win_amd64.whl", + "target/wheels/longbridge-0.2.20-cp310-none-win_amd64.whl", "-I", ] -dependencies = ["build-python-sdk"] +dependencies = ["python"] diff --git a/python/README.md b/python/README.md index 00a463ae62..b6a02ec54c 100644 --- a/python/README.md +++ b/python/README.md @@ -52,8 +52,8 @@ from longbridge.openapi import Config, QuoteContext, SubType, PushQuote config = Config.from_env() # A callback to receive quote data -def on_quote(self, symbol: str, quote: PushQuote): - print(symbol, quote) +def on_quote(self, symbol: str, event: PushQuote): + print(symbol, event) # Create a context for quote APIs ctx = QuoteContext(config) diff --git a/python/docs/index.md b/python/docs/index.md index 79062a1901..e6b903d4b9 100644 --- a/python/docs/index.md +++ b/python/docs/index.md @@ -52,8 +52,8 @@ from longbridge.openapi import Config, QuoteContext, SubType, PushQuote config = Config.from_env() # A callback to receive quote data -def on_quote(self, symbol: str, quote: PushQuote): - print(symbol, quote) +def on_quote(self, symbol: str, event: PushQuote): + print(symbol, event) # Create a context for quote APIs ctx = QuoteContext(config) diff --git a/python/pysrc/longbridge/openapi.pyi b/python/pysrc/longbridge/openapi.pyi index ad60a5b374..5cd1281ad4 100644 --- a/python/pysrc/longbridge/openapi.pyi +++ b/python/pysrc/longbridge/openapi.pyi @@ -188,6 +188,22 @@ class PushTrades: """ +class PushCandlestick: + """ + Candlestick updated event + """ + + period: Period + """ + Period type + """ + + candlestick: Candlestick + """ + Candlestick + """ + + class SubType: """ Subscription flags @@ -1081,6 +1097,11 @@ class Period: Candlestick period """ + class Unknown(Period): + """ + Unknown + """ + class Min_1(Period): """ One Minute @@ -1340,11 +1361,16 @@ class Subscription: Security code """ - sub_types: List[SubType] + sub_types: List[Type[SubType]] """ Subscription types """ + candlesticks: List[Type[Period]] + """ + Candlesticks + """ + class QuoteContext: """ @@ -1363,17 +1389,22 @@ class QuoteContext: def set_on_depth(self, callback: Callable[[str, PushDepth], None]) -> None: """ - Set quote callback, after receiving the depth data push, it will call back to this function. + Set depth callback, after receiving the depth data push, it will call back to this function. """ def set_on_brokers(self, callback: Callable[[str, PushBrokers], None]) -> None: """ - Set quote callback, after receiving the brokers data push, it will call back to this function. + Set brokers callback, after receiving the brokers data push, it will call back to this function. """ def set_on_trades(self, callback: Callable[[str, PushTrades], None]) -> None: """ - Set quote callback, after receiving the trades data push, it will call back to this function. + Set trades callback, after receiving the trades data push, it will call back to this function. + """ + + def set_on_candlestick(self, callback: Callable[[str, PushCandlestick], None]) -> None: + """ + Set candlestick callback, after receiving the candlestick updated event, it will call back to this function. """ def subscribe(self, symbols: List[str], sub_types: List[Type[SubType]], is_first_push: bool = False) -> None: @@ -1391,8 +1422,8 @@ class QuoteContext: from time import sleep from longbridge.openapi import QuoteContext, Config, SubType, PushQuote - def on_quote(symbol: str, quote: PushQuote): - print(symbol, quote) + def on_quote(symbol: str, event: PushQuote): + print(symbol, event) config = Config.from_env() ctx = QuoteContext(config) @@ -1421,6 +1452,37 @@ class QuoteContext: ctx.unsubscribe(["AAPL.US"], [SubType.Quote]) """ + def subscribe_candlesticks(self, symbol: str, period: Type[Period]) -> None: + """ + Subscribe security candlesticks + + Args: + symbol: Security code + period: Period type + + Examples: + :: + + from longbridge.openapi import QuoteContext, Config, PushCandlestick + config = Config.from_env() + ctx = QuoteContext(config) + + def on_candlestick(symbol: str, event: PushCandlestick): + print(symbol, event) + + ctx.subscribe_candlesticks("700.HK", Period.Min_1) + sleep(30) + """ + + def unsubscribe_candlesticks(self, symbol: str, period: Type[Period]) -> None: + """ + Subscribe security candlesticks + + Args: + symbol: Security code + period: Period type + """ + def subscriptions(self) -> List[Subscription]: """ Get subscription information @@ -1922,6 +1984,35 @@ class QuoteContext: print(resp) """ + def realtime_candlesticks(self, symbol: str, period: Type[Period], count: int) -> List[Candlestick]: + """ + Get real-time candlesticks + + Get Get real-time candlesticks of the subscribed symbols, it always returns the data in the local storage. + + Args: + symbol: Security code + period: Period type + count: Count of candlesticks + + Returns: + Security candlesticks + + Examples: + :: + + from time import sleep + from longbridge.openapi import QuoteContext, Config, Period + + config = Config.from_env() + ctx = QuoteContext(config) + + ctx.subscribe_candlesticks("AAPL.US", Period.Min_1) + sleep(5) + resp = ctx.realtime_candlesticks("AAPL.US", Period.Min_1, 10) + print(resp) + """ + class OrderSide: """ @@ -2960,7 +3051,7 @@ class TradeContext: print(resp) """ - def today_orders(self, symbol: Optional[str] = None, status: List[Type[OrderStatus]] = [], side: Optional[Type[OrderSide]] = None, market: Optional[Type[Market]] = None) -> List[Order]: + def today_orders(self, symbol: Optional[str] = None, status: List[Type[OrderStatus]] = [], side: Optional[Type[OrderSide]] = None, market: Optional[Type[Market]] = None, order_id: Optional[str] = None) -> List[Order]: """ Get today orders @@ -2969,6 +3060,7 @@ class TradeContext: status: Filter by order status side: Filter by order side market: Filter by market type + order_id: Filter by order id Returns: Order list diff --git a/python/src/quote/context.rs b/python/src/quote/context.rs index 8c843fa3e0..d171e1429c 100644 --- a/python/src/quote/context.rs +++ b/python/src/quote/context.rs @@ -27,6 +27,7 @@ pub(crate) struct Callbacks { pub(crate) depth: Option, pub(crate) brokers: Option, pub(crate) trades: Option, + pub(crate) candlestick: Option, } #[pyclass] @@ -90,6 +91,16 @@ impl QuoteContext { } } + /// Set candlestick callback, after receiving the candlestick updated event, + /// it will call back to this function. + fn set_on_candlestick(&self, py: Python<'_>, callback: PyObject) { + if callback.is_none(py) { + self.callbacks.lock().candlestick = None; + } else { + self.callbacks.lock().candlestick = Some(callback); + } + } + /// Subscribe #[args(is_first_push = false)] fn subscribe( @@ -112,6 +123,22 @@ impl QuoteContext { Ok(()) } + /// Subscribe security candlesticks + fn subscribe_candlesticks(&self, symbol: String, period: Period) -> PyResult<()> { + self.ctx + .subscribe_candlesticks(symbol, period.into()) + .map_err(ErrorNewType)?; + Ok(()) + } + + /// Subscribe security candlesticks + fn unsubscribe_candlesticks(&self, symbol: String, period: Period) -> PyResult<()> { + self.ctx + .unsubscribe_candlesticks(symbol, period.into()) + .map_err(ErrorNewType)?; + Ok(()) + } + /// Get subscription information fn subscriptions(&self) -> PyResult> { self.ctx @@ -330,4 +357,20 @@ impl QuoteContext { .map(TryInto::try_into) .collect() } + + /// Get real-time candlesticks + #[args(count = 1000)] + fn realtime_candlesticks( + &self, + symbol: String, + period: Period, + count: usize, + ) -> PyResult> { + self.ctx + .realtime_candlesticks(symbol, period.into(), count) + .map_err(ErrorNewType)? + .into_iter() + .map(TryInto::try_into) + .collect() + } } diff --git a/python/src/quote/mod.rs b/python/src/quote/mod.rs index 0591b15abf..3a25f35496 100644 --- a/python/src/quote/mod.rs +++ b/python/src/quote/mod.rs @@ -35,6 +35,7 @@ pub(crate) fn register_types(parent: &PyModule) -> PyResult<()> { parent.add_class::()?; parent.add_class::()?; parent.add_class::()?; + parent.add_class::()?; parent.add_class::()?; Ok(()) diff --git a/python/src/quote/push.rs b/python/src/quote/push.rs index 4bf45ba9a4..f5d7705257 100644 --- a/python/src/quote/push.rs +++ b/python/src/quote/push.rs @@ -1,5 +1,5 @@ use longbridge::quote::{ - PushBrokers, PushDepth, PushEvent, PushEventDetail, PushQuote, PushTrades, + PushBrokers, PushCandlestick, PushDepth, PushEvent, PushEventDetail, PushQuote, PushTrades, }; use pyo3::prelude::*; @@ -11,6 +11,9 @@ pub(crate) fn handle_push_event(callbacks: &Callbacks, event: PushEvent) { PushEventDetail::Depth(depth) => handle_depth(callbacks, event.symbol, depth), PushEventDetail::Brokers(brokers) => handle_brokers(callbacks, event.symbol, brokers), PushEventDetail::Trade(trades) => handle_trades(callbacks, event.symbol, trades), + PushEventDetail::Candlestick(candlestick) => { + handle_candlesticks(callbacks, event.symbol, candlestick) + } } } @@ -61,3 +64,18 @@ fn handle_trades(callbacks: &Callbacks, symbol: String, trades: PushTrades) { }); } } + +fn handle_candlesticks(callbacks: &Callbacks, symbol: String, candlestick: PushCandlestick) { + if let Some(callback) = &callbacks.trades { + let _ = Python::with_gil(|py| { + callback.call( + py, + ( + symbol, + crate::quote::types::PushCandlestick::try_from(candlestick)?, + ), + None, + ) + }); + } +} diff --git a/python/src/quote/types.rs b/python/src/quote/types.rs index 4a45d89a67..f1847d81cb 100644 --- a/python/src/quote/types.rs +++ b/python/src/quote/types.rs @@ -16,6 +16,8 @@ pub(crate) struct Subscription { symbol: String, #[py(sub_types)] sub_types: Vec, + #[py(array)] + candlesticks: Vec, } /// Derivative type @@ -189,8 +191,11 @@ pub(crate) enum WarrantType { #[pyclass] #[allow(non_camel_case_types)] #[derive(Debug, PyEnum, Copy, Clone, Hash, Eq, PartialEq)] -#[py(remote = "longbridge::quote::Period", from = false)] +#[py(remote = "longbridge::quote::Period")] pub(crate) enum Period { + /// Unknown + #[py(remote = "UnknownPeriod")] + Unknown, /// One Minute #[py(remote = "OneMinute")] Min_1, @@ -522,7 +527,7 @@ pub(crate) struct IntradayLine { /// Candlestick #[pyclass] -#[derive(Debug, PyObject)] +#[derive(Debug, PyObject, Clone)] #[py(remote = "longbridge::quote::Candlestick")] pub(crate) struct Candlestick { /// Close price @@ -683,6 +688,17 @@ pub struct PushTrades { trades: Vec, } +/// Push candlestick updated event +#[pyclass] +#[derive(Debug, PyObject)] +#[py(remote = "longbridge::quote::PushCandlestick")] +pub struct PushCandlestick { + /// Period type + period: Period, + /// Candlestick + candlestick: Candlestick, +} + /// Market trading days #[pyclass] #[derive(Debug, PyObject)] diff --git a/python/src/trade/context.rs b/python/src/trade/context.rs index a02de09814..ddbefb588b 100644 --- a/python/src/trade/context.rs +++ b/python/src/trade/context.rs @@ -175,6 +175,7 @@ impl TradeContext { status: Vec, side: Option, market: Option, + order_id: Option, ) -> PyResult> { let mut opts = GetTodayOrdersOptions::new(); @@ -188,6 +189,9 @@ impl TradeContext { if let Some(market) = market { opts = opts.market(market.into()); } + if let Some(order_id) = order_id { + opts = opts.order_id(order_id); + } self.ctx .today_orders(Some(opts)) diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 4fc66cda16..5dd1d236d4 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -17,13 +17,14 @@ blocking = ["flume"] longbridge-wscli = { path = "crates/wsclient", version = "0.2.20" } longbridge-httpcli = { path = "crates/httpclient", version = "0.2.20" } longbridge-proto = { path = "crates/proto", version = "0.2.20" } +longbridge-candlesticks = { path = "crates/candlesticks", version = "0.2.20" } tokio = { version = "1.18.2", features = [ - "time", - "rt", - "macros", - "sync", - "net", + "time", + "rt", + "macros", + "sync", + "net", ] } rust_decimal = { version = "1.23.1", features = ["serde-with-str"] } num_enum = "0.5.7" diff --git a/rust/crates/candlesticks/Cargo.toml b/rust/crates/candlesticks/Cargo.toml new file mode 100644 index 0000000000..4178025205 --- /dev/null +++ b/rust/crates/candlesticks/Cargo.toml @@ -0,0 +1,12 @@ +[package] +edition = "2021" +name = "longbridge-candlesticks" +version = "0.2.20" +description = "Longbridge candlesticks utils for Rust" +license = "MIT OR Apache-2.0" + +[dependencies] +bitflags = "1.3.2" +rust_decimal = "1.25.0" +time = { version = "0.3.10", features = ["macros"] } +time-tz = "1.0.2" diff --git a/rust/crates/candlesticks/src/lib.rs b/rust/crates/candlesticks/src/lib.rs new file mode 100644 index 0000000000..4eaf34b2a5 --- /dev/null +++ b/rust/crates/candlesticks/src/lib.rs @@ -0,0 +1,7 @@ +mod market; +mod merger; +mod types; + +pub use market::Market; +pub use merger::{Candlestick, IsHalfTradeDay, Merger, Trade, UpdateAction}; +pub use types::{Period, Type}; diff --git a/rust/crates/candlesticks/src/market.rs b/rust/crates/candlesticks/src/market.rs new file mode 100644 index 0000000000..bcc1bf006e --- /dev/null +++ b/rust/crates/candlesticks/src/market.rs @@ -0,0 +1,87 @@ +use time::{macros::time, Time}; +use time_tz::Tz; + +use crate::Type; + +bitflags::bitflags! { + pub(crate) struct UpdateFields: u32 { + const PRICE = 0x1; + const VOLUME = 0x2; + } +} + +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum Market { + HK, + US, +} + +impl Market { + #[inline] + pub fn timezone(&self) -> &Tz { + use time_tz::timezones::db; + + match self { + Market::HK => db::asia::HONG_KONG, + Market::US => db::america::NEW_YORK, + } + } + + #[inline] + pub(crate) fn update_fields(&self, trade_type: &str) -> UpdateFields { + match self { + Market::HK => match trade_type { + "" => UpdateFields::all(), + "D" => UpdateFields::VOLUME, + "M" => UpdateFields::VOLUME, + "P" => UpdateFields::VOLUME, + "U" => UpdateFields::all(), + "X" => UpdateFields::VOLUME, + "Y" => UpdateFields::VOLUME, + _ => UpdateFields::empty(), + }, + Market::US => match trade_type { + "" => UpdateFields::all(), + "A" => UpdateFields::all(), + "B" => UpdateFields::all(), + "C" => UpdateFields::VOLUME, + "D" => UpdateFields::all(), + "E" => UpdateFields::all(), + "F" => UpdateFields::all(), + "G" => UpdateFields::all(), + "H" => UpdateFields::VOLUME, + "I" => UpdateFields::VOLUME, + "K" => UpdateFields::all(), + "L" => UpdateFields::all(), + "P" => UpdateFields::all(), + "S" => UpdateFields::all(), + "V" => UpdateFields::VOLUME, + "W" => UpdateFields::VOLUME, + "X" => UpdateFields::all(), + "1" => UpdateFields::all(), + _ => UpdateFields::empty(), + }, + } + } + + #[inline] + pub(crate) fn trade_sessions(&self, ty: Type) -> &'static [(Time, Time)] { + match (self, ty) { + (Market::HK, _) => &[ + (time!(9:30:00), time!(12:00:00)), + (time!(13:00:00), time!(16:00:00)), + ], + (Market::US, Type::USOQ) => &[(time!(9:30:00), time!(16:15:00))], + (Market::US, _) => &[(time!(9:30:00), time!(16:00:00))], + } + } + + #[inline] + pub(crate) fn half_trade_sessions(&self, ty: Type) -> &'static [(Time, Time)] { + match (self, ty) { + (Market::HK, _) => &[(time!(9:30:00), time!(12:00:00))], + (Market::US, Type::USOQ) => &[(time!(9:30:00), time!(13:00:00))], + (Market::US, _) => &[(time!(9:30:00), time!(13:00:00))], + } + } +} diff --git a/rust/crates/candlesticks/src/merger.rs b/rust/crates/candlesticks/src/merger.rs new file mode 100644 index 0000000000..d2c1c41e88 --- /dev/null +++ b/rust/crates/candlesticks/src/merger.rs @@ -0,0 +1,573 @@ +use rust_decimal::{prelude::FromPrimitive, Decimal}; +use time::{macros::time, Date, Duration, Month, OffsetDateTime, Time, Weekday}; +use time_tz::OffsetDateTimeExt; + +use crate::{market::UpdateFields, Market, Period, Type}; + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct Candlestick { + pub time: OffsetDateTime, + pub open: Decimal, + pub high: Decimal, + pub low: Decimal, + pub close: Decimal, + pub volume: i64, + pub turnover: Decimal, +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct Trade<'a> { + pub time: OffsetDateTime, + pub price: Decimal, + pub volume: i64, + pub trade_type: &'a str, +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum UpdateAction { + UpdateLast(Candlestick), + AppendNew(Candlestick), + None, +} + +pub trait IsHalfTradeDay: Copy { + fn is_half(&self, date: Date) -> bool; +} + +impl IsHalfTradeDay for bool { + #[inline] + fn is_half(&self, _date: Date) -> bool { + *self + } +} + +pub struct Merger { + market: Market, + period: Period, + is_half_trade_day: T, +} + +impl Merger +where + T: IsHalfTradeDay, +{ + #[inline] + pub fn new(market: Market, period: Period, is_half_trade_day: T) -> Self { + Self { + market, + period, + is_half_trade_day, + } + } + + fn round_time( + &self, + mut time: OffsetDateTime, + trade_sessions: &[(Time, Time)], + ) -> OffsetDateTime { + for (idx, (start, end)) in trade_sessions.iter().enumerate() { + if time.time() < *start { + time = if idx == 0 { + time.replace_time(*start) + } else { + time.replace_time(trade_sessions[idx - 1].1) + }; + break; + } else if time.time() < *end { + break; + } else if idx == trade_sessions.len() - 1 { + time = time.replace_time(*end); + break; + } + } + + time + } + + pub fn candlestick_time(&self, ty: Type, time: OffsetDateTime) -> OffsetDateTime { + let Merger { + market, + period, + is_half_trade_day, + } = self; + let trade_sessions = if !is_half_trade_day.is_half(time.date()) { + market.trade_sessions(ty) + } else { + market.half_trade_sessions(ty) + }; + match period { + Period::Min_1 => self + .round_time(time, trade_sessions) + .replace_second(0) + .unwrap(), + Period::Min_5 | Period::Min_15 | Period::Min_30 => { + let time = self.round_time(time, trade_sessions); + let n = period.minutes() as i64; + let minutes = time.hour() as i64 * 60 + time.minute() as i64 - 1; + let minutes = (minutes / n + 1) * n; + let mut time = time.replace_time( + Time::from_hms((minutes / 60) as u8, (minutes % 60) as u8, 0).unwrap(), + ); + for (start, end) in trade_sessions { + let s = time.replace_time(*start); + if time < s + Duration::minutes(n as i64) { + time = s + Duration::minutes(n as i64); + break; + } else if time <= time.replace_time(*end) { + break; + } + } + time + } + Period::Min_60 => { + let time = self.round_time(time, trade_sessions); + let (start, end) = trade_sessions + .iter() + .find(|ts| time.time() >= ts.0 && time.time() <= ts.1) + .unwrap(); + let start_minutes = start.hour() as i64 * 60 + start.minute() as i64; + let curr_minutes = time.hour() as i64 * 60 + time.minute() as i64 - 1; + let offset_minutes = ((curr_minutes - start_minutes) / 60 + 1) * 60; + time.replace_time((*start + Duration::minutes(offset_minutes)).min(*end)) + } + Period::Day => time.replace_time(time!(00:00:00)), + Period::Week => { + let week = time.iso_week(); + Date::from_iso_week_date(time.year(), week, Weekday::Monday) + .and_then(|date| date.with_hms(0, 0, 0)) + .unwrap() + .assume_utc() + } + Period::Month => time + .replace_day(1) + .map(|time| time.replace_time(time!(00:00:00))) + .unwrap(), + Period::Year => time + .replace_month(Month::January) + .and_then(|time| time.replace_day(1)) + .map(|time| time.replace_time(time!(00:00:00))) + .and_then(|time| time.replace_day(1)) + .unwrap(), + } + } + + #[must_use] + pub fn merge(&self, ty: Type, prev: Option<&Candlestick>, trade: Trade<'_>) -> UpdateAction { + let Merger { market, .. } = self; + let tz = market.timezone(); + let time = self.candlestick_time(ty, trade.time.to_timezone(tz)); + let update_fields = market.update_fields(trade.trade_type); + + match prev { + Some(prev) if time == prev.time => { + let mut candlestick = *prev; + + if update_fields.contains(UpdateFields::PRICE) { + candlestick.high = candlestick.high.max(trade.price); + candlestick.low = candlestick.low.min(trade.price); + candlestick.close = trade.price; + } + + if update_fields.contains(UpdateFields::VOLUME) { + candlestick.volume += trade.volume; + if let Some(volume) = Decimal::from_i64(trade.volume) { + candlestick.turnover += trade.price * volume; + } + } + + UpdateAction::UpdateLast(candlestick) + } + Some(prev) if time < prev.time => UpdateAction::None, + _ => { + if update_fields.contains(UpdateFields::PRICE) { + Decimal::from_i64(trade.volume) + .map(|volume| { + let new_candlestick = Candlestick { + time: time.to_timezone(time_tz::timezones::db::UTC), + open: trade.price, + high: trade.price, + low: trade.price, + close: trade.price, + volume: trade.volume, + turnover: trade.price * volume, + }; + UpdateAction::AppendNew(new_candlestick) + }) + .unwrap_or(UpdateAction::None) + } else { + UpdateAction::None + } + } + } + } +} + +#[cfg(test)] +mod tests { + use time::macros::datetime; + + use super::*; + + #[test] + fn test_round_time() { + let trade_sessions = Market::HK.trade_sessions(Type::Normal); + let merger = Merger::new(Market::HK, Period::Day, false); + + assert_eq!( + merger.round_time(datetime!(2022-1-1 9:28:0 UTC), trade_sessions), + datetime!(2022-1-1 9:30:0 UTC) + ); + assert_eq!( + merger.round_time(datetime!(2022-1-1 9:31:0 UTC), trade_sessions), + datetime!(2022-1-1 9:31:0 UTC) + ); + assert_eq!( + merger.round_time(datetime!(2022-1-1 12:0:0 UTC), trade_sessions), + datetime!(2022-1-1 12:0:0 UTC) + ); + assert_eq!( + merger.round_time(datetime!(2022-1-1 12:5:0 UTC), trade_sessions), + datetime!(2022-1-1 12:0:0 UTC) + ); + assert_eq!( + merger.round_time(datetime!(2022-1-1 13:0:0 UTC), trade_sessions), + datetime!(2022-1-1 13:0:0 UTC) + ); + assert_eq!( + merger.round_time(datetime!(2022-1-1 14:0:0 UTC), trade_sessions), + datetime!(2022-1-1 14:0:0 UTC) + ); + assert_eq!( + merger.round_time(datetime!(2022-1-1 16:0:0 UTC), trade_sessions), + datetime!(2022-1-1 16:0:0 UTC) + ); + assert_eq!( + merger.round_time(datetime!(2022-1-1 16:2:0 UTC), trade_sessions), + datetime!(2022-1-1 16:0:0 UTC) + ); + } + + #[test] + fn test_time_min1() { + let merger = Merger::new(Market::HK, Period::Min_1, false); + + assert_eq!( + merger.candlestick_time(Type::Normal, datetime!(2022-1-1 9:28:0 UTC)), + datetime!(2022-1-1 9:30:0 UTC) + ); + assert_eq!( + merger.candlestick_time(Type::Normal, datetime!(2022-1-1 9:30:25 UTC)), + datetime!(2022-1-1 9:30:0 UTC) + ); + assert_eq!( + merger.candlestick_time(Type::Normal, datetime!(2022-1-1 9:31:0 UTC)), + datetime!(2022-1-1 9:31:0 UTC) + ); + assert_eq!( + merger.candlestick_time(Type::Normal, datetime!(2022-1-1 12:05:0 UTC)), + datetime!(2022-1-1 12:0:0 UTC) + ); + assert_eq!( + merger.candlestick_time(Type::Normal, datetime!(2022-1-1 13:0:0 UTC)), + datetime!(2022-1-1 13:0:0 UTC) + ); + assert_eq!( + merger.candlestick_time(Type::Normal, datetime!(2022-1-1 16:0:0 UTC)), + datetime!(2022-1-1 16:0:0 UTC) + ); + assert_eq!( + merger.candlestick_time(Type::Normal, datetime!(2022-1-1 16:2:0 UTC)), + datetime!(2022-1-1 16:0:0 UTC) + ); + } + + #[test] + fn test_time_min5() { + let merger = Merger::new(Market::HK, Period::Min_5, false); + + assert_eq!( + merger.candlestick_time(Type::Normal, datetime!(2022-1-1 9:28:0 UTC)), + datetime!(2022-1-1 9:35:0 UTC) + ); + assert_eq!( + merger.candlestick_time(Type::Normal, datetime!(2022-1-1 9:30:25 UTC)), + datetime!(2022-1-1 9:35:0 UTC) + ); + assert_eq!( + merger.candlestick_time(Type::Normal, datetime!(2022-1-1 9:35:59 UTC)), + datetime!(2022-1-1 9:35:0 UTC) + ); + assert_eq!( + merger.candlestick_time(Type::Normal, datetime!(2022-1-1 9:36:0 UTC)), + datetime!(2022-1-1 9:40:0 UTC) + ); + assert_eq!( + merger.candlestick_time(Type::Normal, datetime!(2022-1-1 12:05:0 UTC)), + datetime!(2022-1-1 12:0:0 UTC) + ); + assert_eq!( + merger.candlestick_time(Type::Normal, datetime!(2022-1-1 13:0:0 UTC)), + datetime!(2022-1-1 13:5:0 UTC) + ); + assert_eq!( + merger.candlestick_time(Type::Normal, datetime!(2022-1-1 16:0:0 UTC)), + datetime!(2022-1-1 16:0:0 UTC) + ); + assert_eq!( + merger.candlestick_time(Type::Normal, datetime!(2022-1-1 16:2:0 UTC)), + datetime!(2022-1-1 16:0:0 UTC) + ); + } + + #[test] + fn test_time_min15() { + let merger = Merger::new(Market::HK, Period::Min_15, false); + + assert_eq!( + merger.candlestick_time(Type::Normal, datetime!(2022-1-1 9:28:0 UTC)), + datetime!(2022-1-1 9:45:0 UTC) + ); + assert_eq!( + merger.candlestick_time(Type::Normal, datetime!(2022-1-1 9:30:25 UTC)), + datetime!(2022-1-1 9:45:0 UTC) + ); + assert_eq!( + merger.candlestick_time(Type::Normal, datetime!(2022-1-1 9:35:59 UTC)), + datetime!(2022-1-1 9:45:0 UTC) + ); + assert_eq!( + merger.candlestick_time(Type::Normal, datetime!(2022-1-1 9:36:0 UTC)), + datetime!(2022-1-1 9:45:0 UTC) + ); + assert_eq!( + merger.candlestick_time(Type::Normal, datetime!(2022-1-1 12:05:0 UTC)), + datetime!(2022-1-1 12:0:0 UTC) + ); + assert_eq!( + merger.candlestick_time(Type::Normal, datetime!(2022-1-1 13:0:0 UTC)), + datetime!(2022-1-1 13:15:0 UTC) + ); + assert_eq!( + merger.candlestick_time(Type::Normal, datetime!(2022-1-1 16:0:0 UTC)), + datetime!(2022-1-1 16:0:0 UTC) + ); + assert_eq!( + merger.candlestick_time(Type::Normal, datetime!(2022-1-1 16:2:0 UTC)), + datetime!(2022-1-1 16:0:0 UTC) + ); + } + + #[test] + fn test_time_min30() { + let merger = Merger::new(Market::HK, Period::Min_30, false); + + assert_eq!( + merger.candlestick_time(Type::Normal, datetime!(2022-1-1 9:28:0 UTC)), + datetime!(2022-1-1 10:00:0 UTC) + ); + assert_eq!( + merger.candlestick_time(Type::Normal, datetime!(2022-1-1 9:30:25 UTC)), + datetime!(2022-1-1 10:00:0 UTC) + ); + assert_eq!( + merger.candlestick_time(Type::Normal, datetime!(2022-1-1 9:35:59 UTC)), + datetime!(2022-1-1 10:00:0 UTC) + ); + assert_eq!( + merger.candlestick_time(Type::Normal, datetime!(2022-1-1 9:36:0 UTC)), + datetime!(2022-1-1 10:00:0 UTC) + ); + assert_eq!( + merger.candlestick_time(Type::Normal, datetime!(2022-1-1 12:05:0 UTC)), + datetime!(2022-1-1 12:0:0 UTC) + ); + assert_eq!( + merger.candlestick_time(Type::Normal, datetime!(2022-1-1 13:0:0 UTC)), + datetime!(2022-1-1 13:30:0 UTC) + ); + assert_eq!( + merger.candlestick_time(Type::Normal, datetime!(2022-1-1 16:0:0 UTC)), + datetime!(2022-1-1 16:0:0 UTC) + ); + assert_eq!( + merger.candlestick_time(Type::Normal, datetime!(2022-1-1 16:2:0 UTC)), + datetime!(2022-1-1 16:0:0 UTC) + ); + } + + #[test] + fn test_time_min60() { + let merger = Merger::new(Market::HK, Period::Min_60, false); + + assert_eq!( + merger.candlestick_time(Type::Normal, datetime!(2022-1-1 9:28:0 UTC)), + datetime!(2022-1-1 10:30:0 UTC) + ); + assert_eq!( + merger.candlestick_time(Type::Normal, datetime!(2022-1-1 9:30:25 UTC)), + datetime!(2022-1-1 10:30:0 UTC) + ); + assert_eq!( + merger.candlestick_time(Type::Normal, datetime!(2022-1-1 9:35:59 UTC)), + datetime!(2022-1-1 10:30:0 UTC) + ); + assert_eq!( + merger.candlestick_time(Type::Normal, datetime!(2022-1-1 9:36:0 UTC)), + datetime!(2022-1-1 10:30:0 UTC) + ); + assert_eq!( + merger.candlestick_time(Type::Normal, datetime!(2022-1-1 10:30:59 UTC)), + datetime!(2022-1-1 10:30:0 UTC) + ); + assert_eq!( + merger.candlestick_time(Type::Normal, datetime!(2022-1-1 10:31:0 UTC)), + datetime!(2022-1-1 11:30:0 UTC) + ); + assert_eq!( + merger.candlestick_time(Type::Normal, datetime!(2022-1-1 12:05:0 UTC)), + datetime!(2022-1-1 12:0:0 UTC) + ); + assert_eq!( + merger.candlestick_time(Type::Normal, datetime!(2022-1-1 13:0:0 UTC)), + datetime!(2022-1-1 14:0:0 UTC) + ); + assert_eq!( + merger.candlestick_time(Type::Normal, datetime!(2022-1-1 14:2:0 UTC)), + datetime!(2022-1-1 15:0:0 UTC) + ); + assert_eq!( + merger.candlestick_time(Type::Normal, datetime!(2022-1-1 16:0:0 UTC)), + datetime!(2022-1-1 16:0:0 UTC) + ); + assert_eq!( + merger.candlestick_time(Type::Normal, datetime!(2022-1-1 16:2:0 UTC)), + datetime!(2022-1-1 16:0:0 UTC) + ); + } + + #[test] + fn test_time_min60_usoq() { + let merger = Merger::new(Market::US, Period::Min_60, false); + + assert_eq!( + merger.candlestick_time(Type::USOQ, datetime!(2022-1-1 9:28:0 UTC)), + datetime!(2022-1-1 10:30:0 UTC) + ); + assert_eq!( + merger.candlestick_time(Type::USOQ, datetime!(2022-1-1 9:30:25 UTC)), + datetime!(2022-1-1 10:30:0 UTC) + ); + assert_eq!( + merger.candlestick_time(Type::USOQ, datetime!(2022-1-1 9:35:59 UTC)), + datetime!(2022-1-1 10:30:0 UTC) + ); + assert_eq!( + merger.candlestick_time(Type::USOQ, datetime!(2022-1-1 9:36:0 UTC)), + datetime!(2022-1-1 10:30:0 UTC) + ); + assert_eq!( + merger.candlestick_time(Type::USOQ, datetime!(2022-1-1 10:30:59 UTC)), + datetime!(2022-1-1 10:30:0 UTC) + ); + assert_eq!( + merger.candlestick_time(Type::USOQ, datetime!(2022-1-1 10:31:0 UTC)), + datetime!(2022-1-1 11:30:0 UTC) + ); + assert_eq!( + merger.candlestick_time(Type::USOQ, datetime!(2022-1-1 12:05:0 UTC)), + datetime!(2022-1-1 12:30:0 UTC) + ); + assert_eq!( + merger.candlestick_time(Type::USOQ, datetime!(2022-1-1 13:0:0 UTC)), + datetime!(2022-1-1 13:30:0 UTC) + ); + assert_eq!( + merger.candlestick_time(Type::USOQ, datetime!(2022-1-1 14:2:0 UTC)), + datetime!(2022-1-1 14:30:0 UTC) + ); + assert_eq!( + merger.candlestick_time(Type::USOQ, datetime!(2022-1-1 15:30:59 UTC)), + datetime!(2022-1-1 15:30:0 UTC) + ); + assert_eq!( + merger.candlestick_time(Type::USOQ, datetime!(2022-1-1 15:31:0 UTC)), + datetime!(2022-1-1 16:15:0 UTC) + ); + assert_eq!( + merger.candlestick_time(Type::USOQ, datetime!(2022-1-1 16:2:0 UTC)), + datetime!(2022-1-1 16:15:0 UTC) + ); + } + + #[test] + fn test_time_day() { + let merger = Merger::new(Market::HK, Period::Day, false); + + assert_eq!( + merger.candlestick_time(Type::Normal, datetime!(2022-1-1 9:28:0 UTC)), + datetime!(2022-1-1 0:0:0 UTC) + ); + assert_eq!( + merger.candlestick_time(Type::Normal, datetime!(2022-1-1 10:0:0 UTC)), + datetime!(2022-1-1 0:0:0 UTC) + ); + assert_eq!( + merger.candlestick_time(Type::Normal, datetime!(2022-1-3 10:0:0 UTC)), + datetime!(2022-1-3 0:0:0 UTC) + ); + } + + #[test] + fn test_time_week() { + let merger = Merger::new(Market::HK, Period::Week, false); + + assert_eq!( + merger.candlestick_time(Type::Normal, datetime!(2022-1-6 9:28:0 UTC)), + datetime!(2022-1-3 0:0:0 UTC) + ); + assert_eq!( + merger.candlestick_time(Type::Normal, datetime!(2022-1-10 9:28:0 UTC)), + datetime!(2022-1-10 0:0:0 UTC) + ); + assert_eq!( + merger.candlestick_time(Type::Normal, datetime!(2022-6-8 9:28:0 UTC)), + datetime!(2022-6-6 0:0:0 UTC) + ); + } + + #[test] + fn test_time_month() { + let merger = Merger::new(Market::HK, Period::Month, false); + + assert_eq!( + merger.candlestick_time(Type::Normal, datetime!(2022-1-6 9:28:0 UTC)), + datetime!(2022-1-1 0:0:0 UTC) + ); + assert_eq!( + merger.candlestick_time(Type::Normal, datetime!(2022-1-10 9:28:0 UTC)), + datetime!(2022-1-1 0:0:0 UTC) + ); + assert_eq!( + merger.candlestick_time(Type::Normal, datetime!(2022-6-8 9:28:0 UTC)), + datetime!(2022-6-1 0:0:0 UTC) + ); + } + + #[test] + fn test_time_year() { + let merger = Merger::new(Market::HK, Period::Year, false); + + assert_eq!( + merger.candlestick_time(Type::Normal, datetime!(2022-1-6 9:28:0 UTC)), + datetime!(2022-1-1 0:0:0 UTC) + ); + assert_eq!( + merger.candlestick_time(Type::Normal, datetime!(2022-3-10 9:28:0 UTC)), + datetime!(2022-1-1 0:0:0 UTC) + ); + assert_eq!( + merger.candlestick_time(Type::Normal, datetime!(2022-6-8 9:28:0 UTC)), + datetime!(2022-1-1 0:0:0 UTC) + ); + } +} diff --git a/rust/crates/candlesticks/src/types.rs b/rust/crates/candlesticks/src/types.rs new file mode 100644 index 0000000000..8acce07455 --- /dev/null +++ b/rust/crates/candlesticks/src/types.rs @@ -0,0 +1,31 @@ +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[allow(non_camel_case_types)] +pub enum Period { + Min_1, + Min_5, + Min_15, + Min_30, + Min_60, + Day, + Week, + Month, + Year, +} + +impl Period { + #[inline] + pub(crate) fn minutes(&self) -> u8 { + match self { + Period::Min_5 => 5, + Period::Min_15 => 15, + Period::Min_30 => 30, + Period::Min_60 => 60, + _ => unreachable!(), + } + } +} + +pub enum Type { + Normal, + USOQ, +} diff --git a/rust/crates/wsclient/src/client.rs b/rust/crates/wsclient/src/client.rs index 6b6aea51e6..bc045af2ad 100644 --- a/rust/crates/wsclient/src/client.rs +++ b/rust/crates/wsclient/src/client.rs @@ -202,6 +202,14 @@ pub struct WsSession { pub deadline: SystemTime, } +impl WsSession { + /// Returns `true` if the session id is expired, otherwise returns `false + #[inline] + pub fn is_expired(&self) -> bool { + self.deadline < SystemTime::now() + } +} + /// Longbridge Websocket client #[derive(Clone)] pub struct WsClient { diff --git a/rust/src/blocking/quote.rs b/rust/src/blocking/quote.rs index 9a80d58c0a..40ee6a87fb 100644 --- a/rust/src/blocking/quote.rs +++ b/rust/src/blocking/quote.rs @@ -96,6 +96,49 @@ impl QuoteContextSync { .call(move |ctx| async move { ctx.unsubscribe(symbols, sub_types.into()).await }) } + /// Subscribe security candlesticks + /// + /// # Examples + /// + /// ```no_run + /// use std::{sync::Arc, thread::sleep, time::Duration}; + /// + /// use longbridge::{ + /// blocking::QuoteContextSync, + /// quote::{Period, PushEvent}, + /// Config, + /// }; + /// + /// fn event_handler(event: PushEvent) { + /// println!("{:?}", event); + /// } + /// + /// # fn main() -> Result<(), Box> { + /// let config = Arc::new(Config::from_env()?); + /// let ctx = QuoteContextSync::try_new(config, event_handler)?; + /// + /// ctx.subscribe_candlesticks("AAPL.US", Period::OneMinute)?; + /// sleep(Duration::from_secs(30)); + /// # Ok(()) + /// # } + /// ``` + pub fn subscribe_candlesticks(&self, symbol: T, period: Period) -> Result<()> + where + T: Into + Send + 'static, + { + self.rt + .call(move |ctx| async move { ctx.subscribe_candlesticks(symbol, period).await }) + } + + /// Unsubscribe security candlesticks + pub fn unsubscribe_candlesticks(&self, symbol: T, period: Period) -> Result<()> + where + T: Into + Send + 'static, + { + self.rt + .call(move |ctx| async move { ctx.unsubscribe_candlesticks(symbol, period).await }) + } + /// Get subscription information /// /// # Examples @@ -705,4 +748,38 @@ impl QuoteContextSync { self.rt .call(move |ctx| async move { ctx.realtime_brokers(symbol).await }) } + + /// Get real-time candlesticks + /// + /// Get real-time candlesticks of the subscribed symbols, it always returns + /// the data in the local storage. + /// + /// # Examples + /// + /// ```no_run + /// use std::{sync::Arc, thread::sleep, time::Duration}; + /// + /// use longbridge::{blocking::QuoteContextSync, quote::Period, Config, Market}; + /// + /// # fn main() -> Result<(), Box> { + /// let config = Arc::new(Config::from_env()?); + /// let ctx = QuoteContextSync::try_new(config, |_| ())?; + /// + /// ctx.subscribe_candlesticks("AAPL.US", Period::OneMinute)?; + /// sleep(Duration::from_secs(5)); + /// + /// let resp = ctx.realtime_candlesticks("AAPL.US", Period::OneMinute, 10)?; + /// println!("{:?}", resp); + /// # Ok(()) + /// # } + /// ``` + pub fn realtime_candlesticks( + &self, + symbol: impl Into + Send + 'static, + period: Period, + count: usize, + ) -> Result> { + self.rt + .call(move |ctx| async move { ctx.realtime_candlesticks(symbol, period, count).await }) + } } diff --git a/rust/src/quote/context.rs b/rust/src/quote/context.rs index abb4eed5fc..7566a0e701 100644 --- a/rust/src/quote/context.rs +++ b/rust/src/quote/context.rs @@ -182,6 +182,61 @@ impl QuoteContext { reply_rx.await.map_err(|_| WsClientError::ClientClosed)? } + /// Subscribe security candlesticks + /// + /// # Examples + /// + /// ```no_run + /// use std::sync::Arc; + /// + /// use longbridge::{ + /// quote::{Period, QuoteContext}, + /// Config, + /// }; + /// + /// # tokio::runtime::Runtime::new().unwrap().block_on(async { + /// let config = Arc::new(Config::from_env()?); + /// let (ctx, mut receiver) = QuoteContext::try_new(config).await?; + /// + /// ctx.subscribe_candlesticks("AAPL.US", Period::OneMinute) + /// .await?; + /// while let Some(msg) = receiver.recv().await { + /// println!("{:?}", msg); + /// } + /// # Ok::<_, Box>(()) + /// # }); + /// ``` + pub async fn subscribe_candlesticks(&self, symbol: T, period: Period) -> Result<()> + where + T: Into, + { + let (reply_tx, reply_rx) = oneshot::channel(); + self.command_tx + .send(Command::SubscribeCandlesticks { + symbol: symbol.into(), + period, + reply_tx, + }) + .map_err(|_| WsClientError::ClientClosed)?; + reply_rx.await.map_err(|_| WsClientError::ClientClosed)? + } + + /// Unsubscribe security candlesticks + pub async fn unsubscribe_candlesticks(&self, symbol: T, period: Period) -> Result<()> + where + T: Into, + { + let (reply_tx, reply_rx) = oneshot::channel(); + self.command_tx + .send(Command::UnsubscribeCandlesticks { + symbol: symbol.into(), + period, + reply_tx, + }) + .map_err(|_| WsClientError::ClientClosed)?; + reply_rx.await.map_err(|_| WsClientError::ClientClosed)? + } + /// Get subscription information /// /// # Examples @@ -1060,4 +1115,52 @@ impl QuoteContext { .map_err(|_| WsClientError::ClientClosed)?; Ok(reply_rx.await.map_err(|_| WsClientError::ClientClosed)?) } + + /// Get real-time candlesticks + /// + /// Get real-time candlesticks of the subscribed symbols, it always returns + /// the data in the local storage. + /// + /// # Examples + /// + /// ```no_run + /// use std::{sync::Arc, time::Duration}; + /// + /// use longbridge::{ + /// quote::{Period, QuoteContext}, + /// Config, + /// }; + /// + /// # tokio::runtime::Runtime::new().unwrap().block_on(async { + /// let config = Arc::new(Config::from_env()?); + /// let (ctx, _) = QuoteContext::try_new(config).await?; + /// + /// ctx.subscribe_candlesticks("AAPL.US", Period::OneMinute) + /// .await?; + /// tokio::time::sleep(Duration::from_secs(5)).await; + /// + /// let resp = ctx + /// .realtime_candlesticks("AAPL.US", Period::OneMinute, 10) + /// .await?; + /// println!("{:?}", resp); + /// # Ok::<_, Box>(()) + /// # }); + /// ``` + pub async fn realtime_candlesticks( + &self, + symbol: impl Into, + period: Period, + count: usize, + ) -> Result> { + let (reply_tx, reply_rx) = oneshot::channel(); + self.command_tx + .send(Command::GetRealtimeCandlesticks { + symbol: symbol.into(), + period, + count, + reply_tx, + }) + .map_err(|_| WsClientError::ClientClosed)?; + Ok(reply_rx.await.map_err(|_| WsClientError::ClientClosed)?) + } } diff --git a/rust/src/quote/core.rs b/rust/src/quote/core.rs index d5acd078d1..2ab06a1a37 100644 --- a/rust/src/quote/core.rs +++ b/rust/src/quote/core.rs @@ -1,16 +1,31 @@ -use std::{collections::HashMap, sync::Arc, time::Duration}; +use std::{ + collections::{HashMap, HashSet}, + sync::Arc, +}; +use longbridge_candlesticks::{IsHalfTradeDay, Type, UpdateAction}; use longbridge_httpcli::HttpClient; -use longbridge_proto::quote::{SubscribeRequest, SubscriptionResponse, UnsubscribeRequest}; +use longbridge_proto::quote::{ + AdjustType, MarketTradeDayRequest, MarketTradeDayResponse, Period, SecurityCandlestickRequest, + SecurityCandlestickResponse, SubscribeRequest, TradeSession, UnsubscribeRequest, +}; use longbridge_wscli::{CodecType, Platform, ProtocolVersion, WsClient, WsEvent, WsSession}; -use tokio::sync::{mpsc, oneshot}; +use time::{Date, OffsetDateTime}; +use tokio::{ + sync::{mpsc, oneshot}, + time::{Duration, Instant}, +}; use crate::{ quote::{ - cmd_code, store::Store, sub_flags::SubFlags, PushEvent, RealtimeQuote, SecurityBrokers, + cmd_code, + store::Store, + sub_flags::SubFlags, + utils::{format_date, get_market_from_symbol, parse_date}, + Candlestick, PushCandlestick, PushEvent, PushEventDetail, RealtimeQuote, SecurityBrokers, SecurityDepth, Subscription, Trade, }, - Config, Result, + Config, Error, Market, Result, }; const RECONNECT_DELAY: Duration = Duration::from_secs(2); @@ -32,6 +47,16 @@ pub(crate) enum Command { sub_types: SubFlags, reply_tx: oneshot::Sender>, }, + SubscribeCandlesticks { + symbol: String, + period: Period, + reply_tx: oneshot::Sender>, + }, + UnsubscribeCandlesticks { + symbol: String, + period: Period, + reply_tx: oneshot::Sender>, + }, Subscriptions { reply_tx: oneshot::Sender>, }, @@ -52,6 +77,40 @@ pub(crate) enum Command { symbol: String, reply_tx: oneshot::Sender, }, + GetRealtimeCandlesticks { + symbol: String, + period: Period, + count: usize, + reply_tx: oneshot::Sender>, + }, +} + +#[derive(Debug, Default)] +struct CurrentTradeDays { + half_days: HashMap>, +} + +impl CurrentTradeDays { + #[inline] + fn half_days(&self, market: longbridge_candlesticks::Market) -> HalfDays { + match market { + longbridge_candlesticks::Market::HK => HalfDays(self.half_days.get(&Market::HK)), + longbridge_candlesticks::Market::US => HalfDays(self.half_days.get(&Market::US)), + } + } +} + +#[derive(Debug, Copy, Clone)] +struct HalfDays<'a>(Option<&'a HashSet>); + +impl<'a> IsHalfTradeDay for HalfDays<'a> { + #[inline] + fn is_half(&self, date: Date) -> bool { + match self.0 { + Some(days) => days.contains(&date), + None => false, + } + } } pub(crate) struct Core { @@ -65,6 +124,7 @@ pub(crate) struct Core { session: WsSession, close: bool, subscriptions: HashMap, + current_trade_days: CurrentTradeDays, store: Store, } @@ -95,6 +155,7 @@ impl Core { tracing::debug!(url = config.quote_ws_url.as_str(), "quote server connected"); let session = ws_cli.request_auth(otp).await?; + let current_trade_days = fetch_current_trade_days(&ws_cli).await?; Ok(Self { config, @@ -107,6 +168,7 @@ impl Core { session, close: false, subscriptions: HashMap::new(), + current_trade_days, store: Store::default(), }) } @@ -193,6 +255,11 @@ impl Core { #[tracing::instrument(level = "debug", skip(self))] async fn main_loop(&mut self) -> Result<()> { + let mut update_trade_days_interval = tokio::time::interval_at( + Instant::now() + Duration::from_secs(60 * 60 * 24), + Duration::from_secs(60 * 60 * 24), + ); + loop { tokio::select! { item = self.event_rx.recv() => { @@ -210,6 +277,11 @@ impl Core { } } } + _ = update_trade_days_interval.tick() => { + if let Ok(days) = fetch_current_trade_days(&self.ws_cli).await { + self.current_trade_days = days; + } + } } } } @@ -238,8 +310,23 @@ impl Core { sub_types, reply_tx, } => { - let res = self.handle_unsubscribe(symbols, sub_types).await; - let _ = reply_tx.send(res); + let _ = reply_tx.send(self.handle_unsubscribe(symbols, sub_types).await); + Ok(()) + } + Command::SubscribeCandlesticks { + symbol, + period, + reply_tx, + } => { + let _ = reply_tx.send(self.handle_subscribe_candlesticks(symbol, period).await); + Ok(()) + } + Command::UnsubscribeCandlesticks { + symbol, + period, + reply_tx, + } => { + let _ = reply_tx.send(self.handle_unsubscribe_candlesticks(symbol, period).await); Ok(()) } Command::Subscriptions { reply_tx } => { @@ -267,6 +354,15 @@ impl Core { let _ = reply_tx.send(self.handle_get_realtime_brokers(symbol)); Ok(()) } + Command::GetRealtimeCandlesticks { + symbol, + period, + count, + reply_tx, + } => { + let _ = reply_tx.send(self.handle_get_realtime_candlesticks(symbol, period, count)); + Ok(()) + } } } @@ -277,10 +373,8 @@ impl Core { reply_tx: oneshot::Sender>>, ) -> Result<()> { let ws_cli = self.ws_cli.clone(); - tokio::spawn(async move { - let res = ws_cli.request_raw(command_code, None, body).await; - let _ = reply_tx.send(res.map_err(Into::into)); - }); + let res = ws_cli.request_raw(command_code, None, body).await; + let _ = reply_tx.send(res.map_err(Into::into)); Ok(()) } @@ -292,21 +386,20 @@ impl Core { ) -> Result<()> { tracing::debug!(symbols = ?symbols, sub_types = ?sub_types, "subscribe"); + // send request let req = SubscribeRequest { - symbol: symbols, + symbol: symbols.clone(), sub_type: sub_types.into(), is_first_push, }; - let resp: SubscriptionResponse = - self.ws_cli.request(cmd_code::SUBSCRIBE, None, req).await?; - - for sub in resp.sub_list { - let sub_flags: SubFlags = sub.sub_type.into(); + self.ws_cli.request(cmd_code::SUBSCRIBE, None, req).await?; + // update subscriptions + for symbol in symbols { self.subscriptions - .entry(sub.symbol) - .and_modify(|flags| *flags |= sub_flags) - .or_insert(sub_flags); + .entry(symbol) + .and_modify(|flags| *flags |= sub_types) + .or_insert(sub_types); } Ok(()) @@ -319,39 +412,158 @@ impl Core { ) -> Result<()> { tracing::debug!(symbols = ?symbols, sub_types = ?sub_types, "unsubscribe"); - let req = if !sub_types.is_all() { - UnsubscribeRequest { - symbol: symbols, - sub_type: sub_types.into(), - unsub_all: false, + // send requests + let mut st_group: HashMap> = HashMap::new(); + + for symbol in &symbols { + let mut st = sub_types; + if self + .store + .securities + .get(symbol) + .map(|data| !data.candlesticks.is_empty()) + .unwrap_or_default() + { + st.remove(SubFlags::TRADE); } - } else { - UnsubscribeRequest { - symbol: symbols, - sub_type: vec![], - unsub_all: true, + if !st.is_empty() { + st_group.entry(st).or_default().push(symbol.as_ref()); } - }; + } - let resp: SubscriptionResponse = self - .ws_cli - .request(cmd_code::UNSUBSCRIBE, None, req) - .await?; + let requests = st_group + .iter() + .map(|(st, symbols)| UnsubscribeRequest { + symbol: symbols.iter().map(ToString::to_string).collect(), + sub_type: (*st).into(), + unsub_all: false, + }) + .collect::>(); - let mut remove_symbols = Vec::new(); + for req in requests { + self.ws_cli + .request(cmd_code::UNSUBSCRIBE, None, req) + .await?; + } - for sub in resp.sub_list { - let sub_flags: SubFlags = sub.sub_type.into(); - if let Some(cur_flags) = self.subscriptions.get_mut(&sub.symbol) { - *cur_flags &= !sub_flags; + // update subscriptions + let mut remove_symbols = Vec::new(); + for symbol in &symbols { + if let Some(cur_flags) = self.subscriptions.get_mut(symbol) { + *cur_flags &= !sub_types; if cur_flags.is_empty() { - remove_symbols.push(sub.symbol.clone()); + remove_symbols.push(symbol); } } } for symbol in remove_symbols { - self.subscriptions.remove(&symbol); + self.subscriptions.remove(symbol); + } + Ok(()) + } + + async fn handle_subscribe_candlesticks( + &mut self, + symbol: String, + period: Period, + ) -> Result<()> { + if self + .store + .securities + .get(&symbol) + .map(|data| !data.candlesticks.contains_key(&period)) + .unwrap_or_default() + { + return Ok(()); + } + + // pull candlesticks + let resp: SecurityCandlestickResponse = self + .ws_cli + .request( + cmd_code::GET_SECURITY_CANDLESTICKS, + None, + SecurityCandlestickRequest { + symbol: symbol.clone(), + period: period.into(), + count: 1000, + adjust_type: AdjustType::NoAdjust.into(), + }, + ) + .await?; + *self + .store + .securities + .entry(symbol.clone()) + .or_default() + .candlesticks + .entry(period) + .or_default() = resp + .candlesticks + .into_iter() + .map(TryInto::try_into) + .collect::>>()?; + + // subscribe trades + if self + .subscriptions + .get(&symbol) + .copied() + .unwrap_or_else(SubFlags::empty) + .contains(SubFlags::TRADE) + { + return Ok(()); + } + + let req = SubscribeRequest { + symbol: vec![symbol], + sub_type: SubFlags::TRADE.into(), + is_first_push: false, + }; + self.ws_cli.request(cmd_code::SUBSCRIBE, None, req).await?; + + Ok(()) + } + + async fn handle_unsubscribe_candlesticks( + &mut self, + symbol: String, + period: Period, + ) -> Result<()> { + let mut unsubscribe_trades = false; + + if let Some(periods) = self + .store + .securities + .get_mut(&symbol) + .map(|data| &mut data.candlesticks) + { + if periods.remove(&period).is_some() + && periods.is_empty() + && !self + .subscriptions + .get(&symbol) + .copied() + .unwrap_or_else(SubFlags::empty) + .contains(SubFlags::TRADE) + { + unsubscribe_trades = true; + } + } + + if unsubscribe_trades { + self.ws_cli + .request( + cmd_code::UNSUBSCRIBE, + None, + UnsubscribeRequest { + symbol: vec![symbol], + sub_type: SubFlags::TRADE.into(), + unsub_all: false, + }, + ) + .await?; } Ok(()) @@ -363,6 +575,13 @@ impl Core { .map(|(symbol, sub_flags)| Subscription { symbol: symbol.clone(), sub_types: *sub_flags, + candlesticks: self + .store + .securities + .get(symbol) + .map(|data| &data.candlesticks) + .map(|periods| periods.keys().copied().collect()) + .unwrap_or_default(), }) .collect() } @@ -404,6 +623,98 @@ impl Core { async fn handle_push(&mut self, command_code: u8, body: Vec) -> Result<()> { match PushEvent::parse(command_code, &body) { Ok(mut event) => { + if let PushEventDetail::Trade(trades) = &event.detail { + // merge candlesticks + let market = + get_market_from_symbol(&event.symbol).and_then(|market| match market { + "HK" => Some(longbridge_candlesticks::Market::HK), + "US" => Some(longbridge_candlesticks::Market::US), + _ => None, + }); + if let Some(market) = market { + if let Some(periods) = self + .store + .securities + .get_mut(&event.symbol) + .map(|data| &mut data.candlesticks) + { + for (period, candlesticks) in periods { + let prev = + candlesticks.last().map(|candlestick| (*candlestick).into()); + let period2 = match period { + Period::UnknownPeriod => unreachable!(), + Period::OneMinute => longbridge_candlesticks::Period::Min_1, + Period::FiveMinute => longbridge_candlesticks::Period::Min_5, + Period::FifteenMinute => { + longbridge_candlesticks::Period::Min_15 + } + Period::ThirtyMinute => longbridge_candlesticks::Period::Min_30, + Period::SixtyMinute => longbridge_candlesticks::Period::Min_60, + Period::Day => longbridge_candlesticks::Period::Day, + Period::Week => longbridge_candlesticks::Period::Week, + Period::Month => longbridge_candlesticks::Period::Month, + Period::Year => longbridge_candlesticks::Period::Year, + }; + + let merger = longbridge_candlesticks::Merger::new( + market, + period2, + self.current_trade_days.half_days(market), + ); + + for trade in &trades.trades { + if trade.trade_session != TradeSession::NormalTrade { + continue; + } + + let res = merger.merge( + Type::Normal, + prev.as_ref(), + longbridge_candlesticks::Trade { + time: trade.timestamp, + price: trade.price, + volume: trade.volume, + trade_type: &trade.trade_type, + }, + ); + let candlestick = match res { + UpdateAction::UpdateLast(candlestick) => { + let candlestick = candlestick.into(); + *candlesticks.last_mut().unwrap() = candlestick; + Some(candlestick) + } + UpdateAction::AppendNew(candlestick) => { + let candlestick = candlestick.into(); + candlesticks.push(candlestick); + Some(candlestick) + } + UpdateAction::None => None, + }; + if let Some(candlestick) = candlestick { + let _ = self.push_tx.send(PushEvent { + sequence: 0, + symbol: event.symbol.clone(), + detail: PushEventDetail::Candlestick(PushCandlestick { + period: *period, + candlestick, + }), + }); + } + } + } + } + } + + if !self + .subscriptions + .get(&event.symbol) + .map(|sub_flags| sub_flags.contains(SubFlags::TRADE)) + .unwrap_or_default() + { + return Ok(()); + } + } + self.store.handle_push(&mut event); let _ = self.push_tx.send(event); } @@ -467,4 +778,57 @@ impl Core { } result } + + fn handle_get_realtime_candlesticks( + &self, + symbol: String, + period: Period, + count: usize, + ) -> Vec { + self.store + .securities + .get(&symbol) + .map(|data| &data.candlesticks) + .and_then(|periods| periods.get(&period)) + .map(|candlesticks| { + let candlesticks = if candlesticks.len() >= count { + &candlesticks[candlesticks.len() - count..] + } else { + candlesticks + }; + candlesticks.to_vec() + }) + .unwrap_or_default() + } +} + +async fn fetch_current_trade_days(cli: &WsClient) -> Result { + let mut days = CurrentTradeDays::default(); + let begin_day = OffsetDateTime::now_utc().date() - time::Duration::days(1); + let end_day = begin_day + time::Duration::days(30); + + for market in [Market::HK, Market::US] { + let resp = cli + .request::<_, MarketTradeDayResponse>( + cmd_code::GET_TRADING_DAYS, + None, + MarketTradeDayRequest { + market: market.to_string(), + beg_day: format_date(begin_day), + end_day: format_date(end_day), + }, + ) + .await?; + days.half_days.insert( + market, + resp.half_trade_day + .iter() + .map(|value| { + parse_date(value).map_err(|err| Error::parse_field_error("half_trade_day", err)) + }) + .collect::>>()?, + ); + } + + Ok(days) } diff --git a/rust/src/quote/mod.rs b/rust/src/quote/mod.rs index e7487c1bfb..d48de1d719 100644 --- a/rust/src/quote/mod.rs +++ b/rust/src/quote/mod.rs @@ -12,7 +12,9 @@ mod utils; pub use context::QuoteContext; pub use longbridge_proto::quote::{AdjustType, Period, TradeSession, TradeStatus}; -pub use push_types::{PushBrokers, PushDepth, PushEvent, PushEventDetail, PushQuote, PushTrades}; +pub use push_types::{ + PushBrokers, PushCandlestick, PushDepth, PushEvent, PushEventDetail, PushQuote, PushTrades, +}; pub use sub_flags::SubFlags; pub use types::{ Brokers, Candlestick, CapitalDistribution, CapitalDistributionResponse, CapitalFlowLine, Depth, diff --git a/rust/src/quote/push_types.rs b/rust/src/quote/push_types.rs index 542b1ba3bc..e85fde85cf 100644 --- a/rust/src/quote/push_types.rs +++ b/rust/src/quote/push_types.rs @@ -1,10 +1,10 @@ -use longbridge_proto::quote::{self, TradeSession, TradeStatus}; +use longbridge_proto::quote::{self, Period, TradeSession, TradeStatus}; use prost::Message; use rust_decimal::Decimal; use time::OffsetDateTime; use crate::{ - quote::{cmd_code, Brokers, Depth, Trade}, + quote::{cmd_code, Brokers, Candlestick, Depth, Trade}, Error, Result, }; @@ -72,6 +72,15 @@ pub struct PushTrades { pub trades: Vec, } +/// Candlestick updated message +#[derive(Debug, Copy, Clone)] +pub struct PushCandlestick { + /// Period type + pub period: Period, + /// Candlestick + pub candlestick: Candlestick, +} + /// Push event detail #[derive(Debug)] pub enum PushEventDetail { @@ -83,14 +92,16 @@ pub enum PushEventDetail { Brokers(PushBrokers), /// Trade Trade(PushTrades), + /// Candlestick + Candlestick(PushCandlestick), } /// Push event #[derive(Debug)] pub struct PushEvent { + pub(crate) sequence: i64, /// Security code pub symbol: String, - pub(crate) sequence: i64, /// Event detail pub detail: PushEventDetail, } diff --git a/rust/src/quote/store.rs b/rust/src/quote/store.rs index 1bef24b551..42950578e0 100644 --- a/rust/src/quote/store.rs +++ b/rust/src/quote/store.rs @@ -1,8 +1,10 @@ use std::collections::HashMap; +use longbridge_proto::quote::Period; + use crate::quote::{ push_types::{PushEventDetail, PushQuote}, - Brokers, Depth, PushBrokers, PushDepth, PushEvent, PushTrades, Trade, + Brokers, Candlestick, Depth, PushBrokers, PushDepth, PushEvent, PushTrades, Trade, }; macro_rules! check_sequence { @@ -49,6 +51,8 @@ pub(crate) struct SecuritiesData { pub(crate) trades_sequence: i64, pub(crate) trades: Vec, + + pub(crate) candlesticks: HashMap>, } #[derive(Debug, Default)] @@ -77,6 +81,7 @@ impl Store { check_sequence!(data.trades_sequence, event.sequence); merge_trades(data, trade); } + PushEventDetail::Candlestick(_) => {} } } } diff --git a/rust/src/quote/types.rs b/rust/src/quote/types.rs index 8c2a9c4512..fe1c1edfec 100644 --- a/rust/src/quote/types.rs +++ b/rust/src/quote/types.rs @@ -1,4 +1,4 @@ -use longbridge_proto::quote::{self, TradeSession, TradeStatus}; +use longbridge_proto::quote::{self, Period, TradeSession, TradeStatus}; use num_enum::{FromPrimitive, IntoPrimitive, TryFromPrimitive}; use rust_decimal::Decimal; use strum_macros::EnumString; @@ -16,6 +16,8 @@ pub struct Subscription { pub symbol: String, /// Subscription flags pub sub_types: SubFlags, + /// Candlesticks + pub candlesticks: Vec, } /// Depth @@ -620,7 +622,7 @@ impl TryFrom for IntradayLine { } /// Candlestick -#[derive(Debug, Clone)] +#[derive(Debug, Copy, Clone)] pub struct Candlestick { /// Close price pub close: Decimal, @@ -655,6 +657,36 @@ impl TryFrom for Candlestick { } } +impl From for Candlestick { + #[inline] + fn from(candlestick: longbridge_candlesticks::Candlestick) -> Self { + Self { + close: candlestick.close, + open: candlestick.open, + low: candlestick.low, + high: candlestick.high, + volume: candlestick.volume, + turnover: candlestick.turnover, + timestamp: candlestick.time, + } + } +} + +impl From for longbridge_candlesticks::Candlestick { + #[inline] + fn from(candlestick: Candlestick) -> Self { + Self { + time: candlestick.timestamp, + open: candlestick.open, + high: candlestick.high, + low: candlestick.low, + close: candlestick.close, + volume: candlestick.volume, + turnover: candlestick.turnover, + } + } +} + /// Strike price info #[derive(Debug, Clone)] pub struct StrikePriceInfo { diff --git a/rust/src/quote/utils.rs b/rust/src/quote/utils.rs index 9123605d27..30f93629d2 100644 --- a/rust/src/quote/utils.rs +++ b/rust/src/quote/utils.rs @@ -12,3 +12,8 @@ pub(crate) fn format_date(date: Date) -> String { date.format(time::macros::format_description!("[year][month][day]")) .unwrap() } + +#[inline] +pub(crate) fn get_market_from_symbol(symbol: &str) -> Option<&str> { + symbol.find('.').map(|idx| &symbol[idx + 1..]) +} diff --git a/rust/src/trade/requests/get_today_orders.rs b/rust/src/trade/requests/get_today_orders.rs index 953767ca9d..d60c8ffd3b 100644 --- a/rust/src/trade/requests/get_today_orders.rs +++ b/rust/src/trade/requests/get_today_orders.rs @@ -16,6 +16,8 @@ pub struct GetTodayOrdersOptions { side: Option, #[serde(skip_serializing_if = "Option::is_none")] market: Option, + #[serde(skip_serializing_if = "Option::is_none")] + order_id: Option, } impl GetTodayOrdersOptions { @@ -64,4 +66,14 @@ impl GetTodayOrdersOptions { ..self } } + + /// Set the order id + #[inline] + #[must_use] + pub fn order_id(self, order_id: String) -> Self { + Self { + order_id: Some(order_id), + ..self + } + } } From 49eb6fccdbe8fcb6ca3207fea2e52612771fb50e Mon Sep 17 00:00:00 2001 From: Sunli Date: Fri, 24 Jun 2022 17:33:10 +0800 Subject: [PATCH 087/567] Release 0.2.21 longbridge@0.2.21 longbridge-candlesticks@0.2.21 longbridge-httpcli@0.2.21 longbridge-java@0.2.21 longbridge-java-macros@0.2.21 longbridge-nodejs@0.2.21 longbridge-nodejs-macros@0.2.21 longbridge-proto@0.2.21 longbridge-python@0.2.21 longbridge-python-macros@0.2.21 longbridge-wscli@0.2.21 Generated by cargo-workspaces --- java/Cargo.toml | 2 +- java/crates/macros/Cargo.toml | 2 +- nodejs/Cargo.toml | 2 +- nodejs/crates/macros/Cargo.toml | 2 +- python/Cargo.toml | 2 +- python/Makefile.toml | 2 +- python/crates/macros/Cargo.toml | 2 +- rust/Cargo.toml | 10 +++++----- rust/crates/candlesticks/Cargo.toml | 4 ++-- rust/crates/httpclient/Cargo.toml | 2 +- rust/crates/proto/Cargo.toml | 2 +- rust/crates/wsclient/Cargo.toml | 4 ++-- 12 files changed, 18 insertions(+), 18 deletions(-) diff --git a/java/Cargo.toml b/java/Cargo.toml index f22185b65c..f91f1a466a 100644 --- a/java/Cargo.toml +++ b/java/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge-java" -version = "0.2.20" +version = "0.2.21" [lib] crate-type = ["cdylib"] diff --git a/java/crates/macros/Cargo.toml b/java/crates/macros/Cargo.toml index fb0f5a8767..c7f5080c0c 100644 --- a/java/crates/macros/Cargo.toml +++ b/java/crates/macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "longbridge-java-macros" -version = "0.2.20" +version = "0.2.21" edition = "2021" [lib] diff --git a/nodejs/Cargo.toml b/nodejs/Cargo.toml index 4f3dca0b0e..d0b216245e 100644 --- a/nodejs/Cargo.toml +++ b/nodejs/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge-nodejs" -version = "0.2.20" +version = "0.2.21" [lib] crate-type = ["cdylib"] diff --git a/nodejs/crates/macros/Cargo.toml b/nodejs/crates/macros/Cargo.toml index 7325209b83..0d51bae649 100644 --- a/nodejs/crates/macros/Cargo.toml +++ b/nodejs/crates/macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "longbridge-nodejs-macros" -version = "0.2.20" +version = "0.2.21" edition = "2021" [lib] diff --git a/python/Cargo.toml b/python/Cargo.toml index 50b313d4d4..e9cdef65b3 100644 --- a/python/Cargo.toml +++ b/python/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge-python" -version = "0.2.20" +version = "0.2.21" description = "Longbridge OpenAPI SDK for Python" homepage = "https://open.longbridgeapp.com/en/" readme = "README.md" diff --git a/python/Makefile.toml b/python/Makefile.toml index 293bba1836..dfacd14c16 100644 --- a/python/Makefile.toml +++ b/python/Makefile.toml @@ -12,7 +12,7 @@ args = ["install", "maturin>=0.12,<0.13"] command = "pip" args = [ "install", - "target/wheels/longbridge-0.2.20-cp310-none-win_amd64.whl", + "target/wheels/longbridge-0.2.21-cp310-none-win_amd64.whl", "-I", ] dependencies = ["python"] diff --git a/python/crates/macros/Cargo.toml b/python/crates/macros/Cargo.toml index c09809fb70..4ca6df854e 100644 --- a/python/crates/macros/Cargo.toml +++ b/python/crates/macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "longbridge-python-macros" -version = "0.2.20" +version = "0.2.21" edition = "2021" [lib] diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 5dd1d236d4..71b492ebf6 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge" -version = "0.2.20" +version = "0.2.21" description = "Longbridge OpenAPI SDK for Rust" homepage = "https://open.longbridgeapp.com/en/" readme = "README.md" @@ -14,10 +14,10 @@ categories = ["api-bindings"] blocking = ["flume"] [dependencies] -longbridge-wscli = { path = "crates/wsclient", version = "0.2.20" } -longbridge-httpcli = { path = "crates/httpclient", version = "0.2.20" } -longbridge-proto = { path = "crates/proto", version = "0.2.20" } -longbridge-candlesticks = { path = "crates/candlesticks", version = "0.2.20" } +longbridge-wscli = { path = "crates/wsclient", version = "0.2.21" } +longbridge-httpcli = { path = "crates/httpclient", version = "0.2.21" } +longbridge-proto = { path = "crates/proto", version = "0.2.21" } +longbridge-candlesticks = { path = "crates/candlesticks", version = "0.2.21" } tokio = { version = "1.18.2", features = [ "time", diff --git a/rust/crates/candlesticks/Cargo.toml b/rust/crates/candlesticks/Cargo.toml index 4178025205..f29f04fbc9 100644 --- a/rust/crates/candlesticks/Cargo.toml +++ b/rust/crates/candlesticks/Cargo.toml @@ -1,8 +1,8 @@ [package] edition = "2021" name = "longbridge-candlesticks" -version = "0.2.20" -description = "Longbridge candlesticks utils for Rust" +version = "0.2.21" +description = "Longbridge candlestick utils for Rust" license = "MIT OR Apache-2.0" [dependencies] diff --git a/rust/crates/httpclient/Cargo.toml b/rust/crates/httpclient/Cargo.toml index 2b27a1fcca..53ced37c5d 100644 --- a/rust/crates/httpclient/Cargo.toml +++ b/rust/crates/httpclient/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge-httpcli" -version = "0.2.20" +version = "0.2.21" description = "Longbridge HTTP SDK for Rust" license = "MIT OR Apache-2.0" diff --git a/rust/crates/proto/Cargo.toml b/rust/crates/proto/Cargo.toml index f0f23adbec..374fd49ffb 100644 --- a/rust/crates/proto/Cargo.toml +++ b/rust/crates/proto/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge-proto" -version = "0.2.20" +version = "0.2.21" description = "Longbridge Protocol" license = "MIT OR Apache-2.0" diff --git a/rust/crates/wsclient/Cargo.toml b/rust/crates/wsclient/Cargo.toml index 36df1c6a68..982d1e636b 100644 --- a/rust/crates/wsclient/Cargo.toml +++ b/rust/crates/wsclient/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "longbridge-wscli" -version = "0.2.20" +version = "0.2.21" edition = "2021" description = "Longbridge Websocket SDK for Rust" license = "MIT OR Apache-2.0" [dependencies] -longbridge-proto = { path = "../proto", version = "0.2.20" } +longbridge-proto = { path = "../proto", version = "0.2.21" } tokio = { version = "1.18.2", features = [ "time", From 39096c870a74fb5741ae7eed237ce466ad13bd66 Mon Sep 17 00:00:00 2001 From: Sunli Date: Fri, 24 Jun 2022 18:50:35 +0800 Subject: [PATCH 088/567] Update release.yml --- .github/workflows/release.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3c925d93d4..9c5b4eb764 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,6 +21,9 @@ jobs: - name: longbridge-wscli registryName: longbridge-wscli path: rust/crates/wsclient + - name: longbridge-candlesticks + registryName: longbridge-candlesticks + path: rust/crates/candlesticks - name: longbridge registryName: longbridge path: rust From 1964793ab02cd79fa0ae2fb886e736c0a60a1522 Mon Sep 17 00:00:00 2001 From: Sunli Date: Sat, 25 Jun 2022 10:14:48 +0800 Subject: [PATCH 089/567] Delete Cargo.lock --- examples/rust/Cargo.lock | 1701 -------------------------------------- 1 file changed, 1701 deletions(-) delete mode 100644 examples/rust/Cargo.lock diff --git a/examples/rust/Cargo.lock b/examples/rust/Cargo.lock deleted file mode 100644 index 7413ff98b8..0000000000 --- a/examples/rust/Cargo.lock +++ /dev/null @@ -1,1701 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "account_asset" -version = "0.1.0" -dependencies = [ - "longbridge", - "tokio", -] - -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - -[[package]] -name = "anyhow" -version = "1.0.57" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc" - -[[package]] -name = "arrayvec" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" - -[[package]] -name = "autocfg" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - -[[package]] -name = "base64" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "block-buffer" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" -dependencies = [ - "generic-array", -] - -[[package]] -name = "bumpalo" -version = "3.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" - -[[package]] -name = "byteorder" -version = "1.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" - -[[package]] -name = "bytes" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" - -[[package]] -name = "cc" -version = "1.0.73" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "cmake" -version = "0.1.48" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8ad8cef104ac57b68b89df3208164d228503abbdce70f6880ffa3d970e7443a" -dependencies = [ - "cc", -] - -[[package]] -name = "cpufeatures" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" -dependencies = [ - "libc", -] - -[[package]] -name = "crc32fast" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "crypto-common" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "digest" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" -dependencies = [ - "block-buffer", - "crypto-common", - "subtle", -] - -[[package]] -name = "dotenv" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" - -[[package]] -name = "either" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" - -[[package]] -name = "encoding_rs" -version = "0.8.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "fastrand" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" -dependencies = [ - "instant", -] - -[[package]] -name = "fixedbitset" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "279fb028e20b3c4c320317955b77c5e0c9701f05a1d309905d6fc702cdc5053e" - -[[package]] -name = "flate2" -version = "1.0.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" -dependencies = [ - "crc32fast", - "miniz_oxide", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "form_urlencoded" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" -dependencies = [ - "matches", - "percent-encoding", -] - -[[package]] -name = "futures-channel" -version = "0.3.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" -dependencies = [ - "futures-core", -] - -[[package]] -name = "futures-core" -version = "0.3.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" - -[[package]] -name = "futures-macro" -version = "0.3.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "futures-sink" -version = "0.3.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" - -[[package]] -name = "futures-task" -version = "0.3.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" - -[[package]] -name = "futures-util" -version = "0.3.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" -dependencies = [ - "futures-core", - "futures-macro", - "futures-sink", - "futures-task", - "pin-project-lite", - "pin-utils", - "slab", -] - -[[package]] -name = "generic-array" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "getrandom" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad" -dependencies = [ - "cfg-if", - "libc", - "wasi 0.10.2+wasi-snapshot-preview1", -] - -[[package]] -name = "h2" -version = "0.3.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37a82c6d637fc9515a4694bbf1cb2457b79d81ce52b3108bdeea58b07dd34a57" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "hashbrown" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" - -[[package]] -name = "heck" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" - -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - -[[package]] -name = "hmac" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" -dependencies = [ - "digest", -] - -[[package]] -name = "http" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http-body" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" -dependencies = [ - "bytes", - "http", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c" - -[[package]] -name = "httpdate" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" - -[[package]] -name = "hyper" -version = "0.14.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42dc3c131584288d375f2d07f822b0cb012d8c6fb899a5b9fdb3cb7eb9b6004f" -dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", - "want", -] - -[[package]] -name = "hyper-rustls" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d87c48c02e0dc5e3b849a2041db3029fd066650f8f717c07bf8ed78ccb895cac" -dependencies = [ - "http", - "hyper", - "rustls", - "tokio", - "tokio-rustls", -] - -[[package]] -name = "idna" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" -dependencies = [ - "matches", - "unicode-bidi", - "unicode-normalization", -] - -[[package]] -name = "indexmap" -version = "1.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6012d540c5baa3589337a98ce73408de9b5a25ec9fc2c6fd6be8f0d39e0ca5a" -dependencies = [ - "autocfg", - "hashbrown", -] - -[[package]] -name = "instant" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "ipnet" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" - -[[package]] -name = "itertools" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" -dependencies = [ - "either", -] - -[[package]] -name = "itoa" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" - -[[package]] -name = "js-sys" -version = "0.3.57" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "671a26f820db17c2a2750743f1dd03bafd15b98c9f30c7c2628c024c05d73397" -dependencies = [ - "wasm-bindgen", -] - -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - -[[package]] -name = "libc" -version = "0.2.126" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" - -[[package]] -name = "lock_api" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" -dependencies = [ - "autocfg", - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "longbridge" -version = "0.2.20" -dependencies = [ - "bitflags", - "dotenv", - "futures-util", - "longbridge-candlesticks", - "longbridge-httpcli", - "longbridge-proto", - "longbridge-wscli", - "num_enum", - "prost", - "rust_decimal", - "serde", - "serde_json", - "strum", - "strum_macros", - "thiserror", - "time", - "tokio", - "tracing", -] - -[[package]] -name = "longbridge-candlesticks" -version = "0.2.20" -dependencies = [ - "bitflags", - "rust_decimal", - "time", - "time-tz", -] - -[[package]] -name = "longbridge-httpcli" -version = "0.2.20" -dependencies = [ - "futures-util", - "hmac", - "parking_lot", - "percent-encoding", - "reqwest", - "serde", - "serde_json", - "sha1", - "sha2", - "thiserror", - "tokio", - "tracing", -] - -[[package]] -name = "longbridge-proto" -version = "0.2.20" -dependencies = [ - "prost", - "prost-build", -] - -[[package]] -name = "longbridge-wscli" -version = "0.2.20" -dependencies = [ - "byteorder", - "flate2", - "futures-util", - "longbridge-proto", - "num_enum", - "prost", - "thiserror", - "tokio", - "tokio-tungstenite", - "url", -] - -[[package]] -name = "matches" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" - -[[package]] -name = "memchr" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" - -[[package]] -name = "mime" -version = "0.3.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" - -[[package]] -name = "miniz_oxide" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc" -dependencies = [ - "adler", -] - -[[package]] -name = "mio" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "713d550d9b44d89174e066b7a6217ae06234c10cb47819a88290d2b353c31799" -dependencies = [ - "libc", - "log", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys", -] - -[[package]] -name = "multimap" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" - -[[package]] -name = "num-traits" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" -dependencies = [ - "autocfg", -] - -[[package]] -name = "num_cpus" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" -dependencies = [ - "hermit-abi", - "libc", -] - -[[package]] -name = "num_enum" -version = "0.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf5395665662ef45796a4ff5486c5d41d29e0c09640af4c5f17fd94ee2c119c9" -dependencies = [ - "num_enum_derive", -] - -[[package]] -name = "num_enum_derive" -version = "0.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b0498641e53dd6ac1a4f22547548caa6864cc4933784319cd1775271c5a46ce" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "num_threads" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" -dependencies = [ - "libc", -] - -[[package]] -name = "once_cell" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" - -[[package]] -name = "parking_lot" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-sys", -] - -[[package]] -name = "parse-zoneinfo" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c705f256449c60da65e11ff6626e0c16a0a0b96aaa348de61376b249bc340f41" -dependencies = [ - "regex", -] - -[[package]] -name = "percent-encoding" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" - -[[package]] -name = "petgraph" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d5014253a1331579ce62aa67443b4a658c5e7dd03d4bc6d302b94474888143" -dependencies = [ - "fixedbitset", - "indexmap", -] - -[[package]] -name = "phf" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" -dependencies = [ - "phf_shared", -] - -[[package]] -name = "phf_codegen" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd" -dependencies = [ - "phf_generator", - "phf_shared", -] - -[[package]] -name = "phf_generator" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" -dependencies = [ - "phf_shared", - "rand", -] - -[[package]] -name = "phf_shared" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" -dependencies = [ - "siphasher", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "ppv-lite86" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" - -[[package]] -name = "proc-macro-crate" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" -dependencies = [ - "thiserror", - "toml", -] - -[[package]] -name = "proc-macro2" -version = "1.0.39" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "prost" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71adf41db68aa0daaefc69bb30bcd68ded9b9abaad5d1fbb6304c4fb390e083e" -dependencies = [ - "bytes", - "prost-derive", -] - -[[package]] -name = "prost-build" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae5a4388762d5815a9fc0dea33c56b021cdc8dde0c55e0c9ca57197254b0cab" -dependencies = [ - "bytes", - "cfg-if", - "cmake", - "heck", - "itertools", - "lazy_static", - "log", - "multimap", - "petgraph", - "prost", - "prost-types", - "regex", - "tempfile", - "which", -] - -[[package]] -name = "prost-derive" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b670f45da57fb8542ebdbb6105a925fe571b67f9e7ed9f47a06a84e72b4e7cc" -dependencies = [ - "anyhow", - "itertools", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "prost-types" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d0a014229361011dc8e69c8a1ec6c2e8d0f2af7c91e3ea3f5b2170298461e68" -dependencies = [ - "bytes", - "prost", -] - -[[package]] -name = "quote" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha", - "rand_core", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core", -] - -[[package]] -name = "rand_core" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" -dependencies = [ - "getrandom", -] - -[[package]] -name = "redox_syscall" -version = "0.2.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" -dependencies = [ - "bitflags", -] - -[[package]] -name = "regex" -version = "1.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1" -dependencies = [ - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.6.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64" - -[[package]] -name = "remove_dir_all" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" -dependencies = [ - "winapi", -] - -[[package]] -name = "reqwest" -version = "0.11.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46a1f7aa4f35e5e8b4160449f51afc758f0ce6454315a9fa7d0d113e958c41eb" -dependencies = [ - "base64", - "bytes", - "encoding_rs", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "hyper", - "hyper-rustls", - "ipnet", - "js-sys", - "lazy_static", - "log", - "mime", - "percent-encoding", - "pin-project-lite", - "rustls", - "rustls-pemfile", - "serde", - "serde_json", - "serde_urlencoded", - "tokio", - "tokio-rustls", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "webpki-roots", - "winreg", -] - -[[package]] -name = "ring" -version = "0.16.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" -dependencies = [ - "cc", - "libc", - "once_cell", - "spin", - "untrusted", - "web-sys", - "winapi", -] - -[[package]] -name = "rust_decimal" -version = "1.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34a3bb58e85333f1ab191bf979104b586ebd77475bc6681882825f4532dfe87c" -dependencies = [ - "arrayvec", - "num-traits", - "serde", -] - -[[package]] -name = "rustls" -version = "0.20.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aab8ee6c7097ed6057f43c187a62418d0c05a4bd5f18b3571db50ee0f9ce033" -dependencies = [ - "log", - "ring", - "sct", - "webpki", -] - -[[package]] -name = "rustls-pemfile" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ee86d63972a7c661d1536fefe8c3c8407321c3df668891286de28abcd087360" -dependencies = [ - "base64", -] - -[[package]] -name = "rustversion" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" - -[[package]] -name = "ryu" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" - -[[package]] -name = "scopeguard" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" - -[[package]] -name = "sct" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "serde" -version = "1.0.137" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde-xml-rs" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65162e9059be2f6a3421ebbb4fef3e74b7d9e7c60c50a0e292c6239f19f1edfa" -dependencies = [ - "log", - "serde", - "thiserror", - "xml-rs", -] - -[[package]] -name = "serde_derive" -version = "1.0.137" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.81" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" -dependencies = [ - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "sha-1" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "sha1" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c77f4e7f65455545c2153c1253d25056825e77ee2533f0e41deb65a93a34852f" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "sha2" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "siphasher" -version = "0.3.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" - -[[package]] -name = "slab" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" - -[[package]] -name = "smallvec" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" - -[[package]] -name = "socket2" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "spin" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" - -[[package]] -name = "strum" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e96acfc1b70604b8b2f1ffa4c57e59176c7dbb05d556c71ecd2f5498a1dee7f8" - -[[package]] -name = "strum_macros" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6878079b17446e4d3eba6192bb0a2950d5b14f0ed8424b852310e5a94345d0ef" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "rustversion", - "syn", -] - -[[package]] -name = "submit_order" -version = "0.1.0" -dependencies = [ - "longbridge", - "tokio", -] - -[[package]] -name = "subscribe_candlesticks" -version = "0.1.0" -dependencies = [ - "longbridge", - "tokio", -] - -[[package]] -name = "subscribe_quote" -version = "0.1.0" -dependencies = [ - "longbridge", - "tokio", -] - -[[package]] -name = "subtle" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" - -[[package]] -name = "syn" -version = "1.0.96" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0748dd251e24453cb8717f0354206b91557e4ec8703673a4b30208f2abaf1ebf" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "tempfile" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" -dependencies = [ - "cfg-if", - "fastrand", - "libc", - "redox_syscall", - "remove_dir_all", - "winapi", -] - -[[package]] -name = "thiserror" -version = "1.0.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "time" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72c91f41dcb2f096c05f0873d667dceec1087ce5bcf984ec8ffb19acddbb3217" -dependencies = [ - "itoa", - "libc", - "num_threads", - "time-macros", -] - -[[package]] -name = "time-macros" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" - -[[package]] -name = "time-tz" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "980bd5c9ec52d862803a716b6f3913881910e96124a618a9a0679a984d5c361d" -dependencies = [ - "cfg-if", - "parse-zoneinfo", - "phf", - "phf_codegen", - "serde", - "serde-xml-rs", - "time", -] - -[[package]] -name = "tinyvec" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" - -[[package]] -name = "today_orders" -version = "0.1.0" -dependencies = [ - "longbridge", - "tokio", -] - -[[package]] -name = "tokio" -version = "1.19.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c51a52ed6686dd62c320f9b89299e9dfb46f730c7a48e635c19f21d116cb1439" -dependencies = [ - "bytes", - "libc", - "memchr", - "mio", - "num_cpus", - "once_cell", - "pin-project-lite", - "socket2", - "tokio-macros", - "winapi", -] - -[[package]] -name = "tokio-macros" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tokio-rustls" -version = "0.23.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" -dependencies = [ - "rustls", - "tokio", - "webpki", -] - -[[package]] -name = "tokio-tungstenite" -version = "0.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06cda1232a49558c46f8a504d5b93101d42c0bf7f911f12a105ba48168f821ae" -dependencies = [ - "futures-util", - "log", - "rustls", - "tokio", - "tokio-rustls", - "tungstenite", - "webpki", - "webpki-roots", -] - -[[package]] -name = "tokio-util" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc463cd8deddc3770d20f9852143d50bf6094e640b485cb2e189a2099085ff45" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", - "tracing", -] - -[[package]] -name = "toml" -version = "0.5.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" -dependencies = [ - "serde", -] - -[[package]] -name = "tower-service" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" - -[[package]] -name = "tracing" -version = "0.1.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0ecdcb44a79f0fe9844f0c4f33a342cbcbb5117de8001e6ba0dc2351327d09" -dependencies = [ - "cfg-if", - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc6b8ad3567499f98a1db7a752b07a7c8c7c7c34c332ec00effb2b0027974b7c" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tracing-core" -version = "0.1.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f54c8ca710e81886d498c2fd3331b56c93aa248d49de2222ad2742247c60072f" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "try-lock" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" - -[[package]] -name = "tungstenite" -version = "0.17.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d96a2dea40e7570482f28eb57afbe42d97551905da6a9400acc5c328d24004f5" -dependencies = [ - "base64", - "byteorder", - "bytes", - "http", - "httparse", - "log", - "rand", - "rustls", - "sha-1", - "thiserror", - "url", - "utf-8", - "webpki", -] - -[[package]] -name = "typenum" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" - -[[package]] -name = "unicode-bidi" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" - -[[package]] -name = "unicode-ident" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" - -[[package]] -name = "unicode-normalization" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "untrusted" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" - -[[package]] -name = "url" -version = "2.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" -dependencies = [ - "form_urlencoded", - "idna", - "matches", - "percent-encoding", -] - -[[package]] -name = "utf-8" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" - -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - -[[package]] -name = "want" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" -dependencies = [ - "log", - "try-lock", -] - -[[package]] -name = "wasi" -version = "0.10.2+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "wasm-bindgen" -version = "0.2.80" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27370197c907c55e3f1a9fbe26f44e937fe6451368324e009cba39e139dc08ad" -dependencies = [ - "cfg-if", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.80" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53e04185bfa3a779273da532f5025e33398409573f348985af9a1cbf3774d3f4" -dependencies = [ - "bumpalo", - "lazy_static", - "log", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f741de44b75e14c35df886aff5f1eb73aa114fa5d4d00dcd37b5e01259bf3b2" -dependencies = [ - "cfg-if", - "js-sys", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.80" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17cae7ff784d7e83a2fe7611cfe766ecf034111b49deb850a3dc7699c08251f5" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.80" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99ec0dc7a4756fffc231aab1b9f2f578d23cd391390ab27f952ae0c9b3ece20b" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.80" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744" - -[[package]] -name = "web-sys" -version = "0.3.57" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b17e741662c70c8bd24ac5c5b18de314a2c26c32bf8346ee1e6f53de919c283" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "webpki" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "webpki-roots" -version = "0.22.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d8de8415c823c8abd270ad483c6feeac771fad964890779f9a8cb24fbbc1bf" -dependencies = [ - "webpki", -] - -[[package]] -name = "which" -version = "4.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c4fb54e6113b6a8772ee41c3404fb0301ac79604489467e0a9ce1f3e97c24ae" -dependencies = [ - "either", - "lazy_static", - "libc", -] - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows-sys" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" -dependencies = [ - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" - -[[package]] -name = "windows_i686_gnu" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" - -[[package]] -name = "windows_i686_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" - -[[package]] -name = "winreg" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" -dependencies = [ - "winapi", -] - -[[package]] -name = "xml-rs" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" From 913c500779919e6a7da9b61be54bcbd4613c9796 Mon Sep 17 00:00:00 2001 From: Sunli Date: Sat, 25 Jun 2022 23:23:43 +0800 Subject: [PATCH 090/567] Fixes unable to parse null as empty array in java sdk --- java/src/config.rs | 29 +++++++++-------- java/src/types/object_array.rs | 7 +++- java/test/Main.java | 10 ++---- rust/crates/httpclient/src/request.rs | 46 +++++++++++++++++++++++---- 4 files changed, 63 insertions(+), 29 deletions(-) diff --git a/java/src/config.rs b/java/src/config.rs index b3ca39955c..64bdead955 100644 --- a/java/src/config.rs +++ b/java/src/config.rs @@ -5,7 +5,7 @@ use jni::{ }; use longbridge::Config; -use crate::error::jni_result; +use crate::{error::jni_result, types::FromJValue}; #[no_mangle] pub extern "system" fn Java_com_longbridge_SdkNative_newConfig( @@ -19,22 +19,23 @@ pub extern "system" fn Java_com_longbridge_SdkNative_newConfig( trade_ws_url: JString, ) -> jlong { jni_result(&env, 0, || { - let mut config = Config::new( - env.get_string(app_key)?, - env.get_string(app_secret)?, - env.get_string(access_token)?, - ); + let app_key = String::from_jvalue(&env, app_key.into())?; + let app_secret = String::from_jvalue(&env, app_secret.into())?; + let access_token = String::from_jvalue(&env, access_token.into())?; + let http_url = >::from_jvalue(&env, http_url.into())?; + let quote_ws_url = >::from_jvalue(&env, quote_ws_url.into())?; + let trade_ws_url = >::from_jvalue(&env, trade_ws_url.into())?; - if !http_url.is_null() { - config = config.http_url(env.get_string(http_url)?); - } + let mut config = Config::new(app_key, app_secret, access_token); - if !quote_ws_url.is_null() { - config = config.quote_ws_url(env.get_string(quote_ws_url)?); + if let Some(http_url) = http_url { + config = config.http_url(http_url); } - - if !trade_ws_url.is_null() { - config = config.trade_ws_url(env.get_string(trade_ws_url)?); + if let Some(quote_ws_url) = quote_ws_url { + config = config.quote_ws_url(quote_ws_url); + } + if let Some(trade_ws_url) = trade_ws_url { + config = config.trade_ws_url(trade_ws_url); } Ok(Box::into_raw(Box::new(config)) as jlong) diff --git a/java/src/types/object_array.rs b/java/src/types/object_array.rs index 890c8b3ad8..bade08eb15 100644 --- a/java/src/types/object_array.rs +++ b/java/src/types/object_array.rs @@ -18,7 +18,12 @@ impl JSignature for ObjectArray { impl FromJValue for ObjectArray { fn from_jvalue(env: &JNIEnv, value: JValue) -> Result { - let array = value.l()?.into_inner(); + let obj = value.l()?; + if obj.is_null() { + return Ok(ObjectArray(Vec::new())); + } + + let array = obj.into_inner(); let len = env.get_array_length(array)?; let mut res = Vec::with_capacity(len as usize); diff --git a/java/test/Main.java b/java/test/Main.java index d5465824ca..10e2dfbe0f 100644 --- a/java/test/Main.java +++ b/java/test/Main.java @@ -5,13 +5,9 @@ class Main { public static void main(String[] args) throws Exception { try (Config config = Config.fromEnv(); TradeContext ctx = TradeContext.create(config).get()) { - SubmitOrderOptions opts = new SubmitOrderOptions("700.HK", - OrderType.LO, - OrderSide.Buy, - 200, - TimeInForceType.Day).setSubmittedPrice(new BigDecimal(50)); - SubmitOrderResponse resp = ctx.submitOrder(opts).get(); - System.out.println(resp); + GetHistoryOrdersOptions opts = new GetHistoryOrdersOptions(); + Order[] resp = ctx.getHistoryOrders(opts).get(); + System.out.println(resp[0].toString()); } } } diff --git a/rust/crates/httpclient/src/request.rs b/rust/crates/httpclient/src/request.rs index 07587303e0..eb9ead0f10 100644 --- a/rust/crates/httpclient/src/request.rs +++ b/rust/crates/httpclient/src/request.rs @@ -10,6 +10,9 @@ use crate::{ }; const REQUEST_TIMEOUT: Duration = Duration::from_secs(30); +const RETRY_COUNT: usize = 5; +const RETRY_INITIAL_DELAY: Duration = Duration::from_millis(100); +const RETRY_FACTOR: f32 = 2.0; #[derive(Deserialize)] struct OpenApiResponse { @@ -97,9 +100,7 @@ where Q: Serialize + Send + 'static, R: DeserializeOwned + Send + 'static, { - /// Send request and get the response - #[tracing::instrument(level = "debug", skip(self))] - pub async fn send(self) -> HttpClientResult { + async fn do_send(&self) -> HttpClientResult { let HttpClient { http_cli, config } = &self.client; let now = Timestamp::now(); let app_key_value = @@ -108,22 +109,25 @@ where .map_err(|_| HttpClientError::InvalidAccessToken)?; let mut request_builder = http_cli - .request(self.method, &format!("{}{}", config.http_url, self.path)) + .request( + self.method.clone(), + &format!("{}{}", config.http_url, self.path), + ) .header("X-Api-Key", app_key_value) .header("Authorization", access_token_value) .header("X-Timestamp", now.to_string()) .header("Content-Type", "application/json; charset=utf-8"); // set the request body - if let Some(body) = self.body { + if let Some(body) = &self.body { request_builder = request_builder - .body(serde_json::to_string(&body).map_err(HttpClientError::SerializeRequestBody)?); + .body(serde_json::to_string(body).map_err(HttpClientError::SerializeRequestBody)?); } let mut request = request_builder.build().expect("invalid request"); // set the query string - if let Some(query_params) = self.query_params { + if let Some(query_params) = &self.query_params { let query_string = crate::qs::to_string(&query_params)?; request.url_mut().set_query(Some(&query_string)); } @@ -167,4 +171,32 @@ where Err(_) => Err(HttpClientError::BadStatus(status)), } } + + /// Send request and get the response + #[tracing::instrument(level = "debug", skip(self))] + pub async fn send(self) -> HttpClientResult { + match self.do_send().await { + Ok(resp) => Ok(resp), + Err(HttpClientError::BadStatus(StatusCode::TOO_MANY_REQUESTS)) => { + let mut retry_delay = RETRY_INITIAL_DELAY; + + for _ in 0..RETRY_COUNT { + tokio::time::sleep(retry_delay).await; + + match self.do_send().await { + Ok(resp) => return Ok(resp), + Err(HttpClientError::BadStatus(StatusCode::TOO_MANY_REQUESTS)) => { + retry_delay = + Duration::from_secs_f32(retry_delay.as_secs_f32() * RETRY_FACTOR); + continue; + } + Err(err) => return Err(err), + } + } + + Err(HttpClientError::BadStatus(StatusCode::TOO_MANY_REQUESTS)) + } + Err(err) => Err(err), + } + } } From d88b6f4e8c9de08498f8ee3aeb539fc5964206a3 Mon Sep 17 00:00:00 2001 From: Sunli Date: Sat, 25 Jun 2022 23:24:42 +0800 Subject: [PATCH 091/567] Release 0.2.22 longbridge@0.2.22 longbridge-candlesticks@0.2.22 longbridge-httpcli@0.2.22 longbridge-java@0.2.22 longbridge-java-macros@0.2.22 longbridge-nodejs@0.2.22 longbridge-nodejs-macros@0.2.22 longbridge-proto@0.2.22 longbridge-python@0.2.22 longbridge-python-macros@0.2.22 longbridge-wscli@0.2.22 Generated by cargo-workspaces --- java/Cargo.toml | 2 +- java/crates/macros/Cargo.toml | 2 +- nodejs/Cargo.toml | 2 +- nodejs/crates/macros/Cargo.toml | 2 +- python/Cargo.toml | 2 +- python/Makefile.toml | 2 +- python/crates/macros/Cargo.toml | 2 +- rust/Cargo.toml | 10 +++++----- rust/crates/candlesticks/Cargo.toml | 2 +- rust/crates/httpclient/Cargo.toml | 2 +- rust/crates/proto/Cargo.toml | 2 +- rust/crates/wsclient/Cargo.toml | 2 +- 12 files changed, 16 insertions(+), 16 deletions(-) diff --git a/java/Cargo.toml b/java/Cargo.toml index f91f1a466a..848f09b91e 100644 --- a/java/Cargo.toml +++ b/java/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge-java" -version = "0.2.21" +version = "0.2.22" [lib] crate-type = ["cdylib"] diff --git a/java/crates/macros/Cargo.toml b/java/crates/macros/Cargo.toml index c7f5080c0c..2d98d863fa 100644 --- a/java/crates/macros/Cargo.toml +++ b/java/crates/macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "longbridge-java-macros" -version = "0.2.21" +version = "0.2.22" edition = "2021" [lib] diff --git a/nodejs/Cargo.toml b/nodejs/Cargo.toml index d0b216245e..f408a13c0d 100644 --- a/nodejs/Cargo.toml +++ b/nodejs/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge-nodejs" -version = "0.2.21" +version = "0.2.22" [lib] crate-type = ["cdylib"] diff --git a/nodejs/crates/macros/Cargo.toml b/nodejs/crates/macros/Cargo.toml index 0d51bae649..ea92bba40b 100644 --- a/nodejs/crates/macros/Cargo.toml +++ b/nodejs/crates/macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "longbridge-nodejs-macros" -version = "0.2.21" +version = "0.2.22" edition = "2021" [lib] diff --git a/python/Cargo.toml b/python/Cargo.toml index e9cdef65b3..3dedbe0276 100644 --- a/python/Cargo.toml +++ b/python/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge-python" -version = "0.2.21" +version = "0.2.22" description = "Longbridge OpenAPI SDK for Python" homepage = "https://open.longbridgeapp.com/en/" readme = "README.md" diff --git a/python/Makefile.toml b/python/Makefile.toml index dfacd14c16..ac2d18d1a7 100644 --- a/python/Makefile.toml +++ b/python/Makefile.toml @@ -12,7 +12,7 @@ args = ["install", "maturin>=0.12,<0.13"] command = "pip" args = [ "install", - "target/wheels/longbridge-0.2.21-cp310-none-win_amd64.whl", + "target/wheels/longbridge-0.2.22-cp310-none-win_amd64.whl", "-I", ] dependencies = ["python"] diff --git a/python/crates/macros/Cargo.toml b/python/crates/macros/Cargo.toml index 4ca6df854e..01459d9310 100644 --- a/python/crates/macros/Cargo.toml +++ b/python/crates/macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "longbridge-python-macros" -version = "0.2.21" +version = "0.2.22" edition = "2021" [lib] diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 71b492ebf6..3e472ecaf8 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge" -version = "0.2.21" +version = "0.2.22" description = "Longbridge OpenAPI SDK for Rust" homepage = "https://open.longbridgeapp.com/en/" readme = "README.md" @@ -14,10 +14,10 @@ categories = ["api-bindings"] blocking = ["flume"] [dependencies] -longbridge-wscli = { path = "crates/wsclient", version = "0.2.21" } -longbridge-httpcli = { path = "crates/httpclient", version = "0.2.21" } -longbridge-proto = { path = "crates/proto", version = "0.2.21" } -longbridge-candlesticks = { path = "crates/candlesticks", version = "0.2.21" } +longbridge-wscli = { path = "crates/wsclient", version = "0.2.22" } +longbridge-httpcli = { path = "crates/httpclient", version = "0.2.22" } +longbridge-proto = { path = "crates/proto", version = "0.2.22" } +longbridge-candlesticks = { path = "crates/candlesticks", version = "0.2.22" } tokio = { version = "1.18.2", features = [ "time", diff --git a/rust/crates/candlesticks/Cargo.toml b/rust/crates/candlesticks/Cargo.toml index f29f04fbc9..478d1af55e 100644 --- a/rust/crates/candlesticks/Cargo.toml +++ b/rust/crates/candlesticks/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge-candlesticks" -version = "0.2.21" +version = "0.2.22" description = "Longbridge candlestick utils for Rust" license = "MIT OR Apache-2.0" diff --git a/rust/crates/httpclient/Cargo.toml b/rust/crates/httpclient/Cargo.toml index 53ced37c5d..003076bfcc 100644 --- a/rust/crates/httpclient/Cargo.toml +++ b/rust/crates/httpclient/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge-httpcli" -version = "0.2.21" +version = "0.2.22" description = "Longbridge HTTP SDK for Rust" license = "MIT OR Apache-2.0" diff --git a/rust/crates/proto/Cargo.toml b/rust/crates/proto/Cargo.toml index 374fd49ffb..f8d5ce98b5 100644 --- a/rust/crates/proto/Cargo.toml +++ b/rust/crates/proto/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge-proto" -version = "0.2.21" +version = "0.2.22" description = "Longbridge Protocol" license = "MIT OR Apache-2.0" diff --git a/rust/crates/wsclient/Cargo.toml b/rust/crates/wsclient/Cargo.toml index 982d1e636b..ca50a7208c 100644 --- a/rust/crates/wsclient/Cargo.toml +++ b/rust/crates/wsclient/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "longbridge-wscli" -version = "0.2.21" +version = "0.2.22" edition = "2021" description = "Longbridge Websocket SDK for Rust" license = "MIT OR Apache-2.0" From ba5e12c80ad1f90c7abc5833a8674488de6c7c1c Mon Sep 17 00:00:00 2001 From: Sunli Date: Mon, 27 Jun 2022 10:29:15 +0800 Subject: [PATCH 092/567] Update Java examples --- examples/java/account_asset/pom.xml | 2 +- examples/java/submit_order/pom.xml | 2 +- examples/java/subscribe_candlesticks/pom.xml | 2 +- examples/java/subscribe_quote/pom.xml | 2 +- examples/java/today_orders/pom.xml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/java/account_asset/pom.xml b/examples/java/account_asset/pom.xml index e823160982..400c0fd1d0 100644 --- a/examples/java/account_asset/pom.xml +++ b/examples/java/account_asset/pom.xml @@ -10,7 +10,7 @@ io.github.longbridgeapp openapi-sdk - 0.2.19 + LATEST
diff --git a/examples/java/submit_order/pom.xml b/examples/java/submit_order/pom.xml index 11a51fa85a..bd8b2a1bcd 100644 --- a/examples/java/submit_order/pom.xml +++ b/examples/java/submit_order/pom.xml @@ -10,7 +10,7 @@ io.github.longbridgeapp openapi-sdk - 0.2.19 + LATEST
diff --git a/examples/java/subscribe_candlesticks/pom.xml b/examples/java/subscribe_candlesticks/pom.xml index 4d4a94476d..b6e4dfc0e6 100644 --- a/examples/java/subscribe_candlesticks/pom.xml +++ b/examples/java/subscribe_candlesticks/pom.xml @@ -10,7 +10,7 @@ io.github.longbridgeapp openapi-sdk - 0.2.19 + LATEST
diff --git a/examples/java/subscribe_quote/pom.xml b/examples/java/subscribe_quote/pom.xml index f7e26f43c1..4fa77ea059 100644 --- a/examples/java/subscribe_quote/pom.xml +++ b/examples/java/subscribe_quote/pom.xml @@ -10,7 +10,7 @@ io.github.longbridgeapp openapi-sdk - 0.2.19 + LATEST diff --git a/examples/java/today_orders/pom.xml b/examples/java/today_orders/pom.xml index 23a1b0c5fe..fe778f4858 100644 --- a/examples/java/today_orders/pom.xml +++ b/examples/java/today_orders/pom.xml @@ -10,7 +10,7 @@ io.github.longbridgeapp openapi-sdk - 0.2.19 + LATEST From 54a52f7fb96c98d8a2f7636bcd6af804ecec949c Mon Sep 17 00:00:00 2001 From: Sunli Date: Mon, 27 Jun 2022 10:50:40 +0800 Subject: [PATCH 093/567] Fixes resubscribe --- .../src/main/java/com/longbridge/SdkNative.java | 8 ++++++-- rust/src/quote/core.rs | 17 +++++++++++++---- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/java/javasrc/src/main/java/com/longbridge/SdkNative.java b/java/javasrc/src/main/java/com/longbridge/SdkNative.java index 33c300deef..f231331c7b 100644 --- a/java/javasrc/src/main/java/com/longbridge/SdkNative.java +++ b/java/javasrc/src/main/java/com/longbridge/SdkNative.java @@ -95,7 +95,8 @@ public static native void quoteContextTradingDays(long context, Market market, L public static native void quoteContextRealtimeTrades(long context, String symbol, int count, AsyncCallback callback); - public static native void quoteContextRealtimeCandlesticks(long context, String symbol, Period period, int count, + public static native void quoteContextRealtimeCandlesticks(long context, String symbol, Period period, + int count, AsyncCallback callback); public static native void newTradeContext(long config, AsyncCallback callback); @@ -142,10 +143,13 @@ public static native void tradeContextStockPositions(long context, GetStockPosit try { NativeLoader.loadLibrary("longbridge_java"); } catch (IOException e) { + System.out.println("======================================"); + System.out.println("Failed to load longbridge_java"); + e.printStackTrace(); + System.out.println("======================================"); System.load("longbridge_java"); } finally { SdkNative.init(); } - } } diff --git a/rust/src/quote/core.rs b/rust/src/quote/core.rs index 2ab06a1a37..a49c4c7827 100644 --- a/rust/src/quote/core.rs +++ b/rust/src/quote/core.rs @@ -597,10 +597,19 @@ impl Core { let mut subscriptions: HashMap> = HashMap::new(); for (symbol, flags) in &self.subscriptions { - subscriptions - .entry(*flags) - .or_default() - .push(symbol.clone()); + let mut flags = *flags; + + if self + .store + .securities + .get(symbol) + .map(|data| !data.candlesticks.is_empty()) + .unwrap_or_default() + { + flags |= SubFlags::TRADE; + } + + subscriptions.entry(flags).or_default().push(symbol.clone()); } for (flags, symbols) in subscriptions { From 027e604c211e964d7f24c41e03809a5ce2964557 Mon Sep 17 00:00:00 2001 From: Sunli Date: Mon, 27 Jun 2022 11:02:26 +0800 Subject: [PATCH 094/567] Update parsing cashflow response --- rust/src/trade/serde_utils.rs | 10 +++++----- rust/src/trade/types.rs | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/rust/src/trade/serde_utils.rs b/rust/src/trade/serde_utils.rs index 47c7d6b486..0d27f4fdfa 100644 --- a/rust/src/trade/serde_utils.rs +++ b/rust/src/trade/serde_utils.rs @@ -213,17 +213,17 @@ pub(crate) mod outside_rth { } } -pub(crate) mod cash_flow_symbol { +pub(crate) mod symbol_opt { use super::*; pub(crate) fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, { - let value = String::deserialize(deserializer)?; - match value.as_str() { - "." => Ok(None), - _ => Ok(Some(value)), + match >::deserialize(deserializer)? { + Some(value) if value.is_empty() => Ok(None), + Some(value) => Ok(Some(value)), + _ => Ok(None), } } } diff --git a/rust/src/trade/types.rs b/rust/src/trade/types.rs index b5fb62ee95..4847d74e7d 100644 --- a/rust/src/trade/types.rs +++ b/rust/src/trade/types.rs @@ -403,7 +403,7 @@ pub struct CashFlow { #[serde(with = "serde_utils::timestamp")] pub business_time: OffsetDateTime, /// Associated Stock code information - #[serde(with = "serde_utils::cash_flow_symbol")] + #[serde(with = "serde_utils::symbol_opt")] pub symbol: Option, /// Cash flow description pub description: String, From 5182fb16af747f8f3c047c1a2af7adee30d8e6fd Mon Sep 17 00:00:00 2001 From: Sunli Date: Mon, 27 Jun 2022 13:15:07 +0800 Subject: [PATCH 095/567] Improve options of `TradeContext` for JS SDK --- examples/nodejs/submit_order.js | 17 +- nodejs/README.md | 18 +- nodejs/index.d.ts | 411 ++++++++++-------- nodejs/index.js | 9 +- nodejs/src/trade/context.rs | 272 +++++++----- nodejs/src/trade/requests/get_cash_flow.rs | 75 ++-- .../trade/requests/get_history_executions.rs | 52 +-- .../src/trade/requests/get_history_orders.rs | 91 ++-- .../trade/requests/get_today_executions.rs | 41 +- nodejs/src/trade/requests/get_today_orders.rs | 80 ++-- nodejs/src/trade/requests/replace_order.rs | 97 ++--- nodejs/src/trade/requests/submit_order.rs | 142 +++--- 12 files changed, 633 insertions(+), 672 deletions(-) diff --git a/examples/nodejs/submit_order.js b/examples/nodejs/submit_order.js index 85886e6429..7ccea9eeaf 100644 --- a/examples/nodejs/submit_order.js +++ b/examples/nodejs/submit_order.js @@ -11,14 +11,13 @@ const { let config = Config.fromEnv(); TradeContext.new(config) .then((ctx) => - ctx.submitOrder( - new SubmitOrderOptions( - "700.HK", - OrderType.LO, - OrderSide.Buy, - "200", - TimeInForceType.Day - ).submittedPrice(new Decimal("50")) - ) + ctx.submitOrder({ + symbol: "700.HK", + orderType: OrderType.LO, + side: OrderSide.Buy, + timeInForce: TimeInForceType.Day, + submittedPrice: new Decimal("50"), + submittedQuantity: 200, + }) ) .then((resp) => console.log(resp.toString())); diff --git a/nodejs/README.md b/nodejs/README.md index 6e3aa1d342..dc165e6600 100644 --- a/nodejs/README.md +++ b/nodejs/README.md @@ -63,7 +63,6 @@ QuoteContext.new(config).then((ctx) => { const { Config, TradeContext, - SubmitOrderOptions, Decimal, OrderSide, TimeInForceType, @@ -73,15 +72,14 @@ const { let config = Config.fromEnv(); TradeContext.new(config) .then((ctx) => - ctx.submitOrder( - new SubmitOrderOptions( - "700.HK", - OrderType.LO, - OrderSide.Buy, - 200, - TimeInForceType.Day - ).submittedPrice(new Decimal("50")) - ) + ctx.submitOrder({ + symbol: "700.HK", + orderType: OrderType.LO, + side: OrderSide.Buy, + timeInForce: TimeInForceType.Day, + submittedPrice: new Decimal("50"), + submittedQuantity: 200, + }) ) .then((resp) => console.log(resp.toString())); ``` diff --git a/nodejs/index.d.ts b/nodejs/index.d.ts index c6bd251f53..8f96e30002 100644 --- a/nodejs/index.d.ts +++ b/nodejs/index.d.ts @@ -147,6 +147,116 @@ export const enum AdjustType { /** Adjust forward */ ForwardAdjust = 1 } +/** Options for get cash flow request */ +export interface GetCashFlowOptions { + /** Start time */ + startAt: Date + /** End time */ + endAt: Date + /** Business type */ + businessType?: BalanceType + /** Security symbol */ + symbol?: string + /** Page number */ + page?: number + /** Page size */ + size?: number +} +/** Options for get histroy executions request */ +export interface GetHistoryExecutionsOptions { + /** Security symbol */ + symbol?: string + /** Start time */ + startAt?: Date + /** End time */ + endAt?: Date +} +/** Options for get histroy orders request */ +export interface GetHistoryOrdersOptions { + /** Security symbol */ + symbol?: string + /** Order status */ + status?: Array + /** Order side */ + side?: OrderSide + /** Market */ + market?: Market + /** Start time */ + startAt?: Date + /** End time */ + endAt?: Date +} +/** Options for get today executions request */ +export interface GetTodayExecutionsOptions { + /** Security symbol */ + symbol?: string + /** Order id */ + orderId?: string +} +/** Options for get today orders request */ +export interface GetTodayOrdersOptions { + /** Security symbol */ + symbol?: string + /** Order status */ + status?: Array + /** Order side */ + side?: OrderSide + /** Market */ + market?: Market + /** Order id */ + orderId?: string +} +/** Options for replace order request */ +export interface ReplaceOrderOptions { + /** Order id */ + orderId: string + /** Replaced quantity */ + quantity: number + /** Replaced price */ + price?: Decimal + /** Trigger price (`LIT` / `MIT` Order Required) */ + triggerPrice?: Decimal + /** Limit offset amount (`TSLPAMT` / `TSLPPCT` Required) */ + limitOffset?: Decimal + /** Trailing amount (`TSLPAMT` / `TSMAMT` Required) */ + trailingAmount?: Decimal + /** Trailing percent (`TSLPPCT` / `TSMAPCT` Required) */ + trailingPercent?: Decimal + /** Remark (Maximum 64 characters) */ + remark?: string +} +/** Options for submit order request */ +export interface SubmitOrderOptions { + /** Security code */ + symbol: string + /** Order type */ + orderType: OrderType + /** Order side */ + side: OrderSide + /** Submitted quantity */ + submittedQuantity: number + /** Time in force type */ + timeInForce: TimeInForceType + /** Submitted price */ + submittedPrice?: Decimal + /** Trigger price (`LIT` / `MIT` Required) */ + triggerPrice?: Decimal + /** Limit offset amount (`TSLPAMT` / `TSLPPCT` Required) */ + limitOffset?: Decimal + /** Trailing amount (`TSLPAMT` / `TSMAMT` Required) */ + trailingAmount?: Decimal + /** Trailing percent (`TSLPPCT` / `TSMAPCT` Required) */ + trailingPercent?: Decimal + /** + * Long term order expire date (Required when `time_in_force` is + * `GoodTilDate`) + */ + expireDate?: NaiveDate + /** Enable or disable outside regular trading hours */ + outsideRth?: OutsideRTH + /** Remark (Maximum 64 characters) */ + remark?: string +} /** Topic type */ export const enum TopicType { /** Private notification for trade */ @@ -1447,14 +1557,13 @@ export class TradeContext { * * ```javascript * const { - * Config, - * TradeContext, - * SubmitOrderOptions, - * Decimal, - * OrderSide, - * TimeInForceType, - * OrderType, - * TopicType, + * Config, + * TradeContext, + * Decimal, + * OrderSide, + * TimeInForceType, + * OrderType, + * TopicType, * } = require("longbridge"); * * let config = Config.fromEnv(); @@ -1462,15 +1571,14 @@ export class TradeContext { * .then((ctx) => { * ctx.setOnQuote((_, event) => console.log(event.toString())); * ctx.subscribe([TopicType.Private]); - * return ctx.submitOrder( - * new SubmitOrderOptions( - * "700.HK", - * OrderType.LO, - * OrderSide.Buy, - * 200, - * TimeInForceType.Day - * ).submittedPrice(new Decimal("50")) - * ); + * return ctx.submitOrder({ + * symbol: "700.HK", + * orderType: OrderType.LO, + * side: OrderSide.Buy, + * submittedQuantity: 200, + * timeInForce: TimeInForceType.Day, + * submittedPrice: new Decimal("50"), + * }); * }) * .then((resp) => console.log(resp.toString())); * ``` @@ -1484,20 +1592,22 @@ export class TradeContext { * #### Example * * ```javascript - * const { Config, TradeContext, GetHistoryExecutionsOptions } = require("longbridge") + * const { Config, TradeContext } = require("longbridge"); * - * let config = Config.fromEnv() - * let opts = new GetHistoryExecutionsOptions() - * .symbol("700.HK") - * .startAt(new Date(2022, 5, 9)) - * .endAt(new Date(2022, 5, 12)) + * let config = Config.fromEnv(); * TradeContext.new(config) - * .then((ctx) => ctx.historyExecutions(opts)) + * .then((ctx) => + * ctx.historyExecutions({ + * symbol: "700.HK", + * startAt: new Date(2022, 5, 9), + * endAt: new Date(2022, 5, 12), + * }) + * ) * .then((resp) => { * for (let obj of resp) { - * console.log(obj.toString()) + * console.log(obj.toString()); * } - * }) + * }); * ``` */ historyExecutions(opts?: GetHistoryExecutionsOptions | undefined | null): Promise> @@ -1507,16 +1617,16 @@ export class TradeContext { * #### Example * * ```javascript - * const { Config, TradeContext, GetTodayExecutionsOptions } = require("longbridge") + * const { Config, TradeContext } = require("longbridge"); * - * let config = Config.fromEnv() + * let config = Config.fromEnv(); * TradeContext.new(config) - * .then((ctx) => ctx.todayExecutions(new GetTodayExecutionsOptions().symbol("700.HK"))) + * .then((ctx) => ctx.todayExecutions({ symbol: "700.HK" })) * .then((resp) => { * for (let obj of resp) { - * console.log(obj.toString()) + * console.log(obj.toString()); * } - * }) + * }); * ``` */ todayExecutions(opts?: GetTodayExecutionsOptions | undefined | null): Promise> @@ -1526,23 +1636,31 @@ export class TradeContext { * #### Example * * ```javascript - * const { Config, TradeContext, GetHistoryOrdersOptions, OrderStatus, OrderSide, Market } = require("longbridge") + * const { + * Config, + * TradeContext, + * OrderStatus, + * OrderSide, + * Market, + * } = require("longbridge"); * - * let config = Config.fromEnv() - * let opts = new GetHistoryOrdersOptions() - * .symbol("700.HK") - * .status([OrderStatus.Filled, OrderStatus.New]) - * .side(OrderSide.Buy) - * .market(Market.HK) - * .startAt(2022, 5, 9) - * .endAt(2022, 5, 12) + * let config = Config.fromEnv(); * TradeContext.new(config) - * .then((ctx) => ctx.historyOrders(opts)) + * .then((ctx) => + * ctx.historyOrders({ + * symbol: "700.HK", + * status: [OrderStatus.Filled, OrderStatus.New], + * side: OrderSide.Buy, + * market: Market.HK, + * startAt: new Date(2022, 5, 9), + * endAt: new Date(2022, 5, 12), + * }) + * ) * .then((resp) => { * for (let obj of resp) { - * console.log(obj.toString()) + * console.log(obj.toString()); * } - * }) + * }); * ``` */ historyOrders(opts?: GetHistoryOrdersOptions | undefined | null): Promise> @@ -1552,23 +1670,29 @@ export class TradeContext { * #### Example * * ```javascript - * const { Config, TradeContext, GetTodayOrdersOptions, OrderStatus, OrderSide, Market } = require("longbridge") - * - * let config = Config.fromEnv() + * const { + * Config, + * TradeContext, + * OrderStatus, + * OrderSide, + * Market, + * } = require("longbridge"); * - * let opts = new GetTodayOrdersOptions() - * .symbol("700.HK") - * .status([OrderStatus.Filled, OrderStatus.New]) - * .side(OrderSide.Buy) - * .market(Market.HK) + * let config = Config.fromEnv(); * TradeContext.new(config) - * .then((ctx) => ctx.todayOrders(opts)) + * .then((ctx) => + * ctx.todayOrders({ + * symbol: "700.HK", + * status: [OrderStatus.Filled, OrderStatus.New], + * side: OrderSide.Buy, + * market: Market.HK, + * }) + * ) * .then((resp) => { * for (let obj of resp) { - * console.log(obj.toString()) + * console.log(obj.toString()); * } - * }) - * ) + * }); * ``` */ todayOrders(opts?: GetTodayOrdersOptions | undefined | null): Promise> @@ -1578,16 +1702,22 @@ export class TradeContext { * #### Example * * ```javascript - * const { Config, TradeContext, ReplaceOrderOptions, Decimal } = require("longbridge") + * const { Config, TradeContext, Decimal } = require("longbridge"); * - * let config = Config.fromEnv() + * let config = Config.fromEnv(); * TradeContext.new(config) - * .then((ctx) => ctx.replaceOrder(new ReplaceOrderOptions("700.HK", 100).price(new Decimal("300")))) + * .then((ctx) => + * ctx.replaceOrder({ + * orderId: "709043056541253632", + * quantity: 100, + * price: new Decimal("300"), + * }) + * ) * .then((resp) => { * for (let obj of resp) { - * console.log(obj.toString()) + * console.log(obj.toString()); * } - * }) + * }); * ``` */ replaceOrder(opts: ReplaceOrderOptions): Promise @@ -1597,28 +1727,41 @@ export class TradeContext { * #### Example * * ```javascript - * const { Config, TradeContext, SubmitOrderOptions, OrderType, OrderSide, Decimal, TimeInForceType } = require("longbridge") + * const { + * Config, + * TradeContext, + * OrderType, + * OrderSide, + * Decimal, + * TimeInForceType, + * } = require("longbridge"); * - * let config = Config.fromEnv() - * let opts = new SubmitOrderOptions("700.HK", OrderType.LO, OrderSide.Buy, 200, TimeInForceType.Day) - * .submittedPrice(new Decimal("300")); + * let config = Config.fromEnv(); * TradeContext.new(config) - * .then((ctx) => ctx.submitOrder(opts)) - * .then((resp) => console.log(resp)) + * .then((ctx) => + * ctx.submitOrder({ + * symbol: "700.HK", + * orderType: OrderType.LO, + * side: OrderSide.Buy, + * timeInForce: TimeInForceType.Day, + * submittedQuantity: 200, + * submittedPrice: new Decimal("300"), + * }) + * ) + * .then((resp) => console.log(resp.toString())); * ``` */ submitOrder(opts: SubmitOrderOptions): Promise /** - * Withdraw order + * Cancel order * * #### Example * * ```javascript - * const { Config, TradeContext } = require("longbridge") + * const { Config, TradeContext } = require("longbridge"); * - * let config = Config.fromEnv() - * TradeContext.new(config) - * .then((ctx) => ctx.cancelOrder("709043056541253632")) + * let config = Config.fromEnv(); + * TradeContext.new(config).then((ctx) => ctx.cancelOrder("709043056541253632")); * ``` */ cancelOrder(orderId: string): Promise @@ -1628,16 +1771,16 @@ export class TradeContext { * #### Example * * ```javascript - * const { Config, TradeContext } = require("longbridge") + * const { Config, TradeContext } = require("longbridge"); * - * let config = Config.fromEnv() + * let config = Config.fromEnv(); * TradeContext.new(config) * .then((ctx) => ctx.accountBalance()) * .then((resp) => { * for (let obj of resp) { - * console.log(obj.toString()) + * console.log(obj.toString()); * } - * }) + * }); * ``` */ accountBalance(): Promise> @@ -1647,16 +1790,21 @@ export class TradeContext { * #### Example * * ```javascript - * const { Config, TradeContext, GetCashFlowOptions } = require("longbridge") + * const { Config, TradeContext, GetCashFlowOptions } = require("longbridge"); * - * let config = Config.fromEnv() + * let config = Config.fromEnv(); * TradeContext.new(config) - * .then((ctx) => ctx.cashFlow(new GetCashFlowOptions(new Date(2022, 5, 9), new Date(2022, 5, 12)))) + * .then((ctx) => + * ctx.cashFlow({ + * startAt: new Date(2022, 5, 9), + * endAt: new Date(2022, 5, 12), + * }) + * ) * .then((resp) => { * for (let obj of resp) { - * console.log(obj.toString()) + * console.log(obj.toString()); * } - * }) + * }); * ``` */ cashFlow(opts: GetCashFlowOptions): Promise> @@ -1691,109 +1839,6 @@ export class TradeContext { */ stockPositions(symbols?: Array | undefined | null): Promise } -/** Options for submit order request */ -export class GetCashFlowOptions { - /** Create a new `GetCashFlowOptions` */ - constructor(startAt: Date, endAt: Date) - /** Set the business type */ - businessType(businessType: BalanceType): GetCashFlowOptions - /** Set the security symbol */ - symbol(symbol: string): GetCashFlowOptions - /** Set the page number */ - page(page: number): GetCashFlowOptions - /** Set the page size */ - size(size: number): GetCashFlowOptions -} -/** Options for get histroy executions request */ -export class GetHistoryExecutionsOptions { - /** Create a new `GetHistoryExecutionsOptions` */ - constructor() - /** Set the security symbol */ - symbol(symbol: string): GetHistoryExecutionsOptions - /** Set the start time */ - startAt(startAt: Date): GetHistoryExecutionsOptions - /** Set the end time */ - endAt(endAt: Date): GetHistoryExecutionsOptions -} -/** Options for get histroy orders request */ -export class GetHistoryOrdersOptions { - /** Create a new `GetHistoryOrdersOptions` */ - constructor() - /** Set the security symbol */ - symbol(symbol: string): GetHistoryOrdersOptions - /** Set the order status */ - status(status: OrderStatus | Array): GetHistoryOrdersOptions - /** Set the order side */ - side(side: OrderSide): GetHistoryOrdersOptions - /** Set the market */ - market(market: Market): GetHistoryOrdersOptions - /** Set the start time */ - startAt(startAt: Date): GetHistoryOrdersOptions - /** Set the end time */ - endAt(endAt: Date): GetHistoryOrdersOptions -} -/** Options for get histroy executions request */ -export class GetTodayExecutionsOptions { - /** Create a new `GetTodayExecutionsOptions` */ - constructor() - /** Set the security symbol */ - symbol(symbol: string): GetTodayExecutionsOptions - /** Set the order id */ - orderId(orderId: string): GetTodayExecutionsOptions -} -/** Options for get today orders request */ -export class GetTodayOrdersOptions { - /** Create a new `GetTodayOrdersOptions` */ - constructor() - /** Set the security symbol */ - symbol(symbol: string): GetTodayOrdersOptions - /** Set the order status */ - status(status: OrderStatus | Array): GetTodayOrdersOptions - /** Set the order side */ - side(side: OrderSide): GetTodayOrdersOptions - /** Set the market */ - market(market: Market): GetTodayOrdersOptions - /** Set the order id */ - orderId(orderId: string): GetTodayOrdersOptions -} -/** Options for get today orders request */ -export class ReplaceOrderOptions { - /** Create a new `ReplaceOrderOptions` */ - constructor(orderId: string, quantity: number) - /** Set the price */ - price(price: Decimal): ReplaceOrderOptions - /** Set the trigger price */ - triggerPrice(triggerPrice: Decimal): ReplaceOrderOptions - /** Set the limit offset */ - limitOffset(limitOffset: Decimal): ReplaceOrderOptions - /** Set the trailing amount */ - trailingAmount(trailingAmount: Decimal): ReplaceOrderOptions - /** Set the trailing percent */ - trailingPercent(trailingPercent: Decimal): ReplaceOrderOptions - /** Set the remark */ - remark(remark: string): ReplaceOrderOptions -} -/** Options for submit order request */ -export class SubmitOrderOptions { - /** Create a new `SubmitOrderOptions` */ - constructor(symbol: string, orderType: OrderType, side: OrderSide, submittedQuantity: number, timeInForce: TimeInForceType) - /** Set the submitted price */ - submittedPrice(submittedPrice: Decimal): SubmitOrderOptions - /** Set the trigger price */ - triggerPrice(triggerPrice: Decimal): SubmitOrderOptions - /** Set the limit offset */ - limitOffset(limitOffset: Decimal): SubmitOrderOptions - /** Set the trailing amount */ - trailingAmount(trailingAmount: Decimal): SubmitOrderOptions - /** Set the trailing percent */ - trailingPercent(trailingPercent: Decimal): SubmitOrderOptions - /** Set the expire date */ - expireDate(expireDate: NaiveDate): SubmitOrderOptions - /** Enable or disable outside regular trading hours */ - outsideRth(outsideRth: OutsideRTH): SubmitOrderOptions - /** Set the remark */ - remark(remark: string): SubmitOrderOptions -} /** Trade */ export class Execution { toString(): string diff --git a/nodejs/index.js b/nodejs/index.js index 3328c384ab..1673a05423 100644 --- a/nodejs/index.js +++ b/nodejs/index.js @@ -236,7 +236,7 @@ if (!nativeBinding) { throw new Error(`Failed to load native binding`) } -const { Config, Decimal, QuoteContext, PushQuoteEvent, PushDepthEvent, PushBrokersEvent, PushTradesEvent, PushCandlestickEvent, Subscription, DerivativeType, TradeStatus, TradeSession, SubType, TradeDirection, OptionType, OptionDirection, WarrantType, Period, AdjustType, SecurityStaticInfo, PrePostQuote, SecurityQuote, OptionQuote, WarrantQuote, Depth, SecurityDepth, Brokers, SecurityBrokers, ParticipantInfo, Trade, IntradayLine, Candlestick, StrikePriceInfo, IssuerInfo, TradingSessionInfo, MarketTradingSession, RealtimeQuote, PushQuote, PushDepth, PushBrokers, PushTrades, PushCandlestick, MarketTradingDays, CapitalFlowLine, CapitalDistribution, CapitalDistributionResponse, NaiveDate, Time, TradeContext, GetCashFlowOptions, GetHistoryExecutionsOptions, GetHistoryOrdersOptions, GetTodayExecutionsOptions, GetTodayOrdersOptions, ReplaceOrderOptions, SubmitOrderOptions, TopicType, Execution, OrderStatus, OrderSide, OrderType, OrderTag, TimeInForceType, TriggerStatus, OutsideRTH, Order, PushOrderChanged, SubmitOrderResponse, CashInfo, AccountBalance, BalanceType, CashFlowDirection, CashFlow, FundPositionsResponse, FundPositionChannel, FundPosition, StockPositionsResponse, StockPositionChannel, StockPosition, Market } = nativeBinding +const { Config, Decimal, QuoteContext, PushQuoteEvent, PushDepthEvent, PushBrokersEvent, PushTradesEvent, PushCandlestickEvent, Subscription, DerivativeType, TradeStatus, TradeSession, SubType, TradeDirection, OptionType, OptionDirection, WarrantType, Period, AdjustType, SecurityStaticInfo, PrePostQuote, SecurityQuote, OptionQuote, WarrantQuote, Depth, SecurityDepth, Brokers, SecurityBrokers, ParticipantInfo, Trade, IntradayLine, Candlestick, StrikePriceInfo, IssuerInfo, TradingSessionInfo, MarketTradingSession, RealtimeQuote, PushQuote, PushDepth, PushBrokers, PushTrades, PushCandlestick, MarketTradingDays, CapitalFlowLine, CapitalDistribution, CapitalDistributionResponse, NaiveDate, Time, TradeContext, TopicType, Execution, OrderStatus, OrderSide, OrderType, OrderTag, TimeInForceType, TriggerStatus, OutsideRTH, Order, PushOrderChanged, SubmitOrderResponse, CashInfo, AccountBalance, BalanceType, CashFlowDirection, CashFlow, FundPositionsResponse, FundPositionChannel, FundPosition, StockPositionsResponse, StockPositionChannel, StockPosition, Market } = nativeBinding module.exports.Config = Config module.exports.Decimal = Decimal @@ -287,13 +287,6 @@ module.exports.CapitalDistributionResponse = CapitalDistributionResponse module.exports.NaiveDate = NaiveDate module.exports.Time = Time module.exports.TradeContext = TradeContext -module.exports.GetCashFlowOptions = GetCashFlowOptions -module.exports.GetHistoryExecutionsOptions = GetHistoryExecutionsOptions -module.exports.GetHistoryOrdersOptions = GetHistoryOrdersOptions -module.exports.GetTodayExecutionsOptions = GetTodayExecutionsOptions -module.exports.GetTodayOrdersOptions = GetTodayOrdersOptions -module.exports.ReplaceOrderOptions = ReplaceOrderOptions -module.exports.SubmitOrderOptions = SubmitOrderOptions module.exports.TopicType = TopicType module.exports.Execution = Execution module.exports.OrderStatus = OrderStatus diff --git a/nodejs/src/trade/context.rs b/nodejs/src/trade/context.rs index aa5ca62dde..1aa41b1d5a 100644 --- a/nodejs/src/trade/context.rs +++ b/nodejs/src/trade/context.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use longbridge::trade::{GetFundPositionsOptions, GetStockPositionsOptions, PushEvent}; -use napi::{threadsafe_function::ThreadsafeFunctionCallMode, JsFunction, Result}; +use napi::{threadsafe_function::ThreadsafeFunctionCallMode, Env, JsFunction, JsObject, Result}; use parking_lot::Mutex; use crate::{ @@ -83,32 +83,30 @@ impl TradeContext { /// /// ```javascript /// const { - /// Config, - /// TradeContext, - /// SubmitOrderOptions, - /// Decimal, - /// OrderSide, - /// TimeInForceType, - /// OrderType, - /// TopicType, + /// Config, + /// TradeContext, + /// Decimal, + /// OrderSide, + /// TimeInForceType, + /// OrderType, + /// TopicType, /// } = require("longbridge"); - /// + /// /// let config = Config.fromEnv(); /// TradeContext.new(config) /// .then((ctx) => { /// ctx.setOnQuote((_, event) => console.log(event.toString())); /// ctx.subscribe([TopicType.Private]); - /// return ctx.submitOrder( - /// new SubmitOrderOptions( - /// "700.HK", - /// OrderType.LO, - /// OrderSide.Buy, - /// 200, - /// TimeInForceType.Day - /// ).submittedPrice(new Decimal("50")) - /// ); + /// return ctx.submitOrder({ + /// symbol: "700.HK", + /// orderType: OrderType.LO, + /// side: OrderSide.Buy, + /// timeInForce: TimeInForceType.Day, + /// submittedPrice: new Decimal("50"), + /// submittedQuantity: 200, + /// }); /// }) - /// .then((resp) => console.log(resp.toString())); + /// .then((resp) => console.log(resp.toString())); /// ``` #[napi] pub async fn subscribe(&self, topics: Vec) -> Result<()> { @@ -134,28 +132,30 @@ impl TradeContext { /// #### Example /// /// ```javascript - /// const { Config, TradeContext, GetHistoryExecutionsOptions } = require("longbridge") + /// const { Config, TradeContext } = require("longbridge"); /// - /// let config = Config.fromEnv() - /// let opts = new GetHistoryExecutionsOptions() - /// .symbol("700.HK") - /// .startAt(new Date(2022, 5, 9)) - /// .endAt(new Date(2022, 5, 12)) + /// let config = Config.fromEnv(); /// TradeContext.new(config) - /// .then((ctx) => ctx.historyExecutions(opts)) + /// .then((ctx) => + /// ctx.historyExecutions({ + /// symbol: "700.HK", + /// startAt: new Date(2022, 5, 9), + /// endAt: new Date(2022, 5, 12), + /// }) + /// ) /// .then((resp) => { /// for (let obj of resp) { - /// console.log(obj.toString()) - /// } - /// }) + /// console.log(obj.toString()); + /// } + /// }); /// ``` #[napi] pub async fn history_executions( &self, - opts: Option<&GetHistoryExecutionsOptions>, + opts: Option, ) -> Result> { self.ctx - .history_executions(opts.cloned().map(Into::into)) + .history_executions(opts.map(Into::into)) .await .map_err(ErrorNewType)? .into_iter() @@ -168,24 +168,24 @@ impl TradeContext { /// #### Example /// /// ```javascript - /// const { Config, TradeContext, GetTodayExecutionsOptions } = require("longbridge") + /// const { Config, TradeContext } = require("longbridge"); /// - /// let config = Config.fromEnv() + /// let config = Config.fromEnv(); /// TradeContext.new(config) - /// .then((ctx) => ctx.todayExecutions(new GetTodayExecutionsOptions().symbol("700.HK"))) + /// .then((ctx) => ctx.todayExecutions({ symbol: "700.HK" })) /// .then((resp) => { /// for (let obj of resp) { - /// console.log(obj.toString()) - /// } - /// }) + /// console.log(obj.toString()); + /// } + /// }); /// ``` #[napi] pub async fn today_executions( &self, - opts: Option<&GetTodayExecutionsOptions>, + opts: Option, ) -> Result> { self.ctx - .today_executions(opts.cloned().map(Into::into)) + .today_executions(opts.map(Into::into)) .await .map_err(ErrorNewType)? .into_iter() @@ -198,31 +198,39 @@ impl TradeContext { /// #### Example /// /// ```javascript - /// const { Config, TradeContext, GetHistoryOrdersOptions, OrderStatus, OrderSide, Market } = require("longbridge") + /// const { + /// Config, + /// TradeContext, + /// OrderStatus, + /// OrderSide, + /// Market, + /// } = require("longbridge"); /// - /// let config = Config.fromEnv() - /// let opts = new GetHistoryOrdersOptions() - /// .symbol("700.HK") - /// .status([OrderStatus.Filled, OrderStatus.New]) - /// .side(OrderSide.Buy) - /// .market(Market.HK) - /// .startAt(2022, 5, 9) - /// .endAt(2022, 5, 12) + /// let config = Config.fromEnv(); /// TradeContext.new(config) - /// .then((ctx) => ctx.historyOrders(opts)) + /// .then((ctx) => + /// ctx.historyOrders({ + /// symbol: "700.HK", + /// status: [OrderStatus.Filled, OrderStatus.New], + /// side: OrderSide.Buy, + /// market: Market.HK, + /// startAt: new Date(2022, 5, 9), + /// endAt: new Date(2022, 5, 12), + /// }) + /// ) /// .then((resp) => { /// for (let obj of resp) { - /// console.log(obj.toString()) + /// console.log(obj.toString()); /// } - /// }) + /// }); /// ``` #[napi] pub async fn history_orders( &self, - opts: Option<&GetHistoryOrdersOptions>, + opts: Option, ) -> Result> { self.ctx - .history_orders(opts.cloned().map(Into::into)) + .history_orders(opts.map(Into::into)) .await .map_err(ErrorNewType)? .into_iter() @@ -235,28 +243,34 @@ impl TradeContext { /// #### Example /// /// ```javascript - /// const { Config, TradeContext, GetTodayOrdersOptions, OrderStatus, OrderSide, Market } = require("longbridge") - /// - /// let config = Config.fromEnv() + /// const { + /// Config, + /// TradeContext, + /// OrderStatus, + /// OrderSide, + /// Market, + /// } = require("longbridge"); /// - /// let opts = new GetTodayOrdersOptions() - /// .symbol("700.HK") - /// .status([OrderStatus.Filled, OrderStatus.New]) - /// .side(OrderSide.Buy) - /// .market(Market.HK) + /// let config = Config.fromEnv(); /// TradeContext.new(config) - /// .then((ctx) => ctx.todayOrders(opts)) + /// .then((ctx) => + /// ctx.todayOrders({ + /// symbol: "700.HK", + /// status: [OrderStatus.Filled, OrderStatus.New], + /// side: OrderSide.Buy, + /// market: Market.HK, + /// }) + /// ) /// .then((resp) => { /// for (let obj of resp) { - /// console.log(obj.toString()) + /// console.log(obj.toString()); /// } - /// }) - /// ) + /// }); /// ``` #[napi] - pub async fn today_orders(&self, opts: Option<&GetTodayOrdersOptions>) -> Result> { + pub async fn today_orders(&self, opts: Option) -> Result> { self.ctx - .today_orders(opts.cloned().map(Into::into)) + .today_orders(opts.map(Into::into)) .await .map_err(ErrorNewType)? .into_iter() @@ -269,24 +283,36 @@ impl TradeContext { /// #### Example /// /// ```javascript - /// const { Config, TradeContext, ReplaceOrderOptions, Decimal } = require("longbridge") + /// const { Config, TradeContext, Decimal } = require("longbridge"); /// - /// let config = Config.fromEnv() + /// let config = Config.fromEnv(); /// TradeContext.new(config) - /// .then((ctx) => ctx.replaceOrder(new ReplaceOrderOptions("700.HK", 100).price(new Decimal("300")))) + /// .then((ctx) => + /// ctx.replaceOrder({ + /// orderId: "709043056541253632", + /// quantity: 100, + /// price: new Decimal("300"), + /// }) + /// ) /// .then((resp) => { /// for (let obj of resp) { - /// console.log(obj.toString()) + /// console.log(obj.toString()); /// } - /// }) + /// }); /// ``` - #[napi] - pub async fn replace_order(&self, opts: &ReplaceOrderOptions) -> Result<()> { - self.ctx - .replace_order(opts.clone().into()) - .await - .map_err(ErrorNewType)?; - Ok(()) + #[napi(ts_return_type = "Promise")] + pub fn replace_order(&self, env: Env, opts: ReplaceOrderOptions) -> Result { + let opts: longbridge::trade::ReplaceOrderOptions = opts.into(); + let ctx = self.ctx.clone(); + env.execute_tokio_future( + async move { + ctx.replace_order(opts) + .await + .map_err(ErrorNewType) + .map_err(napi::Error::from) + }, + |_, _| Ok(()), + ) } /// Submit order @@ -294,34 +320,55 @@ impl TradeContext { /// #### Example /// /// ```javascript - /// const { Config, TradeContext, SubmitOrderOptions, OrderType, OrderSide, Decimal, TimeInForceType } = require("longbridge") + /// const { + /// Config, + /// TradeContext, + /// OrderType, + /// OrderSide, + /// Decimal, + /// TimeInForceType, + /// } = require("longbridge"); /// - /// let config = Config.fromEnv() - /// let opts = new SubmitOrderOptions("700.HK", OrderType.LO, OrderSide.Buy, 200, TimeInForceType.Day) - /// .submittedPrice(new Decimal("300")); + /// let config = Config.fromEnv(); /// TradeContext.new(config) - /// .then((ctx) => ctx.submitOrder(opts)) - /// .then((resp) => console.log(resp)) + /// .then((ctx) => + /// ctx.submitOrder({ + /// symbol: "700.HK", + /// orderType: OrderType.LO, + /// side: OrderSide.Buy, + /// timeInForce: TimeInForceType.Day, + /// submittedQuantity: 200, + /// submittedPrice: new Decimal("300"), + /// }) + /// ) + /// .then((resp) => console.log(resp.toString())); /// ``` - #[napi] - pub async fn submit_order(&self, opts: &SubmitOrderOptions) -> Result { - self.ctx - .submit_order(opts.clone().into()) - .await - .map_err(ErrorNewType)? - .try_into() + #[napi(ts_return_type = "Promise")] + pub fn submit_order(&self, env: Env, opts: SubmitOrderOptions) -> Result { + let opts: longbridge::trade::SubmitOrderOptions = opts.into(); + let ctx = self.ctx.clone(); + env.execute_tokio_future( + async move { + let res = ctx + .submit_order(opts) + .await + .map_err(ErrorNewType) + .map_err(napi::Error::from)?; + res.try_into() + }, + |_, res: SubmitOrderResponse| Ok(res), + ) } - /// Withdraw order + /// Cancel order /// /// #### Example /// /// ```javascript - /// const { Config, TradeContext } = require("longbridge") + /// const { Config, TradeContext } = require("longbridge"); /// - /// let config = Config.fromEnv() - /// TradeContext.new(config) - /// .then((ctx) => ctx.cancelOrder("709043056541253632")) + /// let config = Config.fromEnv(); + /// TradeContext.new(config).then((ctx) => ctx.cancelOrder("709043056541253632")); /// ``` #[napi] pub async fn cancel_order(&self, order_id: String) -> Result<()> { @@ -337,16 +384,16 @@ impl TradeContext { /// #### Example /// /// ```javascript - /// const { Config, TradeContext } = require("longbridge") + /// const { Config, TradeContext } = require("longbridge"); /// - /// let config = Config.fromEnv() + /// let config = Config.fromEnv(); /// TradeContext.new(config) /// .then((ctx) => ctx.accountBalance()) /// .then((resp) => { /// for (let obj of resp) { - /// console.log(obj.toString()) - /// } - /// }) + /// console.log(obj.toString()); + /// } + /// }); /// ``` #[napi] pub async fn account_balance(&self) -> Result> { @@ -364,21 +411,26 @@ impl TradeContext { /// #### Example /// /// ```javascript - /// const { Config, TradeContext, GetCashFlowOptions } = require("longbridge") + /// const { Config, TradeContext, GetCashFlowOptions } = require("longbridge"); /// - /// let config = Config.fromEnv() + /// let config = Config.fromEnv(); /// TradeContext.new(config) - /// .then((ctx) => ctx.cashFlow(new GetCashFlowOptions(new Date(2022, 5, 9), new Date(2022, 5, 12)))) + /// .then((ctx) => + /// ctx.cashFlow({ + /// startAt: new Date(2022, 5, 9), + /// endAt: new Date(2022, 5, 12), + /// }) + /// ) /// .then((resp) => { /// for (let obj of resp) { - /// console.log(obj.toString()) + /// console.log(obj.toString()); /// } - /// }) + /// }); /// ``` #[napi] - pub async fn cash_flow(&self, opts: &GetCashFlowOptions) -> Result> { + pub async fn cash_flow(&self, opts: GetCashFlowOptions) -> Result> { self.ctx - .cash_flow(opts.clone().into()) + .cash_flow(opts.into()) .await .map_err(ErrorNewType)? .into_iter() diff --git a/nodejs/src/trade/requests/get_cash_flow.rs b/nodejs/src/trade/requests/get_cash_flow.rs index 148ec0ef8a..5a7e38cca8 100644 --- a/nodejs/src/trade/requests/get_cash_flow.rs +++ b/nodejs/src/trade/requests/get_cash_flow.rs @@ -2,55 +2,42 @@ use chrono::{DateTime, Utc}; use crate::{trade::types::BalanceType, utils::from_datetime}; -/// Options for submit order request -#[napi_derive::napi] -#[derive(Clone)] -pub struct GetCashFlowOptions(longbridge::trade::GetCashFlowOptions); - +/// Options for get cash flow request #[napi_derive::napi(object)] -impl GetCashFlowOptions { - /// Create a new `GetCashFlowOptions` - #[napi(constructor)] - #[inline] - pub fn new(start_at: DateTime, end_at: DateTime) -> GetCashFlowOptions { - Self(longbridge::trade::GetCashFlowOptions::new( - from_datetime(start_at), - from_datetime(end_at), - )) - } - - /// Set the business type - #[napi] - #[inline] - pub fn business_type(&self, business_type: BalanceType) -> GetCashFlowOptions { - Self(self.0.clone().business_type(business_type.into())) - } - - /// Set the security symbol - #[napi] - #[inline] - pub fn symbol(&self, symbol: String) -> GetCashFlowOptions { - Self(self.0.clone().symbol(symbol)) - } - - /// Set the page number - #[napi] - #[inline] - pub fn page(&self, page: i32) -> GetCashFlowOptions { - Self(self.0.clone().page(page.max(0) as usize)) - } - - /// Set the page size - #[napi] - #[inline] - pub fn size(&self, size: i32) -> GetCashFlowOptions { - Self(self.0.clone().size(size.max(0) as usize)) - } +pub struct GetCashFlowOptions { + /// Start time + pub start_at: DateTime, + /// End time + pub end_at: DateTime, + /// Business type + pub business_type: Option, + /// Security symbol + pub symbol: Option, + /// Page number + pub page: Option, + /// Page size + pub size: Option, } impl From for longbridge::trade::GetCashFlowOptions { #[inline] fn from(opts: GetCashFlowOptions) -> Self { - opts.0 + let mut opts2 = longbridge::trade::GetCashFlowOptions::new( + from_datetime(opts.start_at), + from_datetime(opts.end_at), + ); + if let Some(business_type) = opts.business_type { + opts2 = opts2.business_type(business_type.into()); + } + if let Some(symbol) = opts.symbol { + opts2 = opts2.symbol(symbol); + } + if let Some(page) = opts.page { + opts2 = opts2.page(page.max(0) as usize); + } + if let Some(size) = opts.size { + opts2 = opts2.size(size.max(0) as usize); + } + opts2 } } diff --git a/nodejs/src/trade/requests/get_history_executions.rs b/nodejs/src/trade/requests/get_history_executions.rs index 06a7bc8813..6c9b771498 100644 --- a/nodejs/src/trade/requests/get_history_executions.rs +++ b/nodejs/src/trade/requests/get_history_executions.rs @@ -3,44 +3,28 @@ use chrono::{DateTime, Utc}; use crate::utils::from_datetime; /// Options for get histroy executions request -#[napi_derive::napi] -#[derive(Clone, Default)] -pub struct GetHistoryExecutionsOptions(longbridge::trade::GetHistoryExecutionsOptions); - #[napi_derive::napi(object)] -impl GetHistoryExecutionsOptions { - /// Create a new `GetHistoryExecutionsOptions` - #[napi(constructor)] - #[inline] - pub fn new() -> GetHistoryExecutionsOptions { - Default::default() - } - - /// Set the security symbol - #[napi] - #[inline] - pub fn symbol(&self, symbol: String) -> GetHistoryExecutionsOptions { - Self(self.0.clone().symbol(symbol)) - } - - /// Set the start time - #[napi] - #[inline] - pub fn start_at(&self, start_at: DateTime) -> GetHistoryExecutionsOptions { - Self(self.0.clone().start_at(from_datetime(start_at))) - } - - /// Set the end time - #[napi] - #[inline] - pub fn end_at(&self, end_at: DateTime) -> GetHistoryExecutionsOptions { - Self(self.0.clone().end_at(from_datetime(end_at))) - } +pub struct GetHistoryExecutionsOptions { + /// Security symbol + pub symbol: Option, + /// Start time + pub start_at: Option>, + /// End time + pub end_at: Option>, } impl From for longbridge::trade::GetHistoryExecutionsOptions { - #[inline] fn from(opts: GetHistoryExecutionsOptions) -> Self { - opts.0 + let mut opts2 = longbridge::trade::GetHistoryExecutionsOptions::new(); + if let Some(symbol) = opts.symbol { + opts2 = opts2.symbol(symbol); + } + if let Some(start_at) = opts.start_at { + opts2 = opts2.start_at(from_datetime(start_at)); + } + if let Some(end_at) = opts.end_at { + opts2 = opts2.end_at(from_datetime(end_at)); + } + opts2 } } diff --git a/nodejs/src/trade/requests/get_history_orders.rs b/nodejs/src/trade/requests/get_history_orders.rs index 034850e4e1..4bafd44dd5 100644 --- a/nodejs/src/trade/requests/get_history_orders.rs +++ b/nodejs/src/trade/requests/get_history_orders.rs @@ -1,5 +1,4 @@ use chrono::{DateTime, Utc}; -use napi::Either; use crate::{ trade::types::{OrderSide, OrderStatus}, @@ -8,68 +7,44 @@ use crate::{ }; /// Options for get histroy orders request -#[napi_derive::napi] -#[derive(Clone, Default)] -pub struct GetHistoryOrdersOptions(longbridge::trade::GetHistoryOrdersOptions); - #[napi_derive::napi(object)] -impl GetHistoryOrdersOptions { - /// Create a new `GetHistoryOrdersOptions` - #[napi(constructor)] - #[inline] - pub fn new() -> GetHistoryOrdersOptions { - Default::default() - } - - /// Set the security symbol - #[napi] - #[inline] - pub fn symbol(&self, symbol: String) -> GetHistoryOrdersOptions { - Self(self.0.clone().symbol(symbol)) - } - - /// Set the order status - #[napi] - #[inline] - pub fn status(&self, status: Either>) -> GetHistoryOrdersOptions { - Self(self.0.clone().status(match status { - Either::A(status) => vec![status.into()], - Either::B(status) => status.into_iter().map(Into::into).collect(), - })) - } - - /// Set the order side - #[napi] - #[inline] - pub fn side(&self, side: OrderSide) -> GetHistoryOrdersOptions { - Self(self.0.clone().side(side.into())) - } - - /// Set the market - #[napi] - #[inline] - pub fn market(&self, market: Market) -> GetHistoryOrdersOptions { - Self(self.0.clone().market(market.into())) - } - - /// Set the start time - #[napi] - #[inline] - pub fn start_at(&self, start_at: DateTime) -> GetHistoryOrdersOptions { - Self(self.0.clone().start_at(from_datetime(start_at))) - } - - /// Set the end time - #[napi] - #[inline] - pub fn end_at(&self, end_at: DateTime) -> GetHistoryOrdersOptions { - Self(self.0.clone().end_at(from_datetime(end_at))) - } +pub struct GetHistoryOrdersOptions { + /// Security symbol + pub symbol: Option, + /// Order status + pub status: Option>, + /// Order side + pub side: Option, + /// Market + pub market: Option, + /// Start time + pub start_at: Option>, + /// End time + pub end_at: Option>, } impl From for longbridge::trade::GetHistoryOrdersOptions { #[inline] fn from(opts: GetHistoryOrdersOptions) -> Self { - opts.0 + let mut opts2 = longbridge::trade::GetHistoryOrdersOptions::new(); + if let Some(symbol) = opts.symbol { + opts2 = opts2.symbol(symbol); + } + if let Some(status) = opts.status { + opts2 = opts2.status(status.into_iter().map(Into::into)); + } + if let Some(side) = opts.side { + opts2 = opts2.side(side.into()); + } + if let Some(market) = opts.market { + opts2 = opts2.market(market.into()); + } + if let Some(start_at) = opts.start_at { + opts2 = opts2.start_at(from_datetime(start_at)); + } + if let Some(end_at) = opts.end_at { + opts2 = opts2.end_at(from_datetime(end_at)); + } + opts2 } } diff --git a/nodejs/src/trade/requests/get_today_executions.rs b/nodejs/src/trade/requests/get_today_executions.rs index 610407244d..453e32fecd 100644 --- a/nodejs/src/trade/requests/get_today_executions.rs +++ b/nodejs/src/trade/requests/get_today_executions.rs @@ -1,35 +1,22 @@ -/// Options for get histroy executions request -#[napi_derive::napi] -#[derive(Clone, Default)] -pub struct GetTodayExecutionsOptions(longbridge::trade::GetTodayExecutionsOptions); - +/// Options for get today executions request #[napi_derive::napi(object)] -impl GetTodayExecutionsOptions { - /// Create a new `GetTodayExecutionsOptions` - #[napi(constructor)] - #[inline] - pub fn new() -> GetTodayExecutionsOptions { - Default::default() - } - - /// Set the security symbol - #[napi] - #[inline] - pub fn symbol(&self, symbol: String) -> GetTodayExecutionsOptions { - Self(self.0.clone().symbol(symbol)) - } - - /// Set the order id - #[napi] - #[inline] - pub fn order_id(&self, order_id: String) -> GetTodayExecutionsOptions { - Self(self.0.clone().order_id(order_id)) - } +pub struct GetTodayExecutionsOptions { + /// Security symbol + pub symbol: Option, + /// Order id + pub order_id: Option, } impl From for longbridge::trade::GetTodayExecutionsOptions { #[inline] fn from(opts: GetTodayExecutionsOptions) -> Self { - opts.0 + let mut opts2 = longbridge::trade::GetTodayExecutionsOptions::new(); + if let Some(symbol) = opts.symbol { + opts2 = opts2.symbol(symbol); + } + if let Some(order_id) = opts.order_id { + opts2 = opts2.order_id(order_id); + } + opts2 } } diff --git a/nodejs/src/trade/requests/get_today_orders.rs b/nodejs/src/trade/requests/get_today_orders.rs index 20b31d124c..77f1cad27e 100644 --- a/nodejs/src/trade/requests/get_today_orders.rs +++ b/nodejs/src/trade/requests/get_today_orders.rs @@ -1,66 +1,42 @@ -use napi::Either; - use crate::{ trade::types::{OrderSide, OrderStatus}, types::Market, }; /// Options for get today orders request -#[napi_derive::napi] -#[derive(Clone, Default)] -pub struct GetTodayOrdersOptions(longbridge::trade::GetTodayOrdersOptions); - #[napi_derive::napi(object)] -impl GetTodayOrdersOptions { - /// Create a new `GetTodayOrdersOptions` - #[napi(constructor)] - #[inline] - pub fn new() -> GetTodayOrdersOptions { - Default::default() - } - - /// Set the security symbol - #[napi] - #[inline] - pub fn symbol(&self, symbol: String) -> GetTodayOrdersOptions { - Self(self.0.clone().symbol(symbol)) - } - - /// Set the order status - #[napi] - #[inline] - pub fn status(&self, status: Either>) -> GetTodayOrdersOptions { - Self(self.0.clone().status(match status { - Either::A(status) => vec![status.into()], - Either::B(status) => status.into_iter().map(Into::into).collect(), - })) - } - - /// Set the order side - #[napi] - #[inline] - pub fn side(&self, side: OrderSide) -> GetTodayOrdersOptions { - Self(self.0.clone().side(side.into())) - } - - /// Set the market - #[napi] - #[inline] - pub fn market(&self, market: Market) -> GetTodayOrdersOptions { - Self(self.0.clone().market(market.into())) - } - - /// Set the order id - #[napi] - #[inline] - pub fn order_id(&self, order_id: String) -> GetTodayOrdersOptions { - Self(self.0.clone().order_id(order_id)) - } +pub struct GetTodayOrdersOptions { + /// Security symbol + pub symbol: Option, + /// Order status + pub status: Option>, + /// Order side + pub side: Option, + /// Market + pub market: Option, + /// Order id + pub order_id: Option, } impl From for longbridge::trade::GetTodayOrdersOptions { #[inline] fn from(opts: GetTodayOrdersOptions) -> Self { - opts.0 + let mut opts2 = longbridge::trade::GetTodayOrdersOptions::new(); + if let Some(symbol) = opts.symbol { + opts2 = opts2.symbol(symbol); + } + if let Some(status) = opts.status { + opts2 = opts2.status(status.into_iter().map(Into::into)); + } + if let Some(side) = opts.side { + opts2 = opts2.side(side.into()); + } + if let Some(market) = opts.market { + opts2 = opts2.market(market.into()); + } + if let Some(order_id) = opts.order_id { + opts2 = opts2.order_id(order_id); + } + opts2 } } diff --git a/nodejs/src/trade/requests/replace_order.rs b/nodejs/src/trade/requests/replace_order.rs index 7dcbd812cd..1128b215f0 100644 --- a/nodejs/src/trade/requests/replace_order.rs +++ b/nodejs/src/trade/requests/replace_order.rs @@ -1,67 +1,50 @@ -use crate::decimal::Decimal; +use napi::bindgen_prelude::ClassInstance; -/// Options for get today orders request -#[napi_derive::napi] -#[derive(Clone)] -pub struct ReplaceOrderOptions(longbridge::trade::ReplaceOrderOptions); +use crate::decimal::Decimal; +/// Options for replace order request #[napi_derive::napi(object)] -impl ReplaceOrderOptions { - /// Create a new `ReplaceOrderOptions` - #[napi(constructor)] - #[inline] - pub fn new(order_id: String, quantity: i64) -> ReplaceOrderOptions { - Self(longbridge::trade::ReplaceOrderOptions::new( - order_id, quantity, - )) - } - - /// Set the price - #[napi] - #[inline] - pub fn price(&self, price: &Decimal) -> ReplaceOrderOptions { - Self(self.0.clone().price(price.0)) - } - - /// Set the trigger price - #[napi] - #[inline] - pub fn trigger_price(&self, trigger_price: &Decimal) -> Self { - Self(self.0.clone().trigger_price(trigger_price.0)) - } - - /// Set the limit offset - #[napi] - #[inline] - pub fn limit_offset(&self, limit_offset: &Decimal) -> Self { - Self(self.0.clone().limit_offset(limit_offset.0)) - } - - /// Set the trailing amount - #[napi] - #[inline] - pub fn trailing_amount(&self, trailing_amount: &Decimal) -> Self { - Self(self.0.clone().trailing_amount(trailing_amount.0)) - } - - /// Set the trailing percent - #[napi] - #[inline] - pub fn trailing_percent(&self, trailing_percent: &Decimal) -> Self { - Self(self.0.clone().trailing_percent(trailing_percent.0)) - } - - /// Set the remark - #[napi] - #[inline] - pub fn remark(&self, remark: String) -> Self { - Self(self.0.clone().remark(remark)) - } +pub struct ReplaceOrderOptions { + /// Order id + pub order_id: String, + /// Replaced quantity + pub quantity: i64, + /// Replaced price + pub price: Option>, + /// Trigger price (`LIT` / `MIT` Order Required) + pub trigger_price: Option>, + /// Limit offset amount (`TSLPAMT` / `TSLPPCT` Required) + pub limit_offset: Option>, + /// Trailing amount (`TSLPAMT` / `TSMAMT` Required) + pub trailing_amount: Option>, + /// Trailing percent (`TSLPPCT` / `TSMAPCT` Required) + pub trailing_percent: Option>, + /// Remark (Maximum 64 characters) + pub remark: Option, } impl From for longbridge::trade::ReplaceOrderOptions { #[inline] fn from(opts: ReplaceOrderOptions) -> Self { - opts.0 + let mut opts2 = longbridge::trade::ReplaceOrderOptions::new(opts.order_id, opts.quantity); + if let Some(price) = opts.price { + opts2 = opts2.price(price.0); + } + if let Some(trigger_price) = opts.trigger_price { + opts2 = opts2.trigger_price(trigger_price.0); + } + if let Some(limit_offset) = opts.limit_offset { + opts2 = opts2.limit_offset(limit_offset.0); + } + if let Some(trailing_amount) = opts.trailing_amount { + opts2 = opts2.trailing_amount(trailing_amount.0); + } + if let Some(trailing_percent) = opts.trailing_percent { + opts2 = opts2.trailing_percent(trailing_percent.0); + } + if let Some(remark) = opts.remark { + opts2 = opts2.remark(remark); + } + opts2 } } diff --git a/nodejs/src/trade/requests/submit_order.rs b/nodejs/src/trade/requests/submit_order.rs index 70b7be46ed..ba78b7e9cc 100644 --- a/nodejs/src/trade/requests/submit_order.rs +++ b/nodejs/src/trade/requests/submit_order.rs @@ -1,3 +1,5 @@ +use napi::bindgen_prelude::ClassInstance; + use crate::{ decimal::Decimal, time::NaiveDate, @@ -5,91 +7,71 @@ use crate::{ }; /// Options for submit order request -#[napi_derive::napi] -#[derive(Clone)] -pub struct SubmitOrderOptions(longbridge::trade::SubmitOrderOptions); - -#[napi_derive::napi] -impl SubmitOrderOptions { - /// Create a new `SubmitOrderOptions` - #[napi(constructor)] - #[inline] - pub fn new( - symbol: String, - order_type: OrderType, - side: OrderSide, - submitted_quantity: i64, - time_in_force: TimeInForceType, - ) -> SubmitOrderOptions { - Self(longbridge::trade::SubmitOrderOptions::new( - symbol, - order_type.into(), - side.into(), - submitted_quantity, - time_in_force.into(), - )) - } - - /// Set the submitted price - #[napi] - #[inline] - pub fn submitted_price(&self, submitted_price: &Decimal) -> SubmitOrderOptions { - Self(self.0.clone().submitted_price(submitted_price.0)) - } - - /// Set the trigger price - #[napi] - #[inline] - pub fn trigger_price(&self, trigger_price: &Decimal) -> SubmitOrderOptions { - Self(self.0.clone().trigger_price(trigger_price.0)) - } - - /// Set the limit offset - #[napi] - #[inline] - pub fn limit_offset(&self, limit_offset: &Decimal) -> SubmitOrderOptions { - Self(self.0.clone().limit_offset(limit_offset.0)) - } - - /// Set the trailing amount - #[napi] - #[inline] - pub fn trailing_amount(&self, trailing_amount: &Decimal) -> SubmitOrderOptions { - Self(self.0.clone().trailing_amount(trailing_amount.0)) - } - - /// Set the trailing percent - #[napi] - #[inline] - pub fn trailing_percent(&self, trailing_percent: &Decimal) -> SubmitOrderOptions { - Self(self.0.clone().trailing_percent(trailing_percent.0)) - } - - /// Set the expire date - #[napi] - #[inline] - pub fn expire_date(&self, expire_date: &NaiveDate) -> SubmitOrderOptions { - Self(self.0.clone().expire_date(expire_date.0)) - } - +#[napi_derive::napi(object)] +pub struct SubmitOrderOptions { + /// Security code + pub symbol: String, + /// Order type + pub order_type: OrderType, + /// Order side + pub side: OrderSide, + /// Submitted quantity + pub submitted_quantity: i64, + /// Time in force type + pub time_in_force: TimeInForceType, + /// Submitted price + pub submitted_price: Option>, + /// Trigger price (`LIT` / `MIT` Required) + pub trigger_price: Option>, + /// Limit offset amount (`TSLPAMT` / `TSLPPCT` Required) + pub limit_offset: Option>, + /// Trailing amount (`TSLPAMT` / `TSMAMT` Required) + pub trailing_amount: Option>, + /// Trailing percent (`TSLPPCT` / `TSMAPCT` Required) + pub trailing_percent: Option>, + /// Long term order expire date (Required when `time_in_force` is + /// `GoodTilDate`) + pub expire_date: Option>, /// Enable or disable outside regular trading hours - #[napi] - #[inline] - pub fn outside_rth(&self, outside_rth: OutsideRTH) -> SubmitOrderOptions { - Self(self.0.clone().outside_rth(outside_rth.into())) - } - - /// Set the remark - #[napi] - #[inline] - pub fn remark(&self, remark: String) -> SubmitOrderOptions { - Self(self.0.clone().remark(remark)) - } + pub outside_rth: Option, + /// Remark (Maximum 64 characters) + pub remark: Option, } impl From for longbridge::trade::SubmitOrderOptions { #[inline] fn from(opts: SubmitOrderOptions) -> Self { - opts.0 + let mut opts2 = longbridge::trade::SubmitOrderOptions::new( + opts.symbol, + opts.order_type.into(), + opts.side.into(), + opts.submitted_quantity, + opts.time_in_force.into(), + ); + if let Some(submitted_price) = opts.submitted_price { + opts2 = opts2.submitted_price(submitted_price.0); + } + if let Some(trigger_price) = opts.trigger_price { + opts2 = opts2.trigger_price(trigger_price.0); + } + if let Some(limit_offset) = opts.limit_offset { + opts2 = opts2.limit_offset(limit_offset.0); + } + if let Some(trailing_amount) = opts.trailing_amount { + opts2 = opts2.trailing_amount(trailing_amount.0); + } + if let Some(trailing_percent) = opts.trailing_percent { + opts2 = opts2.trailing_percent(trailing_percent.0); + } + if let Some(expire_date) = opts.expire_date { + opts2 = opts2.expire_date(expire_date.0); + } + if let Some(outside_rth) = opts.outside_rth { + opts2 = opts2.outside_rth(outside_rth.into()); + } + if let Some(remark) = opts.remark { + opts2 = opts2.remark(remark); + } + opts2 } } From d89607d0ad5283dae8b95207416c5a8da1a7eda5 Mon Sep 17 00:00:00 2001 From: Sunli Date: Mon, 27 Jun 2022 13:20:52 +0800 Subject: [PATCH 096/567] Release 0.2.23 longbridge@0.2.23 longbridge-candlesticks@0.2.23 longbridge-httpcli@0.2.23 longbridge-java@0.2.23 longbridge-java-macros@0.2.23 longbridge-nodejs@0.2.23 longbridge-nodejs-macros@0.2.23 longbridge-proto@0.2.23 longbridge-python@0.2.23 longbridge-python-macros@0.2.23 longbridge-wscli@0.2.23 Generated by cargo-workspaces --- java/Cargo.toml | 2 +- java/crates/macros/Cargo.toml | 2 +- nodejs/Cargo.toml | 2 +- nodejs/crates/macros/Cargo.toml | 2 +- python/Cargo.toml | 2 +- python/Makefile.toml | 2 +- python/crates/macros/Cargo.toml | 2 +- rust/Cargo.toml | 10 +++++----- rust/crates/candlesticks/Cargo.toml | 2 +- rust/crates/httpclient/Cargo.toml | 2 +- rust/crates/proto/Cargo.toml | 2 +- rust/crates/wsclient/Cargo.toml | 4 ++-- 12 files changed, 17 insertions(+), 17 deletions(-) diff --git a/java/Cargo.toml b/java/Cargo.toml index 848f09b91e..67dffaf0be 100644 --- a/java/Cargo.toml +++ b/java/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge-java" -version = "0.2.22" +version = "0.2.23" [lib] crate-type = ["cdylib"] diff --git a/java/crates/macros/Cargo.toml b/java/crates/macros/Cargo.toml index 2d98d863fa..dace00f97e 100644 --- a/java/crates/macros/Cargo.toml +++ b/java/crates/macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "longbridge-java-macros" -version = "0.2.22" +version = "0.2.23" edition = "2021" [lib] diff --git a/nodejs/Cargo.toml b/nodejs/Cargo.toml index f408a13c0d..14b9184d47 100644 --- a/nodejs/Cargo.toml +++ b/nodejs/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge-nodejs" -version = "0.2.22" +version = "0.2.23" [lib] crate-type = ["cdylib"] diff --git a/nodejs/crates/macros/Cargo.toml b/nodejs/crates/macros/Cargo.toml index ea92bba40b..e97e0da174 100644 --- a/nodejs/crates/macros/Cargo.toml +++ b/nodejs/crates/macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "longbridge-nodejs-macros" -version = "0.2.22" +version = "0.2.23" edition = "2021" [lib] diff --git a/python/Cargo.toml b/python/Cargo.toml index 3dedbe0276..c01738e57b 100644 --- a/python/Cargo.toml +++ b/python/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge-python" -version = "0.2.22" +version = "0.2.23" description = "Longbridge OpenAPI SDK for Python" homepage = "https://open.longbridgeapp.com/en/" readme = "README.md" diff --git a/python/Makefile.toml b/python/Makefile.toml index ac2d18d1a7..baca2a7cf9 100644 --- a/python/Makefile.toml +++ b/python/Makefile.toml @@ -12,7 +12,7 @@ args = ["install", "maturin>=0.12,<0.13"] command = "pip" args = [ "install", - "target/wheels/longbridge-0.2.22-cp310-none-win_amd64.whl", + "target/wheels/longbridge-0.2.23-cp310-none-win_amd64.whl", "-I", ] dependencies = ["python"] diff --git a/python/crates/macros/Cargo.toml b/python/crates/macros/Cargo.toml index 01459d9310..0e3eb3fcb8 100644 --- a/python/crates/macros/Cargo.toml +++ b/python/crates/macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "longbridge-python-macros" -version = "0.2.22" +version = "0.2.23" edition = "2021" [lib] diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 3e472ecaf8..ca93a46c6d 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge" -version = "0.2.22" +version = "0.2.23" description = "Longbridge OpenAPI SDK for Rust" homepage = "https://open.longbridgeapp.com/en/" readme = "README.md" @@ -14,10 +14,10 @@ categories = ["api-bindings"] blocking = ["flume"] [dependencies] -longbridge-wscli = { path = "crates/wsclient", version = "0.2.22" } -longbridge-httpcli = { path = "crates/httpclient", version = "0.2.22" } -longbridge-proto = { path = "crates/proto", version = "0.2.22" } -longbridge-candlesticks = { path = "crates/candlesticks", version = "0.2.22" } +longbridge-wscli = { path = "crates/wsclient", version = "0.2.23" } +longbridge-httpcli = { path = "crates/httpclient", version = "0.2.23" } +longbridge-proto = { path = "crates/proto", version = "0.2.23" } +longbridge-candlesticks = { path = "crates/candlesticks", version = "0.2.23" } tokio = { version = "1.18.2", features = [ "time", diff --git a/rust/crates/candlesticks/Cargo.toml b/rust/crates/candlesticks/Cargo.toml index 478d1af55e..ab9c3ba0a0 100644 --- a/rust/crates/candlesticks/Cargo.toml +++ b/rust/crates/candlesticks/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge-candlesticks" -version = "0.2.22" +version = "0.2.23" description = "Longbridge candlestick utils for Rust" license = "MIT OR Apache-2.0" diff --git a/rust/crates/httpclient/Cargo.toml b/rust/crates/httpclient/Cargo.toml index 003076bfcc..f25637062c 100644 --- a/rust/crates/httpclient/Cargo.toml +++ b/rust/crates/httpclient/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge-httpcli" -version = "0.2.22" +version = "0.2.23" description = "Longbridge HTTP SDK for Rust" license = "MIT OR Apache-2.0" diff --git a/rust/crates/proto/Cargo.toml b/rust/crates/proto/Cargo.toml index f8d5ce98b5..08af353da1 100644 --- a/rust/crates/proto/Cargo.toml +++ b/rust/crates/proto/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge-proto" -version = "0.2.22" +version = "0.2.23" description = "Longbridge Protocol" license = "MIT OR Apache-2.0" diff --git a/rust/crates/wsclient/Cargo.toml b/rust/crates/wsclient/Cargo.toml index ca50a7208c..7fd6780cde 100644 --- a/rust/crates/wsclient/Cargo.toml +++ b/rust/crates/wsclient/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "longbridge-wscli" -version = "0.2.22" +version = "0.2.23" edition = "2021" description = "Longbridge Websocket SDK for Rust" license = "MIT OR Apache-2.0" [dependencies] -longbridge-proto = { path = "../proto", version = "0.2.21" } +longbridge-proto = { path = "../proto", version = "0.2.23" } tokio = { version = "1.18.2", features = [ "time", From 18ca440eac5b63bf6a9f054fb631ef7b4f8307bf Mon Sep 17 00:00:00 2001 From: Sunli Date: Tue, 28 Jun 2022 14:37:31 +0800 Subject: [PATCH 097/567] Change the `stock_derivatives` field type of `SecurityStaticInfo` to bit flags --- java/crates/macros/src/lib.rs | 8 +++- java/src/types/classes.rs | 2 +- java/src/types/enum_types.rs | 64 +++++++++++++++++++++++++--- nodejs/crates/macros/src/jsobject.rs | 9 ++++ nodejs/src/quote/types.rs | 20 +++++++-- python/crates/macros/src/pyobject.rs | 6 +++ python/src/quote/types.rs | 38 +++++++++++------ rust/src/quote/types.rs | 34 ++++++++------- 8 files changed, 144 insertions(+), 37 deletions(-) diff --git a/java/crates/macros/src/lib.rs b/java/crates/macros/src/lib.rs index 3b84d315b7..68359b497c 100644 --- a/java/crates/macros/src/lib.rs +++ b/java/crates/macros/src/lib.rs @@ -14,6 +14,8 @@ struct FieldArgs { objarray: bool, #[darling(default)] priarray: bool, + #[darling(default)] + derivative_types: bool, } #[derive(FromMeta, Debug, Default)] @@ -105,7 +107,11 @@ pub fn impl_java_class(input: TokenStream) -> TokenStream { }; let java_field = ident.to_string().to_camel_case(); - if args.objarray { + if args.derivative_types { + set_fields.push(quote! { + crate::types::set_field(env, obj, #java_field, crate::types::enum_types::DerivativeTypes::from(#ident))?; + }); + } else if args.objarray { set_fields.push(quote! { crate::types::set_field(env, obj, #java_field, crate::types::ObjectArray(#ident))?; }); diff --git a/java/src/types/classes.rs b/java/src/types/classes.rs index 6326377aea..ce8b24a56f 100644 --- a/java/src/types/classes.rs +++ b/java/src/types/classes.rs @@ -111,7 +111,7 @@ impl_java_class!( eps_ttm, bps, dividend_yield, - #[java(objarray)] + #[java(derivative_types)] stock_derivatives ] ); diff --git a/java/src/types/enum_types.rs b/java/src/types/enum_types.rs index 26af56e9f5..0a71de42df 100644 --- a/java/src/types/enum_types.rs +++ b/java/src/types/enum_types.rs @@ -1,5 +1,15 @@ +use std::borrow::Cow; + +use jni::{ + descriptors::Desc, + errors::Result, + objects::{JClass, JObject, JValue}, + JNIEnv, +}; use longbridge_java_macros::impl_java_enum; +use crate::types::{IntoJValue, JSignature}; + impl_java_enum!( "com/longbridge/Market", longbridge::Market, @@ -36,11 +46,55 @@ impl_java_enum!( [Neutral, Down, Up] ); -impl_java_enum!( - "com/longbridge/quote/DerivativeType", - longbridge::quote::DerivativeType, - [Option, Warrant] -); +#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)] +enum DerivativeType { + Option, + Warrant, +} + +pub(crate) struct DerivativeTypes(Vec); + +impl From for DerivativeTypes { + fn from(ty: longbridge::quote::DerivativeType) -> Self { + let mut res = Vec::new(); + if ty.contains(longbridge::quote::DerivativeType::OPTION) { + res.push(DerivativeType::Option); + } + if ty.contains(longbridge::quote::DerivativeType::WARRANT) { + res.push(DerivativeType::Warrant); + } + DerivativeTypes(res) + } +} + +impl JSignature for DerivativeTypes { + fn signature() -> Cow<'static, str> { + concat!("[L", "com/longbridge/quote/DerivativeType", ";").into() + } +} + +impl IntoJValue for DerivativeTypes { + fn into_jvalue<'a>(self, env: &JNIEnv<'a>) -> Result> { + let cls: JClass = "com/longbridge/quote/DerivativeType".lookup(env)?; + let array = env.new_object_array(self.0.len() as i32, cls, JObject::null())?; + for (i, obj) in self.0.into_iter().enumerate() { + let value = match obj { + DerivativeType::Option => env.get_static_field( + cls, + "Option", + concat!("L", "com/longbridge/quote/DerivativeType", ";"), + )?, + DerivativeType::Warrant => env.get_static_field( + cls, + "Warrant", + concat!("L", "com/longbridge/quote/DerivativeType", ";"), + )?, + }; + env.set_object_array_element(array, i as i32, value.l()?)?; + } + Ok(array.into()) + } +} impl_java_enum!( "com/longbridge/quote/OptionType", diff --git a/nodejs/crates/macros/src/jsobject.rs b/nodejs/crates/macros/src/jsobject.rs index 34f9aba70e..de8378e678 100644 --- a/nodejs/crates/macros/src/jsobject.rs +++ b/nodejs/crates/macros/src/jsobject.rs @@ -20,6 +20,8 @@ struct ObjectField { datetime: bool, #[darling(default)] sub_types: bool, + #[darling(default)] + derivative_types: bool, } #[derive(FromDeriveInput)] @@ -69,6 +71,13 @@ pub(crate) fn generate(args: DeriveInput) -> GeneratorResult { continue; } + if field.derivative_types { + from_fields.push(quote! { + #field_ident: crate::quote::types::DerivativeTypes::from(#field_ident).0, + }); + continue; + } + match (field.array, field.opt, field.datetime) { (true, false, false) => { from_fields.push(quote! { diff --git a/nodejs/src/quote/types.rs b/nodejs/src/quote/types.rs index e8fc7bc82a..95e8a609f1 100644 --- a/nodejs/src/quote/types.rs +++ b/nodejs/src/quote/types.rs @@ -23,8 +23,7 @@ pub struct Subscription { /// Derivative type #[napi_derive::napi] -#[derive(JsEnum, Debug, Hash, Eq, PartialEq)] -#[js(remote = "longbridge::quote::DerivativeType")] +#[derive(Debug, Hash, Eq, PartialEq)] pub enum DerivativeType { /// US stock options Option, @@ -32,6 +31,21 @@ pub enum DerivativeType { Warrant, } +struct DerivativeTypes(Vec); + +impl From for DerivativeTypes { + fn from(ty: longbridge::quote::DerivativeType) -> Self { + let mut res = Vec::new(); + if ty.contains(longbridge::quote::DerivativeType::OPTION) { + res.push(DerivativeType::Option); + } + if ty.contains(longbridge::quote::DerivativeType::WARRANT) { + res.push(DerivativeType::Warrant); + } + DerivativeTypes(res) + } +} + #[napi_derive::napi] #[derive(JsEnum, Debug, Hash, Eq, PartialEq)] #[js(remote = "longbridge::quote::TradeStatus")] @@ -267,7 +281,7 @@ pub struct SecurityStaticInfo { /// Dividend yield dividend_yield: Decimal, /// Types of supported derivatives - #[js(array)] + #[js(derivative_types)] stock_derivatives: Vec, } diff --git a/python/crates/macros/src/pyobject.rs b/python/crates/macros/src/pyobject.rs index fa1050f0bb..6b1192c5ed 100644 --- a/python/crates/macros/src/pyobject.rs +++ b/python/crates/macros/src/pyobject.rs @@ -17,6 +17,8 @@ struct ObjectField { opt: bool, #[darling(default)] sub_types: bool, + #[darling(default)] + derivative_types: bool, } #[derive(FromDeriveInput)] @@ -61,6 +63,10 @@ pub(crate) fn generate(args: DeriveInput) -> GeneratorResult { from_fields.push(quote! { #field_ident: crate::quote::types::SubTypes::from(#field_ident).0, }); + } else if field.derivative_types { + from_fields.push(quote! { + #field_ident: crate::quote::types::DerivativeTypes::from(#field_ident).0, + }); } else if field.array { from_fields.push(quote! { #field_ident: #field_ident diff --git a/python/src/quote/types.rs b/python/src/quote/types.rs index f1847d81cb..86aa5a6a7b 100644 --- a/python/src/quote/types.rs +++ b/python/src/quote/types.rs @@ -20,17 +20,6 @@ pub(crate) struct Subscription { candlesticks: Vec, } -/// Derivative type -#[pyclass] -#[derive(PyEnum, Debug, Copy, Clone, Hash, Eq, PartialEq)] -#[py(remote = "longbridge::quote::DerivativeType")] -pub(crate) enum DerivativeType { - /// US stock options - Option, - /// HK warrants - Warrant, -} - #[pyclass] #[derive(PyEnum, Debug, Copy, Clone, Hash, Eq, PartialEq)] #[py(remote = "longbridge::quote::TradeStatus")] @@ -232,6 +221,31 @@ pub(crate) enum AdjustType { ForwardAdjust, } +/// Derivative type +#[pyclass] +#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)] +pub(crate) enum DerivativeType { + /// US stock options + Option, + /// HK warrants + Warrant, +} + +struct DerivativeTypes(Vec); + +impl From for DerivativeTypes { + fn from(ty: longbridge::quote::DerivativeType) -> Self { + let mut res = Vec::new(); + if ty.contains(longbridge::quote::DerivativeType::OPTION) { + res.push(DerivativeType::Option); + } + if ty.contains(longbridge::quote::DerivativeType::WARRANT) { + res.push(DerivativeType::Warrant); + } + DerivativeTypes(res) + } +} + /// The basic information of securities #[pyclass] #[derive(Debug, PyObject)] @@ -266,7 +280,7 @@ pub(crate) struct SecurityStaticInfo { /// Dividend yield dividend_yield: PyDecimal, /// Types of supported derivatives - #[py(array)] + #[py(derivative_types)] stock_derivatives: Vec, } diff --git a/rust/src/quote/types.rs b/rust/src/quote/types.rs index fe1c1edfec..eae3e2863d 100644 --- a/rust/src/quote/types.rs +++ b/rust/src/quote/types.rs @@ -1,5 +1,5 @@ use longbridge_proto::quote::{self, Period, TradeSession, TradeStatus}; -use num_enum::{FromPrimitive, IntoPrimitive, TryFromPrimitive}; +use num_enum::{FromPrimitive, IntoPrimitive}; use rust_decimal::Decimal; use strum_macros::EnumString; use time::{Date, OffsetDateTime, Time}; @@ -141,14 +141,15 @@ impl TryFrom for Trade { } } -/// Derivative type -#[derive(Debug, TryFromPrimitive)] -#[repr(i32)] -pub enum DerivativeType { - /// US stock options - Option = 1, - /// HK warrants - Warrant = 2, +bitflags::bitflags! { + /// Derivative type + pub struct DerivativeType: u8 { + /// Quote + const OPTION = 0x1; + + /// Depth + const WARRANT = 0x2; + } } /// The basic information of securities @@ -183,7 +184,7 @@ pub struct SecurityStaticInfo { /// Dividend yield pub dividend_yield: Decimal, /// Types of supported derivatives - pub stock_derivatives: Vec, + pub stock_derivatives: DerivativeType, } impl TryFrom for SecurityStaticInfo { @@ -205,11 +206,14 @@ impl TryFrom for SecurityStaticInfo { eps_ttm: resp.eps_ttm.parse().unwrap_or_default(), bps: resp.bps.parse().unwrap_or_default(), dividend_yield: resp.dividend_yield.parse().unwrap_or_default(), - stock_derivatives: resp - .stock_derivatives - .into_iter() - .filter_map(|x| x.try_into().ok()) - .collect(), + stock_derivatives: resp.stock_derivatives.into_iter().fold( + DerivativeType::empty(), + |acc, value| match value { + 1 => acc | DerivativeType::OPTION, + 2 => acc | DerivativeType::WARRANT, + _ => acc, + }, + ), }) } } From f38548d478fb07f33ad089ec448a2db1ad021c0c Mon Sep 17 00:00:00 2001 From: Sunli Date: Wed, 29 Jun 2022 10:24:54 +0800 Subject: [PATCH 098/567] Add `User-Agent` header to HTTP request --- rust/crates/httpclient/src/request.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rust/crates/httpclient/src/request.rs b/rust/crates/httpclient/src/request.rs index eb9ead0f10..f6c85a9a0d 100644 --- a/rust/crates/httpclient/src/request.rs +++ b/rust/crates/httpclient/src/request.rs @@ -9,6 +9,7 @@ use crate::{ HttpClient, HttpClientError, HttpClientResult, }; +const USER_AGENT: &str = "openapi-sdk"; const REQUEST_TIMEOUT: Duration = Duration::from_secs(30); const RETRY_COUNT: usize = 5; const RETRY_INITIAL_DELAY: Duration = Duration::from_millis(100); @@ -113,6 +114,7 @@ where self.method.clone(), &format!("{}{}", config.http_url, self.path), ) + .header("User-Agent", USER_AGENT) .header("X-Api-Key", app_key_value) .header("Authorization", access_token_value) .header("X-Timestamp", now.to_string()) From 9fdba77ef0e1e94b72e0047a3956a91b7c3ddbee Mon Sep 17 00:00:00 2001 From: Sunli Date: Thu, 30 Jun 2022 09:21:24 +0800 Subject: [PATCH 099/567] Update CI --- .github/workflows/ci.yml | 20 -------------------- .github/workflows/docs.yml | 4 ---- .github/workflows/release.yml | 30 ------------------------------ rust/Makefile.toml | 26 ++++++++++++++++++++++++++ rust/crates/proto/Cargo.toml | 3 --- rust/crates/proto/build.rs | 18 ------------------ 6 files changed, 26 insertions(+), 75 deletions(-) delete mode 100644 rust/crates/proto/build.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cc81f28212..b75d1920ca 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,11 +36,6 @@ jobs: with: submodules: true - - name: Install Protoc - uses: arduino/setup-protoc@v1 - with: - repo-token: ${{ github.token }} - - name: Install Rust uses: actions-rs/toolchain@v1 with: @@ -68,11 +63,6 @@ jobs: with: submodules: true - - name: Install Protoc - uses: arduino/setup-protoc@v1 - with: - repo-token: ${{ github.token }} - - name: Install Python uses: actions/setup-python@v2 with: @@ -137,11 +127,6 @@ jobs: check-latest: true architecture: ${{ matrix.settings.architecture }} - - name: Install Protoc - uses: arduino/setup-protoc@v1 - with: - repo-token: ${{ github.token }} - - name: Install Rust uses: actions-rs/toolchain@v1 with: @@ -177,11 +162,6 @@ jobs: with: submodules: true - - name: Install Protoc - uses: arduino/setup-protoc@v1 - with: - repo-token: ${{ github.token }} - - name: Install JDK uses: actions/setup-java@v3 with: diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index bbf18933ff..a328ed9400 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -9,10 +9,6 @@ jobs: name: Build API docs runs-on: ubuntu-latest steps: - - name: Install Protoc - uses: arduino/setup-protoc@v1 - with: - repo-token: ${{ github.token }} - name: Checkout uses: actions/checkout@v3 with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9c5b4eb764..d0dd851071 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -28,10 +28,6 @@ jobs: registryName: longbridge path: rust steps: - - name: Install Protoc - uses: arduino/setup-protoc@v1 - with: - repo-token: ${{ github.token }} - name: Checkout uses: actions/checkout@v3 with: @@ -69,10 +65,6 @@ jobs: matrix: python-version: ["3.7", "3.8", "3.9", "3.10"] steps: - - name: Install Protoc - uses: arduino/setup-protoc@v1 - with: - repo-token: ${{ github.token }} - name: Checkout uses: actions/checkout@v3 with: @@ -118,10 +110,6 @@ jobs: python-version: ["3.7", "3.8", "3.9", "3.10"] target: [x64, x86] steps: - - name: Install Protoc - uses: arduino/setup-protoc@v1 - with: - repo-token: ${{ github.token }} - name: Checkout uses: actions/checkout@v3 with: @@ -159,10 +147,6 @@ jobs: python-version: ["3.7", "3.8", "3.9", "3.10"] target: [x86_64, i686] steps: - - name: Install Protoc - uses: arduino/setup-protoc@v1 - with: - repo-token: ${{ github.token }} - name: Checkout uses: actions/checkout@v3 with: @@ -195,10 +179,6 @@ jobs: python-version: ["3.7", "3.8", "3.9", "3.10"] target: [aarch64] steps: - - name: Install Protoc - uses: arduino/setup-protoc@v1 - with: - repo-token: ${{ github.token }} - name: Checkout uses: actions/checkout@v3 with: @@ -303,11 +283,6 @@ jobs: check-latest: true architecture: ${{ matrix.settings.architecture }} - - name: Install Protoc - uses: arduino/setup-protoc@v1 - with: - repo-token: ${{ github.token }} - - name: Install Rust uses: actions-rs/toolchain@v1 with: @@ -397,11 +372,6 @@ jobs: with: submodules: true - - name: Install Protoc - uses: arduino/setup-protoc@v1 - with: - repo-token: ${{ github.token }} - - name: Install Rust uses: actions-rs/toolchain@v1 with: diff --git a/rust/Makefile.toml b/rust/Makefile.toml index 7c69127bac..9ff67c4299 100644 --- a/rust/Makefile.toml +++ b/rust/Makefile.toml @@ -1,3 +1,29 @@ [tasks.rust] command = "cargo" args = ["build", "-p", "longbridge"] + +[tasks.protoc] +script_runner = "@rust" +script = ''' +//! ```cargo +//! [dependencies] +//! prost-build = "0.10.3" +//! ``` +use prost_build::Config; + +fn main() { + Config::new() + .out_dir("rust/crates/proto/src") + .protoc_arg("--experimental_allow_proto3_optional") + .compile_protos( + &[ + "rust/crates/proto/openapi-protobufs/control/control.proto", + "rust/crates/proto/openapi-protobufs/quote/api.proto", + "rust/crates/proto/openapi-protobufs/trade/subscribe.proto", + "rust/crates/proto/error/error.proto", + ], + &["rust/crates/proto/openapi-protobufs", "rust/crates/proto/error"], + ) + .unwrap(); +} +''' diff --git a/rust/crates/proto/Cargo.toml b/rust/crates/proto/Cargo.toml index 08af353da1..1bf5262040 100644 --- a/rust/crates/proto/Cargo.toml +++ b/rust/crates/proto/Cargo.toml @@ -7,6 +7,3 @@ license = "MIT OR Apache-2.0" [dependencies] prost = "0.10.3" - -[build-dependencies] -prost-build = "0.10.3" diff --git a/rust/crates/proto/build.rs b/rust/crates/proto/build.rs deleted file mode 100644 index 0fe06ab8f3..0000000000 --- a/rust/crates/proto/build.rs +++ /dev/null @@ -1,18 +0,0 @@ -use prost_build::Config; - -fn main() { - Config::new() - .out_dir("./src") - .protoc_arg("--experimental_allow_proto3_optional") - .compile_protos( - &[ - "./openapi-protobufs/control/control.proto", - "./openapi-protobufs/quote/api.proto", - "./openapi-protobufs/trade/subscribe.proto", - "./error/error.proto", - ], - &["./openapi-protobufs", "./error"], - ) - .unwrap(); - println!("cargo:rerun-if-changed=openapi-protobufs"); -} From 1faf4f6ea641b28050292eb0cadf2546c838d3e7 Mon Sep 17 00:00:00 2001 From: Sunli Date: Thu, 30 Jun 2022 09:22:50 +0800 Subject: [PATCH 100/567] Release 0.2.24 longbridge@0.2.24 longbridge-candlesticks@0.2.24 longbridge-httpcli@0.2.24 longbridge-java@0.2.24 longbridge-java-macros@0.2.24 longbridge-nodejs@0.2.24 longbridge-nodejs-macros@0.2.24 longbridge-proto@0.2.24 longbridge-python@0.2.24 longbridge-python-macros@0.2.24 longbridge-wscli@0.2.24 Generated by cargo-workspaces --- java/Cargo.toml | 2 +- java/crates/macros/Cargo.toml | 2 +- nodejs/Cargo.toml | 2 +- nodejs/crates/macros/Cargo.toml | 2 +- python/Cargo.toml | 2 +- python/Makefile.toml | 2 +- python/crates/macros/Cargo.toml | 2 +- rust/Cargo.toml | 10 +++++----- rust/crates/candlesticks/Cargo.toml | 2 +- rust/crates/httpclient/Cargo.toml | 2 +- rust/crates/proto/Cargo.toml | 2 +- rust/crates/wsclient/Cargo.toml | 4 ++-- 12 files changed, 17 insertions(+), 17 deletions(-) diff --git a/java/Cargo.toml b/java/Cargo.toml index 67dffaf0be..f9d668f144 100644 --- a/java/Cargo.toml +++ b/java/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge-java" -version = "0.2.23" +version = "0.2.24" [lib] crate-type = ["cdylib"] diff --git a/java/crates/macros/Cargo.toml b/java/crates/macros/Cargo.toml index dace00f97e..6a1ff532d9 100644 --- a/java/crates/macros/Cargo.toml +++ b/java/crates/macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "longbridge-java-macros" -version = "0.2.23" +version = "0.2.24" edition = "2021" [lib] diff --git a/nodejs/Cargo.toml b/nodejs/Cargo.toml index 14b9184d47..4875f33e16 100644 --- a/nodejs/Cargo.toml +++ b/nodejs/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge-nodejs" -version = "0.2.23" +version = "0.2.24" [lib] crate-type = ["cdylib"] diff --git a/nodejs/crates/macros/Cargo.toml b/nodejs/crates/macros/Cargo.toml index e97e0da174..d26b462f1a 100644 --- a/nodejs/crates/macros/Cargo.toml +++ b/nodejs/crates/macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "longbridge-nodejs-macros" -version = "0.2.23" +version = "0.2.24" edition = "2021" [lib] diff --git a/python/Cargo.toml b/python/Cargo.toml index c01738e57b..bb1ba178b2 100644 --- a/python/Cargo.toml +++ b/python/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge-python" -version = "0.2.23" +version = "0.2.24" description = "Longbridge OpenAPI SDK for Python" homepage = "https://open.longbridgeapp.com/en/" readme = "README.md" diff --git a/python/Makefile.toml b/python/Makefile.toml index baca2a7cf9..6d1cdc2c8e 100644 --- a/python/Makefile.toml +++ b/python/Makefile.toml @@ -12,7 +12,7 @@ args = ["install", "maturin>=0.12,<0.13"] command = "pip" args = [ "install", - "target/wheels/longbridge-0.2.23-cp310-none-win_amd64.whl", + "target/wheels/longbridge-0.2.24-cp310-none-win_amd64.whl", "-I", ] dependencies = ["python"] diff --git a/python/crates/macros/Cargo.toml b/python/crates/macros/Cargo.toml index 0e3eb3fcb8..2052ac0899 100644 --- a/python/crates/macros/Cargo.toml +++ b/python/crates/macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "longbridge-python-macros" -version = "0.2.23" +version = "0.2.24" edition = "2021" [lib] diff --git a/rust/Cargo.toml b/rust/Cargo.toml index ca93a46c6d..ee0257b7c8 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge" -version = "0.2.23" +version = "0.2.24" description = "Longbridge OpenAPI SDK for Rust" homepage = "https://open.longbridgeapp.com/en/" readme = "README.md" @@ -14,10 +14,10 @@ categories = ["api-bindings"] blocking = ["flume"] [dependencies] -longbridge-wscli = { path = "crates/wsclient", version = "0.2.23" } -longbridge-httpcli = { path = "crates/httpclient", version = "0.2.23" } -longbridge-proto = { path = "crates/proto", version = "0.2.23" } -longbridge-candlesticks = { path = "crates/candlesticks", version = "0.2.23" } +longbridge-wscli = { path = "crates/wsclient", version = "0.2.24" } +longbridge-httpcli = { path = "crates/httpclient", version = "0.2.24" } +longbridge-proto = { path = "crates/proto", version = "0.2.24" } +longbridge-candlesticks = { path = "crates/candlesticks", version = "0.2.24" } tokio = { version = "1.18.2", features = [ "time", diff --git a/rust/crates/candlesticks/Cargo.toml b/rust/crates/candlesticks/Cargo.toml index ab9c3ba0a0..c19a0ae0ca 100644 --- a/rust/crates/candlesticks/Cargo.toml +++ b/rust/crates/candlesticks/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge-candlesticks" -version = "0.2.23" +version = "0.2.24" description = "Longbridge candlestick utils for Rust" license = "MIT OR Apache-2.0" diff --git a/rust/crates/httpclient/Cargo.toml b/rust/crates/httpclient/Cargo.toml index f25637062c..eb0dd67fc0 100644 --- a/rust/crates/httpclient/Cargo.toml +++ b/rust/crates/httpclient/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge-httpcli" -version = "0.2.23" +version = "0.2.24" description = "Longbridge HTTP SDK for Rust" license = "MIT OR Apache-2.0" diff --git a/rust/crates/proto/Cargo.toml b/rust/crates/proto/Cargo.toml index 1bf5262040..2afa3be9fb 100644 --- a/rust/crates/proto/Cargo.toml +++ b/rust/crates/proto/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge-proto" -version = "0.2.23" +version = "0.2.24" description = "Longbridge Protocol" license = "MIT OR Apache-2.0" diff --git a/rust/crates/wsclient/Cargo.toml b/rust/crates/wsclient/Cargo.toml index 7fd6780cde..2687231136 100644 --- a/rust/crates/wsclient/Cargo.toml +++ b/rust/crates/wsclient/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "longbridge-wscli" -version = "0.2.23" +version = "0.2.24" edition = "2021" description = "Longbridge Websocket SDK for Rust" license = "MIT OR Apache-2.0" [dependencies] -longbridge-proto = { path = "../proto", version = "0.2.23" } +longbridge-proto = { path = "../proto", version = "0.2.24" } tokio = { version = "1.18.2", features = [ "time", From 30d54395646b245d97f7a9aeacb6495dfe3b677d Mon Sep 17 00:00:00 2001 From: Sunli Date: Mon, 4 Jul 2022 15:45:08 +0800 Subject: [PATCH 101/567] Fixes cannot load some classes with custom class loader in child thread --- java/crates/macros/src/lib.rs | 52 ++++++------ java/src/error.rs | 10 +-- java/src/init.rs | 145 +++++++++++++++++++++++++++++----- java/src/quote_context.rs | 5 +- java/src/trade_context.rs | 5 +- java/src/types/datetime.rs | 13 +-- java/src/types/enum_types.rs | 10 ++- java/src/types/mod.rs | 7 +- 8 files changed, 186 insertions(+), 61 deletions(-) diff --git a/java/crates/macros/src/lib.rs b/java/crates/macros/src/lib.rs index 68359b497c..d10a31f2fa 100644 --- a/java/crates/macros/src/lib.rs +++ b/java/crates/macros/src/lib.rs @@ -131,16 +131,22 @@ pub fn impl_java_class(input: TokenStream) -> TokenStream { let def_class_ref = quote! { static #class_ref_name: once_cell::sync::OnceCell = once_cell::sync::OnceCell::new(); }; - let get_class_ref = quote! { - #class_ref_name.get_or_try_init(|| { - let cls: jni::objects::JClass = #classname.lookup(env)?; - env.new_global_ref(cls) - })? - }; let expanded = quote! { #def_class_ref + impl crate::types::ClassLoader for #type_path { + fn init(env: &jni::JNIEnv) { + use jni::descriptors::Desc; + let cls: jni::objects::JClass = #classname.lookup(env).expect(#classname); + let _ = #class_ref_name.set(env.new_global_ref(cls).unwrap()); + } + + fn class_ref() -> jni::objects::GlobalRef { + #class_ref_name.get().cloned().unwrap() + } + } + impl crate::types::JClassName for #type_path { const CLASSNAME: &'static str = #classname; } @@ -154,10 +160,9 @@ pub fn impl_java_class(input: TokenStream) -> TokenStream { impl crate::types::IntoJValue for #type_path { fn into_jvalue<'a>(self, env: &jni::JNIEnv<'a>) -> jni::errors::Result> { - use jni::descriptors::Desc; let #type_path { #(#field_names),* } = self; - let cls = #get_class_ref; - let obj = env.new_object(cls, "()V", &[])?; + let cls = ::class_ref(); + let obj = env.new_object(&cls, "()V", &[])?; #(#set_fields)* Ok(obj.into()) } @@ -244,14 +249,14 @@ pub fn impl_java_enum(input: TokenStream) -> TokenStream { let remote_path = args.remote.as_ref().unwrap_or(&item); from_jsvalue.push(quote! { - let r = env.get_static_field(cls, stringify!(#java_path), concat!("L", #classname, ";"))?.l()?; + let r = env.get_static_field(&cls, stringify!(#java_path), concat!("L", #classname, ";"))?.l()?; if env.is_same_object(value, r)? { return Ok(#remote_path); } }); into_jsvalue.push(quote! { - #remote_path => env.get_static_field(cls, stringify!(#java_path), concat!("L", #classname, ";")), + #remote_path => env.get_static_field(&cls, stringify!(#java_path), concat!("L", #classname, ";")), }); } @@ -259,16 +264,22 @@ pub fn impl_java_enum(input: TokenStream) -> TokenStream { let def_class_ref = quote! { static #class_ref_name: once_cell::sync::OnceCell = once_cell::sync::OnceCell::new(); }; - let get_class_ref = quote! { - #class_ref_name.get_or_try_init(|| { - let cls: jni::objects::JClass = #classname.lookup(env)?; - env.new_global_ref(cls) - })? - }; let expanded = quote! { #def_class_ref + impl crate::types::ClassLoader for #type_path { + fn init(env: &jni::JNIEnv) { + use jni::descriptors::Desc; + let cls: jni::objects::JClass = #classname.lookup(env).expect(#classname); + let _ = #class_ref_name.set(env.new_global_ref(cls).unwrap()); + } + + fn class_ref() -> jni::objects::GlobalRef { + #class_ref_name.get().cloned().unwrap() + } + } + impl crate::types::JClassName for #type_path { const CLASSNAME: &'static str = #classname; } @@ -286,9 +297,7 @@ pub fn impl_java_enum(input: TokenStream) -> TokenStream { value: jni::objects::JValue, ) -> jni::errors::Result { use #type_path::*; - use jni::descriptors::Desc; - - let cls = #get_class_ref; + let cls = ::class_ref(); let value = value.l()?; #(#from_jsvalue)* panic!("invalid enum value") @@ -300,10 +309,9 @@ pub fn impl_java_enum(input: TokenStream) -> TokenStream { self, env: &jni::JNIEnv<'a>, ) -> jni::errors::Result> { - use jni::descriptors::Desc; use #type_path::*; - let cls = #get_class_ref; + let cls = ::class_ref(); match self { #(#into_jsvalue)* } diff --git a/java/src/error.rs b/java/src/error.rs index 4662a54afa..bc82b9fc41 100644 --- a/java/src/error.rs +++ b/java/src/error.rs @@ -1,10 +1,11 @@ use jni::{ - descriptors::Desc, errors::Result, - objects::{JClass, JObject, JThrowable, JValue}, + objects::{JObject, JThrowable, JValue}, JNIEnv, }; +use crate::init::{LONG_CLASS, OPENAPI_EXCEPTION_CLASS}; + #[derive(Debug, thiserror::Error)] pub(crate) enum JniError { #[error(transparent)] @@ -32,13 +33,12 @@ impl JniError { env: &'a JNIEnv, err: longbridge::Error, ) -> Result> { - let exception_cls: JClass = "com/longbridge/OpenApiException".lookup(env)?; + let exception_cls = OPENAPI_EXCEPTION_CLASS.get().unwrap(); let err = err.into_simple_error(); let code = match err.code() { Some(code) => { - let long_cls: JClass = "java/lang/Long".lookup(env)?; - env.new_object(long_cls, "(J)V", &[JValue::from(code)])? + env.new_object(LONG_CLASS.get().unwrap(), "(J)V", &[JValue::from(code)])? } None => JObject::null(), }; diff --git a/java/src/init.rs b/java/src/init.rs index 04744dd8e2..05edc719c0 100644 --- a/java/src/init.rs +++ b/java/src/init.rs @@ -5,28 +5,137 @@ use jni::{ }; use once_cell::sync::OnceCell; +use crate::types::ClassLoader; + +pub(crate) static LONG_CLASS: OnceCell = OnceCell::new(); pub(crate) static DECIMAL_CLASS: OnceCell = OnceCell::new(); +pub(crate) static TIME_INSTANT_CLASS: OnceCell = OnceCell::new(); +pub(crate) static TIME_OFFSETDATETIME_CLASS: OnceCell = OnceCell::new(); +pub(crate) static TIME_LOCALDATE_CLASS: OnceCell = OnceCell::new(); +pub(crate) static TIME_LOCALTIME_CLASS: OnceCell = OnceCell::new(); pub(crate) static TIME_ZONE_ID: OnceCell = OnceCell::new(); +pub(crate) static QUOTE_CONTEXT_CLASS: OnceCell = OnceCell::new(); +pub(crate) static TRADE_CONTEXT_CLASS: OnceCell = OnceCell::new(); +pub(crate) static DERIVATIVE_TYPE_CLASS: OnceCell = OnceCell::new(); +pub(crate) static OPENAPI_EXCEPTION_CLASS: OnceCell = OnceCell::new(); + +fn init_timezone_id(env: &JNIEnv) { + let utc = env.new_string("UTC").unwrap(); + let zone_id = env + .call_static_method( + "java/time/ZoneId", + "of", + "(Ljava/lang/String;)Ljava/time/ZoneId;", + &[JValue::from(utc)], + ) + .expect("create zone id"); + let _ = TIME_ZONE_ID.set(env.new_global_ref(zone_id.l().unwrap()).unwrap()); +} + +macro_rules! init_class { + ($env:expr, $(($id:ident, $ty:literal)),*) => { + $( + let _ = $id.set( + $env.new_global_ref::($ty.lookup(&$env).expect($ty)) + .unwrap(), + ); + )* + }; +} + +macro_rules! init_class_by_classloader { + ($env:expr, $($id:ty),*) => { + $( + <$id>::init(&$env); + )* + } +} #[no_mangle] pub extern "system" fn Java_com_longbridge_SdkNative_init(env: JNIEnv, _class: JClass) { - { - let cls: JClass = "java/math/BigDecimal" - .lookup(&env) - .expect("java/math/BigDecimal exists"); - let _ = DECIMAL_CLASS.set(env.new_global_ref(cls).unwrap()); - } + init_class!( + env, + (LONG_CLASS, "java/lang/Long"), + (DECIMAL_CLASS, "java/math/BigDecimal"), + (TIME_INSTANT_CLASS, "java/time/Instant"), + (TIME_OFFSETDATETIME_CLASS, "java/time/OffsetDateTime"), + (TIME_LOCALDATE_CLASS, "java/time/LocalDate"), + (TIME_LOCALTIME_CLASS, "java/time/LocalTime"), + (DERIVATIVE_TYPE_CLASS, "com/longbridge/quote/DerivativeType"), + (OPENAPI_EXCEPTION_CLASS, "com/longbridge/OpenApiException"), + (QUOTE_CONTEXT_CLASS, "com/longbridge/quote/QuoteContext"), + (TRADE_CONTEXT_CLASS, "com/longbridge/trade/TradeContext") + ); - { - let utc = env.new_string("UTC").unwrap(); - let zone_id = env - .call_static_method( - "java/time/ZoneId", - "of", - "(Ljava/lang/String;)Ljava/time/ZoneId;", - &[JValue::from(utc)], - ) - .expect("create zone id"); - let _ = TIME_ZONE_ID.set(env.new_global_ref(zone_id.l().unwrap()).unwrap()); - } + init_timezone_id(&env); + + // enum types + init_class_by_classloader!( + env, + longbridge::Market, + longbridge::quote::TradeStatus, + longbridge::quote::TradeSession, + longbridge::quote::TradeDirection, + longbridge::quote::OptionType, + longbridge::quote::OptionDirection, + longbridge::quote::WarrantType, + longbridge::quote::Period, + longbridge::quote::AdjustType, + longbridge::trade::OrderSide, + longbridge::trade::OrderType, + longbridge::trade::OrderStatus, + longbridge::trade::OrderTag, + longbridge::trade::TriggerStatus, + longbridge::trade::TopicType, + longbridge::trade::TimeInForceType, + longbridge::trade::OutsideRTH, + longbridge::trade::BalanceType, + longbridge::trade::CashFlowDirection + ); + + // classes + init_class_by_classloader!( + env, + longbridge::quote::Trade, + longbridge::quote::Brokers, + longbridge::quote::Depth, + longbridge::quote::Subscription, + longbridge::quote::PushQuote, + longbridge::quote::PushDepth, + longbridge::quote::PushBrokers, + longbridge::quote::PushTrades, + longbridge::quote::PushCandlestick, + longbridge::quote::SecurityStaticInfo, + longbridge::quote::PrePostQuote, + longbridge::quote::SecurityQuote, + longbridge::quote::OptionQuote, + longbridge::quote::WarrantQuote, + longbridge::quote::SecurityDepth, + longbridge::quote::SecurityBrokers, + longbridge::quote::ParticipantInfo, + longbridge::quote::IntradayLine, + longbridge::quote::Candlestick, + longbridge::quote::StrikePriceInfo, + longbridge::quote::IssuerInfo, + longbridge::quote::MarketTradingSession, + longbridge::quote::TradingSessionInfo, + longbridge::quote::MarketTradingDays, + longbridge::quote::CapitalFlowLine, + longbridge::quote::CapitalDistribution, + longbridge::quote::CapitalDistributionResponse, + longbridge::quote::RealtimeQuote, + longbridge::trade::PushOrderChanged, + longbridge::trade::Execution, + longbridge::trade::Order, + longbridge::trade::SubmitOrderResponse, + longbridge::trade::CashInfo, + longbridge::trade::AccountBalance, + longbridge::trade::CashFlow, + longbridge::trade::FundPositionsResponse, + longbridge::trade::FundPositionChannel, + longbridge::trade::FundPosition, + longbridge::trade::StockPositionsResponse, + longbridge::trade::StockPositionChannel, + longbridge::trade::StockPosition + ); } diff --git a/java/src/quote_context.rs b/java/src/quote_context.rs index 998c0ce4fc..79b8b083ed 100644 --- a/java/src/quote_context.rs +++ b/java/src/quote_context.rs @@ -1,7 +1,6 @@ use std::sync::Arc; use jni::{ - descriptors::Desc, errors::Result, objects::{GlobalRef, JClass, JObject, JString, JValue}, sys::{jboolean, jobjectArray}, @@ -17,6 +16,7 @@ use time::Date; use crate::{ async_util, error::jni_result, + init::QUOTE_CONTEXT_CLASS, types::{set_field, FromJValue, IntoJValue, ObjectArray}, }; @@ -119,8 +119,7 @@ pub unsafe extern "system" fn Java_com_longbridge_SdkNative_newQuoteContext( impl IntoJValue for ContextObjRef { fn into_jvalue<'a>(self, env: &JNIEnv<'a>) -> Result> { - let ctx_cls: JClass = "com/longbridge/quote/QuoteContext".lookup(env)?; - let ctx_obj = env.new_object(ctx_cls, "()V", &[])?; + let ctx_obj = env.new_object(QUOTE_CONTEXT_CLASS.get().unwrap(), "()V", &[])?; set_field(env, ctx_obj, "raw", self.0)?; Ok(JValue::from(ctx_obj)) } diff --git a/java/src/trade_context.rs b/java/src/trade_context.rs index a7f21d3e7a..d927134279 100644 --- a/java/src/trade_context.rs +++ b/java/src/trade_context.rs @@ -1,7 +1,6 @@ use std::sync::Arc; use jni::{ - descriptors::Desc, errors::Result, objects::{GlobalRef, JClass, JObject, JString, JValue}, sys::jobjectArray, @@ -22,6 +21,7 @@ use time::{Date, OffsetDateTime}; use crate::{ async_util, error::jni_result, + init::TRADE_CONTEXT_CLASS, types::{get_field, set_field, FromJValue, IntoJValue, ObjectArray}, }; @@ -65,8 +65,7 @@ pub unsafe extern "system" fn Java_com_longbridge_SdkNative_newTradeContext( impl IntoJValue for ContextObjRef { fn into_jvalue<'a>(self, env: &JNIEnv<'a>) -> Result> { - let ctx_cls: JClass = "com/longbridge/trade/TradeContext".lookup(env)?; - let ctx_obj = env.new_object(ctx_cls, "()V", &[])?; + let ctx_obj = env.new_object(TRADE_CONTEXT_CLASS.get().unwrap(), "()V", &[])?; set_field(env, ctx_obj, "raw", self.0)?; Ok(JValue::from(ctx_obj)) } diff --git a/java/src/types/datetime.rs b/java/src/types/datetime.rs index 730f3beb99..693458cf4e 100644 --- a/java/src/types/datetime.rs +++ b/java/src/types/datetime.rs @@ -4,7 +4,10 @@ use jni::{errors::Result, objects::JValue, JNIEnv}; use time::{Date, Month, OffsetDateTime, Time}; use crate::{ - init::TIME_ZONE_ID, + init::{ + TIME_INSTANT_CLASS, TIME_LOCALDATE_CLASS, TIME_LOCALTIME_CLASS, TIME_OFFSETDATETIME_CLASS, + TIME_ZONE_ID, + }, types::{FromJValue, IntoJValue, JClassName, JSignature}, }; @@ -29,14 +32,14 @@ impl FromJValue for OffsetDateTime { impl IntoJValue for OffsetDateTime { fn into_jvalue<'a>(self, env: &JNIEnv<'a>) -> Result> { let instant = env.call_static_method( - "java/time/Instant", + TIME_INSTANT_CLASS.get().unwrap(), "ofEpochSecond", "(J)Ljava/time/Instant;", &[JValue::from(self.unix_timestamp())], )?; env.call_static_method( - "java/time/OffsetDateTime", + TIME_OFFSETDATETIME_CLASS.get().unwrap(), "ofInstant", "(Ljava/time/Instant;Ljava/time/ZoneId;)Ljava/time/OffsetDateTime;", &[instant, JValue::from(TIME_ZONE_ID.get().unwrap().as_obj())], @@ -70,7 +73,7 @@ impl FromJValue for Date { impl IntoJValue for Date { fn into_jvalue<'a>(self, env: &JNIEnv<'a>) -> Result> { env.call_static_method( - "java/time/LocalDate", + TIME_LOCALDATE_CLASS.get().unwrap(), "of", "(III)Ljava/time/LocalDate;", &[ @@ -105,7 +108,7 @@ impl FromJValue for Time { impl IntoJValue for Time { fn into_jvalue<'a>(self, env: &JNIEnv<'a>) -> Result> { env.call_static_method( - "java/time/LocalTime", + TIME_LOCALTIME_CLASS.get().unwrap(), "of", "(III)Ljava/time/LocalTime;", &[ diff --git a/java/src/types/enum_types.rs b/java/src/types/enum_types.rs index 0a71de42df..abe02792ec 100644 --- a/java/src/types/enum_types.rs +++ b/java/src/types/enum_types.rs @@ -1,14 +1,16 @@ use std::borrow::Cow; use jni::{ - descriptors::Desc, errors::Result, - objects::{JClass, JObject, JValue}, + objects::{JObject, JValue}, JNIEnv, }; use longbridge_java_macros::impl_java_enum; -use crate::types::{IntoJValue, JSignature}; +use crate::{ + init::DERIVATIVE_TYPE_CLASS, + types::{IntoJValue, JSignature}, +}; impl_java_enum!( "com/longbridge/Market", @@ -75,7 +77,7 @@ impl JSignature for DerivativeTypes { impl IntoJValue for DerivativeTypes { fn into_jvalue<'a>(self, env: &JNIEnv<'a>) -> Result> { - let cls: JClass = "com/longbridge/quote/DerivativeType".lookup(env)?; + let cls = DERIVATIVE_TYPE_CLASS.get().unwrap(); let array = env.new_object_array(self.0.len() as i32, cls, JObject::null())?; for (i, obj) in self.0.into_iter().enumerate() { let value = match obj { diff --git a/java/src/types/mod.rs b/java/src/types/mod.rs index adb8b4b69e..a7cdd97373 100644 --- a/java/src/types/mod.rs +++ b/java/src/types/mod.rs @@ -13,13 +13,18 @@ use std::borrow::Cow; use jni::{ errors::Result, - objects::{JObject, JValue}, + objects::{GlobalRef, JObject, JValue}, strings::JNIString, JNIEnv, }; pub(crate) use self::{object_array::ObjectArray, primary_array::PrimaryArray}; +pub(crate) trait ClassLoader { + fn init(env: &JNIEnv); + fn class_ref() -> GlobalRef; +} + pub(crate) trait FromJValue: Sized { fn from_jvalue(env: &JNIEnv, value: JValue) -> Result; } From e3c4b38e9ed7041ebbc92692e221c143995ee1ac Mon Sep 17 00:00:00 2001 From: Sunli Date: Mon, 4 Jul 2022 15:49:33 +0800 Subject: [PATCH 102/567] Release 0.2.25 longbridge@0.2.25 longbridge-candlesticks@0.2.25 longbridge-httpcli@0.2.25 longbridge-java@0.2.25 longbridge-java-macros@0.2.25 longbridge-nodejs@0.2.25 longbridge-nodejs-macros@0.2.25 longbridge-proto@0.2.25 longbridge-python@0.2.25 longbridge-python-macros@0.2.25 longbridge-wscli@0.2.25 Generated by cargo-workspaces --- java/Cargo.toml | 2 +- java/crates/macros/Cargo.toml | 2 +- nodejs/Cargo.toml | 2 +- nodejs/crates/macros/Cargo.toml | 2 +- python/Cargo.toml | 2 +- python/Makefile.toml | 2 +- python/crates/macros/Cargo.toml | 2 +- rust/Cargo.toml | 10 +++++----- rust/crates/candlesticks/Cargo.toml | 2 +- rust/crates/httpclient/Cargo.toml | 2 +- rust/crates/proto/Cargo.toml | 2 +- rust/crates/wsclient/Cargo.toml | 4 ++-- 12 files changed, 17 insertions(+), 17 deletions(-) diff --git a/java/Cargo.toml b/java/Cargo.toml index f9d668f144..ea8db0f0c4 100644 --- a/java/Cargo.toml +++ b/java/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge-java" -version = "0.2.24" +version = "0.2.25" [lib] crate-type = ["cdylib"] diff --git a/java/crates/macros/Cargo.toml b/java/crates/macros/Cargo.toml index 6a1ff532d9..d5736c477c 100644 --- a/java/crates/macros/Cargo.toml +++ b/java/crates/macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "longbridge-java-macros" -version = "0.2.24" +version = "0.2.25" edition = "2021" [lib] diff --git a/nodejs/Cargo.toml b/nodejs/Cargo.toml index 4875f33e16..2b7bc06242 100644 --- a/nodejs/Cargo.toml +++ b/nodejs/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge-nodejs" -version = "0.2.24" +version = "0.2.25" [lib] crate-type = ["cdylib"] diff --git a/nodejs/crates/macros/Cargo.toml b/nodejs/crates/macros/Cargo.toml index d26b462f1a..f317af6bb5 100644 --- a/nodejs/crates/macros/Cargo.toml +++ b/nodejs/crates/macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "longbridge-nodejs-macros" -version = "0.2.24" +version = "0.2.25" edition = "2021" [lib] diff --git a/python/Cargo.toml b/python/Cargo.toml index bb1ba178b2..b860ebea6b 100644 --- a/python/Cargo.toml +++ b/python/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge-python" -version = "0.2.24" +version = "0.2.25" description = "Longbridge OpenAPI SDK for Python" homepage = "https://open.longbridgeapp.com/en/" readme = "README.md" diff --git a/python/Makefile.toml b/python/Makefile.toml index 6d1cdc2c8e..082244a574 100644 --- a/python/Makefile.toml +++ b/python/Makefile.toml @@ -12,7 +12,7 @@ args = ["install", "maturin>=0.12,<0.13"] command = "pip" args = [ "install", - "target/wheels/longbridge-0.2.24-cp310-none-win_amd64.whl", + "target/wheels/longbridge-0.2.25-cp310-none-win_amd64.whl", "-I", ] dependencies = ["python"] diff --git a/python/crates/macros/Cargo.toml b/python/crates/macros/Cargo.toml index 2052ac0899..cca2fe07f9 100644 --- a/python/crates/macros/Cargo.toml +++ b/python/crates/macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "longbridge-python-macros" -version = "0.2.24" +version = "0.2.25" edition = "2021" [lib] diff --git a/rust/Cargo.toml b/rust/Cargo.toml index ee0257b7c8..e0351b6c2c 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge" -version = "0.2.24" +version = "0.2.25" description = "Longbridge OpenAPI SDK for Rust" homepage = "https://open.longbridgeapp.com/en/" readme = "README.md" @@ -14,10 +14,10 @@ categories = ["api-bindings"] blocking = ["flume"] [dependencies] -longbridge-wscli = { path = "crates/wsclient", version = "0.2.24" } -longbridge-httpcli = { path = "crates/httpclient", version = "0.2.24" } -longbridge-proto = { path = "crates/proto", version = "0.2.24" } -longbridge-candlesticks = { path = "crates/candlesticks", version = "0.2.24" } +longbridge-wscli = { path = "crates/wsclient", version = "0.2.25" } +longbridge-httpcli = { path = "crates/httpclient", version = "0.2.25" } +longbridge-proto = { path = "crates/proto", version = "0.2.25" } +longbridge-candlesticks = { path = "crates/candlesticks", version = "0.2.25" } tokio = { version = "1.18.2", features = [ "time", diff --git a/rust/crates/candlesticks/Cargo.toml b/rust/crates/candlesticks/Cargo.toml index c19a0ae0ca..943f1b3ff7 100644 --- a/rust/crates/candlesticks/Cargo.toml +++ b/rust/crates/candlesticks/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge-candlesticks" -version = "0.2.24" +version = "0.2.25" description = "Longbridge candlestick utils for Rust" license = "MIT OR Apache-2.0" diff --git a/rust/crates/httpclient/Cargo.toml b/rust/crates/httpclient/Cargo.toml index eb0dd67fc0..e0b7cb4f75 100644 --- a/rust/crates/httpclient/Cargo.toml +++ b/rust/crates/httpclient/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge-httpcli" -version = "0.2.24" +version = "0.2.25" description = "Longbridge HTTP SDK for Rust" license = "MIT OR Apache-2.0" diff --git a/rust/crates/proto/Cargo.toml b/rust/crates/proto/Cargo.toml index 2afa3be9fb..1775fcc7a7 100644 --- a/rust/crates/proto/Cargo.toml +++ b/rust/crates/proto/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge-proto" -version = "0.2.24" +version = "0.2.25" description = "Longbridge Protocol" license = "MIT OR Apache-2.0" diff --git a/rust/crates/wsclient/Cargo.toml b/rust/crates/wsclient/Cargo.toml index 2687231136..2731c9ca5c 100644 --- a/rust/crates/wsclient/Cargo.toml +++ b/rust/crates/wsclient/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "longbridge-wscli" -version = "0.2.24" +version = "0.2.25" edition = "2021" description = "Longbridge Websocket SDK for Rust" license = "MIT OR Apache-2.0" [dependencies] -longbridge-proto = { path = "../proto", version = "0.2.24" } +longbridge-proto = { path = "../proto", version = "0.2.25" } tokio = { version = "1.18.2", features = [ "time", From 0613a702ed6088ea38efb4739274012df31b0692 Mon Sep 17 00:00:00 2001 From: Sunli Date: Mon, 4 Jul 2022 21:52:41 +0800 Subject: [PATCH 103/567] Fixes cannot load some classes with custom class loader in child thread 2 --- java/crates/macros/src/lib.rs | 8 -------- java/src/init.rs | 2 ++ java/src/types/datetime.rs | 26 +++++++++++++++++++------- java/src/types/decimal.rs | 10 +++++++--- java/src/types/mod.rs | 4 ---- java/src/types/object_array.rs | 6 +++--- java/src/types/optional.rs | 12 +++++++++--- java/src/types/string.rs | 13 ++++++++++--- 8 files changed, 50 insertions(+), 31 deletions(-) diff --git a/java/crates/macros/src/lib.rs b/java/crates/macros/src/lib.rs index d10a31f2fa..27ebe77e4c 100644 --- a/java/crates/macros/src/lib.rs +++ b/java/crates/macros/src/lib.rs @@ -147,10 +147,6 @@ pub fn impl_java_class(input: TokenStream) -> TokenStream { } } - impl crate::types::JClassName for #type_path { - const CLASSNAME: &'static str = #classname; - } - impl crate::types::JSignature for #type_path { #[inline] fn signature() -> ::std::borrow::Cow<'static, str> { @@ -280,10 +276,6 @@ pub fn impl_java_enum(input: TokenStream) -> TokenStream { } } - impl crate::types::JClassName for #type_path { - const CLASSNAME: &'static str = #classname; - } - impl crate::types::JSignature for #type_path { #[inline] fn signature() -> std::borrow::Cow<'static, str> { diff --git a/java/src/init.rs b/java/src/init.rs index 05edc719c0..3dd275cb17 100644 --- a/java/src/init.rs +++ b/java/src/init.rs @@ -8,6 +8,7 @@ use once_cell::sync::OnceCell; use crate::types::ClassLoader; pub(crate) static LONG_CLASS: OnceCell = OnceCell::new(); +pub(crate) static STRING_CLASS: OnceCell = OnceCell::new(); pub(crate) static DECIMAL_CLASS: OnceCell = OnceCell::new(); pub(crate) static TIME_INSTANT_CLASS: OnceCell = OnceCell::new(); pub(crate) static TIME_OFFSETDATETIME_CLASS: OnceCell = OnceCell::new(); @@ -56,6 +57,7 @@ pub extern "system" fn Java_com_longbridge_SdkNative_init(env: JNIEnv, _class: J init_class!( env, (LONG_CLASS, "java/lang/Long"), + (STRING_CLASS, "java/lang/String"), (DECIMAL_CLASS, "java/math/BigDecimal"), (TIME_INSTANT_CLASS, "java/time/Instant"), (TIME_OFFSETDATETIME_CLASS, "java/time/OffsetDateTime"), diff --git a/java/src/types/datetime.rs b/java/src/types/datetime.rs index 693458cf4e..7f81b02f4c 100644 --- a/java/src/types/datetime.rs +++ b/java/src/types/datetime.rs @@ -8,11 +8,15 @@ use crate::{ TIME_INSTANT_CLASS, TIME_LOCALDATE_CLASS, TIME_LOCALTIME_CLASS, TIME_OFFSETDATETIME_CLASS, TIME_ZONE_ID, }, - types::{FromJValue, IntoJValue, JClassName, JSignature}, + types::{ClassLoader, FromJValue, IntoJValue, JSignature}, }; -impl JClassName for OffsetDateTime { - const CLASSNAME: &'static str = "java/time/OffsetDateTime"; +impl ClassLoader for OffsetDateTime { + fn init(_env: &JNIEnv) {} + + fn class_ref() -> jni::objects::GlobalRef { + TIME_OFFSETDATETIME_CLASS.get().cloned().unwrap() + } } impl JSignature for OffsetDateTime { @@ -47,8 +51,12 @@ impl IntoJValue for OffsetDateTime { } } -impl JClassName for Date { - const CLASSNAME: &'static str = "java/time/LocalDate"; +impl ClassLoader for Date { + fn init(_env: &JNIEnv) {} + + fn class_ref() -> jni::objects::GlobalRef { + TIME_LOCALDATE_CLASS.get().cloned().unwrap() + } } impl JSignature for Date { @@ -85,8 +93,12 @@ impl IntoJValue for Date { } } -impl JClassName for Time { - const CLASSNAME: &'static str = "java/time/LocalTime"; +impl ClassLoader for Time { + fn init(_env: &JNIEnv) {} + + fn class_ref() -> jni::objects::GlobalRef { + TIME_LOCALTIME_CLASS.get().cloned().unwrap() + } } impl JSignature for Time { diff --git a/java/src/types/decimal.rs b/java/src/types/decimal.rs index 6837d246ff..61c5d62bfd 100644 --- a/java/src/types/decimal.rs +++ b/java/src/types/decimal.rs @@ -5,11 +5,15 @@ use longbridge::Decimal; use crate::{ init::DECIMAL_CLASS, - types::{FromJValue, IntoJValue, JClassName, JSignature}, + types::{ClassLoader, FromJValue, IntoJValue, JSignature}, }; -impl JClassName for Decimal { - const CLASSNAME: &'static str = "java/math/BigDecimal"; +impl ClassLoader for Decimal { + fn init(_env: &JNIEnv) {} + + fn class_ref() -> jni::objects::GlobalRef { + DECIMAL_CLASS.get().cloned().unwrap() + } } impl JSignature for Decimal { diff --git a/java/src/types/mod.rs b/java/src/types/mod.rs index a7cdd97373..f99a376eb7 100644 --- a/java/src/types/mod.rs +++ b/java/src/types/mod.rs @@ -40,10 +40,6 @@ impl IntoJValue for () { } } -pub(crate) trait JClassName { - const CLASSNAME: &'static str; -} - pub(crate) trait JSignature { fn signature() -> Cow<'static, str>; } diff --git a/java/src/types/object_array.rs b/java/src/types/object_array.rs index bade08eb15..dae3a6fdc7 100644 --- a/java/src/types/object_array.rs +++ b/java/src/types/object_array.rs @@ -6,7 +6,7 @@ use jni::{ JNIEnv, }; -use crate::types::{FromJValue, IntoJValue, JClassName, JSignature}; +use crate::types::{ClassLoader, FromJValue, IntoJValue, JSignature}; pub(crate) struct ObjectArray(pub(crate) Vec); @@ -37,9 +37,9 @@ impl FromJValue for ObjectArray { } } -impl IntoJValue for ObjectArray { +impl IntoJValue for ObjectArray { fn into_jvalue<'a>(self, env: &JNIEnv<'a>) -> Result> { - let array = env.new_object_array(self.0.len() as i32, T::CLASSNAME, JObject::null())?; + let array = env.new_object_array(self.0.len() as i32, &T::class_ref(), JObject::null())?; for (i, obj) in self.0.into_iter().enumerate() { env.set_object_array_element(array, i as i32, obj.into_jvalue(env)?.l()?)?; } diff --git a/java/src/types/optional.rs b/java/src/types/optional.rs index 3f0355a345..3ccbc77bf0 100644 --- a/java/src/types/optional.rs +++ b/java/src/types/optional.rs @@ -6,10 +6,16 @@ use jni::{ JNIEnv, }; -use crate::types::{FromJValue, IntoJValue, JClassName, JSignature}; +use crate::types::{ClassLoader, FromJValue, IntoJValue, JSignature}; -impl JClassName for Option { - const CLASSNAME: &'static str = T::CLASSNAME; +impl ClassLoader for Option { + fn init(env: &JNIEnv) { + T::init(env) + } + + fn class_ref() -> jni::objects::GlobalRef { + T::class_ref() + } } impl JSignature for Option { diff --git a/java/src/types/string.rs b/java/src/types/string.rs index b776fe8a95..dc011bb78c 100644 --- a/java/src/types/string.rs +++ b/java/src/types/string.rs @@ -4,10 +4,17 @@ use jni::{ JNIEnv, }; -use crate::types::{FromJValue, IntoJValue, JClassName, JSignature}; +use crate::{ + init::STRING_CLASS, + types::{ClassLoader, FromJValue, IntoJValue, JSignature}, +}; + +impl ClassLoader for String { + fn init(_env: &JNIEnv) {} -impl JClassName for String { - const CLASSNAME: &'static str = "java/lang/String"; + fn class_ref() -> jni::objects::GlobalRef { + STRING_CLASS.get().cloned().unwrap() + } } impl JSignature for String { From f483849d5bc6b81baab53c94a673577dfe5d256e Mon Sep 17 00:00:00 2001 From: Sunli Date: Mon, 4 Jul 2022 21:53:56 +0800 Subject: [PATCH 104/567] Release 0.2.26 longbridge@0.2.26 longbridge-candlesticks@0.2.26 longbridge-httpcli@0.2.26 longbridge-java@0.2.26 longbridge-java-macros@0.2.26 longbridge-nodejs@0.2.26 longbridge-nodejs-macros@0.2.26 longbridge-proto@0.2.26 longbridge-python@0.2.26 longbridge-python-macros@0.2.26 longbridge-wscli@0.2.26 Generated by cargo-workspaces --- java/Cargo.toml | 2 +- java/crates/macros/Cargo.toml | 2 +- nodejs/Cargo.toml | 2 +- nodejs/crates/macros/Cargo.toml | 2 +- python/Cargo.toml | 2 +- python/Makefile.toml | 2 +- python/crates/macros/Cargo.toml | 2 +- rust/Cargo.toml | 10 +++++----- rust/crates/candlesticks/Cargo.toml | 2 +- rust/crates/httpclient/Cargo.toml | 2 +- rust/crates/proto/Cargo.toml | 2 +- rust/crates/wsclient/Cargo.toml | 4 ++-- 12 files changed, 17 insertions(+), 17 deletions(-) diff --git a/java/Cargo.toml b/java/Cargo.toml index ea8db0f0c4..8322613d47 100644 --- a/java/Cargo.toml +++ b/java/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge-java" -version = "0.2.25" +version = "0.2.26" [lib] crate-type = ["cdylib"] diff --git a/java/crates/macros/Cargo.toml b/java/crates/macros/Cargo.toml index d5736c477c..8f6c148f73 100644 --- a/java/crates/macros/Cargo.toml +++ b/java/crates/macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "longbridge-java-macros" -version = "0.2.25" +version = "0.2.26" edition = "2021" [lib] diff --git a/nodejs/Cargo.toml b/nodejs/Cargo.toml index 2b7bc06242..e0997bda09 100644 --- a/nodejs/Cargo.toml +++ b/nodejs/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge-nodejs" -version = "0.2.25" +version = "0.2.26" [lib] crate-type = ["cdylib"] diff --git a/nodejs/crates/macros/Cargo.toml b/nodejs/crates/macros/Cargo.toml index f317af6bb5..59b110b8a1 100644 --- a/nodejs/crates/macros/Cargo.toml +++ b/nodejs/crates/macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "longbridge-nodejs-macros" -version = "0.2.25" +version = "0.2.26" edition = "2021" [lib] diff --git a/python/Cargo.toml b/python/Cargo.toml index b860ebea6b..e157655d3d 100644 --- a/python/Cargo.toml +++ b/python/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge-python" -version = "0.2.25" +version = "0.2.26" description = "Longbridge OpenAPI SDK for Python" homepage = "https://open.longbridgeapp.com/en/" readme = "README.md" diff --git a/python/Makefile.toml b/python/Makefile.toml index 082244a574..e9766c1b06 100644 --- a/python/Makefile.toml +++ b/python/Makefile.toml @@ -12,7 +12,7 @@ args = ["install", "maturin>=0.12,<0.13"] command = "pip" args = [ "install", - "target/wheels/longbridge-0.2.25-cp310-none-win_amd64.whl", + "target/wheels/longbridge-0.2.26-cp310-none-win_amd64.whl", "-I", ] dependencies = ["python"] diff --git a/python/crates/macros/Cargo.toml b/python/crates/macros/Cargo.toml index cca2fe07f9..04e2079cf1 100644 --- a/python/crates/macros/Cargo.toml +++ b/python/crates/macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "longbridge-python-macros" -version = "0.2.25" +version = "0.2.26" edition = "2021" [lib] diff --git a/rust/Cargo.toml b/rust/Cargo.toml index e0351b6c2c..bbbe4ce63a 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge" -version = "0.2.25" +version = "0.2.26" description = "Longbridge OpenAPI SDK for Rust" homepage = "https://open.longbridgeapp.com/en/" readme = "README.md" @@ -14,10 +14,10 @@ categories = ["api-bindings"] blocking = ["flume"] [dependencies] -longbridge-wscli = { path = "crates/wsclient", version = "0.2.25" } -longbridge-httpcli = { path = "crates/httpclient", version = "0.2.25" } -longbridge-proto = { path = "crates/proto", version = "0.2.25" } -longbridge-candlesticks = { path = "crates/candlesticks", version = "0.2.25" } +longbridge-wscli = { path = "crates/wsclient", version = "0.2.26" } +longbridge-httpcli = { path = "crates/httpclient", version = "0.2.26" } +longbridge-proto = { path = "crates/proto", version = "0.2.26" } +longbridge-candlesticks = { path = "crates/candlesticks", version = "0.2.26" } tokio = { version = "1.18.2", features = [ "time", diff --git a/rust/crates/candlesticks/Cargo.toml b/rust/crates/candlesticks/Cargo.toml index 943f1b3ff7..c0d3d85e13 100644 --- a/rust/crates/candlesticks/Cargo.toml +++ b/rust/crates/candlesticks/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge-candlesticks" -version = "0.2.25" +version = "0.2.26" description = "Longbridge candlestick utils for Rust" license = "MIT OR Apache-2.0" diff --git a/rust/crates/httpclient/Cargo.toml b/rust/crates/httpclient/Cargo.toml index e0b7cb4f75..da652a4548 100644 --- a/rust/crates/httpclient/Cargo.toml +++ b/rust/crates/httpclient/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge-httpcli" -version = "0.2.25" +version = "0.2.26" description = "Longbridge HTTP SDK for Rust" license = "MIT OR Apache-2.0" diff --git a/rust/crates/proto/Cargo.toml b/rust/crates/proto/Cargo.toml index 1775fcc7a7..52f2b3ac2a 100644 --- a/rust/crates/proto/Cargo.toml +++ b/rust/crates/proto/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "longbridge-proto" -version = "0.2.25" +version = "0.2.26" description = "Longbridge Protocol" license = "MIT OR Apache-2.0" diff --git a/rust/crates/wsclient/Cargo.toml b/rust/crates/wsclient/Cargo.toml index 2731c9ca5c..71124ead9f 100644 --- a/rust/crates/wsclient/Cargo.toml +++ b/rust/crates/wsclient/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "longbridge-wscli" -version = "0.2.25" +version = "0.2.26" edition = "2021" description = "Longbridge Websocket SDK for Rust" license = "MIT OR Apache-2.0" [dependencies] -longbridge-proto = { path = "../proto", version = "0.2.25" } +longbridge-proto = { path = "../proto", version = "0.2.26" } tokio = { version = "1.18.2", features = [ "time", From 6f461c6a5b5c213be205ad9a408b9b5efb4bb9f7 Mon Sep 17 00:00:00 2001 From: Sunli Date: Sun, 10 Jul 2022 07:59:51 +0800 Subject: [PATCH 105/567] Add C SDK --- Cargo.toml | 2 +- Makefile.toml | 1 + c/.gitignore | 1 + c/CMakeLists.txt | 27 + c/Cargo.toml | 27 + c/Makefile.toml | 38 + c/build.rs | 14 + c/cbindgen.toml | 114 + c/crates/macros/Cargo.toml | 14 + c/crates/macros/src/cenum.rs | 108 + c/crates/macros/src/error.rs | 22 + c/crates/macros/src/lib.rs | 14 + c/csrc/async_result.hpp | 35 + c/csrc/config.hpp | 41 + c/csrc/decimal.hpp | 199 ++ c/csrc/longbridge.h | 2909 +++++++++++++++++ c/csrc/longbridge.hpp | 6 + c/csrc/quote_context.hpp | 42 + c/csrc/status.hpp | 45 + c/src/async_call.rs | 147 + c/src/config.rs | 79 + c/src/error.rs | 42 + c/src/lib.rs | 6 + c/src/quote_context/constants.rs | 17 + c/src/quote_context/context.rs | 731 +++++ c/src/quote_context/enum_types.rs | 188 ++ c/src/quote_context/mod.rs | 5 + c/src/quote_context/types.rs | 1975 +++++++++++ c/src/trade_context/context.rs | 505 +++ c/src/trade_context/enum_types.rs | 252 ++ c/src/trade_context/mod.rs | 3 + c/src/trade_context/types.rs | 1282 ++++++++ c/src/types/array.rs | 52 + c/src/types/cow.rs | 47 + c/src/types/datetime.rs | 68 + c/src/types/decimal.rs | 352 ++ c/src/types/market.rs | 24 + c/src/types/mod.rs | 38 + c/src/types/option.rs | 28 + c/src/types/string.rs | 21 + c/test/main.c | 46 + c/test/main.cpp | 20 + .../quote/MarketTradingSession.java | 8 +- java/src/async_util.rs | 11 +- java/src/lib.rs | 1 - java/src/runtime.rs | 9 - java/src/types/classes.rs | 2 +- nodejs/index.d.ts | 5 +- nodejs/src/decimal.rs | 5 + nodejs/src/quote/types.rs | 4 +- python/pysrc/longbridge/openapi.pyi | 6 +- python/src/quote/types.rs | 6 +- rust/Cargo.toml | 2 +- rust/src/quote/core.rs | 2 +- rust/src/quote/types.rs | 8 +- rust/src/trade/core.rs | 2 +- rust/src/trade/requests/get_cash_flow.rs | 2 +- 57 files changed, 9625 insertions(+), 35 deletions(-) create mode 100644 c/.gitignore create mode 100644 c/CMakeLists.txt create mode 100644 c/Cargo.toml create mode 100644 c/Makefile.toml create mode 100644 c/build.rs create mode 100644 c/cbindgen.toml create mode 100644 c/crates/macros/Cargo.toml create mode 100644 c/crates/macros/src/cenum.rs create mode 100644 c/crates/macros/src/error.rs create mode 100644 c/crates/macros/src/lib.rs create mode 100644 c/csrc/async_result.hpp create mode 100644 c/csrc/config.hpp create mode 100644 c/csrc/decimal.hpp create mode 100644 c/csrc/longbridge.h create mode 100644 c/csrc/longbridge.hpp create mode 100644 c/csrc/quote_context.hpp create mode 100644 c/csrc/status.hpp create mode 100644 c/src/async_call.rs create mode 100644 c/src/config.rs create mode 100644 c/src/error.rs create mode 100644 c/src/lib.rs create mode 100644 c/src/quote_context/constants.rs create mode 100644 c/src/quote_context/context.rs create mode 100644 c/src/quote_context/enum_types.rs create mode 100644 c/src/quote_context/mod.rs create mode 100644 c/src/quote_context/types.rs create mode 100644 c/src/trade_context/context.rs create mode 100644 c/src/trade_context/enum_types.rs create mode 100644 c/src/trade_context/mod.rs create mode 100644 c/src/trade_context/types.rs create mode 100644 c/src/types/array.rs create mode 100644 c/src/types/cow.rs create mode 100644 c/src/types/datetime.rs create mode 100644 c/src/types/decimal.rs create mode 100644 c/src/types/market.rs create mode 100644 c/src/types/mod.rs create mode 100644 c/src/types/option.rs create mode 100644 c/src/types/string.rs create mode 100644 c/test/main.c create mode 100644 c/test/main.cpp delete mode 100644 java/src/runtime.rs diff --git a/Cargo.toml b/Cargo.toml index 6fdfcc93a8..bcbcfb6d36 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,2 +1,2 @@ [workspace] -members = ["rust", "python", "nodejs", "java"] +members = ["rust", "python", "nodejs", "java", "c"] diff --git a/Makefile.toml b/Makefile.toml index d3f7154e36..7169fbdd3b 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -2,6 +2,7 @@ extend = [ { path = "rust/Makefile.toml" }, { path = "python/Makefile.toml" }, { path = "java/Makefile.toml" }, + { path = "c/Makefile.toml" }, ] [config] diff --git a/c/.gitignore b/c/.gitignore new file mode 100644 index 0000000000..a3ea3e4380 --- /dev/null +++ b/c/.gitignore @@ -0,0 +1 @@ +cmake diff --git a/c/CMakeLists.txt b/c/CMakeLists.txt new file mode 100644 index 0000000000..a6eddf29b9 --- /dev/null +++ b/c/CMakeLists.txt @@ -0,0 +1,27 @@ +cmake_minimum_required(VERSION 3.19) +cmake_policy(SET CMP0048 NEW) +project(longbridge) + +include(FetchContent) +fetchcontent_declare( + Corrosion + GIT_REPOSITORY https://github.com/corrosion-rs/corrosion.git + GIT_TAG v0.2.1 +) +fetchcontent_makeavailable(Corrosion) + +corrosion_import_crate(MANIFEST_PATH Cargo.toml CRATES longbridge-c) + +include_directories(csrc/) + +add_executable(test test/main.c) +target_link_libraries(test longbridge-c) +if(NOT CMAKE_HOST_WIN32) + target_link_libraries(test ncurses) +endif() + +add_executable(test_cpp test/main.cpp) +target_link_libraries(test_cpp longbridge-c) +if(NOT CMAKE_HOST_WIN32) + target_link_libraries(test_cpp ncurses) +endif() diff --git a/c/Cargo.toml b/c/Cargo.toml new file mode 100644 index 0000000000..73d4ab3f2b --- /dev/null +++ b/c/Cargo.toml @@ -0,0 +1,27 @@ +[package] +edition = "2021" +name = "longbridge-c" +version = "0.2.25" +description = "Longbridge OpenAPI SDK for C" +homepage = "https://open.longbridgeapp.com/en/" +readme = "README.md" +repository = "https://github.com/longbridgeapp/openapi-sdk" +license = "MIT OR Apache-2.0" +keywords = ["longbridge", "openapi", "sdk"] +categories = ["api-bindings"] + +[lib] +crate-type = ["cdylib"] + +[dependencies] +longbridge = { path = "../rust" } +longbridge-c-macros = { path = "crates/macros" } + +rust_decimal = { version = "1.23.1", features = ["maths"] } +tokio = { version = "1.19.2", features = ["rt-multi-thread"] } +once_cell = "1.12.0" +parking_lot = "0.12.1" +time = "0.3.9" + +[build-dependencies] +cbindgen = "0.24.3" diff --git a/c/Makefile.toml b/c/Makefile.toml new file mode 100644 index 0000000000..0235c9f991 --- /dev/null +++ b/c/Makefile.toml @@ -0,0 +1,38 @@ +[tasks.c] +command = "make" +args = ["cargo-build_longbridge-c"] +cwd = "c/cmake" + +[tasks.c.windows] +command = "msbuild" +args = [ + "longbridge.sln", + "-p:Configuration=Debug", + "/t:cargo-build_longbridge-c", +] +cwd = "c/cmake" + +[tasks.c-test] +command = "make" +args = ["test"] +cwd = "c/cmake" + +[tasks.c-test.windows] +command = "msbuild" +args = ["longbridge.sln", "-p:Configuration=Debug", "/t:test"] +cwd = "c/cmake" + +[tasks.cpp-test] +command = "make" +args = ["test_cpp"] +cwd = "c/cmake" + +[tasks.cpp-test.windows] +command = "msbuild" +args = ["longbridge.sln", "-p:Configuration=Debug", "/t:test_cpp"] +cwd = "c/cmake" + +[tasks.c-cmake] +command = "cmake" +args = ["../"] +cwd = "c/cmake" diff --git a/c/build.rs b/c/build.rs new file mode 100644 index 0000000000..de43becc97 --- /dev/null +++ b/c/build.rs @@ -0,0 +1,14 @@ +use std::env; + +use cbindgen::Config; + +fn main() { + let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); + + cbindgen::Builder::new() + .with_crate(crate_dir) + .with_config(Config::from_file("cbindgen.toml").expect("load cbindgen.toml")) + .generate() + .expect("Unable to generate bindings") + .write_to_file("csrc/longbridge.h"); +} diff --git a/c/cbindgen.toml b/c/cbindgen.toml new file mode 100644 index 0000000000..5e93294e41 --- /dev/null +++ b/c/cbindgen.toml @@ -0,0 +1,114 @@ +language = "c" +include_guard = "_LONGBRIDGE_H_" +documentation_style = "doxy" +cpp_compat = true + +[export.rename] +"CMarket" = "lb_market_t" +"CDecimal" = "lb_decimal_t" +"CAsyncCallback" = "lb_async_callback_t" +"CAsyncResult" = "lb_async_result_t" +"CError" = "lb_error_t" +"CConfig" = "lb_config_t" +"CPushQuote" = "lb_push_quote_t" +"CPushDepth" = "lb_push_depth_t" +"CPushBrokers" = "lb_push_brokers_t" +"CTradeSession" = "lb_trade_session_t" +"CTradeStatus" = "lb_trade_status_t" +"CTradeDirection" = "lb_trade_direction_t" +"COptionDirection" = "lb_option_direction_t" +"COptionType" = "lb_option_type_t" +"CWarrantType" = "lb_warrant_type_t" +"CAdjustType" = "lb_adjust_type_t" +"CPeriod" = "lb_period_t" +"CDepth" = "lb_depth_t" +"CBrokers" = "lb_brokers_t" +"CTrade" = "lb_trade_t" +"CPushTrades" = "lb_push_trades_t" +"CCandlestick" = "lb_candlestick_t" +"CPushCandlestick" = "lb_push_candlestick_t" +"COnQuoteCallback" = "lb_quote_callback_t" +"COnDepthCallback" = "lb_depth_callback_t" +"COnBrokersCallback" = "lb_brokers_callback_t" +"COnTradesCallback" = "lb_trades_callback_t" +"COnCandlestickCallback" = "lb_candlestick_callback_t" +"CQuoteContext" = "lb_quote_context_t" +"CSecurityStaticInfo" = "lb_security_static_info_t" +"CPrePostQuote" = "lb_prepost_quote_t" +"CSecurityQuote" = "lb_security_quote_t" +"COptionQuote" = "lb_option_quote_t" +"CDate" = "lb_date_t" +"CTime" = "lb_time_t" +"CWarrantQuote" = "lb_warrant_quote_t" +"CSecurityDepth" = "lb_security_depth_t" +"CSecurityBrokers" = "lb_security_brokers_t" +"CParticipantInfo" = "lb_participant_info_t" +"CIntradayLine" = "lb_intraday_line_t" +"CStrikePriceInfo" = "lb_strike_price_info_t" +"CIssuerInfo" = "lb_issuer_info_t" +"CTradingSessionInfo" = "lb_trading_session_info_t" +"CMarketTradingSession" = "lb_market_trading_session_t" +"CMarketTradingDays" = "lb_market_trading_days_t" +"CCapitalFlowLine" = "lb_capital_flow_line_t" +"CCapitalDistributionResponse" = "ln_capital_distribution_response_t" +"CCapitalDistribution" = "lb_capital_distribution_t" +"CRealtimeQuote" = "lb_realtime_quote_t" +"CTradeContext" = "lb_trade_context_t" +"CTopicType" = "lb_topic_type_t" +"COrderSide" = "lb_order_side_t" +"COrderType" = "lb_order_type_t" +"COrderStatus" = "lb_order_status_t" +"COrderTag" = "lb_order_tag_t" +"CTriggerStatus" = "lb_trigger_status_t" +"COutsideRTH" = "lb_outside_rth_t" +"CTimeInForceType" = "lb_time_in_force_type_t" +"COnOrderChangedCallback" = "lb_order_changed_callback_t" +"CPushOrderChanged" = "lb_push_order_changed_t" +"CExecution" = "lb_execution_t" +"CGetHistoryExecutionsOptions" = "lb_get_history_executions_options_t" +"CGetTodayExecutionsOptions" = "lb_get_today_executions_options_t" +"COrder" = "lb_order_t" +"CGetHistoryOrdersOptions" = "lb_get_history_orders_options_t" +"CGetTodayOrdersOptions" = "lb_get_today_orders_options_t" +"CReplaceOrderOptions" = "lb_replace_order_options_t" +"CSubmitOrderOptions" = "lb_submit_order_options_t" +"CCashInfo" = "lb_cash_info_t" +"CAccountBalance" = "lb_account_balance_t" +"CCashFlowDirection" = "lb_cash_flow_direction_t" +"CCashFlow" = "lb_cash_flow_t" +"CBalanceType" = "lb_balance_type_t" +"CGetCashFlowOptions" = "lb_get_cash_flow_options_t" +"CGetFundPositionsOptions" = "lb_get_fund_positions_options_t" +"CFundPositionsResponse" = "lb_fund_position_response_t" +"CFundPositionChannel" = "lb_fund_position_channel_t" +"CFundPosition" = "lb_fund_position_t" +"CGetStockPositionsOptions" = "lb_get_stock_positions_options_t" +"CStockPositionsResponse" = "lb_stock_position_response_t" +"CStockPositionChannel" = "lb_stock_position_channel_t" +"CStockPosition" = "lb_stock_position_t" + +[export] +include = [ + "CSecurityStaticInfo", + "CSecurityQuote", + "COptionQuote", + "CWarrantQuote", + "CSecurityDepth", + "CSecurityBrokers", + "CParticipantInfo", + "CIntradayLine", + "CStrikePriceInfo", + "CIssuerInfo", + "CTradingSessionInfo", + "CMarketTradingSession", + "CMarketTradingDays", + "CCapitalFlowLine", + "CCapitalDistributionResponse", + "CRealtimeQuote", + "CExecution", + "COrder", + "CAccountBalance", + "CCashFlow", + "CFundPositionsResponse", + "CStockPositionsResponse", +] diff --git a/c/crates/macros/Cargo.toml b/c/crates/macros/Cargo.toml new file mode 100644 index 0000000000..78849e1afe --- /dev/null +++ b/c/crates/macros/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "longbridge-c-macros" +version = "0.2.25" +edition = "2021" + +[lib] +proc-macro = true + +[dependencies] +darling = "0.14.0" +proc-macro2 = "1.0.29" +quote = "1.0.9" +syn = { version = "1.0.77", features = [] } +thiserror = "1.0.29" diff --git a/c/crates/macros/src/cenum.rs b/c/crates/macros/src/cenum.rs new file mode 100644 index 0000000000..63c521fd8e --- /dev/null +++ b/c/crates/macros/src/cenum.rs @@ -0,0 +1,108 @@ +use darling::{ + ast::{Data, Fields}, + util::Ignored, + FromDeriveInput, FromVariant, +}; +use proc_macro2::TokenStream; +use quote::quote; +use syn::{DeriveInput, Error, Ident, TypePath}; + +use crate::error::GeneratorResult; + +#[derive(FromVariant)] +#[darling(attributes(c), forward_attrs(doc))] +struct EnumItem { + ident: Ident, + fields: Fields, + + #[darling(default)] + remote: Option, +} + +#[derive(FromDeriveInput)] +#[darling(attributes(c), forward_attrs(doc))] +struct EnumArgs { + ident: Ident, + data: Data, + + remote: TypePath, + #[darling(default)] + from: Option, + #[darling(default)] + into: Option, +} + +pub(crate) fn generate(args: DeriveInput) -> GeneratorResult { + let EnumArgs { + ident, + data, + remote, + from, + into, + } = EnumArgs::from_derive_input(&args)?; + let from = from.unwrap_or(true); + let into = into.unwrap_or(true); + + let e = match data { + Data::Enum(e) => e, + _ => return Err(Error::new_spanned(ident, "can only be applied to an enum").into()), + }; + + let mut from_remote = Vec::new(); + let mut from_local = Vec::new(); + + for variant in e { + if !variant.fields.is_empty() { + return Err(Error::new_spanned( + &variant.ident, + format!("Invalid enum variant {}", variant.ident), + ) + .into()); + } + + let item_ident = &variant.ident; + let remote_ident = variant.remote.as_ref().unwrap_or(&variant.ident); + + from_remote.push(quote! { + #remote::#remote_ident => #ident::#item_ident, + }); + from_local.push(quote! { + #ident::#item_ident => #remote::#remote_ident, + }); + } + + let impl_from = if from { + Some(quote! { + impl ::std::convert::From<#remote> for #ident { + fn from(value: #remote) -> #ident { + match value { + #(#from_remote)* + } + } + } + }) + } else { + None + }; + + let impl_into = if into { + Some(quote! { + impl ::std::convert::From<#ident> for #remote { + fn from(value: #ident) -> #remote { + match value { + #(#from_local)* + } + } + } + }) + } else { + None + }; + + let expanded = quote! { + #impl_from + #impl_into + }; + + Ok(expanded) +} diff --git a/c/crates/macros/src/error.rs b/c/crates/macros/src/error.rs new file mode 100644 index 0000000000..a715a210c0 --- /dev/null +++ b/c/crates/macros/src/error.rs @@ -0,0 +1,22 @@ +use proc_macro2::TokenStream; +use thiserror::Error; + +#[derive(Error, Debug)] +pub(crate) enum GeneratorError { + #[error("{0}")] + Syn(#[from] syn::Error), + + #[error("{0}")] + Darling(#[from] darling::Error), +} + +impl GeneratorError { + pub(crate) fn write_errors(self) -> TokenStream { + match self { + GeneratorError::Syn(err) => err.to_compile_error(), + GeneratorError::Darling(err) => err.write_errors(), + } + } +} + +pub(crate) type GeneratorResult = std::result::Result; diff --git a/c/crates/macros/src/lib.rs b/c/crates/macros/src/lib.rs new file mode 100644 index 0000000000..bba8da1410 --- /dev/null +++ b/c/crates/macros/src/lib.rs @@ -0,0 +1,14 @@ +mod cenum; +mod error; + +use proc_macro::TokenStream; +use syn::{parse_macro_input, DeriveInput}; + +#[proc_macro_derive(CEnum, attributes(c))] +pub fn derive_cenum(input: TokenStream) -> TokenStream { + let args = parse_macro_input!(input as DeriveInput); + match cenum::generate(args) { + Ok(stream) => stream.into(), + Err(err) => err.write_errors().into(), + } +} diff --git a/c/csrc/async_result.hpp b/c/csrc/async_result.hpp new file mode 100644 index 0000000000..86e789d546 --- /dev/null +++ b/c/csrc/async_result.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include + +#include "status.hpp" + +namespace longbridge { + +template +struct AsyncResult { + private: + Ctx ctx_; + Status status_; + const T* data_; + + public: + AsyncResult(Ctx ctx, Status status, const T* data) + : ctx_(ctx), status_(std::move(status)), data_(data) {} + + operator bool() { return status_.is_ok(); } + const T* operator->() { return data_; } + + const Ctx& context() { return ctx_; } + + /// Returns `true` if no errors occurs + bool is_ok() const { return status_.is_ok(); } + + /// Returns `true` if an errors occurs + bool is_err() const { return status_.is_err(); } +}; + +template +using AsyncCallback = std::function)>; + +} // namespace longbridge \ No newline at end of file diff --git a/c/csrc/config.hpp b/c/csrc/config.hpp new file mode 100644 index 0000000000..d0b965a134 --- /dev/null +++ b/c/csrc/config.hpp @@ -0,0 +1,41 @@ +#pragma once + +#include + +#include "longbridge.h" +#include "status.hpp" + +namespace longbridge { + +class Config { + private: + lb_config_t *config_; + + public: + Config() { config_ = nullptr; } + Config(lb_config_t *config) { config_ = config; } + Config(const Config &) = delete; + Config(Config &&other) { + config_ = other.config_; + other.config_ = nullptr; + } + ~Config() { + if (config_) { + lb_config_free(config_); + } + } + + operator const lb_config_t *() const { return config_; } + + static Status from_env(Config &config) { + lb_error_t *err = nullptr; + lb_config_t *config_ptr = lb_config_from_env(&err); + Status status(err); + if (status.is_ok()) { + config.config_ = config_ptr; + } + return status; + } +}; + +} // namespace longbridge \ No newline at end of file diff --git a/c/csrc/decimal.hpp b/c/csrc/decimal.hpp new file mode 100644 index 0000000000..a35a267d42 --- /dev/null +++ b/c/csrc/decimal.hpp @@ -0,0 +1,199 @@ +#pragma once + +#include +#include + +#include "longbridge.h" + +namespace longbridge { + +class Decimal { + private: + lb_decimal_t *value_; + + public: + Decimal(const lb_decimal_t *other) { value_ = lb_decimal_clone(other); } + Decimal(const Decimal &other) { value_ = lb_decimal_clone(other); } + Decimal(const char *str) { value_ = lb_decimal_from_str(str); } + Decimal(const std::string &str) { value_ = lb_decimal_from_str(str.c_str()); } + Decimal(double other) { value_ = lb_decimal_from_double(other); } + ~Decimal() { lb_decimal_free(value_); } + + Decimal clone() const { return Decimal(value_); } + + operator const lb_decimal_t *() const { return value_; } + operator lb_decimal_t *() { return value_; } + + Decimal operator+(const Decimal &other) const { + auto new_value = Decimal(value_); + lb_decimal_add(new_value, other); + return new_value; + } + + Decimal &operator+=(const Decimal &other) { + lb_decimal_add(value_, other); + return *this; + } + + Decimal operator-(const Decimal &other) const { + auto new_value = Decimal(value_); + lb_decimal_sub(new_value, other); + return new_value; + } + + Decimal &operator-=(const Decimal &other) { + lb_decimal_sub(value_, other); + return *this; + } + + Decimal operator*(const Decimal &other) const { + auto new_value = Decimal(value_); + lb_decimal_mul(new_value, other); + return new_value; + } + + Decimal &operator*=(const Decimal &other) { + lb_decimal_sub(value_, other); + return *this; + } + + Decimal operator/(const Decimal &other) const { + auto new_value = Decimal(value_); + lb_decimal_div(new_value, other); + return new_value; + } + + Decimal &operator/=(const Decimal &other) { + lb_decimal_div(value_, other); + return *this; + } + + Decimal operator%(const Decimal &other) const { + auto new_value = Decimal(value_); + lb_decimal_rem(new_value, other); + return new_value; + } + + Decimal &operator%=(const Decimal &other) { + lb_decimal_div(value_, other); + return *this; + } + + Decimal operator-() const { + auto new_value = Decimal(value_); + lb_decimal_neg(new_value); + return new_value; + } + + bool operator>(const Decimal &other) const { + return lb_decimal_gt(value_, other); + } + + bool operator>=(const Decimal &other) const { + return lb_decimal_gte(value_, other); + } + + bool operator==(const Decimal &other) const { + return lb_decimal_eq(value_, other); + } + + bool operator<(const Decimal &other) const { + return lb_decimal_lt(value_, other); + } + + bool operator<=(const Decimal &other) const { + return lb_decimal_lte(value_, other); + } + + double to_double() const { return lb_decimal_to_double(value_); } + + /// Computes the absolute value. + void abs() { lb_decimal_abs(value_); } + + /// Returns the smallest integer greater than or equal to a number. + void ceil() { lb_decimal_ceil(value_); } + + /// Returns the largest integer less than or equal to a number. + void floor() { lb_decimal_floor(value_); } + + /// Returns a new Decimal representing the fractional portion of the number. + void fract() { lb_decimal_fract(value_); } + + /// Returns `true` if the decimal is negative. + bool is_negative() const { return lb_decimal_is_negative(value_); } + + /// Returns `true` if the decimal is positive. + bool is_positive() const { return lb_decimal_is_positive(value_); } + + /// Returns `true` if this Decimal number is equivalent to zero. + bool is_zero() const { return lb_decimal_is_zero(value_); } + + /// Returns the maximum of the two numbers. + Decimal max(const Decimal &other) const { + auto max_value = lb_decimal_max(value_, other); + return Decimal(lb_decimal_clone(max_value)); + } + + /// Returns the minimum of the two numbers. + Decimal min(const Decimal &other) const { + auto max_value = lb_decimal_min(value_, other); + return Decimal(lb_decimal_clone(max_value)); + } + + /// Strips any trailing zero’s from a Decimal and converts `-0` to `0`. + void normalize() { lb_decimal_normalize(value_); } + + /// Returns a new Decimal number with no fractional portion (i.e. an integer). + /// Rounding currently follows “Bankers Rounding” rules. e.g. `6.5` -> `6`, + /// `7.5` -> `8` + void round() { lb_decimal_round(value_); } + + /// Returns a new Decimal integral with no fractional portion. This is a true + /// truncation whereby no rounding is performed. + void trunc() { lb_decimal_trunc(value_); } + + /// Computes the sine of a number (in radians) + void sin() { lb_decimal_sin(value_); } + + /// Computes the cosine of a number (in radians) + void cos() { lb_decimal_cos(value_); } + + /// Computes the tangent of a number (in radians). Panics upon overflow or + /// upon approaching a limit. + void tan() { lb_decimal_tan(value_); } + + /// The square root of a Decimal. Uses a standard Babylonian method. + void sqrt() { lb_decimal_sqrt(value_); } + + /// Raise self to the given Decimal exponent: xy. If `exp` is not + /// whole then the approximation ey*ln(x) is used. + void pow(const Decimal &exp) { lb_decimal_pow(value_, exp); } + + /// Calculates the natural logarithm for a Decimal calculated using Taylor’s + /// series. + void ln() { lb_decimal_ln(value_); } + + /// Calculates the base 10 logarithm of a specified Decimal number. + void log10() { lb_decimal_log10(value_); } + + /// The estimated exponential function, ex. Stops calculating when it is + /// within tolerance of roughly `0.0000002`. + void exp() { lb_decimal_exp(value_); } + + /// The estimated exponential function, ex using the `tolerance` + /// provided as a hint as to when to stop calculating. A larger + /// tolerance will cause the number to stop calculating sooner at the + /// potential cost of a slightly less accurate result. + void exp_with_tolerance(const Decimal &tolerance) { + lb_decimal_exp_with_tolerance(value_, tolerance); + } + + /// Abramowitz Approximation of Error Function from + /// [wikipedia](https://en.wikipedia.org/wiki/Error_function#Numerical_approximations) + void erf() { lb_decimal_erf(value_); } + + /// The Probability density function for a Normal distribution. + void norm_pdf() { lb_decimal_norm_pdf(value_); } +}; + +} // namespace longbridge \ No newline at end of file diff --git a/c/csrc/longbridge.h b/c/csrc/longbridge.h new file mode 100644 index 0000000000..e4b405d268 --- /dev/null +++ b/c/csrc/longbridge.h @@ -0,0 +1,2909 @@ +#ifndef _LONGBRIDGE_H_ +#define _LONGBRIDGE_H_ + +#include +#include +#include +#include + +/** + * Quote + */ +#define LB_SUBFLAGS_QUOTE 1 + +/** + * Depth + */ +#define LB_SUBFLAGS_DEPTH 2 + +/** + * Broker + */ +#define LB_SUBFLAGS_BROKER 4 + +/** + * Trade + */ +#define LB_SUBFLAGS_TRADE 8 + +/** + * US stock options + */ +#define LB_DERIVATIVE_TYPE_OPTION 1 + +/** + * HK warrants + */ +#define LB_DERIVATIVE_TYPE_WARRANT 2 + +/** + * Adjust type + */ +typedef enum lb_adjust_type_t { + /** + * Actual + */ + AdjustTypeNoAdjust, + /** + * Adjust forward + */ + AdjustTypeForward, +} lb_adjust_type_t; + +/** + * Balance type + */ +typedef enum lb_balance_type_t { + /** + * Unknown + */ + BalanceTypeUnknown, + /** + * Unknown + */ + BalanceTypeCash, + /** + * Unknown + */ + BalanceTypeStock, + /** + * Unknown + */ + BalanceTypeFund, +} lb_balance_type_t; + +/** + * Cash flow direction + */ +typedef enum lb_cash_flow_direction_t { + /** + * Unknown + */ + CashFlowDirectionUnknown, + /** + * Out + */ + CashFlowDirectionOutside, + /** + * In + */ + CashFlowDirectionIn, +} lb_cash_flow_direction_t; + +/** + * Market type + */ +typedef enum lb_market_t { + /** + * Unknown + */ + MarketUnknown, + /** + * US market + */ + MarketUS, + /** + * HK market + */ + MarketHK, + /** + * CN market + */ + MarketCN, + /** + * SG market + */ + MarketSG, +} lb_market_t; + +/** + * Option direction + */ +typedef enum lb_option_direction_t { + /** + * Unknown + */ + OptionDirectionUnknown, + /** + * Put + */ + OptionDirectionAmerican, + /** + * Call + */ + OptionDirectionEurope, +} lb_option_direction_t; + +/** + * Option type + */ +typedef enum lb_option_type_t { + /** + * Unknown + */ + OptionTypeUnknown, + /** + * American + */ + OptionTypeAmerican, + /** + * Enrope + */ + OptionTypeEurope, +} lb_option_type_t; + +/** + * Order side + */ +typedef enum lb_order_side_t { + /** + * Unknown + */ + OrderSideUnknown, + /** + * Unknown + */ + OrderSideBuy, + /** + * Unknown + */ + OrderSideSell, +} lb_order_side_t; + +/** + * Order status + */ +typedef enum lb_order_status_t { + /** + * Unknown + */ + OrderStatusUnknown, + /** + * Not reported + */ + OrderStatusNotReported, + /** + * Not reported (Replaced Order) + */ + OrderStatusReplacedNotReported, + /** + * Not reported (Protected Order) + */ + OrderStatusProtectedNotReported, + /** + * Not reported (Conditional Order) + */ + OrderStatusVarietiesNotReported, + /** + * Filled + */ + OrderStatusFilled, + /** + * Wait To New + */ + OrderStatusWaitToNew, + /** + * New + */ + OrderStatusNew, + /** + * Wait To Replace + */ + OrderStatusWaitToReplace, + /** + * Pending Replace + */ + OrderStatusPendingReplace, + /** + * Replaced + */ + OrderStatusReplaced, + /** + * Partial Filled + */ + OrderStatusPartialFilled, + /** + * Wait To Cancel + */ + OrderStatusWaitToCancel, + /** + * Pending Cancel + */ + OrderStatusPendingCancel, + /** + * Rejected + */ + OrderStatusRejected, + /** + * Canceled + */ + OrderStatusCanceled, + /** + * Expired + */ + OrderStatusExpired, + /** + * Partial Withdrawal + */ + OrderStatusPartialWithdrawal, +} lb_order_status_t; + +/** + * Order tag + */ +typedef enum lb_order_tag_t { + /** + * Unknown + */ + OrderTagUnknown, + /** + * Normal Order + */ + OrderTagNormal, + /** + * Long term Order + */ + OrderTagLongTerm, + /** + * Grey Order + */ + OrderTagGrey, +} lb_order_tag_t; + +/** + * Order type + */ +typedef enum lb_order_type_t { + /** + * Unknown + */ + OrderTypeUnknown, + /** + * Limit Order + */ + OrderTypeLO, + /** + * Enhanced Limit Order + */ + OrderTypeELO, + /** + * Market Order + */ + OrderTypeMO, + /** + * At-auction Order + */ + OrderTypeAO, + /** + * At-auction Limit Order + */ + OrderTypeALO, + /** + * Odd Lots + */ + OrderTypeODD, + /** + * Limit If Touched + */ + OrderTypeLIT, + /** + * Market If Touched + */ + OrderTypeMIT, + /** + * Trailing Limit If Touched (Trailing Amount) + */ + OrderTypeTSLPAMT, + /** + * Trailing Limit If Touched (Trailing Percent) + */ + OrderTypeTSLPPCT, + /** + * Trailing Market If Touched (Trailing Amount) + */ + OrderTypeTSMAMT, + /** + * Trailing Market If Touched (Trailing Percent) + */ + OrderTypeTSMPCT, +} lb_order_type_t; + +/** + * Enable or disable outside regular trading hours + */ +typedef enum lb_outside_rth_t { + /** + * Unknown + */ + OutsideRTHUnknown, + /** + * Regular trading hour only + */ + OutsideRTHOnly, + /** + * Any time + */ + OutsideRTHAnyTime, +} lb_outside_rth_t; + +/** + * Candlestick period + */ +typedef enum lb_period_t { + /** + * One Minute + */ + PeriodUnknown, + /** + * One Minute + */ + PeriodMin1, + /** + * Five Minutes + */ + PeriodMin5, + /** + * Fifteen Minutes + */ + PeriodMin15, + /** + * Thirty Minutes + */ + PeriodMin30, + /** + * Sixty Minutes + */ + PeriodMin60, + /** + * One Day + */ + PeriodDay, + /** + * One Week + */ + PeriodWeek, + /** + * One Month + */ + PeriodMonth, + /** + * One Year + */ + PeriodYear, +} lb_period_t; + +/** + * Time in force Type + */ +typedef enum lb_time_in_force_type_t { + /** + * Unknown + */ + TimeInForceUnknown, + /** + * Day Order + */ + TimeInForceDay, + /** + * Good Til Canceled Order + */ + TimeInForceGoodTilCanceled, + /** + * Good Til Date Order + */ + TimeInForceGoodTilDate, +} lb_time_in_force_type_t; + +/** + * Topic type + */ +typedef enum lb_topic_type_t { + /** + * Trading + */ + TopicPrivate, +} lb_topic_type_t; + +/** + * Trade direction + */ +typedef enum lb_trade_direction_t { + /** + * Neutral + */ + TradeDirectionNeutral, + /** + * Down + */ + TradeDirectionDown, + /** + * Up + */ + TradeDirectionUp, +} lb_trade_direction_t; + +/** + * Trade session + */ +typedef enum lb_trade_session_t { + /** + * Trading + */ + TradeSessionNormal, + /** + * Pre-Trading + */ + TradeSessionPre, + /** + * Post-Trading + */ + TradeSessionPost, +} lb_trade_session_t; + +/** + * Trade status + */ +typedef enum lb_trade_status_t { + /** + * Normal + */ + TradeStatusNormal, + /** + * Suspension + */ + TradeStatusHalted, + /** + * Delisted + */ + TradeStatusDelisted, + /** + * Fuse + */ + TradeStatusFuse, + /** + * Papare List + */ + TradeStatusPrepareList, + /** + * Code Moved + */ + TradeStatusCodeMoved, + /** + * To Be Opened + */ + TradeStatusToBeOpened, + /** + * Split Stock Halts + */ + TradeStatusSplitStockHalts, + /** + * Expired + */ + TradeStatusExpired, + /** + * Warrant To BeListed + */ + TradeStatusWarrantPrepareList, + /** + * Suspend + */ + TradeStatusSuspendTrade, +} lb_trade_status_t; + +/** + * Order tag + */ +typedef enum lb_trigger_status_t { + /** + * Unknown + */ + TriggerStatusUnknown, + /** + * Deactive + */ + TriggerStatusDeactive, + /** + * Active + */ + TriggerStatusActive, + /** + * Released + */ + TriggerStatusReleased, +} lb_trigger_status_t; + +/** + * Warrant type + */ +typedef enum lb_warrant_type_t { + /** + * Unknown + */ + WarrantTypeUnknown, + /** + * Put + */ + WarrantTypePut, + /** + * Call + */ + WarrantTypeCall, + /** + * Bull + */ + WarrantTypeBull, + /** + * Bear + */ + WarrantTypeBear, + /** + * Inline + */ + WarrantTypeInline, +} lb_warrant_type_t; + +/** + * Configuration options for Longbridge sdk + */ +typedef struct lb_config_t lb_config_t; + +typedef struct lb_decimal_t lb_decimal_t; + +typedef struct lb_error_t lb_error_t; + +/** + * Quote of US pre/post market + */ +typedef struct lb_prepost_quote_t lb_prepost_quote_t; + +/** + * Quote context + */ +typedef struct lb_quote_context_t lb_quote_context_t; + +/** + * Trade context + */ +typedef struct lb_trade_context_t lb_trade_context_t; + +typedef struct lb_async_result_t { + const void *ctx; + const struct lb_error_t *error; + void *data; + uintptr_t length; + void *userdata; +} lb_async_result_t; + +typedef void (*lb_async_callback_t)(const struct lb_async_result_t*); + +/** + * Quote message + */ +typedef struct lb_push_quote_t { + /** + * Security code + */ + const char *symbol; + /** + * Latest price + */ + const struct lb_decimal_t *last_done; + /** + * Open + */ + const struct lb_decimal_t *open; + /** + * High + */ + const struct lb_decimal_t *high; + /** + * Low + */ + const struct lb_decimal_t *low; + /** + * Time of latest price + */ + int64_t timestamp; + /** + * Volume + */ + int64_t volume; + /** + * Turnover + */ + const struct lb_decimal_t *turnover; + /** + * Security trading status + */ + enum lb_trade_status_t trade_status; + /** + * Trade session + */ + enum lb_trade_session_t trade_session; +} lb_push_quote_t; + +typedef void (*lb_quote_callback_t)(const struct lb_quote_context_t*, const struct lb_push_quote_t*); + +/** + * Depth + */ +typedef struct lb_depth_t { + /** + * Position + */ + int32_t position; + /** + * Price + */ + const struct lb_decimal_t *price; + /** + * Volume + */ + int64_t volume; + /** + * Number of orders + */ + int64_t order_num; +} lb_depth_t; + +/** + * Quote message + */ +typedef struct lb_push_depth_t { + /** + * Security code + */ + const char *symbol; + /** + * Ask depth + */ + const struct lb_depth_t *asks; + /** + * Number of asks + */ + uintptr_t num_asks; + /** + * Bid depth + */ + const struct lb_depth_t *bids; + /** + * Number of bids + */ + uintptr_t num_bids; +} lb_push_depth_t; + +typedef void (*lb_depth_callback_t)(const struct lb_quote_context_t*, const struct lb_push_depth_t*); + +/** + * Brokers + */ +typedef struct lb_brokers_t { + /** + * Position + */ + int32_t position; + /** + * Broker IDs + */ + const int32_t *broker_ids; + /** + * Number of broker IDs + */ + uintptr_t num_broker_ids; +} lb_brokers_t; + +/** + * Brokers message + */ +typedef struct lb_push_brokers_t { + /** + * Security code + */ + const char *symbol; + /** + * Ask depth + */ + const struct lb_brokers_t *ask_brokers; + /** + * Number of ask brokers + */ + uintptr_t num_ask_brokers; + /** + * Bid depth + */ + const struct lb_brokers_t *bid_brokers; + /** + * Number of bid brokers + */ + uintptr_t num_bids; +} lb_push_brokers_t; + +typedef void (*lb_brokers_callback_t)(const struct lb_quote_context_t*, const struct lb_push_brokers_t*); + +/** + * Trade + */ +typedef struct lb_trade_t { + /** + * Price + */ + const struct lb_decimal_t *price; + /** + * Volume + */ + int64_t volume; + /** + * Time of trading + */ + int64_t timestamp; + /** + * Trade type + * + * HK + * + * - `*` - Overseas trade + * - `D` - Odd-lot trade + * - `M` - Non-direct off-exchange trade + * - `P` - Late trade (Off-exchange previous day) + * - `U` - Auction trade + * - `X` - Direct off-exchange trade + * - `Y` - Automatch internalized + * - `` - Automatch normal + * + * US + * + * - `` - Regular sale + * - `A` - Acquisition + * - `B` - Bunched trade + * - `D` - Distribution + * - `F` - Intermarket sweep + * - `G` - Bunched sold trades + * - `H` - Price variation trade + * - `I` - Odd lot trade + * - `K` - Rule 155 trde(NYSE MKT) + * - `M` - Market center close price + * - `P` - Prior reference price + * - `Q` - Market center open price + * - `S` - Split trade + * - `V` - Contingent trade + * - `W` - Average price trade + * - `X` - Cross trade + * - `1` - Stopped stock(Regular trade) + */ + const char *trade_type; + /** + * Trade direction + */ + enum lb_trade_direction_t direction; + /** + * Trade session + */ + enum lb_trade_session_t trade_session; +} lb_trade_t; + +/** + * Trades message + */ +typedef struct lb_push_trades_t { + /** + * Security code + */ + const char *symbol; + /** + * Trades data + */ + const struct lb_trade_t *trades; + /** + * Number of trades + */ + uintptr_t num_trades; +} lb_push_trades_t; + +typedef void (*lb_trades_callback_t)(const struct lb_quote_context_t*, const struct lb_push_trades_t*); + +/** + * Candlestick + */ +typedef struct lb_candlestick_t { + /** + * Close price + */ + const struct lb_decimal_t *close; + /** + * Open price + */ + const struct lb_decimal_t *open; + /** + * Low price + */ + const struct lb_decimal_t *low; + /** + * High price + */ + const struct lb_decimal_t *high; + /** + * Volume + */ + int64_t volume; + /** + * Turnover + */ + const struct lb_decimal_t *turnover; + /** + * Timestamp + */ + int64_t timestamp; +} lb_candlestick_t; + +/** + * Candlestick updated message + */ +typedef struct lb_push_candlestick_t { + /** + * Security code + */ + const char *symbol; + /** + * Period type + */ + enum lb_period_t period; + /** + * Candlestick + */ + struct lb_candlestick_t candlestick; +} lb_push_candlestick_t; + +typedef void (*lb_candlestick_callback_t)(const struct lb_quote_context_t*, const struct lb_push_candlestick_t*); + +typedef struct lb_date_t { + int32_t year; + uint8_t month; + uint8_t day; +} lb_date_t; + +/** + * Order changed message + */ +typedef struct lb_push_order_changed_t { + /** + * Order side + */ + enum lb_order_side_t side; + /** + * Stock name + */ + const char *stock_name; + /** + * Submitted quantity + */ + int64_t submitted_quantity; + /** + * Order symbol + */ + const char *symbol; + /** + * Order type + */ + enum lb_order_type_t order_type; + /** + * Submitted price + */ + const struct lb_decimal_t *submitted_price; + /** + * Executed quantity + */ + int64_t executed_quantity; + /** + * Executed price (maybe null) + */ + const struct lb_decimal_t *executed_price; + /** + * Order ID + */ + const char *order_id; + /** + * Currency + */ + const char *currency; + /** + * Order status + */ + enum lb_order_status_t status; + /** + * Submitted time + */ + int64_t submitted_at; + /** + * Last updated time + */ + int64_t updated_at; + /** + * Order trigger price (maybe null) + */ + const struct lb_decimal_t *trigger_price; + /** + * Rejected message or remark + */ + const char *msg; + /** + * Order tag + */ + enum lb_order_tag_t tag; + /** + * Conditional order trigger status (maybe null) + */ + const enum lb_trigger_status_t *trigger_status; + /** + * Conditional order trigger time (maybe null) + */ + const int64_t *trigger_at; + /** + * Trailing amount (maybe null) + */ + const struct lb_decimal_t *trailing_amount; + /** + * Trailing percent (maybe null) + */ + const struct lb_decimal_t *trailing_percent; + /** + * Limit offset amount (maybe null) + */ + const struct lb_decimal_t *limit_offset; + /** + * Account no + */ + const char *account_no; +} lb_push_order_changed_t; + +typedef void (*lb_order_changed_callback_t)(const struct lb_trade_context_t*, const struct lb_push_order_changed_t*); + +/** + * Options for get histroy executions request + */ +typedef struct lb_get_history_executions_options_t { + /** + * Start time (can null) + */ + const int64_t *start_at; + /** + * End time (can null) + */ + const int64_t *end_at; + /** + * Security code (can null) + */ + const char *symbol; +} lb_get_history_executions_options_t; + +/** + * Options for get today executions request + */ +typedef struct lb_get_today_executions_options_t { + /** + * Security code (can null) + */ + const char *symbol; + /** + * Order id (can null) + */ + const char *order_id; +} lb_get_today_executions_options_t; + +/** + * Options for get history orders request + */ +typedef struct lb_get_history_orders_options_t { + /** + * Security symbol (can null) + */ + const char *symbol; + /** + * Order status (can null) + */ + const enum lb_order_status_t *status; + /** + * Number of order status + */ + uintptr_t num_status; + /** + * Order side (can null) + */ + const enum lb_order_side_t *side; + /** + * Market (can null) + */ + const enum lb_market_t *market; + /** + * Start time (can null) + */ + const int64_t *start_at; + /** + * End time (can null) + */ + const int64_t *end_at; +} lb_get_history_orders_options_t; + +/** + * Options for get today orders request + */ +typedef struct lb_get_today_orders_options_t { + /** + * Security symbol (can null) + */ + const char *symbol; + /** + * Order status (can null) + */ + const enum lb_order_status_t *status; + /** + * Number of order status + */ + uintptr_t num_status; + /** + * Order side (can null) + */ + const enum lb_order_side_t *side; + /** + * Market (can null) + */ + const enum lb_market_t *market; + /** + * Order id (can null) + */ + const char *order_id; +} lb_get_today_orders_options_t; + +/** + * Options for replace order request + */ +typedef struct lb_replace_order_options_t { + /** + * Order ID + */ + const char *order_id; + /** + * Quantity + */ + int64_t quantity; + /** + * Price (can null) + */ + const struct lb_decimal_t *price; + /** + * Trigger price (can null) + */ + const struct lb_decimal_t *trigger_price; + /** + * Limit offset (can null) + */ + const struct lb_decimal_t *limit_offset; + /** + * Trailing amount (can null) + */ + const struct lb_decimal_t *trailing_amount; + /** + * Trailing percent (can null) + */ + const struct lb_decimal_t *trailing_percent; + /** + * Remark (can null) + */ + const char *remark; +} lb_replace_order_options_t; + +/** + * Options for submit order request + */ +typedef struct lb_submit_order_options_t { + /** + * Security symbol + */ + const char *symbol; + /** + * Order type + */ + enum lb_order_type_t order_type; + /** + * Order side + */ + enum lb_order_side_t side; + /** + * Submitted price + */ + int64_t submitted_quantity; + /** + * Time in force type + */ + enum lb_time_in_force_type_t time_in_force; + /** + * Submitted price (can null) + */ + const struct lb_decimal_t *submitted_price; + /** + * Trigger price (`LIT` / `MIT` Required) (can null) + */ + const struct lb_decimal_t *trigger_price; + /** + * Limit offset amount (`TSLPAMT` / `TSLPPCT` Required) (can null) + */ + const struct lb_decimal_t *limit_offset; + /** + * Trailing amount (`TSLPAMT` / `TSMAMT` Required) (can null) + */ + const struct lb_decimal_t *trailing_amount; + /** + * Trailing percent (`TSLPPCT` / `TSMAPCT` Required) (can null) + */ + const struct lb_decimal_t *trailing_percent; + /** + * Long term order expire date (Required when `time_in_force` is + * `GoodTilDate`) (can null) + */ + const struct lb_date_t *expire_date; + /** + * Enable or disable outside regular trading hours (can null) + */ + const enum lb_outside_rth_t *outside_rth; + /** + * Remark (Maximum 64 characters) (can null) + */ + const char *remark; +} lb_submit_order_options_t; + +/** + * Options for get cash flow request + */ +typedef struct lb_get_cash_flow_options_t { + /** + * Start time + */ + int64_t start_at; + /** + * End time + */ + int64_t end_at; + /** + * Business type (can null) + */ + const enum lb_balance_type_t *business_type; + /** + * Security symbol + */ + const char *symbol; + /** + * Page number + */ + const uintptr_t *page; + /** + * Page size + */ + const uintptr_t *size; +} lb_get_cash_flow_options_t; + +/** + * Options for get fund positions request + */ +typedef struct lb_get_fund_positions_options_t { + /** + * Fund symbols (can null) + */ + const char *const *symbols; + /** + * Number of fund symbols + */ + uintptr_t num_symbols; +} lb_get_fund_positions_options_t; + +/** + * Options for get stock positions request + */ +typedef struct lb_get_stock_positions_options_t { + /** + * Fund symbols (can null) + */ + const char *const *symbols; + /** + * Number of fund symbols + */ + uintptr_t num_symbols; +} lb_get_stock_positions_options_t; + +/** + * The basic information of securities + */ +typedef struct lb_security_static_info_t { + /** + * Security code + */ + const char *symbol; + /** + * Security name (zh-CN) + */ + const char *name_cn; + /** + * Security name (en) + */ + const char *name_en; + /** + * Security name (zh-HK) + */ + const char *name_hk; + /** + * Exchange which the security belongs to + */ + const char *exchange; + /** + * Trading currency + */ + const char *currency; + /** + * Lot size + */ + int32_t lot_size; + /** + * Total shares + */ + int64_t total_shares; + /** + * Circulating shares + */ + int64_t circulating_shares; + /** + * HK shares (only HK stocks) + */ + int64_t hk_shares; + /** + * Earnings per share + */ + const struct lb_decimal_t *eps; + /** + * Earnings per share (TTM) + */ + const struct lb_decimal_t *eps_ttm; + /** + * Net assets per share + */ + const struct lb_decimal_t *bps; + /** + * Dividend yield + */ + const struct lb_decimal_t *dividend_yield; + /** + * Types of supported derivatives + */ + uint8_t stock_derivatives; +} lb_security_static_info_t; + +/** + * Quote of securitity + */ +typedef struct lb_security_quote_t { + /** + * Security code + */ + const char *symbol; + /** + * Latest price + */ + const struct lb_decimal_t *last_done; + /** + * Yesterday's close + */ + const struct lb_decimal_t *prev_close; + /** + * Open + */ + const struct lb_decimal_t *open; + /** + * High + */ + const struct lb_decimal_t *high; + /** + * Low + */ + const struct lb_decimal_t *low; + /** + * Time of latest price + */ + int64_t timestamp; + /** + * Volume + */ + int64_t volume; + /** + * Turnover + */ + const struct lb_decimal_t *turnover; + /** + * Security trading status + */ + enum lb_trade_status_t trade_status; + /** + * Quote of US pre market + */ + const struct lb_prepost_quote_t *pre_market_quote; + /** + * Quote of US post market + */ + const struct lb_prepost_quote_t *post_market_quote; +} lb_security_quote_t; + +/** + * Quote of option + */ +typedef struct lb_option_quote_t { + /** + * Security code + */ + const char *symbol; + /** + * Latest price + */ + const struct lb_decimal_t *last_done; + /** + * Yesterday's close + */ + const struct lb_decimal_t *prev_close; + /** + * Open + */ + const struct lb_decimal_t *open; + /** + * High + */ + const struct lb_decimal_t *high; + /** + * Low + */ + const struct lb_decimal_t *low; + /** + * Time of latest price + */ + int64_t timestamp; + /** + * Volume + */ + int64_t volume; + /** + * Turnover + */ + const struct lb_decimal_t *turnover; + /** + * Security trading status + */ + enum lb_trade_status_t trade_status; + /** + * Implied volatility + */ + const struct lb_decimal_t *implied_volatility; + /** + * Number of open positions + */ + int64_t open_interest; + /** + * Exprity date + */ + struct lb_date_t expiry_date; + /** + * Strike price + */ + const struct lb_decimal_t *strike_price; + /** + * Contract multiplier + */ + const struct lb_decimal_t *contract_multiplier; + /** + * Option type + */ + enum lb_option_type_t contract_type; + /** + * Contract size + */ + const struct lb_decimal_t *contract_size; + /** + * Option direction + */ + enum lb_option_direction_t direction; + /** + * Underlying security historical volatility of the option + */ + const struct lb_decimal_t *historical_volatility; + /** + * Underlying security symbol of the option + */ + const char *underlying_symbol; +} lb_option_quote_t; + +/** + * Quote of warrant + */ +typedef struct lb_warrant_quote_t { + /** + * Security code + */ + const char *symbol; + /** + * Latest price + */ + const struct lb_decimal_t *last_done; + /** + * Yesterday's close + */ + const struct lb_decimal_t *prev_close; + /** + * Open + */ + const struct lb_decimal_t *open; + /** + * High + */ + const struct lb_decimal_t *high; + /** + * Low + */ + const struct lb_decimal_t *low; + /** + * Time of latest price + */ + int64_t timestamp; + /** + * Volume + */ + int64_t volume; + /** + * Turnover + */ + const struct lb_decimal_t *turnover; + /** + * Security trading status + */ + enum lb_trade_status_t trade_status; + /** + * Implied volatility + */ + const struct lb_decimal_t *implied_volatility; + /** + * Exprity date + */ + struct lb_date_t expiry_date; + /** + * Last tradalbe date + */ + struct lb_date_t last_trade_date; + /** + * Outstanding ratio + */ + const struct lb_decimal_t *outstanding_ratio; + /** + * Outstanding quantity + */ + int64_t outstanding_quantity; + /** + * Conversion ratio + */ + const struct lb_decimal_t *conversion_ratio; + /** + * Warrant type + */ + enum lb_warrant_type_t category; + /** + * Strike price + */ + const struct lb_decimal_t *strike_price; + /** + * Upper bound price + */ + const struct lb_decimal_t *upper_strike_price; + /** + * Lower bound price + */ + const struct lb_decimal_t *lower_strike_price; + /** + * Call price + */ + const struct lb_decimal_t *call_price; + /** + * Underlying security symbol of the warrant + */ + const char *underlying_symbol; +} lb_warrant_quote_t; + +/** + * Quote message + */ +typedef struct lb_security_depth_t { + /** + * Ask depth + */ + const struct lb_depth_t *asks; + /** + * Number of asks + */ + uintptr_t num_asks; + /** + * Bid depth + */ + const struct lb_depth_t *bids; + /** + * Number of bids + */ + uintptr_t num_bids; +} lb_security_depth_t; + +/** + * Security brokers + */ +typedef struct lb_security_brokers_t { + /** + * Ask brokers + */ + const struct lb_brokers_t *ask_brokers; + /** + * Number of ask brokers + */ + uintptr_t num_ask_brokers; + /** + * Bid brokers + */ + const struct lb_brokers_t *bid_brokers; + /** + * Number of bid brokers + */ + uintptr_t num_bid_brokers; +} lb_security_brokers_t; + +/** + * Participant info + */ +typedef struct lb_participant_info_t { + /** + * Broker IDs + */ + const int32_t *broker_ids; + /** + * Number of broker IDs + */ + uintptr_t num_broker_ids; + /** + * Participant name (zh-CN) + */ + const char *name_cn; + /** + * Participant name (en) + */ + const char *name_en; + /** + * Participant name (zh-HK) + */ + const char *name_hk; +} lb_participant_info_t; + +/** + * Intraday line + */ +typedef struct lb_intraday_line_t { + /** + * Close price of the minute + */ + const struct lb_decimal_t *price; + /** + * Start time of the minute + */ + int64_t timestamp; + /** + * Volume + */ + int64_t volume; + /** + * Turnover + */ + const struct lb_decimal_t *turnover; + /** + * Average price + */ + const struct lb_decimal_t *avg_price; +} lb_intraday_line_t; + +/** + * Strike price info + */ +typedef struct lb_strike_price_info_t { + /** + * Strike price + */ + const struct lb_decimal_t *price; + /** + * Security code of call option + */ + const char *call_symbol; + /** + * Security code of put option + */ + const char *put_symbol; + /** + * Is standard + */ + bool standard; +} lb_strike_price_info_t; + +/** + * Issuer info + */ +typedef struct lb_issuer_info_t { + /** + * Issuer ID + */ + int32_t issuer_id; + /** + * Issuer name (zh-CN) + */ + const char *name_cn; + /** + * Issuer name (en) + */ + const char *name_en; + /** + * Issuer name (zh-HK) + */ + const char *name_hk; +} lb_issuer_info_t; + +typedef struct lb_time_t { + uint8_t hour; + uint8_t minute; + uint8_t second; +} lb_time_t; + +/** + * The information of trading session + */ +typedef struct lb_trading_session_info_t { + /** + * Being trading time + */ + struct lb_time_t begin_time; + /** + * End trading time + */ + struct lb_time_t end_time; + /** + * Trading session + */ + enum lb_trade_session_t trade_session; +} lb_trading_session_info_t; + +/** + * Market trading session + */ +typedef struct lb_market_trading_session_t { + /** + * Market + */ + enum lb_market_t market; + /** + * Trading sessions + */ + const struct lb_trading_session_info_t *trade_sessions; + /** + * Number trading sessions + */ + uintptr_t num_trade_sessions; +} lb_market_trading_session_t; + +/** + * Market trading days + */ +typedef struct lb_market_trading_days_t { + /** + * Trading days + */ + const struct lb_date_t *trading_days; + /** + * Number of trading days + */ + uintptr_t num_trading_days; + /** + * Half trading days + */ + const struct lb_date_t *half_trading_days; + /** + * Number of half trading days + */ + uintptr_t num_half_trading_days; +} lb_market_trading_days_t; + +/** + * Market trading days + */ +typedef struct lb_capital_flow_line_t { + /** + * Inflow capital data + */ + const struct lb_decimal_t *inflow; + /** + * Time + */ + int64_t timestamp; +} lb_capital_flow_line_t; + +/** + * Capital distribution + */ +typedef struct lb_capital_distribution_t { + /** + * Large order + */ + const struct lb_decimal_t *large; + /** + * Medium order + */ + const struct lb_decimal_t *medium; + /** + * Small order + */ + const struct lb_decimal_t *small; +} lb_capital_distribution_t; + +/** + * Capital distribution response + */ +typedef struct ln_capital_distribution_response_t { + /** + * Time + */ + int64_t timestamp; + /** + * Inflow capital data + */ + struct lb_capital_distribution_t capital_in; + /** + * Outflow capital data + */ + struct lb_capital_distribution_t capital_out; +} ln_capital_distribution_response_t; + +/** + * Real-time quote + */ +typedef struct lb_realtime_quote_t { + /** + * Security code + */ + const char *symbol; + /** + * Latest price + */ + const struct lb_decimal_t *last_done; + /** + * Open + */ + const struct lb_decimal_t *open; + /** + * High + */ + const struct lb_decimal_t *high; + /** + * Low + */ + const struct lb_decimal_t *low; + /** + * Time of latest price + */ + int64_t timestamp; + /** + * Volume + */ + int64_t volume; + /** + * Turnover + */ + const struct lb_decimal_t *turnover; + /** + * Security trading status + */ + enum lb_trade_status_t trade_status; +} lb_realtime_quote_t; + +/** + * Execution + */ +typedef struct lb_execution_t { + /** + * Order ID + */ + const char *order_id; + /** + * Execution ID + */ + const char *trade_id; + /** + * Security code + */ + const char *symbol; + /** + * Trade done time + */ + int64_t trade_done_at; + /** + * Executed quantity + */ + int64_t quantity; + /** + * Executed price + */ + const struct lb_decimal_t *price; +} lb_execution_t; + +/** + * Order + */ +typedef struct lb_order_t { + /** + * Order ID + */ + const char *order_id; + /** + * Order status + */ + enum lb_order_status_t status; + /** + * Stock name + */ + const char *stock_name; + /** + * Submitted quantity + */ + int64_t quantity; + /** + * Executed quantity + */ + int64_t executed_quantity; + /** + * Submitted price (maybe null) + */ + const struct lb_decimal_t *price; + /** + * Executed price (maybe null) + */ + const struct lb_decimal_t *executed_price; + /** + * Submitted time + */ + int64_t submitted_at; + /** + * Order side + */ + enum lb_order_side_t side; + /** + * Security code + */ + const char *symbol; + /** + * Order type + */ + enum lb_order_type_t order_type; + /** + * Last done (maybe null) + */ + const struct lb_decimal_t *last_done; + /** + * `LIT` / `MIT` Order Trigger Price (maybe null) + */ + const struct lb_decimal_t *trigger_price; + /** + * Rejected Message or remark + */ + const char *msg; + /** + * Order tag + */ + enum lb_order_tag_t tag; + /** + * Time in force type + */ + enum lb_time_in_force_type_t time_in_force; + /** + * Long term order expire date (maybe null) + */ + const struct lb_date_t *expire_date; + /** + * Last updated time (maybe null) + */ + const int64_t *updated_at; + /** + * Conditional order trigger time (maybe null) + */ + const int64_t *trigger_at; + /** + * `TSMAMT` / `TSLPAMT` order trailing amount (maybe null) + */ + const struct lb_decimal_t *trailing_amount; + /** + * `TSMPCT` / `TSLPPCT` order trailing percent (maybe null) + */ + const struct lb_decimal_t *trailing_percent; + /** + * `TSLPAMT` / `TSLPPCT` order limit offset amount (maybe null) + */ + const struct lb_decimal_t *limit_offset; + /** + * Conditional order trigger status (maybe null) + */ + const enum lb_trigger_status_t *trigger_status; + /** + * Currency + */ + const char *currency; + /** + * Enable or disable outside regular trading hours (maybe null) + */ + const enum lb_outside_rth_t *outside_rth; +} lb_order_t; + +/** + * Account balance + */ +typedef struct lb_cash_info_t { + /** + * Withdraw cash + */ + const struct lb_decimal_t *withdraw_cash; + /** + * Available cash + */ + const struct lb_decimal_t *available_cash; + /** + * Frozen cash + */ + const struct lb_decimal_t *frozen_cash; + /** + * Cash to be settled + */ + const struct lb_decimal_t *settling_cash; + /** + * Currency + */ + const char *currency; +} lb_cash_info_t; + +/** + * Account balance + */ +typedef struct lb_account_balance_t { + /** + * Total cash + */ + const struct lb_decimal_t *total_cash; + /** + * Maximum financing amount + */ + const struct lb_decimal_t *max_finance_amount; + /** + * Remaining financing amount + */ + const struct lb_decimal_t *remaining_finance_amount; + /** + * Risk control level + */ + int32_t risk_level; + /** + * Margin call + */ + const struct lb_decimal_t *margin_call; + /** + * Currency + */ + const char *currency; + /** + * Cash details + */ + const struct lb_cash_info_t *cash_infos; + /** + * Number of cash details + */ + uintptr_t num_cash_infos; + /** + * Net assets + */ + const struct lb_decimal_t *net_assets; + /** + * Initial margin + */ + const struct lb_decimal_t *init_margin; + /** + * Maintenance margin + */ + const struct lb_decimal_t *maintenance_margin; +} lb_account_balance_t; + +/** + * Cash flow + */ +typedef struct lb_cash_flow_t { + /** + * Cash flow name + */ + const char *transaction_flow_name; + /** + * Outflow direction + */ + enum lb_cash_flow_direction_t direction; + /** + * Balance type + */ + enum lb_balance_type_t business_type; + /** + * Cash amount + */ + const struct lb_decimal_t *balance; + /** + * Cash currency + */ + const char *currency; + /** + * Business time + */ + int64_t business_time; + /** + * Associated Stock code information (maybe null) + */ + const char *symbol; + /** + * Cash flow description + */ + const char *description; +} lb_cash_flow_t; + +/** + * Fund position + */ +typedef struct lb_fund_position_t { + /** + * Fund ISIN code + */ + const char *symbol; + /** + * Current equity + */ + const struct lb_decimal_t *current_net_asset_value; + /** + * Current equity time + */ + int64_t net_asset_value_day; + /** + * Fund name + */ + const char *symbol_name; + /** + * Currency + */ + const char *currency; + /** + * Net cost + */ + const struct lb_decimal_t *cost_net_asset_value; + /** + * Holding units + */ + const struct lb_decimal_t *holding_units; +} lb_fund_position_t; + +/** + * Fund position channel + */ +typedef struct lb_fund_position_channel_t { + /** + * Account type + */ + const char *account_channel; + /** + * Fund positions + */ + const struct lb_fund_position_t *positions; + /** + * Number of fund positions + */ + uintptr_t num_positions; +} lb_fund_position_channel_t; + +/** + * Fund positions response + */ +typedef struct lb_fund_position_response_t { + const struct lb_fund_position_channel_t *channels; + uintptr_t num_channels; +} lb_fund_position_response_t; + +/** + * Stock position + */ +typedef struct lb_stock_position_t { + /** + * Stock code + */ + const char *symbol; + /** + * Stock name + */ + const char *symbol_name; + /** + * The number of holdings + */ + int64_t quantity; + /** + * Available quantity + */ + int64_t available_quantity; + /** + * Currency + */ + const char *currency; + /** + * Cost Price(According to the client's choice of average purchase or + * diluted cost) + */ + const struct lb_decimal_t *cost_price; + /** + * Market + */ + enum lb_market_t market; +} lb_stock_position_t; + +/** + * Stock position channel + */ +typedef struct lb_stock_position_channel_t { + /** + * Account type + */ + const char *account_channel; + /** + * Stock positions + */ + const struct lb_stock_position_t *positions; + /** + * Number of stock positions + */ + uintptr_t num_positions; +} lb_stock_position_channel_t; + +/** + * Stock positions response + */ +typedef struct lb_stock_position_response_t { + const struct lb_stock_position_channel_t *channels; + uintptr_t num_channels; +} lb_stock_position_response_t; + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +/** + * Create a new `Config` from the given environment variables + * + * It first gets the environment variables from the `.env` file in the + * current directory. + * + * # Variables + * + * - `LONGBRIDGE_APP_KEY` - App key + * - `LONGBRIDGE_APP_SECRET` - App secret + * - `LONGBRIDGE_ACCESS_TOKEN` - Access token + * - `LONGBRIDGE_HTTP_URL` - HTTP endpoint url (Default: `https://openapi.longbridgeapp.com`) + * - `LONGBRIDGE_QUOTE_WS_URL` - Quote websocket endpoint url (Default: + * `wss://openapi-quote.longbridgeapp.com`) + * - `LONGBRIDGE_TRADE_WS_URL` - Trade websocket endpoint url (Default: + * `wss://openapi-trade.longbridgeapp.com`) + */ +struct lb_config_t *lb_config_from_env(struct lb_error_t **error); + +struct lb_config_t *lb_config_new(const char *app_key, + const char *app_secret, + const char *access_token, + const char *http_url, + const char *quote_ws_url, + const char *trade_ws_url); + +/** + * Free the config object + */ +void lb_config_free(struct lb_config_t *config); + +/** + * Free the error object + */ +void lb_error_free(struct lb_error_t *error); + +const char *lb_error_message(const struct lb_error_t *error); + +int64_t lb_error_code(const struct lb_error_t *error); + +void lb_quote_context_new(const struct lb_config_t *config, + lb_async_callback_t callback, + void *userdata); + +void lb_quote_context_retain(const struct lb_quote_context_t *ctx); + +void lb_quote_context_release(const struct lb_quote_context_t *ctx); + +void lb_quote_context_set_userdata(const struct lb_quote_context_t *ctx, void *userdata); + +void *lb_quote_context_userdata(const struct lb_quote_context_t *ctx); + +/** + * Set quote callback, after receiving the quote data push, it will call back + * to this function. + */ +void lb_quote_context_set_on_quote(const struct lb_quote_context_t *ctx, + lb_quote_callback_t callback); + +/** + * Set depth callback, after receiving the depth data push, it will call + * back to this function. + */ +void lb_quote_context_set_on_depth(const struct lb_quote_context_t *ctx, + lb_depth_callback_t callback); + +/** + * Set brokers callback, after receiving the brokers data push, it will + * call back to this function. + */ +void lb_quote_context_set_on_brokers(const struct lb_quote_context_t *ctx, + lb_brokers_callback_t callback); + +/** + * Set trades callback, after receiving the trades data push, it will call + * back to this function. + */ +void lb_quote_context_set_on_trades(const struct lb_quote_context_t *ctx, + lb_trades_callback_t callback); + +/** + * Set candlestick callback, after receiving the trades data push, it will + * call back to this function. + */ +void lb_quote_context_set_on_candlestick(const struct lb_quote_context_t *ctx, + lb_candlestick_callback_t callback); + +void lb_quote_context_subscribe(const struct lb_quote_context_t *ctx, + const char *const *symbols, + uintptr_t num_symbols, + uint8_t sub_types, + bool is_first_push, + lb_async_callback_t callback, + void *userdata); + +/** + * Unsubscribe + */ +void lb_quote_context_unsubscribe(const struct lb_quote_context_t *ctx, + const char *const *symbols, + uintptr_t num_symbols, + uint8_t sub_types, + lb_async_callback_t callback, + void *userdata); + +/** + * Subscribe security candlesticks + */ +void lb_quote_context_subscribe_candlesticks(const struct lb_quote_context_t *ctx, + const char *symbol, + enum lb_period_t period, + lb_async_callback_t callback, + void *userdata); + +/** + * Unsubscribe security candlesticks + */ +void lb_quote_context_unsubscribe_candlesticks(const struct lb_quote_context_t *ctx, + const char *symbol, + enum lb_period_t period, + lb_async_callback_t callback, + void *userdata); + +/** + * Get subscription information + */ +void lb_quote_context_subscrptions(const struct lb_quote_context_t *ctx, + lb_async_callback_t callback, + void *userdata); + +/** + * Get basic information of securities + */ +void lb_quote_context_static_info(const struct lb_quote_context_t *ctx, + const char *const *symbols, + uintptr_t num_symbols, + lb_async_callback_t callback, + void *userdata); + +/** + * Get quote of securities + */ +void lb_quote_context_quote(const struct lb_quote_context_t *ctx, + const char *const *symbols, + uintptr_t num_symbols, + lb_async_callback_t callback, + void *userdata); + +/** + * Get quote of option securities + */ +void lb_quote_context_option_quote(const struct lb_quote_context_t *ctx, + const char *const *symbols, + uintptr_t num_symbols, + lb_async_callback_t callback, + void *userdata); + +/** + * Get quote of warrant securities + */ +void lb_quote_context_warrant_quote(const struct lb_quote_context_t *ctx, + const char *const *symbols, + uintptr_t num_symbols, + lb_async_callback_t callback, + void *userdata); + +/** + * Get security depth + */ +void lb_quote_context_depth(const struct lb_quote_context_t *ctx, + const char *symbol, + lb_async_callback_t callback, + void *userdata); + +/** + * Get security brokers + */ +void lb_quote_context_brokers(const struct lb_quote_context_t *ctx, + const char *symbol, + lb_async_callback_t callback, + void *userdata); + +/** + * Get participants + */ +void lb_quote_context_participants(const struct lb_quote_context_t *ctx, + lb_async_callback_t callback, + void *userdata); + +/** + * Get security trades + */ +void lb_quote_context_trades(const struct lb_quote_context_t *ctx, + const char *symbol, + uintptr_t count, + lb_async_callback_t callback, + void *userdata); + +/** + * Get security intraday lines + */ +void lb_quote_context_intraday(const struct lb_quote_context_t *ctx, + const char *symbol, + lb_async_callback_t callback, + void *userdata); + +/** + * Get security candlesticks + */ +void lb_quote_context_candlesticks(const struct lb_quote_context_t *ctx, + const char *symbol, + enum lb_period_t period, + uintptr_t count, + enum lb_adjust_type_t adjust_type, + lb_async_callback_t callback, + void *userdata); + +/** + * Get option chain expiry date list + */ +void lb_quote_context_option_chain_expiry_date_list(const struct lb_quote_context_t *ctx, + const char *symbol, + lb_async_callback_t callback, + void *userdata); + +/** + * Get option chain info by date + */ +void lb_quote_context_option_chain_info_by_date(const struct lb_quote_context_t *ctx, + const char *symbol, + const struct lb_date_t *expiry_date, + lb_async_callback_t callback, + void *userdata); + +/** + * Get warrant issuers + */ +void lb_quote_context_warrant_issuers(const struct lb_quote_context_t *ctx, + lb_async_callback_t callback, + void *userdata); + +/** + * Get trading session of the day + */ +void lb_quote_context_trading_session(const struct lb_quote_context_t *ctx, + lb_async_callback_t callback, + void *userdata); + +/** + * Get market trading days + */ +void lb_quote_context_trading_days(const struct lb_quote_context_t *ctx, + enum lb_market_t market, + const struct lb_date_t *begin, + const struct lb_date_t *end, + lb_async_callback_t callback, + void *userdata); + +/** + * Get capital flow intraday + */ +void lb_quote_context_capital_flow(const struct lb_quote_context_t *ctx, + const char *symbol, + lb_async_callback_t callback, + void *userdata); + +/** + * Get capital distribution + */ +void lb_quote_context_capital_distribution(const struct lb_quote_context_t *ctx, + const char *symbol, + lb_async_callback_t callback, + void *userdata); + +/** + * Get quote of securities + * + * Get real-time quotes of the subscribed symbols, it always returns the data + * in the local storage. + */ +void lb_quote_context_realtime_quote(const struct lb_quote_context_t *ctx, + const char *const *symbols, + uintptr_t num_symbols, + lb_async_callback_t callback, + void *userdata); + +/** + * Get real-time depth + * + * Get real-time depth of the subscribed symbols, it always returns the data in + * the local storage. + */ +void lb_quote_context_realtime_depth(const struct lb_quote_context_t *ctx, + const char *symbol, + lb_async_callback_t callback, + void *userdata); + +/** + * Get real-time trades + * + * Get real-time trades of the subscribed symbols, it always returns the data + * in the local storage. + */ +void lb_quote_context_realtime_trades(const struct lb_quote_context_t *ctx, + const char *symbol, + uintptr_t count, + lb_async_callback_t callback, + void *userdata); + +/** + * Get real-time broker queue + * + * Get real-time broker queue of the subscribed symbols, it always returns the + * data in the local storage. + */ +void lb_quote_context_realtime_brokers(const struct lb_quote_context_t *ctx, + const char *symbol, + lb_async_callback_t callback, + void *userdata); + +/** + * Get real-time candlesticks + * + * Get real-time candlesticks of the subscribed symbols, it always returns the + * data in the local storage. + */ +void lb_quote_context_realtime_candlesticks(const struct lb_quote_context_t *ctx, + const char *symbol, + enum lb_period_t period, + uintptr_t count, + lb_async_callback_t callback, + void *userdata); + +void lb_trade_context_new(const struct lb_config_t *config, + lb_async_callback_t callback, + void *userdata); + +void lb_trade_context_retain(const struct lb_trade_context_t *ctx); + +void lb_trade_context_release(const struct lb_trade_context_t *ctx); + +void lb_trade_context_set_userdata(const struct lb_trade_context_t *ctx, void *userdata); + +void *lb_trade_context_userdata(const struct lb_trade_context_t *ctx); + +/** + * Set order changed callback, after receiving the order changed event, it will + * call back to this function. + */ +void lb_trade_context_set_on_order_changed(const struct lb_trade_context_t *ctx, + lb_order_changed_callback_t callback); + +void lb_trade_context_subscribe(const struct lb_trade_context_t *ctx, + const enum lb_topic_type_t *topics, + uintptr_t num_topics, + lb_async_callback_t callback, + void *userdata); + +void lb_trade_context_unsubscribe(const struct lb_trade_context_t *ctx, + const enum lb_topic_type_t *topics, + uintptr_t num_topics, + lb_async_callback_t callback, + void *userdata); + +/** + * Get history executions + * + * @param[in] opts Options for get histroy executions request (can null) + */ +void lb_trade_context_history_executions(const struct lb_trade_context_t *ctx, + const struct lb_get_history_executions_options_t *opts, + lb_async_callback_t callback, + void *userdata); + +/** + * Get today executions + * + * @param[in] opts Options for get today executions request (can null) + */ +void lb_trade_context_today_executions(const struct lb_trade_context_t *ctx, + const struct lb_get_today_executions_options_t *opts, + lb_async_callback_t callback, + void *userdata); + +/** + * Get history orders + * + * @param[in] opts Options for get history orders request (can null) + */ +void lb_trade_context_history_orders(const struct lb_trade_context_t *ctx, + const struct lb_get_history_orders_options_t *opts, + lb_async_callback_t callback, + void *userdata); + +/** + * Get today orders + * + * @param[in] opts Options for get today orders request (can null) + */ +void lb_trade_context_today_orders(const struct lb_trade_context_t *ctx, + const struct lb_get_today_orders_options_t *opts, + lb_async_callback_t callback, + void *userdata); + +/** + * Replace order + * + * @param[in] opts Options for replace order request + */ +void lb_trade_context_replace_order(const struct lb_trade_context_t *ctx, + const struct lb_replace_order_options_t *opts, + lb_async_callback_t callback, + void *userdata); + +/** + * Submit order + * + * @param[in] opts Options for submit order request + */ +void lb_trade_context_submit_order(const struct lb_trade_context_t *ctx, + const struct lb_submit_order_options_t *opts, + lb_async_callback_t callback, + void *userdata); + +/** + * Cancel order + */ +void lb_trade_context_cancel_order(const struct lb_trade_context_t *ctx, + const char *order_id, + lb_async_callback_t callback, + void *userdata); + +/** + * Get account balance + */ +void lb_trade_context_account_balance(const struct lb_trade_context_t *ctx, + lb_async_callback_t callback, + void *userdata); + +/** + * Get cash flow + * + * @param[in] opts Options for get cash flow request + */ +void lb_trade_context_cash_flow(const struct lb_trade_context_t *ctx, + const struct lb_get_cash_flow_options_t *opts, + lb_async_callback_t callback, + void *userdata); + +/** + * Get fund positions + * + * @param[in] opts Options for get fund positions request + */ +void lb_trade_context_fund_positions(const struct lb_trade_context_t *ctx, + const struct lb_get_fund_positions_options_t *opts, + lb_async_callback_t callback, + void *userdata); + +/** + * Get stock positions + * + * @param[in] opts Options for get stock positions request + */ +void lb_trade_context_stock_positions(const struct lb_trade_context_t *ctx, + const struct lb_get_stock_positions_options_t *opts, + lb_async_callback_t callback, + void *userdata); + +/** + * Create a decimal value with a 64 bit `m` representation and corresponding + * `e` scale. + */ +struct lb_decimal_t *lb_decimal_new(int64_t num, uint32_t scale); + +/** + * Clone the decimal value + */ +struct lb_decimal_t *lb_decimal_clone(const struct lb_decimal_t *value); + +/** + * Create a decimal value from string + */ +struct lb_decimal_t *lb_decimal_from_str(const char *value); + +/** + * Create a decimal value from double + */ +struct lb_decimal_t *lb_decimal_from_double(double value); + +/** + * Free the decimal value + */ +void lb_decimal_free(struct lb_decimal_t *value); + +double lb_decimal_to_double(const struct lb_decimal_t *value); + +/** + * Computes the absolute value. + */ +void lb_decimal_abs(struct lb_decimal_t *value); + +/** + * Returns the smallest integer greater than or equal to a number. + */ +void lb_decimal_ceil(struct lb_decimal_t *value); + +/** + * Returns the largest integer less than or equal to a number. + */ +void lb_decimal_floor(struct lb_decimal_t *value); + +/** + * Returns a new Decimal representing the fractional portion of the number. + */ +void lb_decimal_fract(struct lb_decimal_t *value); + +/** + * Returns `true` if the decimal is negative. + */ +bool lb_decimal_is_negative(const struct lb_decimal_t *value); + +/** + * Returns `true` if the decimal is positive. + */ +bool lb_decimal_is_positive(const struct lb_decimal_t *value); + +/** + * Returns `true` if this Decimal number is equivalent to zero. + */ +bool lb_decimal_is_zero(const struct lb_decimal_t *value); + +/** + * Returns the maximum of the two numbers. + */ +const struct lb_decimal_t *lb_decimal_max(const struct lb_decimal_t *a, + const struct lb_decimal_t *b); + +/** + * Returns the minimum of the two numbers. + */ +const struct lb_decimal_t *lb_decimal_min(const struct lb_decimal_t *a, + const struct lb_decimal_t *b); + +/** + * Strips any trailing zero’s from a Decimal and converts `-0` to `0`. + */ +void lb_decimal_normalize(struct lb_decimal_t *value); + +/** + * Returns a new Decimal number with no fractional portion (i.e. an + * integer). Rounding currently follows “Bankers Rounding” rules. e.g. + * `6.5` -> `6`, `7.5` -> `8` + */ +void lb_decimal_round(struct lb_decimal_t *value); + +/** + * Returns a new Decimal integral with no fractional portion. This is a + * true truncation whereby no rounding is performed. + */ +void lb_decimal_trunc(struct lb_decimal_t *value); + +/** + * Performs the `+` operation. + */ +void lb_decimal_add(struct lb_decimal_t *a, const struct lb_decimal_t *b); + +/** + * Performs the `-` operation. + */ +void lb_decimal_sub(struct lb_decimal_t *a, const struct lb_decimal_t *b); + +/** + * Performs the `*` operation. + */ +void lb_decimal_mul(struct lb_decimal_t *a, const struct lb_decimal_t *b); + +/** + * Performs the `/` operation. + */ +void lb_decimal_div(struct lb_decimal_t *a, const struct lb_decimal_t *b); + +/** + * Performs the `%` operation. + */ +void lb_decimal_rem(struct lb_decimal_t *a, const struct lb_decimal_t *b); + +/** + * Performs the unary `-` operation. + */ +void lb_decimal_neg(struct lb_decimal_t *value); + +/** + * Returns `true` if the value of this Decimal is greater than the value of + * `x`, otherwise returns `false`. + */ +bool lb_decimal_gt(const struct lb_decimal_t *a, const struct lb_decimal_t *b); + +/** + * Returns `true` if the value of this Decimal is greater than or equal to + * the value of `x`, otherwise returns `false`. + */ +bool lb_decimal_gte(const struct lb_decimal_t *a, const struct lb_decimal_t *b); + +/** + * Returns `true` if the value of this Decimal equals the value of `x`, + * otherwise returns `false`. + */ +bool lb_decimal_eq(const struct lb_decimal_t *a, const struct lb_decimal_t *b); + +/** + * Returns `true` if the value of this Decimal is less than the value of + * `x`, otherwise returns `false`. + */ +bool lb_decimal_lt(const struct lb_decimal_t *a, const struct lb_decimal_t *b); + +/** + * Returns `true` if the value of this Decimal is less than or equal to the + * value of `x`, otherwise returns `false`. + */ +bool lb_decimal_lte(const struct lb_decimal_t *a, const struct lb_decimal_t *b); + +/** + * Compares the values of two Decimals. + * + * Returns `-1` if the value of this Decimal is less than the value of + * `x`. + * + * Returns `1` if the value of this Decimal is greater than the value of + * `x`. + * + * Returns `0` if the value of this Decimal equals the value of `x`. + */ +int32_t lb_decimal_cmp(const struct lb_decimal_t *a, const struct lb_decimal_t *b); + +/** + * Computes the sine of a number (in radians) + */ +void lb_decimal_sin(struct lb_decimal_t *value); + +/** + * Computes the cosine of a number (in radians) + */ +void lb_decimal_cos(struct lb_decimal_t *value); + +/** + * Computes the tangent of a number (in radians). Panics upon overflow or + * upon approaching a limit. + */ +void lb_decimal_tan(struct lb_decimal_t *value); + +/** + * The square root of a Decimal. Uses a standard Babylonian method. + */ +void lb_decimal_sqrt(struct lb_decimal_t *value); + +/** + * Raise self to the given Decimal exponent: xy. If `exp` is not + * whole then the approximation ey*ln(x) is used. + */ +void lb_decimal_pow(struct lb_decimal_t *value, const struct lb_decimal_t *exp); + +/** + * Calculates the natural logarithm for a Decimal calculated using Taylor’s + * series. + */ +void lb_decimal_ln(struct lb_decimal_t *value); + +/** + * Calculates the base 10 logarithm of a specified Decimal number. + */ +void lb_decimal_log10(struct lb_decimal_t *value); + +/** + * The estimated exponential function, ex. Stops calculating when it is + * within tolerance of roughly `0.0000002`. + */ +void lb_decimal_exp(struct lb_decimal_t *value); + +/** + * The estimated exponential function, ex using the `tolerance` + * provided as a hint as to when to stop calculating. A larger + * tolerance will cause the number to stop calculating sooner at the + * potential cost of a slightly less accurate result. + */ +void lb_decimal_exp_with_tolerance(struct lb_decimal_t *value, + const struct lb_decimal_t *tolerance); + +/** + * Abramowitz Approximation of Error Function from [wikipedia](https://en.wikipedia.org/wiki/Error_function#Numerical_approximations) + */ +void lb_decimal_erf(struct lb_decimal_t *value); + +/** + * The Cumulative distribution function for a Normal distribution + */ +void lb_decimal_normal_cdf(struct lb_decimal_t *value); + +/** + * The Probability density function for a Normal distribution. + */ +void lb_decimal_norm_pdf(struct lb_decimal_t *value); + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus + +#endif /* _LONGBRIDGE_H_ */ diff --git a/c/csrc/longbridge.hpp b/c/csrc/longbridge.hpp new file mode 100644 index 0000000000..166345606d --- /dev/null +++ b/c/csrc/longbridge.hpp @@ -0,0 +1,6 @@ +#pragma once + +#include "async_result.hpp" +#include "config.hpp" +#include "decimal.hpp" +#include "quote_context.hpp" diff --git a/c/csrc/quote_context.hpp b/c/csrc/quote_context.hpp new file mode 100644 index 0000000000..f2542c1049 --- /dev/null +++ b/c/csrc/quote_context.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include "async_result.hpp" +#include "config.hpp" +#include "longbridge.h" + +namespace longbridge { + +class QuoteContext { + private: + const lb_quote_context_t *ctx_; + + public: + QuoteContext(const lb_quote_context_t *ctx) { + ctx_ = ctx; + lb_quote_context_retain(ctx_); + } + + QuoteContext(const QuoteContext &ctx) { + ctx_ = ctx.ctx_; + lb_quote_context_retain(ctx_); + } + + ~QuoteContext() { lb_quote_context_release(ctx_); } + + static void create(Config &config, + AsyncCallback callback) { + lb_quote_context_new( + config, + [](auto res) { + auto callback_ptr = + (AsyncCallback *)res->userdata; + (*callback_ptr)(AsyncResult( + QuoteContext((const lb_quote_context_t *)res->ctx), + Status(res->error), nullptr)); + delete callback_ptr; + }, + new AsyncCallback(callback)); + } +}; + +} // namespace longbridge \ No newline at end of file diff --git a/c/csrc/status.hpp b/c/csrc/status.hpp new file mode 100644 index 0000000000..6de8df338c --- /dev/null +++ b/c/csrc/status.hpp @@ -0,0 +1,45 @@ +#pragma once + +#include "longbridge.h" + +namespace longbridge { +class Status { + private: + const lb_error_t *err_; + bool need_free_; + + public: + Status() { err_ = nullptr; } + Status(const lb_error_t *err) { + err_ = err; + need_free_ = false; + } + Status(lb_error_t *err) { + err_ = err; + need_free_ = true; + } + Status(Status &&status) { + err_ = status.err_; + status.err_ = nullptr; + status.need_free_ = false; + } + ~Status() { + if (err_ && need_free_) { + lb_error_free((lb_error_t *)err_); + } + } + + /// Returns `true` if no errors occurs + bool is_ok() const { return err_ == nullptr; } + + /// Returns `true` if an errors occurs + bool is_err() const { return err_ != nullptr; } + + /// Returns the error code + int code() const { return lb_error_code(err_); } + + /// Returns the error message + const char *message() const { return lb_error_message(err_); } +}; + +} // namespace longbridge \ No newline at end of file diff --git a/c/src/async_call.rs b/c/src/async_call.rs new file mode 100644 index 0000000000..b71b693cef --- /dev/null +++ b/c/src/async_call.rs @@ -0,0 +1,147 @@ +use std::{ffi::c_void, future::Future, sync::Arc}; + +use longbridge::Result; +use once_cell::sync::Lazy; +use tokio::runtime::Runtime; + +use crate::error::CError; + +static RUNTIME: Lazy = Lazy::new(|| { + tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + .expect("create tokio runtime") +}); + +pub type CAsyncCallback = extern "C" fn(*const CAsyncResult); + +#[derive(Debug, Copy, Clone)] +#[repr(C)] +pub struct CAsyncResult { + pub ctx: *const c_void, + pub error: *const CError, + pub data: *mut c_void, + pub length: usize, + pub userdata: *mut c_void, +} + +pub(crate) trait ToAsyncResult { + fn to_async_result(&self, ctx: *const c_void) -> CAsyncResult; +} + +impl ToAsyncResult for CAsyncResult { + #[inline] + fn to_async_result(&self, _ctx: *const c_void) -> CAsyncResult { + *self + } +} + +impl ToAsyncResult for *const T { + #[inline] + fn to_async_result(&self, ctx: *const c_void) -> CAsyncResult { + CAsyncResult { + ctx, + error: std::ptr::null_mut(), + data: *self as *mut c_void, + length: 1, + userdata: std::ptr::null_mut(), + } + } +} + +impl ToAsyncResult for *mut T { + #[inline] + fn to_async_result(&self, ctx: *const c_void) -> CAsyncResult { + CAsyncResult { + ctx, + error: std::ptr::null(), + data: *self as *mut c_void, + length: 1, + userdata: std::ptr::null_mut(), + } + } +} + +impl ToAsyncResult for (*const T, usize) { + #[inline] + fn to_async_result(&self, ctx: *const c_void) -> CAsyncResult { + CAsyncResult { + ctx, + error: std::ptr::null_mut(), + data: self.0 as *mut c_void, + length: self.1, + userdata: std::ptr::null_mut(), + } + } +} + +impl ToAsyncResult for (*mut T, usize) { + #[inline] + fn to_async_result(&self, ctx: *const c_void) -> CAsyncResult { + CAsyncResult { + ctx, + error: std::ptr::null_mut(), + data: self.0 as *mut c_void, + length: self.1, + userdata: std::ptr::null_mut(), + } + } +} + +impl ToAsyncResult for () { + #[inline] + fn to_async_result(&self, ctx: *const c_void) -> CAsyncResult { + CAsyncResult { + ctx, + error: std::ptr::null_mut(), + data: std::ptr::null_mut(), + length: 0, + userdata: std::ptr::null_mut(), + } + } +} + +pub(crate) fn execute_async( + callback: CAsyncCallback, + ctx: *const P, + userdata: *mut c_void, + fut: F, +) where + F: Future> + Send + 'static, + T: ToAsyncResult, + P: Send, +{ + unsafe { + let _guard = RUNTIME.enter(); + let ctx_pointer = ctx as usize; + let userdata_pointer = userdata as usize; + + if !ctx.is_null() { + Arc::increment_strong_count(ctx); + } + tokio::spawn(async move { + match fut.await { + Ok(res) => { + let mut res = res.to_async_result(ctx_pointer as *const c_void); + res.userdata = userdata_pointer as *mut c_void; + callback(&res) + } + Err(err) => { + let err = err.into(); + let res = CAsyncResult { + ctx: ctx_pointer as *mut c_void, + error: &err, + data: std::ptr::null_mut(), + length: 0, + userdata: userdata_pointer as *mut c_void, + }; + callback(&res); + } + } + + if ctx_pointer != 0 { + Arc::from_raw(ctx_pointer as *const P); + } + }); + } +} diff --git a/c/src/config.rs b/c/src/config.rs new file mode 100644 index 0000000000..f3646a7146 --- /dev/null +++ b/c/src/config.rs @@ -0,0 +1,79 @@ +use std::{ffi::CStr, os::raw::c_char, sync::Arc}; + +use longbridge::Config; + +use crate::error::{set_error, CError}; + +/// Configuration options for Longbridge sdk +pub struct CConfig(pub(crate) Arc); + +/// Create a new `Config` from the given environment variables +/// +/// It first gets the environment variables from the `.env` file in the +/// current directory. +/// +/// # Variables +/// +/// - `LONGBRIDGE_APP_KEY` - App key +/// - `LONGBRIDGE_APP_SECRET` - App secret +/// - `LONGBRIDGE_ACCESS_TOKEN` - Access token +/// - `LONGBRIDGE_HTTP_URL` - HTTP endpoint url (Default: `https://openapi.longbridgeapp.com`) +/// - `LONGBRIDGE_QUOTE_WS_URL` - Quote websocket endpoint url (Default: +/// `wss://openapi-quote.longbridgeapp.com`) +/// - `LONGBRIDGE_TRADE_WS_URL` - Trade websocket endpoint url (Default: +/// `wss://openapi-trade.longbridgeapp.com`) +#[no_mangle] +pub unsafe extern "C" fn lb_config_from_env(error: *mut *mut CError) -> *mut CConfig { + match Config::from_env() { + Ok(config) => Box::into_raw(Box::new(CConfig(Arc::new(config)))), + Err(err) => { + set_error(error, err); + std::ptr::null_mut() + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn lb_config_new( + app_key: *const c_char, + app_secret: *const c_char, + access_token: *const c_char, + http_url: *const c_char, + quote_ws_url: *const c_char, + trade_ws_url: *const c_char, +) -> *mut CConfig { + let app_key = CStr::from_ptr(app_key).to_str().expect("invalid app key"); + let app_secret = CStr::from_ptr(app_secret) + .to_str() + .expect("invalid app secret"); + let access_token = CStr::from_ptr(access_token) + .to_str() + .expect("invalid access token"); + let mut config = Config::new(app_key, app_secret, access_token); + + if !http_url.is_null() { + config = config.http_url(CStr::from_ptr(http_url).to_str().expect("invalid http url")); + } + if !quote_ws_url.is_null() { + config = config.quote_ws_url( + CStr::from_ptr(quote_ws_url) + .to_str() + .expect("invalid quote websocket url"), + ); + } + if !trade_ws_url.is_null() { + config = config.trade_ws_url( + CStr::from_ptr(trade_ws_url) + .to_str() + .expect("invalid trade websocket url"), + ); + } + + Box::into_raw(Box::new(CConfig(Arc::new(config)))) +} + +/// Free the config object +#[no_mangle] +pub unsafe extern "C" fn lb_config_free(config: *mut CConfig) { + let _ = Box::from_raw(config); +} diff --git a/c/src/error.rs b/c/src/error.rs new file mode 100644 index 0000000000..27da4d1e5d --- /dev/null +++ b/c/src/error.rs @@ -0,0 +1,42 @@ +use std::os::raw::c_char; + +use longbridge::Error; + +use crate::types::{CString, ToFFI}; + +pub struct CError { + code: i64, + message: CString, +} + +impl From for CError { + fn from(err: Error) -> Self { + let err = err.into_simple_error(); + Self { + code: err.code().unwrap_or_default(), + message: err.message().to_string().into(), + } + } +} + +/// Free the error object +#[no_mangle] +pub unsafe extern "C" fn lb_error_free(error: *mut CError) { + let _ = Box::from_raw(error); +} + +pub(crate) unsafe fn set_error(error: *mut *mut CError, err: Error) { + if !error.is_null() { + *error = Box::into_raw(Box::new(err.into())); + } +} + +#[no_mangle] +pub unsafe extern "C" fn lb_error_message(error: *const CError) -> *const c_char { + (*error).message.to_ffi_type() +} + +#[no_mangle] +pub unsafe extern "C" fn lb_error_code(error: *const CError) -> i64 { + (*error).code +} diff --git a/c/src/lib.rs b/c/src/lib.rs new file mode 100644 index 0000000000..a2cd4ceb56 --- /dev/null +++ b/c/src/lib.rs @@ -0,0 +1,6 @@ +mod async_call; +mod config; +mod error; +mod quote_context; +mod trade_context; +mod types; diff --git a/c/src/quote_context/constants.rs b/c/src/quote_context/constants.rs new file mode 100644 index 0000000000..9f90ca0c23 --- /dev/null +++ b/c/src/quote_context/constants.rs @@ -0,0 +1,17 @@ +/// Quote +pub const LB_SUBFLAGS_QUOTE: u8 = 0x1; + +/// Depth +pub const LB_SUBFLAGS_DEPTH: u8 = 0x2; + +/// Broker +pub const LB_SUBFLAGS_BROKER: u8 = 0x4; + +/// Trade +pub const LB_SUBFLAGS_TRADE: u8 = 0x8; + +/// US stock options +pub const LB_DERIVATIVE_TYPE_OPTION: u8 = 0x1; + +/// HK warrants +pub const LB_DERIVATIVE_TYPE_WARRANT: u8 = 0x2; diff --git a/c/src/quote_context/context.rs b/c/src/quote_context/context.rs new file mode 100644 index 0000000000..536b140a53 --- /dev/null +++ b/c/src/quote_context/context.rs @@ -0,0 +1,731 @@ +use std::{ffi::c_void, os::raw::c_char, sync::Arc}; + +use longbridge::{ + quote::{PushEvent, PushEventDetail, SubFlags}, + QuoteContext, +}; +use parking_lot::Mutex; + +use crate::{ + async_call::{execute_async, CAsyncCallback, CAsyncResult}, + config::CConfig, + quote_context::{ + enum_types::{CAdjustType, CPeriod}, + types::{ + CCandlestickOwned, CCapitalDistributionResponseOwned, CCapitalFlowLineOwned, + CIntradayLineOwned, CIssuerInfoOwned, CMarketTradingDaysOwned, + CMarketTradingSessionOwned, COptionQuoteOwned, CParticipantInfoOwned, CPushBrokers, + CPushBrokersOwned, CPushCandlestick, CPushCandlestickOwned, CPushDepth, + CPushDepthOwned, CPushQuote, CPushQuoteOwned, CPushTrades, CPushTradesOwned, + CRealtimeQuoteOwned, CSecurityBrokersOwned, CSecurityDepthOwned, CSecurityQuoteOwned, + CSecurityStaticInfoOwned, CStrikePriceInfoOwned, CSubscriptionOwned, CTradeOwned, + CWarrantQuoteOwned, + }, + }, + types::{cstr_array_to_rust, cstr_to_rust, CCow, CDate, CMarket, CVec, ToFFI}, +}; + +pub type COnQuoteCallback = extern "C" fn(*const CQuoteContext, *const CPushQuote); + +pub type COnDepthCallback = extern "C" fn(*const CQuoteContext, *const CPushDepth); + +pub type COnBrokersCallback = extern "C" fn(*const CQuoteContext, *const CPushBrokers); + +pub type COnTradesCallback = extern "C" fn(*const CQuoteContext, *const CPushTrades); + +pub type COnCandlestickCallback = extern "C" fn(*const CQuoteContext, *const CPushCandlestick); + +#[derive(Default)] +struct Callbacks { + quote: Option, + depth: Option, + brokers: Option, + trades: Option, + candlestick: Option, +} + +pub struct CQuoteContextState { + userdata: *mut c_void, + callbacks: Callbacks, +} + +unsafe impl Send for CQuoteContextState {} + +/// Quote context +pub struct CQuoteContext { + ctx: QuoteContext, + state: Mutex, +} + +#[no_mangle] +pub unsafe extern "C" fn lb_quote_context_new( + config: *const CConfig, + callback: CAsyncCallback, + userdata: *mut c_void, +) { + let config = (*config).0.clone(); + let userdata_pointer = userdata as usize; + + execute_async( + callback, + std::ptr::null_mut::(), + userdata, + async move { + let (ctx, mut receiver) = QuoteContext::try_new(config).await?; + let state = Mutex::new(CQuoteContextState { + userdata: std::ptr::null_mut(), + callbacks: Callbacks::default(), + }); + let arc_ctx = Arc::new(CQuoteContext { ctx, state }); + let weak_ctx = Arc::downgrade(&arc_ctx); + let ctx = Arc::into_raw(arc_ctx); + + tokio::spawn(async move { + while let Some(event) = receiver.recv().await { + let ctx = match weak_ctx.upgrade() { + Some(ctx) => ctx, + None => return, + }; + + let state = ctx.state.lock(); + match event { + PushEvent { + symbol, + detail: PushEventDetail::Quote(quote), + .. + } => { + if let Some(callback) = state.callbacks.quote { + let quote_owned: CPushQuoteOwned = (symbol, quote).into(); + callback(Arc::as_ptr(&ctx), "e_owned.to_ffi_type()); + } + } + PushEvent { + symbol, + detail: PushEventDetail::Depth(depth), + .. + } => { + if let Some(callback) = state.callbacks.depth { + let depth_owned: CPushDepthOwned = (symbol, depth).into(); + callback(Arc::as_ptr(&ctx), &depth_owned.to_ffi_type()); + } + } + PushEvent { + symbol, + detail: PushEventDetail::Brokers(brokers), + .. + } => { + if let Some(callback) = state.callbacks.brokers { + let brokers_owned: CPushBrokersOwned = (symbol, brokers).into(); + callback(Arc::as_ptr(&ctx), &brokers_owned.to_ffi_type()); + } + } + PushEvent { + symbol, + detail: PushEventDetail::Trade(trades), + .. + } => { + if let Some(callback) = state.callbacks.trades { + let trades_owned: CPushTradesOwned = (symbol, trades).into(); + callback(Arc::as_ptr(&ctx), &trades_owned.to_ffi_type()); + } + } + PushEvent { + symbol, + detail: PushEventDetail::Candlestick(candlestick), + .. + } => { + if let Some(callback) = state.callbacks.candlestick { + let candlestick_owned: CPushCandlestickOwned = + (symbol, candlestick).into(); + callback(Arc::as_ptr(&ctx), &candlestick_owned.to_ffi_type()); + } + } + } + } + }); + + Ok(CAsyncResult { + ctx: ctx as *const c_void, + error: std::ptr::null(), + data: std::ptr::null_mut(), + length: 0, + userdata: userdata_pointer as *mut c_void, + }) + }, + ); +} + +#[no_mangle] +pub unsafe extern "C" fn lb_quote_context_retain(ctx: *const CQuoteContext) { + Arc::increment_strong_count(ctx); +} + +#[no_mangle] +pub unsafe extern "C" fn lb_quote_context_release(ctx: *const CQuoteContext) { + let _ = Arc::from_raw(ctx); +} + +#[no_mangle] +pub unsafe extern "C" fn lb_quote_context_set_userdata( + ctx: *const CQuoteContext, + userdata: *mut c_void, +) { + (*ctx).state.lock().userdata = userdata; +} + +#[no_mangle] +pub unsafe extern "C" fn lb_quote_context_userdata(ctx: *const CQuoteContext) -> *mut c_void { + (*ctx).state.lock().userdata +} + +/// Set quote callback, after receiving the quote data push, it will call back +/// to this function. +#[no_mangle] +pub unsafe extern "C" fn lb_quote_context_set_on_quote( + ctx: *const CQuoteContext, + callback: COnQuoteCallback, +) { + (*ctx).state.lock().callbacks.quote = Some(callback); +} + +/// Set depth callback, after receiving the depth data push, it will call +/// back to this function. +#[no_mangle] +pub unsafe extern "C" fn lb_quote_context_set_on_depth( + ctx: *const CQuoteContext, + callback: COnDepthCallback, +) { + (*ctx).state.lock().callbacks.depth = Some(callback); +} + +/// Set brokers callback, after receiving the brokers data push, it will +/// call back to this function. +#[no_mangle] +pub unsafe extern "C" fn lb_quote_context_set_on_brokers( + ctx: *const CQuoteContext, + callback: COnBrokersCallback, +) { + (*ctx).state.lock().callbacks.brokers = Some(callback); +} + +/// Set trades callback, after receiving the trades data push, it will call +/// back to this function. +#[no_mangle] +pub unsafe extern "C" fn lb_quote_context_set_on_trades( + ctx: *const CQuoteContext, + callback: COnTradesCallback, +) { + (*ctx).state.lock().callbacks.trades = Some(callback); +} + +/// Set candlestick callback, after receiving the trades data push, it will +/// call back to this function. +#[no_mangle] +pub unsafe extern "C" fn lb_quote_context_set_on_candlestick( + ctx: *const CQuoteContext, + callback: COnCandlestickCallback, +) { + (*ctx).state.lock().callbacks.candlestick = Some(callback); +} + +#[no_mangle] +pub unsafe extern "C" fn lb_quote_context_subscribe( + ctx: *const CQuoteContext, + symbols: *const *const c_char, + num_symbols: usize, + sub_types: u8, + is_first_push: bool, + callback: CAsyncCallback, + userdata: *mut c_void, +) { + let ctx_inner = (*ctx).ctx.clone(); + let symbols = cstr_array_to_rust(symbols, num_symbols); + execute_async(callback, ctx, userdata, async move { + ctx_inner + .subscribe( + symbols, + SubFlags::from_bits(sub_types).unwrap_or_else(SubFlags::empty), + is_first_push, + ) + .await + }); +} + +/// Unsubscribe +#[no_mangle] +pub unsafe extern "C" fn lb_quote_context_unsubscribe( + ctx: *const CQuoteContext, + symbols: *const *const c_char, + num_symbols: usize, + sub_types: u8, + callback: CAsyncCallback, + userdata: *mut c_void, +) { + let ctx_inner = (*ctx).ctx.clone(); + let symbols = cstr_array_to_rust(symbols, num_symbols); + execute_async(callback, ctx, userdata, async move { + ctx_inner + .unsubscribe( + symbols, + SubFlags::from_bits(sub_types).unwrap_or_else(SubFlags::empty), + ) + .await + }); +} + +/// Subscribe security candlesticks +#[no_mangle] +pub unsafe extern "C" fn lb_quote_context_subscribe_candlesticks( + ctx: *const CQuoteContext, + symbol: *const c_char, + period: CPeriod, + callback: CAsyncCallback, + userdata: *mut c_void, +) { + let ctx_inner = (*ctx).ctx.clone(); + let symbol = cstr_to_rust(symbol); + execute_async(callback, ctx, userdata, async move { + ctx_inner + .subscribe_candlesticks(symbol, period.into()) + .await + }); +} + +/// Unsubscribe security candlesticks +#[no_mangle] +pub unsafe extern "C" fn lb_quote_context_unsubscribe_candlesticks( + ctx: *const CQuoteContext, + symbol: *const c_char, + period: CPeriod, + callback: CAsyncCallback, + userdata: *mut c_void, +) { + let ctx_inner = (*ctx).ctx.clone(); + let symbol = cstr_to_rust(symbol); + execute_async(callback, ctx, userdata, async move { + ctx_inner + .unsubscribe_candlesticks(symbol, period.into()) + .await + }); +} + +/// Get subscription information +#[no_mangle] +pub unsafe extern "C" fn lb_quote_context_subscrptions( + ctx: *const CQuoteContext, + callback: CAsyncCallback, + userdata: *mut c_void, +) { + let ctx_inner = (*ctx).ctx.clone(); + execute_async(callback, ctx, userdata, async move { + let rows: CVec = ctx_inner.subscriptions().await?.into(); + Ok(rows) + }); +} + +/// Get basic information of securities +#[no_mangle] +pub unsafe extern "C" fn lb_quote_context_static_info( + ctx: *const CQuoteContext, + symbols: *const *const c_char, + num_symbols: usize, + callback: CAsyncCallback, + userdata: *mut c_void, +) { + let ctx_inner = (*ctx).ctx.clone(); + let symbols = cstr_array_to_rust(symbols, num_symbols); + execute_async(callback, ctx, userdata, async move { + let rows: CVec = ctx_inner.static_info(symbols).await?.into(); + Ok(rows) + }); +} + +/// Get quote of securities +#[no_mangle] +pub unsafe extern "C" fn lb_quote_context_quote( + ctx: *const CQuoteContext, + symbols: *const *const c_char, + num_symbols: usize, + callback: CAsyncCallback, + userdata: *mut c_void, +) { + let ctx_inner = (*ctx).ctx.clone(); + let symbols = cstr_array_to_rust(symbols, num_symbols); + execute_async(callback, ctx, userdata, async move { + let rows: CVec = ctx_inner.quote(symbols).await?.into(); + Ok(rows) + }); +} + +/// Get quote of option securities +#[no_mangle] +pub unsafe extern "C" fn lb_quote_context_option_quote( + ctx: *const CQuoteContext, + symbols: *const *const c_char, + num_symbols: usize, + callback: CAsyncCallback, + userdata: *mut c_void, +) { + let ctx_inner = (*ctx).ctx.clone(); + let symbols = cstr_array_to_rust(symbols, num_symbols); + execute_async(callback, ctx, userdata, async move { + let rows: CVec = ctx_inner.option_quote(symbols).await?.into(); + Ok(rows) + }); +} + +/// Get quote of warrant securities +#[no_mangle] +pub unsafe extern "C" fn lb_quote_context_warrant_quote( + ctx: *const CQuoteContext, + symbols: *const *const c_char, + num_symbols: usize, + callback: CAsyncCallback, + userdata: *mut c_void, +) { + let ctx_inner = (*ctx).ctx.clone(); + let symbols = cstr_array_to_rust(symbols, num_symbols); + execute_async(callback, ctx, userdata, async move { + let rows: CVec = ctx_inner.warrant_quote(symbols).await?.into(); + Ok(rows) + }); +} + +/// Get security depth +#[no_mangle] +pub unsafe extern "C" fn lb_quote_context_depth( + ctx: *const CQuoteContext, + symbol: *const c_char, + callback: CAsyncCallback, + userdata: *mut c_void, +) { + let ctx_inner = (*ctx).ctx.clone(); + let symbol = cstr_to_rust(symbol); + execute_async(callback, ctx, userdata, async move { + let resp: CCow = CCow::new( + ctx_inner + .depth(symbol) + .await + .map(CSecurityDepthOwned::from)?, + ); + Ok(resp) + }); +} + +/// Get security brokers +#[no_mangle] +pub unsafe extern "C" fn lb_quote_context_brokers( + ctx: *const CQuoteContext, + symbol: *const c_char, + callback: CAsyncCallback, + userdata: *mut c_void, +) { + let ctx_inner = (*ctx).ctx.clone(); + let symbol = cstr_to_rust(symbol); + execute_async(callback, ctx, userdata, async move { + let resp: CCow = CCow::new( + ctx_inner + .brokers(symbol) + .await + .map(CSecurityBrokersOwned::from)?, + ); + Ok(resp) + }); +} + +/// Get participants +#[no_mangle] +pub unsafe extern "C" fn lb_quote_context_participants( + ctx: *const CQuoteContext, + callback: CAsyncCallback, + userdata: *mut c_void, +) { + let ctx_inner = (*ctx).ctx.clone(); + execute_async(callback, ctx, userdata, async move { + let rows: CVec = ctx_inner.participants().await?.into(); + Ok(rows) + }); +} + +/// Get security trades +#[no_mangle] +pub unsafe extern "C" fn lb_quote_context_trades( + ctx: *const CQuoteContext, + symbol: *const c_char, + count: usize, + callback: CAsyncCallback, + userdata: *mut c_void, +) { + let ctx_inner = (*ctx).ctx.clone(); + let symbol = cstr_to_rust(symbol); + execute_async(callback, ctx, userdata, async move { + let rows: CVec = ctx_inner.trades(symbol, count).await?.into(); + Ok(rows) + }); +} + +/// Get security intraday lines +#[no_mangle] +pub unsafe extern "C" fn lb_quote_context_intraday( + ctx: *const CQuoteContext, + symbol: *const c_char, + callback: CAsyncCallback, + userdata: *mut c_void, +) { + let ctx_inner = (*ctx).ctx.clone(); + let symbol = cstr_to_rust(symbol); + execute_async(callback, ctx, userdata, async move { + let rows: CVec = ctx_inner.intraday(symbol).await?.into(); + Ok(rows) + }); +} + +/// Get security candlesticks +#[no_mangle] +pub unsafe extern "C" fn lb_quote_context_candlesticks( + ctx: *const CQuoteContext, + symbol: *const c_char, + period: CPeriod, + count: usize, + adjust_type: CAdjustType, + callback: CAsyncCallback, + userdata: *mut c_void, +) { + let ctx_inner = (*ctx).ctx.clone(); + let symbol = cstr_to_rust(symbol); + execute_async(callback, ctx, userdata, async move { + let rows: CVec = ctx_inner + .candlesticks(symbol, period.into(), count, adjust_type.into()) + .await? + .into(); + Ok(rows) + }); +} + +/// Get option chain expiry date list +#[no_mangle] +pub unsafe extern "C" fn lb_quote_context_option_chain_expiry_date_list( + ctx: *const CQuoteContext, + symbol: *const c_char, + callback: CAsyncCallback, + userdata: *mut c_void, +) { + let ctx_inner = (*ctx).ctx.clone(); + let symbol = cstr_to_rust(symbol); + execute_async(callback, ctx, userdata, async move { + let rows: CVec = ctx_inner + .option_chain_expiry_date_list(symbol) + .await? + .into(); + Ok(rows) + }); +} + +/// Get option chain info by date +#[no_mangle] +pub unsafe extern "C" fn lb_quote_context_option_chain_info_by_date( + ctx: *const CQuoteContext, + symbol: *const c_char, + expiry_date: *const CDate, + callback: CAsyncCallback, + userdata: *mut c_void, +) { + let ctx_inner = (*ctx).ctx.clone(); + let symbol = cstr_to_rust(symbol); + let expiry_date = (*expiry_date).into(); + execute_async(callback, ctx, userdata, async move { + let rows: CVec = ctx_inner + .option_chain_info_by_date(symbol, expiry_date) + .await? + .into(); + Ok(rows) + }); +} + +/// Get warrant issuers +#[no_mangle] +pub unsafe extern "C" fn lb_quote_context_warrant_issuers( + ctx: *const CQuoteContext, + callback: CAsyncCallback, + userdata: *mut c_void, +) { + let ctx_inner = (*ctx).ctx.clone(); + execute_async(callback, ctx, userdata, async move { + let rows: CVec = ctx_inner.warrant_issuers().await?.into(); + Ok(rows) + }); +} + +/// Get trading session of the day +#[no_mangle] +pub unsafe extern "C" fn lb_quote_context_trading_session( + ctx: *const CQuoteContext, + callback: CAsyncCallback, + userdata: *mut c_void, +) { + let ctx_inner = (*ctx).ctx.clone(); + execute_async(callback, ctx, userdata, async move { + let rows: CVec = ctx_inner.trading_session().await?.into(); + Ok(rows) + }); +} + +/// Get market trading days +#[no_mangle] +pub unsafe extern "C" fn lb_quote_context_trading_days( + ctx: *const CQuoteContext, + market: CMarket, + begin: *const CDate, + end: *const CDate, + callback: CAsyncCallback, + userdata: *mut c_void, +) { + let ctx_inner = (*ctx).ctx.clone(); + let market = market.into(); + let begin = *begin; + let end = *end; + execute_async(callback, ctx, userdata, async move { + let resp: CCow = CCow::new( + ctx_inner + .trading_days(market, begin.into(), end.into()) + .await?, + ); + Ok(resp) + }); +} + +/// Get capital flow intraday +#[no_mangle] +pub unsafe extern "C" fn lb_quote_context_capital_flow( + ctx: *const CQuoteContext, + symbol: *const c_char, + callback: CAsyncCallback, + userdata: *mut c_void, +) { + let ctx_inner = (*ctx).ctx.clone(); + let symbol = cstr_to_rust(symbol); + execute_async(callback, ctx, userdata, async move { + let rows: CVec = ctx_inner.capital_flow(symbol).await?.into(); + Ok(rows) + }); +} + +/// Get capital distribution +#[no_mangle] +pub unsafe extern "C" fn lb_quote_context_capital_distribution( + ctx: *const CQuoteContext, + symbol: *const c_char, + callback: CAsyncCallback, + userdata: *mut c_void, +) { + let ctx_inner = (*ctx).ctx.clone(); + let symbol = cstr_to_rust(symbol); + execute_async(callback, ctx, userdata, async move { + let resp: CCow = + CCow::new(ctx_inner.capital_distribution(symbol).await?); + Ok(resp) + }); +} + +/// Get quote of securities +/// +/// Get real-time quotes of the subscribed symbols, it always returns the data +/// in the local storage. +#[no_mangle] +pub unsafe extern "C" fn lb_quote_context_realtime_quote( + ctx: *const CQuoteContext, + symbols: *const *const c_char, + num_symbols: usize, + callback: CAsyncCallback, + userdata: *mut c_void, +) { + let ctx_inner = (*ctx).ctx.clone(); + let symbols = cstr_array_to_rust(symbols, num_symbols); + execute_async(callback, ctx, userdata, async move { + let rows: CVec = ctx_inner.realtime_quote(symbols).await?.into(); + Ok(rows) + }); +} + +/// Get real-time depth +/// +/// Get real-time depth of the subscribed symbols, it always returns the data in +/// the local storage. +#[no_mangle] +pub unsafe extern "C" fn lb_quote_context_realtime_depth( + ctx: *const CQuoteContext, + symbol: *const c_char, + callback: CAsyncCallback, + userdata: *mut c_void, +) { + let ctx_inner = (*ctx).ctx.clone(); + let symbol = cstr_to_rust(symbol); + execute_async(callback, ctx, userdata, async move { + let resp: CCow = CCow::new(ctx_inner.realtime_depth(symbol).await?); + Ok(resp) + }); +} + +/// Get real-time trades +/// +/// Get real-time trades of the subscribed symbols, it always returns the data +/// in the local storage. +#[no_mangle] +pub unsafe extern "C" fn lb_quote_context_realtime_trades( + ctx: *const CQuoteContext, + symbol: *const c_char, + count: usize, + callback: CAsyncCallback, + userdata: *mut c_void, +) { + let ctx_inner = (*ctx).ctx.clone(); + let symbol = cstr_to_rust(symbol); + execute_async(callback, ctx, userdata, async move { + let rows: CVec = ctx_inner.realtime_trades(symbol, count).await?.into(); + Ok(rows) + }); +} + +/// Get real-time broker queue +/// +/// Get real-time broker queue of the subscribed symbols, it always returns the +/// data in the local storage. +#[no_mangle] +pub unsafe extern "C" fn lb_quote_context_realtime_brokers( + ctx: *const CQuoteContext, + symbol: *const c_char, + callback: CAsyncCallback, + userdata: *mut c_void, +) { + let ctx_inner = (*ctx).ctx.clone(); + let symbol = cstr_to_rust(symbol); + execute_async(callback, ctx, userdata, async move { + let resp: CCow = + CCow::new(ctx_inner.realtime_brokers(symbol).await?); + Ok(resp) + }); +} + +/// Get real-time candlesticks +/// +/// Get real-time candlesticks of the subscribed symbols, it always returns the +/// data in the local storage. +#[no_mangle] +pub unsafe extern "C" fn lb_quote_context_realtime_candlesticks( + ctx: *const CQuoteContext, + symbol: *const c_char, + period: CPeriod, + count: usize, + callback: CAsyncCallback, + userdata: *mut c_void, +) { + let ctx_inner = (*ctx).ctx.clone(); + let symbol = cstr_to_rust(symbol); + execute_async(callback, ctx, userdata, async move { + let rows: CVec = ctx_inner + .realtime_candlesticks(symbol, period.into(), count) + .await? + .into(); + Ok(rows) + }); +} diff --git a/c/src/quote_context/enum_types.rs b/c/src/quote_context/enum_types.rs new file mode 100644 index 0000000000..c86a706b6e --- /dev/null +++ b/c/src/quote_context/enum_types.rs @@ -0,0 +1,188 @@ +use longbridge_c_macros::CEnum; + +/// Trade status +#[derive(Debug, Copy, Clone, Eq, PartialEq, CEnum)] +#[c(remote = "longbridge::quote::TradeStatus")] +#[allow(clippy::enum_variant_names)] +#[repr(C)] +pub enum CTradeStatus { + /// Normal + #[c(remote = "Normal")] + TradeStatusNormal, + /// Suspension + #[c(remote = "Halted")] + TradeStatusHalted, + /// Delisted + #[c(remote = "Delisted")] + TradeStatusDelisted, + /// Fuse + #[c(remote = "Fuse")] + TradeStatusFuse, + /// Papare List + #[c(remote = "PrepareList")] + TradeStatusPrepareList, + /// Code Moved + #[c(remote = "CodeMoved")] + TradeStatusCodeMoved, + /// To Be Opened + #[c(remote = "ToBeOpened")] + TradeStatusToBeOpened, + /// Split Stock Halts + #[c(remote = "SplitStockHalts")] + TradeStatusSplitStockHalts, + /// Expired + #[c(remote = "Expired")] + TradeStatusExpired, + /// Warrant To BeListed + #[c(remote = "WarrantPrepareList")] + TradeStatusWarrantPrepareList, + /// Suspend + #[c(remote = "SuspendTrade")] + TradeStatusSuspendTrade, +} + +/// Trade session +#[derive(Debug, Copy, Clone, Eq, PartialEq, CEnum)] +#[c(remote = "longbridge::quote::TradeSession")] +#[allow(clippy::enum_variant_names)] +#[repr(C)] +pub enum CTradeSession { + /// Trading + #[c(remote = "NormalTrade")] + TradeSessionNormal, + /// Pre-Trading + #[c(remote = "PreTrade")] + TradeSessionPre, + /// Post-Trading + #[c(remote = "PostTrade")] + TradeSessionPost, +} + +/// Trade direction +#[derive(Debug, Copy, Clone, Eq, PartialEq, CEnum)] +#[c(remote = "longbridge::quote::TradeDirection")] +#[allow(clippy::enum_variant_names)] +#[repr(C)] +pub enum CTradeDirection { + /// Neutral + #[c(remote = "Neutral")] + TradeDirectionNeutral, + /// Down + #[c(remote = "Down")] + TradeDirectionDown, + /// Up + #[c(remote = "Up")] + TradeDirectionUp, +} + +/// Option type +#[derive(Debug, Copy, Clone, Eq, PartialEq, CEnum)] +#[c(remote = "longbridge::quote::OptionType")] +#[allow(clippy::enum_variant_names)] +#[repr(C)] +pub enum COptionType { + /// Unknown + #[c(remote = "Unknown")] + OptionTypeUnknown, + /// American + #[c(remote = "American")] + OptionTypeAmerican, + /// Enrope + #[c(remote = "Europe")] + OptionTypeEurope, +} + +/// Option direction +#[derive(Debug, Copy, Clone, Eq, PartialEq, CEnum)] +#[c(remote = "longbridge::quote::OptionDirection")] +#[allow(clippy::enum_variant_names)] +#[repr(C)] +pub enum COptionDirection { + /// Unknown + #[c(remote = "Unknown")] + OptionDirectionUnknown, + /// Put + #[c(remote = "Put")] + OptionDirectionAmerican, + /// Call + #[c(remote = "Call")] + OptionDirectionEurope, +} + +/// Warrant type +#[derive(Debug, Copy, Clone, Eq, PartialEq, CEnum)] +#[c(remote = "longbridge::quote::WarrantType")] +#[allow(clippy::enum_variant_names)] +#[repr(C)] +pub enum CWarrantType { + /// Unknown + #[c(remote = "Unknown")] + WarrantTypeUnknown, + /// Put + #[c(remote = "Put")] + WarrantTypePut, + /// Call + #[c(remote = "Call")] + WarrantTypeCall, + /// Bull + #[c(remote = "Bull")] + WarrantTypeBull, + /// Bear + #[c(remote = "Bear")] + WarrantTypeBear, + /// Inline + #[c(remote = "Inline")] + WarrantTypeInline, +} + +/// Adjust type +#[derive(Debug, Copy, Clone, Eq, PartialEq, CEnum)] +#[c(remote = "longbridge::quote::AdjustType")] +#[allow(clippy::enum_variant_names)] +#[repr(C)] +pub enum CAdjustType { + /// Actual + #[c(remote = "NoAdjust")] + AdjustTypeNoAdjust, + /// Adjust forward + #[c(remote = "ForwardAdjust")] + AdjustTypeForward, +} + +/// Candlestick period +#[derive(Debug, Copy, Clone, Eq, PartialEq, CEnum)] +#[c(remote = "longbridge::quote::Period")] +#[allow(clippy::enum_variant_names, non_camel_case_types)] +#[repr(C)] +pub enum CPeriod { + /// One Minute + #[c(remote = "UnknownPeriod")] + PeriodUnknown, + /// One Minute + #[c(remote = "OneMinute")] + PeriodMin1, + /// Five Minutes + #[c(remote = "FiveMinute")] + PeriodMin5, + /// Fifteen Minutes + #[c(remote = "FifteenMinute")] + PeriodMin15, + /// Thirty Minutes + #[c(remote = "ThirtyMinute")] + PeriodMin30, + /// Sixty Minutes + #[c(remote = "SixtyMinute")] + PeriodMin60, + /// One Day + #[c(remote = "Day")] + PeriodDay, + /// One Week + #[c(remote = "Week")] + PeriodWeek, + /// One Month + #[c(remote = "Month")] + PeriodMonth, + /// One Year + #[c(remote = "Year")] + PeriodYear, +} diff --git a/c/src/quote_context/mod.rs b/c/src/quote_context/mod.rs new file mode 100644 index 0000000000..c3b251cab1 --- /dev/null +++ b/c/src/quote_context/mod.rs @@ -0,0 +1,5 @@ +#[allow(dead_code)] +mod constants; +mod context; +mod types; +mod enum_types; diff --git a/c/src/quote_context/types.rs b/c/src/quote_context/types.rs new file mode 100644 index 0000000000..22619fb600 --- /dev/null +++ b/c/src/quote_context/types.rs @@ -0,0 +1,1975 @@ +use std::os::raw::c_char; + +use longbridge::quote::{ + Brokers, Candlestick, CapitalDistribution, CapitalDistributionResponse, CapitalFlowLine, Depth, + IntradayLine, IssuerInfo, MarketTradingDays, MarketTradingSession, OptionDirection, + OptionQuote, OptionType, ParticipantInfo, Period, PrePostQuote, PushBrokers, PushCandlestick, + PushDepth, PushQuote, PushTrades, RealtimeQuote, SecurityBrokers, SecurityDepth, SecurityQuote, + SecurityStaticInfo, StrikePriceInfo, Subscription, Trade, TradeDirection, TradeSession, + TradeStatus, TradingSessionInfo, WarrantQuote, WarrantType, +}; + +use crate::{ + quote_context::enum_types::{ + COptionDirection, COptionType, CPeriod, CTradeDirection, CTradeSession, CTradeStatus, + CWarrantType, + }, + types::{CDate, CDecimal, CMarket, COption, CString, CTime, CVec, ToFFI}, +}; + +/// Quote message +#[repr(C)] +pub struct CPushQuote { + /// Security code + pub symbol: *const c_char, + /// Latest price + pub last_done: *const CDecimal, + /// Open + pub open: *const CDecimal, + /// High + pub high: *const CDecimal, + /// Low + pub low: *const CDecimal, + /// Time of latest price + pub timestamp: i64, + /// Volume + pub volume: i64, + /// Turnover + pub turnover: *const CDecimal, + /// Security trading status + pub trade_status: CTradeStatus, + /// Trade session + pub trade_session: CTradeSession, +} + +#[derive(Debug)] +pub(crate) struct CPushQuoteOwned { + symbol: CString, + last_done: CDecimal, + open: CDecimal, + high: CDecimal, + low: CDecimal, + timestamp: i64, + volume: i64, + turnover: CDecimal, + trade_status: TradeStatus, + trade_session: TradeSession, +} + +impl From<(String, PushQuote)> for CPushQuoteOwned { + fn from((symbol, quote): (String, PushQuote)) -> Self { + let PushQuote { + last_done, + open, + high, + low, + timestamp, + volume, + turnover, + trade_status, + trade_session, + } = quote; + CPushQuoteOwned { + symbol: symbol.into(), + last_done: last_done.into(), + open: open.into(), + high: high.into(), + low: low.into(), + timestamp: timestamp.unix_timestamp(), + volume, + turnover: turnover.into(), + trade_status, + trade_session, + } + } +} + +impl ToFFI for CPushQuoteOwned { + type FFIType = CPushQuote; + + fn to_ffi_type(&self) -> Self::FFIType { + let CPushQuoteOwned { + symbol, + last_done, + open, + high, + low, + timestamp, + volume, + turnover, + trade_status, + trade_session, + } = self; + CPushQuote { + symbol: symbol.to_ffi_type(), + last_done, + open, + high, + low, + timestamp: *timestamp, + volume: *volume, + turnover, + trade_status: (*trade_status).into(), + trade_session: (*trade_session).into(), + } + } +} + +/// Depth +#[repr(C)] +pub struct CDepth { + /// Position + pub position: i32, + /// Price + pub price: *const CDecimal, + /// Volume + pub volume: i64, + /// Number of orders + pub order_num: i64, +} + +#[derive(Debug)] +pub(crate) struct CDepthOwned { + position: i32, + price: CDecimal, + volume: i64, + order_num: i64, +} + +impl From for CDepthOwned { + fn from(depth: Depth) -> Self { + let Depth { + position, + price, + volume, + order_num, + } = depth; + CDepthOwned { + position, + price: price.into(), + volume, + order_num, + } + } +} + +impl ToFFI for CDepthOwned { + type FFIType = CDepth; + + fn to_ffi_type(&self) -> Self::FFIType { + let CDepthOwned { + position, + price, + volume, + order_num, + } = self; + CDepth { + position: *position, + price, + volume: *volume, + order_num: *order_num, + } + } +} + +/// Quote message +#[repr(C)] +pub struct CPushDepth { + /// Security code + pub symbol: *const c_char, + /// Ask depth + pub asks: *const CDepth, + /// Number of asks + pub num_asks: usize, + /// Bid depth + pub bids: *const CDepth, + /// Number of bids + pub num_bids: usize, +} + +#[derive(Debug)] +pub(crate) struct CPushDepthOwned { + symbol: CString, + asks: CVec, + bids: CVec, +} + +impl From<(String, PushDepth)> for CPushDepthOwned { + fn from((symbol, depth): (String, PushDepth)) -> Self { + let PushDepth { asks, bids } = depth; + CPushDepthOwned { + symbol: symbol.into(), + asks: asks.into(), + bids: bids.into(), + } + } +} + +impl ToFFI for CPushDepthOwned { + type FFIType = CPushDepth; + + fn to_ffi_type(&self) -> Self::FFIType { + let CPushDepthOwned { symbol, asks, bids } = self; + CPushDepth { + symbol: symbol.to_ffi_type(), + asks: asks.to_ffi_type(), + num_asks: asks.len(), + bids: bids.to_ffi_type(), + num_bids: bids.len(), + } + } +} + +/// Brokers +#[derive(Debug)] +#[repr(C)] +pub struct CBrokers { + /// Position + pub position: i32, + /// Broker IDs + pub broker_ids: *const i32, + /// Number of broker IDs + pub num_broker_ids: usize, +} + +impl ToFFI for Brokers { + type FFIType = CBrokers; + + fn to_ffi_type(&self) -> Self::FFIType { + let Brokers { + position, + broker_ids, + } = self; + CBrokers { + position: *position, + broker_ids: broker_ids.as_ptr(), + num_broker_ids: broker_ids.len(), + } + } +} + +/// Brokers message +#[repr(C)] +pub struct CPushBrokers { + /// Security code + pub symbol: *const c_char, + /// Ask depth + pub ask_brokers: *const CBrokers, + /// Number of ask brokers + pub num_ask_brokers: usize, + /// Bid depth + pub bid_brokers: *const CBrokers, + /// Number of bid brokers + pub num_bids: usize, +} + +/// Brokers message +#[derive(Debug)] +pub(crate) struct CPushBrokersOwned { + symbol: CString, + ask_brokers: CVec, + bid_brokers: CVec, +} + +impl From<(String, PushBrokers)> for CPushBrokersOwned { + fn from((symbol, brokers): (String, PushBrokers)) -> Self { + let PushBrokers { + ask_brokers, + bid_brokers, + } = brokers; + CPushBrokersOwned { + symbol: symbol.into(), + ask_brokers: ask_brokers.into(), + bid_brokers: bid_brokers.into(), + } + } +} + +impl ToFFI for CPushBrokersOwned { + type FFIType = CPushBrokers; + + fn to_ffi_type(&self) -> Self::FFIType { + let CPushBrokersOwned { + symbol, + ask_brokers, + bid_brokers, + } = self; + CPushBrokers { + symbol: symbol.to_ffi_type(), + ask_brokers: ask_brokers.to_ffi_type(), + num_ask_brokers: ask_brokers.len(), + bid_brokers: bid_brokers.to_ffi_type(), + num_bids: bid_brokers.len(), + } + } +} + +/// Trade +#[repr(C)] +pub struct CTrade { + /// Price + pub price: *const CDecimal, + /// Volume + pub volume: i64, + /// Time of trading + pub timestamp: i64, + /// Trade type + /// + /// HK + /// + /// - `*` - Overseas trade + /// - `D` - Odd-lot trade + /// - `M` - Non-direct off-exchange trade + /// - `P` - Late trade (Off-exchange previous day) + /// - `U` - Auction trade + /// - `X` - Direct off-exchange trade + /// - `Y` - Automatch internalized + /// - `` - Automatch normal + /// + /// US + /// + /// - `` - Regular sale + /// - `A` - Acquisition + /// - `B` - Bunched trade + /// - `D` - Distribution + /// - `F` - Intermarket sweep + /// - `G` - Bunched sold trades + /// - `H` - Price variation trade + /// - `I` - Odd lot trade + /// - `K` - Rule 155 trde(NYSE MKT) + /// - `M` - Market center close price + /// - `P` - Prior reference price + /// - `Q` - Market center open price + /// - `S` - Split trade + /// - `V` - Contingent trade + /// - `W` - Average price trade + /// - `X` - Cross trade + /// - `1` - Stopped stock(Regular trade) + pub trade_type: *const c_char, + /// Trade direction + pub direction: CTradeDirection, + /// Trade session + pub trade_session: CTradeSession, +} + +pub(crate) struct CTradeOwned { + price: CDecimal, + volume: i64, + timestamp: i64, + trade_type: CString, + direction: TradeDirection, + trade_session: TradeSession, +} + +impl From for CTradeOwned { + fn from(trade: Trade) -> Self { + let Trade { + price, + volume, + timestamp, + trade_type, + direction, + trade_session, + } = trade; + CTradeOwned { + price: price.into(), + volume, + timestamp: timestamp.unix_timestamp(), + trade_type: trade_type.into(), + direction, + trade_session, + } + } +} + +impl ToFFI for CTradeOwned { + type FFIType = CTrade; + + fn to_ffi_type(&self) -> Self::FFIType { + let CTradeOwned { + price, + volume, + timestamp, + trade_type, + direction, + trade_session, + } = self; + CTrade { + price, + volume: *volume, + timestamp: *timestamp, + trade_type: trade_type.to_ffi_type(), + direction: (*direction).into(), + trade_session: (*trade_session).into(), + } + } +} + +/// Trades message +#[repr(C)] +pub struct CPushTrades { + /// Security code + pub symbol: *const c_char, + /// Trades data + pub trades: *const CTrade, + /// Number of trades + pub num_trades: usize, +} + +pub(crate) struct CPushTradesOwned { + symbol: CString, + trades: CVec, +} + +impl From<(String, PushTrades)> for CPushTradesOwned { + fn from((symbol, trades): (String, PushTrades)) -> Self { + let PushTrades { trades } = trades; + CPushTradesOwned { + symbol: symbol.into(), + trades: trades.into(), + } + } +} + +impl ToFFI for CPushTradesOwned { + type FFIType = CPushTrades; + + fn to_ffi_type(&self) -> Self::FFIType { + let CPushTradesOwned { symbol, trades } = self; + CPushTrades { + symbol: symbol.to_ffi_type(), + trades: trades.to_ffi_type(), + num_trades: trades.len(), + } + } +} + +/// Candlestick +#[repr(C)] +pub struct CCandlestick { + /// Close price + pub close: *const CDecimal, + /// Open price + pub open: *const CDecimal, + /// Low price + pub low: *const CDecimal, + /// High price + pub high: *const CDecimal, + /// Volume + pub volume: i64, + /// Turnover + pub turnover: *const CDecimal, + /// Timestamp + pub timestamp: i64, +} + +pub(crate) struct CCandlestickOwned { + close: CDecimal, + open: CDecimal, + low: CDecimal, + high: CDecimal, + volume: i64, + turnover: CDecimal, + timestamp: i64, +} + +impl From for CCandlestickOwned { + fn from(candlestick: Candlestick) -> Self { + let Candlestick { + close, + open, + low, + high, + volume, + turnover, + timestamp, + } = candlestick; + CCandlestickOwned { + close: close.into(), + open: open.into(), + low: low.into(), + high: high.into(), + volume, + turnover: turnover.into(), + timestamp: timestamp.unix_timestamp(), + } + } +} + +impl ToFFI for CCandlestickOwned { + type FFIType = CCandlestick; + + fn to_ffi_type(&self) -> Self::FFIType { + let CCandlestickOwned { + close, + open, + low, + high, + volume, + turnover, + timestamp, + } = self; + CCandlestick { + close, + open, + low, + high, + volume: *volume, + turnover, + timestamp: *timestamp, + } + } +} + +/// Candlestick updated message +#[repr(C)] +pub struct CPushCandlestick { + /// Security code + pub symbol: *const c_char, + /// Period type + pub period: CPeriod, + /// Candlestick + pub candlestick: CCandlestick, +} + +pub(crate) struct CPushCandlestickOwned { + symbol: CString, + period: Period, + candlestick: CCandlestickOwned, +} + +impl From<(String, PushCandlestick)> for CPushCandlestickOwned { + fn from((symbol, candlestick): (String, PushCandlestick)) -> Self { + let PushCandlestick { + period, + candlestick, + } = candlestick; + CPushCandlestickOwned { + symbol: symbol.into(), + period, + candlestick: candlestick.into(), + } + } +} + +impl ToFFI for CPushCandlestickOwned { + type FFIType = CPushCandlestick; + + fn to_ffi_type(&self) -> Self::FFIType { + let CPushCandlestickOwned { + symbol, + period, + candlestick, + } = self; + CPushCandlestick { + symbol: symbol.to_ffi_type(), + period: (*period).into(), + candlestick: candlestick.to_ffi_type(), + } + } +} + +#[repr(C)] +pub struct CSubscription { + symbol: *const c_char, + sub_types: u8, + candlesticks: *const i32, + num_candlesticks: usize, +} + +#[derive(Debug)] +pub(crate) struct CSubscriptionOwned { + symbol: CString, + sub_types: u8, + candlesticks: Vec, +} + +impl From for CSubscriptionOwned { + fn from(subscription: Subscription) -> Self { + let Subscription { + symbol, + sub_types, + candlesticks, + } = subscription; + CSubscriptionOwned { + symbol: symbol.into(), + sub_types: sub_types.bits(), + candlesticks: candlesticks.into_iter().map(|value| value as i32).collect(), + } + } +} + +impl ToFFI for CSubscriptionOwned { + type FFIType = CSubscription; + + fn to_ffi_type(&self) -> Self::FFIType { + let CSubscriptionOwned { + symbol, + sub_types, + candlesticks, + } = self; + CSubscription { + symbol: symbol.to_ffi_type(), + sub_types: *sub_types, + candlesticks: candlesticks.as_ptr(), + num_candlesticks: candlesticks.len(), + } + } +} + +/// The basic information of securities +#[repr(C)] +pub struct CSecurityStaticInfo { + /// Security code + pub symbol: *const c_char, + /// Security name (zh-CN) + pub name_cn: *const c_char, + /// Security name (en) + pub name_en: *const c_char, + /// Security name (zh-HK) + pub name_hk: *const c_char, + /// Exchange which the security belongs to + pub exchange: *const c_char, + /// Trading currency + pub currency: *const c_char, + /// Lot size + pub lot_size: i32, + /// Total shares + pub total_shares: i64, + /// Circulating shares + pub circulating_shares: i64, + /// HK shares (only HK stocks) + pub hk_shares: i64, + /// Earnings per share + pub eps: *const CDecimal, + /// Earnings per share (TTM) + pub eps_ttm: *const CDecimal, + /// Net assets per share + pub bps: *const CDecimal, + /// Dividend yield + pub dividend_yield: *const CDecimal, + /// Types of supported derivatives + pub stock_derivatives: u8, +} + +#[derive(Debug)] +pub(crate) struct CSecurityStaticInfoOwned { + pub symbol: CString, + pub name_cn: CString, + pub name_en: CString, + pub name_hk: CString, + pub exchange: CString, + pub currency: CString, + pub lot_size: i32, + pub total_shares: i64, + pub circulating_shares: i64, + pub hk_shares: i64, + pub eps: CDecimal, + pub eps_ttm: CDecimal, + pub bps: CDecimal, + pub dividend_yield: CDecimal, + pub stock_derivatives: u8, +} + +impl From for CSecurityStaticInfoOwned { + fn from(info: SecurityStaticInfo) -> Self { + let SecurityStaticInfo { + symbol, + name_cn, + name_en, + name_hk, + exchange, + currency, + lot_size, + total_shares, + circulating_shares, + hk_shares, + eps, + eps_ttm, + bps, + dividend_yield, + stock_derivatives, + } = info; + CSecurityStaticInfoOwned { + symbol: symbol.into(), + name_cn: name_cn.into(), + name_en: name_en.into(), + name_hk: name_hk.into(), + exchange: exchange.into(), + currency: currency.into(), + lot_size, + total_shares, + circulating_shares, + hk_shares, + eps: eps.into(), + eps_ttm: eps_ttm.into(), + bps: bps.into(), + dividend_yield: dividend_yield.into(), + stock_derivatives: stock_derivatives.bits(), + } + } +} + +impl ToFFI for CSecurityStaticInfoOwned { + type FFIType = CSecurityStaticInfo; + + fn to_ffi_type(&self) -> Self::FFIType { + let CSecurityStaticInfoOwned { + symbol, + name_cn, + name_en, + name_hk, + exchange, + currency, + lot_size, + total_shares, + circulating_shares, + hk_shares, + eps, + eps_ttm, + bps, + dividend_yield, + stock_derivatives, + } = self; + CSecurityStaticInfo { + symbol: symbol.to_ffi_type(), + name_cn: name_cn.to_ffi_type(), + name_en: name_en.to_ffi_type(), + name_hk: name_hk.to_ffi_type(), + exchange: exchange.to_ffi_type(), + currency: currency.to_ffi_type(), + lot_size: *lot_size, + total_shares: *total_shares, + circulating_shares: *circulating_shares, + hk_shares: *hk_shares, + eps: eps.to_ffi_type(), + eps_ttm: eps_ttm.to_ffi_type(), + bps: bps.to_ffi_type(), + dividend_yield: dividend_yield.to_ffi_type(), + stock_derivatives: *stock_derivatives, + } + } +} + +/// Quote of US pre/post market +#[derive(Debug, Clone)] +pub struct CPrePostQuote { + /// Latest price + pub last_done: *const CDecimal, + /// Time of latest price + pub timestamp: i64, + /// Volume + pub volume: i64, + /// Turnover + pub turnover: *const CDecimal, + /// High + pub high: *const CDecimal, + /// Low + pub low: *const CDecimal, + /// Close of the last trade session + pub prev_close: *const CDecimal, +} + +/// Quote of US pre/post market +pub(crate) struct CPrePostQuoteOwned { + last_done: CDecimal, + timestamp: i64, + volume: i64, + turnover: CDecimal, + high: CDecimal, + low: CDecimal, + prev_close: CDecimal, +} + +impl From for CPrePostQuoteOwned { + fn from(quote: PrePostQuote) -> Self { + let PrePostQuote { + last_done, + timestamp, + volume, + turnover, + high, + low, + prev_close, + } = quote; + Self { + last_done: last_done.into(), + timestamp: timestamp.unix_timestamp(), + volume, + turnover: turnover.into(), + high: high.into(), + low: low.into(), + prev_close: prev_close.into(), + } + } +} + +impl ToFFI for CPrePostQuoteOwned { + type FFIType = CPrePostQuote; + + fn to_ffi_type(&self) -> Self::FFIType { + let CPrePostQuoteOwned { + last_done, + timestamp, + volume, + turnover, + high, + low, + prev_close, + } = self; + CPrePostQuote { + last_done, + timestamp: *timestamp, + volume: *volume, + turnover, + high, + low, + prev_close, + } + } +} + +/// Quote of securitity +#[repr(C)] +pub struct CSecurityQuote { + /// Security code + pub symbol: *const c_char, + /// Latest price + pub last_done: *const CDecimal, + /// Yesterday's close + pub prev_close: *const CDecimal, + /// Open + pub open: *const CDecimal, + /// High + pub high: *const CDecimal, + /// Low + pub low: *const CDecimal, + /// Time of latest price + pub timestamp: i64, + /// Volume + pub volume: i64, + /// Turnover + pub turnover: *const CDecimal, + /// Security trading status + pub trade_status: CTradeStatus, + /// Quote of US pre market + pub pre_market_quote: *const CPrePostQuote, + /// Quote of US post market + pub post_market_quote: *const CPrePostQuote, +} + +pub(crate) struct CSecurityQuoteOwned { + symbol: CString, + last_done: CDecimal, + prev_close: CDecimal, + open: CDecimal, + high: CDecimal, + low: CDecimal, + timestamp: i64, + volume: i64, + turnover: CDecimal, + trade_status: TradeStatus, + pre_market_quote: COption, + post_market_quote: COption, +} + +impl From for CSecurityQuoteOwned { + fn from(quote: SecurityQuote) -> Self { + let SecurityQuote { + symbol, + last_done, + prev_close, + open, + high, + low, + timestamp, + volume, + turnover, + trade_status, + pre_market_quote, + post_market_quote, + } = quote; + Self { + symbol: symbol.into(), + last_done: last_done.into(), + prev_close: prev_close.into(), + open: open.into(), + high: high.into(), + low: low.into(), + timestamp: timestamp.unix_timestamp(), + volume, + turnover: turnover.into(), + trade_status, + pre_market_quote: pre_market_quote.into(), + post_market_quote: post_market_quote.into(), + } + } +} + +impl ToFFI for CSecurityQuoteOwned { + type FFIType = CSecurityQuote; + + fn to_ffi_type(&self) -> Self::FFIType { + let CSecurityQuoteOwned { + symbol, + last_done, + prev_close, + open, + high, + low, + timestamp, + volume, + turnover, + trade_status, + pre_market_quote, + post_market_quote, + } = self; + CSecurityQuote { + symbol: symbol.to_ffi_type(), + last_done: last_done.to_ffi_type(), + prev_close: prev_close.to_ffi_type(), + open: open.to_ffi_type(), + high: high.to_ffi_type(), + low: low.to_ffi_type(), + timestamp: *timestamp, + volume: *volume, + turnover: turnover.to_ffi_type(), + trade_status: (*trade_status).into(), + pre_market_quote: pre_market_quote.to_ffi_type(), + post_market_quote: post_market_quote.to_ffi_type(), + } + } +} + +/// Quote of option +#[repr(C)] +pub struct COptionQuote { + /// Security code + pub symbol: *const c_char, + /// Latest price + pub last_done: *const CDecimal, + /// Yesterday's close + pub prev_close: *const CDecimal, + /// Open + pub open: *const CDecimal, + /// High + pub high: *const CDecimal, + /// Low + pub low: *const CDecimal, + /// Time of latest price + pub timestamp: i64, + /// Volume + pub volume: i64, + /// Turnover + pub turnover: *const CDecimal, + /// Security trading status + pub trade_status: CTradeStatus, + /// Implied volatility + pub implied_volatility: *const CDecimal, + /// Number of open positions + pub open_interest: i64, + /// Exprity date + pub expiry_date: CDate, + /// Strike price + pub strike_price: *const CDecimal, + /// Contract multiplier + pub contract_multiplier: *const CDecimal, + /// Option type + pub contract_type: COptionType, + /// Contract size + pub contract_size: *const CDecimal, + /// Option direction + pub direction: COptionDirection, + /// Underlying security historical volatility of the option + pub historical_volatility: *const CDecimal, + /// Underlying security symbol of the option + pub underlying_symbol: *const c_char, +} + +pub(crate) struct COptionQuoteOwned { + symbol: CString, + last_done: CDecimal, + prev_close: CDecimal, + open: CDecimal, + high: CDecimal, + low: CDecimal, + timestamp: i64, + volume: i64, + turnover: CDecimal, + trade_status: TradeStatus, + implied_volatility: CDecimal, + open_interest: i64, + expiry_date: CDate, + strike_price: CDecimal, + contract_multiplier: CDecimal, + contract_type: OptionType, + contract_size: CDecimal, + direction: OptionDirection, + historical_volatility: CDecimal, + underlying_symbol: CString, +} + +impl From for COptionQuoteOwned { + fn from(quote: OptionQuote) -> Self { + let OptionQuote { + symbol, + last_done, + prev_close, + open, + high, + low, + timestamp, + volume, + turnover, + trade_status, + implied_volatility, + open_interest, + expiry_date, + strike_price, + contract_multiplier, + contract_type, + contract_size, + direction, + historical_volatility, + underlying_symbol, + } = quote; + Self { + symbol: symbol.into(), + last_done: last_done.into(), + prev_close: prev_close.into(), + open: open.into(), + high: high.into(), + low: low.into(), + timestamp: timestamp.unix_timestamp(), + volume, + turnover: turnover.into(), + trade_status, + implied_volatility: implied_volatility.into(), + open_interest, + expiry_date: expiry_date.into(), + strike_price: strike_price.into(), + contract_multiplier: contract_multiplier.into(), + contract_type, + contract_size: contract_size.into(), + direction, + historical_volatility: historical_volatility.into(), + underlying_symbol: underlying_symbol.into(), + } + } +} + +impl ToFFI for COptionQuoteOwned { + type FFIType = COptionQuote; + + fn to_ffi_type(&self) -> Self::FFIType { + let COptionQuoteOwned { + symbol, + last_done, + prev_close, + open, + high, + low, + timestamp, + volume, + turnover, + trade_status, + implied_volatility, + open_interest, + expiry_date, + strike_price, + contract_multiplier, + contract_type, + contract_size, + direction, + historical_volatility, + underlying_symbol, + } = self; + COptionQuote { + symbol: symbol.to_ffi_type(), + last_done: last_done.to_ffi_type(), + prev_close: prev_close.to_ffi_type(), + open: open.to_ffi_type(), + high: high.to_ffi_type(), + low: low.to_ffi_type(), + timestamp: *timestamp, + volume: *volume, + turnover: turnover.to_ffi_type(), + trade_status: (*trade_status).into(), + implied_volatility: implied_volatility.to_ffi_type(), + open_interest: *open_interest, + expiry_date: *expiry_date, + strike_price: strike_price.to_ffi_type(), + contract_multiplier: contract_multiplier.to_ffi_type(), + contract_type: (*contract_type).into(), + contract_size: contract_size.to_ffi_type(), + direction: (*direction).into(), + historical_volatility: historical_volatility.to_ffi_type(), + underlying_symbol: underlying_symbol.to_ffi_type(), + } + } +} + +/// Quote of warrant +#[repr(C)] +pub struct CWarrantQuote { + /// Security code + pub symbol: *const c_char, + /// Latest price + pub last_done: *const CDecimal, + /// Yesterday's close + pub prev_close: *const CDecimal, + /// Open + pub open: *const CDecimal, + /// High + pub high: *const CDecimal, + /// Low + pub low: *const CDecimal, + /// Time of latest price + pub timestamp: i64, + /// Volume + pub volume: i64, + /// Turnover + pub turnover: *const CDecimal, + /// Security trading status + pub trade_status: CTradeStatus, + /// Implied volatility + pub implied_volatility: *const CDecimal, + /// Exprity date + pub expiry_date: CDate, + /// Last tradalbe date + pub last_trade_date: CDate, + /// Outstanding ratio + pub outstanding_ratio: *const CDecimal, + /// Outstanding quantity + pub outstanding_quantity: i64, + /// Conversion ratio + pub conversion_ratio: *const CDecimal, + /// Warrant type + pub category: CWarrantType, + /// Strike price + pub strike_price: *const CDecimal, + /// Upper bound price + pub upper_strike_price: *const CDecimal, + /// Lower bound price + pub lower_strike_price: *const CDecimal, + /// Call price + pub call_price: *const CDecimal, + /// Underlying security symbol of the warrant + pub underlying_symbol: *const c_char, +} + +pub(crate) struct CWarrantQuoteOwned { + symbol: CString, + last_done: CDecimal, + prev_close: CDecimal, + open: CDecimal, + high: CDecimal, + low: CDecimal, + timestamp: i64, + volume: i64, + turnover: CDecimal, + trade_status: TradeStatus, + implied_volatility: CDecimal, + expiry_date: CDate, + last_trade_date: CDate, + outstanding_ratio: CDecimal, + outstanding_quantity: i64, + conversion_ratio: CDecimal, + category: WarrantType, + strike_price: CDecimal, + upper_strike_price: CDecimal, + lower_strike_price: CDecimal, + call_price: CDecimal, + underlying_symbol: CString, +} + +impl From for CWarrantQuoteOwned { + fn from(quote: WarrantQuote) -> Self { + let WarrantQuote { + symbol, + last_done, + prev_close, + open, + high, + low, + timestamp, + volume, + turnover, + trade_status, + implied_volatility, + expiry_date, + last_trade_date, + outstanding_ratio, + outstanding_quantity, + conversion_ratio, + category, + strike_price, + upper_strike_price, + lower_strike_price, + call_price, + underlying_symbol, + } = quote; + Self { + symbol: symbol.into(), + last_done: last_done.into(), + prev_close: prev_close.into(), + open: open.into(), + high: high.into(), + low: low.into(), + timestamp: timestamp.unix_timestamp(), + volume, + turnover: turnover.into(), + trade_status, + implied_volatility: implied_volatility.into(), + expiry_date: expiry_date.into(), + last_trade_date: last_trade_date.into(), + outstanding_ratio: outstanding_ratio.into(), + outstanding_quantity, + conversion_ratio: conversion_ratio.into(), + category, + strike_price: strike_price.into(), + upper_strike_price: upper_strike_price.into(), + lower_strike_price: lower_strike_price.into(), + call_price: call_price.into(), + underlying_symbol: underlying_symbol.into(), + } + } +} + +impl ToFFI for CWarrantQuoteOwned { + type FFIType = CWarrantQuote; + + fn to_ffi_type(&self) -> Self::FFIType { + let CWarrantQuoteOwned { + symbol, + last_done, + prev_close, + open, + high, + low, + timestamp, + volume, + turnover, + trade_status, + implied_volatility, + expiry_date, + last_trade_date, + outstanding_ratio, + outstanding_quantity, + conversion_ratio, + category, + strike_price, + upper_strike_price, + lower_strike_price, + call_price, + underlying_symbol, + } = self; + CWarrantQuote { + symbol: symbol.to_ffi_type(), + last_done: last_done.to_ffi_type(), + prev_close: prev_close.to_ffi_type(), + open: open.to_ffi_type(), + high: high.to_ffi_type(), + low: low.to_ffi_type(), + timestamp: *timestamp, + volume: *volume, + turnover: turnover.to_ffi_type(), + trade_status: (*trade_status).into(), + implied_volatility: implied_volatility.to_ffi_type(), + expiry_date: *expiry_date, + last_trade_date: *last_trade_date, + outstanding_ratio: outstanding_ratio.to_ffi_type(), + outstanding_quantity: *outstanding_quantity, + conversion_ratio: conversion_ratio.to_ffi_type(), + category: (*category).into(), + strike_price: strike_price.to_ffi_type(), + upper_strike_price: upper_strike_price.to_ffi_type(), + lower_strike_price: lower_strike_price.to_ffi_type(), + call_price: call_price.to_ffi_type(), + underlying_symbol: underlying_symbol.to_ffi_type(), + } + } +} + +/// Quote message +#[repr(C)] +pub struct CSecurityDepth { + /// Ask depth + pub asks: *const CDepth, + /// Number of asks + pub num_asks: usize, + /// Bid depth + pub bids: *const CDepth, + /// Number of bids + pub num_bids: usize, +} + +#[derive(Debug)] +pub(crate) struct CSecurityDepthOwned { + asks: CVec, + bids: CVec, +} + +impl From for CSecurityDepthOwned { + fn from(depth: SecurityDepth) -> Self { + let SecurityDepth { asks, bids } = depth; + CSecurityDepthOwned { + asks: asks.into(), + bids: bids.into(), + } + } +} + +impl ToFFI for CSecurityDepthOwned { + type FFIType = CSecurityDepth; + + fn to_ffi_type(&self) -> Self::FFIType { + let CSecurityDepthOwned { asks, bids } = self; + CSecurityDepth { + asks: asks.to_ffi_type(), + num_asks: asks.len(), + bids: bids.to_ffi_type(), + num_bids: bids.len(), + } + } +} + +/// Security brokers +#[repr(C)] +pub struct CSecurityBrokers { + /// Ask brokers + pub ask_brokers: *const CBrokers, + /// Number of ask brokers + pub num_ask_brokers: usize, + /// Bid brokers + pub bid_brokers: *const CBrokers, + /// Number of bid brokers + pub num_bid_brokers: usize, +} + +#[derive(Debug)] +pub(crate) struct CSecurityBrokersOwned { + ask_brokers: CVec, + bid_brokers: CVec, +} + +impl From for CSecurityBrokersOwned { + fn from(brokers: SecurityBrokers) -> Self { + let SecurityBrokers { + ask_brokers, + bid_brokers, + } = brokers; + CSecurityBrokersOwned { + ask_brokers: ask_brokers.into(), + bid_brokers: bid_brokers.into(), + } + } +} + +impl ToFFI for CSecurityBrokersOwned { + type FFIType = CSecurityBrokers; + + fn to_ffi_type(&self) -> Self::FFIType { + let CSecurityBrokersOwned { + ask_brokers, + bid_brokers, + } = self; + CSecurityBrokers { + ask_brokers: ask_brokers.to_ffi_type(), + num_ask_brokers: ask_brokers.len(), + bid_brokers: bid_brokers.to_ffi_type(), + num_bid_brokers: bid_brokers.len(), + } + } +} + +/// Participant info +#[repr(C)] +pub struct CParticipantInfo { + /// Broker IDs + pub broker_ids: *const i32, + /// Number of broker IDs + pub num_broker_ids: usize, + /// Participant name (zh-CN) + pub name_cn: *const c_char, + /// Participant name (en) + pub name_en: *const c_char, + /// Participant name (zh-HK) + pub name_hk: *const c_char, +} + +#[derive(Debug)] +pub struct CParticipantInfoOwned { + broker_ids: Vec, + name_cn: CString, + name_en: CString, + name_hk: CString, +} + +impl From for CParticipantInfoOwned { + fn from(participant: ParticipantInfo) -> Self { + let ParticipantInfo { + broker_ids, + name_cn, + name_en, + name_hk, + } = participant; + CParticipantInfoOwned { + broker_ids, + name_cn: name_cn.into(), + name_en: name_en.into(), + name_hk: name_hk.into(), + } + } +} + +impl ToFFI for CParticipantInfoOwned { + type FFIType = CParticipantInfo; + + fn to_ffi_type(&self) -> Self::FFIType { + let CParticipantInfoOwned { + broker_ids, + name_cn, + name_en, + name_hk, + } = self; + CParticipantInfo { + broker_ids: broker_ids.as_ptr(), + num_broker_ids: broker_ids.len(), + name_cn: name_cn.to_ffi_type(), + name_en: name_en.to_ffi_type(), + name_hk: name_hk.to_ffi_type(), + } + } +} + +/// Intraday line +#[repr(C)] +pub struct CIntradayLine { + /// Close price of the minute + pub price: *const CDecimal, + /// Start time of the minute + pub timestamp: i64, + /// Volume + pub volume: i64, + /// Turnover + pub turnover: *const CDecimal, + /// Average price + pub avg_price: *const CDecimal, +} + +#[derive(Debug)] +pub(crate) struct CIntradayLineOwned { + pub price: CDecimal, + pub timestamp: i64, + pub volume: i64, + pub turnover: CDecimal, + pub avg_price: CDecimal, +} + +impl From for CIntradayLineOwned { + fn from(line: IntradayLine) -> Self { + let IntradayLine { + price, + timestamp, + volume, + turnover, + avg_price, + } = line; + CIntradayLineOwned { + price: price.into(), + timestamp: timestamp.unix_timestamp(), + volume, + turnover: turnover.into(), + avg_price: avg_price.into(), + } + } +} + +impl ToFFI for CIntradayLineOwned { + type FFIType = CIntradayLine; + + fn to_ffi_type(&self) -> Self::FFIType { + let CIntradayLineOwned { + price, + timestamp, + volume, + turnover, + avg_price, + } = self; + CIntradayLine { + price: price.to_ffi_type(), + timestamp: *timestamp, + volume: *volume, + turnover: turnover.to_ffi_type(), + avg_price: avg_price.to_ffi_type(), + } + } +} + +/// Strike price info +#[repr(C)] +pub struct CStrikePriceInfo { + /// Strike price + pub price: *const CDecimal, + /// Security code of call option + pub call_symbol: *const c_char, + /// Security code of put option + pub put_symbol: *const c_char, + /// Is standard + pub standard: bool, +} + +#[derive(Debug)] +pub(crate) struct CStrikePriceInfoOwned { + price: CDecimal, + call_symbol: CString, + put_symbol: CString, + standard: bool, +} + +impl From for CStrikePriceInfoOwned { + fn from(info: StrikePriceInfo) -> Self { + let StrikePriceInfo { + price, + call_symbol, + put_symbol, + standard, + } = info; + CStrikePriceInfoOwned { + price: price.into(), + call_symbol: call_symbol.into(), + put_symbol: put_symbol.into(), + standard, + } + } +} + +impl ToFFI for CStrikePriceInfoOwned { + type FFIType = CStrikePriceInfo; + + fn to_ffi_type(&self) -> Self::FFIType { + let CStrikePriceInfoOwned { + price, + call_symbol, + put_symbol, + standard, + } = self; + CStrikePriceInfo { + price, + call_symbol: call_symbol.to_ffi_type(), + put_symbol: put_symbol.to_ffi_type(), + standard: *standard, + } + } +} + +/// Issuer info +#[repr(C)] +pub struct CIssuerInfo { + /// Issuer ID + pub issuer_id: i32, + /// Issuer name (zh-CN) + pub name_cn: *const c_char, + /// Issuer name (en) + pub name_en: *const c_char, + /// Issuer name (zh-HK) + pub name_hk: *const c_char, +} + +#[derive(Debug)] +pub(crate) struct CIssuerInfoOwned { + issuer_id: i32, + name_cn: CString, + name_en: CString, + name_hk: CString, +} + +impl From for CIssuerInfoOwned { + fn from(info: IssuerInfo) -> Self { + let IssuerInfo { + issuer_id, + name_cn, + name_en, + name_hk, + } = info; + CIssuerInfoOwned { + issuer_id, + name_cn: name_cn.into(), + name_en: name_en.into(), + name_hk: name_hk.into(), + } + } +} + +impl ToFFI for CIssuerInfoOwned { + type FFIType = CIssuerInfo; + + fn to_ffi_type(&self) -> Self::FFIType { + let CIssuerInfoOwned { + issuer_id, + name_cn, + name_en, + name_hk, + } = self; + CIssuerInfo { + issuer_id: *issuer_id, + name_cn: name_cn.to_ffi_type(), + name_en: name_en.to_ffi_type(), + name_hk: name_hk.to_ffi_type(), + } + } +} + +/// The information of trading session +#[repr(C)] +pub struct CTradingSessionInfo { + /// Being trading time + pub begin_time: CTime, + /// End trading time + pub end_time: CTime, + /// Trading session + pub trade_session: CTradeSession, +} + +impl ToFFI for TradingSessionInfo { + type FFIType = CTradingSessionInfo; + + fn to_ffi_type(&self) -> Self::FFIType { + let TradingSessionInfo { + begin_time, + end_time, + trade_session, + } = self; + CTradingSessionInfo { + begin_time: (*begin_time).into(), + end_time: (*end_time).into(), + trade_session: (*trade_session).into(), + } + } +} + +/// Market trading session +#[repr(C)] +pub struct CMarketTradingSession { + /// Market + pub market: CMarket, + /// Trading sessions + pub trade_sessions: *const CTradingSessionInfo, + /// Number trading sessions + pub num_trade_sessions: usize, +} + +#[derive(Debug)] +pub(crate) struct CMarketTradingSessionOwned { + market: CMarket, + trade_sessions: CVec, +} + +impl From for CMarketTradingSessionOwned { + fn from(info: MarketTradingSession) -> Self { + let MarketTradingSession { + market, + trade_sessions, + } = info; + CMarketTradingSessionOwned { + market: market.into(), + trade_sessions: trade_sessions.into(), + } + } +} + +impl ToFFI for CMarketTradingSessionOwned { + type FFIType = CMarketTradingSession; + + fn to_ffi_type(&self) -> Self::FFIType { + let CMarketTradingSessionOwned { + market, + trade_sessions, + } = self; + CMarketTradingSession { + market: *market, + trade_sessions: trade_sessions.to_ffi_type(), + num_trade_sessions: trade_sessions.len(), + } + } +} + +/// Market trading days +#[repr(C)] +pub struct CMarketTradingDays { + /// Trading days + pub trading_days: *const CDate, + /// Number of trading days + pub num_trading_days: usize, + /// Half trading days + pub half_trading_days: *const CDate, + /// Number of half trading days + pub num_half_trading_days: usize, +} + +#[derive(Debug)] +pub(crate) struct CMarketTradingDaysOwned { + /// Trading days + trading_days: CVec, + /// Half trading days + half_trading_days: CVec, +} + +impl From for CMarketTradingDaysOwned { + fn from(trading_days: MarketTradingDays) -> Self { + let MarketTradingDays { + trading_days, + half_trading_days, + } = trading_days; + CMarketTradingDaysOwned { + trading_days: trading_days.into(), + half_trading_days: half_trading_days.into(), + } + } +} + +impl ToFFI for CMarketTradingDaysOwned { + type FFIType = CMarketTradingDays; + + fn to_ffi_type(&self) -> Self::FFIType { + let CMarketTradingDaysOwned { + trading_days, + half_trading_days, + } = self; + CMarketTradingDays { + trading_days: trading_days.to_ffi_type(), + num_trading_days: trading_days.len(), + half_trading_days: half_trading_days.to_ffi_type(), + num_half_trading_days: half_trading_days.len(), + } + } +} + +/// Market trading days +#[repr(C)] +pub struct CCapitalFlowLine { + /// Inflow capital data + pub inflow: *const CDecimal, + /// Time + pub timestamp: i64, +} + +#[derive(Debug)] +pub struct CCapitalFlowLineOwned { + pub inflow: CDecimal, + pub timestamp: i64, +} + +impl From for CCapitalFlowLineOwned { + fn from(line: CapitalFlowLine) -> Self { + let CapitalFlowLine { inflow, timestamp } = line; + CCapitalFlowLineOwned { + inflow: inflow.into(), + timestamp: timestamp.unix_timestamp(), + } + } +} + +impl ToFFI for CCapitalFlowLineOwned { + type FFIType = CCapitalFlowLine; + + fn to_ffi_type(&self) -> Self::FFIType { + let CCapitalFlowLineOwned { inflow, timestamp } = self; + CCapitalFlowLine { + inflow: inflow.to_ffi_type(), + timestamp: *timestamp, + } + } +} + +/// Capital distribution +#[repr(C)] +pub struct CCapitalDistribution { + /// Large order + pub large: *const CDecimal, + /// Medium order + pub medium: *const CDecimal, + /// Small order + pub small: *const CDecimal, +} + +pub(crate) struct CCapitalDistributionOwned { + large: CDecimal, + medium: CDecimal, + small: CDecimal, +} + +impl From for CCapitalDistributionOwned { + fn from(cd: CapitalDistribution) -> Self { + let CapitalDistribution { + large, + medium, + small, + } = cd; + CCapitalDistributionOwned { + large: large.into(), + medium: medium.into(), + small: small.into(), + } + } +} + +impl ToFFI for CCapitalDistributionOwned { + type FFIType = CCapitalDistribution; + + fn to_ffi_type(&self) -> Self::FFIType { + let CCapitalDistributionOwned { + large, + medium, + small, + } = self; + CCapitalDistribution { + large, + medium, + small, + } + } +} + +/// Capital distribution response +#[repr(C)] +pub struct CCapitalDistributionResponse { + /// Time + pub timestamp: i64, + /// Inflow capital data + pub capital_in: CCapitalDistribution, + /// Outflow capital data + pub capital_out: CCapitalDistribution, +} + +pub(crate) struct CCapitalDistributionResponseOwned { + timestamp: i64, + capital_in: CCapitalDistributionOwned, + capital_out: CCapitalDistributionOwned, +} + +impl From for CCapitalDistributionResponseOwned { + fn from(resp: CapitalDistributionResponse) -> Self { + let CapitalDistributionResponse { + timestamp, + capital_in, + capital_out, + } = resp; + CCapitalDistributionResponseOwned { + timestamp: timestamp.unix_timestamp(), + capital_in: capital_in.into(), + capital_out: capital_out.into(), + } + } +} + +impl ToFFI for CCapitalDistributionResponseOwned { + type FFIType = CCapitalDistributionResponse; + + fn to_ffi_type(&self) -> Self::FFIType { + let CCapitalDistributionResponseOwned { + timestamp, + capital_in, + capital_out, + } = self; + CCapitalDistributionResponse { + timestamp: *timestamp, + capital_in: capital_in.to_ffi_type(), + capital_out: capital_out.to_ffi_type(), + } + } +} + +/// Real-time quote +#[repr(C)] +pub struct CRealtimeQuote { + /// Security code + pub symbol: *const c_char, + /// Latest price + pub last_done: *const CDecimal, + /// Open + pub open: *const CDecimal, + /// High + pub high: *const CDecimal, + /// Low + pub low: *const CDecimal, + /// Time of latest price + pub timestamp: i64, + /// Volume + pub volume: i64, + /// Turnover + pub turnover: *const CDecimal, + /// Security trading status + pub trade_status: CTradeStatus, +} + +/// Real-time quote +#[derive(Debug)] +pub(crate) struct CRealtimeQuoteOwned { + symbol: CString, + last_done: CDecimal, + open: CDecimal, + high: CDecimal, + low: CDecimal, + timestamp: i64, + volume: i64, + turnover: CDecimal, + trade_status: TradeStatus, +} + +impl From for CRealtimeQuoteOwned { + fn from(resp: RealtimeQuote) -> Self { + let RealtimeQuote { + symbol, + last_done, + open, + high, + low, + timestamp, + volume, + turnover, + trade_status, + } = resp; + CRealtimeQuoteOwned { + symbol: symbol.into(), + last_done: last_done.into(), + open: open.into(), + high: high.into(), + low: low.into(), + timestamp: timestamp.unix_timestamp(), + volume, + turnover: turnover.into(), + trade_status, + } + } +} + +impl ToFFI for CRealtimeQuoteOwned { + type FFIType = CRealtimeQuote; + + fn to_ffi_type(&self) -> Self::FFIType { + let CRealtimeQuoteOwned { + symbol, + last_done, + open, + high, + low, + timestamp, + volume, + turnover, + trade_status, + } = self; + CRealtimeQuote { + symbol: symbol.to_ffi_type(), + last_done: last_done.to_ffi_type(), + open: open.to_ffi_type(), + high: high.to_ffi_type(), + low: low.to_ffi_type(), + timestamp: *timestamp, + volume: *volume, + turnover: turnover.to_ffi_type(), + trade_status: (*trade_status).into(), + } + } +} diff --git a/c/src/trade_context/context.rs b/c/src/trade_context/context.rs new file mode 100644 index 0000000000..ee3075c3b9 --- /dev/null +++ b/c/src/trade_context/context.rs @@ -0,0 +1,505 @@ +use std::{ffi::c_void, os::raw::c_char, sync::Arc}; + +use longbridge::{ + trade::{ + GetCashFlowOptions, GetFundPositionsOptions, GetHistoryExecutionsOptions, + GetHistoryOrdersOptions, GetStockPositionsOptions, GetTodayExecutionsOptions, + GetTodayOrdersOptions, PushEvent, ReplaceOrderOptions, SubmitOrderOptions, + }, + TradeContext, +}; +use parking_lot::Mutex; +use time::OffsetDateTime; + +use crate::{ + async_call::{execute_async, CAsyncCallback, CAsyncResult}, + config::CConfig, + trade_context::{ + enum_types::CTopicType, + types::{ + CAccountBalanceOwned, CCashFlowOwned, CExecutionOwned, CFundPositionsResponseOwned, + CGetCashFlowOptions, CGetFundPositionsOptions, CGetHistoryExecutionsOptions, + CGetHistoryOrdersOptions, CGetStockPositionsOptions, CGetTodayExecutionsOptions, + CGetTodayOrdersOptions, COrderOwned, CPushOrderChanged, CPushOrderChangedOwned, + CReplaceOrderOptions, CStockPositionsResponseOwned, CSubmitOrderOptions, + CSubmitOrderResponseOwned, + }, + }, + types::{cstr_array_to_rust, cstr_to_rust, CCow, CVec, ToFFI}, +}; + +pub type COnOrderChangedCallback = extern "C" fn(*const CTradeContext, *const CPushOrderChanged); + +#[derive(Default)] +struct Callbacks { + order_changed: Option, +} + +pub struct CTradeContextState { + userdata: *mut c_void, + callbacks: Callbacks, +} + +unsafe impl Send for CTradeContextState {} + +/// Trade context +pub struct CTradeContext { + ctx: TradeContext, + state: Mutex, +} + +#[no_mangle] +pub unsafe extern "C" fn lb_trade_context_new( + config: *const CConfig, + callback: CAsyncCallback, + userdata: *mut c_void, +) { + let config = (*config).0.clone(); + let userdata_pointer = userdata as usize; + + execute_async( + callback, + std::ptr::null_mut::(), + userdata, + async move { + let (ctx, mut receiver) = TradeContext::try_new(config).await?; + let state = Mutex::new(CTradeContextState { + userdata: std::ptr::null_mut(), + callbacks: Callbacks::default(), + }); + let arc_ctx = Arc::new(CTradeContext { ctx, state }); + let weak_ctx = Arc::downgrade(&arc_ctx); + let ctx = Arc::into_raw(arc_ctx); + + tokio::spawn(async move { + while let Some(event) = receiver.recv().await { + let ctx = match weak_ctx.upgrade() { + Some(ctx) => ctx, + None => return, + }; + + let state = ctx.state.lock(); + match event { + PushEvent::OrderChanged(order_changed) => { + if let Some(callback) = state.callbacks.order_changed { + let order_changed_owned: CPushOrderChangedOwned = + order_changed.into(); + callback(Arc::as_ptr(&ctx), &order_changed_owned.to_ffi_type()); + } + } + } + } + }); + + Ok(CAsyncResult { + ctx: ctx as *const c_void, + error: std::ptr::null_mut(), + data: std::ptr::null_mut(), + length: 0, + userdata: userdata_pointer as *mut c_void, + }) + }, + ); +} + +#[no_mangle] +pub unsafe extern "C" fn lb_trade_context_retain(ctx: *const CTradeContext) { + Arc::increment_strong_count(ctx); +} + +#[no_mangle] +pub unsafe extern "C" fn lb_trade_context_release(ctx: *const CTradeContext) { + let _ = Arc::from_raw(ctx); +} + +#[no_mangle] +pub unsafe extern "C" fn lb_trade_context_set_userdata( + ctx: *const CTradeContext, + userdata: *mut c_void, +) { + (*ctx).state.lock().userdata = userdata; +} + +#[no_mangle] +pub unsafe extern "C" fn lb_trade_context_userdata(ctx: *const CTradeContext) -> *mut c_void { + (*ctx).state.lock().userdata +} + +/// Set order changed callback, after receiving the order changed event, it will +/// call back to this function. +#[no_mangle] +pub unsafe extern "C" fn lb_trade_context_set_on_order_changed( + ctx: *const CTradeContext, + callback: COnOrderChangedCallback, +) { + (*ctx).state.lock().callbacks.order_changed = Some(callback); +} + +#[no_mangle] +pub unsafe extern "C" fn lb_trade_context_subscribe( + ctx: *const CTradeContext, + topics: *const CTopicType, + num_topics: usize, + callback: CAsyncCallback, + userdata: *mut c_void, +) { + let ctx_inner = (*ctx).ctx.clone(); + let topics = std::slice::from_raw_parts(topics, num_topics) + .iter() + .copied() + .map(Into::into) + .collect::>(); + execute_async(callback, ctx, userdata, async move { + ctx_inner.subscribe(topics).await + }); +} + +#[no_mangle] +pub unsafe extern "C" fn lb_trade_context_unsubscribe( + ctx: *const CTradeContext, + topics: *const CTopicType, + num_topics: usize, + callback: CAsyncCallback, + userdata: *mut c_void, +) { + let ctx_inner = (*ctx).ctx.clone(); + let topics = std::slice::from_raw_parts(topics, num_topics) + .iter() + .copied() + .map(Into::into) + .collect::>(); + execute_async(callback, ctx, userdata, async move { + ctx_inner.unsubscribe(topics).await + }); +} + +/// Get history executions +/// +/// @param[in] opts Options for get histroy executions request (can null) +#[no_mangle] +pub unsafe extern "C" fn lb_trade_context_history_executions( + ctx: *const CTradeContext, + opts: *const CGetHistoryExecutionsOptions, + callback: CAsyncCallback, + userdata: *mut c_void, +) { + let ctx_inner = (*ctx).ctx.clone(); + let mut opts2 = GetHistoryExecutionsOptions::new(); + if !opts.is_null() { + if !(*opts).symbol.is_null() { + opts2 = opts2.symbol(cstr_to_rust((*opts).symbol)); + } + if !(*opts).start_at.is_null() { + opts2 = opts2.start_at( + OffsetDateTime::from_unix_timestamp(*(*opts).start_at).expect("invalid start at"), + ); + } + if !(*opts).end_at.is_null() { + opts2 = opts2.end_at( + OffsetDateTime::from_unix_timestamp(*(*opts).end_at).expect("invalid end at"), + ); + } + } + execute_async(callback, ctx, userdata, async move { + let rows: CVec = ctx_inner.history_executions(opts2).await?.into(); + Ok(rows) + }); +} + +/// Get today executions +/// +/// @param[in] opts Options for get today executions request (can null) +#[no_mangle] +pub unsafe extern "C" fn lb_trade_context_today_executions( + ctx: *const CTradeContext, + opts: *const CGetTodayExecutionsOptions, + callback: CAsyncCallback, + userdata: *mut c_void, +) { + let ctx_inner = (*ctx).ctx.clone(); + let mut opts2 = GetTodayExecutionsOptions::new(); + if !opts.is_null() { + if !(*opts).symbol.is_null() { + opts2 = opts2.symbol(cstr_to_rust((*opts).symbol)); + } + if !(*opts).order_id.is_null() { + opts2 = opts2.order_id(cstr_to_rust((*opts).order_id)); + } + } + execute_async(callback, ctx, userdata, async move { + let rows: CVec = ctx_inner.today_executions(opts2).await?.into(); + Ok(rows) + }); +} + +/// Get history orders +/// +/// @param[in] opts Options for get history orders request (can null) +#[no_mangle] +pub unsafe extern "C" fn lb_trade_context_history_orders( + ctx: *const CTradeContext, + opts: *const CGetHistoryOrdersOptions, + callback: CAsyncCallback, + userdata: *mut c_void, +) { + let ctx_inner = (*ctx).ctx.clone(); + let mut opts2 = GetHistoryOrdersOptions::new(); + if !opts.is_null() { + if !(*opts).symbol.is_null() { + opts2 = opts2.symbol(cstr_to_rust((*opts).symbol)); + } + if !(*opts).status.is_null() { + let status = std::slice::from_raw_parts((*opts).status, (*opts).num_status); + opts2 = opts2.status(status.iter().copied().map(Into::into)); + } + if !(*opts).side.is_null() { + opts2 = opts2.side((*(*opts).side).into()); + } + if !(*opts).market.is_null() { + opts2 = opts2.market((*(*opts).market).into()); + } + if !(*opts).start_at.is_null() { + opts2 = opts2.start_at( + OffsetDateTime::from_unix_timestamp(*(*opts).start_at).expect("invalid start at"), + ); + } + if !(*opts).end_at.is_null() { + opts2 = opts2.end_at( + OffsetDateTime::from_unix_timestamp(*(*opts).end_at).expect("invalid end at"), + ); + } + } + execute_async(callback, ctx, userdata, async move { + let rows: CVec = ctx_inner.history_orders(opts2).await?.into(); + Ok(rows) + }); +} + +/// Get today orders +/// +/// @param[in] opts Options for get today orders request (can null) +#[no_mangle] +pub unsafe extern "C" fn lb_trade_context_today_orders( + ctx: *const CTradeContext, + opts: *const CGetTodayOrdersOptions, + callback: CAsyncCallback, + userdata: *mut c_void, +) { + let ctx_inner = (*ctx).ctx.clone(); + let mut opts2 = GetTodayOrdersOptions::new(); + if !opts.is_null() { + if !(*opts).symbol.is_null() { + opts2 = opts2.symbol(cstr_to_rust((*opts).symbol)); + } + if !(*opts).status.is_null() { + let status = std::slice::from_raw_parts((*opts).status, (*opts).num_status); + opts2 = opts2.status(status.iter().copied().map(Into::into)); + } + if !(*opts).side.is_null() { + opts2 = opts2.side((*(*opts).side).into()); + } + if !(*opts).market.is_null() { + opts2 = opts2.market((*(*opts).market).into()); + } + if !(*opts).order_id.is_null() { + opts2 = opts2.order_id(cstr_to_rust((*opts).order_id)); + } + } + execute_async(callback, ctx, userdata, async move { + let rows: CVec = ctx_inner.today_orders(opts2).await?.into(); + Ok(rows) + }); +} + +/// Replace order +/// +/// @param[in] opts Options for replace order request +#[no_mangle] +pub unsafe extern "C" fn lb_trade_context_replace_order( + ctx: *const CTradeContext, + opts: *const CReplaceOrderOptions, + callback: CAsyncCallback, + userdata: *mut c_void, +) { + let ctx_inner = (*ctx).ctx.clone(); + let order_id = cstr_to_rust((*opts).order_id); + let quantity = (*opts).quantity; + let mut opts2 = ReplaceOrderOptions::new(order_id, quantity); + if !(*opts).price.is_null() { + opts2 = opts2.price((*(*opts).price).0); + } + if !(*opts).trigger_price.is_null() { + opts2 = opts2.trigger_price((*(*opts).trigger_price).0); + } + if !(*opts).limit_offset.is_null() { + opts2 = opts2.limit_offset((*(*opts).limit_offset).0); + } + if !(*opts).trailing_amount.is_null() { + opts2 = opts2.trailing_amount((*(*opts).trailing_amount).0); + } + if !(*opts).trailing_percent.is_null() { + opts2 = opts2.trailing_percent((*(*opts).trailing_percent).0); + } + if !(*opts).remark.is_null() { + opts2 = opts2.remark(cstr_to_rust((*opts).remark)); + } + execute_async(callback, ctx, userdata, async move { + ctx_inner.replace_order(opts2).await?; + Ok(()) + }); +} + +/// Submit order +/// +/// @param[in] opts Options for submit order request +#[no_mangle] +pub unsafe extern "C" fn lb_trade_context_submit_order( + ctx: *const CTradeContext, + opts: *const CSubmitOrderOptions, + callback: CAsyncCallback, + userdata: *mut c_void, +) { + let ctx_inner = (*ctx).ctx.clone(); + let symbol = cstr_to_rust((*opts).symbol); + let order_type = (*opts).order_type.into(); + let side = (*opts).side.into(); + let submitted_quantity = (*opts).submitted_quantity; + let time_in_force = (*opts).time_in_force.into(); + let mut opts2 = + SubmitOrderOptions::new(symbol, order_type, side, submitted_quantity, time_in_force); + if !(*opts).submitted_price.is_null() { + opts2 = opts2.submitted_price((*(*opts).submitted_price).0); + } + if !(*opts).trigger_price.is_null() { + opts2 = opts2.trigger_price((*(*opts).trigger_price).0); + } + if !(*opts).limit_offset.is_null() { + opts2 = opts2.limit_offset((*(*opts).limit_offset).0); + } + if !(*opts).trailing_amount.is_null() { + opts2 = opts2.trailing_amount((*(*opts).trailing_amount).0); + } + if !(*opts).trailing_percent.is_null() { + opts2 = opts2.trailing_percent((*(*opts).trailing_percent).0); + } + if !(*opts).expire_date.is_null() { + opts2 = opts2.expire_date((*(*opts).expire_date).into()); + } + if !(*opts).outside_rth.is_null() { + opts2 = opts2.outside_rth((*(*opts).outside_rth).into()); + } + if !(*opts).remark.is_null() { + opts2 = opts2.remark(cstr_to_rust((*opts).remark)); + } + execute_async(callback, ctx, userdata, async move { + let resp: CCow = CCow::new(ctx_inner.submit_order(opts2).await?); + Ok(resp) + }); +} + +/// Cancel order +#[no_mangle] +pub unsafe extern "C" fn lb_trade_context_cancel_order( + ctx: *const CTradeContext, + order_id: *const c_char, + callback: CAsyncCallback, + userdata: *mut c_void, +) { + let ctx_inner = (*ctx).ctx.clone(); + let order_id = cstr_to_rust(order_id); + execute_async(callback, ctx, userdata, async move { + ctx_inner.cancel_order(order_id).await?; + Ok(()) + }); +} + +/// Get account balance +#[no_mangle] +pub unsafe extern "C" fn lb_trade_context_account_balance( + ctx: *const CTradeContext, + callback: CAsyncCallback, + userdata: *mut c_void, +) { + let ctx_inner = (*ctx).ctx.clone(); + execute_async(callback, ctx, userdata, async move { + let rows: CVec = ctx_inner.account_balance().await?.into(); + Ok(rows) + }); +} + +/// Get cash flow +/// +/// @param[in] opts Options for get cash flow request +#[no_mangle] +pub unsafe extern "C" fn lb_trade_context_cash_flow( + ctx: *const CTradeContext, + opts: *const CGetCashFlowOptions, + callback: CAsyncCallback, + userdata: *mut c_void, +) { + let ctx_inner = (*ctx).ctx.clone(); + let start_at = + OffsetDateTime::from_unix_timestamp((*opts).start_at).expect("invalid start time"); + let end_at = OffsetDateTime::from_unix_timestamp((*opts).end_at).expect("invalid end time"); + let mut opts2 = GetCashFlowOptions::new(start_at, end_at); + if !(*opts).business_type.is_null() { + opts2 = opts2.business_type((*(*opts).business_type).into()); + } + if !(*opts).symbol.is_null() { + opts2 = opts2.symbol(cstr_to_rust((*opts).symbol)); + } + if !(*opts).page.is_null() { + opts2 = opts2.page(*(*opts).page); + } + if !(*opts).size.is_null() { + opts2 = opts2.size(*(*opts).size); + } + execute_async(callback, ctx, userdata, async move { + let rows: CVec = ctx_inner.cash_flow(opts2).await?.into(); + Ok(rows) + }); +} + +/// Get fund positions +/// +/// @param[in] opts Options for get fund positions request +#[no_mangle] +pub unsafe extern "C" fn lb_trade_context_fund_positions( + ctx: *const CTradeContext, + opts: *const CGetFundPositionsOptions, + callback: CAsyncCallback, + userdata: *mut c_void, +) { + let ctx_inner = (*ctx).ctx.clone(); + let mut opts2 = GetFundPositionsOptions::new(); + if !opts.is_null() && !(*opts).symbols.is_null() { + opts2 = opts2.symbols(cstr_array_to_rust((*opts).symbols, (*opts).num_symbols)); + } + execute_async(callback, ctx, userdata, async move { + let resp: CCow = + CCow::new(ctx_inner.fund_positions(opts2).await?); + Ok(resp) + }); +} + +/// Get stock positions +/// +/// @param[in] opts Options for get stock positions request +#[no_mangle] +pub unsafe extern "C" fn lb_trade_context_stock_positions( + ctx: *const CTradeContext, + opts: *const CGetStockPositionsOptions, + callback: CAsyncCallback, + userdata: *mut c_void, +) { + let ctx_inner = (*ctx).ctx.clone(); + let mut opts2 = GetStockPositionsOptions::new(); + if !opts.is_null() && !(*opts).symbols.is_null() { + opts2 = opts2.symbols(cstr_array_to_rust((*opts).symbols, (*opts).num_symbols)); + } + execute_async(callback, ctx, userdata, async move { + let resp: CCow = + CCow::new(ctx_inner.stock_positions(opts2).await?); + Ok(resp) + }); +} diff --git a/c/src/trade_context/enum_types.rs b/c/src/trade_context/enum_types.rs new file mode 100644 index 0000000000..d35e3a07f4 --- /dev/null +++ b/c/src/trade_context/enum_types.rs @@ -0,0 +1,252 @@ +use longbridge_c_macros::CEnum; + +/// Topic type +#[derive(Debug, Copy, Clone, Eq, PartialEq, CEnum)] +#[c(remote = "longbridge::trade::TopicType")] +#[allow(clippy::enum_variant_names)] +#[repr(C)] +pub enum CTopicType { + /// Trading + #[c(remote = "Private")] + TopicPrivate, +} + +/// Order side +#[derive(Debug, Copy, Clone, Eq, PartialEq, CEnum)] +#[c(remote = "longbridge::trade::OrderSide")] +#[allow(clippy::enum_variant_names)] +#[repr(C)] +pub enum COrderSide { + /// Unknown + #[c(remote = "Unknown")] + OrderSideUnknown, + /// Unknown + #[c(remote = "Buy")] + OrderSideBuy, + /// Unknown + #[c(remote = "Sell")] + OrderSideSell, +} + +/// Order type +#[derive(Debug, Copy, Clone, Eq, PartialEq, CEnum)] +#[c(remote = "longbridge::trade::OrderType")] +#[allow(clippy::enum_variant_names)] +#[repr(C)] +pub enum COrderType { + /// Unknown + #[c(remote = "Unknown")] + OrderTypeUnknown, + /// Limit Order + #[c(remote = "LO")] + OrderTypeLO, + /// Enhanced Limit Order + #[c(remote = "ELO")] + OrderTypeELO, + /// Market Order + #[c(remote = "MO")] + OrderTypeMO, + /// At-auction Order + #[c(remote = "AO")] + OrderTypeAO, + /// At-auction Limit Order + #[c(remote = "ALO")] + OrderTypeALO, + /// Odd Lots + #[c(remote = "ODD")] + OrderTypeODD, + /// Limit If Touched + #[c(remote = "LIT")] + OrderTypeLIT, + /// Market If Touched + #[c(remote = "MIT")] + OrderTypeMIT, + /// Trailing Limit If Touched (Trailing Amount) + #[c(remote = "TSLPAMT")] + OrderTypeTSLPAMT, + /// Trailing Limit If Touched (Trailing Percent) + #[c(remote = "TSLPPCT")] + OrderTypeTSLPPCT, + /// Trailing Market If Touched (Trailing Amount) + #[c(remote = "TSMAMT")] + OrderTypeTSMAMT, + /// Trailing Market If Touched (Trailing Percent) + #[c(remote = "TSMPCT")] + OrderTypeTSMPCT, +} + +/// Order status +#[derive(Debug, Copy, Clone, Eq, PartialEq, CEnum)] +#[c(remote = "longbridge::trade::OrderStatus")] +#[allow(clippy::enum_variant_names)] +#[repr(C)] +pub enum COrderStatus { + /// Unknown + #[c(remote = "Unknown")] + OrderStatusUnknown, + /// Not reported + #[c(remote = "NotReported")] + OrderStatusNotReported, + /// Not reported (Replaced Order) + #[c(remote = "ReplacedNotReported")] + OrderStatusReplacedNotReported, + /// Not reported (Protected Order) + #[c(remote = "ProtectedNotReported")] + OrderStatusProtectedNotReported, + /// Not reported (Conditional Order) + #[c(remote = "VarietiesNotReported")] + OrderStatusVarietiesNotReported, + /// Filled + #[c(remote = "Filled")] + OrderStatusFilled, + /// Wait To New + #[c(remote = "WaitToNew")] + OrderStatusWaitToNew, + /// New + #[c(remote = "New")] + OrderStatusNew, + /// Wait To Replace + #[c(remote = "WaitToReplace")] + OrderStatusWaitToReplace, + /// Pending Replace + #[c(remote = "PendingReplace")] + OrderStatusPendingReplace, + /// Replaced + #[c(remote = "Replaced")] + OrderStatusReplaced, + /// Partial Filled + #[c(remote = "PartialFilled")] + OrderStatusPartialFilled, + /// Wait To Cancel + #[c(remote = "WaitToCancel")] + OrderStatusWaitToCancel, + /// Pending Cancel + #[c(remote = "PendingCancel")] + OrderStatusPendingCancel, + /// Rejected + #[c(remote = "Rejected")] + OrderStatusRejected, + /// Canceled + #[c(remote = "Canceled")] + OrderStatusCanceled, + /// Expired + #[c(remote = "Expired")] + OrderStatusExpired, + /// Partial Withdrawal + #[c(remote = "PartialWithdrawal")] + OrderStatusPartialWithdrawal, +} + +/// Order tag +#[derive(Debug, Copy, Clone, Eq, PartialEq, CEnum)] +#[c(remote = "longbridge::trade::OrderTag")] +#[allow(clippy::enum_variant_names)] +#[repr(C)] +pub enum COrderTag { + /// Unknown + #[c(remote = "Unknown")] + OrderTagUnknown, + /// Normal Order + #[c(remote = "Normal")] + OrderTagNormal, + /// Long term Order + #[c(remote = "LongTerm")] + OrderTagLongTerm, + /// Grey Order + #[c(remote = "Grey")] + OrderTagGrey, +} + +/// Order tag +#[derive(Debug, Copy, Clone, Eq, PartialEq, CEnum)] +#[c(remote = "longbridge::trade::TriggerStatus")] +#[allow(clippy::enum_variant_names)] +#[repr(C)] +pub enum CTriggerStatus { + /// Unknown + #[c(remote = "Unknown")] + TriggerStatusUnknown, + /// Deactive + #[c(remote = "Deactive")] + TriggerStatusDeactive, + /// Active + #[c(remote = "Active")] + TriggerStatusActive, + /// Released + #[c(remote = "Released")] + TriggerStatusReleased, +} + +/// Enable or disable outside regular trading hours +#[derive(Debug, Copy, Clone, Eq, PartialEq, CEnum)] +#[c(remote = "longbridge::trade::OutsideRTH")] +#[allow(clippy::enum_variant_names)] +#[repr(C)] +pub enum COutsideRTH { + /// Unknown + #[c(remote = "Unknown")] + OutsideRTHUnknown, + /// Regular trading hour only + #[c(remote = "RTHOnly")] + OutsideRTHOnly, + /// Any time + #[c(remote = "AnyTime")] + OutsideRTHAnyTime, +} + +/// Time in force Type +#[derive(Debug, Copy, Clone, Eq, PartialEq, CEnum)] +#[c(remote = "longbridge::trade::TimeInForceType")] +#[allow(clippy::enum_variant_names)] +#[repr(C)] +pub enum CTimeInForceType { + /// Unknown + #[c(remote = "Unknown")] + TimeInForceUnknown, + /// Day Order + #[c(remote = "Day")] + TimeInForceDay, + /// Good Til Canceled Order + #[c(remote = "GoodTilCanceled")] + TimeInForceGoodTilCanceled, + /// Good Til Date Order + #[c(remote = "GoodTilDate")] + TimeInForceGoodTilDate, +} + +/// Cash flow direction +#[derive(Debug, Copy, Clone, Eq, PartialEq, CEnum)] +#[c(remote = "longbridge::trade::CashFlowDirection ")] +#[allow(clippy::enum_variant_names)] +#[repr(C)] +pub enum CCashFlowDirection { + /// Unknown + #[c(remote = "Unknown")] + CashFlowDirectionUnknown, + /// Out + #[c(remote = "Out")] + CashFlowDirectionOutside, + /// In + #[c(remote = "In")] + CashFlowDirectionIn, +} + +/// Balance type +#[derive(Debug, Copy, Clone, Eq, PartialEq, CEnum)] +#[c(remote = "longbridge::trade::BalanceType")] +#[allow(clippy::enum_variant_names)] +#[repr(C)] +pub enum CBalanceType { + /// Unknown + #[c(remote = "Unknown")] + BalanceTypeUnknown, + /// Unknown + #[c(remote = "Cash")] + BalanceTypeCash, + /// Unknown + #[c(remote = "Stock")] + BalanceTypeStock, + /// Unknown + #[c(remote = "Fund")] + BalanceTypeFund, +} diff --git a/c/src/trade_context/mod.rs b/c/src/trade_context/mod.rs new file mode 100644 index 0000000000..127deedfcc --- /dev/null +++ b/c/src/trade_context/mod.rs @@ -0,0 +1,3 @@ +mod context; +mod enum_types; +mod types; diff --git a/c/src/trade_context/types.rs b/c/src/trade_context/types.rs new file mode 100644 index 0000000000..eeed0d4085 --- /dev/null +++ b/c/src/trade_context/types.rs @@ -0,0 +1,1282 @@ +use std::os::raw::c_char; + +use longbridge::{ + trade::{ + AccountBalance, BalanceType, CashFlow, CashFlowDirection, CashInfo, Execution, + FundPosition, FundPositionChannel, FundPositionsResponse, Order, OrderSide, OrderStatus, + OrderTag, OrderType, PushOrderChanged, StockPosition, StockPositionChannel, + StockPositionsResponse, SubmitOrderResponse, TimeInForceType, + }, + Market, +}; +use time::OffsetDateTime; + +use crate::{ + trade_context::enum_types::{ + CBalanceType, CCashFlowDirection, COrderSide, COrderStatus, COrderTag, COrderType, + COutsideRTH, CTimeInForceType, CTriggerStatus, + }, + types::{CDate, CDecimal, CMarket, CString, CVec, ToFFI}, +}; + +/// Order changed message +#[repr(C)] +pub struct CPushOrderChanged { + /// Order side + pub side: COrderSide, + /// Stock name + pub stock_name: *const c_char, + /// Submitted quantity + pub submitted_quantity: i64, + /// Order symbol + pub symbol: *const c_char, + /// Order type + pub order_type: COrderType, + /// Submitted price + pub submitted_price: *const CDecimal, + /// Executed quantity + pub executed_quantity: i64, + /// Executed price (maybe null) + pub executed_price: *const CDecimal, + /// Order ID + pub order_id: *const c_char, + /// Currency + pub currency: *const c_char, + /// Order status + pub status: COrderStatus, + /// Submitted time + pub submitted_at: i64, + /// Last updated time + pub updated_at: i64, + /// Order trigger price (maybe null) + pub trigger_price: *const CDecimal, + /// Rejected message or remark + pub msg: *const c_char, + /// Order tag + pub tag: COrderTag, + /// Conditional order trigger status (maybe null) + pub trigger_status: *const CTriggerStatus, + /// Conditional order trigger time (maybe null) + pub trigger_at: *const i64, + /// Trailing amount (maybe null) + pub trailing_amount: *const CDecimal, + /// Trailing percent (maybe null) + pub trailing_percent: *const CDecimal, + /// Limit offset amount (maybe null) + pub limit_offset: *const CDecimal, + /// Account no + pub account_no: *const c_char, +} + +pub struct CPushOrderChangedOwned { + side: OrderSide, + stock_name: CString, + submitted_quantity: i64, + symbol: CString, + order_type: OrderType, + submitted_price: CDecimal, + executed_quantity: i64, + executed_price: Option, + order_id: CString, + currency: CString, + status: OrderStatus, + submitted_at: i64, + updated_at: i64, + trigger_price: Option, + msg: CString, + tag: OrderTag, + trigger_status: Option, + trigger_at: Option, + trailing_amount: Option, + trailing_percent: Option, + limit_offset: Option, + account_no: CString, +} + +impl From for CPushOrderChangedOwned { + fn from(order_changed: PushOrderChanged) -> Self { + let PushOrderChanged { + side, + stock_name, + submitted_quantity, + symbol, + order_type, + submitted_price, + executed_quantity, + executed_price, + order_id, + currency, + status, + submitted_at, + updated_at, + trigger_price, + msg, + tag, + trigger_status, + trigger_at, + trailing_amount, + trailing_percent, + limit_offset, + account_no, + } = order_changed; + CPushOrderChangedOwned { + side, + stock_name: stock_name.into(), + submitted_quantity, + symbol: symbol.into(), + order_type, + submitted_price: submitted_price.into(), + executed_quantity, + executed_price: executed_price.map(Into::into), + order_id: order_id.into(), + currency: currency.into(), + status, + submitted_at: submitted_at.unix_timestamp(), + updated_at: updated_at.unix_timestamp(), + trigger_price: trigger_price.map(Into::into), + msg: msg.into(), + tag, + trigger_status: trigger_status.map(Into::into), + trigger_at: trigger_at.map(OffsetDateTime::unix_timestamp), + trailing_amount: trailing_amount.map(Into::into), + trailing_percent: trailing_percent.map(Into::into), + limit_offset: limit_offset.map(Into::into), + account_no: account_no.into(), + } + } +} + +impl ToFFI for CPushOrderChangedOwned { + type FFIType = CPushOrderChanged; + + fn to_ffi_type(&self) -> Self::FFIType { + let CPushOrderChangedOwned { + side, + stock_name, + submitted_quantity, + symbol, + order_type, + submitted_price, + executed_quantity, + executed_price, + order_id, + currency, + status, + submitted_at, + updated_at, + trigger_price, + msg, + tag, + trigger_status, + trigger_at, + trailing_amount, + trailing_percent, + limit_offset, + account_no, + } = self; + CPushOrderChanged { + side: (*side).into(), + stock_name: stock_name.to_ffi_type(), + submitted_quantity: *submitted_quantity, + symbol: symbol.to_ffi_type(), + order_type: (*order_type).into(), + submitted_price: submitted_price.to_ffi_type(), + executed_quantity: *executed_quantity, + executed_price: executed_price + .as_ref() + .map(ToFFI::to_ffi_type) + .unwrap_or(std::ptr::null()), + order_id: order_id.to_ffi_type(), + currency: currency.to_ffi_type(), + status: (*status).into(), + submitted_at: *submitted_at, + updated_at: *updated_at, + trigger_price: trigger_price + .as_ref() + .map(ToFFI::to_ffi_type) + .unwrap_or(std::ptr::null()), + msg: msg.to_ffi_type(), + tag: (*tag).into(), + trigger_status: trigger_status + .as_ref() + .map(|value| value as *const CTriggerStatus) + .unwrap_or(std::ptr::null()), + trigger_at: trigger_at + .as_ref() + .map(|value| value as *const i64) + .unwrap_or(std::ptr::null()), + trailing_amount: trailing_amount + .as_ref() + .map(ToFFI::to_ffi_type) + .unwrap_or(std::ptr::null()), + trailing_percent: trailing_percent + .as_ref() + .map(ToFFI::to_ffi_type) + .unwrap_or(std::ptr::null()), + limit_offset: limit_offset + .as_ref() + .map(ToFFI::to_ffi_type) + .unwrap_or(std::ptr::null()), + account_no: account_no.to_ffi_type(), + } + } +} + +/// Execution +#[repr(C)] +pub struct CExecution { + /// Order ID + pub order_id: *const c_char, + /// Execution ID + pub trade_id: *const c_char, + /// Security code + pub symbol: *const c_char, + /// Trade done time + pub trade_done_at: i64, + /// Executed quantity + pub quantity: i64, + /// Executed price + pub price: *const CDecimal, +} + +#[derive(Debug)] +pub(crate) struct CExecutionOwned { + order_id: CString, + trade_id: CString, + symbol: CString, + trade_done_at: i64, + quantity: i64, + price: CDecimal, +} + +impl From for CExecutionOwned { + fn from(execution: Execution) -> Self { + let Execution { + order_id, + trade_id, + symbol, + trade_done_at, + quantity, + price, + } = execution; + CExecutionOwned { + order_id: order_id.into(), + trade_id: trade_id.into(), + symbol: symbol.into(), + trade_done_at: trade_done_at.unix_timestamp(), + quantity, + price: price.into(), + } + } +} + +impl ToFFI for CExecutionOwned { + type FFIType = CExecution; + + fn to_ffi_type(&self) -> Self::FFIType { + let CExecutionOwned { + order_id, + trade_id, + symbol, + trade_done_at, + quantity, + price, + } = self; + CExecution { + order_id: order_id.to_ffi_type(), + trade_id: trade_id.to_ffi_type(), + symbol: symbol.to_ffi_type(), + trade_done_at: *trade_done_at, + quantity: *quantity, + price: price.to_ffi_type(), + } + } +} + +/// Options for get histroy executions request +#[repr(C)] +pub struct CGetHistoryExecutionsOptions { + /// Start time (can null) + pub start_at: *const i64, + /// End time (can null) + pub end_at: *const i64, + /// Security code (can null) + pub symbol: *const c_char, +} + +/// Options for get today executions request +#[repr(C)] +pub struct CGetTodayExecutionsOptions { + /// Security code (can null) + pub symbol: *const c_char, + /// Order id (can null) + pub order_id: *const c_char, +} + +/// Order +#[repr(C)] +pub struct COrder { + /// Order ID + pub order_id: *const c_char, + /// Order status + pub status: COrderStatus, + /// Stock name + pub stock_name: *const c_char, + /// Submitted quantity + pub quantity: i64, + /// Executed quantity + pub executed_quantity: i64, + /// Submitted price (maybe null) + pub price: *const CDecimal, + /// Executed price (maybe null) + pub executed_price: *const CDecimal, + /// Submitted time + pub submitted_at: i64, + /// Order side + pub side: COrderSide, + /// Security code + pub symbol: *const c_char, + /// Order type + pub order_type: COrderType, + /// Last done (maybe null) + pub last_done: *const CDecimal, + /// `LIT` / `MIT` Order Trigger Price (maybe null) + pub trigger_price: *const CDecimal, + /// Rejected Message or remark + pub msg: *const c_char, + /// Order tag + pub tag: COrderTag, + /// Time in force type + pub time_in_force: CTimeInForceType, + /// Long term order expire date (maybe null) + pub expire_date: *const CDate, + /// Last updated time (maybe null) + pub updated_at: *const i64, + /// Conditional order trigger time (maybe null) + pub trigger_at: *const i64, + /// `TSMAMT` / `TSLPAMT` order trailing amount (maybe null) + pub trailing_amount: *const CDecimal, + /// `TSMPCT` / `TSLPPCT` order trailing percent (maybe null) + pub trailing_percent: *const CDecimal, + /// `TSLPAMT` / `TSLPPCT` order limit offset amount (maybe null) + pub limit_offset: *const CDecimal, + /// Conditional order trigger status (maybe null) + pub trigger_status: *const CTriggerStatus, + /// Currency + pub currency: *const c_char, + /// Enable or disable outside regular trading hours (maybe null) + pub outside_rth: *const COutsideRTH, +} + +#[derive(Debug)] +pub(crate) struct COrderOwned { + order_id: CString, + status: OrderStatus, + stock_name: CString, + quantity: i64, + executed_quantity: i64, + price: Option, + executed_price: Option, + submitted_at: OffsetDateTime, + side: OrderSide, + symbol: CString, + order_type: OrderType, + last_done: Option, + trigger_price: Option, + msg: CString, + tag: OrderTag, + time_in_force: TimeInForceType, + expire_date: Option, + updated_at: Option, + trigger_at: Option, + trailing_amount: Option, + trailing_percent: Option, + limit_offset: Option, + trigger_status: Option, + currency: CString, + outside_rth: Option, +} + +impl From for COrderOwned { + fn from(order: Order) -> Self { + let Order { + order_id, + status, + stock_name, + quantity, + executed_quantity, + price, + executed_price, + submitted_at, + side, + symbol, + order_type, + last_done, + trigger_price, + msg, + tag, + time_in_force, + expire_date, + updated_at, + trigger_at, + trailing_amount, + trailing_percent, + limit_offset, + trigger_status, + currency, + outside_rth, + } = order; + COrderOwned { + order_id: order_id.into(), + status, + stock_name: stock_name.into(), + quantity, + executed_quantity, + price: price.map(Into::into), + executed_price: executed_price.map(Into::into), + submitted_at, + side, + symbol: symbol.into(), + order_type, + last_done: last_done.map(Into::into), + trigger_price: trigger_price.map(Into::into), + msg: msg.into(), + tag, + time_in_force, + expire_date: expire_date.map(Into::into), + updated_at: updated_at.map(OffsetDateTime::unix_timestamp), + trigger_at: trigger_at.map(OffsetDateTime::unix_timestamp), + trailing_amount: trailing_amount.map(Into::into), + trailing_percent: trailing_percent.map(Into::into), + limit_offset: limit_offset.map(Into::into), + trigger_status: trigger_status.map(Into::into), + currency: currency.into(), + outside_rth: outside_rth.map(Into::into), + } + } +} + +impl ToFFI for COrderOwned { + type FFIType = COrder; + + fn to_ffi_type(&self) -> Self::FFIType { + let COrderOwned { + order_id, + status, + stock_name, + quantity, + executed_quantity, + price, + executed_price, + submitted_at, + side, + symbol, + order_type, + last_done, + trigger_price, + msg, + tag, + time_in_force, + expire_date, + updated_at, + trigger_at, + trailing_amount, + trailing_percent, + limit_offset, + trigger_status, + currency, + outside_rth, + } = self; + COrder { + order_id: order_id.to_ffi_type(), + status: (*status).into(), + stock_name: stock_name.to_ffi_type(), + quantity: *quantity, + executed_quantity: *executed_quantity, + price: price + .as_ref() + .map(ToFFI::to_ffi_type) + .unwrap_or(std::ptr::null()), + executed_price: executed_price + .as_ref() + .map(ToFFI::to_ffi_type) + .unwrap_or(std::ptr::null()), + submitted_at: submitted_at.unix_timestamp(), + side: (*side).into(), + symbol: symbol.to_ffi_type(), + order_type: (*order_type).into(), + last_done: last_done + .as_ref() + .map(ToFFI::to_ffi_type) + .unwrap_or(std::ptr::null()), + trigger_price: trigger_price + .as_ref() + .map(ToFFI::to_ffi_type) + .unwrap_or(std::ptr::null()), + msg: msg.to_ffi_type(), + tag: (*tag).into(), + time_in_force: (*time_in_force).into(), + expire_date: expire_date + .as_ref() + .map(|value| value as *const CDate) + .unwrap_or(std::ptr::null()), + updated_at: updated_at + .as_ref() + .map(|value| value as *const i64) + .unwrap_or(std::ptr::null()), + trigger_at: trigger_at + .as_ref() + .map(|value| value as *const i64) + .unwrap_or(std::ptr::null()), + trailing_amount: trailing_amount + .as_ref() + .map(ToFFI::to_ffi_type) + .unwrap_or(std::ptr::null()), + trailing_percent: trailing_percent + .as_ref() + .map(ToFFI::to_ffi_type) + .unwrap_or(std::ptr::null()), + limit_offset: limit_offset + .as_ref() + .map(ToFFI::to_ffi_type) + .unwrap_or(std::ptr::null()), + trigger_status: trigger_status + .as_ref() + .map(|value| value as *const CTriggerStatus) + .unwrap_or(std::ptr::null()), + currency: currency.to_ffi_type(), + outside_rth: outside_rth + .as_ref() + .map(|value| value as *const COutsideRTH) + .unwrap_or(std::ptr::null()), + } + } +} + +/// Options for get history orders request +#[derive(Debug)] +#[repr(C)] +pub struct CGetHistoryOrdersOptions { + /// Security symbol (can null) + pub symbol: *const c_char, + /// Order status (can null) + pub status: *const COrderStatus, + /// Number of order status + pub num_status: usize, + /// Order side (can null) + pub side: *const COrderSide, + /// Market (can null) + pub market: *const CMarket, + /// Start time (can null) + pub start_at: *const i64, + /// End time (can null) + pub end_at: *const i64, +} + +/// Options for get today orders request +#[derive(Debug)] +#[repr(C)] +pub struct CGetTodayOrdersOptions { + /// Security symbol (can null) + pub symbol: *const c_char, + /// Order status (can null) + pub status: *const COrderStatus, + /// Number of order status + pub num_status: usize, + /// Order side (can null) + pub side: *const COrderSide, + /// Market (can null) + pub market: *const CMarket, + /// Order id (can null) + pub order_id: *const c_char, +} + +/// Options for replace order request +#[derive(Debug)] +#[repr(C)] +pub struct CReplaceOrderOptions { + /// Order ID + pub order_id: *const c_char, + /// Quantity + pub quantity: i64, + /// Price (can null) + pub price: *const CDecimal, + /// Trigger price (can null) + pub trigger_price: *const CDecimal, + /// Limit offset (can null) + pub limit_offset: *const CDecimal, + /// Trailing amount (can null) + pub trailing_amount: *const CDecimal, + /// Trailing percent (can null) + pub trailing_percent: *const CDecimal, + /// Remark (can null) + pub remark: *const c_char, +} + +/// Options for submit order request +#[derive(Debug)] +#[repr(C)] +pub struct CSubmitOrderOptions { + /// Security symbol + pub symbol: *const c_char, + /// Order type + pub order_type: COrderType, + /// Order side + pub side: COrderSide, + /// Submitted price + pub submitted_quantity: i64, + /// Time in force type + pub time_in_force: CTimeInForceType, + /// Submitted price (can null) + pub submitted_price: *const CDecimal, + /// Trigger price (`LIT` / `MIT` Required) (can null) + pub trigger_price: *const CDecimal, + /// Limit offset amount (`TSLPAMT` / `TSLPPCT` Required) (can null) + pub limit_offset: *const CDecimal, + /// Trailing amount (`TSLPAMT` / `TSMAMT` Required) (can null) + pub trailing_amount: *const CDecimal, + /// Trailing percent (`TSLPPCT` / `TSMAPCT` Required) (can null) + pub trailing_percent: *const CDecimal, + /// Long term order expire date (Required when `time_in_force` is + /// `GoodTilDate`) (can null) + pub expire_date: *const CDate, + /// Enable or disable outside regular trading hours (can null) + pub outside_rth: *const COutsideRTH, + /// Remark (Maximum 64 characters) (can null) + pub remark: *const c_char, +} + +#[repr(C)] +pub struct CSubmitOrderResponse { + pub order_id: *const c_char, +} + +#[derive(Debug)] +pub(crate) struct CSubmitOrderResponseOwned { + order_id: CString, +} + +impl From for CSubmitOrderResponseOwned { + fn from(resp: SubmitOrderResponse) -> Self { + CSubmitOrderResponseOwned { + order_id: resp.order_id.into(), + } + } +} + +impl ToFFI for CSubmitOrderResponseOwned { + type FFIType = CSubmitOrderResponse; + + fn to_ffi_type(&self) -> Self::FFIType { + CSubmitOrderResponse { + order_id: self.order_id.to_ffi_type(), + } + } +} + +/// Account balance +#[repr(C)] +pub struct CCashInfo { + /// Withdraw cash + pub withdraw_cash: *const CDecimal, + /// Available cash + pub available_cash: *const CDecimal, + /// Frozen cash + pub frozen_cash: *const CDecimal, + /// Cash to be settled + pub settling_cash: *const CDecimal, + /// Currency + pub currency: *const c_char, +} + +#[derive(Debug)] +pub(crate) struct CCashInfoOwned { + withdraw_cash: CDecimal, + available_cash: CDecimal, + frozen_cash: CDecimal, + settling_cash: CDecimal, + currency: CString, +} + +impl From for CCashInfoOwned { + fn from(info: CashInfo) -> Self { + let CashInfo { + withdraw_cash, + available_cash, + frozen_cash, + settling_cash, + currency, + } = info; + Self { + withdraw_cash: withdraw_cash.into(), + available_cash: available_cash.into(), + frozen_cash: frozen_cash.into(), + settling_cash: settling_cash.into(), + currency: currency.into(), + } + } +} + +impl ToFFI for CCashInfoOwned { + type FFIType = CCashInfo; + + fn to_ffi_type(&self) -> Self::FFIType { + let CCashInfoOwned { + withdraw_cash, + available_cash, + frozen_cash, + settling_cash, + currency, + } = self; + CCashInfo { + withdraw_cash: withdraw_cash.to_ffi_type(), + available_cash: available_cash.to_ffi_type(), + frozen_cash: frozen_cash.to_ffi_type(), + settling_cash: settling_cash.to_ffi_type(), + currency: currency.to_ffi_type(), + } + } +} + +/// Account balance +#[repr(C)] +pub struct CAccountBalance { + /// Total cash + pub total_cash: *const CDecimal, + /// Maximum financing amount + pub max_finance_amount: *const CDecimal, + /// Remaining financing amount + pub remaining_finance_amount: *const CDecimal, + /// Risk control level + pub risk_level: i32, + /// Margin call + pub margin_call: *const CDecimal, + /// Currency + pub currency: *const c_char, + /// Cash details + pub cash_infos: *const CCashInfo, + /// Number of cash details + pub num_cash_infos: usize, + /// Net assets + pub net_assets: *const CDecimal, + /// Initial margin + pub init_margin: *const CDecimal, + /// Maintenance margin + pub maintenance_margin: *const CDecimal, +} + +#[derive(Debug)] +pub(crate) struct CAccountBalanceOwned { + total_cash: CDecimal, + max_finance_amount: CDecimal, + remaining_finance_amount: CDecimal, + risk_level: i32, + margin_call: CDecimal, + currency: CString, + cash_infos: CVec, + net_assets: CDecimal, + init_margin: CDecimal, + maintenance_margin: CDecimal, +} + +impl From for CAccountBalanceOwned { + fn from(info: AccountBalance) -> Self { + let AccountBalance { + total_cash, + max_finance_amount, + remaining_finance_amount, + risk_level, + margin_call, + currency, + cash_infos, + net_assets, + init_margin, + maintenance_margin, + } = info; + Self { + total_cash: total_cash.into(), + max_finance_amount: max_finance_amount.into(), + remaining_finance_amount: remaining_finance_amount.into(), + risk_level, + margin_call: margin_call.into(), + currency: currency.into(), + cash_infos: cash_infos.into(), + net_assets: net_assets.into(), + init_margin: init_margin.into(), + maintenance_margin: maintenance_margin.into(), + } + } +} + +impl ToFFI for CAccountBalanceOwned { + type FFIType = CAccountBalance; + + fn to_ffi_type(&self) -> Self::FFIType { + let CAccountBalanceOwned { + total_cash, + max_finance_amount, + remaining_finance_amount, + risk_level, + margin_call, + currency, + cash_infos, + net_assets, + init_margin, + maintenance_margin, + } = self; + CAccountBalance { + total_cash: total_cash.to_ffi_type(), + max_finance_amount: max_finance_amount.to_ffi_type(), + remaining_finance_amount: remaining_finance_amount.to_ffi_type(), + risk_level: *risk_level, + margin_call: margin_call.to_ffi_type(), + currency: currency.to_ffi_type(), + cash_infos: cash_infos.to_ffi_type(), + num_cash_infos: cash_infos.len(), + net_assets: net_assets.to_ffi_type(), + init_margin: init_margin.to_ffi_type(), + maintenance_margin: maintenance_margin.to_ffi_type(), + } + } +} + +/// Cash flow +#[repr(C)] +pub struct CCashFlow { + /// Cash flow name + pub transaction_flow_name: *const c_char, + /// Outflow direction + pub direction: CCashFlowDirection, + /// Balance type + pub business_type: CBalanceType, + /// Cash amount + pub balance: *const CDecimal, + /// Cash currency + pub currency: *const c_char, + /// Business time + pub business_time: i64, + /// Associated Stock code information (maybe null) + pub symbol: *const c_char, + /// Cash flow description + pub description: *const c_char, +} + +/// Cash flow +#[repr(C)] +pub(crate) struct CCashFlowOwned { + transaction_flow_name: CString, + direction: CashFlowDirection, + business_type: BalanceType, + balance: CDecimal, + currency: CString, + business_time: i64, + symbol: Option, + description: CString, +} + +impl From for CCashFlowOwned { + fn from(cash_flow: CashFlow) -> Self { + let CashFlow { + transaction_flow_name, + direction, + business_type, + balance, + currency, + business_time, + symbol, + description, + } = cash_flow; + CCashFlowOwned { + transaction_flow_name: transaction_flow_name.into(), + direction, + business_type, + balance: balance.into(), + currency: currency.into(), + business_time: business_time.unix_timestamp(), + symbol: symbol.map(Into::into), + description: description.into(), + } + } +} + +impl ToFFI for CCashFlowOwned { + type FFIType = CCashFlow; + + fn to_ffi_type(&self) -> Self::FFIType { + let CCashFlowOwned { + transaction_flow_name, + direction, + business_type, + balance, + currency, + business_time, + symbol, + description, + } = self; + CCashFlow { + transaction_flow_name: transaction_flow_name.to_ffi_type(), + direction: (*direction).into(), + business_type: (*business_type).into(), + balance: balance.to_ffi_type(), + currency: currency.to_ffi_type(), + business_time: *business_time, + symbol: match symbol { + Some(symbol) => symbol.to_ffi_type(), + None => std::ptr::null(), + }, + description: description.to_ffi_type(), + } + } +} + +/// Options for get cash flow request +#[repr(C)] +pub struct CGetCashFlowOptions { + /// Start time + pub start_at: i64, + /// End time + pub end_at: i64, + /// Business type (can null) + pub business_type: *const CBalanceType, + /// Security symbol + pub symbol: *const c_char, + /// Page number + pub page: *const usize, + /// Page size + pub size: *const usize, +} + +/// Options for get fund positions request +#[repr(C)] +pub struct CGetFundPositionsOptions { + /// Fund symbols (can null) + pub symbols: *const *const c_char, + /// Number of fund symbols + pub num_symbols: usize, +} + +/// Fund positions response +#[repr(C)] +pub struct CFundPositionsResponse { + pub channels: *const CFundPositionChannel, + pub num_channels: usize, +} + +pub(crate) struct CFundPositionsResponseOwned { + pub channels: CVec, +} + +impl From for CFundPositionsResponseOwned { + fn from(resp: FundPositionsResponse) -> Self { + let FundPositionsResponse { channels } = resp; + Self { + channels: channels.into(), + } + } +} + +impl ToFFI for CFundPositionsResponseOwned { + type FFIType = CFundPositionsResponse; + + fn to_ffi_type(&self) -> Self::FFIType { + let CFundPositionsResponseOwned { channels } = self; + CFundPositionsResponse { + channels: channels.to_ffi_type(), + num_channels: channels.len(), + } + } +} + +/// Fund position channel +#[repr(C)] +pub struct CFundPositionChannel { + /// Account type + pub account_channel: *const c_char, + /// Fund positions + pub positions: *const CFundPosition, + /// Number of fund positions + pub num_positions: usize, +} + +pub(crate) struct CFundPositionChannelOwned { + account_channel: CString, + positions: CVec, +} + +impl From for CFundPositionChannelOwned { + fn from(channel: FundPositionChannel) -> Self { + let FundPositionChannel { + account_channel, + positions, + } = channel; + CFundPositionChannelOwned { + account_channel: account_channel.into(), + positions: positions.into(), + } + } +} + +impl ToFFI for CFundPositionChannelOwned { + type FFIType = CFundPositionChannel; + + fn to_ffi_type(&self) -> Self::FFIType { + let CFundPositionChannelOwned { + account_channel, + positions, + } = self; + CFundPositionChannel { + account_channel: account_channel.to_ffi_type(), + positions: positions.to_ffi_type(), + num_positions: positions.len(), + } + } +} + +/// Fund position +#[repr(C)] +pub struct CFundPosition { + /// Fund ISIN code + pub symbol: *const c_char, + /// Current equity + pub current_net_asset_value: *const CDecimal, + /// Current equity time + pub net_asset_value_day: i64, + /// Fund name + pub symbol_name: *const c_char, + /// Currency + pub currency: *const c_char, + /// Net cost + pub cost_net_asset_value: *const CDecimal, + /// Holding units + pub holding_units: *const CDecimal, +} + +pub(crate) struct CFundPositionOwned { + symbol: CString, + current_net_asset_value: CDecimal, + net_asset_value_day: i64, + symbol_name: CString, + currency: CString, + cost_net_asset_value: CDecimal, + holding_units: CDecimal, +} + +impl From for CFundPositionOwned { + fn from(position: FundPosition) -> Self { + let FundPosition { + symbol, + current_net_asset_value, + net_asset_value_day, + symbol_name, + currency, + cost_net_asset_value, + holding_units, + } = position; + Self { + symbol: symbol.into(), + current_net_asset_value: current_net_asset_value.into(), + net_asset_value_day: net_asset_value_day.unix_timestamp(), + symbol_name: symbol_name.into(), + currency: currency.into(), + cost_net_asset_value: cost_net_asset_value.into(), + holding_units: holding_units.into(), + } + } +} + +impl ToFFI for CFundPositionOwned { + type FFIType = CFundPosition; + + fn to_ffi_type(&self) -> Self::FFIType { + let CFundPositionOwned { + symbol, + current_net_asset_value, + net_asset_value_day, + symbol_name, + currency, + cost_net_asset_value, + holding_units, + } = self; + CFundPosition { + symbol: symbol.to_ffi_type(), + current_net_asset_value: current_net_asset_value.to_ffi_type(), + net_asset_value_day: *net_asset_value_day, + symbol_name: symbol_name.to_ffi_type(), + currency: currency.to_ffi_type(), + cost_net_asset_value: cost_net_asset_value.to_ffi_type(), + holding_units: holding_units.to_ffi_type(), + } + } +} + +/// Stock position +#[repr(C)] +pub struct CStockPosition { + /// Stock code + pub symbol: *const c_char, + /// Stock name + pub symbol_name: *const c_char, + /// The number of holdings + pub quantity: i64, + /// Available quantity + pub available_quantity: i64, + /// Currency + pub currency: *const c_char, + /// Cost Price(According to the client's choice of average purchase or + /// diluted cost) + pub cost_price: *const CDecimal, + /// Market + pub market: CMarket, +} + +pub(crate) struct CStockPositionOwned { + /// Stock code + symbol: CString, + /// Stock name + symbol_name: CString, + /// The number of holdings + quantity: i64, + /// Available quantity + available_quantity: i64, + /// Currency + currency: CString, + /// Cost Price(According to the client's choice of average purchase or + /// diluted cost) + cost_price: CDecimal, + /// Market + market: Market, +} + +impl From for CStockPositionOwned { + fn from(position: StockPosition) -> Self { + let StockPosition { + symbol, + symbol_name, + quantity, + available_quantity, + currency, + cost_price, + market, + } = position; + Self { + symbol: symbol.into(), + symbol_name: symbol_name.into(), + quantity, + available_quantity, + currency: currency.into(), + cost_price: cost_price.into(), + market, + } + } +} + +impl ToFFI for CStockPositionOwned { + type FFIType = CStockPosition; + + fn to_ffi_type(&self) -> Self::FFIType { + let CStockPositionOwned { + symbol, + symbol_name, + quantity, + available_quantity, + currency, + cost_price, + market, + } = self; + CStockPosition { + symbol: symbol.to_ffi_type(), + symbol_name: symbol_name.to_ffi_type(), + quantity: *quantity, + available_quantity: *available_quantity, + currency: currency.to_ffi_type(), + cost_price: cost_price.to_ffi_type(), + market: (*market).into(), + } + } +} + +/// Stock position channel +#[repr(C)] +pub struct CStockPositionChannel { + /// Account type + pub account_channel: *const c_char, + /// Stock positions + pub positions: *const CStockPosition, + /// Number of stock positions + pub num_positions: usize, +} + +pub(crate) struct CStockPositionChannelOwned { + account_channel: CString, + positions: CVec, +} + +impl From for CStockPositionChannelOwned { + fn from(channel: StockPositionChannel) -> Self { + let StockPositionChannel { + account_channel, + positions, + } = channel; + Self { + account_channel: account_channel.into(), + positions: positions.into(), + } + } +} + +impl ToFFI for CStockPositionChannelOwned { + type FFIType = CStockPositionChannel; + + fn to_ffi_type(&self) -> Self::FFIType { + let CStockPositionChannelOwned { + account_channel, + positions, + } = self; + CStockPositionChannel { + account_channel: account_channel.to_ffi_type(), + positions: positions.to_ffi_type(), + num_positions: positions.len(), + } + } +} + +/// Stock positions response +#[repr(C)] +pub struct CStockPositionsResponse { + pub channels: *const CStockPositionChannel, + pub num_channels: usize, +} + +pub(crate) struct CStockPositionsResponseOwned { + channels: CVec, +} + +impl From for CStockPositionsResponseOwned { + fn from(resp: StockPositionsResponse) -> Self { + let StockPositionsResponse { channels } = resp; + CStockPositionsResponseOwned { + channels: channels.into(), + } + } +} + +impl ToFFI for CStockPositionsResponseOwned { + type FFIType = CStockPositionsResponse; + + fn to_ffi_type(&self) -> Self::FFIType { + let CStockPositionsResponseOwned { channels } = self; + CStockPositionsResponse { + channels: channels.to_ffi_type(), + num_channels: channels.len(), + } + } +} + +/// Options for get stock positions request +#[repr(C)] +pub struct CGetStockPositionsOptions { + /// Fund symbols (can null) + pub symbols: *const *const c_char, + /// Number of fund symbols + pub num_symbols: usize, +} diff --git a/c/src/types/array.rs b/c/src/types/array.rs new file mode 100644 index 0000000000..fb893e2d4d --- /dev/null +++ b/c/src/types/array.rs @@ -0,0 +1,52 @@ +use std::{ffi::c_void, fmt::Debug}; + +use crate::{ + async_call::{CAsyncResult, ToAsyncResult}, + types::ToFFI, +}; + +pub(crate) struct CVec { + #[allow(dead_code)] + owned_values: Vec, + ffi_values: Vec, +} + +impl Debug for CVec { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.owned_values.fmt(f) + } +} + +impl> From> for CVec { + fn from(values: Vec) -> Self { + let owned_values = values.into_iter().map(From::from).collect::>(); + let ffi_values = owned_values.iter().map(ToFFI::to_ffi_type).collect(); + + Self { + owned_values, + ffi_values, + } + } +} + +impl CVec { + #[inline] + pub(crate) fn len(&self) -> usize { + self.ffi_values.len() + } +} + +impl ToFFI for CVec { + type FFIType = *const T::FFIType; + + #[inline] + fn to_ffi_type(&self) -> Self::FFIType { + self.ffi_values.as_ptr() + } +} + +impl ToAsyncResult for CVec { + fn to_async_result(&self, ctx: *const c_void) -> CAsyncResult { + (self.ffi_values.as_ptr(), self.len()).to_async_result(ctx) + } +} diff --git a/c/src/types/cow.rs b/c/src/types/cow.rs new file mode 100644 index 0000000000..ede808ee3c --- /dev/null +++ b/c/src/types/cow.rs @@ -0,0 +1,47 @@ +use std::{ffi::c_void, fmt::Debug}; + +use crate::{ + async_call::{CAsyncResult, ToAsyncResult}, + types::ToFFI, +}; + +pub(crate) struct CCow { + #[allow(dead_code)] + owned_value: Box, + ffi_value: T::FFIType, +} + +impl CCow { + pub(crate) fn new(value: Q) -> Self + where + T: From, + { + let owned_value: Box = Box::new(From::from(value)); + let ffi_value = owned_value.to_ffi_type(); + Self { + owned_value, + ffi_value, + } + } +} + +impl Debug for CCow { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.owned_value.fmt(f) + } +} + +impl ToFFI for CCow { + type FFIType = *const T::FFIType; + + #[inline] + fn to_ffi_type(&self) -> Self::FFIType { + &self.ffi_value + } +} + +impl ToAsyncResult for CCow { + fn to_async_result(&self, ctx: *const c_void) -> CAsyncResult { + (self.to_ffi_type(), 1).to_async_result(ctx) + } +} diff --git a/c/src/types/datetime.rs b/c/src/types/datetime.rs new file mode 100644 index 0000000000..cfbf5edd58 --- /dev/null +++ b/c/src/types/datetime.rs @@ -0,0 +1,68 @@ +use time::{Date, Month, Time}; + +use crate::types::ToFFI; + +#[derive(Debug, Copy, Clone)] +#[repr(C)] +pub struct CDate { + pub year: i32, + pub month: u8, + pub day: u8, +} + +impl From for CDate { + fn from(date: Date) -> Self { + Self { + year: date.year(), + month: date.month() as u8, + day: date.day(), + } + } +} + +impl From for Date { + fn from(date: CDate) -> Self { + Date::from_calendar_date( + date.year, + Month::try_from(date.month).expect("invalid month"), + date.day, + ) + .expect("invalid date") + } +} + +impl ToFFI for CDate { + type FFIType = Self; + + #[inline] + fn to_ffi_type(&self) -> Self::FFIType { + *self + } +} + +#[derive(Debug, Copy, Clone)] +#[repr(C)] +pub struct CTime { + pub hour: u8, + pub minute: u8, + pub second: u8, +} + +impl From