feat: session filter, breakeven SL, daily loss limit, startup alert, multi-pair web

Bot improvements:
- Session filter (SESSION_FROM_UTC/TO_UTC) — backtest confirms 08-13 UTC optimal for XAU
- Breakeven SL management (BREAKEVEN_AT_RR) — disabled by default, hurts XAU momentum
- Daily loss limit circuit breaker (DAILY_LOSS_LIMIT_PCT)
- Telegram startup alert with symbol, session, risk, and balance
- MT5 modify_position (TRADE_ACTION_SLTP) support in mt5-client

Web updates:
- Version badge auto-fetched from GitHub Releases API
- GitHub icon link in Nav and footer
- Multi-pair general (not XAUUSDm-specific)
- BTCUSDm backtest results added, session params in params table
- Trades page uses rolling 90-day window instead of hardcoded date

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
romysaputrasihananda
2026-06-12 00:48:45 +07:00
co-authored by Claude Sonnet 4.6
parent dbba172ed3
commit 272fca4f65
13 changed files with 431 additions and 103 deletions
+18 -11
View File
@@ -3,22 +3,27 @@ import Link from "next/link";
export const metadata: Metadata = {
title: "Backtest Results",
description: "ARES backtest results: M5 Momentum FVG scalper on XAUUSDm. Best run: PF 1.42, +43.2% net return, 11.5% max drawdown.",
description: "ARES backtest results: M5 Momentum FVG scalper on XAUUSDm and BTCUSDm. London session filter, EMA-20 trend filter.",
};
const results = [
{ period: "1 Month", tf: "M5", risk: "1%", trades: 159, wr: 55.3, pf: 1.42, ret: 43.2, dd: -11.5, highlight: true },
{ period: "1 Month", tf: "M5", risk: "5%", trades: 159, wr: 55.3, pf: 1.26, ret: 390, dd: -156, highlight: false },
{ period: "1 Week", tf: "M5", risk: "1%", trades: 34, wr: 47.1, pf: 0.94, ret: -6.2, dd: -8.1, highlight: false },
{ period: "Yesterday", tf: "M1", risk: "1%", trades: 36, wr: 47.2, pf: 1.05, ret: 6.6, dd: -51, highlight: false },
{ period: "Yesterday", tf: "M5", risk: "1%", trades: 3, wr: 66.7, pf: null, ret: null, dd: null, highlight: false, note: "Too few trades" },
const results: Array<{
symbol: string; period: string; tf: string; risk: string;
trades: number | null; wr: number; pf: number | null; ret: number | null; dd: number | null;
highlight: boolean; note?: string;
}> = [
{ symbol: "XAUUSDm", period: "1 Month", tf: "M5", risk: "1%", trades: 159, wr: 55.3, pf: 1.42, ret: 43.2, dd: -11.5, highlight: true },
{ symbol: "XAUUSDm", period: "50k bars", tf: "M5", risk: "5%", trades: 1421, wr: 50.3, pf: 1.17, ret: null, dd: null, highlight: false, note: "Session 0813 UTC" },
{ symbol: "BTCUSDm", period: "50k bars", tf: "M5", risk: "5%", trades: null, wr: 56.9, pf: 1.11, ret: null, dd: null, highlight: false, note: "Session 0813 UTC" },
{ symbol: "XAUUSDm", period: "1 Month", tf: "M5", risk: "5%", trades: 159, wr: 55.3, pf: 1.26, ret: 390, dd: -156, highlight: false },
{ symbol: "XAUUSDm", period: "1 Week", tf: "M5", risk: "1%", trades: 34, wr: 47.1, pf: 0.94, ret: -6.2, dd: -8.1, highlight: false },
];
const params = [
["Timeframe", "M5"],
["Symbol", "XAUUSDm"],
["Symbols", "XAUUSDm · BTCUSDm"],
["Session", "08:0013:00 UTC"],
["EMA Period", "20"],
["Min FVG Pips", "3"],
["Min FVG Pips", "1"],
["Min SL Pips", "5"],
["Min RR", "1.5×"],
["FVG Expiry", "10 candles"],
@@ -36,6 +41,7 @@ export default function BacktestPage() {
</h1>
<p className="text-[15px] text-ink-sub max-w-xl">
Historical simulation on real MT5 tick data. Includes spread costs, commission, and slippage.
London session filter (0813 UTC) applied.
</p>
</section>
@@ -74,7 +80,7 @@ export default function BacktestPage() {
<table className="data-table">
<thead>
<tr>
{["Period", "TF", "Risk", "Trades", "Win Rate", "Profit Factor", "Return", "Max DD", ""].map(h => (
{["Symbol", "Period", "TF", "Risk", "Trades", "Win Rate", "Profit Factor", "Return", "Max DD", ""].map(h => (
<th key={h}>{h}</th>
))}
</tr>
@@ -82,10 +88,11 @@ export default function BacktestPage() {
<tbody>
{results.map((r, i) => (
<tr key={i} className={r.highlight ? "bg-s2" : ""}>
<td className="font-mono font-medium text-ink">{r.symbol}</td>
<td className="font-medium text-ink">{r.period}</td>
<td className="font-mono text-ink-sub">{r.tf}</td>
<td className="font-mono text-ink-sub">{r.risk}</td>
<td className="font-mono text-ink-md">{r.trades}</td>
<td className="font-mono text-ink-md">{r.trades ?? "—"}</td>
<td className="font-mono text-ink-md">{r.wr.toFixed(1)}%</td>
<td className="font-mono font-medium">
{r.pf != null
+36 -12
View File
@@ -1,21 +1,22 @@
import type { Metadata } from "next";
import "./globals.css";
import Nav from "@/components/Nav";
import { getLatestVersion, GITHUB_URL } from "@/lib/github";
const SITE_URL = "https://ares.romys.my.id";
export const metadata: Metadata = {
metadataBase: new URL(SITE_URL),
title: {
default: "ARES — Automated Gold Trading Bot",
default: "ARES — Algorithmic Trading Bot",
template: "%s · ARES",
},
description:
"Live forward-test results for ARES, an M5 Momentum FVG scalper built in Rust. Trades XAUUSDm with EMA-20 trend filter and automated risk management.",
"Open-source algorithmic trading bot built in Rust. Momentum FVG scalper with EMA trend filter, automated risk management, and live forward-test results.",
keywords: [
"algorithmic trading", "gold trading bot", "XAUUSD EA",
"algorithmic trading", "trading bot", "open source", "XAUUSD",
"MT5 expert advisor", "Rust trading bot", "FVG scalper",
"forex robot", "automated trading", "ICT strategy",
"forex robot", "automated trading", "momentum strategy",
],
authors: [{ name: "Romy Saputra Sihananda" }],
creator: "Romy Saputra Sihananda",
@@ -28,14 +29,14 @@ export const metadata: Metadata = {
type: "website",
url: SITE_URL,
siteName: "ARES Trading Bot",
title: "ARES — Automated Gold Trading Bot",
description: "Live forward-test · M5 Momentum FVG scalper · XAUUSDm · Built in Rust",
title: "ARES — Algorithmic Trading Bot",
description: "Open-source Momentum FVG scalper built in Rust · Live forward-test · MT5",
locale: "en_US",
},
twitter: {
card: "summary_large_image",
title: "ARES — Automated Gold Trading Bot",
description: "Live forward-test · M5 Momentum FVG scalper · XAUUSDm · Built in Rust",
title: "ARES — Algorithmic Trading Bot",
description: "Open-source Momentum FVG scalper built in Rust · Live forward-test · MT5",
},
icons: {
icon: "/favicon.svg",
@@ -46,11 +47,13 @@ export const metadata: Metadata = {
},
};
export default function RootLayout({ children }: { children: React.ReactNode }) {
export default async function RootLayout({ children }: { children: React.ReactNode }) {
const version = await getLatestVersion();
return (
<html lang="en">
<body className="min-h-screen antialiased">
<Nav />
<Nav version={version} />
<main className="max-w-5xl mx-auto px-4 sm:px-6 py-12">
{children}
</main>
@@ -64,10 +67,26 @@ export default function RootLayout({ children }: { children: React.ReactNode })
<path d="M10.2 15.5 H15.8" stroke="white" strokeWidth="1.8" strokeLinecap="round"/>
</svg>
<span className="font-mono text-sm font-semibold" style={{ letterSpacing: "0.18em", color: "var(--c-ink)" }}>ARES</span>
{version && (
<span className="text-[10px] font-mono text-ink-ter bg-s2 border border-hl px-1.5 py-0.5 rounded">
{version}
</span>
)}
</div>
<p className="text-sm leading-relaxed" style={{ color: "var(--c-ink-sub)" }}>
M5 Momentum FVG Scalper<br />Built in Rust · XAUUSDm
<p className="text-sm leading-relaxed mb-3" style={{ color: "var(--c-ink-sub)" }}>
Open-source algorithmic trading bot<br />built in Rust · M5 Momentum FVG
</p>
<a
href={GITHUB_URL}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-1.5 text-xs text-ink-sub hover:text-ink transition-colors"
>
<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z"/>
</svg>
View on GitHub
</a>
</div>
<div>
<p className="eyebrow mb-4">Navigation</p>
@@ -75,6 +94,11 @@ export default function RootLayout({ children }: { children: React.ReactNode })
<li><a href="/" className="hover:text-ink transition-colors">Dashboard</a></li>
<li><a href="/trades" className="hover:text-ink transition-colors">Trades</a></li>
<li><a href="/backtest" className="hover:text-ink transition-colors">Backtest</a></li>
<li>
<a href={GITHUB_URL} target="_blank" rel="noopener noreferrer" className="hover:text-ink transition-colors">
GitHub
</a>
</li>
</ul>
</div>
<div>
+3 -2
View File
@@ -11,8 +11,9 @@ export default function DashboardPage() {
ARES Trading Bot
</h1>
<p className="text-[18px] text-ink-md leading-relaxed max-w-2xl mb-8">
M5 Momentum FVG scalper built in Rust. Targets Fair Value Gaps on XAUUSDm
with EMA-20 trend filter, automatic position sizing, and Telegram alerts.
Open-source algorithmic trading bot built in Rust. Momentum FVG scalper
with EMA trend filter, configurable session window, automatic position
sizing, and Telegram alerts runs on any MT5 symbol.
</p>
<div className="flex gap-3 flex-wrap">
<Link href="/trades" className="btn-primary">View Trades</Link>
+2 -2
View File
@@ -6,7 +6,7 @@ import TradesRefresher from "@/components/TradesRefresher";
export const metadata: Metadata = {
title: "Trade History",
description: "Live closed trades and equity curve for ARES — M5 Momentum FVG scalper on XAUUSDm.",
description: "Live closed trades and equity curve for ARES — M5 Momentum FVG scalper on XAUUSDm and BTCUSDm.",
};
export const dynamic = "force-dynamic";
@@ -20,7 +20,7 @@ export default async function TradesPage() {
try {
[account, deals] = await Promise.all([
getAccount(),
getAllDeals("2026-06-10T00:00:00"),
getAllDeals(),
]);
} catch { error = true; }
+26 -7
View File
@@ -2,6 +2,7 @@
import Link from "next/link";
import { usePathname } from "next/navigation";
import clsx from "clsx";
import { GITHUB_URL } from "@/lib/github";
const links = [
{ href: "/", label: "Dashboard" },
@@ -9,10 +10,10 @@ const links = [
{ href: "/backtest", label: "Backtest" },
];
export default function Nav() {
export default function Nav({ version }: { version?: string | null }) {
const path = usePathname();
return (
<nav className="sticky top-0 z-50" style={{ backgroundColor: 'var(--c-canvas)', borderBottom: '1px solid var(--c-hl)' }}>
<nav className="sticky top-0 z-50" style={{ backgroundColor: "var(--c-canvas)", borderBottom: "1px solid var(--c-hl)" }}>
<div className="max-w-5xl mx-auto px-4 sm:px-6 h-14 flex items-center gap-6">
{/* logo + wordmark */}
<Link href="/" className="flex items-center gap-2 group">
@@ -21,9 +22,14 @@ export default function Nav() {
<path d="M8 19 L13 8 L18 19" stroke="white" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"/>
<path d="M10.2 15.5 H15.8" stroke="white" strokeWidth="1.8" strokeLinecap="round"/>
</svg>
<span className="font-mono text-sm font-semibold tracking-widest text-ink group-hover:text-accent transition-colors" style={{ letterSpacing: '0.18em' }}>
<span className="font-mono text-sm font-semibold tracking-widest text-ink group-hover:text-accent transition-colors" style={{ letterSpacing: "0.18em" }}>
ARES
</span>
{version && (
<span className="hidden sm:inline text-[10px] font-mono text-ink-ter bg-s2 border border-hl px-1.5 py-0.5 rounded">
{version}
</span>
)}
</Link>
{/* nav links */}
@@ -44,10 +50,23 @@ export default function Nav() {
))}
</div>
{/* live indicator */}
<div className="ml-auto flex items-center gap-2">
<span className="w-1.5 h-1.5 rounded-full bg-bull pulse-dot" />
<span className="text-xs text-ink-sub font-medium tracking-eyebrow uppercase">Live</span>
{/* right side: github + live */}
<div className="ml-auto flex items-center gap-4">
<a
href={GITHUB_URL}
target="_blank"
rel="noopener noreferrer"
className="text-ink-ter hover:text-ink transition-colors"
aria-label="GitHub"
>
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z"/>
</svg>
</a>
<div className="flex items-center gap-2">
<span className="w-1.5 h-1.5 rounded-full bg-bull pulse-dot" />
<span className="text-xs text-ink-sub font-medium tracking-eyebrow uppercase">Live</span>
</div>
</div>
</div>
</nav>
+21
View File
@@ -0,0 +1,21 @@
const REPO = "RomySaputraSihananda/ares";
export async function getLatestVersion(): Promise<string | null> {
try {
const res = await fetch(
`https://api.github.com/repos/${REPO}/releases/latest`,
{
headers: { Accept: "application/vnd.github+json" },
next: { revalidate: 3600 },
}
);
if (!res.ok) return null;
const data = await res.json();
return (data.tag_name as string) ?? null;
} catch {
return null;
}
}
export const GITHUB_URL = `https://github.com/${REPO}`;
export const GITHUB_REPO = REPO;
+5 -2
View File
@@ -107,8 +107,11 @@ export async function getDeals(dateFrom: string, dateTo: string, symbol?: string
// Fetch all deals from startDate to now by querying one day at a time,
// working around the MT5 bridge per-request deal limit.
export async function getAllDeals(startDate: string): Promise<Deal[]> {
const start = new Date(startDate);
// startDate defaults to 90 days ago if omitted.
export async function getAllDeals(startDate?: string): Promise<Deal[]> {
const start = startDate
? new Date(startDate)
: (() => { const d = new Date(); d.setUTCDate(d.getUTCDate() - 90); return d; })();
const now = new Date();
const days: Array<[string, string]> = [];