diff --git a/src/bin/sim.rs b/src/bin/sim.rs index 6fcffc5..8e41180 100644 --- a/src/bin/sim.rs +++ b/src/bin/sim.rs @@ -16,26 +16,30 @@ async fn main() -> Result<()> { let tg = TelegramConfig { token, chat_id, thread_id }; let http = Client::new(); - // 1. Pending - tracing::info!("step 1: PENDING"); - let msg_id = tg.send(&http, - "๐ŸŸก PENDING\nXAUUSDm Long\nEntry: 3325.50\nSL: 3320.00 TP: 3333.75\nVol: 0.03 lot RR: 1.5", + // โ”€โ”€ Flow: pending โ†’ filled โ†’ TP hit โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + let msg = tg.send(&http, + "๐ŸŸก PENDING\nXAUUSDm ยท Long\n\nEntry 3325.50\nTP 3333.75\nSL 3320.00\nVol 0.03 lot ยท RR 1:1.5" ).await?; - tracing::info!(msg_id, "sent"); + tracing::info!(msg_id = msg, "PENDING sent"); sleep(Duration::from_secs(3)).await; - // 2. Filled - tracing::info!("step 2: FILLED"); - tg.edit(&http, msg_id, - "โšก FILLED\nXAUUSDm Long\nEntry: 3325.50\nSL: 3320.00 TP: 3333.75\nVol: 0.03 lot", + tg.edit(&http, msg, + "โšก FILLED\nXAUUSDm ยท Long\n\nEntry 3325.50\nTP 3333.75\nSL 3320.00\nVol 0.03 lot" ).await?; + tracing::info!(msg_id = msg, "FILLED edited"); sleep(Duration::from_secs(3)).await; - // 3. Result - tracing::info!("step 3: TP HIT"); - tg.edit(&http, msg_id, - "โœ… TP HIT +12.68\nXAUUSDm Long\n3325.50 โ†’ 3333.75\nVol: 0.03 lot\nBal: $5012.68", + tg.edit(&http, msg, + "โœ… TAKE PROFIT +12.68\nXAUUSDm ยท Long\n\n3325.50 โ†’ 3333.75\nVol 0.03 lot ยท Bal $5012.68" ).await?; + tracing::info!(msg_id = msg, "TP HIT edited"); + sleep(Duration::from_secs(3)).await; + + // โ”€โ”€ PnL summary โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + tg.send(&http, + "๐Ÿ“Š PnL Summary\n\nToday ยท XAUUSDm ยท 2026-06-11\n2 trades ยท 1W 1L ยท WR 50%\nNet +$5.18\n\nAll-time\n17 trades ยท 10W 7L ยท WR 59%\nNet +$482.41" + ).await?; + tracing::info!("PnL summary sent"); println!("\nDone โœ… check Telegram"); Ok(()) diff --git a/src/live.rs b/src/live.rs index 0245057..d7b765e 100644 --- a/src/live.rs +++ b/src/live.rs @@ -246,9 +246,9 @@ async fn tick( } if let (Some(tg), Some(msg_id)) = (&cfg.telegram, state.tg_message_id) { let text = format!( - "โฑ EXPIRED\n{} {}\nEntry: {:.5}\nNo fill after {} candles", - symbol, state.side, state.entry, - cfg.fvg_expiry_candles, + "โฑ EXPIRED\n{} ยท {}\n\nEntry {}\nUnfilled after {} candles", + symbol, state.side, + fp(state.entry), cfg.fvg_expiry_candles, ); let _ = tg.edit(http, msg_id, &text).await; } @@ -353,9 +353,9 @@ async fn tick( // send Telegram "Pending" message let tg_msg_id = if let Some(tg) = &cfg.telegram { let text = format!( - "๐ŸŸก PENDING\n{} {}\nEntry: {}\nSL: {} TP: {}\nVol: {} lot RR: {:.1}", + "๐ŸŸก PENDING\n{} ยท {}\n\nEntry {}\nTP {}\nSL {}\nVol {} lot ยท RR 1:{:.1}", symbol, side_str, - fmt_price(fvg.entry, prec), fmt_price(sl, prec), fmt_price(tp, prec), + fmt_price(fvg.entry, prec), fmt_price(tp, prec), fmt_price(sl, prec), volume, rr, ); match tg.send(http, &text).await { @@ -493,8 +493,9 @@ async fn on_position_opened( if let (Some(tg), Some(msg_id)) = (tg, tg_msg_id) { let text = format!( - "โšก FILLED\n{} {}\nEntry: {:.5}\nSL: {:.5} TP: {:.5}\nVol: {:.2} lot", - symbol, ps.side, entry, sl, tp, volume, + "โšก FILLED\n{} ยท {}\n\nEntry {}\nTP {}\nSL {}\nVol {:.2} lot", + symbol, ps.side, + fp(entry), fp(tp), fp(sl), volume, ); let _ = tg.edit(http, msg_id, &text).await; } @@ -516,7 +517,7 @@ async fn on_position_closed( let Some(tg) = tg else { return }; let Some(msg_id) = ps.as_ref().and_then(|s| s.tg_message_id) else { return }; - let (icon, label) = if profit >= 0.0 { ("โœ…", "TP HIT") } else { ("โŒ", "SL HIT") }; + let (icon, label) = if profit >= 0.0 { ("โœ…", "TAKE PROFIT") } else { ("โŒ", "STOP LOSS") }; let exit = payload.price_current.unwrap_or(0.0); let entry = ps.as_ref().map(|s| s.entry).unwrap_or(payload.price_open.unwrap_or(0.0)); let volume = payload.volume.unwrap_or(ps.as_ref().map(|s| s.volume).unwrap_or(0.0)); @@ -525,11 +526,11 @@ async fn on_position_closed( let acct_bal = mt5.account().await.ok().map(|a| a.balance).unwrap_or(Decimal::ZERO); let text = format!( - "{} {} {:+.2}\n{} {}\n{:.5} โ†’ {:.5}\nVol: {:.2} lot\nBal: ${}", + "{} {} {:+.2}\n{} ยท {}\n\n{} โ†’ {}\nVol {:.2} lot ยท Bal ${:.2}", icon, label, profit, symbol, side_str, - entry, exit, - volume, acct_bal, + fp(entry), fp(exit), + volume, d2f(acct_bal), ); let _ = tg.edit(http, msg_id, &text).await; } @@ -550,8 +551,9 @@ async fn on_position_modified( if let (Some(tg), Some(msg_id)) = (tg, ps.tg_message_id) { let text = format!( - "๐Ÿ”„ MODIFIED\n{} {}\nEntry: {:.5}\nSL: {:.5} TP: {:.5}\nVol: {:.2} lot", - symbol, ps.side, ps.entry, ps.sl, ps.tp, ps.volume, + "๐Ÿ”„ MODIFIED\n{} ยท {}\n\nEntry {}\nTP {}\nSL {}\nVol {:.2} lot", + symbol, ps.side, + fp(ps.entry), fp(ps.tp), fp(ps.sl), ps.volume, ); let _ = tg.edit(http, msg_id, &text).await; } @@ -573,8 +575,8 @@ async fn on_order_cancelled( let side_str = state.as_ref().map(|s| s.side.as_str()).unwrap_or("?"); let entry = state.as_ref().map(|s| s.entry).unwrap_or(0.0); let text = format!( - "๐Ÿšซ CANCELLED\n{} {}\nEntry: {:.5}", - symbol, side_str, entry, + "๐Ÿšซ CANCELLED\n{} ยท {}\n\nEntry {}", + symbol, side_str, fp(entry), ); let _ = tg.edit(http, msg_id, &text).await; } @@ -598,8 +600,9 @@ async fn on_order_modified( if let (Some(tg), Some(msg_id)) = (tg, state.tg_message_id) { let text = format!( - "๐Ÿ”„ ORDER MODIFIED\n{} {}\nEntry: {:.5}\nSL: {:.5} TP: {:.5}", - symbol, state.side, state.entry, state.sl, state.tp, + "๐Ÿ”„ ORDER MODIFIED\n{} ยท {}\n\nEntry {}\nTP {}\nSL {}", + symbol, state.side, + fp(state.entry), fp(state.tp), fp(state.sl), ); let _ = tg.edit(http, msg_id, &text).await; } @@ -624,27 +627,27 @@ async fn send_pnl_summary( mt5.history_deals(&epoch_str, &now_str, Some(symbol)), )?; - let summary_text = |label: &str, deals: &[domain::Deal]| { + let summary_text = |deals: &[domain::Deal]| { let closing: Vec<_> = deals.iter() .filter(|d| d.entry == 1 && d.magic == MAGIC) .collect(); let total = closing.len(); let wins = closing.iter().filter(|d| d.profit > Decimal::ZERO).count(); let losses = total - wins; - let profit: Decimal = closing.iter().map(|d| d.profit + d.commission + d.swap).sum(); + let net: Decimal = closing.iter().map(|d| d.profit + d.commission + d.swap).sum(); let wr = if total > 0 { wins as f64 / total as f64 * 100.0 } else { 0.0 }; format!( - "{}\nTrades: {} ({}W / {}L) WR: {:.0}%\nProfit: ${:+.2}", - label, total, wins, losses, wr, profit, + "{} trades ยท {}W {}L ยท WR {:.0}%\nNet {:+.2}", + total, wins, losses, wr, net, ) }; let today_date = now.format("%Y-%m-%d").to_string(); let text = format!( - "๐Ÿ“Š Today โ€” {} {}\n{}\n\n๐Ÿ“ˆ All-time\n{}", + "๐Ÿ“Š PnL Summary\n\nToday ยท {} ยท {}\n{}\n\nAll-time\n{}", symbol, today_date, - summary_text("", &today_deals), - summary_text("", &all_deals), + summary_text(&today_deals), + summary_text(&all_deals), ); tg.send(http, &text).await?; @@ -653,6 +656,12 @@ async fn send_pnl_summary( // โ”€โ”€ helpers โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +fn fp(v: f64) -> String { + let s = format!("{v:.5}"); + let s = s.trim_end_matches('0'); + s.trim_end_matches('.').to_string() +} + fn timeframe_minutes(tf: Timeframe) -> i64 { match tf { Timeframe::M1 => 1,