kiteconnect_async_wasm/models/gtt/
triggers.rs1use crate::models::common::{Exchange, GttStatus, OrderType, Product, TransactionType};
2use chrono::{DateTime, Utc};
3use serde::{Deserialize, Serialize};
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
216 .map(|expires_at| expires_at - chrono::Utc::now())
217 }
218
219 pub fn is_single_trigger(&self) -> bool {
221 self.gtt_type == GTTTriggerType::Single
222 }
223
224 pub fn is_two_leg(&self) -> bool {
226 self.gtt_type == GTTTriggerType::TwoLeg
227 }
228
229 pub fn trigger_values(&self) -> &[f64] {
231 &self.condition.trigger_values
232 }
233
234 pub fn would_trigger(&self, current_price: f64) -> bool {
236 let last_price = self.condition.last_price;
237
238 self.condition.trigger_values.iter().any(|&trigger_price| {
239 (last_price <= trigger_price && current_price >= trigger_price)
241 || (last_price >= trigger_price && current_price <= trigger_price)
242 })
243 }
244
245 pub fn order_count(&self) -> usize {
247 self.orders.len()
248 }
249
250 pub fn has_successful_orders(&self) -> bool {
252 self.orders.iter().any(|order| {
253 order
254 .result
255 .as_ref()
256 .map(|result| !result.order_id.is_empty())
257 .unwrap_or(false)
258 })
259 }
260
261 pub fn successful_order_ids(&self) -> Vec<&str> {
263 self.orders
264 .iter()
265 .filter_map(|order| {
266 order.result.as_ref().and_then(|result| {
267 if !result.order_id.is_empty() {
268 Some(result.order_id.as_str())
269 } else {
270 None
271 }
272 })
273 })
274 .collect()
275 }
276
277 pub fn failed_orders(&self) -> Vec<(>TOrderParams, &str)> {
279 self.orders
280 .iter()
281 .filter_map(|order| {
282 order.result.as_ref().and_then(|result| {
283 result
284 .rejection_reason
285 .as_ref()
286 .map(|reason| (order, reason.as_str()))
287 })
288 })
289 .collect()
290 }
291}
292
293impl GTTCreateParams {
294 pub fn single(condition: GTTCondition, order: GTTOrderParams) -> 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(condition: GTTCondition, orders: Vec<GTTOrderParams>) -> Self {
306 Self {
307 gtt_type: GTTTriggerType::TwoLeg,
308 condition,
309 orders,
310 expires_at: None,
311 }
312 }
313
314 pub fn expires_at(mut self, expires_at: DateTime<Utc>) -> Self {
316 self.expires_at = Some(expires_at);
317 self
318 }
319
320 pub fn validate(&self) -> Result<(), String> {
322 if self.condition.trigger_values.is_empty() {
323 return Err("At least one trigger value is required".to_string());
324 }
325
326 if self.orders.is_empty() {
327 return Err("At least one order is required".to_string());
328 }
329
330 match self.gtt_type {
331 GTTTriggerType::Single => {
332 if self.orders.len() > 1 {
333 return Err("Single trigger GTT can have only one order".to_string());
334 }
335 if self.condition.trigger_values.len() > 1 {
336 return Err("Single trigger GTT can have only one trigger value".to_string());
337 }
338 }
339 GTTTriggerType::TwoLeg => {
340 if self.orders.len() != 2 {
341 return Err("Two-leg GTT must have exactly two orders".to_string());
342 }
343 if self.condition.trigger_values.len() != 2 {
344 return Err("Two-leg GTT must have exactly two trigger values".to_string());
345 }
346 }
347 }
348
349 Ok(())
350 }
351}
352
353impl GTTs {
354 pub fn active_gtts(&self) -> Vec<>T> {
356 self.triggers.iter().filter(|gtt| gtt.is_active()).collect()
357 }
358
359 pub fn triggered_gtts(&self) -> Vec<>T> {
361 self.triggers
362 .iter()
363 .filter(|gtt| gtt.is_triggered())
364 .collect()
365 }
366
367 pub fn expired_gtts(&self) -> Vec<>T> {
369 self.triggers
370 .iter()
371 .filter(|gtt| gtt.is_expired())
372 .collect()
373 }
374
375 pub fn gtts_for_symbol(&self, symbol: &str) -> Vec<>T> {
377 self.triggers
378 .iter()
379 .filter(|gtt| gtt.condition.trading_symbol == symbol)
380 .collect()
381 }
382
383 pub fn find_gtt(&self, gtt_id: u32) -> Option<>T> {
385 self.triggers.iter().find(|gtt| gtt.id == gtt_id)
386 }
387
388 pub fn expiring_soon(&self, hours: i64) -> Vec<>T> {
390 let threshold = chrono::Utc::now() + chrono::Duration::hours(hours);
391
392 self.active_gtts()
393 .into_iter()
394 .filter(|gtt| {
395 gtt.expires_at
396 .map(|expires_at| expires_at <= threshold)
397 .unwrap_or(false)
398 })
399 .collect()
400 }
401}