Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
|
@@ -25,77 +25,107 @@ def get_pro_chart(df, forecast_df, ticker):
|
|
| 25 |
# Professional Subplots: Price + Indicators
|
| 26 |
fig = make_subplots(rows=3, cols=1, shared_xaxes=True,
|
| 27 |
vertical_spacing=0.05, row_heights=[0.5, 0.25, 0.25],
|
| 28 |
-
subplot_titles=(f"{ticker} Forecast", "RSI Momentum", "MACD Trend"))
|
| 29 |
|
| 30 |
-
# 1. Price + Forecast
|
| 31 |
-
|
| 32 |
-
fig.add_trace(go.Scatter(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
|
| 34 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
fig.add_trace(go.Scatter(x=df.index, y=df['RSI'], name='RSI', line=dict(color='#FF9800')), row=2, col=1)
|
| 36 |
fig.add_hline(y=70, line_dash="dot", line_color="red", row=2, col=1)
|
| 37 |
fig.add_hline(y=30, line_dash="dot", line_color="green", row=2, col=1)
|
| 38 |
|
| 39 |
-
# 3. MACD
|
| 40 |
-
fig.add_trace(go.Bar(x=df.index, y=df['MACD'] - df['Signal'], name='Momentum'), row=3, col=1)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
|
| 42 |
-
fig.update_layout(template='plotly_dark', height=800, showlegend=False, paper_bgcolor='#131722', plot_bgcolor='#131722')
|
| 43 |
return fig
|
| 44 |
|
| 45 |
def analyze(ticker, horizon):
|
| 46 |
try:
|
| 47 |
-
# Step 1: Data
|
| 48 |
stock = yf.Ticker(ticker)
|
| 49 |
df = stock.history(period="1y")
|
| 50 |
-
if df.empty: return None, "Symbol Error"
|
| 51 |
|
| 52 |
info = stock.info
|
| 53 |
target_high = info.get('targetHighPrice', 'N/A')
|
| 54 |
current_price = df['Close'].iloc[-1]
|
| 55 |
|
| 56 |
-
# Step 2:
|
| 57 |
df = calculate_indicators(df)
|
| 58 |
|
| 59 |
-
# Step 3:
|
| 60 |
data = df.reset_index()[['Date', 'Close']]
|
| 61 |
data.columns = ['ds', 'y']
|
| 62 |
data['unique_id'] = ticker
|
| 63 |
|
| 64 |
-
# Season length 5 = Weekly trading cycle
|
| 65 |
sf = StatsForecast(models=[AutoARIMA(season_length=5)], freq='B')
|
| 66 |
sf.fit(data)
|
|
|
|
|
|
|
| 67 |
forecast = sf.predict(h=horizon, level=[80])
|
|
|
|
| 68 |
|
| 69 |
# Step 4: Signal Analysis
|
| 70 |
rsi_val = df['RSI'].iloc[-1]
|
| 71 |
-
rsi_stat = "OVERSOLD
|
| 72 |
|
| 73 |
signal_html = f"""
|
| 74 |
-
<div style='background:
|
| 75 |
-
<p
|
| 76 |
-
<
|
| 77 |
-
<hr>
|
| 78 |
-
<p>RSI
|
| 79 |
-
<p>AI
|
|
|
|
| 80 |
</div>
|
| 81 |
"""
|
| 82 |
|
| 83 |
return get_pro_chart(df, forecast, ticker), signal_html
|
| 84 |
except Exception as e:
|
| 85 |
-
return None, f"Error: {str(e)}"
|
| 86 |
|
| 87 |
# --- UI Layout ---
|
| 88 |
-
with gr.Blocks(theme=gr.themes.
|
| 89 |
-
gr.Markdown("
|
| 90 |
with gr.Row():
|
| 91 |
with gr.Column(scale=1):
|
| 92 |
-
t_in = gr.Textbox(label="Ticker
|
| 93 |
h_in = gr.Slider(7, 90, value=30, label="Forecast Days")
|
| 94 |
-
btn = gr.Button("
|
| 95 |
info_out = gr.HTML()
|
| 96 |
with gr.Column(scale=4):
|
| 97 |
plot_out = gr.Plot()
|
| 98 |
|
| 99 |
btn.click(analyze, [t_in, h_in], [plot_out, info_out])
|
| 100 |
|
| 101 |
-
|
|
|
|
|
|
| 25 |
# Professional Subplots: Price + Indicators
|
| 26 |
fig = make_subplots(rows=3, cols=1, shared_xaxes=True,
|
| 27 |
vertical_spacing=0.05, row_heights=[0.5, 0.25, 0.25],
|
| 28 |
+
subplot_titles=(f"{ticker} Forecast & Confidence Zone", "RSI Momentum", "MACD Trend"))
|
| 29 |
|
| 30 |
+
# --- 1. Price + Forecast + CONFIDENCE ZONE ---
|
| 31 |
+
# Shaded Confidence Area (AutoARIMA-hi-80 and AutoARIMA-lo-80)
|
| 32 |
+
fig.add_trace(go.Scatter(
|
| 33 |
+
x=forecast_df['ds'].tolist() + forecast_df['ds'].tolist()[::-1],
|
| 34 |
+
y=forecast_df['AutoARIMA-hi-80'].tolist() + forecast_df['AutoARIMA-lo-80'].tolist()[::-1],
|
| 35 |
+
fill='toself',
|
| 36 |
+
fillcolor='rgba(0, 210, 255, 0.2)',
|
| 37 |
+
line=dict(color='rgba(255,255,255,0)'),
|
| 38 |
+
hoverinfo="skip",
|
| 39 |
+
showlegend=True,
|
| 40 |
+
name='80% Confidence'
|
| 41 |
+
), row=1, col=1)
|
| 42 |
|
| 43 |
+
# Historical Price
|
| 44 |
+
fig.add_trace(go.Scatter(x=df.index, y=df['Close'], name='Historical Price', line=dict(color='#00d2ff', width=2)), row=1, col=1)
|
| 45 |
+
|
| 46 |
+
# AI Mean Forecast Line
|
| 47 |
+
fig.add_trace(go.Scatter(x=forecast_df['ds'], y=forecast_df['AutoARIMA'], name='AI Target', line=dict(color='#F23645', dash='dot')), row=1, col=1)
|
| 48 |
+
|
| 49 |
+
# --- 2. RSI ---
|
| 50 |
fig.add_trace(go.Scatter(x=df.index, y=df['RSI'], name='RSI', line=dict(color='#FF9800')), row=2, col=1)
|
| 51 |
fig.add_hline(y=70, line_dash="dot", line_color="red", row=2, col=1)
|
| 52 |
fig.add_hline(y=30, line_dash="dot", line_color="green", row=2, col=1)
|
| 53 |
|
| 54 |
+
# --- 3. MACD ---
|
| 55 |
+
fig.add_trace(go.Bar(x=df.index, y=df['MACD'] - df['Signal'], name='Momentum', marker_color='#9d50bb'), row=3, col=1)
|
| 56 |
+
|
| 57 |
+
# Styling to match your Aether UI
|
| 58 |
+
fig.update_layout(
|
| 59 |
+
template='plotly_dark',
|
| 60 |
+
height=800,
|
| 61 |
+
showlegend=True,
|
| 62 |
+
paper_bgcolor='rgba(0,0,0,0)', # Transparent to show your terminal background
|
| 63 |
+
plot_bgcolor='rgba(0,0,0,0)',
|
| 64 |
+
font=dict(family="Lato", size=12)
|
| 65 |
+
)
|
| 66 |
+
fig.update_xaxes(showgrid=False, zeroline=False)
|
| 67 |
+
fig.update_yaxes(showgrid=False, zeroline=False)
|
| 68 |
|
|
|
|
| 69 |
return fig
|
| 70 |
|
| 71 |
def analyze(ticker, horizon):
|
| 72 |
try:
|
| 73 |
+
# Step 1: Data
|
| 74 |
stock = yf.Ticker(ticker)
|
| 75 |
df = stock.history(period="1y")
|
| 76 |
+
if df.empty: return None, "Symbol Error"
|
| 77 |
|
| 78 |
info = stock.info
|
| 79 |
target_high = info.get('targetHighPrice', 'N/A')
|
| 80 |
current_price = df['Close'].iloc[-1]
|
| 81 |
|
| 82 |
+
# Step 2: Indicators
|
| 83 |
df = calculate_indicators(df)
|
| 84 |
|
| 85 |
+
# Step 3: StatsForecast with Levels
|
| 86 |
data = df.reset_index()[['Date', 'Close']]
|
| 87 |
data.columns = ['ds', 'y']
|
| 88 |
data['unique_id'] = ticker
|
| 89 |
|
|
|
|
| 90 |
sf = StatsForecast(models=[AutoARIMA(season_length=5)], freq='B')
|
| 91 |
sf.fit(data)
|
| 92 |
+
|
| 93 |
+
# We request 80% level to get the 'lo-80' and 'hi-80' columns
|
| 94 |
forecast = sf.predict(h=horizon, level=[80])
|
| 95 |
+
forecast = forecast.reset_index()
|
| 96 |
|
| 97 |
# Step 4: Signal Analysis
|
| 98 |
rsi_val = df['RSI'].iloc[-1]
|
| 99 |
+
rsi_stat = "OVERSOLD" if rsi_val < 30 else "OVERBOUGHT" if rsi_val > 70 else "NEUTRAL"
|
| 100 |
|
| 101 |
signal_html = f"""
|
| 102 |
+
<div style='background: rgba(255,255,255,0.05); padding: 20px; border-radius: 15px; border: 1px solid rgba(255,255,255,0.1); font-family: "Lato", sans-serif;'>
|
| 103 |
+
<p style='margin:0; color:#888; font-size:12px;'>CURRENT VALUE</p>
|
| 104 |
+
<h2 style='margin:0; color:#00d2ff;'>${current_price:.2f}</h2>
|
| 105 |
+
<hr style='border:0; border-top:1px solid #333; margin:15px 0;'>
|
| 106 |
+
<p>RSI Analysis: <b style='color:#FF9800;'>{rsi_stat} ({rsi_val:.1f})</b></p>
|
| 107 |
+
<p>AI Forecast ({horizon}d): <b style='color:#F23645;'>${forecast['AutoARIMA'].iloc[-1]:.2f}</b></p>
|
| 108 |
+
<p style='font-size:11px; color:#555;'>Confidence Zone: ${forecast['AutoARIMA-lo-80'].iloc[-1]:.2f} - ${forecast['AutoARIMA-hi-80'].iloc[-1]:.2f}</p>
|
| 109 |
</div>
|
| 110 |
"""
|
| 111 |
|
| 112 |
return get_pro_chart(df, forecast, ticker), signal_html
|
| 113 |
except Exception as e:
|
| 114 |
+
return None, f"<p style='color:red;'>Error: {str(e)}</p>"
|
| 115 |
|
| 116 |
# --- UI Layout ---
|
| 117 |
+
with gr.Blocks(theme=gr.themes.Default(primary_hue="blue", secondary_hue="slate")) as demo:
|
| 118 |
+
gr.Markdown("<h1 style='text-align: center; letter-spacing: 5px;'>AETHER BACKEND ENGINE</h1>")
|
| 119 |
with gr.Row():
|
| 120 |
with gr.Column(scale=1):
|
| 121 |
+
t_in = gr.Textbox(label="Ticker", value="NVDA")
|
| 122 |
h_in = gr.Slider(7, 90, value=30, label="Forecast Days")
|
| 123 |
+
btn = gr.Button("UPDATE ENGINE", variant="primary")
|
| 124 |
info_out = gr.HTML()
|
| 125 |
with gr.Column(scale=4):
|
| 126 |
plot_out = gr.Plot()
|
| 127 |
|
| 128 |
btn.click(analyze, [t_in, h_in], [plot_out, info_out])
|
| 129 |
|
| 130 |
+
if __name__ == "__main__":
|
| 131 |
+
demo.launch()
|