use kucoin_api::{
client::{Kucoin, KucoinEnv},
model::market::OrderBookType,
model::websocket::{WSTopic, WSType},
};
use kucoin_arbitrage::broker::gatekeeper::kucoin::task_gatekeep_chances;
use kucoin_arbitrage::broker::order::kucoin::task_place_order;
use kucoin_arbitrage::broker::orderbook::kucoin::{task_pub_orderbook_event, task_sync_orderbook};
use kucoin_arbitrage::broker::orderchange::kucoin::task_pub_orderchange_event;
use kucoin_arbitrage::broker::symbol::filter::{symbol_with_quotes, vector_to_hash};
use kucoin_arbitrage::broker::symbol::kucoin::{format_subscription_list, get_symbols};
use kucoin_arbitrage::event::{
chance::ChanceEvent, order::OrderEvent, orderbook::OrderbookEvent,
orderchange::OrderChangeEvent,
};
use kucoin_arbitrage::model::{counter::Counter, orderbook::FullOrderbook};
use kucoin_arbitrage::strategy::all_taker_btc_usd::task_pub_chance_all_taker_btc_usd;
use kucoin_arbitrage::translator::traits::OrderBookTranslator;
use std::sync::Arc;
use tokio::sync::broadcast::channel;
use tokio::sync::Mutex;
#[tokio::main]
async fn main() -> Result<(), failure::Error> {
kucoin_arbitrage::logger::log_init();
log::info!("Log setup");
let api_input_counter = Arc::new(Mutex::new(Counter::new("api_input")));
let best_price_counter = Arc::new(Mutex::new(Counter::new("best_price")));
let chance_counter = Arc::new(Mutex::new(Counter::new("chance")));
let order_counter = Arc::new(Mutex::new(Counter::new("order")));
let config = kucoin_arbitrage::config::from_file("config.toml")?;
let budget = config.behaviour.usd_cyclic_arbitrage;
let monitor_interval = config.behaviour.monitor_interval_sec;
let api = Kucoin::new(KucoinEnv::Live, Some(config.kucoin_credentials()))?;
let url_public = api.clone().get_socket_endpoint(WSType::Public).await?;
let url_private = api.clone().get_socket_endpoint(WSType::Private).await?;
log::info!("Credentials setup");
let symbol_list = get_symbols(api.clone()).await;
log::info!("Total exchange symbols: {:?}", symbol_list.len());
let symbol_infos = symbol_with_quotes(&symbol_list, "BTC", "USDT");
let hash_symbols = Arc::new(Mutex::new(vector_to_hash(&symbol_infos)));
log::info!("Total symbols in scope: {:?}", symbol_infos.len());
let subs = format_subscription_list(&symbol_infos);
log::info!("Total orderbook WS sessions: {:?}", subs.len());
let (tx_orderbook, rx_orderbook) = channel::<OrderbookEvent>(1024 * 2);
let (tx_orderbook_best, rx_orderbook_best) = channel::<OrderbookEvent>(512);
let (tx_chance, rx_chance) = channel::<ChanceEvent>(64);
let (tx_order, rx_order) = channel::<OrderEvent>(16);
let (tx_orderchange, rx_orderchange) = channel::<OrderChangeEvent>(128);
log::info!("Broadcast channels setup");
let full_orderbook = Arc::new(Mutex::new(FullOrderbook::new()));
log::info!("Local orderbook setup");
tokio::spawn(task_sync_orderbook(
rx_orderbook,
tx_orderbook_best,
full_orderbook.clone(),
api_input_counter.clone(),
));
tokio::spawn(task_pub_chance_all_taker_btc_usd(
rx_orderbook_best,
tx_chance,
full_orderbook.clone(),
hash_symbols,
budget as f64,
best_price_counter.clone(),
));
tokio::spawn(task_gatekeep_chances(
rx_chance,
rx_orderchange,
tx_order,
chance_counter.clone(),
));
tokio::spawn(task_place_order(
rx_order,
api.clone(),
order_counter.clone(),
));
let symbols: Vec<String> = symbol_infos.into_iter().map(|info| info.symbol).collect();
let tasks: Vec<_> = symbols
.iter()
.map(|symbol| {
let api = api.clone();
let full_orderbook_2 = full_orderbook.clone();
let symbol = symbol.clone();
tokio::spawn(async move {
loop {
let res = api.get_orderbook(&symbol, OrderBookType::L100).await;
if res.is_err() {
log::warn!("orderbook[{}] did not respond, retry", &symbol);
continue;
}
let res = res.unwrap().data;
if res.is_none() {
log::warn!("orderbook[{}] received none, retry", &symbol);
continue;
}
let data = res.unwrap();
let mut x = full_orderbook_2.lock().await;
x.insert(symbol.to_string(), data.to_internal());
break;
}
})
})
.collect();
futures::future::join_all(tasks).await;
log::info!("Collected all the symbols");
for (i, sub) in subs.iter().enumerate() {
let mut ws_public = api.websocket();
ws_public.subscribe(url_public.clone(), sub.clone()).await?;
tokio::spawn(task_pub_orderbook_event(ws_public, tx_orderbook.clone()));
log::info!("{i:?}-th session of WS subscription setup");
}
let mut ws_private = api.websocket();
ws_private
.subscribe(url_private.clone(), vec![WSTopic::TradeOrders])
.await?;
tokio::spawn(task_pub_orderchange_event(ws_private, tx_orderchange));
log::info!("All application tasks setup");
let _ = tokio::join!(kucoin_arbitrage::global::task::background_routine(
vec![
api_input_counter.clone(),
best_price_counter.clone(),
chance_counter.clone(),
order_counter.clone()
],
monitor_interval as u64
));
panic!("Program should not arrive here")
}