diff --git a/web/app/trades/page.tsx b/web/app/trades/page.tsx
index b0dd83f..78b6ea2 100644
--- a/web/app/trades/page.tsx
+++ b/web/app/trades/page.tsx
@@ -19,11 +19,8 @@ export default async function TradesPage() {
]);
} catch { error = true; }
- const aresDeals = deals.filter(d => d.magic === MAGIC && d.type !== 2);
- const startBalance = deals.filter(d => d.type === 2).reduce((s, d) => s + d.profit, 600);
- const equityData = buildEquityCurve(deals, startBalance);
-
- const closed = aresDeals.filter(d => d.entry === 1);
+ const aresDeals = deals.filter(d => d.magic === MAGIC && d.type !== 2);
+ const closed = aresDeals.filter(d => d.entry === 1);
const wins = closed.filter(d => d.profit > 0);
const losses = closed.filter(d => d.profit < 0);
const netPnl = closed.reduce((s, d) => s + d.profit + d.swap + d.commission, 0);
@@ -76,7 +73,7 @@ export default async function TradesPage() {
{/* Equity curve */}
{/* Trade table */}
@@ -124,19 +121,3 @@ export default async function TradesPage() {
);
}
-function buildEquityCurve(deals: Deal[], start: number) {
- const sorted = deals
- .filter(d => d.magic === MAGIC && d.entry === 1)
- .sort((a, b) => +new Date(a.time) - +new Date(b.time));
-
- let eq = start;
- const pts = [{ date: "Start", equity: eq }];
- for (const d of sorted) {
- eq += d.profit + d.swap + d.commission;
- pts.push({
- date: new Date(d.time).toLocaleDateString("en-US", { month: "short", day: "numeric" }),
- equity: parseFloat(eq.toFixed(2)),
- });
- }
- return pts;
-}
diff --git a/web/components/EquityChart.tsx b/web/components/EquityChart.tsx
index 04a8798..f15f2ac 100644
--- a/web/components/EquityChart.tsx
+++ b/web/components/EquityChart.tsx
@@ -1,77 +1,152 @@
"use client";
+import { useState } from "react";
import {
AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip,
- ResponsiveContainer,
+ ReferenceLine, ResponsiveContainer,
} from "recharts";
import { formatCurrency } from "@/lib/format";
-interface DataPoint { date: string; equity: number }
+interface Deal {
+ time: string;
+ profit: number;
+ swap: number;
+ commission: number;
+}
-export default function EquityChart({ data, currency = "USD" }: {
- data: DataPoint[];
+type Period = "1D" | "7D" | "30D" | "ALL";
+const PERIODS: Period[] = ["1D", "7D", "30D", "ALL"];
+
+function periodStart(p: Period): Date {
+ const now = new Date();
+ if (p === "1D") return new Date(now.getTime() - 86_400_000);
+ if (p === "7D") return new Date(now.getTime() - 7 * 86_400_000);
+ if (p === "30D") return new Date(now.getTime() - 30 * 86_400_000);
+ return new Date("2000-01-01");
+}
+
+function fmtTick(iso: string, p: Period) {
+ const d = new Date(iso);
+ if (p === "1D") return d.toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit", hour12: false });
+ return d.toLocaleDateString("en-US", { month: "short", day: "numeric" });
+}
+
+export default function EquityChart({ deals, currency = "USD" }: {
+ deals: Deal[];
currency?: string;
}) {
- if (data.length < 2) {
+ const [period, setPeriod] = useState("ALL");
+
+ const since = periodStart(period);
+ const sorted = deals
+ .filter(d => new Date(d.time) >= since)
+ .sort((a, b) => +new Date(a.time) - +new Date(b.time));
+
+ let cum = 0;
+ const pts = [
+ { label: "Start", pnl: 0 },
+ ...sorted.map(d => {
+ cum += d.profit + d.swap + d.commission;
+ return { label: fmtTick(d.time, period), pnl: parseFloat(cum.toFixed(2)) };
+ }),
+ ];
+
+ const netPnl = pts[pts.length - 1]?.pnl ?? 0;
+ const isPos = netPnl >= 0;
+
+ const minPnl = Math.min(0, ...pts.map(d => d.pnl));
+ const maxPnl = Math.max(0, ...pts.map(d => d.pnl));
+ const range = maxPnl - minPnl || 1;
+ const zeroPct = `${((maxPnl / range) * 100).toFixed(1)}%`;
+
+ if (pts.length < 2) {
return (
-
- Not enough data to render chart.
+
+ No trades in this period.
);
}
- const first = data[0].equity;
- const last = data[data.length - 1].equity;
- const isPos = last >= first;
- const color = isPos ? "#27a644" : "#e5484d";
- const min = Math.min(...data.map(d => d.equity));
- const max = Math.max(...data.map(d => d.equity));
-
return (
-
-
-
-
-
-
-
-
-
-
- `$${v.toFixed(0)}`}
- width={60}
- />
- [formatCurrency(v, currency), "Equity"]}
- contentStyle={{
- background: "#141516",
- border: "1px solid #34343a",
- borderRadius: "8px",
- fontSize: "12px",
- fontFamily: "'JetBrains Mono', monospace",
- color: "#f7f8f8",
- }}
- labelStyle={{ color: "#8a8f98", marginBottom: 4 }}
- cursor={{ stroke: "#34343a", strokeWidth: 1 }}
- />
-
-
-
+
+ {/* Period tabs + net P&L */}
+
+
+ {PERIODS.map(p => (
+
+ ))}
+
+
+ {isPos ? "+" : ""}{formatCurrency(netPnl, currency)}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `$${v.toFixed(0)}`}
+ width={60}
+ />
+ [`${v >= 0 ? "+" : ""}${formatCurrency(v, currency)}`, "P&L"]}
+ contentStyle={{
+ background: "#141516",
+ border: "1px solid #34343a",
+ borderRadius: "8px",
+ fontSize: "12px",
+ fontFamily: "'JetBrains Mono', monospace",
+ color: "#f7f8f8",
+ }}
+ labelStyle={{ color: "#8a8f98", marginBottom: 4 }}
+ cursor={{ stroke: "#34343a", strokeWidth: 1 }}
+ />
+
+
+
+
);
}