162 lines
6.2 KiB
Python
162 lines
6.2 KiB
Python
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()
|