IDX-Chronos-API / app.py
omniverse1's picture
Update app.py
2e5e038 verified
raw
history blame
6.22 kB
import gradio as gr
import yfinance as yf
from utils import (
calculate_technical_indicators,
generate_trading_signals,
get_fundamental_data,
create_price_chart,
create_technical_chart,
create_prediction_chart,
predict_prices,
)
import numpy as np
def analyze_stock(symbol, mode, pred_days):
try:
stock = yf.Ticker(symbol)
data = stock.history(period="1y")
if data.empty:
msg = f"No data found for {symbol}. Check the symbol or try adding .JK (e.g., ADRO.JK)"
return (
{"name": "N/A", "current_price": 0, "market_cap": 0, "pe_ratio": 0, "dividend_yield": 0, "volume": 0},
{"overall": "N/A", "strength": 0, "support": 0, "resistance": 0, "stop_loss": 0, "details": msg},
None, None, None, None
)
indicators = calculate_technical_indicators(data)
signals = generate_trading_signals(data, indicators)
fundamentals = get_fundamental_data(stock)
fig_price = create_price_chart(data, indicators)
fig_technical = create_technical_chart(data, indicators)
if mode == "AI Prediction":
prediction = predict_prices(data, prediction_days=pred_days)
fig_prediction = create_prediction_chart(data, prediction)
return fundamentals, signals, fig_price, fig_technical, fig_prediction, prediction
else:
return fundamentals, signals, fig_price, fig_technical, None, None
except Exception as e:
msg = f"Error analyzing {symbol}: {e}"
return (
{"name": "N/A", "current_price": 0, "market_cap": 0, "pe_ratio": 0, "dividend_yield": 0, "volume": 0},
{"overall": "Error", "strength": 0, "support": 0, "resistance": 0, "stop_loss": 0, "details": msg},
None, None, None, None
)
def format_fundamental_output(f):
return f"""
<div class="card">
<h3>COMPANY FUNDAMENTALS</h3>
<p><b>Name:</b> {f['name']}</p>
<p><b>Current Price:</b> Rp{f['current_price']:,.2f}</p>
<p><b>Market Cap:</b> {f['market_cap']:,}</p>
<p><b>P/E Ratio:</b> {f['pe_ratio']:.2f}</p>
<p><b>Dividend Yield:</b> {f['dividend_yield']:.2f}%</p>
<p><b>Volume:</b> {f['volume']:,}</p>
</div>
"""
def format_signal_output(s):
details = s.get("details", "")
detail_list = details.split("\n") if details else []
formatted_details = "<ul>" + "".join(f"<li>{d}</li>" for d in detail_list) + "</ul>"
return f"""
<div class="card">
<h3>TECHNICAL SIGNAL SUMMARY</h3>
<p><b>Overall Trend:</b> {s.get('overall','N/A')}</p>
<p><b>Signal Strength:</b> {s.get('strength',0):.2f}%</p>
<p><b>Support:</b> Rp{s.get('support',0):,.2f}</p>
<p><b>Resistance:</b> Rp{s.get('resistance',0):,.2f}</p>
<p><b>Stop Loss:</b> Rp{s.get('stop_loss',0):,.2f}</p>
<h4>Detailed Signals:</h4>
{formatted_details}
</div>
"""
def format_ai_output(p):
if p is None or not isinstance(p, dict) or "values" not in p or len(p["values"]) == 0:
return """
<div class="card">
<h3>30-DAY AI FORECAST (CHRONOS-BOLT)</h3>
<p>No AI prediction data available.</p>
</div>
"""
tp1 = p["mean_30d"] * 0.97
tp2 = p["mean_30d"] * 1.02
sl = p["low_30d"] * 0.95
return f"""
<div class="card">
<h3>30-DAY AI FORECAST (CHRONOS-BOLT)</h3>
<p><b>Predicted High:</b> Rp{p['high_30d']:,.2f}</p>
<p><b>Predicted Low:</b> Rp{p['low_30d']:,.2f}</p>
<p><b>Expected Change:</b> {p['change_pct']:.2f}%</p>
<p><b>TP1:</b> Rp{tp1:,.2f}</p>
<p><b>TP2:</b> Rp{tp2:,.2f}</p>
<p><b>Stop Loss:</b> Rp{sl:,.2f}</p>
<h4>Model Insight:</h4>
<p style="font-size:13px;line-height:1.4;">{p['summary']}</p>
</div>
"""
with gr.Blocks(css="""
body { font-family: 'Inter', sans-serif; background-color: #f9fafc; color: #222; }
.gradio-container { max-width: 1300px; margin: auto; }
h1 { text-align:center; color:#003366; margin-bottom:20px; }
h3 { color:#003366; margin-bottom:8px; }
.card {
background: #ffffff;
border-radius: 10px;
box-shadow: 0 2px 4px rgba(0,0,0,0.08);
padding: 18px;
margin: 5px;
flex: 1;
min-width: 0;
}
.row-flex {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
gap: 10px;
}
ul { margin: 6px 0 0 20px; padding: 0; }
li { margin-bottom: 4px; font-size: 14px; }
""") as demo:
gr.HTML("<h1>STOCK ANALYSIS DASHBOARD</h1>")
with gr.Row():
stock_input = gr.Textbox(label="Enter Stock Symbol (e.g. BBCA.JK, ADRO.JK)", placeholder="Type stock symbol...")
mode_input = gr.Radio(["Technical Analysis", "AI Prediction"], label="Select Analysis Mode", value="Technical Analysis")
pred_days_input = gr.Slider(7, 60, value=30, step=1, label="Prediction Days (AI only)")
analyze_button = gr.Button("Analyze", variant="primary")
gr.HTML("<hr style='margin:20px 0;'>")
with gr.Row(elem_classes="row-flex"):
fundamentals_output = gr.HTML()
signal_output = gr.HTML()
ai_output = gr.HTML()
gr.HTML("<hr style='margin:20px 0;'>")
with gr.Row():
chart_price = gr.Plot(label="Price Chart")
chart_technical = gr.Plot(label="Technical Chart")
chart_prediction = gr.Plot(label="AI Prediction Chart")
def run_analysis(symbol, mode, pred_days):
fundamentals, signals, fig_price, fig_technical, fig_prediction, prediction = analyze_stock(symbol, mode, pred_days)
return (
format_fundamental_output(fundamentals),
format_signal_output(signals),
format_ai_output(prediction) if mode == "AI Prediction" else "",
fig_price,
fig_technical,
fig_prediction if mode == "AI Prediction" else None,
)
analyze_button.click(
fn=run_analysis,
inputs=[stock_input, mode_input, pred_days_input],
outputs=[fundamentals_output, signal_output, ai_output, chart_price, chart_technical, chart_prediction]
)
demo.launch(server_name="0.0.0.0", server_port=7860)