Spaces:
Running
2️⃣ Prompt for DeepSite (supports Crypto + Forex)
Browse files👉 Copy everything inside this box and paste it into DeepSite:
You are a senior full-stack AI engineer.
Build a production-ready project called **CryptoSignal-Sleuth** that works for **both crypto and forex**, not just Bitcoin.
Tech stack:
- Backend: FastAPI (Python 3)
- Frontend: React + TypeScript + Vite + TailwindCSS
- Models: Hugging Face (vision, OCR, sentiment, LLM reasoning)
The app will be deployed on **Hugging Face Spaces**.
====================================================
## 1. FEATURES
The app must do all of this:
1. Let the user **upload a screenshot** of a trading chart (crypto or forex):
- Run **vision + OCR** on the image.
- Extract basic info: trend (up/down/range), key levels, patterns if visible.
- Pass these cues into an LLM to generate a trading **signal**.
2. Let the user **analyze a market live** by sending:
```json
{
"symbol": "BTCUSDT" or "EURUSD",
"timeframe": "5m" or "15m" or "1h",
"asset_type": "crypto" or "forex"
}
Fetch OHLCV data.
Run technical indicators (EMA, RSI, MACD, ATR, support/resistance).
Optionally include sentiment.
Use an LLM to synthesize a final signal.
Provide a clean REST API + webhook so I can connect:
n8n
Discord bot
Telegram bot
====================================================
2. MARKETS: CRYPTO + FOREX
The app MUST support:
2.1 Crypto
Examples: BTCUSDT, ETHUSDT, SOLUSDT
Data source: Binance public klines (no key).
Implement a helper:
fetch_binance_klines(symbol: str, timeframe: str, limit: int = 200)
2.2 Forex
Examples: EURUSD, GBPUSD, USDJPY, XAUUSD, NAS100
Data source: use yfinance (or similar free source).
Implement helpers:
normalize_forex_symbol("EURUSD") -> "EURUSD=X"
fetch_yfinance_fx(symbol: str, timeframe: str, limit: int = 200)
2.3 Common wrapper
Create in backend/timeseries_analysis.py:
fetch_ohlcv(symbol: str, timeframe: str, asset_type: str = "crypto", limit: int = 200)
Logic:
If asset_type == "crypto" → call fetch_binance_klines.
If asset_type == "forex" → normalize symbol and call fetch_yfinance_fx.
Apply the same technical analysis (EMA, RSI, MACD, ATR, support/resistance, trend, momentum) to both crypto and forex.
====================================================
3. SIGNAL SCHEMA (VERY IMPORTANT)
All signals (image or market) MUST follow this JSON:
{
"direction": "long|short|neutral",
"entry_zone": [0.0, 0.0],
"stop_loss": 0.0,
"take_profit_levels": [0.0, 0.0, 0.0],
"timeframe_inferred": "1m|5m|15m|1h|4h|1d",
"confidence": 0,
"time_horizon": "intra-day|swing|position",
"explanation": "string",
"meta": {
"sources": [],
"sentiment": {}
}
}
Use Pydantic models so FastAPI enforces this structure.
====================================================
4. PROJECT STRUCTURE
Use this exact file structure:
app.py
backend/
config.py
main.py
models_registry.py
signal_engine.py
image_analysis.py
timeseries_analysis.py
sentiment_analysis.py
frontend/
index.html
package.json
tsconfig.json
vite.config.ts
postcss.config.js
tailwind.config.js
src/
index.css
main.tsx
App.tsx
lib/api.ts
pages/
Dashboard.tsx
Backtest.tsx
Settings.tsx
Docs.tsx
components/
Navbar.tsx
Sidebar.tsx
ChartUploadCard.tsx
PairSelector.tsx
SignalPanel.tsx
HistoryTable.tsx
ModelSettingsPanel.tsx
requirements.txt
Fill ALL of these files with real, working code.
====================================================
5. BACKEND DETAILS
5.1 Config
backend/config.py:
Pydantic settings class that loads:
HF_TOKEN
USE_INFERENCE_API (default "1")
INFERENCE_LLM_MODEL (default "Qwen/Qwen2.5-7B-Instruct")
LOCAL_LLM_MODEL (default "google/flan-t5-base")
FRONTEND_BASE_PATH (default "/")
API_BASE_URL (default "/")
WEBHOOK_API_KEY (optional)
5.2 Models Registry
backend/models_registry.py:
Functions using Hugging Face Inference API (with HF_TOKEN):
analyze_chart_image(image_bytes) -> dict
run_ocr(image_bytes) -> dict
analyze_sentiment(texts: list[str]) -> dict
llm_reason(prompt: str) -> str
5.3 Image Analysis
backend/image_analysis.py:
extract_chart_features(image_bytes) -> dict:
Use vision + OCR to infer:
trend: up/down/range
key levels
any visible pattern labels
possible timeframe if visible
5.4 Time-Series Analysis
backend/timeseries_analysis.py:
Implement:
fetch_ohlcv (crypto + forex wrapper as described above).
compute_technicals(ohlcv) -> dict:
EMA fast/slow
RSI
MACD
ATR
support/resistance
trend and momentum labels.
5.5 Sentiment Analysis
backend/sentiment_analysis.py:
Fetch recent crypto/forex headlines (e.g. with RSS or generic feed).
Run sentiment model.
Return {"bullish": x, "bearish": y, "neutral": z, "summary": "..."}
This is stored in meta["sentiment"].
5.6 Signal Engine
backend/signal_engine.py:
Implement:
generate_signal_from_image(image_bytes) -> dict
generate_signal_from_market(symbol: str, timeframe: str, asset_type: str, ohlcv, technicals) -> dict
Build a prompt for the LLM that includes:
asset_type (crypto or forex)
symbol
timeframe
recent price action
trend & momentum
volatility
sentiment summary
image cues (if using screenshot)
Ask the LLM to output a decision that maps cleanly into the Signal schema above.
5.7 Main FastAPI App
backend/main.py routes:
GET /api/health → { "status": "ok" }
GET /api/models → shows which HF models are active.
POST /api/analyze/screenshot
multipart/form-data, field name: file.
returns Signal JSON.
POST /api/analyze/market
JSON body:
{ "symbol": "EURUSD", "timeframe": "5m", "asset_type": "forex" }
returns Signal JSON.
POST /api/webhook/signal
optional header X-API-KEY == WEBHOOK_API_KEY.
accepts any valid Signal JSON and echoes it (for n8n integration).
5.8 app.py
Create FastAPI app.
Include backend router at /api.
Serve frontend static files from frontend/dist at /.
Use FRONTEND_BASE_PATH and API_BASE_URL if needed.
====================================================
6. FRONTEND (React + TypeScript + Vite + Tailwind)
Implement the frontend with a dark trading dashboard look.
6.1 API Client
frontend/src/lib/api.ts:
Base URL: import.meta.env.VITE_API_BASE_URL || "/api"
Functions:
uploadChart(file: File): Promise<SignalResponse>
getMarketSignal(symbol: string, timeframe: string, asset_type: "crypto"|"forex"): Promise<SignalResponse>
getModels()
postWebhookSignal(payload: any)
Define SignalResponse TypeScript interface using the Signal schema.
6.2 Components / Pages
Navbar + Sidebar layout.
Dashboard page:
ChartUploadCard → upload screenshot, show returned signal in SignalPanel.
PairSelector:
Input/select for symbol (BTCUSDT, ETHUSDT, EURUSD, GBPUSD, XAUUSD, etc.)
Select for timeframe.
Dropdown for asset_type ("crypto" or "forex").
Button → calls getMarketSignal.
SignalPanel displays the SignalResponse nicely (direction, entry zone, SL, TP1–3, confidence, explanation, sentiment).
HistoryTable → shows a list of signals from the current session.
Backtest:
Simple placeholder explaining that backtesting is coming later.
Settings:
Show data from /api/models.
Show current base URLs and some info text.
Docs:
Show example request/response for:
/api/analyze/market
/api/analyze/screenshot
/api/webhook/signal
====================================================
7. REQUIREMENTS + ENV
requirements.txt should include at least:
fastapi
uvicorn[standard]
python-multipart
pydantic
httpx or requests
numpy
pandas (if needed)
ta (or manual indicator math)
yfinance
feedparser (or similar)
huggingface-hub or transformers (if needed)
jinja2 (if needed)
Also output a .env.example with:
HF_TOKEN=
USE_INFERENCE_API=1
INFERENCE_LLM_MODEL=Qwen/Qwen2.5-7B-Instruct
LOCAL_LLM_MODEL=google/flan-t5-base
FRONTEND_BASE_PATH=/
API_BASE_URL=/api
WEBHOOK_API_KEY=
====================================================
8. SUMMARY
At the very end of your answer, add a short summary:
How to install and run locally:
pip install -r requirements.txt
cd frontend && npm install && npm run build
uvicorn app:app --host 0.0.0.0 --port 7860
How to deploy on Hugging Face Spaces with app.py as the entrypoint.
Generate the COMPLETE codebase with no TODOs or placeholders.
---
If you want next, I can help you with the **exact JSON body** to use in n8n, or with a **Discord bot command** like `/signal EURUSD 5m forex` that calls your new API.
::contentReference[oaicite:0]{index=0}
- backend/timeseries_analysis.py +3 -3
- components/forex-selector.js +42 -0
- index.html +23 -5
- style.css +15 -9
|
@@ -22,15 +22,15 @@ async def fetch_binance_klines(symbol: str, timeframe: str, limit: int = 200) ->
|
|
| 22 |
response = requests.get(BINANCE_API_URL, params=params)
|
| 23 |
response.raise_for_status()
|
| 24 |
return response.json()
|
| 25 |
-
|
| 26 |
def fetch_yfinance_fx(symbol: str, timeframe: str, limit: int = 200) -> List[List[Any]]:
|
| 27 |
"""Fetch forex data using yfinance"""
|
| 28 |
tf_map = {
|
| 29 |
'1m': '1m', '5m': '5m', '15m': '15m',
|
| 30 |
'30m': '30m', '1h': '1h', '4h': '4h',
|
| 31 |
-
'1D': '1d', '1W': '1wk', '1M': '1mo'
|
|
|
|
| 32 |
}
|
| 33 |
-
|
| 34 |
|
| 35 |
ticker = normalize_forex_symbol(symbol)
|
| 36 |
data = yf.Ticker(ticker)
|
|
|
|
| 22 |
response = requests.get(BINANCE_API_URL, params=params)
|
| 23 |
response.raise_for_status()
|
| 24 |
return response.json()
|
|
|
|
| 25 |
def fetch_yfinance_fx(symbol: str, timeframe: str, limit: int = 200) -> List[List[Any]]:
|
| 26 |
"""Fetch forex data using yfinance"""
|
| 27 |
tf_map = {
|
| 28 |
'1m': '1m', '5m': '5m', '15m': '15m',
|
| 29 |
'30m': '30m', '1h': '1h', '4h': '4h',
|
| 30 |
+
'1D': '1d', '1W': '1wk', '1M': '1mo',
|
| 31 |
+
'1d': '1d', '4h': '4h' # Add common aliases
|
| 32 |
}
|
| 33 |
+
interval = tf_map.get(timeframe, '1h')
|
| 34 |
|
| 35 |
ticker = normalize_forex_symbol(symbol)
|
| 36 |
data = yf.Ticker(ticker)
|
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
class ForexSelector extends HTMLElement {
|
| 2 |
+
constructor() {
|
| 3 |
+
super();
|
| 4 |
+
this.attachShadow({ mode: 'open' });
|
| 5 |
+
this.shadowRoot.innerHTML = `
|
| 6 |
+
<style>
|
| 7 |
+
select, input {
|
| 8 |
+
background: rgba(30, 41, 59, 0.8);
|
| 9 |
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
| 10 |
+
color: white;
|
| 11 |
+
padding: 0.5rem;
|
| 12 |
+
border-radius: 0.375rem;
|
| 13 |
+
width: 100%;
|
| 14 |
+
}
|
| 15 |
+
label {
|
| 16 |
+
display: block;
|
| 17 |
+
margin-bottom: 0.5rem;
|
| 18 |
+
font-size: 0.875rem;
|
| 19 |
+
color: #94a3b8;
|
| 20 |
+
}
|
| 21 |
+
.form-group {
|
| 22 |
+
margin-bottom: 1rem;
|
| 23 |
+
}
|
| 24 |
+
</style>
|
| 25 |
+
<div class="form-group">
|
| 26 |
+
<label for="forex-pair">Forex Pair</label>
|
| 27 |
+
<input id="forex-pair" type="text" placeholder="EURUSD" list="common-pairs">
|
| 28 |
+
<datalist id="common-pairs">
|
| 29 |
+
<option>EURUSD</option>
|
| 30 |
+
<option>GBPUSD</option>
|
| 31 |
+
<option>USDJPY</option>
|
| 32 |
+
<option>AUDUSD</option>
|
| 33 |
+
<option>USDCAD</option>
|
| 34 |
+
<option>XAUUSD</option>
|
| 35 |
+
<option>NAS100</option>
|
| 36 |
+
</datalist>
|
| 37 |
+
</div>
|
| 38 |
+
`;
|
| 39 |
+
}
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
customElements.define('forex-selector', ForexSelector);
|
|
@@ -58,14 +58,15 @@
|
|
| 58 |
<div>
|
| 59 |
<label class="block text-sm font-medium mb-1">Exchange</label>
|
| 60 |
<select class="w-full bg-gray-700 border border-gray-600 rounded-lg px-3 py-2 focus:ring-indigo-500 focus:border-indigo-500">
|
| 61 |
-
<option value="crypto">
|
| 62 |
-
<option value="forex">Forex</option>
|
| 63 |
</select>
|
| 64 |
</div>
|
| 65 |
<div>
|
| 66 |
<label class="block text-sm font-medium mb-1">Pair</label>
|
| 67 |
-
<input type="text" id="pair-input" placeholder="BTCUSDT" class="w-full bg-gray-700 border border-gray-600 rounded-lg px-3 py-2 focus:ring-indigo-500 focus:border-indigo-500">
|
| 68 |
-
|
|
|
|
| 69 |
<div>
|
| 70 |
<label class="block text-sm font-medium mb-1">Timeframe</label>
|
| 71 |
<select class="w-full bg-gray-700 border border-gray-600 rounded-lg px-3 py-2 focus:ring-indigo-500 focus:border-indigo-500">
|
|
@@ -116,5 +117,22 @@
|
|
| 116 |
<span class="text-gray-400">Take Profit 2</span>
|
| 117 |
<span id="tp2{"ok":false,"message":"terminated"}
|
| 118 |
<script src="https://huggingface.co/deepsite/deepsite-badge.js"></script>
|
| 119 |
-
</
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 120 |
</html>
|
|
|
|
| 58 |
<div>
|
| 59 |
<label class="block text-sm font-medium mb-1">Exchange</label>
|
| 60 |
<select class="w-full bg-gray-700 border border-gray-600 rounded-lg px-3 py-2 focus:ring-indigo-500 focus:border-indigo-500">
|
| 61 |
+
<option value="crypto">Crypto (Binance)</option>
|
| 62 |
+
<option value="forex">Forex (yFinance)</option>
|
| 63 |
</select>
|
| 64 |
</div>
|
| 65 |
<div>
|
| 66 |
<label class="block text-sm font-medium mb-1">Pair</label>
|
| 67 |
+
<input type="text" id="pair-input" placeholder="BTCUSDT or EURUSD" class="w-full bg-gray-700 border border-gray-600 rounded-lg px-3 py-2 focus:ring-indigo-500 focus:border-indigo-500">
|
| 68 |
+
<forex-selector class="mt-2 hidden"></forex-selector>
|
| 69 |
+
</div>
|
| 70 |
<div>
|
| 71 |
<label class="block text-sm font-medium mb-1">Timeframe</label>
|
| 72 |
<select class="w-full bg-gray-700 border border-gray-600 rounded-lg px-3 py-2 focus:ring-indigo-500 focus:border-indigo-500">
|
|
|
|
| 117 |
<span class="text-gray-400">Take Profit 2</span>
|
| 118 |
<span id="tp2{"ok":false,"message":"terminated"}
|
| 119 |
<script src="https://huggingface.co/deepsite/deepsite-badge.js"></script>
|
| 120 |
+
<script src="components/forex-selector.js"></script>
|
| 121 |
+
<script>
|
| 122 |
+
document.addEventListener('DOMContentLoaded', () => {
|
| 123 |
+
const assetTypeSelect = document.querySelector('select');
|
| 124 |
+
const forexSelector = document.querySelector('forex-selector');
|
| 125 |
+
|
| 126 |
+
assetTypeSelect.addEventListener('change', (e) => {
|
| 127 |
+
if (e.target.value === 'forex') {
|
| 128 |
+
forexSelector.classList.remove('hidden');
|
| 129 |
+
document.getElementById('pair-input').placeholder = 'EURUSD';
|
| 130 |
+
} else {
|
| 131 |
+
forexSelector.classList.add('hidden');
|
| 132 |
+
document.getElementById('pair-input').placeholder = 'BTCUSDT';
|
| 133 |
+
}
|
| 134 |
+
});
|
| 135 |
+
});
|
| 136 |
+
</script>
|
| 137 |
+
</body>
|
| 138 |
</html>
|
|
@@ -1,8 +1,9 @@
|
|
|
|
|
| 1 |
body {
|
| 2 |
-
|
| 3 |
-
|
|
|
|
| 4 |
}
|
| 5 |
-
|
| 6 |
h1 {
|
| 7 |
font-size: 16px;
|
| 8 |
margin-top: 0;
|
|
@@ -14,15 +15,20 @@ p {
|
|
| 14 |
margin-bottom: 10px;
|
| 15 |
margin-top: 5px;
|
| 16 |
}
|
| 17 |
-
|
| 18 |
.card {
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
|
|
|
|
|
|
| 24 |
}
|
| 25 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
.card p:last-child {
|
| 27 |
margin-bottom: 0;
|
| 28 |
}
|
|
|
|
| 1 |
+
|
| 2 |
body {
|
| 3 |
+
font-family: -apple-system, BlinkMacSystemFont, "Arial", sans-serif;
|
| 4 |
+
background: linear-gradient(to bottom right, #1a1a2e, #16213e);
|
| 5 |
+
color: #e2e8f0;
|
| 6 |
}
|
|
|
|
| 7 |
h1 {
|
| 8 |
font-size: 16px;
|
| 9 |
margin-top: 0;
|
|
|
|
| 15 |
margin-bottom: 10px;
|
| 16 |
margin-top: 5px;
|
| 17 |
}
|
|
|
|
| 18 |
.card {
|
| 19 |
+
background: rgba(30, 41, 59, 0.8);
|
| 20 |
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
| 21 |
+
border-radius: 12px;
|
| 22 |
+
padding: 1.5rem;
|
| 23 |
+
backdrop-filter: blur(10px);
|
| 24 |
+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
| 25 |
+
transition: all 0.3s ease;
|
| 26 |
}
|
| 27 |
|
| 28 |
+
.card:hover {
|
| 29 |
+
box-shadow: 0 10px 15px rgba(0, 0, 0, 0.2);
|
| 30 |
+
transform: translateY(-2px);
|
| 31 |
+
}
|
| 32 |
.card p:last-child {
|
| 33 |
margin-bottom: 0;
|
| 34 |
}
|