All widgets
Stocks
Comes with Chopstick
Live stock ticker. A pinned stock (the S&P 500 until you pin one) sits in the small island window; search any other ticker to see its price, day range and trend, and pin it.
Already installed? It's in your widget library.





Type it in the notch's search
- Stock ticker
- Stock symbol
- Pin stock symbol
What it runs
A widget is a folder of plain files. Here is all of its code.
widget.json
{
"id": "stocks",
"ambient": false,
"name": "Stocks",
"icon": "icon.png",
"description": "Live stock ticker. A pinned stock (the S&P 500 until you pin one) sits in the small island window; search any other ticker to see its price, day range and trend, and pin it.",
"initial": "ready",
"watch": [
{
"script": "quote.sh",
"every": 60,
"on": [
"file:pinned.txt"
],
"as": "q"
}
],
"commands": {
"show": {
"description": "Show the pinned stock's price.",
"phrase": "Stock ticker",
"do": [
{
"go": "ready"
}
]
},
"search": {
"description": "Look up a stock by ticker symbol (e.g. AAPL) without changing the pinned one.",
"phrase": "Stock {symbol}", "preview": { "state": "result", "run": "lookup.sh" },
"inputs": {
"symbol": {
"type": "string",
"description": "Ticker symbol, e.g. AAPL"
}
},
"do": [
{
"set": {
"query": "{{input.symbol}}"
}
},
{
"run": "lookup.sh",
"while": "searching",
"then": "result",
"else": "notfound"
}
]
},
"pin": {
"description": "Pin a stock so it shows in the small island window.",
"phrase": "Pin stock {symbol}",
"inputs": {
"symbol": {
"type": "string",
"description": "Ticker symbol, e.g. AAPL"
}
},
"do": [
{
"set": {
"pin": "{{input.symbol}}"
}
},
{
"run": "pin.sh"
},
{
"refresh": "q"
},
{
"go": "ready"
}
]
}
},
"states": {
"ready": {
"compact": {
"leading": {
"symbol": "{{q.symbol | chart.line.uptrend.xyaxis}}",
"tint": "{{q.tint | gray}}"
},
"trailing": [
{
"sparkline": {
"from": "q.history",
"tint": "{{q.tint | gray}}"
}
},
{
"text": "{{q.price | …}}"
}
]
},
"expanded": {
"size": "regular",
"header": {
"symbol": "{{q.header_symbol | chart.line.uptrend.xyaxis}}",
"title": "{{q.title | Loading…}}",
"tint": "{{q.tint | gray}}"
},
"blocks": [
{
"row": [
{
"text": "{{q.price | –}}",
"style": "title"
},
"spacer",
{
"text": "{{q.change}}",
"tint": "{{q.tint}}"
}
],
"align": "center"
},
{
"sparkline": {
"from": "q.history",
"tint": "{{q.tint}}"
}
},
{
"row": [
{
"note": "Day {{q.low}} – {{q.high}}"
},
"spacer",
{
"note": "52w {{q.range52}}"
}
]
},
{
"note": "{{q.note | }}"
},
{
"input": {
"placeholder": "Search a ticker (AAPL, NVDA…)",
"do": [
{
"set": {
"query": "{{item.text}}"
}
},
{
"run": "lookup.sh",
"while": "searching",
"then": "result",
"else": "notfound"
}
]
}
}
]
},
"keys": [
{
"key": "cmd+c",
"label": "Copy",
"do": [
{
"copy": "{{q.ticker}} {{q.price}} ({{q.pct}})"
}
]
},
{
"key": "cmd+r",
"label": "Refresh",
"do": [
{
"refresh": "q"
}
]
}
]
},
"searching": {
"compact": {
"leading": {
"symbol": "{{q.symbol | arrow.up.right}}",
"tint": "{{q.tint | green}}"
},
"trailing": {
"text": "{{q.price}}"
}
},
"expanded": {
"size": "regular",
"header": {
"symbol": "magnifyingglass",
"title": "{{query}}"
},
"blocks": [
{
"note": "Looking up {{query}}…"
}
]
}
},
"result": {
"compact": {
"leading": {
"symbol": "{{q.symbol | arrow.up.right}}",
"tint": "{{q.tint | green}}"
},
"trailing": [
{
"sparkline": {
"from": "q.history",
"tint": "{{q.tint | green}}"
}
},
{
"text": "{{q.price}}"
}
]
},
"expanded": {
"size": "regular",
"header": {
"symbol": "magnifyingglass",
"title": "{{result.name}} · {{result.ticker}}",
"tint": "{{result.tint | green}}"
},
"blocks": [
{
"row": [
{
"text": "{{result.price | …}}",
"style": "title"
},
"spacer",
{
"text": "{{result.change}}",
"tint": "{{result.tint}}"
}
],
"align": "center"
},
{
"sparkline": {
"from": "result.history",
"tint": "{{result.tint}}"
}
},
{
"row": [
{
"note": "Day {{result.low}} – {{result.high}}"
},
"spacer",
{
"note": "52w {{result.range52}}"
}
]
},
{
"row": [
{
"button": {
"symbol": "pin.fill",
"label": "Pin {{result.ticker}}",
"style": "filled",
"do": [
{
"set": {
"pin": "{{result.ticker}}"
}
},
{
"run": "pin.sh"
},
{
"refresh": "q"
},
{
"go": "ready"
}
]
}
},
{
"button": {
"symbol": "chevron.left",
"label": "Back",
"style": "plain",
"do": [
{
"go": "ready"
}
]
}
}
]
},
{
"input": {
"placeholder": "Search another ticker",
"do": [
{
"set": {
"query": "{{item.text}}"
}
},
{
"run": "lookup.sh",
"while": "searching",
"then": "result",
"else": "notfound"
}
]
}
}
]
},
"keys": [
{
"key": "esc",
"label": "Back",
"do": [
{
"go": "ready"
}
]
}
]
},
"notfound": {
"compact": {
"leading": {
"symbol": "{{q.symbol | arrow.up.right}}",
"tint": "{{q.tint | green}}"
},
"trailing": {
"text": "{{q.price}}"
}
},
"expanded": {
"size": "regular",
"header": {
"symbol": "questionmark.circle",
"title": "No match for “{{query}}”",
"tint": "orange"
},
"blocks": [
{
"button": {
"symbol": "chevron.left",
"label": "Back",
"style": "plain",
"do": [
{
"go": "ready"
}
]
}
},
{
"input": {
"placeholder": "Try another ticker",
"do": [
{
"set": {
"query": "{{item.text}}"
}
},
{
"run": "lookup.sh",
"while": "searching",
"then": "result",
"else": "notfound"
}
]
}
}
]
},
"keys": [
{
"key": "esc",
"label": "Back",
"do": [
{
"go": "ready"
}
]
}
]
}
},
"preview": {
"states": [
"ready",
"result",
"notfound"
],
"data": {
"query": "AAPL",
"q": {
"ticker": "TSLA",
"name": "Tesla, Inc.",
"price": "$372.22",
"change": "-2.36 (-0.63%)",
"pct": "-0.63%",
"tint": "red",
"symbol": "arrow.down.right",
"high": "$377.30",
"low": "$365.27",
"range52": "$297.00 – $499.00",
"history": [
375,
374,
371,
368,
366,
369,
372,
371,
372
],
"title": "Tesla, Inc. · TSLA",
"pinned": true,
"header_symbol": "pin.fill",
"note": ""
},
"r": {
"ticker": "AAPL",
"name": "Apple Inc.",
"price": "$231.10",
"change": "+1.80 (+0.79%)",
"pct": "+0.79%",
"tint": "green",
"symbol": "arrow.up.right",
"high": "$232.00",
"low": "$229.10",
"range52": "$164.00 – $260.00",
"history": [
229,
229.5,
230,
229.8,
230.6,
231,
231.1
]
}
}
}
}
lookup.sh
CHOPSTICK_FETCH="${CHOPSTICK_QUERY:-$CHOPSTICK_SYMBOL}" exec sh quote.sh
pin.sh
printf '%s\n' "$CHOPSTICK_PIN" | tr 'a-z' 'A-Z' | tr -cd 'A-Z0-9.^=-\n' > "$CHOPSTICK_STORAGE/pinned.txt"
echo '{}'
quote.sh
# The quote for the pinned stock (the S&P 500 until one's pinned), or for $CHOPSTICK_FETCH (a
# lookup, which fails when it can't find one). The pinned quote never fails: on an error it says
# so in its fields, so the island shows what's wrong instead of nothing.
SYM="$CHOPSTICK_FETCH"
PINNED=true
[ -z "$SYM" ] && SYM=$(cat "$CHOPSTICK_STORAGE/pinned.txt" 2>/dev/null)
[ -z "$SYM" ] && { SYM=^GSPC; PINNED=false; }
SYM=$(printf '%s' "$SYM" | tr 'a-z' 'A-Z' | tr -cd 'A-Z0-9.^=-')
if curl -s -m 10 -A "Mozilla/5.0" "https://query1.finance.yahoo.com/v8/finance/chart/$SYM?range=1d&interval=15m" \
| PINNED=$PINNED python3 -c '
import sys, json, os
r = json.load(sys.stdin)["chart"]["result"][0]
m = r["meta"]
price = m["regularMarketPrice"]
chg = m.get("fulldayChange") or 0
pct = m.get("regularMarketChangePercent") or 0
closes = [c for c in r["indicators"]["quote"][0]["close"] if c is not None]
up = pct >= 0
cur = {"USD": "$"}.get(m.get("currency"), "")
pinned = os.environ["PINNED"] == "true"
def f(k):
v = m.get(k)
return "%s%.2f" % (cur, v) if v is not None else "–"
name = m.get("shortName") or m.get("longName") or m.get("symbol")
print(json.dumps({
"ticker": m.get("symbol"),
"name": name,
"title": "%s · %s" % (name, m.get("symbol")),
"pinned": pinned,
"header_symbol": "pin.fill" if pinned else "chart.line.uptrend.xyaxis",
"note": "" if pinned else "Showing the S&P 500. Search a ticker and pin it to keep it here.",
"price": "%s%.2f" % (cur, price),
"change": "%s%.2f (%s%.2f%%)" % ("+" if up else "", chg, "+" if up else "", pct),
"pct": "%s%.2f%%" % ("+" if up else "", pct),
"tint": "green" if up else "red",
"symbol": "arrow.up.right" if up else "arrow.down.right",
"high": f("regularMarketDayHigh"),
"low": f("regularMarketDayLow"),
"range52": "%s – %s" % (f("fiftyTwoWeekLow"), f("fiftyTwoWeekHigh")),
"history": closes,
}))
' 2>/dev/null; then exit 0; fi
[ -n "$CHOPSTICK_FETCH" ] && exit 1
printf '{"ticker": "%s", "title": "Stocks", "price": "–", "symbol": "exclamationmark.triangle", "tint": "orange", "header_symbol": "exclamationmark.triangle", "note": "Couldn'"'"'t load %s. ⌘R to try again.", "history": []}\n' "$SYM" "$SYM"