StockAnalysis / README.md
igardner's picture
docs: correct dev-mode guidance after incident
5a17ca0
|
Raw History Blame Contribute Delete
12.8 kB
metadata
title: StockAnalysis
emoji: πŸ“Š
colorFrom: pink
colorTo: gray
sdk: gradio
sdk_version: 6.30.0
python_version: '3.13'
app_file: app.py
pinned: false
license: mit
short_description: Structured stock analysis as JSON for agents.
suggested_hardware: cpu-basic
tags:
  - stock-analysis
  - finance
  - agents
  - gradio
  - huggingface-hub
  - inference-providers
  - smoke-test
  - model-evaluation
models:
  - Qwen/Qwen3.8-27B
  - Qwen/Qwen3.8-Flash-Next
  - meta-models/Muse-Spark-Open-816B-A42B
  - reflection/Beam-1.0-501B-A23B
  - deepseek-ai/DeepSeek-V4.1-Flash
  - meta-llama/Meta-Llama-3-8B-Instruct
  - nvidia/NVIDIA-Nemotron-3.5-Super-VL-120B-A12B-NVFP4
  - moonshotai/Kimi-K3
  - zai-org/GLM-5.3-Flash
  - meta-llama/Llama-3.1-8B-Instruct
  - meta-models/Muse-Spark-Open-816B-A42B-NVFP4
  - OBLITERATUS/Qwen3.8-27B-OBLITERATED
  - zai-org/GLM-5.3
  - meta-models/Muse-Glimmer-30B
  - google/gemma-4-31B-it
  - Qwen/Qwen3.5-9B
  - meta-llama/Llama-3.2-3B-Instruct
  - XiaomiMiMo/MiMo-V2.6-Pro-RL
  - inclusionAI/Ling-3.1-flash
  - Qwen/Qwen3.5-4B
  - openai/gpt-oss-20b
  - google/gemma-4-26B-A4B-it
  - Qwen/Qwen3.6-35B-A3B
  - huihui-ai/Huihui-Qwen3.8-27B-abliterated
  - openai/gpt-oss-120b
  - XiaomiMiMo/MiMo-V2.6-Flash-RL
  - Qwen/Qwen2.5-1.5B-Instruct
  - Qwen/Qwen3-0.6B
  - Qwen/Qwen3-8B
  - deepseek-ai/DeepSeek-R1

StockAnalysis

StockAnalysis returns stock-analysis output as JSON for humans and agents.

The main screen collects a ticker, horizon, risk profile, and an optional question. The Space sends that request to a separate stock-analysis project and returns the upstream result as JSON, with the raw response kept beside it. If the upstream project returns text, an optional local model can convert it to JSON without inventing prices, targets, or recommendations.

The original inference smoke test is still here. It now lives in a compact panel fixed to the bottom-right corner of the page, with the live model table, run history, JSON download, and settings.

Agent API

There is no separate REST layer. Agents call the normal Gradio event behind the Analyze button. The Hub also generates a live agents.md for the Space. The repository copy at agents.md adds concrete examples.

