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;
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>),
FormOwned(HashMap<String,String>),
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(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())
}
}
}
}
#[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("X-Kite-Version", "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::FormOwned(form_data)) => {
let mut headers = HeaderMap::new();
headers.insert("X-Kite-Version", "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("X-Kite-Version", "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("X-Kite-Version", "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);
}
}
}
#[derive(Debug, Clone)]
pub struct GttOrder {
pub transaction_type: String,
pub quantity: i64,
pub order_type: String,
pub product: String,
pub price: f64,
}
impl GttOrder {
pub fn new(transaction_type: &str, quantity: i64, order_type: &str, product: &str, price: f64) -> Self {
Self {
transaction_type: transaction_type.to_string(),
quantity,
order_type: order_type.to_string(),
product: product.to_string(),
price,
}
}
}
#[derive(Debug, Clone)]
pub struct GttParams {
pub trigger_type: String,
pub exchange: String,
pub tradingsymbol: String,
pub last_price: f64,
pub trigger_values: Vec<f64>,
pub orders: Vec<GttOrder>,
}
impl GttParams {
pub fn new(trigger_type: &str, exchange: &str, tradingsymbol: &str, last_price: f64) -> Self {
Self {
trigger_type: trigger_type.to_string(),
exchange: exchange.to_string(),
tradingsymbol: tradingsymbol.to_string(),
last_price,
trigger_values: Vec::new(),
orders: Vec::new(),
}
}
pub fn trigger_value(mut self, value: f64) -> Self {
self.trigger_values.push(value);
self
}
pub fn order(mut self, order: GttOrder) -> Self {
self.orders.push(order);
self
}
fn condition_json(&self) -> JsonValue {
json!({
"exchange": self.exchange,
"tradingsymbol": self.tradingsymbol,
"trigger_values": self.trigger_values,
"last_price": self.last_price,
})
}
fn orders_json(&self) -> JsonValue {
let orders: Vec<JsonValue> = self.orders.iter().map(|o| json!({
"exchange": self.exchange,
"tradingsymbol": self.tradingsymbol,
"transaction_type": o.transaction_type,
"quantity": o.quantity,
"order_type": o.order_type,
"product": o.product,
"price": o.price,
})).collect();
json!(orders)
}
}
#[derive(Debug, Clone)]
pub struct AlertParams {
pub name: String,
pub alert_type: String,
pub lhs_exchange: String,
pub lhs_tradingsymbol: String,
pub lhs_attribute: String,
pub operator: String,
pub rhs_type: String,
pub rhs_constant: Option<f64>,
pub rhs_exchange: Option<String>,
pub rhs_tradingsymbol: Option<String>,
pub rhs_attribute: Option<String>,
pub basket: Option<JsonValue>,
}
impl AlertParams {
pub fn simple(
name: &str,
lhs_exchange: &str,
lhs_tradingsymbol: &str,
lhs_attribute: &str,
operator: &str,
rhs_constant: f64,
) -> Self {
Self {
name: name.to_string(),
alert_type: "simple".to_string(),
lhs_exchange: lhs_exchange.to_string(),
lhs_tradingsymbol: lhs_tradingsymbol.to_string(),
lhs_attribute: lhs_attribute.to_string(),
operator: operator.to_string(),
rhs_type: "constant".to_string(),
rhs_constant: Some(rhs_constant),
rhs_exchange: None,
rhs_tradingsymbol: None,
rhs_attribute: None,
basket: None,
}
}
pub fn rhs_instrument(mut self, exchange: &str, tradingsymbol: &str, attribute: &str) -> Self {
self.rhs_type = "instrument".to_string();
self.rhs_constant = None;
self.rhs_exchange = Some(exchange.to_string());
self.rhs_tradingsymbol = Some(tradingsymbol.to_string());
self.rhs_attribute = Some(attribute.to_string());
self
}
pub fn ato(mut self, basket: JsonValue) -> Self {
self.alert_type = "ato".to_string();
self.basket = Some(basket);
self
}
fn to_form(&self) -> HashMap<String, String> {
let mut form = HashMap::new();
form.insert("name".to_string(), self.name.clone());
form.insert("type".to_string(), self.alert_type.clone());
form.insert("lhs_exchange".to_string(), self.lhs_exchange.clone());
form.insert("lhs_tradingsymbol".to_string(), self.lhs_tradingsymbol.clone());
form.insert("lhs_attribute".to_string(), self.lhs_attribute.clone());
form.insert("operator".to_string(), self.operator.clone());
form.insert("rhs_type".to_string(), self.rhs_type.clone());
if let Some(c) = self.rhs_constant { form.insert("rhs_constant".to_string(), c.to_string()); }
if let Some(ref e) = self.rhs_exchange { form.insert("rhs_exchange".to_string(), e.clone()); }
if let Some(ref s) = self.rhs_tradingsymbol { form.insert("rhs_tradingsymbol".to_string(), s.clone()); }
if let Some(ref a) = self.rhs_attribute { form.insert("rhs_attribute".to_string(), a.clone()); }
if let Some(ref b) = self.basket { form.insert("basket".to_string(), b.to_string()); }
form
}
}
#[derive(Debug, Clone)]
pub struct OrderParams {
pub variety: String,
pub exchange: String,
pub tradingsymbol: String,
pub transaction_type: String,
pub order_type: String,
pub quantity: i64,
pub product: String,
pub price: Option<f64>,
pub trigger_price: Option<f64>,
pub disclosed_quantity: Option<i64>,
pub validity: Option<String>,
pub validity_ttl: Option<i64>,
pub iceberg_legs: Option<i64>,
pub iceberg_quantity: Option<i64>,
pub auction_number: Option<String>,
pub market_protection: Option<f64>,
pub tag: Option<String>,
pub auto_slice: Option<bool>,
}
impl OrderParams {
pub fn new(
variety: &str,
exchange: &str,
tradingsymbol: &str,
transaction_type: &str,
order_type: &str,
quantity: i64,
product: &str,
) -> Self {
Self {
variety: variety.to_string(),
exchange: exchange.to_string(),
tradingsymbol: tradingsymbol.to_string(),
transaction_type: transaction_type.to_string(),
order_type: order_type.to_string(),
quantity,
product: product.to_string(),
price: None,
trigger_price: None,
disclosed_quantity: None,
validity: None,
validity_ttl: None,
iceberg_legs: None,
iceberg_quantity: None,
auction_number: None,
market_protection: None,
tag: None,
auto_slice: None,
}
}
pub fn price(mut self, price: f64) -> Self { self.price = Some(price); self }
pub fn trigger_price(mut self, trigger_price: f64) -> Self { self.trigger_price = Some(trigger_price); self }
pub fn disclosed_quantity(mut self, qty: i64) -> Self { self.disclosed_quantity = Some(qty); self }
pub fn validity(mut self, validity: &str) -> Self { self.validity = Some(validity.to_string()); self }
pub fn validity_ttl(mut self, minutes: i64) -> Self { self.validity_ttl = Some(minutes); self }
pub fn iceberg(mut self, legs: i64, quantity: i64) -> Self {
self.iceberg_legs = Some(legs);
self.iceberg_quantity = Some(quantity);
self
}
pub fn auction_number(mut self, number: &str) -> Self { self.auction_number = Some(number.to_string()); self }
pub fn market_protection(mut self, percent: f64) -> Self { self.market_protection = Some(percent); self }
pub fn tag(mut self, tag: &str) -> Self { self.tag = Some(tag.to_string()); self }
pub fn auto_slice(mut self, enabled: bool) -> Self { self.auto_slice = Some(enabled); self }
fn to_form(&self) -> HashMap<String, String> {
let mut form = HashMap::new();
form.insert("exchange".to_string(), self.exchange.clone());
form.insert("tradingsymbol".to_string(), self.tradingsymbol.clone());
form.insert("transaction_type".to_string(), self.transaction_type.clone());
form.insert("order_type".to_string(), self.order_type.clone());
form.insert("quantity".to_string(), self.quantity.to_string());
form.insert("product".to_string(), self.product.clone());
if let Some(p) = self.price { form.insert("price".to_string(), p.to_string()); }
if let Some(t) = self.trigger_price { form.insert("trigger_price".to_string(), t.to_string()); }
if let Some(d) = self.disclosed_quantity { form.insert("disclosed_quantity".to_string(), d.to_string()); }
if let Some(ref v) = self.validity { form.insert("validity".to_string(), v.clone()); }
if let Some(t) = self.validity_ttl { form.insert("validity_ttl".to_string(), t.to_string()); }
if let Some(l) = self.iceberg_legs { form.insert("iceberg_legs".to_string(), l.to_string()); }
if let Some(q) = self.iceberg_quantity { form.insert("iceberg_quantity".to_string(), q.to_string()); }
if let Some(ref a) = self.auction_number { form.insert("auction_number".to_string(), a.clone()); }
if let Some(m) = self.market_protection { form.insert("market_protection".to_string(), m.to_string()); }
if let Some(ref t) = self.tag { form.insert("tag".to_string(), t.clone()); }
if let Some(a) = self.auto_slice { form.insert("auto_slice".to_string(), a.to_string()); }
form
}
}
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, params: &OrderParams, retry_count: u64)
-> Result<JsonValue, Box<dyn Error>> {
let url = self.build_url(format!("/orders/{}", params.variety).as_str(), None);
let zreq = self.retry_middleware(&url, "POST",
&Some(ZerodhaPayload::FormOwned(params.to_form())), 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,
continuous: bool,
oi: bool,
retry_count: u64
) -> Result<JsonValue, Box<dyn Error>> {
let params: Vec<(&str, &str)> = vec![
("from", from_date),
("to", to_date),
("continuous", if continuous { "1" } else { "0" }),
("oi", if oi { "1" } else { "0" }),
];
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 place_gtt(&self, params: &GttParams, retry_count: u64) -> Result<JsonValue, Box<dyn Error>> {
let mut form: HashMap<String, String> = HashMap::new();
form.insert("type".to_string(), params.trigger_type.clone());
form.insert("condition".to_string(), params.condition_json().to_string());
form.insert("orders".to_string(), params.orders_json().to_string());
let url = self.build_url("/gtt/triggers", None);
let zreq = self.retry_middleware(&url, "POST",
&Some(ZerodhaPayload::FormOwned(form)), retry_count);
self._raise_or_return_json(zreq)
}
pub fn modify_gtt(&self, trigger_id: &str, params: &GttParams, retry_count: u64)
-> Result<JsonValue, Box<dyn Error>> {
let mut form: HashMap<String, String> = HashMap::new();
form.insert("type".to_string(), params.trigger_type.clone());
form.insert("condition".to_string(), params.condition_json().to_string());
form.insert("orders".to_string(), params.orders_json().to_string());
let url = self.build_url(format!("/gtt/triggers/{}", trigger_id).as_str(), None);
let zreq = self.retry_middleware(&url, "PUT",
&Some(ZerodhaPayload::FormOwned(form)), 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)
}
pub fn create_alert(&self, params: &AlertParams, retry_count: u64)
-> Result<JsonValue, Box<dyn Error>> {
let url = self.build_url("/alerts", None);
let zreq = self.retry_middleware(&url, "POST",
&Some(ZerodhaPayload::FormOwned(params.to_form())), retry_count);
self._raise_or_return_json(zreq)
}
pub fn get_alerts(&self, status: Option<&str>, retry_count: u64)
-> Result<JsonValue, Box<dyn Error>> {
let url = match status {
Some(s) => self.build_url("/alerts", Some(vec![("status", s)])),
None => self.build_url("/alerts", None),
};
let zreq = self.retry_middleware(&url, "GET", &None, retry_count);
self._raise_or_return_json(zreq)
}
pub fn get_alert(&self, uuid: &str, retry_count: u64) -> Result<JsonValue, Box<dyn Error>> {
let url = self.build_url(format!("/alerts/{}", uuid).as_str(), None);
let zreq = self.retry_middleware(&url, "GET", &None, retry_count);
self._raise_or_return_json(zreq)
}
pub fn modify_alert(&self, uuid: &str, params: &AlertParams, retry_count: u64)
-> Result<JsonValue, Box<dyn Error>> {
let url = self.build_url(format!("/alerts/{}", uuid).as_str(), None);
let zreq = self.retry_middleware(&url, "PUT",
&Some(ZerodhaPayload::FormOwned(params.to_form())), retry_count);
self._raise_or_return_json(zreq)
}
pub fn delete_alerts(&self, uuids: &[&str], retry_count: u64)
-> Result<JsonValue, Box<dyn Error>> {
let params: Vec<(&str, &str)> = uuids.iter().map(|u| ("uuid", *u)).collect();
let url = self.build_url("/alerts", Some(params));
let zreq = self.retry_middleware(&url, "DELETE", &None, retry_count);
self._raise_or_return_json(zreq)
}
pub fn alert_history(&self, uuid: &str, retry_count: u64) -> Result<JsonValue, Box<dyn Error>> {
let url = self.build_url(format!("/alerts/{}/history", uuid).as_str(), None);
let zreq = self.retry_middleware(&url, "GET", &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 params = OrderParams::new("regular", "NSE", "NIFTY", "BUY", "MARKET", 10, "NRML");
let data = kiteconnect.place_order(¶ms, 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 params = OrderParams::new("regular", "NSE", "NIFTY", "BUY", "MARKET", 10, "NRML");
let data = kiteconnect.place_order(¶ms, 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());
}
#[test]
fn test_place_gtt() {
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"^/gtt/triggers".to_string()))
.with_body_from_file("mocks/gtt_place.json")
.create();
let params = GttParams::new("single", "NSE", "INFY", 798.0)
.trigger_value(702.0)
.order(GttOrder::new("BUY", 1, "LIMIT", "CNC", 702.5));
let data = kiteconnect.place_gtt(¶ms, 10).unwrap();
assert!(data["status"] == "success");
assert!(data["data"]["trigger_id"] == 123);
_mock.assert()
}
#[test]
fn test_modify_gtt() {
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"^/gtt/triggers/123".to_string()))
.with_body_from_file("mocks/gtt_place.json")
.create();
let params = GttParams::new("two-leg", "NSE", "INFY", 798.0)
.trigger_value(702.0)
.trigger_value(850.0)
.order(GttOrder::new("SELL", 1, "LIMIT", "CNC", 701.5))
.order(GttOrder::new("SELL", 1, "LIMIT", "CNC", 851.5));
let data = kiteconnect.modify_gtt("123", ¶ms, 10).unwrap();
assert!(data["status"] == "success");
_mock.assert()
}
#[test]
fn test_get_gtts() {
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"^/gtt/triggers$".to_string()))
.with_body_from_file("mocks/gtt_triggers.json")
.create();
let data = kiteconnect.get_gtts(None, 10).unwrap();
assert!(data["status"] == "success");
assert!(data["data"][0]["id"] == 112127);
_mock.assert()
}
#[test]
fn test_get_gtt_single() {
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"^/gtt/triggers/123".to_string()))
.with_body_from_file("mocks/gtt_trigger.json")
.create();
let data = kiteconnect.get_gtts(Some("123"), 10).unwrap();
assert!(data["status"] == "success");
assert!(data["data"]["type"] == "two-leg");
_mock.assert()
}
#[test]
fn test_delete_gtt() {
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"^/gtt/triggers/123".to_string()))
.with_body_from_file("mocks/gtt_place.json")
.create();
let data = kiteconnect.delete_gtt("123", 10).unwrap();
assert!(data["status"] == "success");
_mock.assert()
}
#[test]
fn test_create_alert() {
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"^/alerts".to_string()))
.with_body_from_file("mocks/create_alert.json")
.create();
let params = AlertParams::simple(
"NIFTY 50", "INDICES", "NIFTY 50", "LastTradedPrice", ">=", 27000.0);
let data = kiteconnect.create_alert(¶ms, 10).unwrap();
assert!(data["status"] == "success");
assert!(data["data"]["uuid"] == "550e8400-e29b-41d4-a716-446655440000");
_mock.assert()
}
#[test]
fn test_create_ato_alert() {
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"^/alerts".to_string()))
.with_body_from_file("mocks/create_alert.json")
.create();
let basket = json!({
"name": "alerts-basket",
"type": "alert",
"tags": [],
"items": [{
"type": "insert",
"tradingsymbol": "RELIANCE",
"exchange": "NSE",
"weight": 10000,
"params": {
"transaction_type": "BUY",
"product": "CNC",
"order_type": "MARKET",
"validity": "DAY",
"quantity": 1,
"variety": "regular"
}
}]
});
let params = AlertParams::simple(
"buy reliance", "NSE", "RELIANCE", "LastTradedPrice", "<=", 1200.0)
.ato(basket);
let data = kiteconnect.create_alert(¶ms, 10).unwrap();
assert!(data["status"] == "success");
_mock.assert()
}
#[test]
fn test_get_alerts() {
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"^/alerts".to_string()))
.with_body_from_file("mocks/alerts.json")
.create();
let data = kiteconnect.get_alerts(Some("enabled"), 10).unwrap();
assert!(data["status"] == "success");
assert!(data["data"][0]["type"] == "simple");
assert!(data["data"][1]["type"] == "ato");
_mock.assert()
}
#[test]
fn test_get_alert() {
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"^/alerts/550e8400".to_string()))
.with_body_from_file("mocks/alert.json")
.create();
let data = kiteconnect.get_alert("550e8400-e29b-41d4-a716-446655440000", 10).unwrap();
assert!(data["status"] == "success");
assert!(data["data"]["name"] == "NIFTY 50");
_mock.assert()
}
#[test]
fn test_modify_alert() {
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"^/alerts/550e8400".to_string()))
.with_body_from_file("mocks/create_alert.json")
.create();
let params = AlertParams::simple(
"NIFTY 50", "INDICES", "NIFTY 50", "LastTradedPrice", ">=", 27500.0);
let data = kiteconnect.modify_alert("550e8400-e29b-41d4-a716-446655440000", ¶ms, 10).unwrap();
assert!(data["status"] == "success");
_mock.assert()
}
#[test]
fn test_delete_alerts() {
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"^/alerts".to_string()))
.with_body_from_file("mocks/delete_alerts.json")
.create();
let data = kiteconnect.delete_alerts(&["550e8400", "660f9511"], 10).unwrap();
assert!(data["status"] == "success");
_mock.assert()
}
#[test]
fn test_alert_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"^/alerts/550e8400.*/history".to_string()))
.with_body_from_file("mocks/alert_history.json")
.create();
let data = kiteconnect.alert_history("550e8400-e29b-41d4-a716-446655440000", 10).unwrap();
assert!(data["status"] == "success");
assert!(data["data"][0]["type"] == "simple");
_mock.assert()
}
#[test]
fn test_place_order_iceberg() {
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/iceberg".to_string()))
.with_body_from_file("mocks/place_order.json")
.create();
let params = OrderParams::new("iceberg", "NSE", "INFY", "BUY", "LIMIT", 1000, "CNC")
.price(1500.0)
.validity("TTL")
.validity_ttl(5)
.iceberg(5, 200)
.market_protection(-1.0)
.tag("algo1");
let data = kiteconnect.place_order(¶ms, 10).unwrap();
assert!(data["status"] == "success");
_mock.assert()
}
}