kiteticker_async_manager/models/
order.rs

1use chrono::{DateTime, NaiveDateTime, Utc};
2use serde::{Deserialize, Serialize};
3use serde_json::Value;
4use serde_with::{serde_as, DefaultOnNull};
5
6use crate::Exchange;
7
8#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, PartialOrd)]
9#[serde(rename_all = "UPPERCASE")]
10pub enum OrderTransactionType {
11  Buy,
12  Sell,
13}
14
15#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, PartialOrd)]
16pub enum OrderValidity {
17  DAY,
18  IOC,
19  TTL,
20}
21
22#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, PartialOrd)]
23#[serde(rename_all = "UPPERCASE")]
24pub enum OrderStatus {
25  COMPLETE,
26  REJECTED,
27  CANCELLED,
28  UPDATE,
29}
30
31#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, PartialOrd)]
32#[serde(transparent)]
33pub struct TimeStamp(i64);
34
35impl From<String> for TimeStamp {
36  fn from(value: String) -> Self {
37    let secs = NaiveDateTime::parse_from_str(&value, "%Y-%m-%d %H:%M:%S")
38      .unwrap()
39      .and_utc()
40      .timestamp();
41    TimeStamp(secs)
42  }
43}
44
45impl From<TimeStamp> for String {
46  fn from(value: TimeStamp) -> Self {
47    DateTime::<Utc>::from_timestamp(value.0, 0)
48      .unwrap_or_default()
49      .naive_utc()
50      .format("%Y-%m-%d %H:%M:%S")
51      .to_string()
52  }
53}
54
55#[serde_with::serde_as]
56#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
57pub struct Order {
58  pub order_id: String,
59
60  #[serde_as(as = "DefaultOnNull")]
61  pub exchange_order_id: Option<String>,
62
63  #[serde_as(as = "DefaultOnNull")]
64  pub parent_order_id: Option<String>,
65
66  pub placed_by: String,
67  pub app_id: u64,
68
69  pub status: OrderStatus,
70
71  #[serde_as(as = "DefaultOnNull")]
72  pub status_message: Option<String>,
73
74  #[serde_as(as = "DefaultOnNull")]
75  pub status_message_raw: Option<String>,
76
77  pub tradingsymbol: String,
78  pub instrument_token: u32,
79
80  #[serde_as(as = "serde_with::FromInto<String>")]
81  pub exchange: Exchange,
82
83  pub order_type: String,
84  pub transaction_type: OrderTransactionType,
85
86  pub validity: OrderValidity,
87  pub variety: String,
88  pub product: Option<String>,
89
90  #[serde(default)]
91  pub average_price: f64,
92
93  #[serde(default)]
94  pub disclosed_quantity: f64,
95
96  pub price: f64,
97  pub quantity: u64,
98  pub filled_quantity: u64,
99
100  #[serde(default)]
101  pub unfilled_quantity: u64,
102
103  #[serde(default)]
104  pub pending_quantity: u64,
105
106  #[serde(default)]
107  pub cancelled_quantity: u64,
108
109  #[serde(default)]
110  pub trigger_price: f64,
111
112  pub user_id: String,
113
114  #[serde_as(as = "serde_with::FromInto<String>")]
115  pub order_timestamp: TimeStamp,
116  #[serde_as(as = "serde_with::FromInto<String>")]
117  pub exchange_timestamp: TimeStamp,
118  #[serde_as(as = "serde_with::FromInto<String>")]
119  pub exchange_update_timestamp: TimeStamp,
120
121  pub checksum: String,
122  #[serde(default)]
123  pub meta: Option<serde_json::Map<String, Value>>,
124
125  #[serde_as(as = "DefaultOnNull")]
126  #[serde(default)]
127  pub tag: Option<String>,
128}
129
130#[cfg(test)]
131mod tests {
132
133  use sha2::{Digest, Sha256};
134
135  use super::*;
136
137  #[test]
138  fn test_order() {
139    let postback_json = include_str!("../../kiteconnect-mocks/postback.json");
140    let exp_order = Order {
141      order_id: "220303000308932".to_string(),
142      exchange_order_id: Some("1000000001482421".to_string()),
143      parent_order_id: None,
144      placed_by: "AB1234".to_string(),
145      app_id: 1234,
146      status: OrderStatus::COMPLETE,
147      status_message: None,
148      status_message_raw: None,
149      tradingsymbol: "SBIN".to_string(),
150      instrument_token: 779521,
151      exchange: Exchange::NSE,
152      order_type: "MARKET".to_string(),
153      transaction_type: OrderTransactionType::Buy,
154      validity: OrderValidity::DAY,
155      variety: "regular".to_string(),
156      product: Some("CNC".to_string()),
157      average_price: 470.0,
158      disclosed_quantity: 0.0,
159      price: 0.0,
160      quantity: 1,
161      filled_quantity: 1,
162      unfilled_quantity: 0,
163      pending_quantity: 0,
164      cancelled_quantity: 0,
165      trigger_price: 0.0,
166      user_id: "AB1234".to_string(),
167      order_timestamp: TimeStamp(1646299465),
168      exchange_timestamp: TimeStamp(1646299465),
169      exchange_update_timestamp: TimeStamp(1646299465),
170      checksum:
171        "2011845d9348bd6795151bf4258102a03431e3bb12a79c0df73fcb4b7fde4b5d"
172          .to_string(),
173      meta: Some(serde_json::Map::new()),
174      tag: None,
175    };
176    let order = serde_json::from_str::<Order>(postback_json).unwrap();
177    assert_eq!(order.clone(), exp_order);
178
179    let mut hasher = Sha256::new();
180    hasher.update(order.order_id.as_bytes());
181    hasher.update(Into::<String>::into(order.order_timestamp));
182    hasher.update(b"0hdv7iw5examplesecret");
183    let expected = hasher.finalize();
184    let actual = hex::decode(order.checksum).unwrap();
185    assert_eq!(expected[..], actual[..]);
186  }
187}