Spaces:
Sleeping
Sleeping
| import logging | |
| from fastapi import FastAPI | |
| from pydantic import BaseModel | |
| import pandas as pd | |
| import numpy as np | |
| import yfinance as yf | |
| from datetime import datetime, timedelta | |
| # Konfigurasi logging | |
| logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') | |
| app = FastAPI( | |
| title="Model B β EMA & Dynamic Scaling API", | |
| description="API untuk menghitung EMA, normalisasi, dan analisis tren otomatis berdasarkan data yfinance", | |
| version="2.1" | |
| ) | |
| PAIR = "EURUSD=X" | |
| BASE_WINDOW = 60 | |
| class DateRange(BaseModel): | |
| start_date: str | |
| end_date: str | |
| def ema_manual(prices, span): | |
| ema = [np.nan] * len(prices) | |
| alpha = 2 / (span + 1) | |
| for i in range(len(prices)): | |
| if i < span - 1: | |
| ema[i] = np.nan | |
| elif i == span - 1: | |
| ema[i] = np.mean(prices[:span]) | |
| else: | |
| ema[i] = alpha * prices[i] + (1 - alpha) * ema[i - 1] | |
| return ema | |
| def get_dynamic_minmax(): | |
| today = datetime.now().date() | |
| start = today - timedelta(days=BASE_WINDOW) | |
| logging.info(f"Mengunduh data untuk min/max: start={start}, end={today + timedelta(days=1)}") | |
| df = yf.download(PAIR, start=start, end=today + timedelta(days=1), auto_adjust=True) | |
| if df.empty: | |
| logging.error("Gagal mengambil data harga terbaru untuk min/max.") | |
| raise ValueError("Gagal mengambil data harga terbaru.") | |
| close_min = df["Close"].min() | |
| close_max = df["Close"].max() | |
| logging.info(f"Min/Max Close: {close_min}/{close_max}") | |
| return close_min, close_max | |
| def normalize_close(value, close_min, close_max): | |
| if close_max == close_min: # Hindari pembagian dengan nol | |
| return 1.0 # Atau nilai default lain yang masuk akal | |
| return (value - close_min) / (close_max - close_min) | |
| def analyze_trend(latest_row): | |
| ema20 = latest_row["EMA20"] | |
| ema50 = latest_row["EMA50"] | |
| close = latest_row["close"] | |
| if ema20 > ema50: | |
| trend = "bullish" | |
| elif ema20 < ema50: | |
| trend = "bearish" | |
| else: | |
| trend = "neutral" | |
| diff = abs(ema20 - ema50) / ema50 * 100 | |
| if diff > 0.3: | |
| strength = "strong" | |
| elif diff > 0.1: | |
| strength = "moderate" | |
| else: | |
| strength = "weak" | |
| if close > ema20 and close > ema50: | |
| price_position = "above both EMA β possible continuation" | |
| elif close < ema20 and close < ema50: | |
| price_position = "below both EMA β possible correction" | |
| else: | |
| price_position = "between EMAs β indecision zone" | |
| return { | |
| "trend": trend, | |
| "strength": strength, | |
| "price_position": price_position, | |
| "ema_gap_percent": round(diff, 3) | |
| } | |
| def analyze_ema(input_data: DateRange): | |
| try: | |
| logging.info(f"Menerima permintaan /analyze dengan start_date={input_data.start_date}, end_date={input_data.end_date}") | |
| start_date = pd.to_datetime(input_data.start_date) | |
| end_date = pd.to_datetime(input_data.end_date) | |
| extended_start = start_date - timedelta(days=60) | |
| logging.info(f"Mengunduh data dari yfinance: start={extended_start}, end={end_date + timedelta(days=1)}") | |
| df = yf.download(PAIR, start=extended_start, end=end_date + timedelta(days=1), auto_adjust=True) | |
| if df.empty: | |
| logging.warning("Data tidak ditemukan untuk rentang tanggal yang diperluas.") | |
| return {"status": "error", "message": "Data tidak ditemukan untuk rentang tanggal tersebut"} | |
| df = df.reset_index()[["Date", "Close"]] | |
| df.rename(columns={"Date": "date", "Close": "close"}, inplace=True) | |
| if len(df) < 50: | |
| logging.warning(f"Data terlalu sedikit ({len(df)} hari) untuk EMA50.") | |
| return {"status": "error", "message": f"Data terlalu sedikit ({len(df)} hari). Butuh minimal 50 hari untuk EMA50."} | |
| df["EMA20"] = ema_manual(df["close"], 20) | |
| df["EMA50"] = ema_manual(df["close"], 50) | |
| df = df.dropna().reset_index(drop=True) | |
| close_min, close_max = get_dynamic_minmax() | |
| df["norm_close"] = df["close"].apply(lambda x: normalize_close(x, close_min, close_max)) | |
| chart_data = { | |
| "dates": df["date"].dt.strftime("%Y-%m-%d").tolist(), | |
| "close": df["close"].round(6).tolist(), | |
| "EMA20": df["EMA20"].round(6).tolist(), | |
| "EMA50": df["EMA50"].round(6).tolist(), | |
| "norm_close": df["norm_close"].round(6).tolist(), | |
| "min_close": float(close_min), | |
| "max_close": float(close_max), | |
| } | |
| logging.info(f"Analisis /analyze berhasil, {len(df)} data point.") | |
| return { | |
| "status": "ok", | |
| "pair": PAIR, | |
| "start_date": str(start_date.date()), | |
| "end_date": str(end_date.date()), | |
| "data_points": len(df), | |
| "chart_data": chart_data | |
| } | |
| except Exception as e: | |
| logging.error(f"Error di /analyze: {e}", exc_info=True) # exc_info=True akan mencetak traceback | |
| return {"status": "error", "message": str(e)} | |
| def ema_summary(input_data: DateRange): | |
| try: | |
| logging.info(f"Menerima permintaan /summary dengan start_date={input_data.start_date}, end_date={input_data.end_date}") | |
| start_date = pd.to_datetime(input_data.start_date) | |
| end_date = pd.to_datetime(input_data.end_date) | |
| extended_start = start_date - timedelta(days=60) | |
| df = yf.download(PAIR, start=extended_start, end=end_date + timedelta(days=1), auto_adjust=True) | |
| if df.empty: | |
| logging.warning("Data tidak ditemukan untuk rentang tanggal yang diperluas.") | |
| return {"status": "error", "message": "Data tidak ditemukan"} | |
| df = df.reset_index()[["Date", "Close"]] | |
| df.rename(columns={"Date": "date", "Close": "close"}, inplace=True) | |
| if len(df) < 50: | |
| logging.warning(f"Data terlalu sedikit ({len(df)} hari) untuk EMA50.") | |
| return {"status": "error", "message": f"Data terlalu sedikit ({len(df)} hari). Butuh minimal 50 hari untuk EMA50."} | |
| df["EMA20"] = ema_manual(df["close"], 20) | |
| df["EMA50"] = ema_manual(df["close"], 50) | |
| df = df.dropna().reset_index(drop=True) | |
| latest = df.iloc[-1] | |
| analysis = analyze_trend(latest) | |
| logging.info(f"Analisis /summary berhasil, tanggal terakhir: {latest['date'].strftime('%Y-%m-%d')}") | |
| return { | |
| "status": "ok", | |
| "pair": PAIR, | |
| "as_of_date": latest["date"].strftime("%Y-%m-%d"), | |
| "close": round(float(latest["close"]), 6), | |
| "EMA20": round(float(latest["EMA20"]), 6), | |
| "EMA50": round(float(latest["EMA50"]), 6), | |
| "trend_analysis": analysis | |
| } | |
| except Exception as e: | |
| logging.error(f"Error di /summary: {e}", exc_info=True) | |
| return {"status": "error", "message": str(e)} |