""" app.py — "Did This Stock Actually Change?" Type a ticker. Free Yahoo data comes in, and you get a plain answer: which moves in the past year were real events, which were ordinary wobble, whether the ride itself got rougher — plus the latest free headlines. No API key, no account, nothing stored. """ import matplotlib matplotlib.use("Agg") from matplotlib.figure import Figure import numpy as np import gradio as gr import stock INK = "#1b1b1b" RED = "#d64545" ORANGE = "#e8a33d" CALM = "#2e9e5b" PERIODS = {"6 months": "6mo", "1 year": "1y", "2 years": "2y"} SENS = {1: 1.5, 2: 1.0, 3: 0.7} # 1 = only the undeniable … 3 = flag early def make_plot(ticker, dates, close, a, currency): n = len(close) fig = Figure(figsize=(7.6, 4.0), dpi=96) ax = fig.add_subplot(111) t = np.arange(n) ax.plot(t, close, color=INK, lw=1.2) for e in a["shocks"]: ax.axvspan(e["i0"] + 1, e["i1"] + 1, color=RED, alpha=0.18) mid = (e["i0"] + e["i1"]) // 2 + 1 ax.text(mid, float(np.max(close)), stock.pct(e["cum_ret"]), color=RED, fontsize=8.5, ha="center", va="top", fontweight="bold") for v in a["vols"]: ax.axvline(v["at"] + 1, color=ORANGE, lw=1.6, ls="--") ax.text(v["at"] + 1, float(np.min(close)), " ride changed", color=ORANGE, fontsize=8.5, va="bottom", fontweight="bold") # x ticks: ~6 date labels idx = np.linspace(0, n - 1, 6).astype(int) ax.set_xticks(idx) ax.set_xticklabels([dates[i] for i in idx], fontsize=8) cur = f" ({currency})" if currency else "" ax.set_ylabel(f"price{cur}") ne = len(a["shocks"]) ax.set_title(f"{ticker.upper()} — red = real events ({ne}), everything else = ordinary wobble", fontsize=11) ax.grid(alpha=0.22) fig.tight_layout() return fig def news_md(ticker, items, a, dates): if not items: return ("*No free headlines available right now (Yahoo occasionally rate-limits " "shared servers — try again in a minute).*") lines = [] recent_shock = any(e["i1"] + 1 >= len(dates) - 15 for e in a["shocks"]) if recent_shock: lines.append("One of the flagged events is **recent** — these headlines may be " "the *why*:") lines.append(f"#### 📰 Latest free headlines for {ticker.upper()}") for it in items: meta = " · ".join(x for x in (it["publisher"], it["when"]) if x) title = it["title"].replace("|", "-") if it["url"]: lines.append(f"- [{title}]({it['url']}) {meta}") else: lines.append(f"- {title} {meta}") lines.append("Headlines are Yahoo Finance's free feed — recent items only; " "they cannot be matched to events from months ago.") return "\n".join(lines) def run_check(ticker, period_label, sensitivity): ticker = (ticker or "").strip() if not ticker: return "Type a ticker first (Yahoo format — e.g. `AAPL`, `NOK`, `BTC-USD`, `^GSPC`).", None, "" dates, close, currency, err = stock.fetch_prices(ticker, PERIODS[period_label]) if err: return err, None, "" a = stock.analyze(dates, close, SENS[int(sensitivity)]) md = stock.verdict_md(ticker.upper(), dates, close, a, currency) fig = make_plot(ticker, dates, close, a, currency) nm = news_md(ticker, stock.fetch_news(ticker), a, dates) return md, fig, nm HEADER = """ # 📉 Did This Stock Actually Change? Financial charts make **every** wiggle look like a story. Most wiggles are dice. Type any ticker and get the honest split: > which moves were **real events** (worth asking *why*), which were **ordinary > wobble** (worth ignoring), whether **the ride itself got rougher** — and whether the > period's overall drift is even **distinguishable from luck**. Free Yahoo data, free headlines, **no API key, no account, nothing stored**. Works for stocks (`AAPL`, `NOK`), crypto (`BTC-USD`), indices (`^GSPC`, `^OMXH25`). """ FOOTER = """ --- Engine: the same *Clutch* surprise gate that powers the [compute demo](https://huggingface.co/spaces/Aluode/Clutch2) and [Did Something Actually Change?](https://huggingface.co/spaces/Aluode/DidItChange), here fed daily returns (shocks) and rolling volatility (regime changes), with measured false-alarm and detection rates in the README. It describes the past only, predicts nothing, and is not investment advice. Built by Antti Luode (PerceptionLab). *Do not hype. Do not lie. Just show.* """ with gr.Blocks(title="Did This Stock Actually Change?") as demo: gr.Markdown(HEADER) with gr.Row(): s_ticker = gr.Textbox(label="Ticker (Yahoo format)", value="NOK", scale=2) s_period = gr.Dropdown(list(PERIODS), value="1 year", label="Window", scale=1) s_sens = gr.Slider(1, 3, 2, step=1, scale=2, label="suspicion (1 = only the undeniable · 3 = flag early hints)") s_run = gr.Button("📉 Check it", variant="primary", scale=1) with gr.Row(): with gr.Column(scale=3): s_plot = gr.Plot(label="the year, judged") s_md = gr.Markdown() with gr.Column(scale=2): s_news = gr.Markdown() gr.Examples([["NOK"], ["AAPL"], ["NVDA"], ["BTC-USD"], ["^GSPC"]], inputs=[s_ticker]) s_run.click(run_check, [s_ticker, s_period, s_sens], [s_md, s_plot, s_news]) gr.Markdown(FOOTER) if __name__ == "__main__": demo.launch()