dhan_rs/api/
global_stocks.rs1use 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 pub async fn get_global_stock_orders(&self) -> Result<Vec<GlobalStockOrder>> {
75 self.get("/v2/globalstocks/orders").await
76 }
77
78 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 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 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 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 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 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 pub async fn get_global_stock_trades(&self) -> Result<Vec<GlobalStockTrade>> {
151 self.get("/v2/globalstocks/trades").await
152 }
153
154 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 pub async fn get_global_stock_market_status(&self) -> Result<GlobalStockMarketStatus> {
170 self.get("/v2/globalstocks/marketstatus").await
171 }
172
173 pub async fn get_global_stock_holdings(&self) -> Result<Vec<GlobalStockHolding>> {
177 self.get("/v2/globalstocks/holdings").await
178 }
179
180 pub async fn get_global_stock_fund_limit(&self) -> Result<GlobalStockFundLimit> {
184 self.get("/v2/globalstocks/fundlimit").await
185 }
186}