Bot & Automation
Market AI
/root/hermes-projects/Market AI
packages/core/src/formatters.ts
text
import { BOT_NAME, MEME_MENU } from "./constants.js";
import type { MemeCoinCandidate, MemeSignalAnalysis, TradeExecutionResult, TradeQuote } from "./types.js";
export function escapeHtml(value: string): string {
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
}
export function formatMainMenuCard(): string {
return [
`<b>${BOT_NAME}</b>`,
"<i>Scanner dan trading assistant khusus meme coin. Default aman: paper trading sampai live executor dikonfigurasi.</i>",
"",
"<b>Menu:</b>",
`- ${MEME_MENU.scanner}`,
`- ${MEME_MENU.topPicks}`,
`- ${MEME_MENU.analyze}`,
`- ${MEME_MENU.buy} / ${MEME_MENU.sell}`,
`- ${MEME_MENU.positions}`,
`- ${MEME_MENU.watchlist}`,
`- ${MEME_MENU.alerts}`,
"",
"<b>Contoh:</b>",
"<code>/scan solana</code>",
"<code>/analyze BONK</code>",
"<code>/buy TOKEN_ADDRESS 0.05</code>",
"<code>/sell TOKEN_ADDRESS 25</code>",
"",
"<i>Bot tidak menjamin profit. Semua live trade butuh konfirmasi dan env executor.</i>",
].join("\n");
}
export function formatCandidateLine(candidate: MemeCoinCandidate, index: number): string {
const token = candidate.token;
const metrics = candidate.metrics;
return [
`<b>${index}. ${escapeHtml(token.symbol)}</b> ${escapeHtml(token.name)}`,
`Score ${candidate.score}/100 | ${candidate.verdict} | Risk ${candidate.riskLevel}`,
`Chain ${escapeHtml(token.chainId)} | Liq $${Math.round(metrics.liquidityUsd).toLocaleString("en-US")} | Vol24 $${Math.round(metrics.volumeH24).toLocaleString("en-US")}`,
`H1 ${metrics.priceChangeH1 ?? 0}% | H24 ${metrics.priceChangeH24 ?? 0}% | Buys/Sells ${metrics.buysH24}/${metrics.sellsH24}`,
token.url ? `<a href="${escapeHtml(token.url)}">DexScreener</a>` : "",
]
.filter(Boolean)
.join("\n");
}
export function formatScannerResults(candidates: MemeCoinCandidate[]): string {
if (candidates.length === 0) {
return [
"<b>Scanner Result</b>",
"",
"Belum ada kandidat yang lolos filter saat ini.",
"Turunkan filter atau scan lagi nanti saat volume market meningkat.",
].join("\n");
}
return [
"<b>Meme Coin Scanner</b>",
"<i>Kandidat diurutkan berdasarkan liquidity, volume, momentum, buy/sell pressure, dan penalti risiko.</i>",
"",
...candidates.flatMap((candidate, index) => [formatCandidateLine(candidate, index + 1), ""]),
].join("\n");
}
export function formatMemeSignalCard(signal: MemeSignalAnalysis): string {
const { candidate } = signal;
return [
"<b>MEME COIN ANALYSIS</b>",
"",
`<b>Token</b>: ${escapeHtml(candidate.token.symbol)} - ${escapeHtml(candidate.token.name)}`,
`<b>Chain</b>: ${escapeHtml(candidate.token.chainId)}`,
`<b>Address</b>: <code>${escapeHtml(candidate.token.tokenAddress)}</code>`,
candidate.token.url ? `<b>Chart</b>: <a href="${escapeHtml(candidate.token.url)}">DexScreener</a>` : "",
"",
`<b>Action</b>: ${signal.action}`,
`<b>Trend</b>: ${signal.trend}`,
`<b>Confidence</b>: ${signal.confidence}%`,
`<b>Scanner Score</b>: ${candidate.score}/100`,
`<b>Verdict</b>: ${candidate.verdict}`,
`<b>Risk</b>: ${signal.riskLevel}`,
`<b>Max Position Risk</b>: ${escapeHtml(signal.maxPositionSize)}`,
"",
"<b>Market Data</b>",
`Price: $${candidate.metrics.priceUsd}`,
`Liquidity: $${Math.round(candidate.metrics.liquidityUsd).toLocaleString("en-US")}`,
`Volume 24h: $${Math.round(candidate.metrics.volumeH24).toLocaleString("en-US")}`,
`FDV: ${candidate.metrics.fdv ? `$${Math.round(candidate.metrics.fdv).toLocaleString("en-US")}` : "N/A"}`,
`H1/H24: ${candidate.metrics.priceChangeH1 ?? 0}% / ${candidate.metrics.priceChangeH24 ?? 0}%`,
`Buys/Sells 24h: ${candidate.metrics.buysH24}/${candidate.metrics.sellsH24}`,
"",
"<b>Plan</b>",
`Entry: ${escapeHtml(signal.entryPlan)}`,
`Stop: ${escapeHtml(signal.stopLossPlan)}`,
`TP: ${escapeHtml(signal.takeProfitPlan)}`,
"",
"<b>Reason</b>",
...signal.reasons.map((reason) => `- ${escapeHtml(reason)}`),
signal.warnings.length ? "" : "",
signal.warnings.length ? "<b>Warnings</b>" : "",
...signal.warnings.map((warning) => `- ${escapeHtml(warning)}`),
"",
`<b>Invalidation</b>: ${escapeHtml(signal.invalidation)}`,
"",
`<i>${escapeHtml(signal.disclaimer)}</i>`,
]
.filter((line) => line !== "")
.join("\n");
}
export function formatTradeQuote(quote: TradeQuote): string {
return [
"<b>Trade Preview</b>",
"",
`<b>Mode</b>: ${quote.request.mode.toUpperCase()}`,
`<b>Side</b>: ${quote.request.side}`,
`<b>Chain</b>: ${escapeHtml(quote.request.chainId)}`,
`<b>Token</b>: <code>${escapeHtml(quote.request.tokenAddress)}</code>`,
`<b>Amount</b>: ${quote.request.amount}`,
`<b>Slippage</b>: ${quote.request.slippageBps / 100}%`,
`<b>Estimated Output</b>: ${escapeHtml(quote.estimatedOutput)} ${escapeHtml(quote.outputSymbol)}`,
`<b>Route</b>: ${escapeHtml(quote.routeSummary)}`,
quote.priceImpactPct !== undefined ? `<b>Price Impact</b>: ${quote.priceImpactPct}%` : "",
"",
...quote.warnings.map((warning) => `- ${escapeHtml(warning)}`),
"",
"<i>Tekan Confirm hanya jika sudah paham risiko. Live trade membutuhkan executor/wallet yang valid.</i>",
]
.filter(Boolean)
.join("\n");
}
export function formatTradeResult(result: TradeExecutionResult): string {
return [
"<b>Trade Result</b>",
"",
`<b>Status</b>: ${result.status}`,
`<b>Mode</b>: ${result.mode}`,
result.txHash ? `<b>Tx</b>: <code>${escapeHtml(result.txHash)}</code>` : "",
escapeHtml(result.message),
]
.filter(Boolean)
.join("\n");
}
export function formatAlertHelp(): string {
return [
"<b>Meme Coin Alert</b>",
"",
"Format:",
"<code>/alert TOKEN_ADDRESS price_above 0.001</code>",
"<code>/alert TOKEN_ADDRESS score_above 75</code>",
"",
"Alert aktif saat worker database-backed rule dihubungkan ke scheduler.",
].join("\n");
}