Spaces:
Sleeping
Sleeping
File size: 5,532 Bytes
3151dff | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 | """
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']}) <small>{meta}</small>")
else:
lines.append(f"- {title} <small>{meta}</small>")
lines.append("<small>Headlines are Yahoo Finance's free feed β recent items only; "
"they cannot be matched to events from months ago.</small>")
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 = """
---
<small>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.*</small>
"""
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()
|