from __future__ import annotations import base64 import io import logging from .candle_rows import rows_to_ohl LOGGER = logging.getLogger("onchain_scout.chart_candles") def daily_candles_png_base64(rows_1d: list[list[str]], symbol: str, max_bars: int = 48) -> str | None: """ 生成简易日线蜡烛图 PNG(base64,无 data URL 前缀),供 Ollama 多模态。 若 matplotlib 不可用或失败则返回 None。 """ try: import matplotlib matplotlib.use("Agg") import matplotlib.pyplot as plt from matplotlib.patches import Rectangle except ImportError: LOGGER.warning("matplotlib not installed, skip chart image") return None o, h, l, c = rows_to_ohl(rows_1d) n = len(c) if n < 5: return None start = max(0, n - max_bars) o, h, l, c = o[start:], h[start:], l[start:], c[start:] x = list(range(len(c))) fig, ax = plt.subplots(figsize=(7, 3), facecolor="#030308") ax.set_facecolor("#050510") for i in x: up = c[i] >= o[i] col = "#00f5d4" if up else "#ff006e" ax.plot([i, i], [l[i], h[i]], color=col, linewidth=0.9, alpha=0.9) body_low = min(o[i], c[i]) body_h = abs(c[i] - o[i]) if body_h < 1e-12: body_h = (h[i] - l[i]) * 0.08 or 1e-8 ax.add_patch( Rectangle( (i - 0.35, body_low), 0.7, body_h, facecolor=col, edgecolor=col, linewidth=0.4, alpha=0.85, ) ) ax.set_title(f"{symbol} 1D", color="#00fff7", fontsize=11, fontfamily="monospace") ax.tick_params(colors="#7dffb3", labelsize=7) for spine in ax.spines.values(): spine.set_color("#1b3d2f") ax.grid(True, alpha=0.12, color="#00fff7") plt.tight_layout() buf = io.BytesIO() fig.savefig(buf, format="png", dpi=100, facecolor=fig.get_facecolor()) plt.close(fig) buf.seek(0) return base64.b64encode(buf.read()).decode("ascii")