IDX-Chronos / app.py
omniverse1's picture
Update Gradio app with multiple files
8dcd0a1 verified
raw
history blame
15.3 kB
import gradio as gr
import pandas as pd
import yfinance as yf
from utils import (
calculate_technical_indicators,
generate_trading_signals,
get_fundamental_data,
predict_prices,
create_price_chart,
create_technical_chart,
create_prediction_chart,
)
import warnings
warnings.filterwarnings("ignore")
def analyze_stock(symbol, prediction_days=30):
try:
if not symbol.strip():
raise ValueError("Please enter a valid stock symbol.")
if not symbol.endswith(".JK"):
symbol = symbol.upper() + ".JK"
stock = yf.Ticker(symbol)
data = stock.history(period="6mo", interval="1d")
if data.empty:
raise ValueError("No price data available for this stock.")
indicators = calculate_technical_indicators(data)
signals = generate_trading_signals(data, indicators)
fundamental_info = get_fundamental_data(stock)
predictions = predict_prices(data, prediction_days=prediction_days)
fig_price = create_price_chart(data, indicators)
fig_technical = create_technical_chart(data, indicators)
fig_prediction = create_prediction_chart(data, predictions)
# kalkulasi TP1, TP2, SL
last_price = data['Close'].iloc[-1]
tp1 = last_price * (1 + (predictions.get("change_pct", 0) / 200))
tp2 = last_price * (1 + (predictions.get("change_pct", 0) / 100))
sl = last_price * 0.95
predictions["tp1"] = tp1
predictions["tp2"] = tp2
predictions["sl"] = sl
return fundamental_info, indicators, signals, fig_price, fig_technical, fig_prediction, predictions
except Exception as e:
print(f"Error analyzing {symbol}: {e}")
empty_fig = gr.Plot.update(value=None)
empty_predictions = {
"high_30d": 0,
"low_30d": 0,
"change_pct": 0,
"summary": "Prediction unavailable.",
}
return {}, {}, {}, empty_fig, empty_fig, empty_fig, empty_predictions
def update_analysis(symbol, prediction_days):
(
fundamental_info,
indicators,
signals,
fig_price,
fig_technical,
fig_prediction,
predictions,
) = analyze_stock(symbol, prediction_days)
if not fundamental_info:
return (
"Unable to fetch stock data.",
gr.Plot.update(value=None),
gr.Plot.update(value=None),
gr.Plot.update(value=None),
)
fundamentals = f"""
<h4>COMPANY FUNDAMENTALS</h4>
<b>Name:</b> {fundamental_info.get('name', 'N/A')} ({symbol.upper()})<br>
<b>Current Price:</b> Rp{fundamental_info.get('current_price', 0):,.2f}<br>
<b>Market Cap:</b> {fundamental_info.get('market_cap', 0):,}<br>
<b>P/E Ratio:</b> {fundamental_info.get('pe_ratio', 0):.2f}<br>
<b>Dividend Yield:</b> {fundamental_info.get('dividend_yield', 0):.2f}%<br>
<b>Volume:</b> {fundamental_info.get('volume', 0):,}<br>
"""
details_list = "".join(
[f"<li>{line.strip()}</li>" for line in signals.get("details", "").split("\n") if line.strip()]
)
trading_signal = f"""
<h4>TECHNICAL SIGNAL SUMMARY</h4>
<b>Overall Trend:</b> {signals.get('overall', 'N/A')}<br>
<b>Signal Strength:</b> {signals.get('strength', 0):.2f}%<br>
<b>Support:</b> Rp{signals.get('support', 0):,.2f}<br>
<b>Resistance:</b> Rp{signals.get('resistance', 0):,.2f}<br>
<b>Stop Loss:</b> Rp{signals.get('stop_loss', 0):,.2f}<br><br>
<b>Detailed Signals:</b>
<ul style="margin-top: 8px; padding-left: 20px; line-height: 1.6;">
{details_list}
</ul>
"""
prediction = f"""
<h4>30-DAY AI FORECAST (CHRONOS-BOLT)</h4>
<b>Predicted High:</b> Rp{predictions.get('high_30d', 0):,.2f}<br>
<b>Predicted Low:</b> Rp{predictions.get('low_30d', 0):,.2f}<br>
<b>Expected Change:</b> {predictions.get('change_pct', 0):.2f}%<br><br>
<b>TP1:</b> Rp{predictions.get('tp1', 0):,.2f}<br>
<b>TP2:</b> Rp{predictions.get('tp2', 0):,.2f}<br>
<b>Stop Loss:</b> Rp{predictions.get('sl', 0):,.2f}<br><br>
<b>Model Insight:</b><br>{predictions.get('summary', 'No analysis available')}
"""
# Karena custom CSS dihapus, kita akan menggunakan div sederhana tanpa class 'panel-box' dan 'triple-panel'
# Gradio secara otomatis akan menata elemen-elemen ini dengan lebih standar
return (
f"""
<div class="panel-card" style="background: rgba(255,255,255,0.1); backdrop-filter: blur(10px); border: 1px solid rgba(255,255,255,0.2); border-radius: 15px; padding: 0; overflow: hidden;">
<div style="display: flex; flex-direction: row; gap: 0; height: auto;">
<div style="flex: 1; min-width: 30%; padding: 20px; border-right: 1px solid rgba(255,255,255,0.1);">
<div style="margin-bottom: 15px;">
<h3 style="margin: 0; font-size: 1.1rem; font-weight: 600; color: #4fc3f7;">๐Ÿข COMPANY FUNDAMENTALS</h3>
</div>
<div style="line-height: 1.8; font-size: 0.95rem;">
{fundamentals.replace('<h4>COMPANY FUNDAMENTALS</h4>', '').replace('<br>', '<br>')}
</div>
</div>
<div style="flex: 1; min-width: 30%; padding: 20px; border-right: 1px solid rgba(255,255,255,0.1);">
<div style="margin-bottom: 15px;">
<h3 style="margin: 0; font-size: 1.1rem; font-weight: 600; color: #81c784;">๐Ÿ“Š TECHNICAL SIGNAL SUMMARY</h3>
</div>
<div style="line-height: 1.8; font-size: 0.95rem;">
{trading_signal.replace('<h4>TECHNICAL SIGNAL SUMMARY</h4>', '').replace('<br>', '<br>').replace('<ul style="margin-top: 8px; padding-left: 20px; line-height: 1.6;">', '<ul style="margin-top: 8px; padding-left: 20px; line-height: 1.4; font-size: 0.9rem;">')}
</div>
</div>
<div style="flex: 1; min-width: 30%; padding: 20px;">
<div style="margin-bottom: 15px;">
<h3 style="margin: 0; font-size: 1.1rem; font-weight: 600; color: #ffb74d;">๐Ÿค– AI FORECAST INSIGHTS</h3>
</div>
<div style="line-height: 1.8; font-size: 0.95rem;">
{prediction.replace('<h4>30-DAY AI FORECAST (CHRONOS-BOLT)</h4>', '').replace('<br>', '<br>')}
</div>
</div>
</div>
</div>
""",
fig_price,
fig_technical,
fig_prediction,
)
# Theme management
theme_state = gr.State(value="light")
def toggle_theme(current_theme):
return "dark" if current_theme == "light" else "light"
def get_theme_css(theme):
if theme == "dark":
return """
<style>
body { background-color: #1a1a1a; color: #ffffff; }
.main-container { background-color: #2a2a2a !important; }
.panel-modern {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
color: white !important;
border-radius: 15px !important;
padding: 20px !important;
margin: 10px !important;
box-shadow: 0 10px 30px rgba(0,0,0,0.3) !important;
}
.panel-card {
background: rgba(255,255,255,0.05) !important;
backdrop-filter: blur(10px) !important;
border: 1px solid rgba(255,255,255,0.1) !important;
border-radius: 12px !important;
padding: 16px !important;
margin: 8px !important;
transition: all 0.3s ease !important;
}
.panel-card:hover {
transform: translateY(-2px) !important;
box-shadow: 0 8px 25px rgba(0,0,0,0.4) !important;
}
.header-gradient {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
-webkit-background-clip: text !important;
-webkit-text-fill-color: transparent !important;
background-clip: text !important;
}
.theme-toggle {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
border: none !important;
border-radius: 25px !important;
padding: 12px 20px !important;
color: white !important;
font-weight: bold !important;
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4) !important;
transition: all 0.3s ease !important;
}
.theme-toggle:hover {
transform: translateY(-2px) !important;
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.6) !important;
}
.input-modern {
background: rgba(255,255,255,0.05) !important;
border: 1px solid rgba(255,255,255,0.1) !important;
border-radius: 10px !important;
color: white !important;
}
.tab-content {
background: rgba(255,255,255,0.02) !important;
border-radius: 12px !important;
padding: 20px !important;
}
</style>
"""
else:
return """
<style>
body { background-color: #f8fafc; color: #1a202c; }
.main-container { background-color: #ffffff !important; }
.panel-modern {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
color: white !important;
border-radius: 15px !important;
padding: 20px !important;
margin: 10px !important;
box-shadow: 0 10px 30px rgba(0,0,0,0.1) !important;
}
.panel-card {
background: rgba(255,255,255,0.9) !important;
backdrop-filter: blur(10px) !important;
border: 1px solid rgba(0,0,0,0.05) !important;
border-radius: 12px !important;
padding: 16px !important;
margin: 8px !important;
transition: all 0.3s ease !important;
}
.panel-card:hover {
transform: translateY(-2px) !important;
box-shadow: 0 8px 25px rgba(0,0,0,0.15) !important;
}
.header-gradient {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
-webkit-background-clip: text !important;
-webkit-text-fill-color: transparent !important;
background-clip: text !important;
}
.theme-toggle {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
border: none !important;
border-radius: 25px !important;
padding: 12px 20px !important;
color: white !important;
font-weight: bold !important;
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4) !important;
transition: all 0.3s ease !important;
}
.theme-toggle:hover {
transform: translateY(-2px) !important;
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.6) !important;
}
.input-modern {
background: rgba(255,255,255,0.9) !important;
border: 1px solid rgba(0,0,0,0.1) !important;
border-radius: 10px !important;
}
.tab-content {
background: rgba(255,255,255,0.7) !important;
border-radius: 12px !important;
padding: 20px !important;
}
</style>
"""
# --- Modern UI Redesign ---
with gr.Blocks(
title="REXPRO FINANCIAL AI DASHBOARD",
css="""
.main-container {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
max-width: 1400px;
margin: 0 auto;
padding: 20px;
}
.header-section {
text-align: center;
padding: 30px 0;
margin-bottom: 30px;
}
.control-panel {
background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%);
border-radius: 20px;
padding: 25px;
margin-bottom: 30px;
box-shadow: 0 8px 32px rgba(0,0,0,0.1);
}
""",
elem_classes=["main-container"]
) as app:
# Header Section
with gr.Row(elem_classes=["header-section"]):
gr.HTML("""
<div class="header-gradient">
<h1 style="font-size: 2.5rem; margin: 0; font-weight: 800;">
๐Ÿš€ REXPRO FINANCIAL AI DASHBOARD
</h1>
<p style="font-size: 1.2rem; margin: 10px 0 0 0; opacity: 0.8;">
Comprehensive stock analytics powered by <strong>AI forecasting and technical analysis</strong>
</p>
</div>
""")
# Theme toggle
with gr.Row():
theme_toggle = gr.Button(
"๐ŸŒ™ Switch to Dark Mode" if gr.load else "โ˜€๏ธ Switch to Light Mode",
variant="primary",
size="sm",
elem_classes=["theme-toggle"]
)
# Control Panel
with gr.Row(elem_classes=["control-panel"]):
with gr.Column(scale=2):
symbol = gr.Textbox(
label="๐Ÿ“ˆ STOCK SYMBOL (IDX)",
value="BBCA",
placeholder="Example: BBCA, TLKM, ADRO, BMRI",
interactive=True,
elem_classes=["input-modern"]
)
with gr.Column(scale=1):
prediction_days = gr.Slider(
label="๐Ÿ”ฎ FORECAST PERIOD (DAYS)",
minimum=5,
maximum=60,
step=5,
value=30,
interactive=True,
elem_classes=["input-modern"]
)
with gr.Column(scale=1):
analyze_button = gr.Button(
"โšก RUN ANALYSIS",
variant="primary",
size="lg",
elem_classes=["theme-toggle"]
)
# Modern theme-aware CSS
theme_css = gr.HTML("")
gr.HTML('<div style="height: 20px;"></div>')
# Enhanced report section with modern styling
report_section = gr.HTML(elem_classes=["panel-modern"])
gr.HTML('<div style="height: 20px;"></div>')
with gr.Tab("๐Ÿ“Š MARKET CHARTS", elem_classes=["tab-content"]):
with gr.Row():
price_chart = gr.Plot(label="๐Ÿ’น PRICE & MOVING AVERAGES", elem_classes=["panel-card"])
technical_chart = gr.Plot(label="๐Ÿ“ˆ TECHNICAL INDICATORS OVERVIEW", elem_classes=["panel-card"])
gr.HTML('<div style="height: 20px;"></div>')
prediction_chart = gr.Plot(label="๐Ÿค– AI FORECAST PROJECTION", elem_classes=["panel-card"])
analyze_button.click(
fn=update_analysis,
inputs=[symbol, prediction_days],
outputs=[report_section, price_chart, technical_chart, prediction_chart],
)
# Theme toggle functionality
theme_toggle.click(
fn=lambda x: toggle_theme(x),
inputs=[theme_state],
outputs=[theme_state]
)
# Update theme CSS when theme changes
theme_state.change(
fn=get_theme_css,
inputs=[theme_state],
outputs=[theme_css]
)
if __name__ == "__main__":
app.launch(server_name="0.0.0.0", server_port=7860, ssr_mode=True)