feat(web): add Next.js portfolio with live MT5 dashboard

- Linear design system (dark canvas, lavender accent)
- Live account stats, open positions, pending orders (30s polling)
- Trade history with equity curve chart
- Backtest results table
- Proxy API routes — MT5_BASE_URL server-side only, never exposed to browser
- Deploy on Vercel: set MT5_BASE_URL env var

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
romysaputrasihananda
2026-06-11 14:51:55 +07:00
co-authored by Claude Sonnet 4.6
parent 4fdc3da4ed
commit 9716fc0955
26 changed files with 3854 additions and 0 deletions
+13
View File
@@ -0,0 +1,13 @@
import { NextResponse } from "next/server";
import { getAccount } from "@/lib/mt5";
export const dynamic = "force-dynamic";
export async function GET() {
try {
const data = await getAccount();
return NextResponse.json(data);
} catch (e) {
return NextResponse.json({ error: String(e) }, { status: 502 });
}
}
+18
View File
@@ -0,0 +1,18 @@
import { NextResponse } from "next/server";
import { NextRequest } from "next/server";
import { getDeals } from "@/lib/mt5";
export const dynamic = "force-dynamic";
export async function GET(req: NextRequest) {
const { searchParams } = req.nextUrl;
const dateFrom = searchParams.get("date_from") ?? "2025-01-01T00:00:00";
const dateTo = searchParams.get("date_to") ?? new Date().toISOString().slice(0, 19);
const symbol = searchParams.get("symbol") ?? undefined;
try {
const data = await getDeals(dateFrom, dateTo, symbol);
return NextResponse.json(data);
} catch (e) {
return NextResponse.json({ error: String(e) }, { status: 502 });
}
}
+15
View File
@@ -0,0 +1,15 @@
import { NextResponse } from "next/server";
import { getOrders } from "@/lib/mt5";
import { NextRequest } from "next/server";
export const dynamic = "force-dynamic";
export async function GET(req: NextRequest) {
const symbol = req.nextUrl.searchParams.get("symbol") ?? undefined;
try {
const data = await getOrders(symbol);
return NextResponse.json(data);
} catch (e) {
return NextResponse.json({ error: String(e) }, { status: 502 });
}
}
+13
View File
@@ -0,0 +1,13 @@
import { NextResponse } from "next/server";
import { getPositions } from "@/lib/mt5";
export const dynamic = "force-dynamic";
export async function GET() {
try {
const data = await getPositions();
return NextResponse.json(data);
} catch (e) {
return NextResponse.json({ error: String(e) }, { status: 502 });
}
}