Spaces:
Running
Easy fix. Nothing in your idea is “crypto-only” — we just need to tell the code (and DeepSite) to:
Browse filesSupport both crypto and forex symbols, and
Use the right data source depending on the market.
I’ll show you:
What to change in the logic
EXACT text to add to your DeepSite prompt so it builds crypto + forex together
1️⃣ What needs to work for Forex?
You want:
/api/analyze/market to handle:
BTCUSDT (crypto)
ETHUSDT (crypto)
EURUSD, GBPUSD, XAUUSD, USDJPY, etc. (forex)
Same signal 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": "1h",
"confidence": 0,
"time_horizon": "intra-day|swing|position",
"explanation": "string",
"meta": { "sources": [], "sentiment": {} }
}
So we just make the market analysis layer asset-agnostic:
asset_type: "crypto" or "forex"
Use:
Binance klines for crypto
yfinance (or similar) for forex
2️⃣ How the backend logic should work
In backend/timeseries_analysis.py, instead of only fetch_klines for Binance, you want:
async def fetch_ohlcv(
symbol: str,
timeframe: str,
asset_type: str = "crypto",
limit: int = 200
):
if asset_type == "crypto":
# Use Binance public klines, e.g. BTCUSDT
return await fetch_binance_klines(symbol, timeframe, limit)
elif asset_type == "forex":
# Use yfinance (EURUSD -> "EURUSD=X")
return fetch_yfinance_fx(symbol, timeframe, limit)
else:
raise ValueError("Unsupported asset_type")
And your FastAPI route:
class MarketRequest(BaseModel):
symbol: str
timeframe: str = "1h"
asset_type: str = "crypto" # "crypto" | "forex"
@app .post("/api/analyze/market", response_model=SignalResponse)
async def analyze_market(req: MarketRequest):
ohlcv = await fetch_ohlcv(
symbol=req.symbol,
timeframe=req.timeframe,
asset_type=req.asset_type,
)
technicals = compute_technicals(ohlcv)
signal = await generate_signal_from_market(
symbol=req.symbol,
timeframe=req.timeframe,
asset_type=req.asset_type,
ohlcv=ohlcv,
technicals=technicals,
)
return signal
Example request body for forex:
{
"symbol": "EURUSD",
"timeframe": "5m",
"asset_type": "forex"
}
That’s it. Same UI, same panel — just more markets.
3️⃣ What to add to your DeepSite prompt (copy–paste)
You don’t need a whole new prompt — just extend the one I gave you.
👉 In your DeepSite master prompt, right after the project description, add this block:
IMPORTANT – MARKETS (CRYPTO + FOREX)
This app MUST support both:
1. Crypto markets
- Examples: BTCUSDT, ETHUSDT, SOLUSDT
- Data source: Binance public klines (no API key)
2. Forex markets
- Examples: EURUSD, GBPUSD, USDJPY, XAUUSD, NAS100, etc.
- Implement an `asset_type` flag with allowed values `"crypto"` or `"forex"`.
- For `asset_type="forex"`, fetch OHLCV using a free public data source such as `yfinance`:
- Map `"EURUSD"` → `"EURUSD=X"`, `"GBPUSD"` → `"GBPUSD=X"`, `"XAUUSD"` → `"XAUUSD=X"`, etc.
- Implement a helper `normalize_forex_symbol(symbol: str) -> str` that returns the proper yfinance ticker.
- The technical analysis (EMA, RSI, MACD, ATR, support/resistance, bias) should be applied the same way to forex and crypto.
Update the backend so that:
- `backend/timeseries_analysis.py` exposes:
- `fetch_ohlcv(symbol: str, timeframe: str, asset_type: str = "crypto", limit: int = 200)`
- It internally calls:
- `fetch_binance_klines(...)` for `"crypto"`
- `fetch_yfinance_fx(...)` for `"forex"`
- The `/api/analyze/market` endpoint accepts an `asset_type` field in the JSON body with default `"crypto"`.
The signal JSON schema remains exactly the same and is **asset-agnostic**, so the frontend and webhooks work for both crypto and forex with no change.
If you want the frontend to make it obvious, you can also tell DeepSite this (add to the frontend section of the prompt):
In the frontend `PairSelector` component, add a simple toggle or dropdown:
- Market type: "Crypto" or "Forex"
Pass this as `asset_type` in the `/api/analyze/market` request.
4️⃣ TL;DR for you
Yes, it can work for forex, not just Bitcoin.
Add asset_type (crypto/forex) everywhere: request, timeseries fetcher, signal engine.
Use Binance for crypto, yfinance (or similar) for forex candles.
Update the DeepSite prompt with the block I gave you, then regenerate.
- backend/main.py +21 -5
- backend/signal_engine.py +26 -13
- backend/timeseries_analysis.py +44 -1
- index.html +5 -6
- requirements.txt +1 -0
|
@@ -94,14 +94,30 @@ async def analyze_screenshot(
|
|
| 94 |
image_bytes = await file.read()
|
| 95 |
signal = await generate_signal_from_image(image_bytes, symbol or "BTCUSDT")
|
| 96 |
return signal
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 97 |
|
| 98 |
@app.post("/api/analyze/market")
|
| 99 |
-
async def analyze_market(
|
| 100 |
try:
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 105 |
raise HTTPException(500, detail=str(e))
|
| 106 |
|
| 107 |
@app.post("/api/webhook/signal")
|
|
|
|
| 94 |
image_bytes = await file.read()
|
| 95 |
signal = await generate_signal_from_image(image_bytes, symbol or "BTCUSDT")
|
| 96 |
return signal
|
| 97 |
+
from pydantic import BaseModel
|
| 98 |
+
|
| 99 |
+
class MarketRequest(BaseModel):
|
| 100 |
+
symbol: str
|
| 101 |
+
timeframe: str = "1h"
|
| 102 |
+
asset_type: str = "crypto" # "crypto" | "forex"
|
| 103 |
|
| 104 |
@app.post("/api/analyze/market")
|
| 105 |
+
async def analyze_market(request: MarketRequest):
|
| 106 |
try:
|
| 107 |
+
ohlcv = await fetch_ohlcv(
|
| 108 |
+
symbol=request.symbol,
|
| 109 |
+
timeframe=request.timeframe,
|
| 110 |
+
asset_type=request.asset_type
|
| 111 |
+
)
|
| 112 |
+
technicals = compute_technicals(ohlcv)
|
| 113 |
+
return await generate_signal_from_market(
|
| 114 |
+
symbol=request.symbol,
|
| 115 |
+
timeframe=request.timeframe,
|
| 116 |
+
asset_type=request.asset_type,
|
| 117 |
+
ohlcv=ohlcv,
|
| 118 |
+
technicals=technicals
|
| 119 |
+
)
|
| 120 |
+
except Exception as e:
|
| 121 |
raise HTTPException(500, detail=str(e))
|
| 122 |
|
| 123 |
@app.post("/api/webhook/signal")
|
|
@@ -64,27 +64,40 @@ Provide your response in this exact JSON format:
|
|
| 64 |
"sentiment": {{}}
|
| 65 |
}}
|
| 66 |
}}"""
|
| 67 |
-
async def generate_signal_from_market(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 68 |
"""Generate trading signal from live market data."""
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
technicals
|
|
|
|
| 72 |
|
| 73 |
-
# Get sentiment analysis
|
| 74 |
-
sentiment =
|
|
|
|
|
|
|
| 75 |
|
| 76 |
# Prepare LLM prompt
|
| 77 |
-
prompt = build_market_prompt(technicals, sentiment, symbol, timeframe)
|
| 78 |
-
|
| 79 |
-
# Get LLM reasoning
|
| 80 |
llm_response = await model_registry.llm_reason(prompt)
|
| 81 |
|
| 82 |
# Parse response into structured format
|
| 83 |
return parse_llm_response(llm_response, {}, technicals, sentiment)
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 88 |
Technical Indicators ({timeframe} timeframe):
|
| 89 |
- Trend: {technicals['trend']}
|
| 90 |
- Momentum: {technicals['momentum']}
|
|
|
|
| 64 |
"sentiment": {{}}
|
| 65 |
}}
|
| 66 |
}}"""
|
| 67 |
+
async def generate_signal_from_market(
|
| 68 |
+
symbol: str,
|
| 69 |
+
timeframe: str,
|
| 70 |
+
asset_type: str = "crypto",
|
| 71 |
+
ohlcv: List[List[Any]] = None,
|
| 72 |
+
technicals: Dict[str, Any] = None
|
| 73 |
+
) -> Dict[str, Any]:
|
| 74 |
"""Generate trading signal from live market data."""
|
| 75 |
+
if ohlcv is None:
|
| 76 |
+
ohlcv = await fetch_ohlcv(symbol, timeframe, asset_type)
|
| 77 |
+
if technicals is None:
|
| 78 |
+
technicals = compute_technicals(ohlcv)
|
| 79 |
|
| 80 |
+
# Get sentiment analysis (only for crypto)
|
| 81 |
+
sentiment = {}
|
| 82 |
+
if asset_type == "crypto":
|
| 83 |
+
sentiment = await get_crypto_sentiment(symbol[:3]) # Extract base symbol
|
| 84 |
|
| 85 |
# Prepare LLM prompt
|
| 86 |
+
prompt = build_market_prompt(technicals, sentiment, symbol, timeframe, asset_type)
|
| 87 |
+
# Get LLM reasoning
|
|
|
|
| 88 |
llm_response = await model_registry.llm_reason(prompt)
|
| 89 |
|
| 90 |
# Parse response into structured format
|
| 91 |
return parse_llm_response(llm_response, {}, technicals, sentiment)
|
| 92 |
+
def build_market_prompt(
|
| 93 |
+
technicals: Dict,
|
| 94 |
+
sentiment: Dict,
|
| 95 |
+
symbol: str,
|
| 96 |
+
timeframe: str,
|
| 97 |
+
asset_type: str = "crypto"
|
| 98 |
+
) -> str:
|
| 99 |
+
asset_type_str = "cryptocurrency" if asset_type == "crypto" else "forex"
|
| 100 |
+
return f"""Analyze this {asset_type_str} trading situation and provide a professional trading signal in JSON format:
|
| 101 |
Technical Indicators ({timeframe} timeframe):
|
| 102 |
- Trend: {technicals['trend']}
|
| 103 |
- Momentum: {technicals['momentum']}
|
|
@@ -2,11 +2,18 @@
|
|
| 2 |
import pandas as pd
|
| 3 |
import numpy as np
|
| 4 |
import requests
|
|
|
|
| 5 |
from typing import Dict, Any, List
|
| 6 |
|
| 7 |
BINANCE_API_URL = "https://api.binance.com/api/v3/klines"
|
| 8 |
|
| 9 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
params = {
|
| 11 |
"symbol": symbol.upper(),
|
| 12 |
"interval": timeframe,
|
|
@@ -16,6 +23,42 @@ async def fetch_klines(symbol: str, timeframe: str, limit: int = 200) -> List[Li
|
|
| 16 |
response.raise_for_status()
|
| 17 |
return response.json()
|
| 18 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
def compute_technicals(ohlcv: List[List[Any]]) -> Dict[str, Any]:
|
| 20 |
# Convert to pandas DataFrame
|
| 21 |
df = pd.DataFrame(ohlcv, columns=["timestamp", "open", "high", "low", "close", "volume"])
|
|
|
|
| 2 |
import pandas as pd
|
| 3 |
import numpy as np
|
| 4 |
import requests
|
| 5 |
+
import yfinance as yf
|
| 6 |
from typing import Dict, Any, List
|
| 7 |
|
| 8 |
BINANCE_API_URL = "https://api.binance.com/api/v3/klines"
|
| 9 |
|
| 10 |
+
def normalize_forex_symbol(symbol: str) -> str:
|
| 11 |
+
"""Convert forex symbol to yfinance format (EURUSD -> EURUSD=X)"""
|
| 12 |
+
if symbol.endswith('=X'):
|
| 13 |
+
return symbol
|
| 14 |
+
return f"{symbol}=X"
|
| 15 |
+
|
| 16 |
+
async def fetch_binance_klines(symbol: str, timeframe: str, limit: int = 200) -> List[List[Any]]:
|
| 17 |
params = {
|
| 18 |
"symbol": symbol.upper(),
|
| 19 |
"interval": timeframe,
|
|
|
|
| 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 |
+
interval = tf_map.get(timeframe, '1h')
|
| 34 |
+
|
| 35 |
+
ticker = normalize_forex_symbol(symbol)
|
| 36 |
+
data = yf.Ticker(ticker)
|
| 37 |
+
hist = data.history(period=f"{limit}d", interval=interval)
|
| 38 |
+
|
| 39 |
+
# Convert to same format as Binance klines
|
| 40 |
+
ohlcv = []
|
| 41 |
+
for _, row in hist.iterrows():
|
| 42 |
+
ohlcv.append([
|
| 43 |
+
int(row.name.timestamp()) * 1000, # timestamp
|
| 44 |
+
row['Open'], row['High'], row['Low'], row['Close'],
|
| 45 |
+
row['Volume']
|
| 46 |
+
])
|
| 47 |
+
return ohlcv
|
| 48 |
+
|
| 49 |
+
async def fetch_ohlcv(
|
| 50 |
+
symbol: str,
|
| 51 |
+
timeframe: str,
|
| 52 |
+
asset_type: str = "crypto",
|
| 53 |
+
limit: int = 200
|
| 54 |
+
) -> List[List[Any]]:
|
| 55 |
+
"""Fetch OHLCV data for either crypto or forex markets"""
|
| 56 |
+
if asset_type == "crypto":
|
| 57 |
+
return await fetch_binance_klines(symbol, timeframe, limit)
|
| 58 |
+
elif asset_type == "forex":
|
| 59 |
+
return fetch_yfinance_fx(symbol, timeframe, limit)
|
| 60 |
+
else:
|
| 61 |
+
raise ValueError("Unsupported asset_type. Must be 'crypto' or 'forex'")
|
| 62 |
def compute_technicals(ohlcv: List[List[Any]]) -> Dict[str, Any]:
|
| 63 |
# Convert to pandas DataFrame
|
| 64 |
df = pd.DataFrame(ohlcv, columns=["timestamp", "open", "high", "low", "close", "volume"])
|
|
@@ -58,16 +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 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
</select>
|
| 65 |
</div>
|
| 66 |
<div>
|
| 67 |
<label class="block text-sm font-medium mb-1">Pair</label>
|
| 68 |
-
<input type="text" 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">
|
| 69 |
</div>
|
| 70 |
-
|
| 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">
|
| 73 |
<option>5m</option>
|
|
|
|
| 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">Binance (Crypto)</option>
|
| 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 |
</div>
|
| 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">
|
| 72 |
<option>5m</option>
|
|
@@ -10,6 +10,7 @@ feedparser==6.0.10
|
|
| 10 |
huggingface-hub==0.15.1
|
| 11 |
transformers==4.29.2
|
| 12 |
python-dotenv==1.0.0
|
|
|
|
| 13 |
```
|
| 14 |
|
| 15 |
5. Let's create the .env.example file:
|
|
|
|
| 10 |
huggingface-hub==0.15.1
|
| 11 |
transformers==4.29.2
|
| 12 |
python-dotenv==1.0.0
|
| 13 |
+
yfinance==0.2.28
|
| 14 |
```
|
| 15 |
|
| 16 |
5. Let's create the .env.example file:
|