use serde::{Deserialize, Deserializer, Serialize};
use std::collections::HashMap;
use crate::{
KiteConnect,
constants::Endpoints,
models::{Depth, KiteConnectError, OHLC, time},
};
fn bool_from_int<'de, D>(deserializer: D) -> Result<bool, D::Error>
where
D: Deserializer<'de>,
{
match u8::deserialize(deserializer)? {
0 => Ok(false),
1 => Ok(true),
other => Err(serde::de::Error::invalid_value(
serde::de::Unexpected::Unsigned(other as u64),
&"0 or 1",
)),
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QuoteData {
pub instrument_token: u32,
#[serde(default)]
pub timestamp: time::Time,
pub last_price: f64,
pub last_quantity: u32,
#[serde(default)]
pub last_trade_time: time::Time,
pub average_price: f64,
pub volume: u32,
pub buy_quantity: u32,
pub sell_quantity: u32,
pub ohlc: OHLC,
pub net_change: f64,
pub oi: f64,
pub oi_day_high: f64,
pub oi_day_low: f64,
pub lower_circuit_limit: f64,
pub upper_circuit_limit: f64,
pub depth: Depth,
}
pub type Quote = HashMap<String, QuoteData>;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QuoteOHLCData {
pub instrument_token: u32,
pub last_price: f64,
pub ohlc: OHLC,
}
pub type QuoteOHLC = HashMap<String, QuoteOHLCData>;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QuoteLTPData {
pub instrument_token: u32,
pub last_price: f64,
}
pub type QuoteLTP = HashMap<String, QuoteLTPData>;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HistoricalData {
#[serde(default)]
pub date: time::Time,
pub open: f64,
pub high: f64,
pub low: f64,
pub close: f64,
pub volume: u32,
pub oi: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct HistoricalDataResponse {
pub candles: Vec<Vec<serde_json::Value>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HistoricalDataParams {
pub from: String,
pub to: String,
pub continuous: bool,
pub oi: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Instrument {
pub instrument_token: u32,
pub exchange_token: u32,
pub tradingsymbol: String,
pub name: String,
pub last_price: f64,
#[serde(default)]
pub expiry: time::Time,
pub strike: f64,
pub tick_size: f64,
pub lot_size: f64,
pub instrument_type: String,
pub segment: String,
pub exchange: String,
}
pub type Instruments = Vec<Instrument>;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MFInstrument {
pub tradingsymbol: String,
pub name: String,
pub last_price: f64,
pub amc: String,
#[serde(deserialize_with = "bool_from_int")]
pub purchase_allowed: bool,
#[serde(deserialize_with = "bool_from_int")]
pub redemption_allowed: bool,
pub minimum_purchase_amount: f64,
pub purchase_amount_multiplier: f64,
pub minimum_additional_purchase_amount: f64,
pub minimum_redemption_quantity: f64,
pub redemption_quantity_multiplier: f64,
pub dividend_type: String,
pub scheme_type: String,
pub plan: String,
pub settlement_type: String,
#[serde(default)]
pub last_price_date: time::Time,
}
pub type MFInstruments = Vec<MFInstrument>;
impl KiteConnect {
pub async fn get_quote(&self, instruments: &[&str]) -> Result<Quote, KiteConnectError> {
let params = instruments
.iter()
.map(|&inst| ("i".to_string(), inst.to_string()))
.collect();
self.get_with_query(Endpoints::GET_QUOTE, params).await
}
pub async fn get_ltp(&self, instruments: &[&str]) -> Result<QuoteLTP, KiteConnectError> {
let params = instruments
.iter()
.map(|&inst| ("i".to_string(), inst.to_string()))
.collect();
self.get_with_query(Endpoints::GET_LTP, params).await
}
pub async fn get_ohlc(&self, instruments: &[&str]) -> Result<QuoteOHLC, KiteConnectError> {
let params = instruments
.iter()
.map(|&inst| ("i".to_string(), inst.to_string()))
.collect();
self.get_with_query(Endpoints::GET_OHLC, params).await
}
pub async fn get_historical_data(
&self,
instrument_token: u32,
interval: &str,
from_date: &str,
to_date: &str,
continuous: bool,
oi: bool,
) -> Result<Vec<HistoricalData>, KiteConnectError> {
let endpoint = &Endpoints::GET_HISTORICAL
.replace("{instrument_token}", &instrument_token.to_string())
.replace("{interval}", interval);
let mut params = HashMap::new();
params.insert("from".to_string(), from_date.to_string());
params.insert("to".to_string(), to_date.to_string());
params.insert(
"continuous".to_string(),
if continuous { "1" } else { "0" }.to_string(),
);
params.insert("oi".to_string(), if oi { "1" } else { "0" }.to_string());
let response: HistoricalDataResponse = self.get_with_query(endpoint, params).await?;
self.format_historical_data(response)
}
fn format_historical_data(
&self,
response: HistoricalDataResponse,
) -> Result<Vec<HistoricalData>, KiteConnectError> {
let mut data = Vec::new();
for candle in response.candles {
if candle.len() < 6 {
return Err(KiteConnectError::other(
"Invalid candle data format".to_string(),
));
}
let date_str = candle[0]
.as_str()
.ok_or_else(|| KiteConnectError::other("Invalid date format".to_string()))?;
let open = candle[1]
.as_f64()
.ok_or_else(|| KiteConnectError::other("Invalid open price".to_string()))?;
let high = candle[2]
.as_f64()
.ok_or_else(|| KiteConnectError::other("Invalid high price".to_string()))?;
let low = candle[3]
.as_f64()
.ok_or_else(|| KiteConnectError::other("Invalid low price".to_string()))?;
let close = candle[4]
.as_f64()
.ok_or_else(|| KiteConnectError::other("Invalid close price".to_string()))?;
let volume = candle[5]
.as_f64()
.ok_or_else(|| KiteConnectError::other("Invalid volume".to_string()))?
as u32;
let oi = if candle.len() > 6 {
candle[6].as_f64().unwrap_or(0.0) as u32
} else {
0
};
let parsed_date = if date_str.len() > 19 {
let date_with_colon = if date_str.ends_with("+0530") {
date_str.replace("+0530", "+05:30")
} else if date_str.ends_with("-0530") {
date_str.replace("-0530", "-05:30")
} else {
date_str.to_string()
};
chrono::DateTime::parse_from_rfc3339(&date_with_colon)
.or_else(|_| chrono::DateTime::parse_from_rfc3339(date_str))
} else {
chrono::DateTime::parse_from_rfc3339(date_str)
};
let date = parsed_date
.map_err(|e| {
KiteConnectError::other(format!("Failed to parse date '{}': {}", date_str, e))
})?
.with_timezone(&chrono::Utc);
data.push(HistoricalData {
date: time::Time::new(date),
open,
high,
low,
close,
volume,
oi,
});
}
Ok(data)
}
pub async fn get_instruments(&self) -> Result<Instruments, KiteConnectError> {
let csv_text: String = self.get(Endpoints::GET_INSTRUMENTS).await?;
let mut reader = csv::Reader::from_reader(csv_text.as_bytes());
let mut instruments = Vec::new();
for result in reader.deserialize() {
let instrument: Instrument =
result.map_err(|e| KiteConnectError::other(format!("CSV parsing error: {}", e)))?;
instruments.push(instrument);
}
Ok(instruments)
}
pub async fn get_instruments_by_exchange(
&self,
exchange: &str,
) -> Result<Instruments, KiteConnectError> {
let endpoint = &Endpoints::GET_INSTRUMENTS_EXCHANGE.replace("{exchange}", exchange);
let csv_text: String = self.get(endpoint).await?;
let mut reader = csv::Reader::from_reader(csv_text.as_bytes());
let mut instruments = Vec::new();
for result in reader.deserialize() {
let instrument: Instrument =
result.map_err(|e| KiteConnectError::other(format!("CSV parsing error: {}", e)))?;
instruments.push(instrument);
}
Ok(instruments)
}
pub async fn get_mf_instruments(&self) -> Result<MFInstruments, KiteConnectError> {
let csv_text: String = self.get(Endpoints::GET_MF_INSTRUMENTS).await?;
let mut reader = csv::Reader::from_reader(csv_text.as_bytes());
let mut instruments = Vec::new();
for result in reader.deserialize() {
let instrument: MFInstrument =
result.map_err(|e| KiteConnectError::other(format!("CSV parsing error: {}", e)))?;
instruments.push(instrument);
}
Ok(instruments)
}
}