DeryFerd commited on
Commit
87ee345
·
verified ·
1 Parent(s): 0f86824

Upload 3 files

Browse files
Files changed (3) hide show
  1. Dockerfile.txt +16 -0
  2. app.py +129 -0
  3. requirements.txt +6 -0
Dockerfile.txt ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Gunakan image Python versi slim sebagai dasar
2
+ FROM python:3.9-slim
3
+
4
+ # Set direktori kerja di dalam container
5
+ WORKDIR /code
6
+
7
+ # Salin file requirements dulu dan install dependensi
8
+ COPY ./requirements.txt /code/requirements.txt
9
+ RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
10
+
11
+ # Salin semua sisa file proyek ke dalam container
12
+ COPY . /code
13
+
14
+ # Perintah untuk menjalankan aplikasi saat container dimulai
15
+ # Hugging Face menggunakan port 7860 secara default
16
+ CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"]
app.py ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # File: main.py
2
+
3
+ # 1. IMPORT LIBRARY
4
+ from fastapi import FastAPI
5
+ from pydantic import BaseModel
6
+ from typing import List
7
+ from datetime import datetime
8
+ import yfinance as yf
9
+ import pandas as pd
10
+ import numpy as np
11
+ from prophet import Prophet
12
+ from fastapi.middleware.cors import CORSMiddleware
13
+
14
+ # 2. INISIALISASI APLIKASI FASTAPI
15
+ app = FastAPI(
16
+ title="Stock Risk & Forecast API",
17
+ description="API untuk analisis risiko portfolio dan peramalan harga saham."
18
+ )
19
+
20
+ # --- REVISI 1: Tambahkan Middleware CORS ---
21
+ origins = [
22
+ "http://localhost:8080",
23
+ "http://127.0.0.1:8080",
24
+ # PASTE URL FORWARDED DARI FRONTEND ANDA DI SINI
25
+ "https://psychic-goldfish-jvvvxjrwgp9hg56-8080.app.github.dev",
26
+ ]
27
+
28
+ app.add_middleware(
29
+ CORSMiddleware,
30
+ allow_origins=origins,
31
+ allow_credentials=True,
32
+ allow_methods=["*"], # Izinkan semua method (GET, POST, dll)
33
+ allow_headers=["*"], # Izinkan semua header
34
+ )
35
+ # --- AKHIR REVISI 1 ---
36
+
37
+ # 3. KUMPULKAN FUNGSI INTI (Versi Terbaru yang Sudah Diperbaiki)
38
+ def get_stock_data(tickers: List[str], start_date: str, end_date: str):
39
+ try:
40
+ # Langsung ambil data 'Close'. yfinance akan mengembalikan DataFrame
41
+ # baik untuk satu maupun banyak ticker.
42
+ stock_data = yf.download(tickers, start=start_date, end=end_date)['Close']
43
+
44
+ # Blok 'if len(tickers) == 1:' yang lama sudah tidak diperlukan dan dihapus.
45
+ # Jika yfinance hanya mengembalikan satu Series (kasus langka),
46
+ # baris berikutnya akan mengubahnya jadi DataFrame secara otomatis.
47
+ if isinstance(stock_data, pd.Series):
48
+ stock_data = stock_data.to_frame(name=tickers[0])
49
+
50
+ return stock_data.dropna()
51
+ except Exception as e:
52
+ print(f"!!! TERJADI ERROR DI get_stock_data: {e}, Tickers: {tickers}")
53
+ return None
54
+
55
+ def calculate_risk_metrics(price_data: pd.DataFrame):
56
+ """Menghitung metrik risiko untuk portfolio dengan ASUMSI BOBOT SAMA."""
57
+ daily_returns = price_data.pct_change().dropna()
58
+ num_assets = len(price_data.columns)
59
+ weights = np.array([1/num_assets] * num_assets)
60
+ portfolio_returns = daily_returns.dot(weights)
61
+
62
+ expected_return = portfolio_returns.mean() * 252
63
+ volatility = portfolio_returns.std() * np.sqrt(252)
64
+ sharpe_ratio = expected_return / volatility if volatility != 0 else 0
65
+
66
+ return {
67
+ "expected_annual_return": expected_return,
68
+ "annual_volatility": volatility,
69
+ "sharpe_ratio": sharpe_ratio
70
+ }
71
+
72
+ def get_forecast(ticker: str, price_data: pd.DataFrame, forecast_horizon: int):
73
+ """Membuat prediksi dan mengembalikan DataFrame forecast DAN modelnya."""
74
+ df_prophet = price_data[[ticker]].reset_index()
75
+ df_prophet.columns = ['ds', 'y']
76
+
77
+ model = Prophet(daily_seasonality=False, weekly_seasonality=True, yearly_seasonality=True)
78
+ model.fit(df_prophet)
79
+
80
+ future = model.make_future_dataframe(periods=forecast_horizon)
81
+ forecast = model.predict(future)
82
+
83
+ # Mengubah format tanggal agar bisa dikirim sebagai JSON
84
+ forecast['ds'] = forecast['ds'].dt.strftime('%Y-%m-%d')
85
+
86
+ # Mengembalikan DUA nilai, sesuai revisi Anda
87
+ return forecast, model
88
+
89
+ # 4. DEFINISIKAN MODEL REQUEST (INPUT DARI USER) - Tidak ada perubahan
90
+ class RiskRequest(BaseModel):
91
+ tickers: List[str] = ['BBCA.JK', 'TLKM.JK']
92
+ start_date: str = '2020-01-01'
93
+ end_date: str = datetime.now().strftime('%Y-%m-%d')
94
+
95
+ class ForecastRequest(BaseModel):
96
+ ticker: str = 'BBCA.JK'
97
+ start_date: str = '2020-01-01'
98
+ end_date: str = datetime.now().strftime('%Y-%m-%d')
99
+ forecast_horizon: int = 30
100
+
101
+
102
+
103
+ # 5. BUAT ENDPOINT API (Dengan Penyesuaian)
104
+ @app.post("/calculate-risk")
105
+ def calculate_risk_endpoint(request: RiskRequest):
106
+ price_data = get_stock_data(request.tickers, request.start_date, request.end_date)
107
+ if price_data is None or price_data.empty:
108
+ return {"error": "Tidak dapat mengambil data saham. Periksa kembali ticker."}
109
+
110
+ risk_metrics = calculate_risk_metrics(price_data)
111
+ return risk_metrics
112
+
113
+ @app.post("/get-forecast")
114
+ def get_forecast_endpoint(request: ForecastRequest):
115
+ price_data = get_stock_data([request.ticker], request.start_date, request.end_date)
116
+ if price_data is None or price_data.empty:
117
+ return {"error": "Tidak dapat mengambil data saham. Periksa kembali ticker."}
118
+
119
+ # --- INI BAGIAN YANG BERUBAH ---
120
+ # Kita panggil fungsi yang mengembalikan 2 nilai
121
+ # Tapi kita hanya butuh yang pertama (forecast_data) untuk dikirim ke frontend
122
+ # _ (underscore) adalah konvensi di Python untuk variabel yang nilainya kita abaikan
123
+ forecast_data, _ = get_forecast(request.ticker, price_data, request.forecast_horizon)
124
+
125
+ # Kita hanya mengirim data forecast yang sudah bersih
126
+ final_forecast_data = forecast_data[['ds', 'yhat', 'yhat_lower', 'yhat_upper']]
127
+
128
+ # Konversi DataFrame ke format JSON yang bisa dikirim
129
+ return final_forecast_data.to_dict('records')
requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ fastapi
2
+ uvicorn[standard]
3
+ yfinance
4
+ prophet
5
+ pandas
6
+ numpy