Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
18 changes: 15 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ current coverage boundary.

- `KisClient` builder with explicit real/mock environment selection and shared
`reqwest` client reuse.
- Redacted `AppCredentials`, `Account`, and `SecretString` helpers.
- Redacted `AppCredentials`, `Account`, `AccountProductCode`, and
`SecretString` helpers.
- OAuth token issuance, token revoke, and WebSocket approval-key issuance, with
in-memory token reuse and static bearer token injection for tests and mock
workflows.
Expand All @@ -36,9 +37,11 @@ current coverage boundary.
trading/account, quotation, market-analysis, and realtime-quotation
collections.
- Domain-scoped domestic futures/options inventory methods for 44
order/account, quotation, and realtime quotation endpoints.
order/account, quotation, and realtime quotation endpoints, with typed
operation-id newtypes for safer call sites.
- Domain-scoped inventory helpers for 29 domestic stock realtime tryitout
endpoints and 18 listed bond endpoints.
endpoints and 18 listed bond endpoints, with typed operation-id newtypes
available alongside the legacy string constants.
- Collection-specific overseas futures/options inventory wrapper covering all
35 order/account, quotation, and realtime endpoints from the bundled
official inventory.
Expand Down Expand Up @@ -138,6 +141,15 @@ 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. |

For new call sites, prefer the typed variants where they exist:
`Account::domestic_stock`, `InquirePriceRequest::with_market`,
`CashOrderRequest::with_order_division`,
`execute_domestic_stock_realtime_tryitout_operation`,
`execute_bond_*_operation`, and
`execute_domestic_futures_options_operation`. The older `String` fields,
string constants, and `&str` operation-id methods remain available for
compatibility.

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

Expand Down
150 changes: 150 additions & 0 deletions src/apis/bond.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use serde::de::DeserializeOwned;
use std::{fmt, str::FromStr};

use crate::{
client::{KisClient, KisEnvelope},
Expand Down Expand Up @@ -58,7 +59,120 @@ pub const BOND_QUOTATION_OPERATIONS: [&str; 8] = [
pub const BOND_REALTIME_TRYITOUT_OPERATIONS: [&str; 3] =
[REALTIME_TRADE, REALTIME_ASKING_PRICE, INDEX_REALTIME_TRADE];

#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
pub struct BondTradingAccountOperation(&'static str);

impl BondTradingAccountOperation {
pub const BUY_ORDER: Self = Self(BUY_ORDER);
pub const SELL_ORDER: Self = Self(SELL_ORDER);
pub const REVISE_CANCEL_ORDER: Self = Self(REVISE_CANCEL_ORDER);
pub const INQUIRE_REVERSIBLE_CANCELABLE_ORDERS: Self =
Self(INQUIRE_REVERSIBLE_CANCELABLE_ORDERS);
pub const INQUIRE_DAILY_EXECUTIONS: Self = Self(INQUIRE_DAILY_EXECUTIONS);
pub const INQUIRE_BALANCE: Self = Self(INQUIRE_BALANCE);
pub const INQUIRE_BUYABLE_ORDER: Self = Self(INQUIRE_BUYABLE_ORDER);

pub fn operation_id(self) -> &'static str {
self.0
}
}

impl fmt::Display for BondTradingAccountOperation {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.operation_id())
}
}

impl FromStr for BondTradingAccountOperation {
type Err = KisError;

fn from_str(value: &str) -> Result<Self, Self::Err> {
typed_operation_id(
value,
&BOND_TRADING_ACCOUNT_OPERATIONS,
"bond trading/account",
)
.map(Self)
}
}

#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
pub struct BondQuotationOperation(&'static str);

impl BondQuotationOperation {
pub const INQUIRE_ASKING_PRICE: Self = Self(INQUIRE_ASKING_PRICE);
pub const INQUIRE_PRICE: Self = Self(INQUIRE_PRICE);
pub const INQUIRE_EXECUTIONS: Self = Self(INQUIRE_EXECUTIONS);
pub const INQUIRE_DAILY_PRICE: Self = Self(INQUIRE_DAILY_PRICE);
pub const INQUIRE_DAILY_ITEM_CHART_PRICE: Self = Self(INQUIRE_DAILY_ITEM_CHART_PRICE);
pub const INQUIRE_AVG_UNIT: Self = Self(INQUIRE_AVG_UNIT);
pub const INQUIRE_ISSUE_INFO: Self = Self(INQUIRE_ISSUE_INFO);
pub const SEARCH_BOND_INFO: Self = Self(SEARCH_BOND_INFO);

pub fn operation_id(self) -> &'static str {
self.0
}
}

impl fmt::Display for BondQuotationOperation {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.operation_id())
}
}

impl FromStr for BondQuotationOperation {
type Err = KisError;

fn from_str(value: &str) -> Result<Self, Self::Err> {
typed_operation_id(value, &BOND_QUOTATION_OPERATIONS, "bond quotation").map(Self)
}
}

#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
pub struct BondRealtimeOperation(&'static str);

impl BondRealtimeOperation {
pub const REALTIME_TRADE: Self = Self(REALTIME_TRADE);
pub const REALTIME_ASKING_PRICE: Self = Self(REALTIME_ASKING_PRICE);
pub const INDEX_REALTIME_TRADE: Self = Self(INDEX_REALTIME_TRADE);

pub fn operation_id(self) -> &'static str {
self.0
}
}

impl fmt::Display for BondRealtimeOperation {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.operation_id())
}
}

impl FromStr for BondRealtimeOperation {
type Err = KisError;

fn from_str(value: &str) -> Result<Self, Self::Err> {
typed_operation_id(
value,
&BOND_REALTIME_TRYITOUT_OPERATIONS,
"bond realtime tryitout",
)
.map(Self)
}
}

impl KisClient {
pub async fn execute_bond_trading_account_operation<T>(
&self,
operation: BondTradingAccountOperation,
request: InventoryRequest,
) -> Result<KisEnvelope<T>, KisError>
where
T: DeserializeOwned,
{
self.execute_bond_trading_account(operation.operation_id(), request)
.await
}

pub async fn execute_bond_trading_account<T>(
&self,
operation_id: &str,
Expand All @@ -75,6 +189,18 @@ impl KisClient {
self.execute_inventory(operation_id, request).await
}

pub async fn execute_bond_quotation_operation<T>(
&self,
operation: BondQuotationOperation,
request: InventoryRequest,
) -> Result<KisEnvelope<T>, KisError>
where
T: DeserializeOwned,
{
self.execute_bond_quotation(operation.operation_id(), request)
.await
}

pub async fn execute_bond_quotation<T>(
&self,
operation_id: &str,
Expand All @@ -87,6 +213,18 @@ impl KisClient {
self.execute_inventory(operation_id, request).await
}

pub async fn execute_bond_realtime_tryitout_operation<T>(
&self,
operation: BondRealtimeOperation,
request: InventoryRequest,
) -> Result<KisEnvelope<T>, KisError>
where
T: DeserializeOwned,
{
self.execute_bond_realtime_tryitout(operation.operation_id(), request)
.await
}

pub async fn execute_bond_realtime_tryitout<T>(
&self,
operation_id: &str,
Expand All @@ -104,6 +242,18 @@ impl KisClient {
}
}

fn typed_operation_id(
operation_id: &str,
allowed: &[&'static str],
label: &str,
) -> Result<&'static str, KisError> {
allowed
.iter()
.copied()
.find(|candidate| *candidate == operation_id)
.ok_or_else(|| KisError::Validation(format!("{operation_id} is not a {label} operation")))
}

fn ensure_operation(operation_id: &str, allowed: &[&str], label: &str) -> Result<(), KisError> {
if allowed.contains(&operation_id) {
Ok(())
Expand Down
53 changes: 53 additions & 0 deletions src/apis/domestic_futures_options.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use serde::de::DeserializeOwned;
use std::{fmt, str::FromStr};

use crate::{
client::{KisClient, KisEnvelope},
Expand Down Expand Up @@ -66,7 +67,59 @@ pub fn operation_ids() -> impl Iterator<Item = &'static str> {
.chain(REALTIME_QUOTATION_OPERATION_IDS)
}

#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
pub struct DomesticFuturesOptionsOperation(&'static str);

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

pub fn operation_id(self) -> &'static str {
self.0
}
}

impl fmt::Display for DomesticFuturesOptionsOperation {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.operation_id())
}
}

impl FromStr for DomesticFuturesOptionsOperation {
type Err = KisError;

fn from_str(value: &str) -> Result<Self, Self::Err> {
operation_ids()
.find(|candidate| *candidate == value)
.map(Self)
.ok_or_else(|| {
KisError::Validation(format!(
"operation id {value} is not a domestic futures/options endpoint"
))
})
}
}

impl KisClient {
pub async fn execute_domestic_futures_options_operation<T>(
&self,
operation: DomesticFuturesOptionsOperation,
request: InventoryRequest,
) -> Result<KisEnvelope<T>, KisError>
where
T: DeserializeOwned,
{
self.execute_domestic_futures_options(operation.operation_id(), request)
.await
}

pub async fn execute_domestic_futures_options<T>(
&self,
operation_id: &str,
Expand Down
Loading
Loading