mirror of
https://github.com/RomySaputraSihananda/ares.git
synced 2026-08-23 07:38:12 +00:00
feat: improve Telegram message formatting
- Consistent header: icon + bold status + symbol · side - Prices wrapped in <code> for monospace rendering - TP/SL shown on separate lines (Entry/TP/SL aligned) - Closed trades: entry → exit with vol + balance on one line - PnL summary: trades · wins/losses · WR% with bold net P&L - Add fp() helper for f64 price without trailing zeros Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
954cf6b8cf
commit
4fdc3da4ed
+17
-13
@@ -16,26 +16,30 @@ async fn main() -> Result<()> {
|
|||||||
let tg = TelegramConfig { token, chat_id, thread_id };
|
let tg = TelegramConfig { token, chat_id, thread_id };
|
||||||
let http = Client::new();
|
let http = Client::new();
|
||||||
|
|
||||||
// 1. Pending
|
// ── Flow: pending → filled → TP hit ──────────────────────────────────────
|
||||||
tracing::info!("step 1: PENDING");
|
let msg = tg.send(&http,
|
||||||
let msg_id = tg.send(&http,
|
"🟡 <b>PENDING</b>\nXAUUSDm · Long\n\nEntry <code>3325.50</code>\nTP <code>3333.75</code>\nSL <code>3320.00</code>\nVol 0.03 lot · RR 1:1.5"
|
||||||
"🟡 <b>PENDING</b>\nXAUUSDm Long\nEntry: 3325.50\nSL: 3320.00 TP: 3333.75\nVol: 0.03 lot RR: 1.5",
|
|
||||||
).await?;
|
).await?;
|
||||||
tracing::info!(msg_id, "sent");
|
tracing::info!(msg_id = msg, "PENDING sent");
|
||||||
sleep(Duration::from_secs(3)).await;
|
sleep(Duration::from_secs(3)).await;
|
||||||
|
|
||||||
// 2. Filled
|
tg.edit(&http, msg,
|
||||||
tracing::info!("step 2: FILLED");
|
"⚡ <b>FILLED</b>\nXAUUSDm · Long\n\nEntry <code>3325.50</code>\nTP <code>3333.75</code>\nSL <code>3320.00</code>\nVol 0.03 lot"
|
||||||
tg.edit(&http, msg_id,
|
|
||||||
"⚡ <b>FILLED</b>\nXAUUSDm Long\nEntry: 3325.50\nSL: 3320.00 TP: 3333.75\nVol: 0.03 lot",
|
|
||||||
).await?;
|
).await?;
|
||||||
|
tracing::info!(msg_id = msg, "FILLED edited");
|
||||||
sleep(Duration::from_secs(3)).await;
|
sleep(Duration::from_secs(3)).await;
|
||||||
|
|
||||||
// 3. Result
|
tg.edit(&http, msg,
|
||||||
tracing::info!("step 3: TP HIT");
|
"✅ <b>TAKE PROFIT +12.68</b>\nXAUUSDm · Long\n\n<code>3325.50</code> → <code>3333.75</code>\nVol 0.03 lot · Bal $5012.68"
|
||||||
tg.edit(&http, msg_id,
|
|
||||||
"✅ <b>TP HIT +12.68</b>\nXAUUSDm Long\n3325.50 → 3333.75\nVol: 0.03 lot\nBal: $5012.68",
|
|
||||||
).await?;
|
).await?;
|
||||||
|
tracing::info!(msg_id = msg, "TP HIT edited");
|
||||||
|
sleep(Duration::from_secs(3)).await;
|
||||||
|
|
||||||
|
// ── PnL summary ───────────────────────────────────────────────────────────
|
||||||
|
tg.send(&http,
|
||||||
|
"📊 <b>PnL Summary</b>\n\n<b>Today</b> · XAUUSDm · 2026-06-11\n2 trades · 1W 1L · WR 50%\nNet <b>+$5.18</b>\n\n<b>All-time</b>\n17 trades · 10W 7L · WR 59%\nNet <b>+$482.41</b>"
|
||||||
|
).await?;
|
||||||
|
tracing::info!("PnL summary sent");
|
||||||
|
|
||||||
println!("\nDone ✅ check Telegram");
|
println!("\nDone ✅ check Telegram");
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
+33
-24
@@ -246,9 +246,9 @@ async fn tick(
|
|||||||
}
|
}
|
||||||
if let (Some(tg), Some(msg_id)) = (&cfg.telegram, state.tg_message_id) {
|
if let (Some(tg), Some(msg_id)) = (&cfg.telegram, state.tg_message_id) {
|
||||||
let text = format!(
|
let text = format!(
|
||||||
"⏱ <b>EXPIRED</b>\n{} {}\nEntry: {:.5}\nNo fill after {} candles",
|
"⏱ <b>EXPIRED</b>\n{} · {}\n\nEntry <code>{}</code>\nUnfilled after {} candles",
|
||||||
symbol, state.side, state.entry,
|
symbol, state.side,
|
||||||
cfg.fvg_expiry_candles,
|
fp(state.entry), cfg.fvg_expiry_candles,
|
||||||
);
|
);
|
||||||
let _ = tg.edit(http, msg_id, &text).await;
|
let _ = tg.edit(http, msg_id, &text).await;
|
||||||
}
|
}
|
||||||
@@ -353,9 +353,9 @@ async fn tick(
|
|||||||
// send Telegram "Pending" message
|
// send Telegram "Pending" message
|
||||||
let tg_msg_id = if let Some(tg) = &cfg.telegram {
|
let tg_msg_id = if let Some(tg) = &cfg.telegram {
|
||||||
let text = format!(
|
let text = format!(
|
||||||
"🟡 <b>PENDING</b>\n{} {}\nEntry: {}\nSL: {} TP: {}\nVol: {} lot RR: {:.1}",
|
"🟡 <b>PENDING</b>\n{} · {}\n\nEntry <code>{}</code>\nTP <code>{}</code>\nSL <code>{}</code>\nVol {} lot · RR 1:{:.1}",
|
||||||
symbol, side_str,
|
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,
|
volume, rr,
|
||||||
);
|
);
|
||||||
match tg.send(http, &text).await {
|
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) {
|
if let (Some(tg), Some(msg_id)) = (tg, tg_msg_id) {
|
||||||
let text = format!(
|
let text = format!(
|
||||||
"⚡ <b>FILLED</b>\n{} {}\nEntry: {:.5}\nSL: {:.5} TP: {:.5}\nVol: {:.2} lot",
|
"⚡ <b>FILLED</b>\n{} · {}\n\nEntry <code>{}</code>\nTP <code>{}</code>\nSL <code>{}</code>\nVol {:.2} lot",
|
||||||
symbol, ps.side, entry, sl, tp, volume,
|
symbol, ps.side,
|
||||||
|
fp(entry), fp(tp), fp(sl), volume,
|
||||||
);
|
);
|
||||||
let _ = tg.edit(http, msg_id, &text).await;
|
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(tg) = tg else { return };
|
||||||
let Some(msg_id) = ps.as_ref().and_then(|s| s.tg_message_id) 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 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 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));
|
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 acct_bal = mt5.account().await.ok().map(|a| a.balance).unwrap_or(Decimal::ZERO);
|
||||||
|
|
||||||
let text = format!(
|
let text = format!(
|
||||||
"{} <b>{} {:+.2}</b>\n{} {}\n{:.5} → {:.5}\nVol: {:.2} lot\nBal: ${}",
|
"{} <b>{} {:+.2}</b>\n{} · {}\n\n<code>{}</code> → <code>{}</code>\nVol {:.2} lot · Bal ${:.2}",
|
||||||
icon, label, profit,
|
icon, label, profit,
|
||||||
symbol, side_str,
|
symbol, side_str,
|
||||||
entry, exit,
|
fp(entry), fp(exit),
|
||||||
volume, acct_bal,
|
volume, d2f(acct_bal),
|
||||||
);
|
);
|
||||||
let _ = tg.edit(http, msg_id, &text).await;
|
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) {
|
if let (Some(tg), Some(msg_id)) = (tg, ps.tg_message_id) {
|
||||||
let text = format!(
|
let text = format!(
|
||||||
"🔄 <b>MODIFIED</b>\n{} {}\nEntry: {:.5}\nSL: {:.5} TP: {:.5}\nVol: {:.2} lot",
|
"🔄 <b>MODIFIED</b>\n{} · {}\n\nEntry <code>{}</code>\nTP <code>{}</code>\nSL <code>{}</code>\nVol {:.2} lot",
|
||||||
symbol, ps.side, ps.entry, ps.sl, ps.tp, ps.volume,
|
symbol, ps.side,
|
||||||
|
fp(ps.entry), fp(ps.tp), fp(ps.sl), ps.volume,
|
||||||
);
|
);
|
||||||
let _ = tg.edit(http, msg_id, &text).await;
|
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 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 entry = state.as_ref().map(|s| s.entry).unwrap_or(0.0);
|
||||||
let text = format!(
|
let text = format!(
|
||||||
"🚫 <b>CANCELLED</b>\n{} {}\nEntry: {:.5}",
|
"🚫 <b>CANCELLED</b>\n{} · {}\n\nEntry <code>{}</code>",
|
||||||
symbol, side_str, entry,
|
symbol, side_str, fp(entry),
|
||||||
);
|
);
|
||||||
let _ = tg.edit(http, msg_id, &text).await;
|
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) {
|
if let (Some(tg), Some(msg_id)) = (tg, state.tg_message_id) {
|
||||||
let text = format!(
|
let text = format!(
|
||||||
"🔄 <b>ORDER MODIFIED</b>\n{} {}\nEntry: {:.5}\nSL: {:.5} TP: {:.5}",
|
"🔄 <b>ORDER MODIFIED</b>\n{} · {}\n\nEntry <code>{}</code>\nTP <code>{}</code>\nSL <code>{}</code>",
|
||||||
symbol, state.side, state.entry, state.sl, state.tp,
|
symbol, state.side,
|
||||||
|
fp(state.entry), fp(state.tp), fp(state.sl),
|
||||||
);
|
);
|
||||||
let _ = tg.edit(http, msg_id, &text).await;
|
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)),
|
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()
|
let closing: Vec<_> = deals.iter()
|
||||||
.filter(|d| d.entry == 1 && d.magic == MAGIC)
|
.filter(|d| d.entry == 1 && d.magic == MAGIC)
|
||||||
.collect();
|
.collect();
|
||||||
let total = closing.len();
|
let total = closing.len();
|
||||||
let wins = closing.iter().filter(|d| d.profit > Decimal::ZERO).count();
|
let wins = closing.iter().filter(|d| d.profit > Decimal::ZERO).count();
|
||||||
let losses = total - wins;
|
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 };
|
let wr = if total > 0 { wins as f64 / total as f64 * 100.0 } else { 0.0 };
|
||||||
format!(
|
format!(
|
||||||
"{}\nTrades: {} ({}W / {}L) WR: {:.0}%\nProfit: ${:+.2}",
|
"{} trades · {}W {}L · WR {:.0}%\nNet <b>{:+.2}</b>",
|
||||||
label, total, wins, losses, wr, profit,
|
total, wins, losses, wr, net,
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
let today_date = now.format("%Y-%m-%d").to_string();
|
let today_date = now.format("%Y-%m-%d").to_string();
|
||||||
let text = format!(
|
let text = format!(
|
||||||
"📊 <b>Today</b> — {} {}\n{}\n\n📈 <b>All-time</b>\n{}",
|
"📊 <b>PnL Summary</b>\n\n<b>Today</b> · {} · {}\n{}\n\n<b>All-time</b>\n{}",
|
||||||
symbol, today_date,
|
symbol, today_date,
|
||||||
summary_text("", &today_deals),
|
summary_text(&today_deals),
|
||||||
summary_text("", &all_deals),
|
summary_text(&all_deals),
|
||||||
);
|
);
|
||||||
|
|
||||||
tg.send(http, &text).await?;
|
tg.send(http, &text).await?;
|
||||||
@@ -653,6 +656,12 @@ async fn send_pnl_summary(
|
|||||||
|
|
||||||
// ── helpers ───────────────────────────────────────────────────────────────────
|
// ── 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 {
|
fn timeframe_minutes(tf: Timeframe) -> i64 {
|
||||||
match tf {
|
match tf {
|
||||||
Timeframe::M1 => 1,
|
Timeframe::M1 => 1,
|
||||||
|
|||||||
Reference in New Issue
Block a user