kiteconnect_async_wasm/models/gtt/
triggers.rs1use serde::{Deserialize, Serialize};
2use chrono::{DateTime, Utc};
3use crate::models::common::{Exchange, Product, TransactionType, OrderType, GttStatus};
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
7pub struct GTTCondition {
8 pub exchange: Exchange,
10
11 #[serde(rename = "tradingsymbol")]
13 pub trading_symbol: String,
14
15 #[serde(rename = "trigger_values")]
17 pub trigger_values: Vec<f64>,
18
19 #[serde(rename = "last_price")]
21 pub last_price: f64,
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct GTTOrderParams {
27 pub exchange: Exchange,
29
30 #[serde(rename = "tradingsymbol")]
32 pub trading_symbol: String,
33
34 #[serde(rename = "transaction_type")]
36 pub transaction_type: TransactionType,
37
38 #[serde(rename = "order_type")]
40 pub order_type: OrderType,
41
42 pub product: Product,
44
45 pub quantity: u32,
47
48 pub price: f64,
50
51 pub result: Option<GTTOrderResult>,
53}
54
55#[derive(Debug, Clone, Serialize, Deserialize)]
57pub struct GTTOrderResult {
58 #[serde(rename = "order_id")]
60 pub order_id: String,
61
62 #[serde(rename = "rejection_reason")]
64 pub rejection_reason: Option<String>,
65}
66
67#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
69#[serde(rename_all = "lowercase")]
70pub enum GTTTriggerType {
71 Single,
73 #[serde(rename = "two-leg")]
75 TwoLeg,
76}
77
78#[derive(Debug, Clone, Serialize, Deserialize)]
80pub struct GTT {
81 pub id: u32,
83
84 #[serde(rename = "user_id")]
86 pub user_id: String,
87
88 #[serde(rename = "parent_trigger")]
90 pub parent_trigger: Option<u32>,
91
92 #[serde(rename = "type")]
94 pub gtt_type: GTTTriggerType,
95
96 #[serde(rename = "created_at")]
98 pub created_at: DateTime<Utc>,
99
100 #[serde(rename = "updated_at")]
102 pub updated_at: DateTime<Utc>,
103
104 #[serde(rename = "expires_at")]
106 pub expires_at: Option<DateTime<Utc>>,
107
108 pub status: GttStatus,
110
111 pub condition: GTTCondition,
113
114 pub orders: Vec<GTTOrderParams>,
116
117 pub meta: Option<serde_json::Value>,
119}
120
121#[derive(Debug, Clone, Serialize, Deserialize)]
123pub struct GTTCreateParams {
124 #[serde(rename = "type")]
126 pub gtt_type: GTTTriggerType,
127
128 pub condition: GTTCondition,
130
131 pub orders: Vec<GTTOrderParams>,
133
134 #[serde(rename = "expires_at", skip_serializing_if = "Option::is_none")]
136 pub expires_at: Option<DateTime<Utc>>,
137}
138
139#[derive(Debug, Clone, Serialize, Deserialize)]
141pub struct GTTModifyParams {
142 #[serde(skip_serializing)]
144 pub gtt_id: u32,
145
146 #[serde(skip_serializing_if = "Option::is_none")]
148 pub condition: Option<GTTCondition>,
149
150 #[serde(skip_serializing_if = "Option::is_none")]
152 pub orders: Option<Vec<GTTOrderParams>>,
153
154 #[serde(rename = "expires_at", skip_serializing_if = "Option::is_none")]
156 pub expires_at: Option<DateTime<Utc>>,
157}
158
159#[derive(Debug, Clone, Serialize, Deserialize)]
161pub struct GTTResponse {
162 pub id: u32,
164}
165
166#[derive(Debug, Clone, Serialize, Deserialize)]
168pub struct GTTs {
169 pub triggers: Vec<GTT>,
171}
172
173impl GTT {
174 pub fn is_active(&self) -> bool {
176 self.status == GttStatus::Active
177 }
178
179 pub fn is_triggered(&self) -> bool {
181 self.status == GttStatus::Triggered
182 }
183
184 pub fn is_disabled(&self) -> bool {
186 self.status == GttStatus::Disabled
187 }
188
189 pub fn is_expired(&self) -> bool {
191 self.status == GttStatus::Expired
192 }
193
194 pub fn is_cancelled(&self) -> bool {
196 self.status == GttStatus::Cancelled
197 }
198
199 pub fn is_rejected(&self) -> bool {
201 self.status == GttStatus::Rejected
202 }
203
204 pub fn has_time_expired(&self) -> bool {
206 if let Some(expires_at) = self.expires_at {
207 chrono::Utc::now() > expires_at
208 } else {
209 false
210 }
211 }
212
213 pub fn time_to_expiry(&self) -> Option<chrono::Duration> {
215 self.expires_at.map(|expires_at| expires_at - chrono::Utc::now())
216 }
217
218 pub fn is_single_trigger(&self) -> bool {
220 self.gtt_type == GTTTriggerType::Single
221 }
222
223 pub fn is_two_leg(&self) -> bool {
225 self.gtt_type == GTTTriggerType::TwoLeg
226 }
227
228 pub fn trigger_values(&self) -> &[f64] {
230 &self.condition.trigger_values
231 }
232
233 pub fn would_trigger(&self, current_price: f64) -> bool {
235 let last_price = self.condition.last_price;
236
237 self.condition.trigger_values.iter().any(|&trigger_price| {
238 (last_price <= trigger_price && current_price >= trigger_price) ||
240 (last_price >= trigger_price && current_price <= trigger_price)
241 })
242 }
243
244 pub fn order_count(&self) -> usize {
246 self.orders.len()
247 }
248
249 pub fn has_successful_orders(&self) -> bool {
251 self.orders.iter().any(|order| {
252 order.result.as_ref()
253 .map(|result| !result.order_id.is_empty())
254 .unwrap_or(false)
255 })
256 }
257
258 pub fn successful_order_ids(&self) -> Vec<&str> {
260 self.orders
261 .iter()
262 .filter_map(|order| {
263 order.result.as_ref()
264 .and_then(|result| {
265 if !result.order_id.is_empty() {
266 Some(result.order_id.as_str())
267 } else {
268 None
269 }
270 })
271 })
272 .collect()
273 }
274
275 pub fn failed_orders(&self) -> Vec<(>TOrderParams, &str)> {
277 self.orders
278 .iter()
279 .filter_map(|order| {
280 order.result.as_ref()
281 .and_then(|result| {
282 result.rejection_reason.as_ref()
283 .map(|reason| (order, reason.as_str()))
284 })
285 })
286 .collect()
287 }
288}
289
290impl GTTCreateParams {
291 pub fn single(
293 condition: GTTCondition,
294 order: GTTOrderParams,
295 ) -> Self {
296 Self {
297 gtt_type: GTTTriggerType::Single,
298 condition,
299 orders: vec![order],
300 expires_at: None,
301 }
302 }
303
304 pub fn two_leg(
306 condition: GTTCondition,
307 orders: Vec<GTTOrderParams>,
308 ) -> Self {
309 Self {
310 gtt_type: GTTTriggerType::TwoLeg,
311 condition,
312 orders,
313 expires_at: None,
314 }
315 }
316
317 pub fn expires_at(mut self, expires_at: DateTime<Utc>) -> Self {
319 self.expires_at = Some(expires_at);
320 self
321 }
322
323 pub fn validate(&self) -> Result<(), String> {
325 if self.condition.trigger_values.is_empty() {
326 return Err("At least one trigger value is required".to_string());
327 }
328
329 if self.orders.is_empty() {
330 return Err("At least one order is required".to_string());
331 }
332
333 match self.gtt_type {
334 GTTTriggerType::Single => {
335 if self.orders.len() > 1 {
336 return Err("Single trigger GTT can have only one order".to_string());
337 }
338 if self.condition.trigger_values.len() > 1 {
339 return Err("Single trigger GTT can have only one trigger value".to_string());
340 }
341 }
342 GTTTriggerType::TwoLeg => {
343 if self.orders.len() != 2 {
344 return Err("Two-leg GTT must have exactly two orders".to_string());
345 }
346 if self.condition.trigger_values.len() != 2 {
347 return Err("Two-leg GTT must have exactly two trigger values".to_string());
348 }
349 }
350 }
351
352 Ok(())
353 }
354}
355
356impl GTTs {
357 pub fn active_gtts(&self) -> Vec<>T> {
359 self.triggers.iter().filter(|gtt| gtt.is_active()).collect()
360 }
361
362 pub fn triggered_gtts(&self) -> Vec<>T> {
364 self.triggers.iter().filter(|gtt| gtt.is_triggered()).collect()
365 }
366
367 pub fn expired_gtts(&self) -> Vec<>T> {
369 self.triggers.iter().filter(|gtt| gtt.is_expired()).collect()
370 }
371
372 pub fn gtts_for_symbol(&self, symbol: &str) -> Vec<>T> {
374 self.triggers
375 .iter()
376 .filter(|gtt| gtt.condition.trading_symbol == symbol)
377 .collect()
378 }
379
380 pub fn find_gtt(&self, gtt_id: u32) -> Option<>T> {
382 self.triggers.iter().find(|gtt| gtt.id == gtt_id)
383 }
384
385 pub fn expiring_soon(&self, hours: i64) -> Vec<>T> {
387 let threshold = chrono::Utc::now() + chrono::Duration::hours(hours);
388
389 self.active_gtts()
390 .into_iter()
391 .filter(|gtt| {
392 gtt.expires_at
393 .map(|expires_at| expires_at <= threshold)
394 .unwrap_or(false)
395 })
396 .collect()
397 }
398}