From 799cd53695dcb8a7f2feaddb5a1494b59b516768 Mon Sep 17 00:00:00 2001 From: Mukan Erkin Date: Sat, 25 Apr 2026 12:02:09 +0300 Subject: [PATCH] feat(mse): wallet page + insufficient balance skip in strategy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - BinanceClient::get_usdt_spot_balance() helper - strategy.rs: check USDT balance before market_buy; skip (Ok(None)) if insufficient - GET /api/wallet/spot endpoint returning all non-zero spot balances - /wallet page: Spot tab with sortable balance table (USDT pinned top) - Earn/Futures tabs as placeholders for future implementation - Header nav: Cüzdanlar link added Co-Authored-By: Claude Sonnet 4.6 --- src/api/mod.rs | 1 + src/api/routes.rs | 17 +++- src/api/wallet.rs | 33 ++++++++ src/binance/client.rs | 8 ++ src/bot/strategy.rs | 9 +++ src/tmpl.rs | 1 + src/web/_header.html | 1 + src/web/wallet.html | 178 ++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 247 insertions(+), 1 deletion(-) create mode 100644 src/api/wallet.rs create mode 100644 src/web/wallet.html diff --git a/src/api/mod.rs b/src/api/mod.rs index b2cf961..3e04342 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -4,3 +4,4 @@ pub mod events; pub mod mode; pub mod positions; pub mod routes; +pub mod wallet; diff --git a/src/api/routes.rs b/src/api/routes.rs index 1a6fff2..339da73 100644 --- a/src/api/routes.rs +++ b/src/api/routes.rs @@ -7,7 +7,7 @@ use axum::{ }; use axum_extra::extract::CookieJar; -use crate::api::{auth, bots, events, mode, positions}; +use crate::api::{auth, bots, events, mode, positions, wallet}; use crate::AppState; pub fn build(state: AppState) -> Router { @@ -23,6 +23,7 @@ pub fn build(state: AppState) -> Router { .route("/bots/:id/log-stream", get(events::bot_log_sse)) .route("/bots/:id/positions/:order_id/cancel", post(bots::cancel_position)) .route("/bots/:id/positions/:order_id/sell", post(bots::sell_position)) + .route("/wallet/spot", get(wallet::spot_balances)) .route("/positions", get(positions::open_positions)) .route("/positions/closed", get(positions::closed_positions)) .route("/mode", get(mode::get_mode).post(mode::set_mode)) @@ -41,6 +42,7 @@ pub fn build(state: AppState) -> Router { .route("/bots", get(bots_handler)) .route("/bots/:id", get(bot_detail_handler)) .route("/positions", get(positions_handler)) + .route("/wallet", get(wallet_handler)) .with_state(state) } @@ -105,6 +107,19 @@ async fn bot_detail_handler( } } +async fn wallet_handler( + State(state): State, + jar: CookieJar, +) -> impl IntoResponse { + let token = jar.get("mse_session").map(|c| c.value().to_string()); + let authed = match token { + Some(t) => { let db = state.db.lock().await; db.session_exists(&t).unwrap_or(false) } + None => false, + }; + if authed { axum::response::Html(crate::tmpl::render("wallet.html", "wallet")).into_response() } + else { Redirect::to("/").into_response() } +} + async fn dashboard_handler( State(state): State, jar: CookieJar, diff --git a/src/api/wallet.rs b/src/api/wallet.rs new file mode 100644 index 0000000..1db0742 --- /dev/null +++ b/src/api/wallet.rs @@ -0,0 +1,33 @@ +use axum::{extract::State, response::IntoResponse, Json}; +use serde::Serialize; + +use crate::binance::client::BinanceClient; +use crate::AppState; + +#[derive(Serialize)] +pub struct BalanceEntry { + pub asset: String, + pub free: f64, + pub locked: f64, +} + +pub async fn spot_balances(State(state): State) -> impl IntoResponse { + let mode = state.current_mode.lock().await.clone(); + let (api_key, api_secret) = if mode == "live" { + (state.live_api_key.clone(), state.live_api_secret.clone()) + } else { + (state.testnet_api_key.clone(), state.testnet_api_secret.clone()) + }; + let client = BinanceClient::new(api_key, api_secret, mode == "testnet"); + + match client.get_account_balances().await { + Ok(balances) => { + let entries: Vec = balances + .into_iter() + .map(|(asset, free, locked)| BalanceEntry { asset, free, locked }) + .collect(); + Json(entries).into_response() + } + Err(e) => (axum::http::StatusCode::BAD_GATEWAY, e.to_string()).into_response(), + } +} diff --git a/src/binance/client.rs b/src/binance/client.rs index 67ab26a..db8e50d 100644 --- a/src/binance/client.rs +++ b/src/binance/client.rs @@ -309,6 +309,14 @@ impl BinanceClient { Ok(symbols) } + pub async fn get_usdt_spot_balance(&self) -> Result { + let balances = self.get_account_balances().await?; + Ok(balances.iter() + .find(|(asset, _, _)| asset == "USDT") + .map(|(_, free, _)| *free) + .unwrap_or(0.0)) + } + /// Binance hesabındaki sıfır olmayan tüm bakiyeleri döner: Vec<(asset, free, locked)> pub async fn get_account_balances(&self) -> Result> { let timestamp = chrono::Utc::now().timestamp_millis().to_string(); diff --git a/src/bot/strategy.rs b/src/bot/strategy.rs index bf4b655..7e50e5a 100644 --- a/src/bot/strategy.rs +++ b/src/bot/strategy.rs @@ -28,6 +28,15 @@ impl RedCandleStrategy { symbol, kline.open, kline.close ); + let usdt_balance = client.get_usdt_spot_balance().await?; + if usdt_balance < usdt_amount { + info!( + "[{}] Yetersiz USDT bakiyesi ({:.2} < {:.2}), bekleniyor", + symbol, usdt_balance, usdt_amount + ); + return Ok(None); + } + let filters = client.get_symbol_filters(symbol).await?; let raw_qty = usdt_amount / kline.close; let quantity = filters.format_qty(raw_qty); diff --git a/src/tmpl.rs b/src/tmpl.rs index 2f626d3..0df50eb 100644 --- a/src/tmpl.rs +++ b/src/tmpl.rs @@ -12,6 +12,7 @@ fn env() -> &'static Environment<'static> { e.add_template_owned("bots.html", include_str!("web/bots.html").to_string()).unwrap(); e.add_template_owned("bot.html", include_str!("web/bot.html").to_string()).unwrap(); e.add_template_owned("positions.html", include_str!("web/positions.html").to_string()).unwrap(); + e.add_template_owned("wallet.html", include_str!("web/wallet.html").to_string()).unwrap(); e }) } diff --git a/src/web/_header.html b/src/web/_header.html index 0be915a..b114f80 100644 --- a/src/web/_header.html +++ b/src/web/_header.html @@ -5,6 +5,7 @@ Dashboard Botlar Pozisyonlar + Cüzdanlar
diff --git a/src/web/wallet.html b/src/web/wallet.html new file mode 100644 index 0000000..ca2201a --- /dev/null +++ b/src/web/wallet.html @@ -0,0 +1,178 @@ + + + + + + Cüzdanlar — CryptoFox Mukan Edition + + + + +{% include "_header.html" %} + +
+
+ Cüzdanlar +
+ + + +
+
+ +
+
+
+ Spot Cüzdan + +
+ + + +
VarlıkKullanılabilirKilitli
Yükleniyor...
+
+
+ + + + +
+ + + +