From c3e50e390d29f2aca3b6fe8aeefe10ecf43dd826 Mon Sep 17 00:00:00 2001 From: Artem Shtelzer Date: Tue, 20 Oct 2020 18:01:27 +0300 Subject: [PATCH] Websockets (stop) & Order API endpoints --- src/auth.rs | 7 +-- src/client.rs | 29 +++++++--- src/ledger.rs | 4 +- src/lib.rs | 3 +- src/model.rs | 137 ++++++++++++++++++++++++++++++++++++++++++++++ src/orders.rs | 102 +++++++++++++++++++--------------- src/websockets.rs | 6 +- 7 files changed, 228 insertions(+), 60 deletions(-) create mode 100644 src/model.rs diff --git a/src/auth.rs b/src/auth.rs index acedb9f..a10c09f 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -14,7 +14,6 @@ pub fn generate_nonce() -> Result { let start = SystemTime::now(); let since_epoch = start.duration_since(UNIX_EPOCH)?; - let timestamp = since_epoch.as_secs() * 1000 + since_epoch.subsec_nanos() as u64 / 1_000_000; - - Ok((timestamp + 1).to_string()) -} + let timestamp = since_epoch.as_millis() * 1000 as u128; + Ok(timestamp.to_string()) +} \ No newline at end of file diff --git a/src/client.rs b/src/client.rs index ad154ee..ae62981 100644 --- a/src/client.rs +++ b/src/client.rs @@ -8,6 +8,7 @@ use serde::Serialize; static API1_HOST : &'static str = "https://api.bitfinex.com/v2/"; static API_SIGNATURE_PATH : &'static str = "/api/v2/auth/r/"; +static API_SIGNATURE_PATH_ORDER : &'static str = "/api/v2/auth/w/"; static NO_PARAMS: &'static [(); 0] = &[]; #[derive(Clone)] @@ -36,7 +37,11 @@ impl Client { } pub fn post_signed(&self, request: String, payload: String) -> Result { - self.post_signed_params(request, payload, NO_PARAMS) + self.post_signed_params(request, payload, NO_PARAMS, false) + } + + pub fn post_signed_order(&self, request: String, payload: String) -> Result { + self.post_signed_params(request, payload, NO_PARAMS, true) } pub fn post_signed_params( @@ -44,12 +49,20 @@ impl Client { request: String, payload: String, params: &P, + is_order_request: bool ) -> Result { - let url: String = format!("{}auth/r/{}", API1_HOST, request); + let url = match is_order_request { + true => format!("{}auth/w/{}", API1_HOST, request), + _ => format!("{}auth/r/{}", API1_HOST, request) + }; + + let api_signature_path = if is_order_request { API_SIGNATURE_PATH_ORDER } else { API_SIGNATURE_PATH }; + + println!("{} {} {}", url, api_signature_path, payload); let client = reqwest::Client::new(); let response = client.post(url.as_str()) - .headers(self.build_headers(request, payload.clone())?) + .headers(self.build_headers(request, payload.clone(), api_signature_path.to_string())?) .body(payload) .query(params) .send()?; @@ -57,9 +70,9 @@ impl Client { self.handler(response) } - fn build_headers(&self, request: String, payload: String) -> Result { + fn build_headers(&self, request: String, payload: String, api_signature_path: String) -> Result { let nonce: String = auth::generate_nonce()?; - let signature_path: String = format!("{}{}{}{}", API_SIGNATURE_PATH, request, nonce, payload); + let signature_path: String = format!("{}{}{}{}", api_signature_path, request, nonce, payload); let signature = auth::sign_payload(self.secret_key.as_bytes(), signature_path.as_bytes())?; @@ -74,10 +87,12 @@ impl Client { } fn handler(&self, mut response: Response) -> Result { + let mut body = String::new(); + response.read_to_string(&mut body)?; + println!("{:?}", body); + match response.status() { StatusCode::OK => { - let mut body = String::new(); - response.read_to_string(&mut body)?; return Ok(body); }, StatusCode::INTERNAL_SERVER_ERROR => { diff --git a/src/ledger.rs b/src/ledger.rs index 15bb04c..fa2deb3 100644 --- a/src/ledger.rs +++ b/src/ledger.rs @@ -46,13 +46,13 @@ impl Ledger { { let payload: String = format!("{}", "{}"); let request: String = format!("ledgers/{}/hist", symbol.into()); - let params = HistoryParams{ + let params = HistoryParams { start: format!("{}", start), end: format!("{}", end), limit: limit, }; - let data = self.client.post_signed_params(request, payload, ¶ms)?; + let data = self.client.post_signed_params(request, payload, ¶ms, false)?; let entry: Vec = from_str(data.as_str())?; diff --git a/src/lib.rs b/src/lib.rs index 9b7cac5..218e722 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,14 +21,15 @@ extern crate url; extern crate serde_derive; mod book; -mod client; mod ticker; mod trades; mod orders; mod account; mod ledger; mod auth; +mod client; +pub mod model; pub mod candles; pub mod api; pub mod pairs; diff --git a/src/model.rs b/src/model.rs new file mode 100644 index 0000000..b4d9491 --- /dev/null +++ b/src/model.rs @@ -0,0 +1,137 @@ +use serde::{Serialize, Deserialize}; + +#[derive(Debug, Serialize, Deserialize)] +pub struct Order { + pub id: i64, + pub group_id: Option, + pub client_id: i64, + pub symbol: String, + pub creation_timestamp: i64, + pub update_timestamp: i64, + pub amount: f64, + pub amount_original: f64, + pub order_type: String, + pub previous_order_type: Option, + + #[serde(skip_serializing)] + _placeholder_1: Option, + #[serde(skip_serializing)] + _placeholder_2: Option, + + pub flags: Option, + pub order_status: Option, + + #[serde(skip_serializing)] + _placeholder_3: Option, + #[serde(skip_serializing)] + _placeholder_4: Option, + + pub price: f64, + pub price_avg: f64, + pub price_trailing: Option, + pub price_aux_limit: Option, + + #[serde(skip_serializing)] + __placeholder_5: Option, + #[serde(skip_serializing)] + _placeholder_6: Option, + #[serde(skip_serializing)] + _placeholder_7: Option, + + pub notify: i32, + pub hidden: i32, + pub placed_id: Option +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Trade { + pub id: i64, + pub group_id: Option, + pub client_id: i64, + pub symbol: String, + pub creation_timestamp: i64, + pub update_timestamp: i64, + pub amount: f64, + pub amount_original: f64, + pub order_type: String, + pub previous_order_type: Option, + pub time_in_forse: Option, + + #[serde(skip_serializing)] + _placeholder_1: Option, + + pub flags: Option, + pub order_status: String, + + #[serde(skip_serializing)] + _placeholder_2: Option, + #[serde(skip_serializing)] + _placeholder_3: Option, + + pub price: f64, + pub price_avg: f64, + pub price_trailing: Option, + pub price_aux_limit: Option, + + #[serde(skip_serializing)] + _placeholder_4: Option, + #[serde(skip_serializing)] + _placeholder_5: Option, + #[serde(skip_serializing)] + _placeholder_6: Option, + + pub hidden: i32, + pub placed_id: Option, + + #[serde(skip_serializing)] + _placeholder_7: Option, + #[serde(skip_serializing)] + _placeholder_8: Option, + #[serde(skip_serializing)] + _placeholder_9: Option, + + routing: Option, + + #[serde(skip_serializing)] + _placeholder_10: Option, + #[serde(skip_serializing)] + _placeholder_11: Option, + + meta: Option, +} + + +#[derive(Debug, Deserialize, Serialize)] +pub struct TradeResponse { + pub millisecond_timestamp: i64, + pub req_type: String, + + pub message_id: Option, + + #[serde(skip_serializing)] + pub _null: Option<()>, + + pub order: Vec, + + pub code: Option, + pub status: String, + pub text: String, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct TradeCancelResponse { + pub millisecond_timestamp: i64, + pub req_type: String, + + pub message_id: Option, + + #[serde(skip_serializing)] + pub _null: Option<()>, + + pub order: Trade, + + pub code: Option, + pub status: String, + pub text: String, +} + diff --git a/src/orders.rs b/src/orders.rs index c6001ac..9f461e8 100644 --- a/src/orders.rs +++ b/src/orders.rs @@ -1,55 +1,23 @@ use client::*; use errors::*; -use serde_json::from_str; +use model::*; +use serde_json::{from_str, to_string}; +use std::collections::BTreeMap; -#[derive(Serialize, Deserialize)] -pub struct Order { - pub id: i64, - pub group_id: Option, - pub client_id: i64, - pub symbol: String, - pub creation_timestamp: i64, - pub update_timestamp: i64, - pub amount: f64, - pub amount_original: f64, - pub order_type: String, - pub previous_order_type: Option, - - #[serde(skip_serializing)] - _placeholder_1: Option, - #[serde(skip_serializing)] - _placeholder_2: Option, - - pub flags: Option, - pub order_status: Option, - - #[serde(skip_serializing)] - _placeholder_3: Option, - #[serde(skip_serializing)] - _placeholder_4: Option, - - pub price: f64, - pub price_avg: f64, - pub price_trailing: Option, - pub price_aux_limit: Option, - - #[serde(skip_serializing)] - __placeholder_5: Option, - #[serde(skip_serializing)] - _placeholder_6: Option, - #[serde(skip_serializing)] - _placeholder_7: Option, - - pub notify: i32, - pub hidden: i32, - pub placed_id: Option -} +static ORDER_TYPE_LIMIT: &str = "EXCHANGE LIMIT"; #[derive(Clone)] pub struct Orders { client: Client, } +struct OrderRequest { + pub order_type: String, + pub symbol: String, + pub amount: f64, + pub price: f64 +} + impl Orders { pub fn new(api_key: Option, secret_key: Option) -> Self { Orders { @@ -81,9 +49,55 @@ impl Orders { where S: Into { let data = self.client.post_signed(request.into(), payload.into())?; - let orders: Vec = from_str(data.as_str())?; Ok(orders) } + pub fn submit_order(&self, symbol: S, qty: F, price: f64) -> Result + where + S: Into, + F: Into, + { + let buy: OrderRequest = OrderRequest { + order_type: ORDER_TYPE_LIMIT.to_string(), + symbol: symbol.into(), + amount: qty.into(), + price, + }; + + let order = self.build_order(buy); + let payload = to_string(&order)?; + + let data = self.client.post_signed_order("order/submit".into(), payload)?; + println!("DATA: {:?}", data.as_str()); + let transaction: TradeResponse = from_str(data.as_str())?; + + println!("Trans: {:?}", transaction); + Ok(transaction) + } + + pub fn cancel_order(&self, order_id: i64) -> Result + { + let mut parameters: BTreeMap = BTreeMap::new(); + parameters.insert("id".into(), order_id); + let payload = to_string(¶meters)?; + let data = self.client.post_signed_order("order/cancel".into(), payload)?; + let order_canceled: TradeCancelResponse = from_str(data.as_str())?; + + Ok(order_canceled) + } + + fn build_order(&self, order: OrderRequest) -> BTreeMap { + let mut order_parameters: BTreeMap = BTreeMap::new(); + + order_parameters.insert("symbol".into(), order.symbol); + order_parameters.insert("type".into(), order.order_type); + order_parameters.insert("amount".into(), order.amount.to_string()); + + if order.price != 0.0 { + order_parameters.insert("price".into(), order.price.to_string()); + } + + order_parameters + } } \ No newline at end of file diff --git a/src/websockets.rs b/src/websockets.rs index a40bd3f..be25919 100644 --- a/src/websockets.rs +++ b/src/websockets.rs @@ -11,6 +11,7 @@ use tungstenite::client::AutoStream; use tungstenite::handshake::client::Response; use std::sync::mpsc::{self, channel}; +use std::sync::atomic::{AtomicBool, Ordering}; static INFO: &'static str = "info"; static SUBSCRIBED: &'static str = "subscribed"; @@ -196,8 +197,8 @@ impl WebSockets { local_symbol } - pub fn event_loop(&mut self) -> Result<()> { - loop { + pub fn event_loop(&mut self, running: &AtomicBool) -> Result<()> { + while running.load(Ordering::Relaxed) { if let Some(ref mut socket) = self.socket { loop { match self.rx.try_recv() { @@ -251,6 +252,7 @@ impl WebSockets { } } } + Ok(()) } }