1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use sqlx::FromRow;
4
5#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
8pub struct MarketHierarchyNode {
9 pub id: String,
11 pub name: String,
13 pub parent_id: Option<String>,
15 pub exchange: String,
17 pub level: i32,
19 pub path: String,
21 pub created_at: DateTime<Utc>,
23 pub updated_at: DateTime<Utc>,
25}
26
27#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
30pub struct MarketInstrument {
31 pub epic: String,
33 pub instrument_name: String,
35 pub instrument_type: String,
37 pub node_id: String,
39 pub exchange: String,
41 pub expiry: String,
43 pub high_limit_price: Option<f64>,
45 pub low_limit_price: Option<f64>,
47 pub market_status: String,
49 pub net_change: Option<f64>,
51 pub percentage_change: Option<f64>,
53 pub update_time: Option<String>,
55 pub update_time_utc: Option<DateTime<Utc>>,
57 pub bid: Option<f64>,
59 pub offer: Option<f64>,
61 pub created_at: DateTime<Utc>,
63 pub updated_at: DateTime<Utc>,
65}
66
67impl MarketInstrument {
68 pub fn is_call(&self) -> bool {
81 self.instrument_name.contains("CALL")
82 }
83
84 pub fn is_put(&self) -> bool {
96 self.instrument_name.contains("PUT")
97 }
98}
99
100impl MarketHierarchyNode {
101 pub fn new(
103 id: String,
104 name: String,
105 parent_id: Option<String>,
106 exchange: String,
107 level: i32,
108 path: String,
109 ) -> Self {
110 let now = Utc::now();
111 Self {
112 id,
113 name,
114 parent_id,
115 exchange,
116 level,
117 path,
118 created_at: now,
119 updated_at: now,
120 }
121 }
122
123 pub fn build_path(parent_path: Option<&str>, node_name: &str) -> String {
125 match parent_path {
126 Some(parent) if !parent.is_empty() => format!("{parent}/{node_name}"),
127 _ => format!("/{node_name}"),
128 }
129 }
130}
131
132impl MarketInstrument {
133 pub fn new(
135 epic: String,
136 instrument_name: String,
137 instrument_type: String,
138 node_id: String,
139 exchange: String,
140 ) -> Self {
141 let now = Utc::now();
142 Self {
143 epic,
144 instrument_name,
145 instrument_type,
146 node_id,
147 exchange,
148 expiry: String::new(),
149 high_limit_price: None,
150 low_limit_price: None,
151 market_status: String::new(),
152 net_change: None,
153 percentage_change: None,
154 update_time: None,
155 update_time_utc: None,
156 bid: None,
157 offer: None,
158 created_at: now,
159 updated_at: now,
160 }
161 }
162
163 pub fn parse_update_time_utc(&mut self) {
165 if let Some(ref time_str) = self.update_time
166 && let Ok(parsed_time) = DateTime::parse_from_rfc3339(time_str)
167 {
168 self.update_time_utc = Some(parsed_time.with_timezone(&Utc));
169 }
170 }
171}
172
173#[cfg(test)]
174mod tests {
175 use super::*;
176
177 #[test]
178 fn test_build_path() {
179 assert_eq!(MarketHierarchyNode::build_path(None, "Root"), "/Root");
180 assert_eq!(
181 MarketHierarchyNode::build_path(Some("/Root"), "Child"),
182 "/Root/Child"
183 );
184 assert_eq!(
185 MarketHierarchyNode::build_path(Some("/Root/Child"), "Grandchild"),
186 "/Root/Child/Grandchild"
187 );
188 }
189
190 #[test]
191 fn test_market_hierarchy_node_creation() {
192 let node = MarketHierarchyNode::new(
193 "test_id".to_string(),
194 "Test Node".to_string(),
195 Some("parent_id".to_string()),
196 "IG".to_string(),
197 1,
198 "/Test Node".to_string(),
199 );
200
201 assert_eq!(node.id, "test_id");
202 assert_eq!(node.name, "Test Node");
203 assert_eq!(node.parent_id, Some("parent_id".to_string()));
204 assert_eq!(node.exchange, "IG");
205 assert_eq!(node.level, 1);
206 assert_eq!(node.path, "/Test Node");
207 }
208
209 #[test]
210 fn test_market_instrument_creation() {
211 let mut instrument = MarketInstrument::new(
212 "IX.D.DAX.DAILY.IP".to_string(),
213 "Germany 40".to_string(),
214 "INDICES".to_string(),
215 "node_123".to_string(),
216 "IG".to_string(),
217 );
218
219 assert_eq!(instrument.epic, "IX.D.DAX.DAILY.IP");
220 assert_eq!(instrument.instrument_name, "Germany 40");
221 assert_eq!(instrument.instrument_type, "INDICES");
222 assert_eq!(instrument.node_id, "node_123");
223 assert_eq!(instrument.exchange, "IG");
224
225 instrument.update_time = Some("2023-12-01T10:30:00Z".to_string());
227 instrument.parse_update_time_utc();
228 assert!(instrument.update_time_utc.is_some());
229 }
230}