pumpfun-scorer / app.py
jrhode666
Fix: bind 0.0.0.0:7860 for HF Spaces
65052f6
Raw
History Blame Contribute Delete
8 kB
"""
Multi-Indicator Z-Score Scorer — Gradio app.
Pure Pine Script port. No ML, no entity tracking.
"""
import gradio as gr
import numpy as np
import pandas as pd
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import tempfile
from scorer import Config, score_series
def run_scoring(csv_file, prices_text, rsi_len, macd_fast, macd_slow, macd_sig,
stoch_len, stoch_smooth, trend_len, z_lookback,
w_rsi, w_macd, w_stoch, w_trend, buy_level, sell_level):
# Accept either CSV upload OR manual prices
if csv_file is not None:
df = pd.read_csv(csv_file.name)
if "close" not in df.columns:
return "CSV must have a 'close' column.", None, None
close = df["close"].to_numpy(dtype=float)
high = df["high"].to_numpy(dtype=float) if "high" in df.columns else None
low = df["low"].to_numpy(dtype=float) if "low" in df.columns else None
elif prices_text and prices_text.strip():
try:
prices = [float(x.strip()) for x in prices_text.replace("\n", ",").split(",") if x.strip()]
except ValueError:
return "Invalid prices. Use comma or newline separated numbers.", None, None
if len(prices) < 20:
return f"Need at least 20 prices (got {len(prices)}).", None, None
close = np.array(prices, dtype=float)
high = None
low = None
else:
return "Upload a CSV or paste prices to begin.", None, None
cfg = Config(
rsi_len=int(rsi_len), macd_fast=int(macd_fast), macd_slow=int(macd_slow),
macd_sig=int(macd_sig), stoch_len=int(stoch_len),
stoch_smooth=int(stoch_smooth), trend_len=int(trend_len),
z_lookback=int(z_lookback),
w_rsi=float(w_rsi), w_macd=float(w_macd),
w_stoch=float(w_stoch), w_trend=float(w_trend),
buy_level=float(buy_level), sell_level=float(sell_level),
)
results = score_series(close, high, low, cfg)
composite = np.array([r.composite_z for r in results])
signals = [r.signal for r in results]
buys = sum(1 for s in signals if s == "buy")
sells = sum(1 for s in signals if s == "sell")
latest = results[-1]
summary_md = (
"### Summary\n\n"
"**Bars analyzed:** " + str(len(close)) + "\n\n"
"**Buy signals:** " + str(buys) + "\n\n"
"**Sell signals:** " + str(sells) + "\n\n"
"### Latest Bar\n\n"
"| Metric | Value |\n"
"|--------|-------|\n"
"| Signal | **" + latest.signal.upper() + "** |\n"
"| Composite Z | `" + format(latest.composite_z, "+.3f") + "` |\n"
"| RSI Z | `" + format(latest.z_rsi, "+.3f") + "` (raw: " + format(latest.rsi, ".1f") + ") |\n"
"| MACD Z | `" + format(latest.z_macd, "+.3f") + "` |\n"
"| Stoch Z | `" + format(latest.z_stoch, "+.3f") + "` (raw: " + format(latest.stoch_k, ".1f") + ") |\n"
"| Trend Z | `" + format(latest.z_trend, "+.3f") + "` |\n"
"| Buy Level | `" + str(cfg.buy_level) + "` |\n"
"| Sell Level | `" + str(cfg.sell_level) + "` |\n"
)
fig = make_subplots(
rows=2, cols=1, shared_xaxes=True,
row_heights=[0.6, 0.4],
subplot_titles=("Price", "Composite Z-Score"),
vertical_spacing=0.08,
)
x = list(range(len(close)))
fig.add_trace(
go.Scatter(x=x, y=close.tolist(), mode="lines", name="Close",
line=dict(color="#60a5fa", width=1.5)),
row=1, col=1,
)
buy_x = [i for i, s in enumerate(signals) if s == "buy"]
sell_x = [i for i, s in enumerate(signals) if s == "sell"]
if buy_x:
fig.add_trace(
go.Scatter(x=buy_x, y=[float(close[i]) for i in buy_x], mode="markers",
name="Buy", marker=dict(color="#10b981", size=10, symbol="triangle-up")),
row=1, col=1,
)
if sell_x:
fig.add_trace(
go.Scatter(x=sell_x, y=[float(close[i]) for i in sell_x], mode="markers",
name="Sell", marker=dict(color="#ef4444", size=10, symbol="triangle-down")),
row=1, col=1,
)
fig.add_trace(
go.Scatter(x=x, y=composite.tolist(), mode="lines", name="Composite Z",
line=dict(color="#a78bfa", width=1.5)),
row=2, col=1,
)
fig.add_hline(y=cfg.buy_level, line_dash="dash", line_color="#10b981", row=2, col=1)
fig.add_hline(y=cfg.sell_level, line_dash="dash", line_color="#ef4444", row=2, col=1)
fig.add_hline(y=0, line_dash="dot", line_color="#6b7280", row=2, col=1)
fig.update_layout(
template="plotly_dark",
height=600,
showlegend=True,
margin=dict(l=10, r=10, t=40, b=10),
)
out_df = pd.DataFrame({
"close": close,
"composite_z": composite,
"z_rsi": [r.z_rsi for r in results],
"z_macd": [r.z_macd for r in results],
"z_stoch": [r.z_stoch for r in results],
"z_trend": [r.z_trend for r in results],
"signal": signals,
})
tmp = tempfile.NamedTemporaryFile(mode="w", suffix=".csv", delete=False)
out_df.to_csv(tmp.name, index=False)
return summary_md, fig, tmp.name
with gr.Blocks(title="Multi-Indicator Z-Score Scorer") as demo:
gr.Markdown("# Multi-Indicator Z-Score Scorer")
gr.Markdown(
"Pure Pine script port. RSI + MACD + Stochastic + Trend, "
"each normalized to z-scores over a lookback window, weighted into a composite. "
"Buy/sell on threshold crossovers. **No ML, no entity tracking.**"
)
with gr.Row():
with gr.Column(scale=1):
gr.Markdown("### Input (CSV or manual prices)")
csv_input = gr.File(label="CSV (needs 'close' column)", file_types=[".csv"])
prices_input = gr.Textbox(
label="OR paste prices (comma/newline separated, min 20)",
lines=6,
placeholder="100.0, 101.5, 102.3, 103.1, ...",
)
gr.Markdown("### Indicators")
rsi_len = gr.Slider(2, 50, value=14, step=1, label="RSI Length")
macd_fast = gr.Slider(2, 50, value=12, step=1, label="MACD Fast")
macd_slow = gr.Slider(5, 100, value=26, step=1, label="MACD Slow")
macd_sig = gr.Slider(2, 50, value=9, step=1, label="MACD Signal")
stoch_len = gr.Slider(2, 50, value=14, step=1, label="Stoch Length")
stoch_smooth = gr.Slider(1, 10, value=3, step=1, label="Stoch Smooth K")
trend_len = gr.Slider(5, 200, value=50, step=1, label="Trend EMA Length")
gr.Markdown("### Weights & Thresholds")
z_lookback = gr.Slider(10, 500, value=100, step=5, label="Z-Score Lookback")
w_rsi = gr.Slider(0.0, 3.0, value=1.0, step=0.1, label="RSI Weight")
w_macd = gr.Slider(0.0, 3.0, value=1.0, step=0.1, label="MACD Weight")
w_stoch = gr.Slider(0.0, 3.0, value=1.0, step=0.1, label="Stoch Weight")
w_trend = gr.Slider(0.0, 3.0, value=1.0, step=0.1, label="Trend Weight")
buy_level = gr.Slider(0.1, 5.0, value=1.5, step=0.1, label="Buy Level")
sell_level = gr.Slider(-5.0, -0.1, value=-1.5, step=0.1, label="Sell Level")
btn = gr.Button("Score", variant="primary", size="lg")
with gr.Column(scale=2):
summary = gr.Markdown("Upload a CSV or paste prices to begin.")
chart = gr.Plot()
download = gr.File(label="Download Scores CSV")
btn.click(
run_scoring,
inputs=[csv_input, prices_input, rsi_len, macd_fast, macd_slow, macd_sig,
stoch_len, stoch_smooth, trend_len, z_lookback,
w_rsi, w_macd, w_stoch, w_trend, buy_level, sell_level],
outputs=[summary, chart, download],
)
gr.Markdown("---")
gr.Markdown("Based on `multi_indicator_zscore.pine`")
if __name__ == "__main__":
demo.launch(server_name="0.0.0.0", server_port=7860)