--- 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`](./agents.md) adds concrete examples. ```text 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`](./agents.md) for the full boundary and promise list, error shapes, retry guidance, and the private smoke-test caveat. ```bash 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: ```bash 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: ```json { "ticker": "AAPL", "horizon": "3 months", "risk_profile": "balanced", "question": "" } ``` It adds `"model": ""` 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`: ```python 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: ```text /data/hf-inference-smoke/ ├── latest.json # most recent run (mirror) ├── runs/.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 ```text 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.