mirror of
https://github.com/0xrsydn/idx-cli.git
synced 2026-11-07 01:35:36 +00:00
fix(quote): market_status reads open or close with a login too
Logged-in quotes passed Stockbit's session state through verbatim (STATE_NAME_SESSION_2), while public-page quotes say open or close, so a consumer checking for "open" broke after login. Quotes now use the two words either way; the order book keeps the state verbatim. Claude-Session: https://claude.ai/code/session_01RqPPVeVDrnLoP7NUZmLRxY
This commit is contained in:
parent
63587e4dc2
commit
c03c0f41f7
8 changed files with 30 additions and 11 deletions
|
|
@ -1,4 +1,4 @@
|
||||||
exit: 0
|
exit: 0
|
||||||
--- stdout
|
--- stdout
|
||||||
[{"symbol":"BBCA.JK","price":5175,"change":-900,"change_pct":-14.81,"volume":1241098200,"market_cap":null,"week52_high":null,"week52_low":null,"week52_position":null,"range_signal":null,"prev_close":6075,"avg_volume":null,"as_of":null,"behind_session":false,"market_status":"STATE_NAME_MARKET_CLOSED"}]
|
[{"symbol":"BBCA.JK","price":5175,"change":-900,"change_pct":-14.81,"volume":1241098200,"market_cap":null,"week52_high":null,"week52_low":null,"week52_position":null,"range_signal":null,"prev_close":6075,"avg_volume":null,"as_of":null,"behind_session":false,"market_status":"close"}]
|
||||||
--- stderr
|
--- stderr
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ exit: 0
|
||||||
┌─────────┬───────┬──────┬─────────┬───────────────┬─────┬───────┬────────┬───────┐
|
┌─────────┬───────┬──────┬─────────┬───────────────┬─────┬───────┬────────┬───────┐
|
||||||
│ SYMBOL ┆ PRICE ┆ CHG ┆ CHG% ┆ VOLUME ┆ BID ┆ OFFER ┆ MARKET ┆ AS OF │
|
│ SYMBOL ┆ PRICE ┆ CHG ┆ CHG% ┆ VOLUME ┆ BID ┆ OFFER ┆ MARKET ┆ AS OF │
|
||||||
╞═════════╪═══════╪══════╪═════════╪═══════════════╪═════╪═══════╪════════╪═══════╡
|
╞═════════╪═══════╪══════╪═════════╪═══════════════╪═════╪═══════╪════════╪═══════╡
|
||||||
│ BBCA.JK ┆ 5,175 ┆ -900 ┆ -14.81% ┆ 1,241,098,200 ┆ - ┆ - ┆ closed ┆ - │
|
│ BBCA.JK ┆ 5,175 ┆ -900 ┆ -14.81% ┆ 1,241,098,200 ┆ - ┆ - ┆ close ┆ - │
|
||||||
└─────────┴───────┴──────┴─────────┴───────────────┴─────┴───────┴────────┴───────┘
|
└─────────┴───────┴──────┴─────────┴───────────────┴─────┴───────┴────────┴───────┘
|
||||||
Source: Stockbit, your session when logged in (`idx stockbit status`), else its public page, delayed at least 10 minutes (Stockbit's terms); bid/offer only while the market is open.
|
Source: Stockbit, your session when logged in (`idx stockbit status`), else its public page, delayed at least 10 minutes (Stockbit's terms); bid/offer only while the market is open.
|
||||||
--- stderr
|
--- stderr
|
||||||
|
|
|
||||||
|
|
@ -335,6 +335,9 @@ pub fn parse_orderbook(
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A quote from the order book; bid/offer only while the market is open.
|
/// A quote from the order book; bid/offer only while the market is open.
|
||||||
|
/// Its `market_status` is the public page's vocabulary, `open` or `close`,
|
||||||
|
/// so a quote reads the same with or without a login; the order book keeps
|
||||||
|
/// Stockbit's state verbatim.
|
||||||
pub fn quote_from_book(book: &OrderBook) -> Quote {
|
pub fn quote_from_book(book: &OrderBook) -> Quote {
|
||||||
let open = book.market_status.as_deref().is_some_and(is_trading);
|
let open = book.market_status.as_deref().is_some_and(is_trading);
|
||||||
let best = |levels: &[BookLevel]| open.then(|| levels.first().map(|l| l.price)).flatten();
|
let best = |levels: &[BookLevel]| open.then(|| levels.first().map(|l| l.price)).flatten();
|
||||||
|
|
@ -355,7 +358,10 @@ pub fn quote_from_book(book: &OrderBook) -> Quote {
|
||||||
behind_session: false,
|
behind_session: false,
|
||||||
bid: best(&book.bids),
|
bid: best(&book.bids),
|
||||||
offer: best(&book.offers),
|
offer: best(&book.offers),
|
||||||
market_status: book.market_status.clone(),
|
market_status: book
|
||||||
|
.market_status
|
||||||
|
.as_ref()
|
||||||
|
.map(|_| if open { "open" } else { "close" }.to_string()),
|
||||||
session_note: book.session_note.clone(),
|
session_note: book.session_note.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -498,11 +504,14 @@ mod tests {
|
||||||
let open = quote_from_book(&book(trading));
|
let open = quote_from_book(&book(trading));
|
||||||
assert_eq!((open.bid, open.offer), (Some(4850), Some(4860)));
|
assert_eq!((open.bid, open.offer), (Some(4850), Some(4860)));
|
||||||
assert_eq!(open.price, 4860);
|
assert_eq!(open.price, 4860);
|
||||||
|
assert_eq!(open.market_status.as_deref(), Some("open"));
|
||||||
for state in ["market_session_break.json", "market_session.json"] {
|
for state in ["market_session_break.json", "market_session.json"] {
|
||||||
let shut = quote_from_book(&book(parse_market_status(&fixture(state))));
|
let shut = quote_from_book(&book(parse_market_status(&fixture(state))));
|
||||||
assert_eq!((shut.bid, shut.offer), (None, None), "{state}");
|
assert_eq!((shut.bid, shut.offer), (None, None), "{state}");
|
||||||
|
assert_eq!(shut.market_status.as_deref(), Some("close"), "{state}");
|
||||||
}
|
}
|
||||||
assert_eq!(quote_from_book(&book(None)).bid, None);
|
let unknown = quote_from_book(&book(None));
|
||||||
|
assert_eq!((unknown.bid, unknown.market_status), (None, None));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -65,8 +65,10 @@ pub struct Quote {
|
||||||
/// Best offer in IDR (whole Rupiah), gated like `bid`.
|
/// Best offer in IDR (whole Rupiah), gated like `bid`.
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub offer: Option<i64>,
|
pub offer: Option<i64>,
|
||||||
/// The provider's own session state, verbatim (Stockbit
|
/// Stockbit's market state as its public page words it: `open` while
|
||||||
/// `market_hour.status`, e.g. `close`). Absent for providers without one.
|
/// the regular market trades, `close` otherwise (also the lunch break
|
||||||
|
/// and auctions). A logged-in quote maps its session state to the same
|
||||||
|
/// two words. Absent for other providers, or when the state is unknown.
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub market_status: Option<String>,
|
pub market_status: Option<String>,
|
||||||
/// What the provider says about this symbol's session: suspended,
|
/// What the provider says about this symbol's session: suspended,
|
||||||
|
|
|
||||||
|
|
@ -124,8 +124,8 @@ impl IdxServer {
|
||||||
for one or more IDX tickers. `as_of` is the last trade in WIB; `behind_session` is true \
|
for one or more IDX tickers. `as_of` is the last trade in WIB; `behind_session` is true \
|
||||||
when that is before the latest IDX session, so the price is not that session's close: the \
|
when that is before the latest IDX session, so the price is not that session's close: the \
|
||||||
provider lags, the stock did not trade, or the day was an exchange holiday. When the server \
|
provider lags, the stock did not trade, or the day was an exchange holiday. When the server \
|
||||||
runs with the stockbit provider, quotes also carry `market_status` (Stockbit's, e.g. \
|
runs with the stockbit provider, quotes also carry `market_status` (\"open\" while the \
|
||||||
\"open\", \"close\"), `bid`/`offer` (best price, only while the market is open) and \
|
regular market trades, else \"close\", with or without a login), `bid`/`offer` (best price, only while the market is open) and \
|
||||||
`session_note` (suspended, delisted, no trades); Stockbit data is delayed at least 10 \
|
`session_note` (suspended, delisted, no trades); Stockbit data is delayed at least 10 \
|
||||||
minutes."
|
minutes."
|
||||||
)]
|
)]
|
||||||
|
|
|
||||||
|
|
@ -451,7 +451,7 @@
|
||||||
"name": "get_price_history"
|
"name": "get_price_history"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Latest quotes (price, change, volume, 52-week range, market cap) for one or more IDX tickers. `as_of` is the last trade in WIB; `behind_session` is true when that is before the latest IDX session, so the price is not that session's close: the provider lags, the stock did not trade, or the day was an exchange holiday. When the server runs with the stockbit provider, quotes also carry `market_status` (Stockbit's, e.g. \"open\", \"close\"), `bid`/`offer` (best price, only while the market is open) and `session_note` (suspended, delisted, no trades); Stockbit data is delayed at least 10 minutes.",
|
"description": "Latest quotes (price, change, volume, 52-week range, market cap) for one or more IDX tickers. `as_of` is the last trade in WIB; `behind_session` is true when that is before the latest IDX session, so the price is not that session's close: the provider lags, the stock did not trade, or the day was an exchange holiday. When the server runs with the stockbit provider, quotes also carry `market_status` (\"open\" while the regular market trades, else \"close\", with or without a login), `bid`/`offer` (best price, only while the market is open) and `session_note` (suspended, delisted, no trades); Stockbit data is delayed at least 10 minutes.",
|
||||||
"inputSchema": {
|
"inputSchema": {
|
||||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
|
||||||
|
|
@ -195,6 +195,7 @@ What the login adds:
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `--provider stockbit` quotes | public page, delayed | Stockbit's API, live |
|
| `--provider stockbit` quotes | public page, delayed | Stockbit's API, live |
|
||||||
| Bid and offer in quotes | best price, while open | best price, while open |
|
| Bid and offer in quotes | best price, while open | best price, while open |
|
||||||
|
| `market_status` in quotes | `open` or `close` | `open` or `close` |
|
||||||
| `stocks orderbook` | `AUTHERROR` | every level with lots and queued orders, OHLC, value, trades; foreign buy/sell/net once Stockbit marks them current |
|
| `stocks orderbook` | `AUTHERROR` | every level with lots and queued orders, OHLC, value, trades; foreign buy/sell/net once Stockbit marks them current |
|
||||||
| `stocks history` under `--provider stockbit` | Yahoo | Stockbit's daily candles for `5d` to `1y`; Yahoo for other windows |
|
| `stocks history` under `--provider stockbit` | Yahoo | Stockbit's daily candles for `5d` to `1y`; Yahoo for other windows |
|
||||||
| `stocks intraday` | public page | public page |
|
| `stocks intraday` | public page | public page |
|
||||||
|
|
@ -206,7 +207,14 @@ the previous session's, so idx leaves them out (`stocks flow` has foreign
|
||||||
flow session by session). Sizes are lots in the
|
flow session by session). Sizes are lots in the
|
||||||
table and shares in JSON. `--history-provider stockbit` asks for Stockbit's
|
table and shares in JSON. `--history-provider stockbit` asks for Stockbit's
|
||||||
candles explicitly; windows it charts only weekly or hourly (`2y`, `5y`,
|
candles explicitly; windows it charts only weekly or hourly (`2y`, `5y`,
|
||||||
`1wk`) answer `UNSUPPORTED`.
|
`1wk`) answer `UNSUPPORTED`. If Stockbit refuses a stored session, `auto`
|
||||||
|
history falls back to Yahoo with a warning; an explicit
|
||||||
|
`--history-provider stockbit` fails with `AUTHERROR`.
|
||||||
|
|
||||||
|
`market_status` in quotes is `open` while the regular market trades and
|
||||||
|
`close` otherwise, including the lunch break and auctions, with or without a
|
||||||
|
login. `stocks orderbook` keeps Stockbit's state verbatim
|
||||||
|
(`STATE_NAME_SESSION_2`, `STATE_NAME_BREAK`, ...).
|
||||||
|
|
||||||
## Terms
|
## Terms
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -125,7 +125,7 @@ OHLCV price history for one IDX ticker.
|
||||||
|
|
||||||
## `get_quotes`
|
## `get_quotes`
|
||||||
|
|
||||||
Latest quotes (price, change, volume, 52-week range, market cap) for one or more IDX tickers. `as_of` is the last trade in WIB; `behind_session` is true when that is before the latest IDX session, so the price is not that session's close: the provider lags, the stock did not trade, or the day was an exchange holiday. When the server runs with the stockbit provider, quotes also carry `market_status` (Stockbit's, e.g. "open", "close"), `bid`/`offer` (best price, only while the market is open) and `session_note` (suspended, delisted, no trades); Stockbit data is delayed at least 10 minutes.
|
Latest quotes (price, change, volume, 52-week range, market cap) for one or more IDX tickers. `as_of` is the last trade in WIB; `behind_session` is true when that is before the latest IDX session, so the price is not that session's close: the provider lags, the stock did not trade, or the day was an exchange holiday. When the server runs with the stockbit provider, quotes also carry `market_status` ("open" while the regular market trades, else "close", with or without a login), `bid`/`offer` (best price, only while the market is open) and `session_note` (suspended, delisted, no trades); Stockbit data is delayed at least 10 minutes.
|
||||||
|
|
||||||
| Input | Type | Required | Description |
|
| Input | Type | Required | Description |
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue