Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 20 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ more ergonomic typed wrappers.
workflows.
- Typed domestic stock methods for quotation price, balance inquiry, and cash
order calls.
- Domain-scoped domestic futures/options inventory methods for 44
order/account, quotation, and realtime quotation endpoints.
- Domain-scoped inventory helpers for 29 domestic stock realtime tryitout
endpoints and 18 listed bond endpoints.
- Collection-specific overseas futures/options inventory wrapper covering all
Expand Down Expand Up @@ -123,10 +125,25 @@ The typed SDK currently exposes:
| `execute_bond_realtime_tryitout` | `/tryitout/*` | Domain-scoped inventory execution for 3 listed bond realtime tryitout/mock-contract endpoints. This is not a live WebSocket subscription API. |
| `execute_overseas_futures_options` | 35 overseas futures/options inventory endpoints | Collection-specific wrapper keyed by `OverseasFuturesOptionsEndpoint`; all bundled endpoints are real-only, required fields are validated from inventory, and real trading mutations are locally blocked. |

The domestic futures/options SDK surface exposes inventory-backed domain
methods for all 44 endpoints in these bundled official collections:

| Collection | Endpoint count | SDK entry point |
| --- | ---: | --- |
| Domestic futures/options trading/account | 15 | `execute_domestic_futures_options_trading_account` |
| Domestic futures/options quotations | 9 | `execute_domestic_futures_options_quotation` |
| Domestic futures/options realtime quotations | 20 | `execute_domestic_futures_options_realtime_quotation` |

The same endpoints are also available through the combined
`execute_domestic_futures_options` method. Operation ids are exposed through
`kis_sdk::apis::domestic_futures_options::{TRADING_ACCOUNT_OPERATION_IDS,
QUOTATION_OPERATION_IDS, REALTIME_QUOTATION_OPERATION_IDS}`.

The bundled inventory covers 338 official endpoints across 22 collections.
Endpoints outside the typed SDK surface do not yet have ergonomic typed Rust
request methods, but they can be addressed and called through the lower-level
inventory execution API with stable operation ids:
Endpoints outside the typed domestic stock methods and domain-scoped inventory
surfaces do not yet have ergonomic typed Rust request methods, but they can be
addressed and called through the lower-level inventory execution API with
stable operation ids:

```rust
use kis_sdk::endpoint::InventoryRequest;
Expand Down
28 changes: 25 additions & 3 deletions docs/contract-quality-report.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,26 @@ The initial typed SDK surface intentionally exposes a narrow domestic stock slic
| `place_domestic_stock_cash_order` | POST | `/uapi/domestic-stock/v1/trading/order-cash` | Covered | Buy/sell TR IDs are selected by side and environment. Real trading is locally blocked by `KisError::LiveTradingDisabled` before network I/O. |
| `execute_overseas_futures_options` | Mixed | 35 `[해외선물옵션]` order/account, quotation, and realtime endpoints | Covered | Uses `OverseasFuturesOptionsEndpoint` enum plus bundled inventory validation. The whole slice is real-only in the captured contract, mock mode rejects it locally, live trading mutations remain disabled, and ambiguous revision/cancel TR IDs require caller override. |

## Domestic Futures/Options SDK Coverage

The domestic futures/options child surface exposes scoped inventory-backed SDK
methods for all 44 endpoints in the requested official collections:

| Collection | Endpoint count | Mock support in inventory | SDK coverage |
| --- | ---: | ---: | --- |
| Domestic futures/options trading/account | 15 | 5 real+mock, 10 real-only | Covered by `execute_domestic_futures_options_trading_account` and `TRADING_ACCOUNT_OPERATION_IDS`. |
| Domestic futures/options quotations | 9 | 3 real+mock, 6 real-only | Covered by `execute_domestic_futures_options_quotation` and `QUOTATION_OPERATION_IDS`. |
| Domestic futures/options realtime | 20 | 1 real+mock, 19 real-only | Covered by `execute_domestic_futures_options_realtime_quotation` and `REALTIME_QUOTATION_OPERATION_IDS`. |

The combined `execute_domestic_futures_options` method accepts the same 44
operation ids and rejects operation ids outside the domestic futures/options
collections before network I/O. Order-changing endpoints retain local
`KisError::LiveTradingDisabled` protection in `Environment::Real`, and
side/session-specific TR ID metadata requires explicit
`InventoryRequest::tr_id_override(...)` selection.

## Additional Domain-Scoped SDK Coverage

Domain-scoped inventory helpers also expose stable operation-id constants and
execution methods for these follow-on slices:

Expand All @@ -61,7 +81,7 @@ execution methods for these follow-on slices:

The remaining official endpoints are represented in the bundled contract and
mock route inventory, but are not yet promoted to typed SDK request/response
methods.
methods or domain-scoped wrappers.

## Domestic Stock REST Inventory API

Expand Down Expand Up @@ -89,8 +109,8 @@ Executable coverage added in `tests/mock_server_contract.rs` and

- Validates source metadata: official URL, checked date, 338 endpoints, 22 collections.
- Verifies route index cardinality equals the official endpoint count.
- Verifies domestic stock realtime and listed bond domain helper constants cover
their 47 targeted inventory endpoints exactly.
- Verifies domestic futures/options, domestic stock realtime, and listed bond
domain helper constants cover their 91 targeted inventory endpoints exactly.
- Starts the mock server and requests every bundled endpoint.
- Confirms 3 auth endpoints return success.
- Confirms 43 non-auth `real+mock` endpoints return KIS success envelopes when required headers/TR IDs are supplied.
Expand All @@ -103,6 +123,8 @@ Executable coverage added in `tests/mock_server_contract.rs` and
- Real-to-mock fallback is opt-in and read-only. POST trading fallback is rejected by policy.
- Fallback requires separate fallback credentials and fallback bearer token, preventing primary real credentials from crossing into the mock fallback target.
- Real cash orders are blocked locally by `KisError::LiveTradingDisabled` before network I/O.
- Domestic futures/options order mutations are blocked locally by `KisError::LiveTradingDisabled` before network I/O.
- Domestic futures/options side/session-specific order TR ID metadata requires the caller to choose the concrete TR ID.
- Overseas futures/options order mutations are blocked locally by `KisError::LiveTradingDisabled` before network I/O.
- Overseas futures/options revision/cancel keeps the captured ambiguous TR ID boundary and requires the caller to choose the concrete TR ID.
- Account/order request validation rejects malformed account, product, quantity, and price fields before network I/O.
Expand Down
44 changes: 41 additions & 3 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ their own secret-management path.
The typed SDK surface currently covers OAuth token issuance and revoke,
WebSocket approval-key issuance, domestic stock price inquiry, domestic stock
balance inquiry, and domestic stock cash-order requests. Domain-scoped
inventory helpers cover 29 domestic stock realtime tryitout endpoints and 18
listed bond endpoints. The shared inventory-backed execution API can also call
every endpoint captured in
inventory helpers cover domestic futures/options, 29 domestic stock realtime
tryitout endpoints, and 18 listed bond endpoints. The shared inventory-backed
execution API can also call every endpoint captured in
`contracts/kis_official_endpoint_inventory.compact.json` by stable operation id
while follow-on work adds more ergonomic typed wrappers.

Expand Down Expand Up @@ -202,6 +202,44 @@ filled by the client. Endpoints with ambiguous TR IDs require
`Environment::Mock`, and real trading mutations remain blocked locally by
`KisError::LiveTradingDisabled`.

## Call A Domestic Futures/Options Endpoint

Domestic futures/options coverage is exposed as a scoped inventory API for 44
official endpoints: 15 trading/account endpoints, 9 quotation endpoints, and 20
realtime quotation endpoints. The operation id constants are available from
`kis_sdk::apis::domestic_futures_options`.

```rust
use kis_sdk::{
apis::domestic_futures_options::QUOTATION_OPERATION_IDS,
endpoint::InventoryRequest,
};
use serde_json::json;

async fn domestic_futures_options_quote(
client: &kis_sdk::KisClient,
) -> Result<(), kis_sdk::KisError> {
let response = client
.execute_domestic_futures_options_quotation::<serde_json::Value>(
QUOTATION_OPERATION_IDS[0],
InventoryRequest::new().query(json!({
"FID_COND_MRKT_DIV_CODE": "F",
"FID_INPUT_ISCD": "101W09"
})),
)
.await?;

assert!(response.is_success());
Ok(())
}
```

Order-changing domestic futures/options endpoints keep the same SDK safety
rules as other trading mutations. Inventory metadata with side/session-specific
TR ID text requires `InventoryRequest::tr_id_override(...)`, and real
environment trading mutations are blocked locally by
`KisError::LiveTradingDisabled`.

## Call A Realtime Tryitout Endpoint

Realtime domain helpers use the REST-style `/tryitout/*` shape preserved in the
Expand Down
1 change: 1 addition & 0 deletions src/apis.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod bond;
pub mod domestic_futures_options;
pub mod domestic_stock;
pub mod domestic_stock_realtime;
pub mod overseas_futures_options;
153 changes: 153 additions & 0 deletions src/apis/domestic_futures_options.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
use serde::de::DeserializeOwned;

use crate::{
client::{KisClient, KisEnvelope},
endpoint::InventoryRequest,
error::KisError,
};

pub const TRADING_ACCOUNT_OPERATION_IDS: [&str; 15] = [
"domestic_futures_options_trading_account.post_domestic_futureoption_trading_order",
"domestic_futures_options_trading_account.post_domestic_futureoption_trading_order_rvsecncl",
"domestic_futures_options_trading_account.get_domestic_futureoption_trading_inquire_ccnl",
"domestic_futures_options_trading_account.get_domestic_futureoption_trading_inquire_balance",
"domestic_futures_options_trading_account.get_domestic_futureoption_trading_inquire_psbl_order",
"domestic_futures_options_trading_account.get_domestic_futureoption_trading_inquire_ngt_ccnl",
"domestic_futures_options_trading_account.get_domestic_futureoption_trading_inquire_ngt_balance",
"domestic_futures_options_trading_account.get_domestic_futureoption_trading_inquire_psbl_ngt_order",
"domestic_futures_options_trading_account.get_domestic_futureoption_trading_ngt_margin_detail",
"domestic_futures_options_trading_account.get_domestic_futureoption_trading_inquire_balance_settlement_pl",
"domestic_futures_options_trading_account.get_domestic_futureoption_trading_inquire_deposit",
"domestic_futures_options_trading_account.get_domestic_futureoption_trading_inquire_balance_valuation_pl",
"domestic_futures_options_trading_account.get_domestic_futureoption_trading_inquire_ccnl_bstime",
"domestic_futures_options_trading_account.get_domestic_futureoption_trading_inquire_daily_amount_fee",
"domestic_futures_options_trading_account.get_domestic_futureoption_quotations_margin_rate",
];

pub const QUOTATION_OPERATION_IDS: [&str; 9] = [
"domestic_futures_options_quotation.get_domestic_futureoption_quotations_inquire_price",
"domestic_futures_options_quotation.get_domestic_futureoption_quotations_inquire_asking_price",
"domestic_futures_options_quotation.get_domestic_futureoption_quotations_inquire_daily_fuopchartprice",
"domestic_futures_options_quotation.get_domestic_futureoption_quotations_inquire_time_fuopchartprice",
"domestic_futures_options_quotation.get_domestic_futureoption_quotations_display_board_option_list",
"domestic_futures_options_quotation.get_domestic_futureoption_quotations_display_board_top",
"domestic_futures_options_quotation.get_domestic_futureoption_quotations_display_board_callput",
"domestic_futures_options_quotation.get_domestic_futureoption_quotations_display_board_futures",
"domestic_futures_options_quotation.get_domestic_futureoption_quotations_exp_price_trend",
];

pub const REALTIME_QUOTATION_OPERATION_IDS: [&str; 20] = [
"domestic_futures_options_realtime_quotation.post_tryitout_h0ifasp0",
"domestic_futures_options_realtime_quotation.post_tryitout_h0ifcnt0",
"domestic_futures_options_realtime_quotation.post_tryitout_h0ioasp0",
"domestic_futures_options_realtime_quotation.post_tryitout_h0iocnt0",
"domestic_futures_options_realtime_quotation.post_tryitout_h0ifcni0",
"domestic_futures_options_realtime_quotation.post_tryitout_h0cfasp0",
"domestic_futures_options_realtime_quotation.post_tryitout_h0cfcnt0",
"domestic_futures_options_realtime_quotation.post_tryitout_h0zfasp0",
"domestic_futures_options_realtime_quotation.post_tryitout_h0zfcnt0",
"domestic_futures_options_realtime_quotation.post_tryitout_h0zfanc0",
"domestic_futures_options_realtime_quotation.post_tryitout_h0zoasp0",
"domestic_futures_options_realtime_quotation.post_tryitout_h0zocnt0",
"domestic_futures_options_realtime_quotation.post_tryitout_h0zoanc0",
"domestic_futures_options_realtime_quotation.post_tryitout_h0euasp0",
"domestic_futures_options_realtime_quotation.post_tryitout_h0eucnt0",
"domestic_futures_options_realtime_quotation.post_tryitout_h0euanc0",
"domestic_futures_options_realtime_quotation.post_tryitout_h0eucni0",
"domestic_futures_options_realtime_quotation.post_tryitout_h0mfasp0",
"domestic_futures_options_realtime_quotation.post_tryitout_h0mfcnt0",
"domestic_futures_options_realtime_quotation.post_tryitout_h0mfcni0",
];

pub fn operation_ids() -> impl Iterator<Item = &'static str> {
TRADING_ACCOUNT_OPERATION_IDS
.into_iter()
.chain(QUOTATION_OPERATION_IDS)
.chain(REALTIME_QUOTATION_OPERATION_IDS)
}

impl KisClient {
pub async fn execute_domestic_futures_options<T>(
&self,
operation_id: &str,
request: InventoryRequest,
) -> Result<KisEnvelope<T>, KisError>
where
T: DeserializeOwned,
{
validate_domestic_futures_options_operation(operation_id)?;
self.execute_inventory(operation_id, request).await
}

pub async fn execute_domestic_futures_options_trading_account<T>(
&self,
operation_id: &str,
request: InventoryRequest,
) -> Result<KisEnvelope<T>, KisError>
where
T: DeserializeOwned,
{
validate_collection_operation(
"domestic futures/options trading/account",
operation_id,
&TRADING_ACCOUNT_OPERATION_IDS,
)?;
self.execute_inventory(operation_id, request).await
}

pub async fn execute_domestic_futures_options_quotation<T>(
&self,
operation_id: &str,
request: InventoryRequest,
) -> Result<KisEnvelope<T>, KisError>
where
T: DeserializeOwned,
{
validate_collection_operation(
"domestic futures/options quotation",
operation_id,
&QUOTATION_OPERATION_IDS,
)?;
self.execute_inventory(operation_id, request).await
}

pub async fn execute_domestic_futures_options_realtime_quotation<T>(
&self,
operation_id: &str,
request: InventoryRequest,
) -> Result<KisEnvelope<T>, KisError>
where
T: DeserializeOwned,
{
validate_collection_operation(
"domestic futures/options realtime quotation",
operation_id,
&REALTIME_QUOTATION_OPERATION_IDS,
)?;
self.execute_inventory(operation_id, request).await
}
}

fn validate_domestic_futures_options_operation(operation_id: &str) -> Result<(), KisError> {
if operation_ids().any(|candidate| candidate == operation_id) {
Ok(())
} else {
Err(KisError::Validation(format!(
"operation id {operation_id} is not a domestic futures/options endpoint"
)))
}
}

fn validate_collection_operation(
collection: &str,
operation_id: &str,
allowed: &[&str],
) -> Result<(), KisError> {
if allowed.contains(&operation_id) {
Ok(())
} else {
Err(KisError::Validation(format!(
"operation id {operation_id} is not a {collection} endpoint"
)))
}
}
Loading
Loading