Spaces:
Running
Since I can’t see the exact /dashboard/scalp-trading UI because it’s behind login, I’ll recreate the same vibe:
Browse filesClean table/grid of pairs
Columns like: Pair • TF • Bias • Entry • SL • TP1/TP2 • Confidence • Updated • Button
Timeframe tabs (1m / 5m / 15m / 1h)
Filters for Forex / Crypto
One-click “Analyze” / “Refresh” for each pair
Below is copy-pasteable code you can drop into your project to get that Tradvio-style scalp dashboard.
1️⃣ Backend: Add a “Scalp Trading” Endpoint
In backend/main.py, add this to your router (or similar):
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
from typing import List, Literal
import asyncio
from backend.signal_engine import generate_signal_from_market
router = APIRouter()
class ScalpSignalRequest(BaseModel):
timeframe: str = "5m"
asset_type: Literal["crypto", "forex", "mixed"] = "forex"
symbols: List[str] | None = None # optional custom list
class ScalpSignal(BaseModel):
symbol: str
asset_type: str
timeframe: str
direction: str
entry_zone: List[float]
stop_loss: float
take_profit_levels: List[float]
confidence: int
time_horizon: str
explanation: str
updated_at: str
class ScalpSignalResponse(BaseModel):
signals: List[ScalpSignal]
DEFAULT_CRYPTO = ["BTCUSDT", "ETHUSDT", "SOLUSDT"]
DEFAULT_FOREX = ["EURUSD", "GBPUSD", "XAUUSD", "USDJPY", "NAS100"]
@router.post("/scalp/signals", response_model=ScalpSignalResponse)
async def get_scalp_signals(req: ScalpSignalRequest):
if req.symbols:
symbols = req.symbols
else:
if req.asset_type == "crypto":
symbols = DEFAULT_CRYPTO
elif req.asset_type == "forex":
symbols = DEFAULT_FOREX
else:
symbols = DEFAULT_CRYPTO + DEFAULT_FOREX
tasks = []
for symbol in symbols:
tasks.append(
generate_signal_from_market(
symbol=symbol,
timeframe=req.timeframe,
asset_type="crypto" if symbol.endswith("USDT") else "forex",
)
)
try:
results = await asyncio.gather(*tasks)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
scalp_signals: List[ScalpSignal] = []
from datetime import datetime, timezone
now_iso = datetime.now(timezone.utc).isoformat()
for symbol, sig in zip(symbols, results):
scalp_signals.append(
ScalpSignal(
symbol=symbol,
asset_type="crypto" if symbol.endswith("USDT") else "forex",
timeframe=req.timeframe,
direction=sig["direction"],
entry_zone=sig["entry_zone"],
stop_loss=sig["stop_loss"],
take_profit_levels=sig["take_profit_levels"],
confidence=sig["confidence"],
time_horizon=sig["time_horizon"],
explanation=sig["explanation"],
updated_at=now_iso,
)
)
return ScalpSignalResponse(signals=scalp_signals)
This gives you a single API like Tradvio’s scalp page:
POST /api/scalp/signals
{
"timeframe": "5m",
"asset_type": "forex"
}
→ returns a list of live signals for multiple pairs in one shot.
2️⃣ Frontend: Tradvio-Style “Scalp Trading” Page
2.1 Add a New Page: frontend/src/pages/ScalpDashboard.tsx
import { useEffect, useState } from "react";
import { getScalpSignals } from "../lib/api";
type AssetType = "crypto" | "forex" | "mixed";
interface ScalpSignal {
symbol: string;
asset_type: string;
timeframe: string;
direction: "long" | "short" | "neutral" | string;
entry_zone: number[];
stop_loss: number;
take_profit_levels: number[];
confidence: number;
time_horizon: string;
explanation: string;
updated_at: string;
}
const TIMEFRAMES = ["1m", "5m", "15m", "1h"];
export default function ScalpDashboard() {
const [timeframe, setTimeframe] = useState<string>("5m");
const [assetType, setAssetType] = useState<AssetType>("forex");
const [signals, setSignals] = useState<ScalpSignal[]>([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [search, setSearch] = useState("");
const loadSignals = async () => {
try {
setLoading(true);
setError(null);
const data = await getScalpSignals({ timeframe, asset_type: assetType });
setSignals(data.signals || []);
} catch (e: any) {
console.error(e);
setError("Failed to load signals");
} finally {
setLoading(false);
}
};
useEffect(() => {
loadSignals();
}, [timeframe, assetType]);
const filteredSignals = signals.filter((s) =>
s.symbol.toLowerCase().includes(search.toLowerCase())
);
const directionBadge = (dir: string) => {
let base =
"px-2 py-1 text-xs rounded-full font-semibold uppercase tracking-wide ";
if (dir.toLowerCase() === "long") {
return base + "bg-emerald-500/20 text-emerald-300 border border-emerald-500/40";
}
if (dir.toLowerCase() === "short") {
return base + "bg-rose-500/20 text-rose-300 border border-rose-500/40";
}
return base + "bg-slate-500/20 text-slate-200 border border-slate-500/40";
};
const confidenceBarWidth = (conf: number) =>
`${Math.min(Math.max(conf, 0), 100)}%`;
return (
<div className="flex flex-col gap-4 h-full">
{/* Header */}
<div className="flex flex-col md:flex-row md:items-center md:justify-between gap-3">
<div>
<h1 className="text-xl md:text-2xl font-semibold text-slate-50">
Scalp Trading Dashboard
</h1>
<p className="text-slate-400 text-sm">
Live multi-pair signals for crypto & forex · powered by your AI
</p>
</div>
<div className="flex flex-wrap gap-2 items-center">
{/* Timeframe tabs */}
<div className="flex bg-slate-800/80 rounded-xl p-1">
{TIMEFRAMES.map((tf) => (
<button
key={tf}
onClick={() => setTimeframe(tf)}
className={`px-3 py-1 text-xs md:text-sm rounded-lg transition ${
timeframe === tf
? "bg-slate-100 text-slate-900 font-semibold"
: "text-slate-300 hover:bg-slate-700/80"
}`}
>
{tf}
</button>
))}
</div>
{/* Asset type */}
<select
className="bg-slate-800 text-slate-100 text-sm rounded-lg px-3 py-1.5 border border-slate-700"
value={assetType}
onChange={(e) => setAssetType(e.target.value as AssetType)}
>
<option value="forex">Forex</option>
<option value="crypto">Crypto</option>
<option value="mixed">Mixed</option>
</select>
{/* Search */}
<input
type="text"
placeholder="Search pair (e.g. EURUSD, BTCUSDT)"
className="bg-slate-800 text-slate-100 text-sm rounded-lg px-3 py-1.5 border border-slate-700 placeholder:text-slate-500"
value={search}
onChange={(e) => setSearch(e.target.value)}
/>
<button
onClick={loadSignals}
className="px-4 py-1.5 rounded-lg text-sm bg-indigo-500 hover:bg-indigo-400 text-white font-semibold shadow-md shadow-indigo-500/30"
>
Refresh
</button>
</div>
</div>
{/* Table */}
<div className="flex-1 overflow-auto rounded-2xl bg-slate-900/80 border border-slate-800">
{loading && (
<div className="p-6 text-center text-slate-300 text-sm">
Loading signals…
</div>
)}
{error && !loading && (
<div className="p-4 text-center text-rose-400 text-sm">{error}</div>
)}
{!loading && !error && filteredSignals.length === 0 && (
<div className="p-6 text-center text-slate-400 text-sm">
No signals found for this filter.
</div>
)}
{!loading && !error && filteredSignals.length > 0 && (
<table className="w-full text-sm">
<thead className="bg-slate-900/90 sticky top-0 z-10">
<tr className="text-slate-400 text-xs uppercase tracking-wide">
<th className="px-4 py-3 text-left">Pair</th>
<th className="px-4 py-3 text-left">Type</th>
<th className="px-4 py-3 text-left">TF</th>
<th className="px-4 py-3 text-left">Bias</th>
<th className="px-4 py-3 text-left">Entry Zone</th>
<th className="px-4 py-3 text-left">SL</th>
<th className="px-4 py-3 text-left">TP1 / TP2 / TP3</th>
<th className="px-4 py-3 text-left">Confidence</th>
<th className="px-4 py-3 text-left">Updated</th>
<th className="px-4 py-3 text-right">Action</th>
</tr>
</thead>
<tbody>
{filteredSignals.map((s) => (
<tr
key={`${s.symbol}-${s.timeframe}`}
className="border-t border-slate-800 hover:bg-slate-800/60"
>
<td className="px-4 py-3 text-slate-100 font-medium">
{s.symbol}
</td>
<td className="px-4 py-3 text-slate-300 text-xs">
{s.asset_type.toUpperCase()}
</td>
<td className="px-4 py-3 text-slate-300 text-xs">
{s.timeframe}
</td>
<td className="px-4 py-3">
<span className={directionBadge(s.direction)}>
{s.direction.toUpperCase()}
</span>
</td>
<td className="px-4 py-3 text-slate-200 text-xs">
{s.entry_zone && s.entry_zone.length === 2
? `${s.entry_zone[0].toFixed(5)} – ${s.entry_zone[1].toFixed(5)}`
: "-"}
- backend/main.py +43 -2
- scalp.html +175 -0
- style.css +25 -0
|
@@ -1,5 +1,5 @@
|
|
| 1 |
```python
|
| 2 |
-
from fastapi import FastAPI, UploadFile, File, HTTPException, Depends
|
| 3 |
from fastapi.middleware.cors import CORSMiddleware
|
| 4 |
from fastapi.security import APIKeyHeader
|
| 5 |
from typing import Optional
|
|
@@ -10,9 +10,50 @@ from .timeseries_analysis import fetch_ohlcv, compute_technicals
|
|
| 10 |
from .sentiment_analysis import get_crypto_sentiment
|
| 11 |
from .models_registry import model_registry
|
| 12 |
from pydantic import BaseModel
|
| 13 |
-
|
| 14 |
app = FastAPI(title="CryptoSignal Sleuth Pro API")
|
| 15 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
# CORS settings
|
| 17 |
app.add_middleware(
|
| 18 |
CORSMiddleware,
|
|
|
|
| 1 |
```python
|
| 2 |
+
from fastapi import FastAPI, UploadFile, File, HTTPException, Depends, APIRouter
|
| 3 |
from fastapi.middleware.cors import CORSMiddleware
|
| 4 |
from fastapi.security import APIKeyHeader
|
| 5 |
from typing import Optional
|
|
|
|
| 10 |
from .sentiment_analysis import get_crypto_sentiment
|
| 11 |
from .models_registry import model_registry
|
| 12 |
from pydantic import BaseModel
|
|
|
|
| 13 |
app = FastAPI(title="CryptoSignal Sleuth Pro API")
|
| 14 |
|
| 15 |
+
# Create a router for scalp signals
|
| 16 |
+
scalp_router = APIRouter()
|
| 17 |
+
|
| 18 |
+
DEFAULT_CRYPTO = ["BTCUSDT", "ETHUSDT", "SOLUSDT"]
|
| 19 |
+
DEFAULT_FOREX = ["EURUSD", "GBPUSD", "XAUUSD", "USDJPY", "NAS100"]
|
| 20 |
+
|
| 21 |
+
@scalp_router.post("/scalp/signals")
|
| 22 |
+
async def get_scalp_signals(
|
| 23 |
+
timeframe: str = "5m",
|
| 24 |
+
asset_type: str = "forex"
|
| 25 |
+
):
|
| 26 |
+
if asset_type == "crypto":
|
| 27 |
+
symbols = DEFAULT_CRYPTO
|
| 28 |
+
elif asset_type == "forex":
|
| 29 |
+
symbols = DEFAULT_FOREX
|
| 30 |
+
else: # mixed
|
| 31 |
+
symbols = DEFAULT_CRYPTO + DEFAULT_FOREX
|
| 32 |
+
|
| 33 |
+
from datetime import datetime, timezone
|
| 34 |
+
from .signal_engine import generate_signal_from_market
|
| 35 |
+
|
| 36 |
+
try:
|
| 37 |
+
signals = []
|
| 38 |
+
for symbol in symbols:
|
| 39 |
+
signal = await generate_signal_from_market(
|
| 40 |
+
symbol=symbol,
|
| 41 |
+
timeframe=timeframe,
|
| 42 |
+
asset_type="crypto" if symbol.endswith("USDT") else "forex"
|
| 43 |
+
)
|
| 44 |
+
signals.append({
|
| 45 |
+
**signal,
|
| 46 |
+
"symbol": symbol,
|
| 47 |
+
"asset_type": "crypto" if symbol.endswith("USDT") else "forex",
|
| 48 |
+
"timeframe": timeframe,
|
| 49 |
+
"updated_at": datetime.now(timezone.utc).isoformat()
|
| 50 |
+
})
|
| 51 |
+
|
| 52 |
+
return {"signals": signals}
|
| 53 |
+
except Exception as e:
|
| 54 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 55 |
+
|
| 56 |
+
app.include_router(scalp_router)
|
| 57 |
# CORS settings
|
| 58 |
app.add_middleware(
|
| 59 |
CORSMiddleware,
|
|
@@ -0,0 +1,175 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>Scalp Trading Dashboard | CryptoSignal Sleuth Pro</title>
|
| 7 |
+
<link rel="stylesheet" href="style.css">
|
| 8 |
+
<script src="https://cdn.tailwindcss.com"></script>
|
| 9 |
+
<script src="https://unpkg.com/feather-icons"></script>
|
| 10 |
+
</head>
|
| 11 |
+
<body class="bg-gray-900 text-gray-100">
|
| 12 |
+
<custom-navbar></custom-navbar>
|
| 13 |
+
|
| 14 |
+
<div class="container mx-auto px-4 py-6">
|
| 15 |
+
<!-- Header -->
|
| 16 |
+
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-4 mb-6">
|
| 17 |
+
<div>
|
| 18 |
+
<h1 class="text-2xl font-bold">Scalp Trading Dashboard</h1>
|
| 19 |
+
<p class="text-gray-400">Live multi-pair signals for crypto & forex</p>
|
| 20 |
+
</div>
|
| 21 |
+
|
| 22 |
+
<div class="flex flex-wrap gap-2 items-center">
|
| 23 |
+
<!-- Timeframe tabs -->
|
| 24 |
+
<div class="flex bg-gray-800 rounded-lg p-1">
|
| 25 |
+
<button class="timeframe-tab active" data-timeframe="1m">1m</button>
|
| 26 |
+
<button class="timeframe-tab" data-timeframe="5m">5m</button>
|
| 27 |
+
<button class="timeframe-tab" data-timeframe="15m">15m</button>
|
| 28 |
+
<button class="timeframe-tab" data-timeframe="1h">1h</button>
|
| 29 |
+
</div>
|
| 30 |
+
|
| 31 |
+
<!-- Asset type -->
|
| 32 |
+
<select id="asset-type" class="bg-gray-800 text-sm rounded-lg px-3 py-1.5 border border-gray-700">
|
| 33 |
+
<option value="forex">Forex</option>
|
| 34 |
+
<option value="crypto">Crypto</option>
|
| 35 |
+
<option value="mixed">Mixed</option>
|
| 36 |
+
</select>
|
| 37 |
+
|
| 38 |
+
<!-- Search -->
|
| 39 |
+
<input type="text" id="pair-search" placeholder="Search pair"
|
| 40 |
+
class="bg-gray-800 text-sm rounded-lg px-3 py-1.5 border border-gray-700 w-40">
|
| 41 |
+
|
| 42 |
+
<button id="refresh-btn" class="px-4 py-1.5 rounded-lg text-sm bg-indigo-600 hover:bg-indigo-500">
|
| 43 |
+
<i data-feather="refresh-cw" class="w-4 h-4"></i> Refresh
|
| 44 |
+
</button>
|
| 45 |
+
</div>
|
| 46 |
+
</div>
|
| 47 |
+
|
| 48 |
+
<!-- Signals Table -->
|
| 49 |
+
<div class="rounded-xl overflow-hidden border border-gray-800">
|
| 50 |
+
<table class="w-full text-sm">
|
| 51 |
+
<thead class="bg-gray-800">
|
| 52 |
+
<tr class="text-gray-400 text-xs uppercase">
|
| 53 |
+
<th class="px-4 py-3 text-left">Pair</th>
|
| 54 |
+
<th class="px-4 py-3 text-left">Type</th>
|
| 55 |
+
<th class="px-4 py-3 text-left">TF</th>
|
| 56 |
+
<th class="px-4 py-3 text-left">Bias</th>
|
| 57 |
+
<th class="px-4 py-3 text-left">Entry Zone</th>
|
| 58 |
+
<th class="px-4 py-3 text-left">SL</th>
|
| 59 |
+
<th class="px-4 py-3 text-left">TP1 / TP2</th>
|
| 60 |
+
<th class="px-4 py-3 text-left">Confidence</th>
|
| 61 |
+
<th class="px-4 py-3 text-left">Updated</th>
|
| 62 |
+
<th class="px-4 py-3 text-right">Action</th>
|
| 63 |
+
</tr>
|
| 64 |
+
</thead>
|
| 65 |
+
<tbody id="signals-body" class="divide-y divide-gray-800">
|
| 66 |
+
<!-- Signals will be loaded here -->
|
| 67 |
+
</tbody>
|
| 68 |
+
</table>
|
| 69 |
+
</div>
|
| 70 |
+
</div>
|
| 71 |
+
|
| 72 |
+
<script src="components/navbar.js"></script>
|
| 73 |
+
<script src="script.js"></script>
|
| 74 |
+
<script>
|
| 75 |
+
feather.replace();
|
| 76 |
+
|
| 77 |
+
// Timeframe tabs
|
| 78 |
+
document.querySelectorAll('.timeframe-tab').forEach(tab => {
|
| 79 |
+
tab.addEventListener('click', () => {
|
| 80 |
+
document.querySelectorAll('.timeframe-tab').forEach(t => t.classList.remove('active'));
|
| 81 |
+
tab.classList.add('active');
|
| 82 |
+
loadSignals();
|
| 83 |
+
});
|
| 84 |
+
});
|
| 85 |
+
|
| 86 |
+
// Asset type and search
|
| 87 |
+
document.getElementById('asset-type').addEventListener('change', loadSignals);
|
| 88 |
+
document.getElementById('pair-search').addEventListener('input', filterSignals);
|
| 89 |
+
document.getElementById('refresh-btn').addEventListener('click', loadSignals);
|
| 90 |
+
|
| 91 |
+
async function loadSignals() {
|
| 92 |
+
const timeframe = document.querySelector('.timeframe-tab.active').dataset.timeframe;
|
| 93 |
+
const assetType = document.getElementById('asset-type').value;
|
| 94 |
+
|
| 95 |
+
try {
|
| 96 |
+
const response = await fetch('/api/scalp/signals', {
|
| 97 |
+
method: 'POST',
|
| 98 |
+
headers: { 'Content-Type': 'application/json' },
|
| 99 |
+
body: JSON.stringify({ timeframe, asset_type: assetType })
|
| 100 |
+
});
|
| 101 |
+
|
| 102 |
+
const data = await response.json();
|
| 103 |
+
renderSignals(data.signals);
|
| 104 |
+
} catch (error) {
|
| 105 |
+
console.error('Error loading signals:', error);
|
| 106 |
+
}
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
+
function renderSignals(signals) {
|
| 110 |
+
const tbody = document.getElementById('signals-body');
|
| 111 |
+
tbody.innerHTML = '';
|
| 112 |
+
|
| 113 |
+
signals.forEach(signal => {
|
| 114 |
+
const row = document.createElement('tr');
|
| 115 |
+
row.className = 'hover:bg-gray-800/50';
|
| 116 |
+
|
| 117 |
+
const directionClass = signal.direction === 'long' ?
|
| 118 |
+
'bg-emerald-500/20 text-emerald-300 border-emerald-500/40' :
|
| 119 |
+
signal.direction === 'short' ?
|
| 120 |
+
'bg-rose-500/20 text-rose-300 border-rose-500/40' :
|
| 121 |
+
'bg-gray-500/20 text-gray-300 border-gray-500/40';
|
| 122 |
+
|
| 123 |
+
row.innerHTML = `
|
| 124 |
+
<td class="px-4 py-3 font-medium">${signal.symbol}</td>
|
| 125 |
+
<td class="px-4 py-3 text-xs">${signal.asset_type.toUpperCase()}</td>
|
| 126 |
+
<td class="px-4 py-3 text-xs">${signal.timeframe}</td>
|
| 127 |
+
<td class="px-4 py-3">
|
| 128 |
+
<span class="px-2 py-1 text-xs rounded-full font-semibold uppercase border ${directionClass}">
|
| 129 |
+
${signal.direction.toUpperCase()}
|
| 130 |
+
</span>
|
| 131 |
+
</td>
|
| 132 |
+
<td class="px-4 py-3 text-xs">
|
| 133 |
+
${signal.entry_zone[0].toFixed(5)} - ${signal.entry_zone[1].toFixed(5)}
|
| 134 |
+
</td>
|
| 135 |
+
<td class="px-4 py-3 text-xs">${signal.stop_loss.toFixed(5)}</td>
|
| 136 |
+
<td class="px-4 py-3 text-xs">
|
| 137 |
+
${signal.take_profit_levels[0].toFixed(5)} / ${signal.take_profit_levels[1].toFixed(5)}
|
| 138 |
+
</td>
|
| 139 |
+
<td class="px-4 py-3">
|
| 140 |
+
<div class="flex items-center gap-2">
|
| 141 |
+
<div class="h-1.5 w-16 bg-gray-700 rounded-full overflow-hidden">
|
| 142 |
+
<div class="h-full bg-emerald-500" style="width: ${signal.confidence}%"></div>
|
| 143 |
+
</div>
|
| 144 |
+
<span class="text-xs">${signal.confidence}%</span>
|
| 145 |
+
</div>
|
| 146 |
+
</td>
|
| 147 |
+
<td class="px-4 py-3 text-xs text-gray-400">
|
| 148 |
+
${new Date(signal.updated_at).toLocaleTimeString()}
|
| 149 |
+
</td>
|
| 150 |
+
<td class="px-4 py-3 text-right">
|
| 151 |
+
<button class="text-xs px-3 py-1 rounded-lg bg-gray-700 hover:bg-gray-600">
|
| 152 |
+
View Plan
|
| 153 |
+
</button>
|
| 154 |
+
</td>
|
| 155 |
+
`;
|
| 156 |
+
|
| 157 |
+
tbody.appendChild(row);
|
| 158 |
+
});
|
| 159 |
+
}
|
| 160 |
+
|
| 161 |
+
function filterSignals() {
|
| 162 |
+
const search = document.getElementById('pair-search').value.toLowerCase();
|
| 163 |
+
const rows = document.querySelectorAll('#signals-body tr');
|
| 164 |
+
|
| 165 |
+
rows.forEach(row => {
|
| 166 |
+
const pair = row.querySelector('td:first-child').textContent.toLowerCase();
|
| 167 |
+
row.style.display = pair.includes(search) ? '' : 'none';
|
| 168 |
+
});
|
| 169 |
+
}
|
| 170 |
+
|
| 171 |
+
// Load initial signals
|
| 172 |
+
loadSignals();
|
| 173 |
+
</script>
|
| 174 |
+
</body>
|
| 175 |
+
</html>
|
|
@@ -45,6 +45,31 @@ p {
|
|
| 45 |
.signal-card.buy {
|
| 46 |
animation: pulse 2s infinite;
|
| 47 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
.card p:last-child {
|
| 49 |
margin-bottom: 0;
|
| 50 |
}
|
|
|
|
| 45 |
.signal-card.buy {
|
| 46 |
animation: pulse 2s infinite;
|
| 47 |
}
|
| 48 |
+
|
| 49 |
+
/* Scalp Trading Dashboard */
|
| 50 |
+
.timeframe-tab {
|
| 51 |
+
padding: 0.35rem 0.75rem;
|
| 52 |
+
font-size: 0.75rem;
|
| 53 |
+
color: #9ca3af;
|
| 54 |
+
cursor: pointer;
|
| 55 |
+
transition: all 0.2s;
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
.timeframe-tab.active {
|
| 59 |
+
background: #374151;
|
| 60 |
+
color: white;
|
| 61 |
+
border-radius: 0.375rem;
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
#signals-body tr:hover {
|
| 65 |
+
background: rgba(55, 65, 81, 0.5);
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
#signals-body td {
|
| 69 |
+
padding: 0.75rem 1rem;
|
| 70 |
+
color: #e5e7eb;
|
| 71 |
+
font-size: 0.875rem;
|
| 72 |
+
}
|
| 73 |
.card p:last-child {
|
| 74 |
margin-bottom: 0;
|
| 75 |
}
|