Skip to main content

dhan_rs/api/
global_stocks.rs

1//! Global Stocks REST API endpoint implementations.
2
3use crate::client::{DhanClient, required_path_segment};
4use crate::error::{DhanError, Result};
5use crate::types::global_stocks::*;
6
7fn require_non_empty(name: &str, value: &str) -> Result<()> {
8    if value.trim().is_empty() {
9        return Err(DhanError::InvalidArgument(format!(
10            "{name} must not be empty"
11        )));
12    }
13    Ok(())
14}
15
16fn require_positive_decimal(name: &str, value: &str) -> Result<()> {
17    require_non_empty(name, value)?;
18    let decimal = value.parse::<f64>().map_err(|_| {
19        DhanError::InvalidArgument(format!("{name} must be a positive finite decimal string"))
20    })?;
21    if !decimal.is_finite() || decimal <= 0.0 {
22        return Err(DhanError::InvalidArgument(format!(
23            "{name} must be a positive finite decimal string"
24        )));
25    }
26    Ok(())
27}
28
29fn validate_optional_positive(name: &str, value: Option<f64>) -> Result<()> {
30    if let Some(value) = value {
31        if !value.is_finite() || value <= 0.0 {
32            return Err(DhanError::InvalidArgument(format!(
33                "{name} must be a positive finite number"
34            )));
35        }
36    }
37    Ok(())
38}
39
40fn validate_order_request(request: &GlobalStockOrderRequest) -> Result<()> {
41    require_non_empty("security_id", &request.security_id)?;
42    if let Some(correlation_id) = &request.correlation_id {
43        if correlation_id.chars().count() > 30 {
44            return Err(DhanError::InvalidArgument(
45                "correlation_id must not exceed 30 characters".into(),
46            ));
47        }
48    }
49    validate_optional_positive("quantity", request.quantity)?;
50    validate_optional_positive("price", request.price)?;
51    validate_optional_positive("trigger_price", request.trigger_price)?;
52    validate_optional_positive("stop_loss_price", request.stop_loss_price)?;
53    validate_optional_positive("target_price", request.target_price)?;
54    validate_optional_positive("amount", request.amount)?;
55    Ok(())
56}
57
58fn validate_modify_request(request: &GlobalStockModifyOrderRequest) -> Result<()> {
59    require_non_empty("security_id", &request.security_id)?;
60    validate_optional_positive("quantity", request.quantity)?;
61    validate_optional_positive("price", request.price)
62}
63
64fn validate_estimator_request(request: &GlobalStockEstimatorRequest) -> Result<()> {
65    require_non_empty("security_id", &request.security_id)?;
66    require_positive_decimal("price", &request.price)?;
67    require_positive_decimal("quantity", &request.quantity)
68}
69
70impl DhanClient {
71    /// Retrieve all Global Stocks orders.
72    ///
73    /// **Endpoint:** `GET /v2/globalstocks/orders`
74    pub async fn get_global_stock_orders(&self) -> Result<Vec<GlobalStockOrder>> {
75        self.get("/v2/globalstocks/orders").await
76    }
77
78    /// Place a Global Stocks order.
79    ///
80    /// **Endpoint:** `POST /v2/globalstocks/orders`
81    pub async fn place_global_stock_order(
82        &self,
83        request: &GlobalStockOrderRequest,
84    ) -> Result<GlobalStockOrderStatusResponse> {
85        validate_order_request(request)?;
86        self.post("/v2/globalstocks/orders", request).await
87    }
88
89    /// Retrieve a Global Stocks order by ID.
90    ///
91    /// **Endpoint:** `GET /v2/globalstocks/orders/{order-id}`
92    pub async fn get_global_stock_order(&self, order_id: &str) -> Result<GlobalStockOrder> {
93        let order_id = required_path_segment("order_id", order_id)?;
94        self.get(&format!("/v2/globalstocks/orders/{order_id}"))
95            .await
96    }
97
98    /// Modify a Global Stocks order.
99    ///
100    /// **Endpoint:** `PUT /v2/globalstocks/orders/{order-id}`
101    pub async fn modify_global_stock_order(
102        &self,
103        order_id: &str,
104        request: &GlobalStockModifyOrderRequest,
105    ) -> Result<GlobalStockOrderStatusResponse> {
106        let order_id = required_path_segment("order_id", order_id)?;
107        validate_modify_request(request)?;
108        self.put(&format!("/v2/globalstocks/orders/{order_id}"), request)
109            .await
110    }
111
112    /// Cancel a Global Stocks order.
113    ///
114    /// **Endpoint:** `DELETE /v2/globalstocks/orders/{order-id}`
115    pub async fn cancel_global_stock_order(
116        &self,
117        order_id: &str,
118    ) -> Result<GlobalStockOrderStatusResponse> {
119        let order_id = required_path_segment("order_id", order_id)?;
120        self.delete(&format!("/v2/globalstocks/orders/{order_id}"))
121            .await
122    }
123
124    /// Estimate charges for a Global Stocks transaction.
125    ///
126    /// **Endpoint:** `POST /v2/globalstocks/transEstimate`
127    pub async fn estimate_global_stock_order(
128        &self,
129        request: &GlobalStockEstimatorRequest,
130    ) -> Result<GlobalStockEstimatorResponse> {
131        validate_estimator_request(request)?;
132        self.post("/v2/globalstocks/transEstimate", request).await
133    }
134
135    /// Calculate Global Stocks margin requirements.
136    ///
137    /// **Endpoint:** `POST /v2/globalstocks/margincalculator`
138    pub async fn calculate_global_stock_margin(
139        &self,
140        request: &GlobalStockEstimatorRequest,
141    ) -> Result<GlobalStockMarginResponse> {
142        validate_estimator_request(request)?;
143        self.post("/v2/globalstocks/margincalculator", request)
144            .await
145    }
146
147    /// Retrieve all Global Stocks trades.
148    ///
149    /// **Endpoint:** `GET /v2/globalstocks/trades`
150    pub async fn get_global_stock_trades(&self) -> Result<Vec<GlobalStockTrade>> {
151        self.get("/v2/globalstocks/trades").await
152    }
153
154    /// Retrieve Global Stocks trades for one security.
155    ///
156    /// **Endpoint:** `GET /v2/globalstocks/trades/{security-id}`
157    pub async fn get_global_stock_trades_for_security(
158        &self,
159        security_id: &str,
160    ) -> Result<Vec<GlobalStockTrade>> {
161        let security_id = required_path_segment("security_id", security_id)?;
162        self.get(&format!("/v2/globalstocks/trades/{security_id}"))
163            .await
164    }
165
166    /// Retrieve Global Stocks market status.
167    ///
168    /// **Endpoint:** `GET /v2/globalstocks/marketstatus`
169    pub async fn get_global_stock_market_status(&self) -> Result<GlobalStockMarketStatus> {
170        self.get("/v2/globalstocks/marketstatus").await
171    }
172
173    /// Retrieve Global Stocks holdings.
174    ///
175    /// **Endpoint:** `GET /v2/globalstocks/holdings`
176    pub async fn get_global_stock_holdings(&self) -> Result<Vec<GlobalStockHolding>> {
177        self.get("/v2/globalstocks/holdings").await
178    }
179
180    /// Retrieve Global Stocks cash and margin limits.
181    ///
182    /// **Endpoint:** `GET /v2/globalstocks/fundlimit`
183    pub async fn get_global_stock_fund_limit(&self) -> Result<GlobalStockFundLimit> {
184        self.get("/v2/globalstocks/fundlimit").await
185    }
186}