|
|
@@ -0,0 +1,206 @@
|
|
|
+use std::collections::{BTreeMap, HashMap, HashSet};
|
|
|
+use std::sync::Arc;
|
|
|
+use std::sync::atomic::AtomicBool;
|
|
|
+use exchanges::binance_swap_rest::BinanceSwapRest;
|
|
|
+use exchanges::binance_swap_ws::{BinanceSwapSubscribeType, BinanceSwapWs, BinanceSwapWsType};
|
|
|
+use exchanges::gate_swap_rest::GateSwapRest;
|
|
|
+use exchanges::gate_swap_ws::{GateSwapSubscribeType, GateSwapWs, GateSwapWsType};
|
|
|
+use exchanges::response_base::ResponseData;
|
|
|
+use serde::Serialize;
|
|
|
+use tokio::sync::Mutex;
|
|
|
+use tracing::info;
|
|
|
+
|
|
|
+
|
|
|
+#[derive(Debug, Clone, Serialize)]
|
|
|
+pub struct A1B1Info {
|
|
|
+ // 交易所 binance
|
|
|
+ pub source: String,
|
|
|
+ // 合约还是现货 swap spot
|
|
|
+ pub b_type: String,
|
|
|
+ // 币对
|
|
|
+ pub coin: String,
|
|
|
+ // 时间戳 秒级
|
|
|
+ pub time: i64,
|
|
|
+ // 买1价
|
|
|
+ pub bid: String,
|
|
|
+ // 卖1价
|
|
|
+ pub ask: String
|
|
|
+}
|
|
|
+
|
|
|
+pub async fn run_listener(is_shutdown_arc: Arc<AtomicBool>, data_arc: Arc<Mutex<HashMap<String, A1B1Info>>>) {
|
|
|
+ // 获取两个交易所共有的币种
|
|
|
+ let binance_symbols = get_binance_swap_symbols().await;
|
|
|
+ let gate_symbols = get_gate_swap_symbols().await;
|
|
|
+ let set1: HashSet<_> = binance_symbols.into_iter().collect();
|
|
|
+ let set2: HashSet<_> = gate_symbols.into_iter().collect();
|
|
|
+ // 获取两个交易所共有的币种
|
|
|
+ let symbols: Vec<String> = set1.intersection(&set2).cloned().collect();
|
|
|
+
|
|
|
+ // 币安ws订阅启动
|
|
|
+ for chunk in symbols.chunks(20) {
|
|
|
+ let ws_name = "binance_usdt_swap_listener1".to_string();
|
|
|
+ let (write_tx, write_rx) = futures_channel::mpsc::unbounded();
|
|
|
+ let write_tx_am = Arc::new(Mutex::new(write_tx));
|
|
|
+ let symbols_chunk = chunk.iter().cloned().collect::<Vec<String>>();
|
|
|
+ let is_shutdown_clone = Arc::clone(&is_shutdown_arc);
|
|
|
+ let data_arc_binance_clone_for = Arc::clone(&data_arc);
|
|
|
+
|
|
|
+ tokio::spawn(async move {
|
|
|
+ let mut ws = BinanceSwapWs::new_with_tag(ws_name, false, None, BinanceSwapWsType::PublicAndPrivate);
|
|
|
+ ws.set_subscribe(vec![
|
|
|
+ BinanceSwapSubscribeType::PuBookTicker
|
|
|
+ ]);
|
|
|
+
|
|
|
+ let data_arc_binance_clone = Arc::clone(&data_arc_binance_clone_for);
|
|
|
+
|
|
|
+ let fun = move |data: ResponseData| {
|
|
|
+ let data_arc_cc = data_arc_binance_clone.clone();
|
|
|
+ async move {
|
|
|
+ binance_data_listener(data,data_arc_cc).await
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 建立链接
|
|
|
+ ws.set_symbols(symbols_chunk);
|
|
|
+ ws.ws_connect_async(is_shutdown_clone, fun, &write_tx_am, write_rx).await.expect("binance链接失败(内部一个心跳线程应该已经关闭了)");
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // gate ws订阅启动
|
|
|
+ for chunk in symbols.chunks(20) {
|
|
|
+ let ws_name = "gate_usdt_swap_listener1".to_string();
|
|
|
+ let (write_tx, write_rx) = futures_channel::mpsc::unbounded();
|
|
|
+ let write_tx_am = Arc::new(Mutex::new(write_tx));
|
|
|
+ let symbols_chunk = chunk.iter().cloned().collect::<Vec<String>>();
|
|
|
+ let is_shutdown_clone = Arc::clone(&is_shutdown_arc);
|
|
|
+ let data_arc_gate_clone_for = Arc::clone(&data_arc);
|
|
|
+
|
|
|
+ tokio::spawn(async move {
|
|
|
+ let mut ws = GateSwapWs::new_with_tag(ws_name, false, None, GateSwapWsType::PublicAndPrivate("usdt".to_string()));
|
|
|
+ ws.set_subscribe(vec![
|
|
|
+ GateSwapSubscribeType::PuFuturesBookTicker
|
|
|
+ ]);
|
|
|
+ let data_arc_gate_clone = Arc::clone(&data_arc_gate_clone_for);
|
|
|
+
|
|
|
+ let fun = move |data: ResponseData| {
|
|
|
+ let data_arc_cc = data_arc_gate_clone.clone();
|
|
|
+ async move {
|
|
|
+ gate_data_listener(data,data_arc_cc).await
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 建立链接
|
|
|
+ ws.set_symbols(symbols_chunk);
|
|
|
+ ws.ws_connect_async(is_shutdown_clone, fun, &write_tx_am, write_rx).await.expect("gate链接失败(内部一个心跳线程应该已经关闭了)");
|
|
|
+ });
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+pub async fn get_binance_swap_symbols() -> Vec<String> {
|
|
|
+ // 订阅所有币种
|
|
|
+ let login = BTreeMap::new();
|
|
|
+ let mut binance_rest = BinanceSwapRest::new(false, login);
|
|
|
+ let response = binance_rest.get_exchange_info().await;
|
|
|
+ let mut symbols = vec![];
|
|
|
+ if response.code == 200 {
|
|
|
+ let data = response.data["symbols"].as_array().unwrap();
|
|
|
+ for info in data {
|
|
|
+ let s = info["symbol"].as_str().unwrap().to_string();
|
|
|
+ if !s.ends_with("USDT") {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ let symbol = s.replace("USDT", "_USDT");
|
|
|
+ symbols.push(symbol)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // XXX_USDT
|
|
|
+ symbols
|
|
|
+}
|
|
|
+
|
|
|
+pub async fn get_gate_swap_symbols() -> Vec<String> {
|
|
|
+ // 币种
|
|
|
+ let login = BTreeMap::new();
|
|
|
+ let mut gate_rest = GateSwapRest::new(false, login);
|
|
|
+ let response = gate_rest.get_market_details("usdt".to_string()).await;
|
|
|
+ let mut symbols = vec![];
|
|
|
+ if response.code == 200 {
|
|
|
+ let symbol_infos = response.data.as_array().unwrap();
|
|
|
+ for symbol_info in symbol_infos {
|
|
|
+ let symbol = symbol_info["name"].as_str().unwrap().to_string();
|
|
|
+ symbols.push(symbol)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // XXX_USDT
|
|
|
+ symbols
|
|
|
+}
|
|
|
+
|
|
|
+async fn binance_data_listener(response: ResponseData, data_arc_cc: Arc<Mutex<HashMap<String, A1B1Info>>>) {
|
|
|
+ if response.code != 200 {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ match response.channel.as_str() {
|
|
|
+ "bookTicker" => {
|
|
|
+ // 买一卖一数据
|
|
|
+ let a1b1_info = binance_handle_book_ticker(&response);
|
|
|
+ data_arc_cc.lock().await.insert(format!("{}{}{}", a1b1_info.source, a1b1_info.coin, a1b1_info.time), a1b1_info);
|
|
|
+ },
|
|
|
+ _ => {
|
|
|
+ info!("150 binance未知的数据类型: {:?}", response)
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 读取数据
|
|
|
+async fn gate_data_listener(response: ResponseData, data_arc_cc: Arc<Mutex<HashMap<String, A1B1Info>>>) {
|
|
|
+ if response.code != 200 {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ match response.channel.as_str() {
|
|
|
+ // 深度数据
|
|
|
+ "futures.book_ticker" => {
|
|
|
+ let a1b1_info = gate_handle_book_ticker(&response);
|
|
|
+ data_arc_cc.lock().await.insert(format!("{}{}{}", a1b1_info.source, a1b1_info.coin, a1b1_info.time), a1b1_info);
|
|
|
+ }
|
|
|
+ _ => {
|
|
|
+ info!("168 gate未知的数据类型: {:?}", response)
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+fn gate_handle_book_ticker(res_data: &ResponseData) -> A1B1Info {
|
|
|
+ // 获取秒级
|
|
|
+ let create_at = &res_data.data["t"].as_i64().unwrap() / 1000;
|
|
|
+ let symbol = res_data.data["s"].as_str().unwrap().to_string().replace("USDT", "_USDT");
|
|
|
+ // 取卖一
|
|
|
+ let ap = res_data.data["a"].as_str().unwrap().to_string();
|
|
|
+ // 取买一
|
|
|
+ let bp = res_data.data["b"].as_str().unwrap().to_string();
|
|
|
+
|
|
|
+ A1B1Info {
|
|
|
+ source: "gate".to_string(),
|
|
|
+ b_type: "swap".to_string(),
|
|
|
+ time: create_at,
|
|
|
+ bid: bp.to_string(),
|
|
|
+ coin: symbol,
|
|
|
+ ask: ap.to_string(),
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+fn binance_handle_book_ticker(res_data: &ResponseData) -> A1B1Info {
|
|
|
+ let bp = (*res_data).data["b"].as_str().unwrap();
|
|
|
+ let ap = (*res_data).data["a"].as_str().unwrap();
|
|
|
+ // 获取秒级
|
|
|
+ let create_at = (*res_data).data["E"].as_i64().unwrap()/1000;
|
|
|
+ let symbol = (*res_data).data["s"].as_str().unwrap().to_string().replace("USDT", "_USDT");
|
|
|
+ A1B1Info {
|
|
|
+ source: "binance".to_string(),
|
|
|
+ b_type: "swap".to_string(),
|
|
|
+ time: create_at,
|
|
|
+ bid: bp.to_string(),
|
|
|
+ coin: symbol,
|
|
|
+ ask: ap.to_string(),
|
|
|
+ }
|
|
|
+}
|