Add new feature to handle user input

This commit is contained in:
Stefan
2026-10-30 09:20:09 +01:00
parent 033adb87da
commit 202f48d94f
10 changed files with 1535 additions and 0 deletions
+152
View File
@@ -0,0 +1,152 @@
"""
ranker.py — Asset ranker
Combineert: technische analyse score + sentiment score
Geeft top 6 tradeable assets terug uit het universe
"""
import asyncio
import logging
from dataclasses import dataclass
from exchange import fetch_ohlcv, fetch_tickers, TRADING_UNIVERSE
from analysis import calculate_indicators, score_asset, AssetScore
from sentiment import fetch_all_sentiments, SentimentResult
logger = logging.getLogger(__name__)
# Gewichten voor gecombineerde score
TA_WEIGHT = 0.70 # Technische analyse weegt zwaarder
SENTIMENT_WEIGHT = 0.30 # Sentiment is bevestigend, niet leidend
@dataclass
class RankedAsset:
symbol: str
rank: int
combined_score: float # 0-100
ta_score: float # 0-100 (AssetScore.opportunity_score)
sentiment_score: float # -1.0 tot +1.0
sentiment_label: str
sentiment_news: int
rsi: float
rsi_signal: str
ema_signal: str
vwap_signal: str
trend_strength: str
atr_pct: float
volume_score: float
price: float
change_24h: float
volume_24h: float
tradeable: bool
reason: str # Mensleesbare reden voor ranking
async def rank_assets(top_n: int = 6) -> list[RankedAsset]:
"""
Rangschik alle assets in het universe op combined score.
Geeft top_n tradeable assets terug.
"""
logger.info(f"Start ranking van {len(TRADING_UNIVERSE)} assets")
# 1. Ticker data ophalen (prijs, volume, 24h change)
tickers = fetch_tickers(TRADING_UNIVERSE)
# 2. OHLCV + TA berekeningen — sequentieel om rate limits te respecteren
ta_scores: dict[str, AssetScore] = {}
for symbol in TRADING_UNIVERSE:
try:
df = fetch_ohlcv(symbol, timeframe="5m", limit=100)
indicators = calculate_indicators(df)
ta_scores[symbol] = score_asset(symbol, indicators)
except Exception as e:
logger.warning(f"TA berekening mislukt voor {symbol}: {e}")
# 3. Sentiment — parallel ophalen
coin_codes = [s.split("/")[0] for s in TRADING_UNIVERSE]
sentiments = await fetch_all_sentiments(coin_codes)
# 4. Combineer scores
ranked = []
for symbol in TRADING_UNIVERSE:
ta = ta_scores.get(symbol)
coin_code = symbol.split("/")[0]
sent: SentimentResult = sentiments.get(coin_code)
ticker = tickers.get(symbol, {})
if not ta or not sent:
continue
# Sentiment score omzetten naar 0-100 schaal
sent_normalized = (sent.score + 1) * 50 # -1→0, 0→50, +1→100
combined = (
ta.opportunity_score * TA_WEIGHT +
sent_normalized * SENTIMENT_WEIGHT
)
# Reden genereren
reason = _build_reason(ta, sent)
ranked.append(RankedAsset(
symbol=symbol,
rank=0, # Wordt hieronder ingevuld
combined_score=round(combined, 1),
ta_score=ta.opportunity_score,
sentiment_score=sent.score,
sentiment_label=sent.label,
sentiment_news=sent.news_count,
rsi=ta.rsi,
rsi_signal=ta.rsi_signal,
ema_signal=ta.ema_signal,
vwap_signal=ta.vwap_signal,
trend_strength=ta.trend_strength,
atr_pct=ta.atr_pct,
volume_score=ta.volume_score,
price=ticker.get("price", 0),
change_24h=ticker.get("change_24h", 0),
volume_24h=ticker.get("volume_24h", 0),
tradeable=ta.tradeable and sent.score >= 0,
reason=reason,
))
# Sorteer op combined score
ranked.sort(key=lambda x: x.combined_score, reverse=True)
# Ken rang toe
for i, asset in enumerate(ranked):
asset.rank = i + 1
# Geef top_n terug
logger.info(f"Ranking klaar. Top asset: {ranked[0].symbol if ranked else 'geen'}")
return ranked[:top_n]
def _build_reason(ta: AssetScore, sent: SentimentResult) -> str:
"""Genereer een mensleesbare reden voor de ranking."""
parts = []
if ta.rsi_signal == "koopzone":
parts.append("RSI in koopzone")
elif ta.rsi_signal == "oversold":
parts.append("RSI oversold")
if ta.ema_signal == "bullish":
parts.append("EMA bullish")
if ta.vwap_signal == "onder_vwap":
parts.append("prijs onder VWAP")
if ta.bb_position < 0.3:
parts.append("dicht bij BB lower band")
if sent.score > 0.2:
parts.append(f"sterk positief nieuws ({sent.news_count} items)")
elif sent.score > 0:
parts.append("neutraal-positief nieuws")
elif sent.score < -0.1:
parts.append("negatief sentiment")
if not parts:
return "Geen duidelijk signaal"
return " · ".join(parts)