To use this application (igardner/StockAnalysis: Structured stock analysis as JSON for agents):
API schema: GET https://igardner-stockanalysis.hf.space/gradio_api/info
Call endpoint: POST https://igardner-stockanalysis.hf.space/gradio_api/call/v2/analyze {"ticker": "AAPL", "horizon": "3 months", "risk_profile": "balanced", "question": ""}
Poll result: GET https://igardner-stockanalysis.hf.space/gradio_api/call/analyze/{event_id}
File inputs: none - all inputs are JSON scalars.
Auth: Bearer $HF_TOKEN (https://huggingface.co/settings/tokens)

The endpoint returns three values in order: the JSON result, the raw upstream response, and a short human-readable status string. The JSON result is the value agents should use. See agents.md for the full boundary and promise list, error shapes, retry guidance, and the private smoke-test caveat.

EVENT_ID=$(curl -sS -X POST \
  "https://igardner-stockanalysis.hf.space/gradio_api/call/v2/analyze" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $HF_TOKEN" \
  -d '{"ticker":"AAPL","horizon":"3 months","risk_profile":"balanced","question":""}' \
  | python -c 'import json,sys; print(json.load(sys.stdin)["event_id"])')

curl -sS -N \
  "https://igardner-stockanalysis.hf.space/gradio_api/call/analyze/$EVENT_ID" \
  -H "Authorization: Bearer $HF_TOKEN"

Always fetch /gradio_api/info before calling. The schema is the runtime contract, and Gradio can change the generated examples between versions.

Configuration

Variable Required Default Purpose
EXTERNAL_API_URL for real results blank HTTP endpoint of the external stock-analysis project.
STOCK_MODEL_ID no blank Uploaded custom model ID. Sent as model when set.
LOCAL_PARSER_URL no blank OpenAI-compatible chat endpoint used to convert text to JSON.
LOCAL_PARSER_MODEL no llama3.1 Model name for the local parser.
HF_TOKEN for smoke test and private calls Space secret Token read with os.getenv("HF_TOKEN"). Never committed.

EXTERNAL_API_URL is blank until the external project is ready. Until it is set, the app returns a clear not_configured JSON response instead of failing silently. STOCK_MODEL_ID is also blank until the custom model is uploaded.

Add HF_TOKEN under Settings β†’ Variables and secrets β†’ New secret in the Space. Secrets are injected as environment variables and are write-only in the UI. Restart the Space after adding or changing one. The app never writes the token to disk or into run results.

Custom model

The custom stock-analysis model is not wired in yet. Set STOCK_MODEL_ID to its Hub model ID (or to whatever identifier the external project expects) when it is ready. If it is blank, the external request omits the model field.

Local JSON parser

The parser is optional. It is useful when the external project returns text but an agent needs JSON. It calls any OpenAI-compatible chat-completions endpoint and asks the model to preserve the source facts.

With Ollama:

ollama serve
ollama pull llama3.1
export LOCAL_PARSER_URL=http://127.0.0.1:11434/v1
export LOCAL_PARSER_MODEL=llama3.1

The app also accepts a full /chat/completions URL. A base URL ending in /v1 works, and a bare host gets /v1/chat/completions appended.

On Hugging Face Spaces, 127.0.0.1 points at the Space container. Use a reachable parser endpoint or a Docker Space with a sidecar if you want the local parser to run next to the app. The normal deployment path is the external project plus its own JSON output.

External project contract

The adapter in external_api.py sends:

{
  "ticker": "AAPL",
  "horizon": "3 months",
  "risk_profile": "balanced",
  "question": ""
}

It adds "model": "<STOCK_MODEL_ID>" when a custom model is configured. It sends Authorization: Bearer $HF_TOKEN when the secret is present.

Response handling:

  • HTTP 2xx with JSON: passed through unchanged.
  • HTTP 2xx with text: wrapped as {"status": "ok", "ticker": "...", "raw": "..."} unless the local parser is configured.
  • Non-2xx: returned as {"status": "error", ...} with the HTTP status and a short error message.
  • No EXTERNAL_API_URL: returned as {"status": "not_configured", ...}.

The adapter is the only place to change when the external request or response contract changes.

Inference smoke test

The smoke test is still the original liveness check. It queries the Hub for the most trending conversational models with a live inference provider, sends one tiny chat completion to each, and records a literal True or False. The compact bottom-right panel shows the model table, run history, download, and settings.

The smoke-test events are marked api_visibility="private", so they do not appear in the public API docs or the Gradio clients. Private does not block direct HTTP, so it is not a security boundary. The Analyze event is the only public endpoint.

Settings live at the top of app.py:

MODEL_LIMIT = 30              # how many trending models to test
CONCURRENCY = 3               # parallel requests
MAX_TOKENS = 8                # output tokens per attempt
REQUEST_TIMEOUT_SECONDS = 60  # per-request timeout
RESULTS_DIR = "/data/hf-inference-smoke"
PAGE_SIZE = 100               # Hub discovery page size
MAX_SCAN = 500                # how far past MODEL_LIMIT to scan
AUTO_RUN_ON_START = False     # run automatically on page load

Every run is saved to persistent storage, not the repository:

/data/hf-inference-smoke/
β”œβ”€β”€ latest.json              # most recent run (mirror)
β”œβ”€β”€ runs/<run_id>.json       # one file per run
└── models/latest.json       # most recent discovery snapshot

Each run file records the model list, trending scores, live providers, per-attempt status, latency, usage, and errors.

Models used

The list below is the last recorded snapshot, kept so the tested models and the smoke-test history are not lost. Selection is dynamic, so the next run can use a different order. The run JSON always records the exact list it used.

Snapshot: 2026-10-25T20:18:59Z
Last recorded run: 20261025T202058Z-ff6262 at 2026-10-25T20:20:58Z - 21/30 passed in 64.56s.

# Model Result
1 Qwen/Qwen3.8-27B True
2 Qwen/Qwen3.8-Flash-Next False
3 meta-models/Muse-Spark-Open-816B-A42B True
4 reflection/Beam-1.0-501B-A23B True
5 deepseek-ai/DeepSeek-V4.1-Flash True
6 meta-llama/Meta-Llama-3-8B-Instruct False
7 nvidia/NVIDIA-Nemotron-3.5-Super-VL-120B-A12B-NVFP4 True
8 moonshotai/Kimi-K3 True
9 zai-org/GLM-5.3-Flash True
10 meta-llama/Llama-3.1-8B-Instruct True
11 meta-models/Muse-Spark-Open-816B-A42B-NVFP4 False
12 OBLITERATUS/Qwen3.8-27B-OBLITERATED False
13 zai-org/GLM-5.3 True
14 meta-models/Muse-Glimmer-30B True
15 google/gemma-4-31B-it True
16 Qwen/Qwen3.5-9B True
17 meta-llama/Llama-3.2-3B-Instruct False
18 XiaomiMiMo/MiMo-V2.6-Pro-RL True
19 inclusionAI/Ling-3.1-flash True
20 Qwen/Qwen3.5-4B False
21 openai/gpt-oss-20b True
22 google/gemma-4-26B-A4B-it True
23 Qwen/Qwen3.6-35B-A3B True
24 huihui-ai/Huihui-Qwen3.8-27B-abliterated False
25 openai/gpt-oss-120b True
26 XiaomiMiMo/MiMo-V2.6-Flash-RL True
27 Qwen/Qwen2.5-1.5B-Instruct False
28 Qwen/Qwen3-0.6B False
29 Qwen/Qwen3-8B True
30 deepseek-ai/DeepSeek-R1 True

How the smoke test works

  1. Discovery queries the Hub for conversational models sorted by trendingScore, scanning past the requested count so models without a live inference provider are skipped.
  2. Routing calls each model through the HF router with :cheapest. If that fails, it retries once with :fastest.
  3. A model passes only when the router returns HTTP 200 with a non-empty choices array.
  4. Each attempt asks for at most 8 output tokens, so a 30-model run costs well under a cent.
  5. Every run is saved to /data/hf-inference-smoke/.

A False result means the model could not produce a valid chat completion through either routing policy at the time of the run. It is a liveness check, not a quality benchmark.

Claims and disclaimer

The app is built for structured stock analysis and predictions. It does not promise a specific return, and it does not guarantee that any result will be profitable. Output comes from an external project and may be incomplete or wrong.

This is research tooling, not financial advice. Markets involve risk, including loss of principal. Verify anything before acting on it.

Project layout

app.py             # Gradio UI, settings, public /analyze event, smoke corner
external_api.py    # external stock-analysis project adapter
local_parser.py    # optional OpenAI-compatible text-to-JSON converter
smoke.py           # discovery, routing, smoke runner, persistence
agents.md          # concrete agent contract and examples
PROGRESS.md        # implementation notes, committed as work proceeds
requirements.txt   # pinned dependencies
README.md          # Space metadata and documentation

Requirements

  • Python 3.13 and Gradio 6.30.0 via the README sdk_version.
  • huggingface_hub==1.16.1 and requests==2.34.2 pinned in requirements.txt.
  • CPU basic hardware is sufficient. No GPU is required.
  • HF_TOKEN should be a Space secret for the smoke test and authenticated outbound calls.

Deployment

  1. Add HF_TOKEN as a Space secret.
  2. Set EXTERNAL_API_URL when the external stock-analysis project is ready.
  3. Set STOCK_MODEL_ID when the custom model is uploaded.
  4. Optionally set LOCAL_PARSER_URL and LOCAL_PARSER_MODEL for text-to-JSON conversion.
  5. Restart the Space.

After restart, GET /gradio_api/info lists the analyze endpoint. The smoke test remains available in the bottom-right corner and does not appear in the public endpoint list.

Maintainer note: dev mode

If the Space is in dev mode, pushed commits do not trigger a rebuild. Do not disable dev mode or run a factory reboot on a live dev container; that tears down the running environment. Use the dev-mode workflow to apply changes, or restart the Space intentionally after the dev session is finished.