use reqwest::{self};
use reqwest::header::{HeaderMap, AUTHORIZATION, USER_AGENT, CONTENT_TYPE};
use std::collections::HashMap;
use std::fmt::{self};
use serde_json::{Value as JsonValue, json};
use serde::{Deserialize, Serialize};
use std::error::Error;
use std::{thread, time};
use csv::ReaderBuilder;
use std::fs;
use toml;
use std::path::Path;
use std::env;
use crate::config::ZrsClientConfig;
#[derive(Deserialize,Debug)]
struct CredentialsProfile {
default: Credential,
}
#[derive(Debug)]
enum ZerodhaPayload<'a> {
Form(HashMap<&'a str,&'a str>),
Json(JsonValue)
}
#[derive(Deserialize,Debug)]
struct Credential {
api_key: String,
access_token: String
}
trait CredentialProvider {
fn get_credentials_from_file() -> Result<CredentialsProfile,ZerodhaError>;
}
impl CredentialProvider for KiteConnect {
fn get_credentials_from_file() -> Result<CredentialsProfile,ZerodhaError> {
let home = env::var("HOME").unwrap();
let path_to_cred_file = format!("{}/.zrsclient/credentials",home);
let path = Path::new(&path_to_cred_file).canonicalize();
match path {
Ok(_) => (),
Err(_) => return Err(ZerodhaError{recoverable:false,
description:"Path to credential file does not exist".to_string()})
}
let creds_file = fs::read_to_string(path.unwrap());
match creds_file {
Ok(_) => (),
Err(_) => return Err(ZerodhaError{recoverable:false, description:"No credential file".to_string()})
}
let credentials:Result<CredentialsProfile,toml::de::Error> = toml::from_str(&creds_file.unwrap());
match credentials {
Ok(_) => return Ok(credentials.unwrap()),
Err(_) => return Err(ZerodhaError{recoverable:false, description:"No credential file".to_string()})
}
}
}
#[derive(Serialize, Deserialize,Debug)]
struct KiteErrorResponse {
error_type: String
}
#[derive(Debug,Clone)]
struct ZerodhaError {
pub recoverable: bool,
pub description: String
}
impl fmt::Display for ZerodhaError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}:{}", self.description, self.recoverable)
}
}
impl From<reqwest::Error> for ZerodhaError {
fn from(err: reqwest::Error) -> Self {
let is_timeout = err.is_timeout();
let is_decode = err.is_decode();
let is_connect: bool = err.is_connect();
let status_code = err.status();
if is_timeout == true || is_connect == true {
return ZerodhaError {
recoverable: true,
description: err.to_string()
}
}
else if is_decode == true {
return ZerodhaError {
recoverable: false,
description: err.to_string()
}
}
match status_code {
Some(http_response_code) => {
if http_response_code.as_u16() == reqwest::StatusCode::TOO_MANY_REQUESTS {
return ZerodhaError {
recoverable: true,
description: err.to_string()
}
}
return ZerodhaError {
recoverable: false,
description: err.to_string()
}
},
None => return ZerodhaError {
recoverable: false,
description: format!("Unknown Error : {}", err.to_string())
}
}
}
}
impl Error for ZerodhaError {
fn description(&self) -> &str {
&self.description
}
}
#[cfg(test)]
use mockito;
#[allow(unused_variables)]
trait RequestHandler{
fn api_request(
&self,
url: &reqwest::Url,
method: &str,
data: &Option<ZerodhaPayload>,
) -> Result<reqwest::blocking::Response,ZerodhaError>;
}
#[allow(unused_variables)]
trait LinearMiddleware{
fn retry_middleware(
&self,
url: &reqwest::Url,
method: &str,
data: &Option<ZerodhaPayload>,
retry_count: u64
) -> reqwest::blocking::Response;
}
impl RequestHandler for KiteConnect {
fn api_request(
&self,
url: &reqwest::Url,
method: &str,
data: &Option<ZerodhaPayload>,
) -> Result<reqwest::blocking::Response,ZerodhaError> {
match data {
Some(ZerodhaPayload::Form(form_data)) => {
let mut headers = HeaderMap::new();
headers.insert("XKiteVersion", "3".parse().unwrap());
headers.insert(AUTHORIZATION, format!("token {}:{}", self.api_key,
self.access_token).parse().unwrap());
headers.insert(USER_AGENT, "Rust".parse().unwrap());
let client = reqwest::blocking::Client::new();
match method {
"GET" => Ok(client.get(url.clone()).headers(headers).send()?),
"POST" => Ok(client.post(url.clone()).headers(headers).form(&form_data).send()?),
"PUT" => Ok(client.put(url.clone()).headers(headers).form(&form_data).send()?),
"DELETE" => Ok(client.delete(url.clone()).headers(headers).form(&form_data).send()?),
_ => Err(ZerodhaError { recoverable: false, description: "Unknown Method".to_string() })
}
},
Some(ZerodhaPayload::Json(json_data)) => {
let mut headers = HeaderMap::new();
headers.insert("XKiteVersion", "3".parse().unwrap());
headers.insert(AUTHORIZATION, format!("token {}:{}", self.api_key,
self.access_token).parse().unwrap());
headers.insert(USER_AGENT, "Rust".parse().unwrap());
headers.insert(CONTENT_TYPE, "application/json".parse().unwrap());
let client = reqwest::blocking::Client::new();
match method {
"GET" => Ok(client.get(url.clone()).headers(headers).send()?),
"POST" => Ok(client.post(url.clone()).headers(headers).json(json_data).send()?),
"PUT" => Ok(client.put(url.clone()).headers(headers).json(json_data).send()?),
"DELETE" => Ok(client.delete(url.clone()).headers(headers).json(json_data).send()?),
_ => Err(ZerodhaError { recoverable: false, description: "Unknown Method".to_string() })
}
},
None => {
let mut headers = HeaderMap::new();
headers.insert("XKiteVersion", "3".parse().unwrap());
headers.insert(AUTHORIZATION, format!("token {}:{}", self.api_key,
self.access_token).parse().unwrap());
headers.insert(USER_AGENT, "Rust".parse().unwrap());
let client = reqwest::blocking::Client::new();
match method {
"GET" => Ok(client.get(url.clone()).headers(headers).send()?),
"DELETE" => Ok(client.delete(url.clone()).headers(headers).send()?),
_ => Err(ZerodhaError { recoverable: false, description: "Unknown Method".to_string() })
}
}
}
}
}
impl LinearMiddleware for KiteConnect{
fn retry_middleware(
&self,
url: &reqwest::Url,
method: &str,
data: &Option<ZerodhaPayload>,
retry_count: u64
) -> reqwest::blocking::Response {
let response = self.api_request(url, method, data);
let max_retry = retry_count;
let mut retry_counter = max_retry;
let mut retry_flag = false;
let base_time = 1;
match &response {
Ok(_) => {},
Err(err) => {
if err.recoverable == false {
panic!("{}", err.description);
} else {
println!("{}", err.description);
retry_flag = true;
}
}
};
if retry_flag == false {
return response.unwrap()
}
loop {
println!("Retrying...");
let retry_response = self.api_request(url, method, data);
match &retry_response {
Ok(_) => {
retry_flag = false;
},
Err(err) => {
if err.recoverable == false {
panic!("{}", err.description);
} else {
println!("{}", err.description)
}
}
};
if retry_flag == false {
return retry_response.unwrap()
}
if retry_counter == 0 {
panic!("All retries failed !")
}
retry_counter = retry_counter - 1;
let wait_time = time::Duration::from_secs(base_time * (max_retry - retry_counter));
thread::sleep(wait_time);
}
}
}
pub struct KiteConnect {
api_key: String,
access_token: String,
base_url: String
}
impl KiteConnect{
#[cfg(test)]
pub fn new_mock(api_key: &str, access_token: &str,base_url: &str) -> Self {
Self {
api_key: api_key.to_string(),
access_token: access_token.to_string(),
base_url: base_url.to_string()
}
}
pub fn new(config:Option<ZrsClientConfig>) -> Self {
if config.is_none() {
let profile = KiteConnect::get_credentials_from_file();
match profile {
Ok(p) => return Self {
api_key: p.default.api_key,
access_token: p.default.access_token,
base_url: "https://api.kite.trade".to_string()
},
Err(cred_error) => panic!("{}",cred_error.description)
}
} else {
let access_token = config.as_ref().unwrap().access_token.unwrap().to_string();
let api_key = config.as_ref().unwrap().api_key.unwrap().to_string();
return Self {
api_key: api_key,
access_token: access_token,
base_url: "https://api.kite.trade".to_string()
}
}
}
fn build_url(&self, path: &str, param: Option<Vec<(&str, &str)>>) -> reqwest::Url {
let url: &str = &format!("{}/{}", self.base_url, &path[1..]);
let mut url = reqwest::Url::parse(url).unwrap();
if let Some(data) = param {
url.query_pairs_mut().extend_pairs(data.iter());
}
url
}
fn _raise_or_return_json(&self, response: reqwest::blocking::Response)
-> Result<JsonValue, Box<dyn Error>> {
let text = response.text().unwrap();
match serde_json::from_str(&text) {
Ok(obj) => Ok(obj),
Err(parse_error) => {
Err(format!("JSON Parsing Error: {} \nRaw Response: {}", parse_error, text).into())
}
}
}
pub fn margins(&self, segment: Option<&str>, retry_count: u64) -> Result<JsonValue, Box<dyn Error>> {
let url : reqwest::Url;
if segment.is_some() {
url = self.build_url(format!("/user/margins/{}", segment.unwrap()).as_str(), None);
} else {
url = self.build_url("/user/margins", None);
}
let zreq = self.retry_middleware(&url, "GET", &None,retry_count);
self._raise_or_return_json(zreq)
}
pub fn order_margins(&self, orders: JsonValue, mode: Option<&str>, retry_count: u64)
-> Result<JsonValue, Box<dyn Error>> {
let url: reqwest::Url;
if mode.is_some() {
let query_params: Vec<(&str,&str)> = vec![("mode", mode.unwrap())];
url = self.build_url("/margins/orders", Some(query_params));
} else {
url = self.build_url("/margins/orders", None);
}
let zreq = self.retry_middleware(&url, "POST",
&Some(ZerodhaPayload::Json(orders)), retry_count);
self._raise_or_return_json(zreq)
}
pub fn basket_order_margins(&self, orders: JsonValue, consider_positions: &str,
mode: Option<&str>, retry_count: u64) -> Result<JsonValue, Box<dyn Error>> {
let mut query_params : Vec<(&str,&str)> = vec![("consider_positions", consider_positions)];
if mode.is_some() {
let tuple = ("mode", mode.unwrap());
query_params.push(tuple);
}
let url = self.build_url("/margins/basket", Some(query_params));
let zreq = self.retry_middleware(&url,"POST",
&Some(ZerodhaPayload::Json(orders)), retry_count);
self._raise_or_return_json(zreq)
}
pub fn virtual_contract_note(&self, orders: JsonValue, retry_count: u64)
-> Result<JsonValue, Box<dyn Error>> {
let url = self.build_url("/charges/orders", None);
let zreq = self.retry_middleware(&url, "POST",
&Some(ZerodhaPayload::Json(orders)), retry_count);
self._raise_or_return_json(zreq)
}
pub fn positions(&self, retry_count: u64) -> Result<JsonValue, Box<dyn Error>> {
let url = self.build_url("/portfolio/positions", None);
let zreq = self.retry_middleware(&url, "GET", &None, retry_count);
self._raise_or_return_json(zreq)
}
pub fn holdings(&self, retry_count: u64) -> Result<JsonValue, Box<dyn Error>> {
let url = self.build_url("/portfolio/holdings", None);
let zreq = self.retry_middleware(&url, "GET", &None,retry_count);
self._raise_or_return_json(zreq)
}
pub fn holdings_auctions(&self, retry_count: u64) -> Result<JsonValue, Box<dyn Error>> {
let url = self.build_url("/portfolio/holdings/auctions", None);
let zreq = self.retry_middleware(&url, "GET", &None,retry_count);
self._raise_or_return_json(zreq)
}
pub fn profile(&self, retry_count: u64) -> Result<JsonValue, Box<dyn Error>> {
let url = self.build_url("/user/profile", None);
let zreq = self.retry_middleware(&url, "GET", &None, retry_count);
self._raise_or_return_json(zreq)
}
pub fn invalidate_access_token(&self, retry_count: u64) -> Result<JsonValue, Box<dyn Error>> {
let url = self.build_url(format!("/session/token?api_key={}&access_token={}",
self.api_key, self.access_token).as_str(), None);
let zreq = self.retry_middleware(&url, "DELETE", &None, retry_count);
self._raise_or_return_json(zreq)
}
pub fn get_live_balance(&self, segment: Option<&str>, retry_count: u64)
-> Result<f64, Box<dyn Error>> {
let margins_response = self.margins(segment, retry_count);
let margins = match margins_response {
Ok(m) => m,
Err(e) => {return Err(format!("Failed to get live_balance ! {}", e).into())}
};
let margins_data = margins.get("data").unwrap();
let mut balance : f64 = 0.0;
if segment.is_none() {
balance += margins_data["commodity"]["available"]["live_balance"].as_f64().unwrap();
balance += margins_data["equity"]["available"]["live_balance"].as_f64().unwrap();
} else {
balance += margins_data["available"]["live_balance"].as_f64().unwrap();
}
return Ok(balance)
}
pub fn intraday_pnl(&self, tradingsymbols: &Option<Vec<&str>>, retry_count: u64)
-> Result<f64, Box<dyn Error>> {
let positions_response = self.positions(retry_count);
let positions = match positions_response {
Ok(p) => p,
Err(e) => {return Err(format!("Failed to get intraday_pnl ! {}", e).into())}
};
if let None = positions.get("data") {
println!("No Positions yet !");
return Ok(0.0)
}
if let Some(first_value) = positions.get("data") {
if let None = first_value.get("day") {
println!("No Intraday Positions yet !");
return Ok(0.0)
}
}
let pos_day_array = positions["data"]["day"].as_array().unwrap();
let mut pos_arr : Vec<(String,i64,i64,f64,f64)> = vec![];
let mut instrument_list : Vec<String> = vec![];
let mut pnl : f64 = 0.0;
for v in pos_day_array {
let token = format!("{}:{}",v["exchange"].as_str().unwrap(),v["tradingsymbol"].as_str().unwrap());
let quantity = v["quantity"].as_i64().unwrap();
let multiplier = v["multiplier"].as_i64().unwrap();
let buy_value = v["buy_value"].as_f64().unwrap();
let sell_value = v["sell_value"].as_f64().unwrap();
pos_arr.push((token.clone(),quantity,multiplier,buy_value,sell_value));
instrument_list.push(token);
}
if tradingsymbols.is_some() {
let ltp_json = self.ltp(tradingsymbols.as_ref().unwrap(), 10).unwrap();
for (symbol,quantity,multiplier,buy,sell) in pos_arr {
if let Some(has_value) = ltp_json["data"].get(symbol) {
pnl += (sell - buy) as f64 + (quantity as f64 * multiplier as f64 * has_value["last_price"].as_f64().unwrap());
}
}
return Ok(pnl)
} else {
let v2 : Vec<&str> = instrument_list.iter().map(AsRef::as_ref).collect();
let ltp_json = self.ltp(&v2,10).unwrap();
for (symbol,quantity,multiplier,buy,sell) in pos_arr {
let sym_ltp = ltp_json["data"].get(symbol).unwrap()["last_price"].as_f64().unwrap();
pnl += (sell - buy) as f64 + (quantity as f64 * multiplier as f64 * sym_ltp);
}
return Ok(pnl)
}
}
pub fn holding_pnl(&self, tradingsymbols: &Option<Vec<&str>>, retry_count: u64)
-> Result<f64, Box<dyn Error>> {
let holdings_response = self.holdings(retry_count);
let holdings = match holdings_response {
Ok(h) => h,
Err(e) => {return Err(format!("Failed to get intraday_pnl ! {}", e).into())}
};
let mut pnl : f64 = 0.0;
if let None = holdings.get("data") {
return Ok(0.0)
}
let hold_arr = holdings["data"].as_array().unwrap();
if tradingsymbols.is_some() {
for v in hold_arr {
let symbol = v["tradingsymbol"].as_str().unwrap();
let last_price = v["last_price"].as_f64().unwrap();
let avg_price = v["average_price"].as_f64().unwrap();
let quantity = v["quantity"].as_f64().unwrap();
let t1_quantity = v["t1_quantity"].as_f64().unwrap();
if tradingsymbols.as_ref().unwrap().contains(&symbol) {
pnl += (last_price - avg_price) * (quantity * t1_quantity);
}
}
} else {
for v in hold_arr {
let last_price = v["last_price"].as_f64().unwrap();
let avg_price = v["average_price"].as_f64().unwrap();
let quantity = v["quantity"].as_f64().unwrap();
let t1_quantity = v["t1_quantity"].as_f64().unwrap();
pnl += (last_price - avg_price) * (quantity * t1_quantity);
}
}
return Ok(pnl)
}
pub fn place_order(&self,
exchange: &str,
tradingsymbol: &str,
transaction_type: &str,
quantity: &str,
variety: &str,
product: &str,
order_type: &str,
price: Option<&str>,
validity: Option<&str>,
disclosed_quantity: Option<&str>,
trigger_price: Option<&str>,
squareoff: Option<&str>,
stoploss: Option<&str>,
trailing_stoploss: Option<&str>,
tag: Option<&str>,
retry_count: u64
) -> Result<JsonValue, Box<dyn Error>> {
let mut params = HashMap::new();
params.insert("exchange", exchange);
params.insert("tradingsymbol", tradingsymbol);
params.insert("transaction_type", transaction_type);
params.insert("quantity", quantity);
params.insert("variety", variety);
params.insert("product",product);
params.insert("order_type", order_type);
if price.is_some() { params.insert("price", price.unwrap()); }
if validity.is_some() { params.insert("validity", validity.unwrap()); }
if disclosed_quantity.is_some() { params.insert("disclosed_quantity", disclosed_quantity.unwrap()); }
if trigger_price.is_some() { params.insert("trigger_price", trigger_price.unwrap()); }
if squareoff.is_some() { params.insert("squareoff", squareoff.unwrap()); }
if stoploss.is_some() { params.insert("stoploss", stoploss.unwrap()); }
if trailing_stoploss.is_some() { params.insert("trailing_stoploss", trailing_stoploss.unwrap()); }
if tag.is_some() { params.insert("tag", tag.unwrap()); }
let url = self.build_url(format!("/orders/{}",variety).as_str(), None);
let zreq = self.retry_middleware(&url, "POST",
&Some(ZerodhaPayload::Form(params)), retry_count);
self._raise_or_return_json(zreq)
}
pub fn modify_order(
&self,
order_id: &str,
variety: &str,
parent_order_id: Option<&str>,
exchange: Option<&str>,
tradingsymbol: Option<&str>,
transaction_type: Option<&str>,
quantity: Option<&str>,
price: Option<&str>,
order_type: Option<&str>,
product: Option<&str>,
trigger_price: Option<&str>,
validity: Option<&str>,
disclosed_quantity: Option<&str>,
retry_count: u64
) -> Result<JsonValue, Box<dyn Error>> {
let mut params = HashMap::new();
params.insert("order_id", order_id);
params.insert("variety", variety);
if parent_order_id.is_some() { params.insert("parent_order_id", parent_order_id.unwrap()); }
if exchange.is_some() { params.insert("exchange", exchange.unwrap()); }
if tradingsymbol.is_some() { params.insert("tradingsymbol", tradingsymbol.unwrap()); }
if transaction_type.is_some() { params.insert("transaction_type", transaction_type.unwrap()); }
if quantity.is_some() { params.insert("quantity", quantity.unwrap()); }
if price.is_some() { params.insert("price", price.unwrap()); }
if order_type.is_some() { params.insert("order_type", order_type.unwrap()); }
if product.is_some() { params.insert("product", product.unwrap()); }
if trigger_price.is_some() { params.insert("trigger_price", trigger_price.unwrap()); }
if validity.is_some() { params.insert("validity", validity.unwrap()); }
if disclosed_quantity.is_some() { params.insert("disclosed_quantity", disclosed_quantity.unwrap()); }
let url = self.build_url(format!("/orders/{}/{}", variety, order_id).as_str(), None);
let zreq = self.retry_middleware(&url, "PUT",
&Some(ZerodhaPayload::Form(params)),retry_count);
self._raise_or_return_json(zreq)
}
pub fn cancel_order(
&self,
order_id: &str,
variety: &str,
parent_order_id: Option<&str>,
retry_count: u64
) -> Result<JsonValue, Box<dyn Error>> {
let mut params = HashMap::new();
params.insert("order_id", order_id);
params.insert("variety", variety);
if parent_order_id.is_some() { params.insert("parent_order_id", parent_order_id.unwrap()); }
let url = self.build_url(format!("/orders/{}/{}", variety, order_id).as_str(), None);
let zreq = self.retry_middleware(&url, "DELETE",
&Some(ZerodhaPayload::Form(params)),retry_count);
self._raise_or_return_json(zreq)
}
pub fn exit_order(
&self,
order_id: &str,
variety: &str,
parent_order_id: Option<&str>,
retry_count: u64
) -> Result<JsonValue, Box<dyn Error>> {
self.cancel_order(order_id, variety, parent_order_id, retry_count)
}
pub fn orders(&self, retry_count: u64) -> Result<JsonValue, Box<dyn Error>> {
let url = self.build_url("/orders", None);
let zreq = self.retry_middleware(&url, "GET", &None, retry_count);
self._raise_or_return_json(zreq)
}
pub fn order_history(&self, order_id: &str, retry_count: u64) -> Result<JsonValue, Box<dyn Error>> {
let mut params: Vec<(&str, &str)> = Vec::new();
params.push(("order_id", order_id));
let url = self.build_url("/orders", Some(params));
let zreq = self.retry_middleware(&url, "GET", &None, retry_count);
self._raise_or_return_json(zreq)
}
pub fn trades(&self, retry_count: u64) -> Result<JsonValue, Box<dyn Error>> {
let url = self.build_url("/trades", None);
let zreq = self.retry_middleware(&url, "GET", &None, retry_count);
self._raise_or_return_json(zreq)
}
pub fn order_trades(&self, order_id: &str, retry_count: u64) -> Result<JsonValue, Box<dyn Error>> {
let url = self.build_url(format!("/orders/{}/trades", order_id).as_str(), None);
let zreq = self.retry_middleware(&url, "GET", &None, retry_count);
self._raise_or_return_json(zreq)
}
pub fn convert_position(
&self,
exchange: &str,
tradingsymbol: &str,
transaction_type: &str,
position_type: &str,
quantity: &str,
old_product: &str,
new_product: &str,
retry_count: u64
) -> Result<JsonValue, Box<dyn Error>> {
let mut params = HashMap::new();
params.insert("exchange", exchange);
params.insert("tradingsymbol", tradingsymbol);
params.insert("transaction_type", transaction_type);
params.insert("position_type", position_type);
params.insert("quantity", quantity);
params.insert("old_product", old_product);
params.insert("new_product", new_product);
let url = self.build_url("/portfolio/positions/", None);
let zreq = self.retry_middleware(&url, "PUT",
&Some(ZerodhaPayload::Form(params)), retry_count);
self._raise_or_return_json(zreq)
}
pub fn mf_orders(&self, order_id: Option<&str>, retry_count: u64) -> Result<JsonValue, Box<dyn Error>> {
let url : reqwest::Url;
if order_id.is_some() {
url = self.build_url(format!("/mf/orders/{}", order_id.unwrap()).as_str(), None);
} else {
url = self.build_url("/mf/orders", None);
}
let zreq = self.retry_middleware(&url, "GET", &None, retry_count);
self._raise_or_return_json(zreq)
}
pub fn place_mf_order(
&self,
tradingsymbol: &str,
transaction_type: &str,
quantity: Option<&str>,
amount: Option<&str>,
tag: Option<&str>,
retry_count: u64
) -> Result<JsonValue, Box<dyn Error>> {
let mut params = HashMap::new();
params.insert("tradingsymbol", tradingsymbol);
params.insert("transaction_type", transaction_type);
if quantity.is_some() { params.insert("quantity", quantity.unwrap()); }
if amount.is_some() { params.insert("amount", amount.unwrap()); }
if tag.is_some() { params.insert("tag", tag.unwrap()); }
let url = self.build_url("/mf/orders", None);
let zreq = self.retry_middleware(&url, "POST",
&Some(ZerodhaPayload::Form(params)), retry_count);
self._raise_or_return_json(zreq)
}
pub fn cancel_mf_order(&self, order_id: &str, retry_count: u64) -> Result<JsonValue, Box<dyn Error>> {
let url = self.build_url(format!("/mf/orders/{}", order_id).as_str(), None);
let zreq = self.retry_middleware(&url, "DELETE", &None, retry_count);
self._raise_or_return_json(zreq)
}
pub fn mf_sips(&self, sip_id : Option<&str>, retry_count: u64) -> Result<JsonValue, Box<dyn Error>> {
let url : reqwest::Url;
if sip_id.is_some() {
url = self.build_url(format!("/mf/sips/{}", sip_id.unwrap()).as_str(), None);
} else {
url = self.build_url("/mf/sips", None);
}
let zreq = self.retry_middleware(&url, "GET", &None, retry_count);
self._raise_or_return_json(zreq)
}
pub fn place_mf_sip(
&self,
tradingsymbol: &str,
amount: &str,
instalments: &str,
frequency: &str,
initial_amount: Option<&str>,
instalment_day: Option<&str>,
tag: Option<&str>,
retry_count: u64
) -> Result<JsonValue, Box<dyn Error>> {
let mut params = HashMap::new();
params.insert("tradingsymbol", tradingsymbol);
params.insert("amount", amount);
params.insert("instalments", instalments);
params.insert("frequency", frequency);
if initial_amount.is_some() { params.insert("initial_amount", initial_amount.unwrap()); }
if instalment_day.is_some() { params.insert("instalment_day", instalment_day.unwrap()); }
if tag.is_some() { params.insert("tag", tag.unwrap()); }
let url = self.build_url("/mf/sips", None);
let zreq = self.retry_middleware(&url, "POST",
&Some(ZerodhaPayload::Form(params)), retry_count);
self._raise_or_return_json(zreq)
}
pub fn modify_mf_sip(
&self,
sip_id: &str,
amount: &str,
status: &str,
instalments: &str,
frequency: &str,
instalment_day: Option<&str>,
retry_count: u64
) -> Result<JsonValue, Box<dyn Error>> {
let mut params = HashMap::new();
params.insert("sip_id", sip_id);
params.insert("amount", amount);
params.insert("status", status);
params.insert("instalments", instalments);
params.insert("frequency", frequency);
if instalment_day.is_some() { params.insert("instalment_day", instalment_day.unwrap()); }
let url = self.build_url(format!("/mf/sip/{}", sip_id).as_str(), None);
let zreq = self.retry_middleware(&url, "PUT",
&Some(ZerodhaPayload::Form(params)), retry_count);
self._raise_or_return_json(zreq)
}
pub fn cancel_mf_sip(&self, sip_id: &str, retry_count: u64) -> Result<JsonValue, Box<dyn Error>> {
let url = self.build_url(format!("/mf/sips/{}", sip_id).as_str(), None);
let zreq = self.retry_middleware(&url, "DELETE", &None, retry_count);
self._raise_or_return_json(zreq)
}
pub fn mf_holdings(&self, retry_count: u64) -> Result<JsonValue, Box<dyn Error>> {
let url = self.build_url("/mf/holdings", None);
let zreq = self.retry_middleware(&url, "GET", &None, retry_count);
self._raise_or_return_json(zreq)
}
pub fn mf_instruments(&self, retry_count: u64) -> Result<JsonValue, Box<dyn Error>> {
let url = self.build_url("/mf/instruments", None);
let zreq = self.retry_middleware(&url, "GET", &None, retry_count);
let content = zreq.text().unwrap();
let mut csv_reader = ReaderBuilder::new().from_reader(content.as_bytes());
let mut mf_instruments : Vec<JsonValue> = Vec::new();
for record in csv_reader.records() {
let mf_instrument = record.unwrap();
mf_instruments.push(json!({
"tradingsymbol": mf_instrument[0],
"amc": mf_instrument[1],
"name": mf_instrument[2],
"purchase_allowed": mf_instrument[3],
"redemption_allowed": mf_instrument[4],
"minimum_purchase_amount": mf_instrument[5],
"purchase_amount_multiplier": mf_instrument[6],
"minimum_additional_purchase_amount": mf_instrument[7],
"minimum_redemption_quantity": mf_instrument[8],
"redemption_quantity_multiplier": mf_instrument[9],
"dividend_type": mf_instrument[10],
"scheme_type": mf_instrument[11],
"plan": mf_instrument[12],
"settlement_type": mf_instrument[13],
"last_price": mf_instrument[14],
"last_price_date": mf_instrument[15],
}))
}
Ok(json!(mf_instruments))
}
pub fn instruments(&self, exchange: Option<&str>, retry_count: u64)
-> Result<JsonValue, Box<dyn Error>> {
let url : reqwest::Url;
if exchange.is_some() {
url = self.build_url(format!("/instruments/{}", exchange.unwrap()).as_str(), None);
} else {
url = self.build_url("/instruments", None);
}
let zreq = self.retry_middleware(&url, "GET", &None, retry_count);
let content = zreq.text().unwrap();
let mut csv_reader = ReaderBuilder::new().from_reader(content.as_bytes());
let mut instruments : Vec<JsonValue> = Vec::new();
for record in csv_reader.records() {
let instrument = record.unwrap();
instruments.push(json!({
"instrument_token": instrument[0],
"exchange_token": instrument[1],
"tradingsymbol": instrument[2],
"name": instrument[3],
"last_price": instrument[4],
"expiry": instrument[5],
"strike": instrument[6],
"tick_size": instrument[7],
"lot_size": instrument[8],
"instrument_type": instrument[9],
"segment": instrument[10],
"exchange": instrument[11]
}))
}
Ok(json!(instruments))
}
pub fn quote(&self, instruments: &Vec<&str>, retry_count: u64) -> Result<JsonValue, Box<dyn Error>> {
let params : Vec<_> = instruments.into_iter().map(|i| ("i", *i)).collect();
let url = self.build_url("/quote", Some(params));
let zreq = self.retry_middleware(&url, "GET", &None, retry_count);
self._raise_or_return_json(zreq)
}
pub fn ohlc(&self, instruments: &Vec<&str>, retry_count: u64) -> Result<JsonValue, Box<dyn Error>> {
let params : Vec<(&str,&str)> = instruments.into_iter().map(|i| ("i", *i)).collect();
let url = self.build_url("/quote/ohlc", Some(params));
let zreq = self.retry_middleware(&url, "GET", &None, retry_count);
self._raise_or_return_json(zreq)
}
pub fn ltp(&self, instruments: &Vec<&str>, retry_count: u64) -> Result<JsonValue, Box<dyn Error>> {
let params : Vec<_> = instruments.into_iter().map(|i| ("i", *i)).collect();
let url = self.build_url("/quote/ltp", Some(params));
let zreq = self.retry_middleware(&url, "GET", &None, retry_count);
self._raise_or_return_json(zreq)
}
pub fn segment_margins(&self, segment: &str, retry_count: u64) -> Result<JsonValue, Box<dyn Error>> {
let url = self.build_url(format!("/margins/{}", segment).as_str(), None);
let zreq = self.retry_middleware(&url, "GET", &None, retry_count);
self._raise_or_return_json(zreq)
}
pub fn historical_data(
&self,
instrument_token: &str,
from_date: &str,
to_date: &str,
interval: &str,
continuos: &str,
retry_count: u64
) -> Result<JsonValue, Box<dyn Error>> {
let mut params = HashMap::new();
params.insert("instrument_token", instrument_token);
params.insert("from", from_date);
params.insert("to", to_date);
params.insert("interval", interval);
params.insert("continuos", continuos);
let params : Vec<_> = params.into_iter().map(|(k, v)| (k, v)).collect();
let url = self.build_url(format!("/instruments/historical/{}/{}",
instrument_token, interval).as_str(), Some(params));
let zreq = self.retry_middleware(&url, "GET", &None, retry_count);
self._raise_or_return_json(zreq)
}
pub fn trigger_range(&self, transaction_type: &str, instruments: Vec<&str>, retry_count: u64)
-> Result<JsonValue, Box<dyn Error>> {
let params : Vec<_> = instruments.into_iter().map(|i| ("i", i)).collect();
let url = self.build_url(format!("/instruments/trigger_range/{}",
transaction_type.to_lowercase()).as_str(), Some(params));
let zreq = self.retry_middleware(&url, "GET", &None, retry_count);
self._raise_or_return_json(zreq)
}
pub fn get_gtts(&self, trigger_id: Option<&str>, retry_count: u64) -> Result<JsonValue, Box<dyn Error>> {
let url : reqwest::Url;
if trigger_id.is_some() {
url = self.build_url(format!("/gtt/triggers/{}", trigger_id.unwrap()).as_str(), None);
} else {
url = self.build_url("/gtt/triggers", None);
}
let zreq = self.retry_middleware(&url, "GET", &None, retry_count);
self._raise_or_return_json(zreq)
}
pub fn delete_gtt(&self, trigger_id: &str, retry_count: u64) -> Result<JsonValue, Box<dyn Error>> {
let url = self.build_url(format!("/gtt/triggers/{}",trigger_id).as_str(), None);
let zreq = self.retry_middleware(&url,"DELETE", &None, retry_count);
self._raise_or_return_json(zreq)
}
}
#[cfg(test)]
mod mock_server_tests{
use super::*;
#[test]
fn test_margins() {
let mut server = mockito::Server::new();
let kiteconnect = KiteConnect::new_mock(
"API_KEY",
"ACCESS_TOKEN",
&server.url());
let _mock = server.mock("GET",
mockito::Matcher::Regex(r"^/user/margins".to_string()))
.with_body_from_file("mocks/margins.json")
.create();
let data = kiteconnect.margins(Some("Equity"),10).unwrap();
println!("{:?}", data);
assert!(data["status"] == "success");
_mock.assert()
}
#[test]
fn test_order_margins() {
let mut server = mockito::Server::new();
let kiteconnect = KiteConnect::new_mock(
"API_KEY",
"ACCESS_TOKEN",
&server.url());
let _mock = server.mock("POST",
mockito::Matcher::Regex(r"^/margins/orders".to_string()))
.with_body_from_file("mocks/order_margins.json")
.create();
let params = json!([
{
"exchange": "NSE",
"tradingsymbol": "INFY",
"transaction_type": "BUY",
"variety": "regular",
"product": "CNC",
"order_type": "MARKET",
"quantity": 1,
"price": 0,
"trigger_price": 0
}
]);
let data = kiteconnect.order_margins(params, None,10).unwrap();
println!("{:?}", data);
assert!(data["status"] == "success");
_mock.assert()
}
#[test]
fn test_basket_order_margins() {
let mut server = mockito::Server::new();
let kiteconnect = KiteConnect::new_mock("API_KEY",
"ACCESS_TOKEN",&server.url());
let _mock = server.mock("POST",
mockito::Matcher::Regex(r"^/margins/basket".to_string()))
.with_body_from_file("mocks/basket_order_margins.json")
.create();
let params = json!([
{
"exchange": "NFO",
"tradingsymbol": "NIFTY23JUL20600CE",
"transaction_type": "SELL",
"variety": "regular",
"product": "NRML",
"order_type": "MARKET",
"quantity": 75,
"price": 0,
"trigger_price": 0
},
{
"exchange": "NFO",
"tradingsymbol": "NIFTY23JUL20700CE",
"transaction_type": "BUY",
"variety": "regular",
"product": "NRML",
"order_type": "MARKET",
"quantity": 75,
"price": 0,
"trigger_price": 0
}
]);
let data = kiteconnect.basket_order_margins(params,
"true",Some("compact"),10).unwrap();
println!("{:?}", data);
assert!(data["status"] == "success");
_mock.assert()
}
#[test]
fn test_virtual_contract_note() {
let mut server = mockito::Server::new();
let kiteconnect = KiteConnect::new_mock(
"API_KEY",
"ACCESS_TOKEN",
&server.url());
let _mock = server.mock("POST",
mockito::Matcher::Regex(r"^/charges/orders".to_string()))
.with_body_from_file("mocks/virtual_contract_note.json")
.create();
let params = json!([
{
"order_id": "111111111",
"exchange": "NSE",
"tradingsymbol": "SBIN",
"transaction_type": "BUY",
"variety": "regular",
"product": "CNC",
"order_type": "MARKET",
"quantity": 1,
"average_price": 560
}
]);
let data = kiteconnect.virtual_contract_note(params, 10).unwrap();
println!("{:?}", data);
assert!(data["status"] == "success");
_mock.assert()
}
#[test]
fn test_holdings_200() {
let mut server = mockito::Server::new();
let kiteconnect = KiteConnect::new_mock(
"API_KEY",
"ACCESS_TOKEN",
&server.url());
let _mock = server.mock("GET",
mockito::Matcher::Regex(r"^/portfolio/holdings".to_string()))
.with_body_from_file("mocks/holdings.json")
.create();
let data = kiteconnect.holdings(10).unwrap();
println!("{:?}", data);
assert!(data["status"] == "success");
_mock.assert()
}
#[test]
fn test_holdings_auctions() {
let mut server = mockito::Server::new();
let kiteconnect = KiteConnect::new_mock(
"API_KEY",
"ACCESS_TOKEN",
&server.url());
let _mock = server.mock("GET",
mockito::Matcher::Regex(r"^/portfolio/holdings/auctions".to_string()))
.with_body_from_file("mocks/holdings_auctions.json")
.create();
let data = kiteconnect.holdings_auctions(10).unwrap();
println!("{:?}", data);
assert!(data["status"] == "success");
_mock.assert()
}
#[test]
fn test_positions_200() {
let mut server = mockito::Server::new();
let kiteconnect = KiteConnect::new_mock(
"API_KEY",
"ACCESS_TOKEN",
&server.url());
let _mock = server.mock("GET",
mockito::Matcher::Regex(r"^/portfolio/positions".to_string()))
.with_body_from_file("mocks/positions.json")
.create();
let data = kiteconnect.positions(10).unwrap();
println!("{:?}", data);
assert!(data["status"] == "success");
_mock.assert()
}
#[test]
fn test_profile() {
let mut server = mockito::Server::new();
let kiteconnect = KiteConnect::new_mock(
"API_KEY",
"ACCESS_TOKEN",
&server.url());
let _mock = server.mock("GET",
mockito::Matcher::Regex(r"^/user/profile".to_string()))
.with_body_from_file("mocks/profile.json")
.create();
let data = kiteconnect.profile(10).unwrap();
println!("{:?}", data);
assert!(data["status"]== "success");
_mock.assert()
}
#[test]
fn test_place_order_200(){
let mut server = mockito::Server::new();
let kiteconnect = KiteConnect::new_mock(
"API_KEY",
"ACCESS_TOKEN",
&server.url());
let _mock = server.mock("POST",
mockito::Matcher::Regex(r"^/orders/regular".to_string()))
.with_body_from_file("mocks/place_order.json")
.create();
let exchange = "NSE";
let trading_symbol = "NIFTY";
let transaction_type = "";
let quantity ="10";
let variety = "regular";
let product = "NRML";
let order_type = "MARKET";
let data = kiteconnect.place_order(
exchange, trading_symbol,
transaction_type,
quantity,
variety,
product,
order_type,
None,
None,
None,
None,
None,
None,
None,
None,
10).unwrap();
assert!(data["status"] == "success");
}
#[test]
#[should_panic]
fn test_place_order_wrong_path(){
let mut server = mockito::Server::new();
let kiteconnect = KiteConnect::new_mock(
"API_KEY",
"ACCESS_TOKEN",
&server.url());
let _mock = server.mock("POST",
mockito::Matcher::Regex(r"^/orders/regular/890".to_string()))
.with_body_from_file("mocks/place_order.json")
.create();
let exchange = "NSE";
let trading_symbol = "NIFTY";
let transaction_type = "";
let quantity ="10";
let variety = "regular";
let product = "NRML";
let order_type = "MARKET";
let data = kiteconnect.place_order(
exchange, trading_symbol,
transaction_type,
quantity,
variety,
product,
order_type,
None,
None,
None,
None,
None,
None,
None,
None,
10).unwrap();
assert!(data["status"] == "success");
}
#[test]
fn test_modify_order() {
let mut server = mockito::Server::new();
let kiteconnect = KiteConnect::new_mock(
"API_KEY",
"ACCESS_TOKEN",
&server.url());
let _mock = server.mock("PUT",
mockito::Matcher::Regex(r"^/orders/regular/".to_string()))
.with_body_from_file("mocks/modify_order.json")
.create();
let data = kiteconnect.modify_order(
"151220000000000",
"regular",
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
10).unwrap();
println!("{:?}", data);
assert!(data["status"] == "success");
_mock.assert()
}
#[test]
fn test_cancel_order() {
let mut server = mockito::Server::new();
let kiteconnect = KiteConnect::new_mock(
"API_KEY",
"ACCESS_TOKEN",
&server.url());
let _mock = server.mock("DELETE",
mockito::Matcher::Regex(r"^/orders/regular/".to_string()))
.with_body_from_file("mocks/cancel_order.json")
.create();
let data = kiteconnect.cancel_order(
"151220000000000",
"regular",
None,
10).unwrap();
println!("{:?}", data);
assert!(data["status"] == "success");
_mock.assert()
}
#[test]
fn test_exit_order() {
let mut server = mockito::Server::new();
let kiteconnect = KiteConnect::new_mock(
"API_KEY",
"ACCESS_TOKEN",
&server.url());
let _mock = server.mock("DELETE",
mockito::Matcher::Regex(r"^/orders/regular/".to_string()))
.with_body_from_file("mocks/cancel_order.json")
.create();
let data = kiteconnect.exit_order(
"151220000000000",
"regular",
None,
10).unwrap();
println!("{:?}", data);
assert!(data["status"] == "success");
_mock.assert()
}
#[test]
fn test_orders() {
let mut server = mockito::Server::new();
let kiteconnect = KiteConnect::new_mock(
"API_KEY",
"ACCESS_TOKEN",
&server.url());
let _mock = server.mock("GET",
mockito::Matcher::Regex(r"^/orders".to_string()))
.with_body_from_file("mocks/orders.json")
.create();
let data = kiteconnect.orders(10).unwrap();
println!("{:?}", data);
assert!(data["status"] == "success");
_mock.assert()
}
#[test]
fn test_trades() {
let mut server = mockito::Server::new();
let kiteconnect = KiteConnect::new_mock(
"API_KEY",
"ACCESS_TOKEN",
&server.url());
let _mock = server.mock("GET",
mockito::Matcher::Regex(r"^/trades".to_string()))
.with_body_from_file("mocks/trades.json")
.create();
let data = kiteconnect.trades(10).unwrap();
println!("{:?}", data);
assert!(data["status"] == "success");
_mock.assert()
}
#[test]
fn test_trigger_range() {
let mut server = mockito::Server::new();
let kiteconnect = KiteConnect::new_mock(
"API_KEY",
"ACCESS_TOKEN",
&server.url());
let _mock = server.mock("GET",
mockito::Matcher::Regex(r"^/instruments/trigger_range".to_string()))
.with_body_from_file("mocks/trigger_range.json")
.create();
let data = kiteconnect.trigger_range(
"BUY",
vec!["INFY"],
10).unwrap();
println!("{:?}", data);
assert!(data["status"] == "success");
_mock.assert()
}
#[test]
fn test_order_trades() {
let mut server = mockito::Server::new();
let kiteconnect = KiteConnect::new_mock(
"API_KEY",
"ACCESS_TOKEN",
&server.url());
let _mock = server.mock("GET",
mockito::Matcher::Regex(r"^/orders/171229000724687/trades".to_string()))
.with_body_from_file("mocks/order_trades.json")
.create();
let data = kiteconnect.order_trades("171229000724687", 10).unwrap();
assert!(data["status"] == "success");
_mock.assert()
}
#[test]
fn test_order_history() {
let mut server = mockito::Server::new();
let kiteconnect = KiteConnect::new_mock(
"API_KEY",
"ACCESS_TOKEN",
&server.url());
let _mock = server.mock("GET",
mockito::Matcher::Regex(r"^/orders".to_string()))
.with_body_from_file("mocks/order_history.json")
.create();
let data = kiteconnect.order_history("171229000724687",10).unwrap();
assert!(data["status"] == "success");
_mock.assert()
}
#[test]
fn test_convert_position() {
let mut server = mockito::Server::new();
let kiteconnect = KiteConnect::new_mock(
"API_KEY",
"ACCESS_TOKEN",
&server.url());
let _mock = server.mock("PUT",
mockito::Matcher::Regex(r"^/portfolio/positions/".to_string()))
.with_body_from_file("mocks/convert_position.json")
.create();
let data = kiteconnect.convert_position(
"NSE",
"INFY",
"BUY",
"overnight",
"1",
"NRML",
"MIS",
10).unwrap();
assert!(data["status"] == "success");
_mock.assert()
}
#[test]
fn test_mf_orders() {
let mut server = mockito::Server::new();
let kiteconnect = KiteConnect::new_mock(
"API_KEY",
"ACCESS_TOKEN",
&server.url());
let _mock1 = server.mock("GET",
mockito::Matcher::Regex(r"^/mf/orders$".to_string()))
.with_body_from_file("mocks/mf_orders.json")
.create();
let _mock2 = server.mock("GET",
mockito::Matcher::Regex(r"^/mf/orders".to_string()))
.with_body_from_file("mocks/mf_orders_info.json")
.create();
let data = kiteconnect.mf_orders(None,10).unwrap();
assert!(data["status"] == "success");
_mock1.assert();
let data = kiteconnect.mf_orders(Some("460687158435713"), 10).unwrap();
assert!(data["status"] == "success");
_mock2.assert();
}
#[test]
fn test_place_mf_order() {
let mut server = mockito::Server::new();
let kiteconnect = KiteConnect::new_mock(
"API_KEY",
"ACCESS_TOKEN",
&server.url());
let _mock = server.mock("POST",
mockito::Matcher::Regex(r"^/mf/orders".to_string()))
.with_body_from_file("mocks/mf_place_order.json")
.create();
let data = kiteconnect.place_mf_order(
"INF174K01LS2",
"BUY",
None,
Some("1000"),
None,
10).unwrap();
println!("{:?}", data);
assert!(data["status"] == "success");
_mock.assert()
}
#[test]
fn test_cancel_mf_order() {
let mut server = mockito::Server::new();
let kiteconnect = KiteConnect::new_mock(
"API_KEY",
"ACCESS_TOKEN",
&server.url());
let _mock = server.mock("DELETE",
mockito::Matcher::Regex(r"^/mf/orders".to_string()))
.with_body_from_file("mocks/cancel_mf_order.json")
.create();
let data = kiteconnect.cancel_mf_order(
"3bb085d1-5038-450e-a807-6543fef6c9ae",
10).unwrap();
assert!(data["status"] == "success");
_mock.assert()
}
#[test]
fn test_mf_sips() {
let mut server = mockito::Server::new();
let kiteconnect = KiteConnect::new_mock(
"API_KEY",
"ACCESS_TOKEN",
&server.url());
let _mock = server.mock("GET",
mockito::Matcher::Regex(r"^/mf/sips".to_string()))
.with_body_from_file("mocks/mf_sips.json")
.create();
let data = kiteconnect.mf_sips(None,10).unwrap();
assert!(data["data"][0]["status"] == "ACTIVE");
_mock.assert()
}
#[test]
fn test_place_mf_sip() {
let mut server = mockito::Server::new();
let kiteconnect = KiteConnect::new_mock(
"API_KEY",
"ACCESS_TOKEN",
&server.url());
let _mock = server.mock("POST",
mockito::Matcher::Regex(r"^/mf/sips".to_string()))
.with_body_from_file("mocks/place_mf_sip.json")
.create();
let data = kiteconnect.place_mf_sip(
"INF174K01LS2",
"1000",
"-1",
"monthly",
Some("5000"),
Some("1"),
None,
10).unwrap();
assert!(data["status"] == "success");
_mock.assert()
}
#[test]
fn test_modify_mf_sip() {
let mut server = mockito::Server::new();
let kiteconnect = KiteConnect::new_mock(
"API_KEY",
"ACCESS_TOKEN",
&server.url());
let _mock = server.mock("PUT",
mockito::Matcher::Regex(r"^/mf/sip/".to_string()))
.with_body_from_file("mocks/modify_mf_sip.json")
.create();
let data = kiteconnect.modify_mf_sip(
"1234",
"1000",
"paused",
"10",
"monthly",
Some("1"),
10).unwrap();
assert!(data["status"] == "success");
_mock.assert()
}
#[test]
fn test_cancel_mf_sip() {
let mut server = mockito::Server::new();
let kiteconnect = KiteConnect::new_mock(
"API_KEY",
"ACCESS_TOKEN",
&server.url());
let _mock = server.mock("DELETE",
mockito::Matcher::Regex(r"^/mf/sips".to_string()))
.with_body_from_file("mocks/cancel_mf_sip.json")
.create();
let data = kiteconnect.cancel_mf_sip("1234",10).unwrap();
assert!(data["status"] == "success");
_mock.assert()
}
#[test]
fn test_mf_holdings() {
let mut server = mockito::Server::new();
let kiteconnect = KiteConnect::new_mock(
"API_KEY",
"ACCESS_TOKEN",
&server.url());
let _mock = server.mock("GET",
mockito::Matcher::Regex(r"^/mf/holdings".to_string()))
.with_body_from_file("mocks/mf_holdings.json")
.create();
let data = kiteconnect.mf_holdings(10).unwrap();
assert!(data["status"] == "success");
_mock.assert()
}
#[test]
fn test_instruments() {
let mut server = mockito::Server::new();
let kiteconnect = KiteConnect::new_mock(
"API_KEY",
"ACCESS_TOKEN",
&server.url());
let _mock = server.mock("GET",
mockito::Matcher::Regex(r"^/instruments".to_string()))
.with_body_from_file("mocks/instruments.csv")
.create();
let data = kiteconnect.instruments(Some("NSE"),10).unwrap();
assert_eq!(data[0]["instrument_token"].as_str(), Some("408065"));
_mock.assert()
}
#[test]
fn test_mf_instruments() {
let mut server = mockito::Server::new();
let kiteconnect = KiteConnect::new_mock(
"API_KEY",
"ACCESS_TOKEN",
&server.url());
let _mock = server.mock("GET",
mockito::Matcher::Regex(r"^/mf/instruments".to_string()))
.with_body_from_file("mocks/mf_instruments.csv")
.create();
let data = kiteconnect.mf_instruments(10).unwrap();
assert_eq!(data[0]["tradingsymbol"].as_str(), Some("INF846K01DP8"));
_mock.assert()
}
#[test]
fn test_quote() {
let mut server = mockito::Server::new();
let kiteconnect = KiteConnect::new_mock(
"API_KEY",
"ACCESS_TOKEN",
&server.url());
let _mock = server.mock("GET",
mockito::Matcher::Regex(r"^/quote".to_string()))
.with_body_from_file("mocks/quote.json")
.create();
let data = kiteconnect.quote(&vec!["NSE:INFY"],10).unwrap();
assert!(data["status"] == "success");
_mock.assert()
}
#[test]
fn test_ohlc() {
let mut server = mockito::Server::new();
let kiteconnect = KiteConnect::new_mock(
"API_KEY",
"ACCESS_TOKEN",
&server.url());
let _mock = server.mock("GET",
mockito::Matcher::Regex(r"^/quote/ohlc".to_string()))
.with_body_from_file("mocks/ohlc.json")
.create();
let data = kiteconnect.ohlc(&vec!["NSE:INFY"],10).unwrap();
assert!(data["status"] == "success");
_mock.assert()
}
#[test]
fn test_ltp() {
let mut server = mockito::Server::new();
let kiteconnect = KiteConnect::new_mock(
"API_KEY",
"ACCESS_TOKEN",
&server.url());
let _mock = server.mock("GET",
mockito::Matcher::Regex(r"^/quote/ltp".to_string()))
.with_body_from_file("mocks/ltp.json")
.create();
let data = kiteconnect.ltp(&vec!["NSE:INFY"],10).unwrap();
assert!(data["status"] == "success");
_mock.assert()
}
#[test]
fn test_build_url() {
let test_url = "https://test.trade";
let kiteconnect = KiteConnect::new_mock(
"key",
"token",
test_url);
let url = kiteconnect.build_url("/my-holdings", None);
assert_eq!(url.as_str(), format!("{}/my-holdings",test_url).as_str());
let mut params: Vec<(&str, &str)> = Vec::new();
params.push(("one", "1"));
let url = kiteconnect.build_url("/my-holdings", Some(params));
assert_eq!(url.as_str(), format!("{}/my-holdings?one=1", test_url).as_str());
}
}