Async client for the Kite Connect WebSocket API
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: add readme, and justfile

+116 -27
+63 -4
README.md
··· 11 11 [apache-2-0-url]: https://github.com/kaychaks/kiteticker-async/blob/master/LICENSE 12 12 13 13 [Guide](https://kite.trade/docs/connect/v3/websocket/#websocket-streaming) | 14 - [API Docs](https://docs.rs/kiteticker-async/latest/kiteticker-async) |' 14 + [API Docs](https://docs.rs/kiteticker-async/latest/kiteticker-async) 15 15 16 16 ## Overview 17 17 18 - _TODO_ 18 + The official [kiteconnect-rs](https://crates.io/crates/kiteconnect) is an unmaintained project compared to the Python or Go implementations. As per this [issue](https://github.com/zerodha/kiteconnect-rs/issues/39), it will not get any further updates from the Zerodha Tech team. 19 + 20 + Even though the Kite Connect REST APIs are feature-complete, the Ticker APIs are lagging. Here are some of the issues with Ticker API Rust implementation: 21 + - It lacks a few updates, which are present in actively maintained [Python](https://github.com/zerodha/pykiteconnect) & [Go](https://github.com/zerodha/gokiteconnect) implementations. 22 + - It does not parse and serialise quote structure to proper Rust structs and leaves it at an untyped JSON value. This is again a departure from how the same is implemented in libraries of typed languages like [Go](https://github.com/zerodha/gokiteconnect/blob/master/ticker/ticker.go) or [Java](https://github.com/zerodha/javakiteconnect/tree/master/kiteconnect/src/com/zerodhatech/models). 23 + - The design requires the applications to handle the streaming WebSocket messages via callbacks. It is not an idiomatic Rust library design, primarily when the downstream applications rely on modern Rust async concurrency primitives using frameworks like [tokio](https://tokio.rs/). 24 + 25 + This crate is an attempt to address the above issues. The primary goal is to have an async-friendly design following Rust's async library design principles championed by [tokio](https://tokio.rs/tokio/tutorial). 26 + 27 + ## Usage 28 + 29 + Add kiteticker-async crate as a dependency in Cargo.toml 30 + 31 + ``` 32 + [dependencies] 33 + kiteticker-async = "0.1.0" 34 + ``` 19 35 20 36 ## Example 21 37 22 - _TODO_ 38 + ```rust 39 + #[tokio::main] 40 + pub async fn main() -> Result<(), String> { 41 + let api_key = std::env::var("KITE_API_KEY").unwrap(); 42 + let access_token = std::env::var("KITE_ACCESS_TOKEN").unwrap(); 43 + let ticker = KiteTickerAsync::connect(&api_key, &access_token).await?; 44 + 45 + let token = 408065; 46 + // subscribe to an instrument 47 + let mut subscriber = ticker 48 + .subscribe(&[token], Some(Mode::Full)) 49 + .await?; 50 + 51 + // await quotes 52 + if let Ok(Some(msg)) = subscriber.next_message().await? { 53 + match msg { 54 + TickerMessage::Tick(ticks) => { 55 + let tick = ticks.first().unwrap(); 56 + println!("Received tick for instrument_token {}, {}", tick.instrument_token, tick); 57 + } 58 + } 59 + } 60 + 61 + Ok(()) 62 + } 63 + ``` 23 64 24 65 ## Contributing 25 66 26 - _TODO_ 67 + Use [just](https://github.com/casey/just) to run the development tasks. 68 + 69 + ```sh 70 + $ just --list 71 + Available recipes: 72 + build 73 + check 74 + doc 75 + doc-open 76 + doc-test api_key='' access_token='' 77 + example api_key access_token 78 + test api_key='' access_token='' 79 + ``` 80 + 81 + ## License 82 + 83 + This project is licensed under the [Apache 2.0 License] 84 + 85 + [Apache 2.0 license]: https://github.com/kaychaks/kiteticker-async/blob/master/LICENSE
+12 -4
justfile
··· 3 3 check: 4 4 cargo check 5 5 6 - test: 7 - cargo test 6 + test api_key='' access_token='': 7 + KITE_API_KEY={{api_key}} KITE_ACCESS_TOKEN={{access_token}} cargo test --lib 8 8 9 9 build: 10 - cargo build 10 + cargo clean --quiet -r 11 + cargo build --release --quiet 11 12 12 13 doc: 13 - rm -r target/doc 14 + cargo clean --doc --quiet 14 15 {{cargo-doc-command}} 16 + 17 + doc-test api_key='' access_token='': 18 + KITE_API_KEY={{api_key}} KITE_ACCESS_TOKEN={{access_token}} cargo test --quiet --doc 19 + 15 20 doc-open: 16 21 rm -r target/doc 17 22 {{cargo-doc-command}} --open 23 + 24 + example api_key access_token: 25 + KITE_API_KEY={{api_key}} KITE_ACCESS_TOKEN={{access_token}} cargo run --example sample
+35 -6
src/lib.rs
··· 3 3 clippy::large_enum_variant, 4 4 clippy::needless_doctest_main 5 5 )] 6 - #![warn( 7 - missing_debug_implementations, 8 - rust_2018_idioms, 9 - unreachable_pub 10 - )] 6 + #![warn(missing_debug_implementations, rust_2018_idioms, unreachable_pub)] 11 7 #![doc(test( 12 8 no_crate_inject, 13 9 attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_variables)) ··· 19 15 //! The response is parsed and converted into Rust types. 20 16 //! The WebSocket connection is managed by the library and reconnected automatically. 21 17 //! 22 - 18 + //! # Usage 19 + //! ``` 20 + //! 21 + //! use kiteticker_async::{KiteTickerAsync, Mode, TickerMessage}; 22 + //! 23 + //! #[tokio::main] 24 + //! pub async fn main() -> Result<(), String> { 25 + //! let api_key = std::env::var("KITE_API_KEY").unwrap_or_default(); 26 + //! let access_token = std::env::var("KITE_ACCESS_TOKEN").unwrap_or_default(); 27 + //! let ticker = KiteTickerAsync::connect(&api_key, &access_token).await?; 28 + //! 29 + //! let token = 408065; 30 + //! // subscribe to an instrument 31 + //! let mut subscriber = ticker 32 + //! .subscribe(&[token], Some(Mode::Full)) 33 + //! .await?; 34 + //! 35 + //! // await quotes 36 + //! loop { 37 + //! if let Some(msg) = subscriber.next_message().await? { 38 + //! match msg { 39 + //! TickerMessage::Ticks(ticks) => { 40 + //! let tick = ticks.first().unwrap(); 41 + //! println!("Received tick for instrument_token {}, {:?}", tick.instrument_token, tick); 42 + //! break; 43 + //! }, 44 + //! _ => continue, 45 + //! } 46 + //! } 47 + //! } 48 + //! 49 + //! Ok(()) 50 + //! } 51 + //! ``` 23 52 mod models; 24 53 pub use models::{ 25 54 Depth, DepthItem, Exchange, Mode, Request, TextMessage, Tick, TickMessage,
+1 -1
src/models.rs
··· 265 265 /// 266 266 pub enum TickerMessage { 267 267 /// Quote packets for subscribed tokens 268 - Tick(Vec<TickMessage>), 268 + Ticks(Vec<TickMessage>), 269 269 /// Error response 270 270 Error(String), 271 271 /// Order postback
+5 -12
src/ticker.rs
··· 173 173 .map(|t| (t.clone(), mode.clone().unwrap_or_default())), 174 174 ); 175 175 let tks = self.get_subscribed(); 176 - dbg!(tks.clone()); 177 176 self.ticker.subscribe_cmd(tks.as_slice(), None).await?; 178 177 Ok(()) 179 178 } ··· 196 195 instrument_tokens: &[u32], 197 196 ) -> Result<(), String> { 198 197 let tokens = self.get_subscribed_or(instrument_tokens); 199 - dbg!(tokens.clone()); 200 198 self.ticker.unsubscribe_cmd(tokens.as_slice()).await 201 199 } 202 200 ··· 218 216 Message::Text(text_message) => self.process_text_message(text_message), 219 217 Message::Binary(ref binary_message) => { 220 218 if binary_message.len() < 2 { 221 - return Some(TickerMessage::Tick(vec![])); 219 + return Some(TickerMessage::Ticks(vec![])); 222 220 } else { 223 221 self.process_binary(binary_message.as_slice()) 224 222 } ··· 240 238 let num_packets = 241 239 i16::from_be_bytes(binary_message[0..=1].try_into().unwrap()) as usize; 242 240 if num_packets > 0 { 243 - Some(TickerMessage::Tick( 241 + Some(TickerMessage::Ticks( 244 242 [0..num_packets] 245 243 .into_iter() 246 244 .fold((vec![], 2), |(mut acc, start), _| { ··· 262 260 &self, 263 261 text_message: String, 264 262 ) -> Option<TickerMessage> { 265 - dbg!(text_message.clone()); 266 263 serde_json::from_str::<TextMessage>(&text_message) 267 264 .map(|x| x.into()) 268 265 .ok() ··· 285 282 loop { 286 283 match sb.next_message().await { 287 284 Ok(message) => match message { 288 - Some(TickerMessage::Tick(xs)) => { 285 + Some(TickerMessage::Ticks(xs)) => { 289 286 if xs.len() == 0 { 290 287 continue; 291 288 } ··· 298 295 break; 299 296 } 300 297 _ => { 301 - dbg!(message); 302 298 continue; 303 299 } 304 300 }, ··· 333 329 match n.to_owned() { 334 330 Some(message) => { 335 331 match message { 336 - TickerMessage::Tick(xs) => { 332 + TickerMessage::Ticks(xs) => { 337 333 if xs.len() == 0 { 338 334 if loop_cnt > 5 { 339 335 break; ··· 343 339 } 344 340 assert_eq!(xs.len(), 1); 345 341 let tick_message = xs.first().unwrap(); 346 - dbg!(tick_message); 347 342 assert!(tick_message.instrument_token == token); 348 343 assert_eq!(tick_message.content.mode, mode); 349 344 if loop_cnt > 5 { ··· 395 390 loop { 396 391 match sb.next_message().await { 397 392 Ok(message) => match message { 398 - Some(TickerMessage::Tick(xs)) => { 393 + Some(TickerMessage::Ticks(xs)) => { 399 394 if xs.len() == 0 { 400 395 if loop_cnt > 4 { 401 396 assert!(true); ··· 407 402 } 408 403 assert_eq!(xs.len(), 1); 409 404 let tick_message = xs.first().unwrap(); 410 - dbg!(tick_message); 411 405 assert!(tick_message.instrument_token == token); 412 406 sb.unsubscribe(&[]).await.unwrap(); 413 407 loop_cnt += 1; ··· 417 411 } 418 412 } 419 413 _ => { 420 - dbg!(message); 421 414 continue; 422 415 } 423 416 },