File size: 4,046 Bytes
5aa1b59
e55ebde
 
 
 
5aa1b59
c1d305f
 
 
5aa1b59
b9d9d1e
 
e55ebde
b9d9d1e
 
 
e55ebde
 
b9d9d1e
 
 
 
e55ebde
 
f2b5dca
a6ab183
f2b5dca
a6ab183
 
f2b5dca
a6ab183
 
 
 
49d9149
e55ebde
 
49d9149
a6ab183
f2b5dca
 
e55ebde
 
b9d9d1e
e55ebde
c1d305f
49d9149
 
 
 
 
 
c1d305f
 
7897e32
49d9149
e55ebde
49d9149
 
 
c1d305f
b9d9d1e
e55ebde
c1d305f
 
49d9149
 
 
 
 
c1d305f
49d9149
c1d305f
 
7897e32
49d9149
e55ebde
49d9149
 
 
c1d305f
b9d9d1e
e55ebde
c1d305f
49d9149
 
 
 
 
 
 
 
 
 
c1d305f
 
7897e32
49d9149
e55ebde
49d9149
 
 
c1d305f
b9d9d1e
e55ebde
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
"""
Crypto Dashboard — Plotly Edition (clean layout)
• убраны colorbar заголовки (percent_change_*)
• уменьшены отступы KPI
• без глобального Markdown-заголовка
"""
import requests
import pandas as pd
import plotly.express as px
from services.llm_client import llm_service


def fetch_coinlore_data(limit=100):
    url = "https://api.coinlore.net/api/tickers/"
    data = requests.get(url).json()["data"]
    df = pd.DataFrame(data)
    for col in ["price_usd", "market_cap_usd", "volume24",
                "percent_change_1h", "percent_change_24h", "percent_change_7d"]:
        df[col] = pd.to_numeric(df[col], errors="coerce")
    return df.head(limit)


def _kpi_line(df) -> str:
    """Формирует компактную KPI-строку без лишних пробелов"""
    tracked = ["BTC", "ETH", "SOL", "DOGE"]
    parts = []
    for sym in tracked:
        row = df[df["symbol"] == sym]
        if row.empty:
            continue
        price = float(row["price_usd"])
        ch = float(row["percent_change_24h"])
        arrow = "↑" if ch > 0 else "↓"
        color = "#4ade80" if ch > 0 else "#f87171"
        parts.append(
            f"<b>{sym}</b> ${price:,.0f} "
            f"<span style='color:{color}'>{arrow} {abs(ch):.2f}%</span>"
        )
    return " , ".join(parts)


def build_crypto_dashboard(top_n=50):
    df = fetch_coinlore_data(top_n)

    # === Treemap ===
    fig_treemap = px.treemap(
        df,
        path=["symbol"],
        values="market_cap_usd",
        color="percent_change_24h",
        color_continuous_scale="RdYlGn",
        height=420,
    )
    fig_treemap.update_layout(
        title=None,
        template="plotly_dark",
        coloraxis_colorbar=dict(title=None),  # 🔹 убираем надпись percent_change_24h
        margin=dict(l=5, r=5, t=5, b=5),
        paper_bgcolor="rgba(0,0,0,0)",
        plot_bgcolor="rgba(0,0,0,0)",
    )

    # === Bar chart (Top gainers) ===
    top = df.sort_values("percent_change_24h", ascending=False).head(12)
    fig_bar = px.bar(
        top,
        x="percent_change_24h",
        y="symbol",
        orientation="h",
        color="percent_change_24h",
        color_continuous_scale="Blues",
        height=320,
    )
    fig_bar.update_layout(
        title=None,
        template="plotly_dark",
        coloraxis_colorbar=dict(title=None),  # 🔹 убираем надпись percent_change_24h
        margin=dict(l=40, r=10, t=5, b=18),
        paper_bgcolor="rgba(0,0,0,0)",
        plot_bgcolor="rgba(0,0,0,0)",
    )

    # === Scatter (Market Cap vs Volume) ===
    fig_bubble = px.scatter(
        df.head(60),
        x="market_cap_usd",
        y="volume24",
        size="price_usd",
        color="percent_change_7d",
        hover_name="symbol",
        log_x=True,
        log_y=True,
        color_continuous_scale="RdYlGn",
        height=320,
    )
    fig_bubble.update_layout(
        title=None,
        template="plotly_dark",
        coloraxis_colorbar=dict(title=None),  # 🔹 убираем надпись percent_change_7d
        margin=dict(l=36, r=10, t=5, b=18),
        paper_bgcolor="rgba(0,0,0,0)",
        plot_bgcolor="rgba(0,0,0,0)",
    )

    # === LLM summary ===
    summary = _ai_summary(df)
    kpi_text = _kpi_line(df)
    return fig_treemap, fig_bar, fig_bubble, summary, kpi_text


def _ai_summary(df):
    leaders = df.sort_values("percent_change_24h", ascending=False).head(3)["symbol"].tolist()
    laggards = df.sort_values("percent_change_24h").head(3)["symbol"].tolist()
    prompt = f"""
Summarize today's crypto market based on Coinlore data.
Top gainers: {', '.join(leaders)}.
Top losers: {', '.join(laggards)}.
Include: overall sentiment, volatility/liquidity, short-term outlook.
"""
    text = ""
    for delta in llm_service.stream_chat(
        messages=[{"role": "user", "content": prompt}],
        model="meta-llama/Meta-Llama-3.1-8B-Instruct",
    ):
        text += delta
    return text