Spaces:
Sleeping
Sleeping
| import io | |
| import os | |
| import numpy as np | |
| from scipy.interpolate import PchipInterpolator | |
| import matplotlib | |
| matplotlib.use('Agg') | |
| import matplotlib.pyplot as plt | |
| import matplotlib.dates as mdates | |
| from datetime import datetime, timezone, timedelta | |
| from fastapi import FastAPI, Header, HTTPException | |
| from fastapi.responses import Response | |
| from pydantic import BaseModel | |
| from typing import List, Tuple | |
| app = FastAPI() | |
| # Set this to a strong password. You will put this same password in your bot's Render dashboard. | |
| API_SECRET = os.environ.get("API_SECRET", "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") | |
| class ChartRequest(BaseModel): | |
| symbol: str | |
| history: List[Tuple[str, float]] | |
| current_price: float | |
| def verify_request(secret: str): | |
| if secret != API_SECRET: | |
| raise HTTPException(status_code=401, detail="Unauthorized") | |
| def health_check(): | |
| return {"status": "alive"} | |
| def generate_chart(req: ChartRequest, secret: str = Header(None)): | |
| verify_request(secret) | |
| # Reconstruct the timestamps | |
| parsed_history = [] | |
| for ts_str, price in req.history: | |
| dt = datetime.fromisoformat(ts_str) | |
| parsed_history.append((dt, price)) | |
| bg_color, text_color, grid_color = '#111214', '#b5bac1', '#2b2d31' | |
| fig, ax = plt.subplots(figsize=(7, 3)) | |
| fig.patch.set_facecolor(bg_color) | |
| ax.set_facecolor(bg_color) | |
| if not parsed_history: | |
| now = datetime.now(timezone.utc) | |
| timestamps = [now - timedelta(minutes=5), now] | |
| prices = [req.current_price, req.current_price] | |
| else: | |
| timestamps = [row[0] for row in parsed_history] + [datetime.now(timezone.utc).replace(tzinfo=None)] | |
| prices = [row[1] for row in parsed_history] + [req.current_price] | |
| unique_data = {t: p for t, p in zip(timestamps, prices)} | |
| sorted_times = sorted(unique_data.keys()) | |
| sorted_prices = [unique_data[t] for t in sorted_times] | |
| color = '#00ff55' if sorted_prices[-1] >= sorted_prices[0] else '#ff2a2a' | |
| min_price, max_price = min(sorted_prices), max(sorted_prices) | |
| padding = max((max_price - min_price) * 0.1, req.current_price * 0.05) | |
| bottom_bound = max(0, min_price - padding) | |
| ax.set_ylim(bottom_bound, max_price + padding) | |
| if len(sorted_times) >= 3: | |
| date_nums = mdates.date2num(sorted_times) | |
| x_new = np.linspace(date_nums.min(), date_nums.max(), 300) | |
| spl = PchipInterpolator(date_nums, sorted_prices) | |
| y_smooth = spl(x_new) | |
| ax.plot(x_new, y_smooth, color=color, linewidth=2.5) | |
| ax.fill_between(x_new, y_smooth, bottom_bound, color=color, alpha=0.15) | |
| ax.xaxis_date() | |
| else: | |
| ax.plot(sorted_times, sorted_prices, color=color, linewidth=2.5, marker='o', markersize=4) | |
| ax.fill_between(sorted_times, sorted_prices, bottom_bound, color=color, alpha=0.15) | |
| ax.set_ylabel('Price ($)', color=text_color, fontsize=9) | |
| ax.tick_params(axis='x', colors=text_color, labelsize=8) | |
| ax.tick_params(axis='y', colors=text_color, labelsize=8) | |
| ax.grid(True, linestyle='--', color=grid_color, alpha=0.8) | |
| ax.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M')) | |
| fig.autofmt_xdate(rotation=30) | |
| for spine in ax.spines.values(): | |
| spine.set_color(grid_color) | |
| plt.title(f"{req.symbol} Price Action", color='#ffffff', fontsize=10, pad=10) | |
| fig.tight_layout() | |
| buf = io.BytesIO() | |
| plt.savefig(buf, format='png', dpi=120, bbox_inches='tight', facecolor=fig.get_facecolor()) | |
| buf.seek(0) | |
| plt.close(fig) | |
| return Response(content=buf.read(), media_type="image/png") | |