Elixir SDK for the Longbridge OpenAPI trading platform — real-time market data, push subscriptions, trading, watchlists, screener, financial calendar, fundamental data, and portfolio analytics for US, HK, SG, and CN markets.
The SDK speaks Longbridge's binary protocol over WebSocket for streaming quote and trade data, and uses signed HTTP requests for everything else (order placement, account queries, market data on demand, screener, watchlist, calendar, fundamentals). Quote and trade WebSockets authenticate with a one-time password (OTP) derived from the long-lived access token; HTTP requests sign with HMAC-SHA256.
Status: beta. The quote endpoint is feature-complete; the trade endpoint supports subscription, order submission, position queries, account balance, and execution history via the Longbridge REST API. The API is subject to change before 1.0.0.
Installation
Add longbridge to your mix.exs:
def deps do
[
{:longbridge, "~> 0.1.0"}
]
endprotoc is a build-time dependency of protox (one of the transitive deps). On macOS:
brew install protobuf
Quick start
config = Longbridge.Config.new(
token: System.fetch_env!("LONGBRIDGE_TOKEN"),
app_key: System.fetch_env!("LONGBRIDGE_APP_KEY"),
app_secret: System.fetch_env!("LONGBRIDGE_APP_SECRET")
)
{:ok, quote_ctx} = Longbridge.QuoteContext.start_link(config)
# One-shot request
{:ok, %Longbridge.Quote.V1.SecurityQuote{} = quote} =
Longbridge.QuoteContext.quote(quote_ctx, ["AAPL.US", "00700.HK"])
IO.inspect(quote)
# Subscribe to real-time push
:ok = Longbridge.QuoteContext.subscribe(quote_ctx, ["AAPL.US"], [:QUOTE])
# Push frames arrive as messages in the *caller* process
receive do
{:longbridge_push, {:push, 101, body}} ->
IO.inspect(Protox.decode!(body, Longbridge.Quote.V1.PushQuote))
endTrade example
{:ok, trade_ctx} = Longbridge.TradeContext.start_link(config)
# Submit an order (LO = limit order)
{:ok, %{"order_id" => order_id}} =
Longbridge.TradeContext.submit_order(trade_ctx,
symbol: "AAPL.US",
side: :buy,
order_type: :lo,
submitted_quantity: "100",
time_in_force: :day,
submitted_price: "150.00"
)
# Cancel an order
:ok = Longbridge.TradeContext.cancel_order(trade_ctx, order_id)
# Today's orders
{:ok, %{"orders" => orders}} = Longbridge.TradeContext.today_orders(trade_ctx)
# Subscribe to order-change push events
:ok = Longbridge.TradeContext.subscribe(trade_ctx, [:private])
:ok = Longbridge.TradeContext.set_on_order_changed(trade_ctx, fn event ->
IO.inspect(event, label: "order changed")
end)Configuration
Longbridge.Config holds everything needed to open a connection:
| Field | Default | Notes |
|---|---|---|
token | nil | OAuth access token. Required for WS contexts and signed HTTP requests; WSConnection aborts with {:error, :no_token} if nil. |
app_key / app_secret | nil | App credentials. Required for legacy refresh_access_token/2. OAuth users only need them for register_client/1. |
expired_at | nil | Unix timestamp when token expires. Set after refresh_access_token/2. |
china | false | When true, switch to .longbridge.cn endpoints (HTTP, quote WS, trade WS). |
http_url | https://openapi.longbridge.{com,cn} | HTTP API base. Used by every HTTP context and refresh_access_token/2. |
quote_ws_url | wss://openapi-quote.longbridge.{com,cn} | Quote WebSocket URL. Override for staging or proxies. |
trade_ws_url | wss://openapi-trade.longbridge.{com,cn} | Trade WebSocket URL. Same. |
gzip_threshold | 1024 bytes | Bodies ≥ this size will be gzipped on send. |
heartbeat_interval | 15_000 ms | Client→server keep-alive cadence. |
request_timeout | 10_000 ms | Per-request timeout. |
idle_timeout | 600_000 ms | Close the connection after this much inactivity (used by Mint.WebSocket). |
headers | nil | Extra {name, value} headers appended to every HTTP and WS upgrade request (mirrors Rust SDK 4.0.6's Config::header). |
To inject custom HTTP/WS request headers (e.g. X-Forwarded-For,
custom auth, tenant routing), pass :headers as a list of
{name, value} tuples. Mirrors Config::header(key, value) from
the Rust SDK (4.0.6).
config = Longbridge.Config.new(
token: "...",
app_key: "...",
app_secret: "...",
headers: [{"X-Forwarded-For", "1.2.3.4"}, {"X-Tenant", "acme"}]
)The headers are appended to every signed HTTP request and to the WebSocket upgrade request.
Refreshing the access token (legacy API key)
The legacy API Key access_token expires after 90 days. To obtain a
new one, call Longbridge.Config.refresh_access_token/2 and use the returned
config. The new token and expired_at are set on the result.
{:ok, new_config} = Longbridge.Config.refresh_access_token(config)
{:ok, quote_ctx} = Longbridge.QuoteContext.start_link(new_config)Internally this calls Longbridge's GET /v1/token/refresh HTTP
endpoint, signed with HMAC-SHA256 using the same scheme as the
official Python / Go SDKs. No new dependency beyond the existing
finch (HTTP) and jason (JSON) deps is required.
Working with monetary values
Longbridge returns monetary fields as strings to preserve
precision. The Longbridge.Decimal helper provides two layers
of support:
# No dependency required — returns int or float:
price = Longbridge.Decimal.parse_number("0.0723")
# For exact arithmetic, add :decimal to your app's deps and use:
dec = Longbridge.Decimal.to_bigdecimal("3241500000000")to_bigdecimal/1 and sum_bigdecimal/1 raise ArgumentError with
a hint if :decimal is not loaded.
OAuth 2.0 (browser flow)
# Desktop / interactive (browser available)
{:ok, config} = Longbridge.OAuth.authorize("your-client-id")
{:ok, ctx} = Longbridge.QuoteContext.start_link(config)The first call opens a browser, exchanges the code for a token, and
persists it to ~/.longbridge/openapi/tokens/<client_id>. Subsequent
calls reuse the cached token transparently.
If you don't have a client_id yet, register one first:
{:ok, client_id} = Longbridge.OAuth.register_client("My App")OAuth 2.0 (headless server)
OAuth 2.0 in Longbridge only supports authorization_code (browser
required) and refresh_token grants. For server-side programs,
authorize once on a developer's workstation, then copy the token
file to the server.
OAuth 2.0 (custom token storage)
The default storage writes tokens to
~/.longbridge/openapi/tokens/<client_id>. For tests or
ephemeral sessions, use the bundled in-memory implementation:
Longbridge.OAuth.authorize("client-id",
storage: Longbridge.OAuth.InMemoryTokenStorage
)To plug in Redis, Vault, an encrypted file, or anything else,
implement the Longbridge.OAuth.TokenStorage behaviour and pass
it via the :storage option:
defmodule MyApp.RedisStorage do
@behaviour Longbridge.OAuth.TokenStorage
@impl true
def load(client_id) do
case Redix.command(:redix, ["GET", "oauth:token:#{client_id}"]) do
{:ok, nil} -> {:error, :not_found}
{:ok, json} -> decode(json)
{:error, _} -> {:error, :not_found}
end
end
@impl true
def save(client_id, token) do
Redix.command(:redix, ["SET", "oauth:token:#{client_id}", Jason.encode!(token)])
:ok
end
end
Longbridge.OAuth.authorize("client-id", storage: MyApp.RedisStorage)The :storage option is supported by authorize/2,
refresh_token/2, load_token/2, and export_token/2.
The typical headless workflow:
# On dev machine (browser available)
mix run -e 'Longbridge.OAuth.authorize("your-client-id")'
# → writes ~/.longbridge/openapi/tokens/<client_id>
# Copy that file to the server (same path)
# On server
{:ok, config} = Longbridge.OAuth.load_token("your-client-id")
load_token/1 auto-refreshes expired tokens via the refresh_token
grant. The Longbridge.OAuth module's full API:
authorize/2— full browser flowload_token/1— load from disk, auto-refresh if expiredexport_token/1— export for cross-machine copy / secret managerrefresh_token/2— manual refreshregister_client/1— register a new OAuth clientauthorize_url/5— build the authorization URL (testable)pkce_challenge/1— PKCE S256 helper (testable)
config.token must be a fresh OAuth token from open.longbridge.com. For OAuth, token refresh is handled automatically by Longbridge.OAuth.load_token/1 and Longbridge.HTTPClient (when :refresh_on_401 is set on a request).
Architecture
lib/
├── longbridge.ex # top-level module, public entry, docs
└── longbridge/
├── _protos.ex # use Protox, files: [protos/*.proto] — code-gen entry
├── alert_context.ex # price alert management (add/enable/disable/update/delete)
├── application.ex # supervision tree, starts Longbridge.Finch + Symbol.Store
├── asset_context.ex # account statement download
├── calendar_context.ex # financial calendar (earnings, dividends, IPOs, macro)
├── config.ex # Longbridge.Config struct + refresh_access_token/2
├── connection/
│ └── session.ex # transport-agnostic reconnect / idle / broadcast logic
├── content_context.ex # news, community topics, announcements
├── dca_context.ex # dollar-cost averaging plan management
├── decimal.ex # optional helpers for wire-format string fields
├── fundamental_context.ex # financial reports, analyst ratings, dividends, valuation
├── http_client.ex # HMAC-SHA256 signed HTTP requests via Finch (+ 401 retry)
├── market_context.ex # market status, broker holdings, indices, anomaly alerts
├── oauth.ex # OAuth 2.0 Authorization Code flow with PKCE
├── oauth/
│ ├── file_token_storage.ex # default ~/.longbridge/openapi/tokens/<id> JSON file
│ ├── in_memory_token_storage.ex
│ └── token_storage.ex # behaviour for plugging custom storage (Redis, Vault...)
├── portfolio_context.ex # exchange rates, portfolio P&L analysis
├── protocol.ex # wire-format constants + pack/unpack whole packets
├── protocol/header.ex # 11/10/5-byte header encode/decode (request/response/push)
├── quote_context.ex # public API: 25+ quote methods + push subscription
├── quote_context/realtime_store.ex # in-memory push-data cache
├── quote_http_context.ex # HTTP-backed quote methods (watchlist, market temp, ...)
├── screener_context.ex # screener strategies, indicator search, AI recommendations
├── sharelist_context.ex # community sharelist management
├── symbol.ex # user symbol ↔ counter_id conversion (with remote fallback)
├── symbol/
│ ├── cache.ex # resolved counter_id cache (disk-backed)
│ ├── directory.ex # embedded US-ETF / US-IX / US-WT directory
│ └── store.ex # long-lived GenServer that owns the cache/dir ETS tables
├── trade_context.ex # public API: orders, positions, account, executions, push
└── ws_connection.ex # WebSocket GenServer (one per endpoint, Mint-based)
protos/
├── api.proto # quote + trade request/response messages
├── control.proto # Auth, Heartbeat, Close
├── error.proto # Error envelope
└── subscribe.proto # Subscribe/Unsubscribe/PushQuote/PushDepth/...Wire format
Each WebSocket binary message is one or more length-prefixed protocol packets concatenated together. A single protocol packet is:
┌─────────────────────────────────────────────────────────────┐
│ length (4 bytes, big-endian uint32) │
├─────────────────────────────────────────────────────────────┤
│ header (11 / 10 / 5 bytes, big-endian) │
│ type:4 │ v:1 │ g:1 │ res:2 │ cmd:8 │ ... │
├─────────────────────────────────────────────────────────────┤
│ body (Protobuf-encoded, ≤ 16 MiB) │
└─────────────────────────────────────────────────────────────┘The WS handshake is plain WebSocket upgrade (wss://openapi-quote.longbridge.com); the Longbridge protocol handshake is then sent as the first packet with version=1, codec=protobuf, platform=OpenAPI=9. See Longbridge.Protocol.handshake/0. The WSConnection module buffers and splits received binary messages so callers don't have to.
Connection lifecycle
Longbridge.WSConnection is a GenServer that owns the Mint WebSocket. Its lifecycle:
init → do_connect_and_auth → idle (heartbeat every heartbeat_interval, request/response pairing)
│
└─ on error / disconnect → Session.do_connect_and_auth (reconnect with back-off)Longbridge.Connection.Session owns the transport-agnostic pieces (reconnect with exponential back-off, idle-timer scheduling, request broadcasting, push dispatch) so swapping the transport later wouldn't require rewriting them.
After auth succeeds, the connection enters active mode, sends a heartbeat every heartbeat_interval, dispatches response packets to in-flight request_id callers, and forwards push packets to subscribed context processes. The connection automatically reconnects with exponential back-off after a disconnect; once reconnected, every context that has subscribed resubscribes automatically (see Longbridge.QuoteContext and Longbridge.TradeContext).
Push data flow
Contexts subscribe themselves to the connection on start_link/2. When a push frame arrives:
- The connection's
process_data/2buffers bytes, splits on the 4-byte length prefix, and decodes the header + body. - The connection's
dispatch_push/3sends{:longbridge, conn_pid, {:push, cmd_code, body}}to every subscriber. - Each context receives this message, re-dispatches as
{:longbridge_push, msg}to its own caller process, and the user pattern-matches in their own process.
The context's caller must be alive to receive push messages — Longbridge.QuoteContext.start_link/2 is the right way to spawn one per long-running process.
For typed callbacks instead of mailbox pattern-matching, Longbridge.QuoteContext also supports:
QuoteContext.set_on_quote(ctx, fn push ->
IO.inspect(push.symbol)
end)
QuoteContext.set_on_depth(ctx, fn depth -> ... end)
QuoteContext.set_on_brokers(ctx, fn brokers -> ... end)
QuoteContext.set_on_trades(ctx, fn trades -> ... end)
# Fallback for any topic
QuoteContext.set_default_push_callback(ctx, fn msg -> ... end)Each callback receives the decoded proto struct (Longbridge.Quote.V1.PushQuote, PushDepth, PushBrokers, or PushTrade). Mailbox delivery still works alongside the callbacks.
API reference
Longbridge.QuoteContext
| Method | Sub-command | Notes |
|---|---|---|
user_quote_profile/2 | QueryUserQuoteProfile (4) | language option, defaults to "en". Returns member_id, quote_level, rate_limit, quote_level_detail, subscribe_limit, history_candlestick_limit. |
member_id/1 | (convenience) | Extracts member_id from user_quote_profile/2. |
quote_level/1 | (convenience) | Extracts the user's quote level string (e.g. "Lv1", "Lv2"). |
quote_package_details/2 | (convenience) | Returns subscribed quote packages by market. |
static_info/2 | QuerySecurityStaticInfo (10) | |
quote/2 | QuerySecurityQuote (11) | |
option_quote/2 | QueryOptionQuote (12) | |
warrant_quote/2 | QueryWarrantQuote (13) | |
depth/2 | QueryDepth (14) | |
brokers/2 | QueryBrokers (15) | |
participant_broker_ids/1 | QueryParticipantBrokerIds (16) | |
trades/3 | QueryTrade (17) | count defaults to 100. |
intraday/3 | QueryIntraday (18) | trade_session ∈ {:NORMAL_TRADE, :PRE_TRADE, :POST_TRADE, :OVERNIGHT_TRADE} (or 0–3). |
candlesticks/6 | QueryCandlestick (19) | period accepts :DAY, :ONE_MINUTE, :FIVE_MINUTE, :WEEK, :MONTH, :QUARTER, :YEAR, etc. |
| history_candlesticks_by_offset/3 | QueryHistoryCandlestick (27) | Walks forward/backward from a date. direction: :forward | :backward. |
| history_candlesticks_by_date/3 | QueryHistoryCandlestick (27) | Fetches within a start_date/end_date range. |
| option_chain_date/2 | QueryOptionChainDate (20) | |
| option_chain_strike_info/3 | QueryOptionChainDateStrikeInfo (21) | |
| warrant_issuer_info/1 | QueryWarrantIssuerInfo (22) | |
| warrant_list/2 | QueryWarrantFilterList (23) | Filtered HK warrant list. Options: :symbol, :language, :sort_by, :sort_order, :type, :expiry_date, :status, :price_type, :issuer. |
| realtime_quote/2 | (cache) | Returns cached PushQuote for symbols from local push-data store. |
| realtime_depth/2 | (cache) | Returns cached PushDepth for a symbol. |
| realtime_brokers/2 | (cache) | Returns cached PushBrokers for a symbol. |
| realtime_trades/3 | (cache) | Returns up to count cached trades (cap 500). |
| reset_realtime_cache/1 | (cache) | Clears the local push-data cache. |
| market_trade_period/1 | QueryMarketTradePeriod (8) | |
| market_trade_day/4 | QueryMarketTradeDay (9) | |
| calc_index/3 | QuerySecurityCalcIndex (26) | calc_indexes is a list of CalcIndex atoms. |
| capital_flow_intraday/2 | QueryCapitalFlowIntraday (24) | |
| capital_flow_distribution/2 | QueryCapitalFlowDistribution (25) | |
| subscription/1 | Subscription (5) | |
| subscribe/4 | Subscribe (6) | sub_types ∈ {:QUOTE, :DEPTH, :BROKERS, :TRADE}. is_first_push: true requests the current snapshot to be pushed immediately. |
| unsubscribe/4 | Unsubscribe (7) | unsub_all: true removes every subscription for those symbols. |
QuoteContext.subscribe/4 and TradeContext.subscribe/2 both survive reconnect automatically: the context records every subscription and re-issues it after the WS reconnects, so push delivery resumes without any extra wiring on the caller's side.
Longbridge.TradeContext
All HTTP requests (order_detail, today_orders, etc.)
automatically retry once on a 401 Unauthorized response after
refreshing the access token. OAuth users must pass
token_refresher: to start_link/2 (a (Config.t -> {:ok, Config.t} | {:error, term}))
so the trade context knows how to obtain a fresh token — the
default Config.refresh_access_token/2 only works for legacy
app-key auth. If the refresh fails (e.g. invalid_grant), the
original 401 error is returned unchanged so callers can detect a
revoked refresh token and re-authorize.
Push & subscription
| Method | Description |
|---|---|
subscribe/2 | Subscribe to trade push topics (:private for orders). Survives WS reconnect. |
unsubscribe/2 | Unsubscribe from trade push |
put_callback/3 | Register a topic-specific callback (e.g. "/v1/trade/order_changed") |
remove_callback/2 | Remove a topic callback |
set_default_push_callback/2 | Fallback callback for unregistered topics |
set_on_order_changed/2 | Convenience wrapper for order-changed events |
on_order_changed/2 | Alias for set_on_order_changed/2 |
Orders (HTTP — REST API)
| Method | Description |
|---|
| submit_order/2 | Place a new order (:market | :lo | :elo | :alo | :mit | :odd | :lit | :tslpamt | :tslppct) |
| replace_order/2 | Replace an existing order |
| cancel_order/2 | Cancel an order by ID |
| today_orders/2 | List today's orders (:status, :market, :side, pagination) |
| history_orders/2 | Search historical orders (date range, status, market, side) |
| order_detail/2 | Get a single order by ID |
Executions (HTTP)
| Method | Description |
|---|---|
today_executions/2 | List today's fills (:symbol, :market, :start_at, :end_at, pagination) |
history_executions/2 | Search historical fills (date range, symbol, market, side) |
Account & positions (HTTP)
| Method | Description |
|---|---|
account_balance/2 | Get cash / buying power (per currency, e.g. "USD") |
stock_positions/2 | List stock holdings (optional symbol filter) |
fund_positions/2 | List fund holdings (optional symbol filter) |
cash_flow/2 | Cash flow history within a date range |
margin_ratio/2 | Initial/maintain margin ratios for a symbol |
| estimate_max_purchase_quantity/2 | Estimate the max qty buyable for :LO | :ELO | :ALO | :MIT orders |
Longbridge.AssetContext (HTTP)
| Method | Description |
|---|---|
statements/2 | List account statements (daily/monthly) |
download_url/2 | Get presigned download URL for a statement file |
Longbridge.MarketContext (HTTP)
| Method | Description |
|---|---|
trading_days/4 | Deprecated — removed upstream; always returns {:error, :removed_upstream}. Use Longbridge.CalendarContext.market_closures/2 instead. |
market_session/2 | Current trading session for all markets (US/HK/CN/SG; no market filter — filter client-side). |
broker_holdings/3 | Top broker holdings (buy/sell leaders) for a symbol. :period ∈ {:rct_1, :rct_5, :rct_20, :rct_60}. |
ah_premium/2 | A/H share premium K-line data for a dual-listed H-share |
trade_status/2 | Buy/sell/neutral trade statistics for a symbol |
index_constituents/2 | Constituents of a market index (e.g. "HSI.HK", "/DJI.US") |
anomaly_alerts/2 | Market anomaly alerts (trading halts, suspensions) per market |
top_movers/2 | Stocks with anomalous 20d price movement + linked news. :sort ∈ {:hot, :time, :change}. Renamed from stock_events in longbridge/openapi 4.2.0. |
rank_categories/1 | List rank categories for rank_list/3 |
rank_list/4 | Ranked securities for a category (adds ib_ prefix automatically) |
Longbridge.ContentContext (HTTP)
| Method | Description |
|---|---|
news/2 | News articles with filtering/pagination |
topics/2 | Community topics/posts |
my_topics/2 | Topics created by the current authenticated user |
create_topic/2 | Create a community topic; returns {:ok, topic_id} |
topic_detail/2 | Single topic by ID |
list_topic_replies/3 | Replies for a topic |
create_topic_reply/3 | Post a reply to a topic |
announcements/2 | Company announcements |
Longbridge.FundamentalContext (HTTP)
| Method | Description |
|---|---|
company_profile/2 | Company profile / overview |
financial_reports/3 | Income, balance, cash flow reports |
analyst_ratings/2 | Analyst ratings and targets |
dividends/2 | Dividend history |
valuation/2 | PE, PB, PS metrics |
shareholders/2 | Shareholder distribution |
etf_asset_allocation/2 | ETF asset allocation (holdings, regional, asset class, industry) |
filings/2 | Regulatory filings for a symbol (e.g. SEC 10-K, 10-Q, insider forms) |
macroeconomic_indicators/2 | List macroeconomic indicators for a country |
macroeconomic/3 | Historical data for a specific macroeconomic indicator |
valuation_comparison/4 | Compare valuation metrics across stocks (auto-selects peers when comparison_symbols is nil) |
Longbridge.CalendarContext (HTTP)
| Method | Description |
|---|---|
earnings/4 | Upcoming earnings dates |
dividend_dates/4 | Dividend schedule |
stock_splits/4 | Stock split calendar |
ipo_calendar/4 | IPO calendar |
macro_events/4 | Macro economic event dates |
market_closures/2 | Market closure dates (holidays) |
Longbridge.PortfolioContext (HTTP)
| Method | Description |
|---|---|
exchange_rates/3 | Real-time exchange rates |
portfolio_pl/2 | Portfolio P&L analysis |
portfolio_positions/1 | Portfolio position list |
Longbridge.AlertContext (HTTP)
| Method | Description |
|---|---|
add_alert/2 | Create a price alert |
list_alerts/1 | List active price alerts |
update/3 | Update an alert (enable/disable) — re-sends the full item, use this instead of enable_alert/disable_alert |
enable_alert/2 | Deprecated. Enable a price alert by alert_id (may fail server-side for alerts created through add_alert/2). |
disable_alert/2 | Deprecated. Disable a price alert by alert_id. |
delete_alert/2 | Delete one or more price alerts by id (string or list of strings) |
Longbridge.DCAContext (HTTP)
| Method | Description |
|---|---|
create_plan/2 | Create a DCA plan |
list_plans/2 | List DCA plans |
plan_detail/2 | Get plan details |
update_plan/2 | Update an existing plan |
pause_plan/2 | Pause an active plan |
resume_plan/2 | Resume a paused plan |
delete_plan/2 | Delete a plan |
Longbridge.SharelistContext (HTTP)
| Method | Description |
|---|---|
create/2 | Create a new sharelist |
list/2 | List owned sharelists |
detail/2 | Get symbols in a sharelist |
rename/3 | Rename a sharelist |
add_symbols/3 | Add symbols to a sharelist |
remove_symbols/3 | Remove symbols from a sharelist |
delete/2 | Delete a sharelist |
Longbridge.ScreenerContext (HTTP)
| Method | Description |
|---|---|
recommend_strategies/2 | Pre-defined recommended screener strategies for a market |
user_strategies/2 | Current user's saved screener strategies |
strategy/2 | A single screener strategy by ID (strips filter_ prefix from filter.filters[].key) |
search/3 | Run a screener search — Mode A (strategy_id:) or Mode B (typed conditions:) |
indicators/1 | List available screener indicators (strips filter_ prefix; builds tech_values from tech_indicators) |
Longbridge.QuoteHTTPContext (HTTP)
| Method | Description |
|---|---|
short_positions/3 | Short interest data (.HK = HKEX daily, others = US FINRA bi-monthly) |
option_volume/2 | Real-time call/put volume snapshot for an option symbol |
option_volume_daily/4 | Daily option volume for a symbol within a date range |
update_pinned/4 | Pin or unpin a security to the top of a watchlist group |
security_list/2 | List securities available for a market (market, category, page, count) |
market_temperature/2 | Current market temperature (0-100) for a market |
history_market_temperature/4 | Historical market temperature within a YYYY-MM-DD date range |
short_trades/3 | Recent short-selling trades (.HK → /hk, others → /us; count, last_timestamp) |
watchlist_groups/1 | List watchlist groups |
create_watchlist_group/3 | Create a watchlist group; returns {:ok, group_id} |
delete_watchlist_group/3 | Delete a watchlist group (purge: false by default) |
| update_watchlist_group/5 | Update a watchlist group (:add | :remove | :replace modes) |
| filings/2 | Regulatory filings for a symbol (returns list with id, title, file_urls, publish_at, ...) |
| symbol_to_counter_ids/2 | Batch-convert user symbols to internal counter_ids (HTTP-only; for local-first resolution see Longbridge.Symbol.resolve_counter_ids/2) |
Longbridge.Symbol
| Method | Description |
|---|---|
to_counter_id/1 | Convert a user symbol ("AAPL.US", "00700.HK") to its internal counter_id ("ST/US/AAPL", "ST/HK/700"). Uses the embedded US-ETF / US-IX / US-WT directory for known symbols and falls back to ST/{MARKET}/{CODE} otherwise. |
index_to_counter_id/1 | Convert an index symbol (e.g. "HSI.HK", "/DJI.US") to its IX/... counter_id. |
from_counter_id/1 | Inverse of to_counter_id/1 (best-effort — some counter_ids are internal-only and have no public symbol). |
resolve_counter_ids/2 | Local-first batch resolution: embedded directory → on-disk cache → server symbol_to_counter_ids/2. Results for unknown symbols are cached in $LONGBRIDGE_CACHE_DIR/counter-ids.csv. |
Push command codes (consumer-side)
| Code | Message | Decode with |
|---|---|---|
| 101 | PushQuote | Longbridge.Quote.V1.PushQuote |
| 102 | PushDepth | Longbridge.Quote.V1.PushDepth |
| 103 | PushBrokers | Longbridge.Quote.V1.PushBrokers |
| 104 | PushTrade | Longbridge.Quote.V1.PushTrade |
| 18 | Notification (trade) | Longbridge.Trade.V1.Notification |
Errors and reconnect behavior
Every public function returns
{:ok, value} | {:error, reason}so callers never see raw exceptions from the wire layer.Protox.decode!/2errors are caught and returned as{:error, {:decode_error, exception}}.- HTTP
401 Unauthorizedresponses fromLongbridge.HTTPClient.request/5are automatically retried once after a token refresh when called withrefresh_on_401: true(the default for the TradeContext). The refreshed config is kept on the connection state so subsequent requests use it without another refresh. - The WS connection (
Longbridge.WSConnection) reconnects with exponential back-off (capped) after any disconnect or auth failure. BothQuoteContextandTradeContextre-apply their recorded push subscriptions on reconnect, so you do not need to callsubscribe/4again after a network blip. Longbridge.HTTPClient.refresh_access_token/1(used by the legacy API-key flow) updates bothtokenandexpired_aton the config so the next call doesn't refresh again.
Development
mix deps.get
mix compile # generates 98 proto modules on first build
mix test # unit tests
mix docs # ExDoc HTML in doc/
The full quality gate is one command:
mix ci
It runs, in order: compile --all-warnings --warnings-as-errors, format --check-formatted, credo --strict, deps.unlock --check-unused, deps.audit, xref graph --label compile-connected --fail-above 0, dialyzer, ex_dna (duplication), reach.check --dead-code --smells. See mix.exs for the alias definition.
Proto specs
The four .proto files under protos/ are vendored from longbridge/openapi-protobufs, pinned to tag gen/go/v0.7.0 via a git dep in mix.exs. To upgrade:
# 1. bump the tag in mix.exs
# 2. mix deps.get
# 3. sync the vendored files
cp deps/openapi_protobuf_specs/control/*.proto protos/
cp deps/openapi_protobuf_specs/quote/*.proto protos/
cp deps/openapi_protobuf_specs/trade/*.proto protos/
# 4. mix compile && mix test
Don't hand-edit files under protos/ — the next sync will clobber your changes.
License
TBD. See the LICENSE file once one is added.