omniverse1 commited on
Commit
fc0fb8b
·
verified ·
1 Parent(s): 37ef262
Files changed (4) hide show
  1. Dockerfile +21 -0
  2. README.md +5 -4
  3. app.py +68 -130
  4. requirements.txt +2 -1
Dockerfile ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Mulai dari base image Python
2
+ FROM python:3.10-slim
3
+
4
+ # Tetapkan direktori kerja di dalam container
5
+ WORKDIR /code
6
+
7
+ # Salin file requirements terlebih dahulu untuk caching layer Docker
8
+ COPY ./requirements.txt /code/requirements.txt
9
+
10
+ # Instal dependensi
11
+ RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
12
+
13
+ # Salin semua file proyek Anda ke dalam direktori kerja
14
+ COPY . /code/
15
+
16
+ # Beri tahu Docker bahwa container berjalan di port 7860
17
+ EXPOSE 7860
18
+
19
+ # Perintah untuk menjalankan aplikasi saat container dimulai
20
+ # Kita menggunakan port 7860 agar sesuai dengan default Hugging Face
21
+ CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
README.md CHANGED
@@ -3,12 +3,13 @@ title: Smart Studio 554
3
  emoji: 🌖
4
  colorFrom: yellow
5
  colorTo: indigo
6
- sdk: gradio
7
- sdk_version: 5.49.1
8
  app_file: app.py
9
- pinned: false
 
 
10
  tags:
11
  - anycoder
12
  ---
13
 
14
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
3
  emoji: 🌖
4
  colorFrom: yellow
5
  colorTo: indigo
6
+ sdk: docker
 
7
  app_file: app.py
8
+ app_port: 7860
9
+ hardware: gpu
10
+ gpu_duration: 120
11
  tags:
12
  - anycoder
13
  ---
14
 
15
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
app.py CHANGED
@@ -1,6 +1,8 @@
1
- import gradio as gr
2
- import pandas as pd
3
  import yfinance as yf
 
 
 
