Alexo19 commited on
Commit
278fca3
·
verified ·
1 Parent(s): ff607b4

Since I can’t see the exact /dashboard/scalp-trading UI because it’s behind login, I’ll recreate the same vibe:

Browse files

Clean 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)}`
: "-"}

Files changed (3) hide show
  1. backend/main.py +43 -2
  2. scalp.html +175 -0
  3. style.css +25 -0
backend/main.py CHANGED
@@ -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,
scalp.html ADDED
@@ -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>
style.css CHANGED
@@ -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
  }