kiteconnect_async_wasm/models/mutual_funds/
instruments.rs1use chrono::NaiveDate;
2use serde::{Deserialize, Deserializer, Serialize};
3
4fn deserialize_string_to_f64<'de, D>(deserializer: D) -> Result<f64, D::Error>
6where
7 D: Deserializer<'de>,
8{
9 let s: String = Deserialize::deserialize(deserializer)?;
10 s.parse::<f64>().map_err(serde::de::Error::custom)
11}
12
13#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct MFInstrument {
16 #[serde(rename = "tradingsymbol")]
18 pub trading_symbol: String,
19
20 pub amc: String,
22
23 pub name: String,
25
26 #[serde(default)]
28 pub fund_type: Option<String>,
29
30 pub plan: String,
32
33 #[serde(rename = "settlement_type")]
35 pub settlement_type: String,
36
37 #[serde(
39 rename = "minimum_purchase_amount",
40 deserialize_with = "deserialize_string_to_f64"
41 )]
42 pub minimum_purchase_amount: f64,
43
44 #[serde(
46 rename = "purchase_amount_multiplier",
47 deserialize_with = "deserialize_string_to_f64"
48 )]
49 pub purchase_amount_multiplier: f64,
50
51 #[serde(
53 rename = "minimum_additional_purchase_amount",
54 deserialize_with = "deserialize_string_to_f64"
55 )]
56 pub minimum_additional_purchase_amount: f64,
57
58 #[serde(
60 rename = "minimum_redemption_quantity",
61 deserialize_with = "deserialize_string_to_f64"
62 )]
63 pub minimum_redemption_quantity: f64,
64
65 #[serde(
67 rename = "redemption_quantity_multiplier",
68 deserialize_with = "deserialize_string_to_f64"
69 )]
70 pub redemption_quantity_multiplier: f64,
71
72 #[serde(rename = "dividend_type")]
74 pub dividend_type: String,
75
76 #[serde(rename = "scheme_type")]
78 pub scheme_type: String,
79
80 #[serde(rename = "last_price", deserialize_with = "deserialize_string_to_f64")]
82 pub last_price: f64,
83
84 #[serde(rename = "last_price_date")]
86 pub last_price_date: NaiveDate,
87}
88
89#[derive(Debug, Clone, Serialize, Deserialize)]
91pub struct MFPerformance {
92 #[serde(rename = "tradingsymbol")]
94 pub trading_symbol: String,
95
96 pub name: String,
98
99 pub nav: f64,
101
102 #[serde(rename = "nav_date")]
104 pub nav_date: NaiveDate,
105
106 #[serde(rename = "return_1d")]
108 pub return_1d: Option<f64>,
109
110 #[serde(rename = "return_1w")]
112 pub return_1w: Option<f64>,
113
114 #[serde(rename = "return_1m")]
116 pub return_1m: Option<f64>,
117
118 #[serde(rename = "return_3m")]
120 pub return_3m: Option<f64>,
121
122 #[serde(rename = "return_6m")]
124 pub return_6m: Option<f64>,
125
126 #[serde(rename = "return_1y")]
128 pub return_1y: Option<f64>,
129
130 #[serde(rename = "return_3y")]
132 pub return_3y: Option<f64>,
133
134 #[serde(rename = "return_5y")]
136 pub return_5y: Option<f64>,
137
138 #[serde(rename = "return_inception")]
140 pub return_inception: Option<f64>,
141}
142
143#[derive(Debug, Clone, Serialize, Deserialize)]
145pub struct MFInstrumentSearch {
146 pub query: String,
148
149 pub amc: Option<String>,
151
152 pub fund_type: Option<String>,
154
155 pub plan: Option<String>,
157
158 pub limit: Option<u32>,
160}
161
162impl MFInstrument {
163 pub fn is_equity_fund(&self) -> bool {
165 self.plan.to_lowercase().contains("equity")
166 }
167
168 pub fn is_debt_fund(&self) -> bool {
170 self.plan.to_lowercase().contains("debt")
171 }
172
173 pub fn is_hybrid_fund(&self) -> bool {
175 self.plan.to_lowercase().contains("hybrid")
176 }
177
178 pub fn is_growth_plan(&self) -> bool {
180 self.dividend_type.to_lowercase().contains("growth")
181 }
182
183 pub fn is_dividend_plan(&self) -> bool {
185 self.dividend_type.to_lowercase().contains("dividend")
186 }
187
188 pub fn allows_sip(&self) -> bool {
190 self.minimum_additional_purchase_amount > 0.0
191 }
192
193 pub fn settlement_days(&self) -> u32 {
195 self.settlement_type
197 .chars()
198 .filter(|c| c.is_ascii_digit())
199 .collect::<String>()
200 .parse()
201 .unwrap_or(1)
202 }
203
204 pub fn is_valid_purchase_amount(&self, amount: f64) -> bool {
206 if amount < self.minimum_purchase_amount {
207 return false;
208 }
209
210 let remainder = (amount - self.minimum_purchase_amount) % self.purchase_amount_multiplier;
211 remainder.abs() < 0.01 }
213
214 pub fn next_valid_purchase_amount(&self, amount: f64) -> f64 {
216 if amount <= self.minimum_purchase_amount {
217 return self.minimum_purchase_amount;
218 }
219
220 let excess = amount - self.minimum_purchase_amount;
221 let multiplier_count = (excess / self.purchase_amount_multiplier).ceil();
222 self.minimum_purchase_amount + (multiplier_count * self.purchase_amount_multiplier)
223 }
224}
225
226impl MFPerformance {
227 pub fn best_return(&self) -> Option<f64> {
229 [
230 self.return_1d,
231 self.return_1w,
232 self.return_1m,
233 self.return_3m,
234 self.return_6m,
235 self.return_1y,
236 self.return_3y,
237 self.return_5y,
238 self.return_inception,
239 ]
240 .iter()
241 .filter_map(|&r| r)
242 .max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
243 }
244
245 pub fn worst_return(&self) -> Option<f64> {
247 [
248 self.return_1d,
249 self.return_1w,
250 self.return_1m,
251 self.return_3m,
252 self.return_6m,
253 self.return_1y,
254 self.return_3y,
255 self.return_5y,
256 self.return_inception,
257 ]
258 .iter()
259 .filter_map(|&r| r)
260 .min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
261 }
262
263 pub fn is_consistently_positive(&self) -> bool {
265 [
266 self.return_1m,
267 self.return_3m,
268 self.return_6m,
269 self.return_1y,
270 ]
271 .iter()
272 .filter_map(|&r| r)
273 .all(|r| r > 0.0)
274 }
275
276 pub fn volatility_indicator(&self) -> Option<f64> {
278 match (self.best_return(), self.worst_return()) {
279 (Some(best), Some(worst)) => Some(best - worst),
280 _ => None,
281 }
282 }
283}
284
285impl MFInstrumentSearch {
286 pub fn new(query: String) -> Self {
288 Self {
289 query,
290 amc: None,
291 fund_type: None,
292 plan: None,
293 limit: None,
294 }
295 }
296
297 pub fn amc<S: Into<String>>(mut self, amc: S) -> Self {
299 self.amc = Some(amc.into());
300 self
301 }
302
303 pub fn fund_type<S: Into<String>>(mut self, fund_type: S) -> Self {
305 self.fund_type = Some(fund_type.into());
306 self
307 }
308
309 pub fn plan<S: Into<String>>(mut self, plan: S) -> Self {
311 self.plan = Some(plan.into());
312 self
313 }
314
315 pub fn limit(mut self, limit: u32) -> Self {
317 self.limit = Some(limit);
318 self
319 }
320
321 pub fn equity_only(mut self) -> Self {
323 self.plan = Some("equity".to_string());
324 self
325 }
326
327 pub fn debt_only(mut self) -> Self {
329 self.plan = Some("debt".to_string());
330 self
331 }
332}