4
  from utils import (
5
  calculate_technical_indicators,
6
  generate_trading_signals,
@@ -10,12 +12,16 @@ from utils import (
10
  create_technical_chart,
11
  create_prediction_chart,
12
  )
13
- import warnings
14
 
15
  warnings.filterwarnings("ignore")
16
 
 
 
 
 
 
17
 
18
- def analyze_stock(symbol, prediction_days=30):
19
  try:
20
  if not symbol.strip():
21
  raise ValueError("Please enter a valid stock symbol.")
@@ -27,7 +33,7 @@ def analyze_stock(symbol, prediction_days=30):
27
  data = stock.history(period="6mo", interval="1d")
28
 
29
  if data.empty:
30
- raise ValueError("No price data available for this stock.")
31
 
32
  indicators = calculate_technical_indicators(data)
33
  signals = generate_trading_signals(data, indicators)
@@ -52,134 +58,66 @@ def analyze_stock(symbol, prediction_days=30):
52
 
53
  except Exception as e:
54
  print(f"Error analyzing {symbol}: {e}")
55
- empty_fig = gr.Plot.update(value=None)
56
- empty_predictions = {
57
- "high_30d": 0,
58
- "low_30d": 0,
59
- "change_pct": 0,
60
- "summary": "Prediction unavailable.",
61
- }
62
- return {}, {}, {}, empty_fig, empty_fig, empty_fig, empty_predictions
63
-
64
-
65
- def update_analysis(symbol, prediction_days):
66
- (
67
- fundamental_info,
68
- indicators,
69
- signals,
70
- fig_price,
71
- fig_technical,
72
- fig_prediction,
73
- predictions,
74
- ) = analyze_stock(symbol, prediction_days)
75
-
76
- if not fundamental_info:
77
- return (
78
- "Unable to fetch stock data.",
79
- gr.Plot.update(value=None),
80
- gr.Plot.update(value=None),
81
- gr.Plot.update(value=None),
82
- )
83
-
84
- fundamentals = f"""
85
- <h4>COMPANY FUNDAMENTALS</h4>
86
- <b>Name:</b> {fundamental_info.get('name', 'N/A')} ({symbol.upper()})<br>
87
- <b>Current Price:</b> Rp{fundamental_info.get('current_price', 0):,.2f}<br>
88
- <b>Market Cap:</b> {fundamental_info.get('market_cap', 0):,}<br>
89
- <b>P/E Ratio:</b> {fundamental_info.get('pe_ratio', 0):.2f}<br>
90
- <b>Dividend Yield:</b> {fundamental_info.get('dividend_yield', 0):.2f}%<br>
91
- <b>Volume:</b> {fundamental_info.get('volume', 0):,}<br>
92
- """
93
 
94
- details_list = "".join(
95
- [f"<li>{line.strip()}</li>" for line in signals.get("details", "").split("\n") if line.strip()]
96
- )
97
-
98
- trading_signal = f"""
99
- <h4>TECHNICAL SIGNAL SUMMARY</h4>
100
- <b>Overall Trend:</b> {signals.get('overall', 'N/A')}<br>
101
- <b>Signal Strength:</b> {signals.get('strength', 0):.2f}%<br>
102
- <b>Support:</b> Rp{signals.get('support', 0):,.2f}<br>
103
- <b>Resistance:</b> Rp{signals.get('resistance', 0):,.2f}<br>
104
- <b>Stop Loss:</b> Rp{signals.get('stop_loss', 0):,.2f}<br><br>
105
- <b>Detailed Signals:</b>
106
- <ul style="margin-top: 8px; padding-left: 20px; line-height: 1.6;">
107
- {details_list}
108
- </ul>
109
- """
110
 
111
- prediction = f"""
112
- <h4>30-DAY AI FORECAST (CHRONOS-BOLT)</h4>
113
- <b>Predicted High:</b> Rp{predictions.get('high_30d', 0):,.2f}<br>
114
- <b>Predicted Low:</b> Rp{predictions.get('low_30d', 0):,.2f}<br>
115
- <b>Expected Change:</b> {predictions.get('change_pct', 0):.2f}%<br><br>
116
- <b>TP1:</b> Rp{predictions.get('tp1', 0):,.2f}<br>
117
- <b>TP2:</b> Rp{predictions.get('tp2', 0):,.2f}<br>
118
- <b>Stop Loss:</b> Rp{predictions.get('sl', 0):,.2f}<br><br>
119
- <b>Model Insight:</b><br>{predictions.get('summary', 'No analysis available')}
120
  """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
 
122
- # Karena custom CSS dihapus, kita akan menggunakan div sederhana tanpa class 'panel-box' dan 'triple-panel'
123
- # Gradio secara otomatis akan menata elemen-elemen ini dengan lebih standar
124
- return (
125
- f"""
126
- <div style="display: flex; flex-direction: row; gap: 16px;">
127
- <div style="flex: 1; min-width: 30%; border: 1px solid #ccc; padding: 10px; border-radius: 5px;">{fundamentals}</div>
128
- <div style="flex: 1; min-width: 30%; border: 1px solid #ccc; padding: 10px; border-radius: 5px;">{trading_signal}</div>
129
- <div style="flex: 1; min-width: 30%; border: 1px solid #ccc; padding: 10px; border-radius: 5px;">{prediction}</div>
130
- </div>
131
- """,
132
- fig_price,
133
- fig_technical,
134
- fig_prediction,
135
- )
136
-
137
-
138
- # --- Perubahan utama di sini ---
139
- with gr.Blocks(
140
- title="REXPRO FINANCIAL AI DASHBOARD"
141
- # Parameter theme dan css dihapus untuk kembali ke default Gradio
142
- ) as app:
143
- gr.Markdown("# REXPRO FINANCIAL AI DASHBOARD")
144
- gr.Markdown(
145
- "Comprehensive stock analytics powered by **AI forecasting and technical analysis.**"
146
- )
147
-
148
- with gr.Row():
149
- symbol = gr.Textbox(
150
- label="STOCK SYMBOL (IDX)",
151
- value="BBCA",
152
- placeholder="Example: BBCA, TLKM, ADRO, BMRI",
153
- interactive=True,
154
- )
155
- prediction_days = gr.Slider(
156
- label="FORECAST PERIOD (DAYS)",
157
- minimum=5,
158
- maximum=60,
159
- step=5,
160
- value=30,
161
- interactive=True,
162
- )
163
- analyze_button = gr.Button("RUN ANALYSIS")
164
-
165
- gr.Markdown("---")
166
- # Bagian report_section diubah agar lebih kompatibel dengan tema default,
167
- # menggunakan sedikit inline CSS untuk meniru tata letak tiga kolom dasar.
168
- report_section = gr.HTML()
169
- gr.Markdown("---")
170
-
171
- with gr.Tab("MARKET CHARTS"):
172
- with gr.Row():
173
- price_chart = gr.Plot(label="PRICE & MOVING AVERAGES")
174
- technical_chart = gr.Plot(label="TECHNICAL INDICATORS OVERVIEW")
175
- gr.Markdown("---")
176
- prediction_chart = gr.Plot(label="AI FORECAST PROJECTION")
177
-
178
- analyze_button.click(
179
- fn=update_analysis,
180
- inputs=[symbol, prediction_days],
181
- outputs=[report_section, price_chart, technical_chart, prediction_chart],
182
- )
183
 
184
  if __name__ == "__main__":
185
- app.launch(server_name="0.0.0.0", server_port=7860, ssr_mode=True)
 
 
1
+ import warnings
 
2
  import yfinance as yf
3
+ from fastapi import FastAPI, HTTPException, Query
4
+ from fastapi.responses import JSONResponse
5
+ import uvicorn
6
  from utils import (
7
  calculate_technical_indicators,
8
  generate_trading_signals,
 
12
  create_technical_chart,
13
  create_prediction_chart,
14
  )
 
15
 
16
  warnings.filterwarnings("ignore")
17
 
18
+ app = FastAPI(
19
+ title="IDX Stock Analysis API",
20
+ description="API untuk analisis teknikal, fundamental, dan prediksi AI untuk saham IDX.",
21
+ version="1.0.0"
22
+ )
23
 
24
+ def analyze_stock_logic(symbol, prediction_days=30):
25
  try:
26
  if not symbol.strip():
27
  raise ValueError("Please enter a valid stock symbol.")
 
33
  data = stock.history(period="6mo", interval="1d")
34
 
35
  if data.empty:
36
+ raise ValueError(f"No price data available for stock: {symbol}")
37
 
38
  indicators = calculate_technical_indicators(data)
39
  signals = generate_trading_signals(data, indicators)
 
58
 
59
  except Exception as e:
60
  print(f"Error analyzing {symbol}: {e}")
61
+ raise HTTPException(status_code=404, detail=str(e))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
 
64
+ @app.get("/analyze/")
65
+ async def get_stock_analysis(
66
+ symbol: str = Query(..., description="Simbol saham IDX (contoh: BBCA, TLKM)"),
67
+ prediction_days: int = Query(30, ge=5, le=60, description="Jumlah hari untuk prediksi ke depan")
68
+ ):
 
 
 
 
69
  """
70
+ Menjalankan analisis lengkap untuk simbol saham yang diberikan.
71
+ """
72
+ try:
73
+ (
74
+ fundamental_info,
75
+ indicators,
76
+ signals,
77
+ fig_price,
78
+ fig_technical,
79
+ fig_prediction,
80
+ predictions,
81
+ ) = analyze_stock_logic(symbol, prediction_days)
82
+
83
+ # Ubah figur Plotly menjadi JSON agar bisa dikirim via API
84
+ charts_json = {
85
+ "price_chart": fig_price.to_json(),
86
+ "technical_chart": fig_technical.to_json(),
87
+ "prediction_chart": fig_prediction.to_json()
88
+ }
89
+
90
+ # Hapus data 'values' yang besar dari indicators agar response tidak terlalu besar
91
+ # Klien bisa membuat chart ini dari data chart utama jika perlu
92
+ if 'rsi' in indicators:
93
+ indicators['rsi'].pop('values', None)
94
+ if 'macd' in indicators:
95
+ indicators['macd'].pop('macd_values', None)
96
+ indicators['macd'].pop('signal_values', None)
97
+ if 'bollinger' in indicators:
98
+ indicators['bollinger'].pop('upper_values', None)
99
+ indicators['bollinger'].pop('middle_values', None)
100
+ indicators['bollinger'].pop('lower_values', None)
101
+ if 'moving_averages' in indicators:
102
+ indicators['moving_averages'].pop('sma_20_values', None)
103
+ indicators['moving_averages'].pop('sma_50_values', None)
104
+
105
+
106
+ return JSONResponse(content={
107
+ "symbol": symbol.upper() + ".JK",
108
+ "fundamentals": fundamental_info,
109
+ "technical_indicators": indicators,
110
+ "trading_signals": signals,
111
+ "ai_predictions": predictions,
112
+ "charts_json": charts_json
113
+ })
114
+
115
+ except HTTPException as http_exc:
116
+ raise http_exc
117
+ except Exception as e:
118
+ raise HTTPException(status_code=500, detail=f"An internal error occurred: {str(e)}")
119
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
120
 
121
  if __name__ == "__main__":
122
+ # Gunakan port 7860 yang merupakan default untuk Hugging Face Spaces
123
+ uvicorn.run(app, host="0.0.0.0", port=7860)
requirements.txt CHANGED
@@ -1,4 +1,3 @@
1
- gradio>=4.0.0
2
  yfinance>=0.2.0
3
  pandas>=1.5.0
4
  numpy>=1.24.0
@@ -10,3 +9,5 @@ accelerate>=0.20.0
10
  tiktoken
11
  sentencepiece
12
  chronos-forecasting
 
 
 
 
1
  yfinance>=0.2.0
2
  pandas>=1.5.0
3
  numpy>=1.24.0
 
9
  tiktoken
10
  sentencepiece
11
  chronos-forecasting
12
+ fastapi>=0.100.0
13
+ uvicorn[standard]>=0.20.0