import streamlit as st import logging from logging.handlers import RotatingFileHandler import os import json from config import ( BINANCE_API_KEY, BINANCE_SECRET, CRYPTOPANIC_API_KEY, NEWS_API_KEY, TWITTER_BEARER_TOKEN, REDDIT_CLIENT_ID, REDDIT_SECRET, REDDIT_USER_AGENT, ANTHROPIC_API_KEY, DB_PATH, LOG_DIR, TOP_N_COINS, INITIAL_CAPITAL, ANALYSIS_INTERVAL_MINUTES, DEFAULT_WEIGHTS, SURGE_VOLUME_MULTIPLIER, ) from data.db import Database from data.binance_rest import BinanceRestClient from data.binance_ws import BinanceWSClient from data.news_client import NewsClient from data.social_client import SocialClient from agents.ai_analyst import AIAgent from engine.signal import SignalEngine from engine.surge import SurgeDetector from engine.portfolio import PortfolioManager from scheduler.jobs import AnalysisJob, create_scheduler from dashboard.sidebar import render_sidebar from dashboard.detail import render_detail from dashboard.portfolio_view import render_portfolio os.makedirs(LOG_DIR, exist_ok=True) logging.basicConfig( level=logging.INFO, format="%(asctime)s [%(levelname)s] %(name)s: %(message)s", handlers=[ RotatingFileHandler(os.path.join(LOG_DIR, "app.log"), maxBytes=10_000_000, backupCount=5), logging.StreamHandler(), ], ) logger = logging.getLogger(__name__) st.set_page_config(page_title="Crypto Signal Dashboard", layout="wide", page_icon="📊") if "initialized" not in st.session_state: st.session_state.initialized = False st.session_state.selected_coin = None @st.cache_resource def init_services(): db = Database(DB_PATH) db.init() binance_rest = BinanceRestClient(BINANCE_API_KEY, BINANCE_SECRET) binance_ws = BinanceWSClient(BINANCE_API_KEY, BINANCE_SECRET) news_client = NewsClient(CRYPTOPANIC_API_KEY, NEWS_API_KEY) social_client = SocialClient(REDDIT_CLIENT_ID, REDDIT_SECRET, REDDIT_USER_AGENT, TWITTER_BEARER_TOKEN) ai_agent = AIAgent(ANTHROPIC_API_KEY) signal_engine = SignalEngine() surge_detector = SurgeDetector(SURGE_VOLUME_MULTIPLIER) portfolio = PortfolioManager(INITIAL_CAPITAL) saved_weights = db.load_setting("weights") if saved_weights: try: signal_engine.set_weights(json.loads(saved_weights)) except (json.JSONDecodeError, ValueError): pass try: monitored = binance_rest.get_top_coins(TOP_N_COINS) except Exception: monitored = ["BTCUSDT", "ETHUSDT", "BNBUSDT", "SOLUSDT", "XRPUSDT"] logger.warning("Failed to fetch top coins, using defaults") job = AnalysisJob( binance=binance_rest, news_client=news_client, social_client=social_client, ai_agent=ai_agent, signal_engine=signal_engine, surge_detector=surge_detector, portfolio=portfolio, db=db, monitored_coins=monitored, ) scheduler = create_scheduler(job, ANALYSIS_INTERVAL_MINUTES) scheduler.start() try: binance_ws.start(monitored[:20]) except Exception as e: logger.warning(f"WebSocket start failed: {e}") job.run_analysis() return { "db": db, "binance_rest": binance_rest, "binance_ws": binance_ws, "news_client": news_client, "social_client": social_client, "signal_engine": signal_engine, "portfolio": portfolio, "job": job, "monitored": monitored, } def main(): services = init_services() db = services["db"] job = services["job"] portfolio = services["portfolio"] binance_rest = services["binance_rest"] binance_ws = services["binance_ws"] news_client = services["news_client"] social_client = services["social_client"] current_prices = binance_ws.get_all_prices() if not current_prices: try: current_prices = binance_rest.get_all_prices() except Exception: current_prices = {} page = render_sidebar(job.latest_results, db) if page == "Signals": selected = st.session_state.get("selected_coin") if selected and selected in job.latest_results: coin_data = job.latest_results[selected] try: ohlcv = binance_rest.get_ohlcv(selected, interval="1h", limit=100) except Exception: ohlcv = None coin_news = news_client.filter_by_coin(news_client._cache, selected) coin_posts = social_client.filter_posts_by_coin(social_client._cache, selected) sentiment = social_client.simple_sentiment(coin_posts) ai_summary = job.ai_summaries.get(selected, "AI analysis not yet available.") render_detail(selected, coin_data, ohlcv, coin_news, sentiment, ai_summary) else: st.title("Crypto Signal Dashboard") st.info("Select a coin from the sidebar to view detailed analysis.") if job.latest_results: _render_overview(job.latest_results) elif page == "Portfolio": render_portfolio(portfolio, current_prices, db) def _render_overview(results: dict): import pandas as pd st.subheader("Signal Overview") buy_coins = [r for r in results.values() if r["signal"] == "BUY"] sell_coins = [r for r in results.values() if r["signal"] == "SELL"] col1, col2 = st.columns(2) with col1: st.markdown("### :green[BUY Signals]") if buy_coins: rows = [{"Coin": c["symbol"].replace("USDT",""), "Score": f"{c['composite']:.0f}", "Tech": f"{c['technical']:.0f}", "News": f"{c['news']:.0f}"} for c in sorted(buy_coins, key=lambda x: x["composite"], reverse=True)] st.dataframe(pd.DataFrame(rows), use_container_width=True, hide_index=True) else: st.info("No BUY signals currently") with col2: st.markdown("### :red[SELL Signals]") if sell_coins: rows = [{"Coin": c["symbol"].replace("USDT",""), "Score": f"{c['composite']:.0f}", "Tech": f"{c['technical']:.0f}", "News": f"{c['news']:.0f}"} for c in sorted(sell_coins, key=lambda x: x["composite"])] st.dataframe(pd.DataFrame(rows), use_container_width=True, hide_index=True) else: st.info("No SELL signals currently") if __name__ == "__main__": main()