Insights
Build a Short-Locate Cost and Availability Monitor in Python
Alphanume Team · October 28, 2026
A short-selling watchlist needs more than a price forecast. Before reviewing a position, you need to know whether shares were offered, what a locate quote cost, and when that observation was made. This Python workflow retrieves historical short-locate quotes, saves a dated receipt, and creates a review table for a hypothetical position size.
Alphanume's Short Locates dataset records Alpaca hard-to-borrow quote snapshots. It is not a comparison of brokers, a market-wide lending feed, or a reservation of inventory. History begins October 8, 2026, with no earlier backfill. The API reference documents the current fields and access requirements.
Decide what the monitor should answer
Start with three questions: did the snapshot offer shares, was the quoted quantity large enough for the position being reviewed, and what would the quoted per-share fee imply for that size? Keep the answers separate. A priced row with no shares offered is different from an available quote, while a missing quote is different from a zero-dollar fee.
The response has one row per trading date, slot, and ticker. Scheduled snapshots are at 11:45 and 17:30 UTC on trading days. Early observations can have different times, so retain snapshot_time_utc. It describes the sweep's UTC minute shared across its rows, not an individual ticker's exact quote time or the moment your request received it.
| Field | How to use it |
|---|---|
status | Keep offered, no_shares and other refusals distinct. |
price_per_share / cost_per_100 | USD locate quote per share or per 100 shares. Neither is an annualized borrow rate. |
available_qty | Observed share quantity. Zero and unknown are different. |
snapshot_time_utc | Recorded sweep time. Preserve null when it is unavailable. |
Retrieve every page before saving the review
Install requests and set ALPHANUME_API_KEY in your environment using your authorized account's key. REST access requires Pro or Enterprise; this dataset has no public row preview. See current access and pricing. The script reads an existing dataset through GET requests. It does not request a locate from a broker or place an order.
Save the following as short_locate_review.py and run it with Python. The fixed October 8–15 range makes the example reproducible; change the dates for another bounded window. Leave the status and minimum-cost filters unset so refusals and unpriced observations remain in the review.
import csv
import json
import os
from datetime import datetime, timezone
from decimal import Decimal
from pathlib import Path
import requests
URL = "https://api.alphanume.com/v1/short-locates"
CURSOR_KEYS = ("cursor_date", "cursor_slot", "cursor_ticker")
FIELDS = ("date", "slot", "snapshot_time_utc", "ticker", "status",
"price_per_share", "cost_per_100", "available_qty")
def fetch_quotes(session, filters, max_pages=20):
params = dict(filters)
rows, seen_rows, seen_cursors = [], set(), set()
for _ in range(max_pages):
response = session.get(URL, params=params, timeout=(10, 60))
response.raise_for_status()
payload = response.json()
if (not isinstance(payload, dict)
or not isinstance(payload.get("data"), list)
or not isinstance(payload.get("has_more"), bool)):
raise ValueError("Unexpected response shape")
for row in payload["data"]:
if not isinstance(row, dict) or not set(FIELDS).issubset(row):
raise ValueError("A quote is missing required fields")
identity = (row["date"], row["slot"], row["ticker"])
if identity in seen_rows:
raise ValueError("Duplicate quote; stop and inspect pagination")
seen_rows.add(identity)
rows.append({name: row[name] for name in FIELDS})
if not payload["has_more"]:
return rows
cursor = payload.get("next_cursor")
if (not payload["data"] or not isinstance(cursor, dict)
or any(not cursor.get(key) for key in CURSOR_KEYS)):
raise ValueError("Incomplete pagination cursor")
marker = tuple(cursor[key] for key in CURSOR_KEYS)
if marker in seen_cursors:
raise ValueError("Repeated cursor; stop before saving partial history")
seen_cursors.add(marker)
params = {**filters, **{key: cursor[key] for key in CURSOR_KEYS}}
raise RuntimeError("Page bound reached; narrow the date range")
def review_rows(rows, shares):
if not isinstance(shares, int) or isinstance(shares, bool) or shares <= 0:
raise ValueError("shares must be a positive integer")
result = []
for row in rows:
price, quantity = row["price_per_share"], row["available_qty"]
status, stamp = row["status"], row["snapshot_time_utc"]
# This is the sweep's UTC minute, not a per-ticker fill/publication time.
if stamp is not None:
datetime.strptime(stamp, "%Y-%m-%d %H:%M")
if status == "no_shares":
assessment = "no_shares"
elif status == "offered" and quantity is not None and price is not None and stamp is not None:
assessment = "quoted_for_size" if quantity >= shares else "insufficient_quantity"
else:
assessment = "unknown"
cost = None
if status == "offered" and price is not None:
cost = str((Decimal(str(price)) * shares).quantize(Decimal("0.01")))
result.append({**row, "hypothetical_shares": shares,
"hypothetical_locate_cost_usd": cost,
"quote_assessment": assessment})
return sorted(result, key=lambda r: (r["ticker"], r["date"], r["slot"]))
def main():
filters = {"date_gte": "2026-10-08", "date_lte": "2026-10-15"}
output = Path("locate-review")
with requests.Session() as session:
session.headers["X-API-Key"] = os.environ["ALPHANUME_API_KEY"]
rows = fetch_quotes(session, filters)
reviewed = review_rows(rows, shares=1000)
receipt = {"retrieved_at_utc": datetime.now(timezone.utc).isoformat(),
"filters": filters, "data": rows}
output.mkdir(exist_ok=True)
(output / "quotes.json").write_text(json.dumps(receipt, indent=2), encoding="utf-8")
columns = [*FIELDS, "hypothetical_shares", "hypothetical_locate_cost_usd", "quote_assessment"]
with (output / "review.csv").open("w", newline="", encoding="utf-8") as handle:
writer = csv.DictWriter(handle, fieldnames=columns)
writer.writeheader()
writer.writerows(reviewed)
print(f"Saved {len(rows)} quotes to {output}")
if __name__ == "__main__":
main()
The script follows all three fields returned in next_cursor, retaining the original date filters on each request. It stops on an HTTP error, incomplete or repeated cursor, duplicate row, or page bound rather than labeling a partial history complete. An empty successful result writes an empty review with headers; it does not establish that every stock was borrowable.
quotes.json retains the returned fields, request filters and retrieval time, without saving the API key. review.csv is sorted by ticker, date and slot for side-by-side inspection. JSON preserves null values; blank CSV fields represent those unknowns rather than zeroes. This is an on-demand script, not a scheduled service.
Read the hypothetical cost alongside quantity and status
For a fictional quote of $0.075 per share, a 1,000-share review implies a $75 locate fee: $0.075 × 1,000. The same quote is $7.50 per 100 shares. Those numbers are hypothetical, not a historical quote or the full cost of the trade.
quoted_for_size means the returned row had offered status, a known price and timestamp, and sufficient observed quantity for the requested size. It is a review label, not permission to trade. insufficient_quantity retains a priced offered row that was too small; no_shares records that explicit status; unknown retains other incomplete or refused observations.
The fee calculation can still be present when quantity is insufficient or timing is unknown. Never use the cost column alone as a feasibility filter. A genuine zero quote is preserved as zero; a null price does not become a free locate.
Compare the same ticker's observations to investigate changing fees or inventory, but describe the result as change between returned snapshots. The series does not show everything that happened between them. A missing ticker can reflect coverage or collection limits, not an inventory estimate.
Connect the monitor to a premarket research queue
Pre-Market Drop Risk supplies a different input: a model-ranked subset of US microcaps with probabilities of an open-to-close fall of at least 5%. It does not supply locate availability, quote fees or executable prices. The premarket watchlist tutorial retrieves that research queue.
Review a candidate alongside an appropriate locate observation and source documents, keeping each timestamp. The model's 09:00 ET feature cutoff is not proof that a row was published or retrieved at 09:00. A same-date afternoon locate snapshot cannot support a morning decision. For historical simulation, require evidence that each input was available before the chosen decision time; a sweep-start minute alone does not prove when every quote or API row reached a user.
For a prospective process, save your own retrieval receipt for both inputs and obtain fresh executable terms from your broker. Keep ongoing borrow accrual, dividends owed, commissions, spreads, slippage and recalls separate from the quoted locate fee. The short-selling broker guide explains what to check at the account and execution layer. These records help make a research decision inspectable; they do not establish profitable short trades or guaranteed access to shares.