1use base64::prelude::*;
2use base64::Engine;
3use clap::ValueEnum;
4use dotenv::dotenv;
5use hmac::{Hmac, Mac};
6use reqwest;
7use reqwest::blocking::Client;
8use reqwest::header::USER_AGENT;
9use serde_derive::{Deserialize, Serialize};
10use serde_json::Value;
11use serde_urlencoded;
12use sha2::Digest;
13use sha2::{Sha256, Sha512};
14use std::collections::HashMap;
15use std::time::{SystemTime, UNIX_EPOCH};
16
17
18
19#[derive(Debug, ValueEnum, Clone)]
20pub enum Symbols {
21 Etheur,
22 Adaeur,
23 Xlmeur,
24}
25impl std::fmt::Display for Symbols {
26 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
27 match self {
28 Symbols::Etheur => write!(f, "etheur"),
29 Symbols::Adaeur => write!(f, "adaeur"),
30 Symbols::Xlmeur => write!(f, "xlmeur"),
31 }
32 }
33}
34impl std::str::FromStr for Symbols {
35 type Err = ();
36 fn from_str(s: &str) -> Result<Self, Self::Err> {
37 match s {
38 "etheur" => Ok(Symbols::Etheur),
39 "adaeur" => Ok(Symbols::Adaeur),
40 "xlmeur" => Ok(Symbols::Xlmeur),
41 _ => Err(()),
42 }
43 }
44}
45impl Symbols {
46 pub fn to_alt_string(&self) -> String {
47 match self {
48 Symbols::Etheur => "XETHZEUR".to_string(),
49 Symbols::Adaeur => "ADAEUR".to_string(),
50 Symbols::Xlmeur => "XXLMZEUR".to_string(),
51 }
52 }
53}
54#[derive(Debug, ValueEnum, Clone)]
55pub enum OrderType {
56 Market,
57 Limit,
58 StopLoss,
59 TakeProfit,
60 StopLossLimit,
61 TakeProfitLimit,
62 BestLimit,
63}
64impl std::fmt::Display for OrderType {
65 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
66 match self {
67 OrderType::Market => write!(f, "market"),
68 OrderType::Limit => write!(f, "limit"),
69 OrderType::StopLoss => write!(f, "stop_loss"),
70 OrderType::TakeProfit => write!(f, "take_profit"),
71 OrderType::StopLossLimit => write!(f, "stop_loss_limit"),
72 OrderType::TakeProfitLimit => write!(f, "take_profit_limit"),
73 OrderType::BestLimit => write!(f, "best_limit"),
74 }
75 }
76}
77
78#[derive(Debug, ValueEnum, Clone)]
79pub enum Action {
80 Buy,
81 Sell,
82}
83impl std::fmt::Display for Action {
84 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
85 match self {
86 Action::Buy => write!(f, "buy"),
87 Action::Sell => write!(f, "sell"),
88 }
89 }
90}
91
92#[derive(Serialize, Deserialize, Debug)]
93pub struct KrakenResponse {
94 error: Vec<String>,
95 result: Option<HashMap<String, Value>>,
96}
97impl KrakenResponse {
98 pub fn from_block_response(
102 response: reqwest::blocking::Response,
103 ) -> Result<KrakenResponse, Box<dyn std::error::Error>> {
104 let body = response.text()?;
105 let kraken_response: KrakenResponse = serde_json::from_str(&body).unwrap();
106 match kraken_response.error.len() {
108 0 => Ok(kraken_response),
109 _ => Err(Box::new(std::io::Error::new(
110 std::io::ErrorKind::Other,
111 kraken_response.error.join("\n"),
112 ))),
113 }
114 }
115}
116
117#[derive(Debug)]
118pub struct Kraken {
119 api_key_public: String,
120 hmac_sha_key: Vec<u8>,
121}
122impl Kraken {
123 pub fn new() -> Self {
124 dotenv().ok();
125 let api_key_public = std::env::var("API_Public_Key").expect("API_Public_Key not found in enviroment variables. Make sure to have them defined or create an .env file with \n API_Public_Key=... \n API_Private_Key=... variables.");
126 let api_key_private = std::env::var("API_Private_Key").expect("API_Private_Key not found in enviroment variables. Make sure to have them defined or create an .env file with \n API_Public_Key=... \n API_Private_Key=... variables.");
127 let hmac_sha_key = BASE64_STANDARD
128 .decode(&api_key_private)
129 .expect("Error decoding API_Private_Key");
130 Self {
131 api_key_public,
132 hmac_sha_key,
133 }
134 }
135 pub fn order_book(
136 &self,
137 symbol: &Symbols,
138 ) -> Result<KrakenResponse, Box<dyn std::error::Error>> {
139 let url = format!(
142 "https://api.kraken.com/0/public/Depth?pair={}&count=10",
143 symbol,
144 );
145 let client = reqwest::blocking::Client::new();
146 let response = client.get(url).header(USER_AGENT, "Test").send().unwrap();
147 KrakenResponse::from_block_response(response)
149 }
150 pub fn list_ticks(
151 &self,
152 symbol: Symbols,
153 interval: u32,
154 ) -> Result<KrakenResponse, Box<dyn std::error::Error>> {
155 let url = format!(
158 "https://api.kraken.com/0/public/OHLC?pair={}&interval={}",
159 symbol, interval
160 );
161 let client = reqwest::blocking::Client::new();
162 let response = client.get(url).header(USER_AGENT, "Test").send().unwrap();
163 KrakenResponse::from_block_response(response)
165 }
166 pub fn get_nonce(&self) -> String {
170 SystemTime::now()
171 .duration_since(UNIX_EPOCH)
172 .unwrap()
173 .as_millis()
174 .to_string()
175 }
176
177 pub fn request_private(
178 &self,
179 api_path: &str,
180 params: &[(&str, &String)],
181 api_nonce: &str,
182 ) -> Result<KrakenResponse, Box<dyn std::error::Error>> {
183 let api_post = serde_urlencoded::to_string(params)?;
184 let api_signature = self.get_api_signature(api_path, &api_nonce, &api_post);
185
186 let client = Client::new();
187 let response: reqwest::blocking::Response = client
188 .post(format!("https://api.kraken.com{}", api_path))
189 .body(api_post)
190 .header("API-Key", &self.api_key_public)
191 .header("API-Sign", api_signature)
192 .header("User-Agent", "Kraken trading bot example")
193 .send()?;
194 KrakenResponse::from_block_response(response)
195 }
196
197 pub fn get_api_signature(&self, api_path: &str, api_nonce: &str, api_post: &str) -> String {
201 let sha2_result = {
203 let mut hasher = Sha256::default();
204 hasher.update(&api_nonce);
205 hasher.update(&api_post);
206 hasher.finalize()
207 };
208
209 type HmacSha = Hmac<Sha512>;
210 let mut mac = HmacSha::new_from_slice(&self.hmac_sha_key).unwrap();
211 mac.update(api_path.as_bytes());
212 mac.update(&sha2_result);
213 let out = mac.finalize().into_bytes();
214
215 let api_signature = BASE64_STANDARD.encode(&out);
216 api_signature
217 }
218 pub fn place_limit_order(
219 &self,
220 symbol: Symbols,
221 volume: f64,
222 action: Action,
223 limit: f64,
224 ) -> Result<KrakenResponse, Box<dyn std::error::Error>> {
225 let api_path = "/0/private/AddOrder";
227 let api_nonce = self.get_nonce();
228
229 let params = [
231 ("nonce", &api_nonce),
232 ("ordertype", &"limit".to_string()),
233 ("pair", &symbol.to_string()), ("type", &action.to_string()),
235 ("volume", &format!("{volume:.6}")),
236 ("price", &format!("{limit:.6}")),
237 ];
238 self.request_private(api_path, ¶ms, &api_nonce)
239 }
240 pub fn place_best_limit_order(
244 &self,
245 symbol: Symbols,
246 volume: f64,
247 action: Action,
248 ) -> Result<KrakenResponse, Box<dyn std::error::Error>> {
249 let orderbook = self.order_book(&symbol)?;
250 let binding = orderbook.result.unwrap();
251 let best_price = match action {
252 Action::Buy => &binding
253 .get(&symbol.to_alt_string())
254 .unwrap()
255 .get("asks")
256 .unwrap()[0][0],
257 Action::Sell => &binding
258 .get(&symbol.to_alt_string())
259 .unwrap()
260 .get("bids")
261 .unwrap()[0][0],
262 };
263 let limit = best_price.as_str().unwrap().parse().unwrap();
264 self.place_limit_order(symbol, volume, action, limit)
265
266 }
267
268 pub fn place_market_order(
269 &self,
270 symbol: Symbols,
271 volume: f64,
272 action: Action,
273 ) -> Result<KrakenResponse, Box<dyn std::error::Error>> {
274 let api_path = "/0/private/AddOrder";
278 let api_nonce = self.get_nonce();
279
280 let params = [
282 ("nonce", &api_nonce),
283 ("ordertype", &"market".to_string()),
284 ("pair", &symbol.to_string()), ("type", &action.to_string()),
286 ("volume", &format!("{volume:.6}")),
287 ];
288 self.request_private(api_path, ¶ms, &api_nonce)
289 }
290 pub fn cancel_order(
291 &self,
292 order_id: String,
293 ) -> Result<KrakenResponse, Box<dyn std::error::Error>> {
294 let api_path = "/0/private/CancelOrder";
295 let api_nonce = self.get_nonce();
296
297 let params = [("nonce", &api_nonce), ("txid", &order_id)];
298 self.request_private(api_path, ¶ms, &api_nonce)
299 }
300 pub fn get_open_orders(&self) -> Result<KrakenResponse, Box<dyn std::error::Error>> {
301 println!("Open orders");
302 let api_path = "/0/private/OpenOrders";
303 let api_nonce = self.get_nonce();
304
305 let params = [("nonce", &api_nonce), ("trades", &"true".to_string())];
306 self.request_private(api_path, ¶ms, &api_nonce)
307 }
308 pub fn get_closed_orders(&self) -> Result<KrakenResponse, Box<dyn std::error::Error>> {
309 println!("Closed orders");
310 let api_path = "/0/private/ClosedOrders";
311 let api_nonce = self.get_nonce();
312
313 let params = [("nonce", &api_nonce), ("trades", &"true".to_string())];
314 self.request_private(api_path, ¶ms, &api_nonce)
315 }
316}