chopstick
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"