67 lines
2.0 KiB
Python
67 lines
2.0 KiB
Python
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")
|