This commit is contained in:
tinh
2026-03-22 15:32:16 +07:00
parent 2b9ac70ae4
commit 3ffbef67ac
4 changed files with 264 additions and 2 deletions
+3
View File
@@ -0,0 +1,3 @@
{
"python-envs.defaultEnvManager": "ms-python.python:system"
}
@@ -0,0 +1,258 @@
export default function ForexOpenClawDashboard() {
const overview = {
botStatus: "Running",
mt5: "Connected",
provider: "Anthropic",
openPositions: 4,
balance: 10342.18,
equity: 10196.42,
drawdownPct: 1.41,
latestSignal: "BUY EURUSDm",
lastUpdate: "2026-03-21 10:25",
};
const signals = [
{
symbol: "EURUSDm",
bias: "BUY",
score: 82,
regime: "TREND",
entry: "1.0842 - 1.0848",
rsiH1: 58,
rsiM15: 34,
adx: 27,
atr: 0.00086,
note: "H1 bullish, M15 pullback",
},
{
symbol: "USDJPYm",
bias: "SELL",
score: 74,
regime: "TREND",
entry: "148.20 - 148.35",
rsiH1: 43,
rsiM15: 67,
adx: 29,
atr: 0.118,
note: "Sell pullback in downtrend",
},
{
symbol: "XAUUSDm",
bias: "NO TRADE",
score: 49,
regime: "RANGE",
entry: "Wait",
rsiH1: 51,
rsiM15: 53,
adx: 18,
atr: 7.2,
note: "Range, no edge",
},
];
const positions = [
{ ticket: 120381, symbol: "EURUSDm", side: "BUY", lot: 0.1, pnl: 24.8, opened: "09:12" },
{ ticket: 120382, symbol: "EURUSDm", side: "BUY", lot: 0.2, pnl: -12.4, opened: "09:35" },
{ ticket: 120410, symbol: "USDJPYm", side: "SELL", lot: 0.1, pnl: 31.2, opened: "10:01" },
{ ticket: 120425, symbol: "GBPUSDm", side: "BUY", lot: 0.1, pnl: -5.6, opened: "10:14" },
];
const riskChecks = [
{ name: "Daily Loss Limit", value: "Pass", detail: "-1.41% / max 3.00%" },
{ name: "Symbol Exposure", value: "Pass", detail: "EURUSDm 0.3 / max 0.5 lot" },
{ name: "Basket Layers", value: "Warn", detail: "EURUSDm 2 / max 3 layers" },
{ name: "Spread Filter", value: "Pass", detail: "All symbols within threshold" },
{ name: "Kill Switch", value: "Off", detail: "No emergency lock" },
];
const logs = [
"10:25 Analyze EURUSDm -> BUY score 82",
"10:24 Risk check EURUSDm BUY 0.1 -> approved",
"10:24 Execute EURUSDm BUY 0.1 -> queued",
"10:12 Analyze XAUUSDm -> NO TRADE score 49",
"10:07 MT5 heartbeat OK",
];
const StatCard = ({ title, value, sub }) => (
<div className="rounded-2xl border bg-white p-4 shadow-sm">
<div className="text-xs text-slate-500">{title}</div>
<div className="mt-2 text-2xl font-semibold text-slate-900">{value}</div>
{sub ? <div className="mt-1 text-sm text-slate-500">{sub}</div> : null}
</div>
);
const Pill = ({ children, tone = "slate" }) => {
const tones = {
green: "bg-green-100 text-green-700",
red: "bg-red-100 text-red-700",
amber: "bg-amber-100 text-amber-700",
blue: "bg-blue-100 text-blue-700",
slate: "bg-slate-100 text-slate-700",
};
return (
<span className={`inline-flex rounded-full px-2.5 py-1 text-xs font-medium ${tones[tone]}`}>
{children}
</span>
);
};
const toneForBias = (bias) => {
if (bias === "BUY") return "green";
if (bias === "SELL") return "red";
return "amber";
};
const toneForRisk = (value) => {
if (value === "Pass") return "green";
if (value === "Warn") return "amber";
if (value === "Off") return "slate";
return "red";
};
const pnlTone = (pnl) => (pnl >= 0 ? "text-green-600" : "text-red-600");
return (
<div className="min-h-screen bg-slate-50 text-slate-900">
<div className="mx-auto max-w-7xl p-6">
<div className="mb-6 flex flex-col gap-4 rounded-3xl border bg-white p-6 shadow-sm md:flex-row md:items-center md:justify-between">
<div>
<div className="text-sm text-slate-500">OpenClaw + Forex Copilot</div>
<h1 className="text-3xl font-bold">Dashboard v1</h1>
<div className="mt-2 text-sm text-slate-500">Giám sát bot, tín hiệu, risk lệnh mở trong một màn hình.</div>
</div>
<div className="flex flex-wrap gap-2">
<Pill tone="green">Bot {overview.botStatus}</Pill>
<Pill tone="blue">MT5 {overview.mt5}</Pill>
<Pill tone="slate">LLM {overview.provider}</Pill>
<Pill tone="amber">Updated {overview.lastUpdate}</Pill>
</div>
</div>
<div className="grid gap-4 md:grid-cols-2 xl:grid-cols-4">
<StatCard title="Balance" value={`$${overview.balance.toFixed(2)}`} sub="Account balance" />
<StatCard title="Equity" value={`$${overview.equity.toFixed(2)}`} sub="Real-time equity" />
<StatCard title="Drawdown" value={`${overview.drawdownPct.toFixed(2)}%`} sub="Current floating DD" />
<StatCard title="Open Positions" value={overview.openPositions} sub={`Latest: ${overview.latestSignal}`} />
</div>
<div className="mt-6 grid gap-6 xl:grid-cols-3">
<div className="xl:col-span-2 rounded-3xl border bg-white p-5 shadow-sm">
<div className="mb-4 flex items-center justify-between">
<h2 className="text-lg font-semibold">Latest Signals</h2>
<div className="text-sm text-slate-500">Rule-based core, OpenClaw orchestration</div>
</div>
<div className="overflow-x-auto">
<table className="min-w-full text-sm">
<thead>
<tr className="border-b text-left text-slate-500">
<th className="py-3 pr-4">Symbol</th>
<th className="py-3 pr-4">Bias</th>
<th className="py-3 pr-4">Score</th>
<th className="py-3 pr-4">Regime</th>
<th className="py-3 pr-4">Entry</th>
<th className="py-3 pr-4">RSI H1/M15</th>
<th className="py-3 pr-4">ADX</th>
<th className="py-3 pr-4">ATR</th>
</tr>
</thead>
<tbody>
{signals.map((row) => (
<tr key={row.symbol} className="border-b last:border-0">
<td className="py-3 pr-4 font-medium">{row.symbol}</td>
<td className="py-3 pr-4"><Pill tone={toneForBias(row.bias)}>{row.bias}</Pill></td>
<td className="py-3 pr-4">{row.score}</td>
<td className="py-3 pr-4">{row.regime}</td>
<td className="py-3 pr-4">{row.entry}</td>
<td className="py-3 pr-4">{row.rsiH1} / {row.rsiM15}</td>
<td className="py-3 pr-4">{row.adx}</td>
<td className="py-3 pr-4">{row.atr}</td>
</tr>
))}
</tbody>
</table>
</div>
<div className="mt-4 grid gap-3 md:grid-cols-3">
{signals.map((row) => (
<div key={`${row.symbol}-note`} className="rounded-2xl bg-slate-50 p-3 text-sm">
<div className="font-medium">{row.symbol}</div>
<div className="mt-1 text-slate-600">{row.note}</div>
</div>
))}
</div>
</div>
<div className="rounded-3xl border bg-white p-5 shadow-sm">
<div className="mb-4 flex items-center justify-between">
<h2 className="text-lg font-semibold">Risk Guard</h2>
<Pill tone="blue">Live</Pill>
</div>
<div className="space-y-3">
{riskChecks.map((item) => (
<div key={item.name} className="rounded-2xl border p-3">
<div className="flex items-center justify-between gap-3">
<div className="font-medium">{item.name}</div>
<Pill tone={toneForRisk(item.value)}>{item.value}</Pill>
</div>
<div className="mt-1 text-sm text-slate-500">{item.detail}</div>
</div>
))}
</div>
</div>
</div>
<div className="mt-6 grid gap-6 xl:grid-cols-2">
<div className="rounded-3xl border bg-white p-5 shadow-sm">
<div className="mb-4 flex items-center justify-between">
<h2 className="text-lg font-semibold">Open Positions</h2>
<Pill tone="slate">{positions.length} tickets</Pill>
</div>
<div className="overflow-x-auto">
<table className="min-w-full text-sm">
<thead>
<tr className="border-b text-left text-slate-500">
<th className="py-3 pr-4">Ticket</th>
<th className="py-3 pr-4">Symbol</th>
<th className="py-3 pr-4">Side</th>
<th className="py-3 pr-4">Lot</th>
<th className="py-3 pr-4">PnL</th>
<th className="py-3 pr-4">Opened</th>
</tr>
</thead>
<tbody>
{positions.map((p) => (
<tr key={p.ticket} className="border-b last:border-0">
<td className="py-3 pr-4 font-medium">#{p.ticket}</td>
<td className="py-3 pr-4">{p.symbol}</td>
<td className="py-3 pr-4"><Pill tone={p.side === "BUY" ? "green" : "red"}>{p.side}</Pill></td>
<td className="py-3 pr-4">{p.lot}</td>
<td className={`py-3 pr-4 font-medium ${pnlTone(p.pnl)}`}>{p.pnl >= 0 ? `+$${p.pnl}` : `-$${Math.abs(p.pnl)}`}</td>
<td className="py-3 pr-4">{p.opened}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
<div className="rounded-3xl border bg-white p-5 shadow-sm">
<div className="mb-4 flex items-center justify-between">
<h2 className="text-lg font-semibold">Recent Logs</h2>
<Pill tone="amber">Heartbeat OK</Pill>
</div>
<div className="space-y-2">
{logs.map((line, idx) => (
<div key={idx} className="rounded-2xl bg-slate-50 px-3 py-2 text-sm text-slate-700">
{line}
</div>
))}
</div>
<div className="mt-4 rounded-2xl border border-dashed p-4 text-sm text-slate-500">
Nút v1 cần thêm sau: Kill Switch, Refresh Signal, Pause New Entries, Export Report.
</div>
</div>
</div>
</div>
</div>
);
}
@@ -1,9 +1,10 @@
from __future__ import annotations
import sys
from pathlib import Path
import pandas as pd
from src.strategies.smart_money_adx_atr_rsi_strategy import SmartMoneyADXATRRSIStrategy
sys.path.append(str(Path(__file__).parent.parent.parent))
from strategies.smart_money_adx_atr_rsi_strategy import SmartMoneyADXATRRSIStrategy
def load_processed(symbol: str, timeframe: str, suffix: str = "clean") -> pd.DataFrame: