diff --git a/src/binance/client.rs b/src/binance/client.rs index da8fc64..8e9c46a 100644 --- a/src/binance/client.rs +++ b/src/binance/client.rs @@ -562,6 +562,21 @@ fn parse_filled_order(data: &Value, side: OrderSide) -> Result { } }; + // fills'den komisyonu qty * fill_price * rate olarak USDT'ye çevir + let commission_usdt = data["fills"] + .as_array() + .map(|fills| { + fills.iter().fold(0.0f64, |acc, f| { + let qty: f64 = f["qty"].as_str().unwrap_or("0").parse().unwrap_or(0.0); + let fill_price: f64 = f["price"].as_str().unwrap_or("0").parse().unwrap_or(0.0); + let commission: f64 = f["commission"].as_str().unwrap_or("0").parse().unwrap_or(0.0); + // komisyon oranı = commission / qty, USDT karşılığı = oran × qty × fiyat + let rate = if qty > 0.0 { commission / qty } else { 0.001 }; + acc + rate * qty * fill_price + }) + }) + .unwrap_or(0.0); + Ok(FilledOrder { order_id: data["orderId"].as_u64().unwrap_or(0), symbol: data["symbol"].as_str().unwrap_or("").to_string(), @@ -569,5 +584,6 @@ fn parse_filled_order(data: &Value, side: OrderSide) -> Result { price, quantity: executed_qty, timestamp: data["transactTime"].as_i64().unwrap_or(0), + commission_usdt, }) } diff --git a/src/binance/models.rs b/src/binance/models.rs index a5ee071..bce1622 100644 --- a/src/binance/models.rs +++ b/src/binance/models.rs @@ -37,6 +37,7 @@ pub struct FilledOrder { pub price: f64, pub quantity: f64, pub timestamp: i64, + pub commission_usdt: f64, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] diff --git a/src/bot/runner.rs b/src/bot/runner.rs index bf05658..b65ccd8 100644 --- a/src/bot/runner.rs +++ b/src/bot/runner.rs @@ -142,15 +142,16 @@ impl BotRunner { }; let position = OpenPosition { - bot_id: config.id.clone(), - bot_name: config.name.clone(), - symbol: config.symbol.clone(), - order_id: result.sell_order.order_id, - buy_price: result.buy_order.price, - sell_target: result.sell_order.price, - quantity: result.buy_order.quantity, - profit_percent: config.profit_percent, - opened_at: result.timestamp, + bot_id: config.id.clone(), + bot_name: config.name.clone(), + symbol: config.symbol.clone(), + order_id: result.sell_order.order_id, + buy_price: result.buy_order.price, + sell_target: result.sell_order.price, + quantity: result.buy_order.quantity, + profit_percent: config.profit_percent, + opened_at: result.timestamp, + buy_commission_usdt: result.buy_order.commission_usdt, }; { diff --git a/src/storage/db.rs b/src/storage/db.rs index e4e9b75..fc66da9 100644 --- a/src/storage/db.rs +++ b/src/storage/db.rs @@ -85,6 +85,10 @@ impl Database { ); CREATE INDEX IF NOT EXISTS idx_bot_logs_bot_id ON bot_logs(bot_id, created_at DESC);", )?; + // migration: mevcut DB'ye buy_commission_usdt sütunu ekle + conn.execute_batch( + "ALTER TABLE open_positions ADD COLUMN buy_commission_usdt REAL NOT NULL DEFAULT 0;", + ).ok(); Ok(Self { conn }) } @@ -188,12 +192,12 @@ impl Database { pub fn insert_position(&self, p: &OpenPosition) -> SqlResult<()> { self.conn.execute( "INSERT OR REPLACE INTO open_positions - (order_id, bot_id, bot_name, symbol, buy_price, sell_target, quantity, profit_percent, opened_at) - VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)", + (order_id, bot_id, bot_name, symbol, buy_price, sell_target, quantity, profit_percent, opened_at, buy_commission_usdt) + VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)", params![ p.order_id as i64, p.bot_id, p.bot_name, p.symbol, p.buy_price, p.sell_target, p.quantity, - p.profit_percent, p.opened_at + p.profit_percent, p.opened_at, p.buy_commission_usdt ], )?; Ok(()) @@ -201,21 +205,22 @@ impl Database { pub fn get_positions(&self) -> SqlResult> { let mut stmt = self.conn.prepare( - "SELECT order_id, bot_id, bot_name, symbol, buy_price, sell_target, quantity, profit_percent, opened_at + "SELECT order_id, bot_id, bot_name, symbol, buy_price, sell_target, quantity, profit_percent, opened_at, buy_commission_usdt FROM open_positions ORDER BY opened_at DESC", )?; let rows = stmt.query_map([], |row| { let order_id: i64 = row.get(0)?; Ok(OpenPosition { - order_id: order_id as u64, - bot_id: row.get(1)?, - bot_name: row.get(2)?, - symbol: row.get(3)?, - buy_price: row.get(4)?, - sell_target: row.get(5)?, - quantity: row.get(6)?, - profit_percent: row.get(7)?, - opened_at: row.get(8)?, + order_id: order_id as u64, + bot_id: row.get(1)?, + bot_name: row.get(2)?, + symbol: row.get(3)?, + buy_price: row.get(4)?, + sell_target: row.get(5)?, + quantity: row.get(6)?, + profit_percent: row.get(7)?, + opened_at: row.get(8)?, + buy_commission_usdt: row.get(9)?, }) })?; rows.collect() @@ -338,21 +343,22 @@ impl Database { pub fn get_positions_by_bot(&self, bot_id: &str) -> SqlResult> { let mut stmt = self.conn.prepare( - "SELECT order_id, bot_id, bot_name, symbol, buy_price, sell_target, quantity, profit_percent, opened_at + "SELECT order_id, bot_id, bot_name, symbol, buy_price, sell_target, quantity, profit_percent, opened_at, buy_commission_usdt FROM open_positions WHERE bot_id = ?1 ORDER BY opened_at DESC", )?; let rows = stmt.query_map(params![bot_id], |row| { let order_id: i64 = row.get(0)?; Ok(crate::storage::positions::OpenPosition { - order_id: order_id as u64, - bot_id: row.get(1)?, - bot_name: row.get(2)?, - symbol: row.get(3)?, - buy_price: row.get(4)?, - sell_target: row.get(5)?, - quantity: row.get(6)?, - profit_percent: row.get(7)?, - opened_at: row.get(8)?, + order_id: order_id as u64, + bot_id: row.get(1)?, + bot_name: row.get(2)?, + symbol: row.get(3)?, + buy_price: row.get(4)?, + sell_target: row.get(5)?, + quantity: row.get(6)?, + profit_percent: row.get(7)?, + opened_at: row.get(8)?, + buy_commission_usdt: row.get(9)?, }) })?; rows.collect() diff --git a/src/storage/positions.rs b/src/storage/positions.rs index b8114fc..ad0198d 100644 --- a/src/storage/positions.rs +++ b/src/storage/positions.rs @@ -12,4 +12,5 @@ pub struct OpenPosition { pub quantity: f64, pub profit_percent: f64, pub opened_at: i64, // ms + pub buy_commission_usdt: f64, } diff --git a/src/web/bot.html b/src/web/bot.html index 1ecb0d7..ab3b182 100644 --- a/src/web/bot.html +++ b/src/web/bot.html @@ -621,9 +621,17 @@ function renderOpenTable() { return; } tbody.innerHTML = openPositionsCache.map(p => { - const pnl = currentPrice > 0 - ? ((currentPrice - p.buy_price) / p.buy_price * 100) - : null; + let pnl = null; + if (currentPrice > 0) { + const grossPnl = (currentPrice - p.buy_price) / p.buy_price * 100; + // alış komisyonu % olarak: buy_commission_usdt / (buy_price * quantity) * 100 + const buyCommissionPct = p.buy_commission_usdt > 0 + ? (p.buy_commission_usdt / (p.buy_price * p.quantity) * 100) + : 0.1; + // satış komisyonu tahmini: aynı oran üzerinden (currentPrice × quantity için) + const sellCommissionPct = buyCommissionPct; + pnl = grossPnl - buyCommissionPct - sellCommissionPct; + } const pnlHtml = pnl !== null ? `${pnl >= 0 ? '+' : ''}${pnl.toFixed(2)}%` : '';