diff --git a/CHANGELOG.md b/CHANGELOG.md index bb862e3bce..5dc967571d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - **Rust:** `market::TradeStatus` models `/v1/quote/market-status` trade status codes, including engine-compatible normalization and display helpers. +- **C++:** `market::TradeStatus` models `/v1/quote/market-status` trade status codes, with status-code conversion, normalization, display helpers, and typed `MarketTimeItem` status fields. ### Fixed diff --git a/cpp/include/types.hpp b/cpp/include/types.hpp index 7c66849b0e..8c24b6d09b 100644 --- a/cpp/include/types.hpp +++ b/cpp/include/types.hpp @@ -1,8 +1,10 @@ #pragma once #include "decimal.hpp" +#include #include #include +#include #include namespace longbridge { @@ -2301,13 +2303,112 @@ struct CreateTopicOptions // ── MarketContext types ─────────────────────────────────────────── namespace market { +/// Market trading status code. +enum class TradeStatus : int32_t +{ + /// Unknown status + UNKNOWN = -1, + /// Quote is not registered + NO_REGISTER_QUOTE = 0, + /// Clearing before the market opens + CLEAN = 101, + /// Opening auction + OPEN_BID = 102, + /// Morning break, currently used by VIX indexes + MORNING_CLOSING = 103, + /// Regular trading + TRADING = 105, + /// Midday break + NOON_CLOSING = 106, + /// Closing auction + CLOSE_BID = 107, + /// Market closed + CLOSING = 108, + /// Dark trading waiting to open + DARK_WAIT = 110, + /// Dark trading + DARK_TRADING = 111, + /// Dark trading closed + DARK_CLOSING = 112, + /// After-hours fixed-price trading + AFTER_FIX = 120, + /// Half-day market closed + HALF_CLOSING = 121, + /// Not opened because the exchange is waiting to open under special conditions + NOT_OPENED = 122, + /// Temporary intraday break + REALTIME_QUOTE = 123, + /// US pre-market + US_PREV = 201, + /// US regular trading + US_TRADING = 202, + /// US post-market + US_AFTER = 203, + /// US closed + US_CLOSING = 204, + /// US halted + US_STOP = 205, + /// US clearing plus pre-market + US_CLEAN = 206, + /// US overnight trading + US_NIGHT = 207, + /// US pre-market clearing alias returned by the quote engine + US_PREV_MARKET_CLEAN = 209, + /// US post-market clearing alias returned by the quote engine + US_AFTER_MARKET_CLEAN = 210, + /// Stock refresh. Deprecated in the status definition + REFRESH = 1000, + /// Delisted + DELIST = 1001, + /// Preparing to list + PREPARE = 1002, + /// Code changed + CODE_CHANGE = 1003, + /// Halted + STOP = 1004, + /// Waiting to open, typically for a US IPO auction + WILL_OPEN = 1005, + /// Split or merge suspended + COMMON_SUSPEND = 1006, + /// Expired + EXPIRE = 1007, + /// No quote data + NO_QUOTE = 1008, + /// Not listed + UNITED = 1009, + /// Terminated trading, usually for warrants + TRADING_HALT = 1010, + /// Waiting to list, usually for new warrants + WAIT_LISTING = 1011, + /// Fuse + FUSE = 2001, +}; + +TradeStatus from_trade_status_code(int32_t value); +int32_t code(TradeStatus status); +const char* trade_status_as_string(TradeStatus status); +const char* label(TradeStatus status); +const char* name(TradeStatus status); +bool is_us_market(TradeStatus status); +bool is_us_pre_post(TradeStatus status); +bool is_us_night(TradeStatus status); +bool is_us_closing(TradeStatus status); +bool is_closing(TradeStatus status); +bool is_us_prev(TradeStatus status); +bool is_us_after(TradeStatus status); +bool is_trading(TradeStatus status); +bool is_dark(TradeStatus status); +bool allow_trading(TradeStatus status); +TradeStatus normalize(TradeStatus status); +bool is_special(TradeStatus status); + /// Current trading status and timestamps for one market. struct MarketTimeItem { longbridge::Market market; - int32_t trade_status; + TradeStatus trade_status; std::string timestamp; - int32_t delay_trade_status; + TradeStatus delay_trade_status; std::string delay_timestamp; int32_t sub_status; int32_t delay_sub_status; @@ -3635,4 +3736,4 @@ struct SharelistDetail } // namespace sharelist -} // namespace longbridge \ No newline at end of file +} // namespace longbridge diff --git a/cpp/src/convert.hpp b/cpp/src/convert.hpp index 8789e1a772..b32eefcfe2 100644 --- a/cpp/src/convert.hpp +++ b/cpp/src/convert.hpp @@ -2387,7 +2387,13 @@ inline fundamental::AssetAllocationResponse convert(const lb_asset_allocation_re // ── MarketContext conversions ───────────────────────────────────── inline market::MarketTimeItem convert(const lb_market_time_item_t* item) { - return { convert(item->market), item->trade_status, item->timestamp, item->delay_trade_status, item->delay_timestamp, item->sub_status, item->delay_sub_status }; + return { convert(item->market), + market::from_trade_status_code(item->trade_status), + item->timestamp, + market::from_trade_status_code(item->delay_trade_status), + item->delay_timestamp, + item->sub_status, + item->delay_sub_status }; } inline market::MarketStatusResponse convert(const lb_market_status_response_t* r) { std::vector v; diff --git a/cpp/src/types.cpp b/cpp/src/types.cpp index 3a8a382b0f..d589993509 100644 --- a/cpp/src/types.cpp +++ b/cpp/src/types.cpp @@ -42,4 +42,369 @@ DerivativeType::has_warrant() } // namespace quote -} // namespace longbridge \ No newline at end of file +namespace market { + +TradeStatus +from_trade_status_code(int32_t value) +{ + switch (value) { + case -1: + return TradeStatus::UNKNOWN; + case 0: + return TradeStatus::NO_REGISTER_QUOTE; + case 101: + return TradeStatus::CLEAN; + case 102: + return TradeStatus::OPEN_BID; + case 103: + return TradeStatus::MORNING_CLOSING; + case 105: + return TradeStatus::TRADING; + case 106: + return TradeStatus::NOON_CLOSING; + case 107: + return TradeStatus::CLOSE_BID; + case 108: + return TradeStatus::CLOSING; + case 110: + return TradeStatus::DARK_WAIT; + case 111: + return TradeStatus::DARK_TRADING; + case 112: + return TradeStatus::DARK_CLOSING; + case 120: + return TradeStatus::AFTER_FIX; + case 121: + return TradeStatus::HALF_CLOSING; + case 122: + return TradeStatus::NOT_OPENED; + case 123: + return TradeStatus::REALTIME_QUOTE; + case 201: + return TradeStatus::US_PREV; + case 202: + return TradeStatus::US_TRADING; + case 203: + return TradeStatus::US_AFTER; + case 204: + return TradeStatus::US_CLOSING; + case 205: + return TradeStatus::US_STOP; + case 206: + return TradeStatus::US_CLEAN; + case 207: + return TradeStatus::US_NIGHT; + case 209: + return TradeStatus::US_PREV_MARKET_CLEAN; + case 210: + return TradeStatus::US_AFTER_MARKET_CLEAN; + case 1000: + return TradeStatus::REFRESH; + case 1001: + return TradeStatus::DELIST; + case 1002: + return TradeStatus::PREPARE; + case 1003: + return TradeStatus::CODE_CHANGE; + case 1004: + return TradeStatus::STOP; + case 1005: + return TradeStatus::WILL_OPEN; + case 1006: + return TradeStatus::COMMON_SUSPEND; + case 1007: + return TradeStatus::EXPIRE; + case 1008: + return TradeStatus::NO_QUOTE; + case 1009: + return TradeStatus::UNITED; + case 1010: + return TradeStatus::TRADING_HALT; + case 1011: + return TradeStatus::WAIT_LISTING; + case 2001: + return TradeStatus::FUSE; + default: + return TradeStatus::UNKNOWN; + } +} + +int32_t +code(TradeStatus status) +{ + return static_cast(status); +} + +const char* +trade_status_as_string(TradeStatus status) +{ + switch (status) { + case TradeStatus::UNKNOWN: + return "UNKNOWN"; + case TradeStatus::NO_REGISTER_QUOTE: + return "NO_REGISTER_QUOTE"; + case TradeStatus::CLEAN: + return "CLEAN"; + case TradeStatus::OPEN_BID: + return "OPEN_BID"; + case TradeStatus::MORNING_CLOSING: + return "MORNING_CLOSING"; + case TradeStatus::TRADING: + return "TRADING"; + case TradeStatus::NOON_CLOSING: + return "NOON_CLOSING"; + case TradeStatus::CLOSE_BID: + return "CLOSE_BID"; + case TradeStatus::CLOSING: + return "CLOSING"; + case TradeStatus::DARK_WAIT: + return "DARK_WAIT"; + case TradeStatus::DARK_TRADING: + return "DARK_TRADING"; + case TradeStatus::DARK_CLOSING: + return "DARK_CLOSING"; + case TradeStatus::AFTER_FIX: + return "AFTER_FIX"; + case TradeStatus::HALF_CLOSING: + return "HALF_CLOSING"; + case TradeStatus::NOT_OPENED: + return "NOT_OPENED"; + case TradeStatus::REALTIME_QUOTE: + return "REALTIME_QUOTE"; + case TradeStatus::US_PREV: + return "US_PREV"; + case TradeStatus::US_TRADING: + return "US_TRADING"; + case TradeStatus::US_AFTER: + return "US_AFTER"; + case TradeStatus::US_CLOSING: + return "US_CLOSING"; + case TradeStatus::US_STOP: + return "US_STOP"; + case TradeStatus::US_CLEAN: + return "US_CLEAN"; + case TradeStatus::US_NIGHT: + return "US_NIGHT"; + case TradeStatus::US_PREV_MARKET_CLEAN: + return "US_PREV_MARKET_CLEAN"; + case TradeStatus::US_AFTER_MARKET_CLEAN: + return "US_AFTER_MARKET_CLEAN"; + case TradeStatus::REFRESH: + return "REFRESH"; + case TradeStatus::DELIST: + return "DELIST"; + case TradeStatus::PREPARE: + return "PREPARE"; + case TradeStatus::CODE_CHANGE: + return "CODE_CHANGE"; + case TradeStatus::STOP: + return "STOP"; + case TradeStatus::WILL_OPEN: + return "WILL_OPEN"; + case TradeStatus::COMMON_SUSPEND: + return "COMMON_SUSPEND"; + case TradeStatus::EXPIRE: + return "EXPIRE"; + case TradeStatus::NO_QUOTE: + return "NO_QUOTE"; + case TradeStatus::UNITED: + return "UNITED"; + case TradeStatus::TRADING_HALT: + return "TRADING_HALT"; + case TradeStatus::WAIT_LISTING: + return "WAIT_LISTING"; + case TradeStatus::FUSE: + return "FUSE"; + default: + return "UNKNOWN"; + } +} + +TradeStatus +normalize(TradeStatus status) +{ + switch (status) { + case TradeStatus::CLEAN: + return TradeStatus::CLOSING; + case TradeStatus::US_PREV_MARKET_CLEAN: + return TradeStatus::US_CLOSING; + case TradeStatus::US_CLEAN: + return TradeStatus::US_PREV; + case TradeStatus::US_AFTER_MARKET_CLEAN: + return TradeStatus::US_TRADING; + default: + return status; + } +} + +const char* +name(TradeStatus status) +{ + switch (normalize(status)) { + case TradeStatus::UNKNOWN: + case TradeStatus::NO_REGISTER_QUOTE: + return "Unknown"; + case TradeStatus::OPEN_BID: + return "Open Bid"; + case TradeStatus::MORNING_CLOSING: + return "Morning Break"; + case TradeStatus::TRADING: + case TradeStatus::US_TRADING: + return "Trading"; + case TradeStatus::NOON_CLOSING: + return "Mid-Day Break"; + case TradeStatus::CLOSE_BID: + return "Close Bid"; + case TradeStatus::CLOSING: + case TradeStatus::HALF_CLOSING: + case TradeStatus::US_CLOSING: + return "Closed"; + case TradeStatus::DARK_WAIT: + return "Dark Wait"; + case TradeStatus::DARK_TRADING: + return "Dark Trading"; + case TradeStatus::DARK_CLOSING: + return "Closing"; + case TradeStatus::AFTER_FIX: + return "After Fix"; + case TradeStatus::NOT_OPENED: + return "Not Open"; + case TradeStatus::REALTIME_QUOTE: + return "Temporary Break"; + case TradeStatus::US_PREV: + return "Pre-Market"; + case TradeStatus::US_AFTER: + return "Post-Market"; + case TradeStatus::US_STOP: + case TradeStatus::STOP: + return "Stop"; + case TradeStatus::US_NIGHT: + return "Overnight"; + case TradeStatus::REFRESH: + return "Refresh"; + case TradeStatus::DELIST: + return "Delist"; + case TradeStatus::PREPARE: + return "Prepare"; + case TradeStatus::CODE_CHANGE: + return "Code Change"; + case TradeStatus::WILL_OPEN: + return "Will Open"; + case TradeStatus::COMMON_SUSPEND: + return "Common Suspend"; + case TradeStatus::EXPIRE: + return "Expire"; + case TradeStatus::NO_QUOTE: + return "No Quote"; + case TradeStatus::UNITED: + return "Not Listed"; + case TradeStatus::TRADING_HALT: + return "Terminated"; + case TradeStatus::WAIT_LISTING: + return "Wait Listing"; + case TradeStatus::FUSE: + return "Fuse"; + default: + return "Unknown"; + } +} + +const char* +label(TradeStatus status) +{ + TradeStatus normalized = normalize(status); + switch (normalized) { + case TradeStatus::US_PREV: + case TradeStatus::US_TRADING: + case TradeStatus::US_AFTER: + case TradeStatus::US_NIGHT: + case TradeStatus::US_CLOSING: + case TradeStatus::TRADING: + case TradeStatus::CLOSING: + return name(normalized); + default: + return ""; + } +} + +bool +is_us_market(TradeStatus status) +{ + return code(status) >= 200 && code(status) < 300; +} + +bool +is_us_pre_post(TradeStatus status) +{ + return is_us_prev(status) || is_us_after(status); +} + +bool +is_us_night(TradeStatus status) +{ + return status == TradeStatus::US_NIGHT; +} + +bool +is_us_closing(TradeStatus status) +{ + return status == TradeStatus::US_CLOSING || + status == TradeStatus::US_PREV_MARKET_CLEAN; +} + +bool +is_closing(TradeStatus status) +{ + return status == TradeStatus::US_CLOSING || + status == TradeStatus::US_PREV_MARKET_CLEAN || + status == TradeStatus::CLOSING || status == TradeStatus::HALF_CLOSING; +} + +bool +is_us_prev(TradeStatus status) +{ + return status == TradeStatus::US_PREV || status == TradeStatus::US_CLEAN; +} + +bool +is_us_after(TradeStatus status) +{ + return status == TradeStatus::US_AFTER; +} + +bool +is_trading(TradeStatus status) +{ + return status == TradeStatus::TRADING || status == TradeStatus::US_TRADING || + status == TradeStatus::US_AFTER_MARKET_CLEAN; +} + +bool +is_dark(TradeStatus status) +{ + return status == TradeStatus::DARK_WAIT || + status == TradeStatus::DARK_TRADING || + status == TradeStatus::DARK_CLOSING; +} + +bool +allow_trading(TradeStatus status) +{ + return status == TradeStatus::OPEN_BID || status == TradeStatus::TRADING || + status == TradeStatus::CLOSE_BID || + status == TradeStatus::NOT_OPENED || + status == TradeStatus::NOON_CLOSING || + status == TradeStatus::US_TRADING || + status == TradeStatus::US_AFTER_MARKET_CLEAN; +} + +bool +is_special(TradeStatus status) +{ + return code(status) < 100 || status == TradeStatus::US_STOP || + code(status) >= 1000; +} + +} // namespace market + +} // namespace longbridge diff --git a/cpp/test/main.cpp b/cpp/test/main.cpp index 51f7015df6..0ea07f65d1 100644 --- a/cpp/test/main.cpp +++ b/cpp/test/main.cpp @@ -1,5 +1,7 @@ #include #include +#include +#include #ifdef WIN32 #include @@ -7,6 +9,45 @@ using namespace longbridge; +static_assert(std::is_same_v); +static_assert(std::is_same_v); + +static void +test_market_trade_status() +{ + using market::TradeStatus; + + assert(market::code(TradeStatus::US_TRADING) == 202); + assert(market::code(market::normalize(TradeStatus::US_CLEAN)) == 201); + assert(market::normalize(TradeStatus::CLEAN) == TradeStatus::CLOSING); + assert(market::label(TradeStatus::US_CLEAN) == std::string("Pre-Market")); + assert(std::string(market::label(TradeStatus::OPEN_BID)).empty()); + assert(market::name(TradeStatus::REALTIME_QUOTE) == + std::string("Temporary Break")); + assert(market::name(TradeStatus::UNITED) == std::string("Not Listed")); + assert(market::name(TradeStatus::TRADING_HALT) == std::string("Terminated")); + assert(market::name(TradeStatus::FUSE) == std::string("Fuse")); + assert(market::is_us_market(TradeStatus::US_TRADING)); + assert(market::is_us_pre_post(TradeStatus::US_AFTER)); + assert(market::is_us_night(TradeStatus::US_NIGHT)); + assert(market::is_us_closing(TradeStatus::US_CLOSING)); + assert(market::is_closing(TradeStatus::HALF_CLOSING)); + assert(market::is_us_prev(TradeStatus::US_CLEAN)); + assert(market::is_us_after(TradeStatus::US_AFTER)); + assert(market::is_trading(TradeStatus::US_AFTER_MARKET_CLEAN)); + assert(market::is_dark(TradeStatus::DARK_TRADING)); + assert(market::allow_trading(TradeStatus::NOON_CLOSING)); + assert(market::is_special(TradeStatus::TRADING_HALT)); + assert(market::from_trade_status_code(456) == TradeStatus::UNKNOWN); + assert(market::from_trade_status_code(2001) == TradeStatus::FUSE); + assert(market::trade_status_as_string(TradeStatus::US_CLEAN) == + std::string("US_CLEAN")); + assert(market::trade_status_as_string(TradeStatus::FUSE) == + std::string("FUSE")); +} + int main(int argc, char const* argv[]) { @@ -14,6 +55,8 @@ main(int argc, char const* argv[]) SetConsoleOutputCP(CP_UTF8); #endif + test_market_trade_status(); + Status status; Config config = Config::from_apikey_env(status); if (!status) {