Spaces:
Runtime error
Runtime error
Delete BLOG_DRAFT.md
Browse files- BLOG_DRAFT.md +0 -107
BLOG_DRAFT.md
DELETED
|
@@ -1,107 +0,0 @@
|
|
| 1 |
-
# Field Notes: Putting a 20-Year-Old Chinese Trading Theory on a Fine-Tuned 1.7B
|
| 2 |
-
|
| 3 |
-
*Built for the Build Small hackathon — a custom-frontend Gradio app (`gradio.Server`), a local llama.cpp sub-agent pool, a model I fine-tuned myself, and zero cloud APIs.*
|
| 4 |
-
|
| 5 |
-
## The problem (a real one)
|
| 6 |
-
|
| 7 |
-
My family trades US stocks using 缠论 (Chan theory) — a Chinese technical-analysis
|
| 8 |
-
framework built on fractals, strokes, segments and "pivots" (中枢), famous for its
|
| 9 |
-
rigor and infamous for its complexity. We had a battle-tested Python engine that
|
| 10 |
-
decomposes monthly → weekly → daily → 60m → 30m → 15m → 5m charts and emits the
|
| 11 |
-
classic three buy / three sell points. Two problems: the engine's reasoning logs
|
| 12 |
-
are dense Chinese jargon, and someone had to run it by hand every evening.
|
| 13 |
-
|
| 14 |
-
**Chan Compass** fixes both: a Gradio Space that auto-updates after the US close
|
| 15 |
-
(18:10 ET), answers exactly one question per ticker — *do I buy or sell tomorrow,
|
| 16 |
-
at what buy point, in which zone, and where am I wrong?* — and uses a pool of small
|
| 17 |
-
local models as the translators and analysts the rule engine can't be. The results
|
| 18 |
-
are persisted to disk, so when the family opens the app the next morning, the
|
| 19 |
-
overnight run is already there waiting — no spinner, no recompute.
|
| 20 |
-
|
| 21 |
-
## Why small models are the honest fit
|
| 22 |
-
|
| 23 |
-
The LLMs are **not** doing the math. Fractal merging, divergence grading and
|
| 24 |
-
nested-interval confirmation are deterministic code — a 32B (or 320B) model would
|
| 25 |
-
add nothing but hallucination risk and latency. What the rule engine can't do is
|
| 26 |
-
*language*: turn a Chinese decision chain into a plain-English summary, brief
|
| 27 |
-
today's headlines on my holdings, narrate sector rotation, write a structured
|
| 28 |
-
research note. That's bounded, evidence-grounded text work — exactly what 1.7B–4B
|
| 29 |
-
models handle on 8 CPU cores. The constraint drew the architecture: **rules
|
| 30 |
-
compute, small models speak.**
|
| 31 |
-
|
| 32 |
-
## A pool of sub-agents, not one model
|
| 33 |
-
|
| 34 |
-
Early versions shared a single model behind one lock, so the moment two features
|
| 35 |
-
ran at once you'd get "model busy." The fix was a **sub-agent pool** — four small
|
| 36 |
-
models, each with its own lock, running through llama.cpp:
|
| 37 |
-
|
| 38 |
-
* **Interpreter** (Signals → AI Interpret) — my **fine-tuned Chan-Tuned Qwen3-1.7B**
|
| 39 |
-
* **Narrator** (sector rotation) — Qwen3-1.7B
|
| 40 |
-
* **Reporter** (watchlist news + research support) — Qwen3-1.7B
|
| 41 |
-
* **Analyst** (Auto Research) — Qwen3-4B
|
| 42 |
-
|
| 43 |
-
They genuinely run in parallel: you can stream a research report while the Signals
|
| 44 |
-
summary writes, with no collision.
|
| 45 |
-
|
| 46 |
-
## The fine-tune (the part I'm proudest of)
|
| 47 |
-
|
| 48 |
-
The Interpreter sub-agent runs a model I trained myself. The app captures every
|
| 49 |
-
(raw read → English summary) pair it produces into a dataset on the `/data`
|
| 50 |
-
bucket; a couple hundred focused pairs later, I exported the JSONL straight from
|
| 51 |
-
the app's Model tab, LoRA-tuned **Qwen3-1.7B** on a free Colab T4 with Unsloth,
|
| 52 |
-
converted to GGUF, and published it to the Hub
|
| 53 |
-
(`ranranrunforit/chan-compass-qwen3-1.7b-gguf`). One line in `MODEL_ZOO` wires it
|
| 54 |
-
back in. The loop closes: the app generated its own training data, and now runs on
|
| 55 |
-
the model that data produced — a 1.7B doing one job well instead of a giant model
|
| 56 |
-
doing everything adequately.
|
| 57 |
-
|
| 58 |
-
## What's in the box
|
| 59 |
-
|
| 60 |
-
* **Tomorrow's plan** — one row per ticker: BUY/SELL/HOLD/WAIT, explicit **buy
|
| 61 |
-
point**, entry zone, invalidation price. Long-hold mode: ride the weekly pivot
|
| 62 |
-
uplift, exit only on third-sell / structural stop / an armed nested-interval line.
|
| 63 |
-
* **Sector rotation** — flow proxy (Δ% × dollar volume) across the 11 SPDR sector
|
| 64 |
-
ETFs vs SPY, 1/5/20-day windows, instant tables + on-demand AI narrative.
|
| 65 |
-
* **Watchlist news** — today-only headlines per holding, streamed as they arrive,
|
| 66 |
-
each with an AI brief.
|
| 67 |
-
* **Multi-agent Auto Research** — PLAN → six evidence tools in parallel
|
| 68 |
-
(fundamentals, financials, price, *the Chan engine itself*, money-flow proxy,
|
| 69 |
-
news) → Analyst and Reporter writing different sections **simultaneously**:
|
| 70 |
-
valuation · tech moat · supply-chain map with tickers · bull/bear · money flow ·
|
| 71 |
-
Chan timing · risks. Every run writes a full JSON trace — the plan, each tool
|
| 72 |
-
call and its result, each sub-agent's request and response — **published as a Hub
|
| 73 |
-
dataset** so anyone can see how the agent actually reasoned.
|
| 74 |
-
* **Email any result** — each tab can email its output; on HF (where SMTP is
|
| 75 |
-
blocked) delivery goes over the Resend HTTPS API, markdown rendered to clean HTML.
|
| 76 |
-
* **A real custom frontend** — instead of the default Gradio component render, `server.py` builds a `gradio.Server` that serves a hand-built React + Adobe Spectrum 2 UI at `/` and exposes the Python backend as `/api/*` JSON + streaming endpoints. It's still a Gradio app — just wearing its own clothes.
|
| 77 |
-
|
| 78 |
-
## Five things I learned the hard way
|
| 79 |
-
|
| 80 |
-
1. **HF build containers are tiny.** `llama-cpp-python` got OOM-killed compiling at
|
| 81 |
-
*build* time on a Space with 32 GB of *runtime* RAM. Fix: install the runtime at
|
| 82 |
-
first launch (prebuilt CPU wheel, capped-compile fallback), persisted to `/data`
|
| 83 |
-
so it happens once. Pin `python_version: 3.11` — prebuilt wheels stop at 3.12.
|
| 84 |
-
2. **Don't put the model on the hot path.** My first "Run analysis" generated an AI
|
| 85 |
-
narrative inline — 10 tickers took 186 s. Moving all LLM calls off the rule path
|
| 86 |
-
and parallelizing downloads brought it to a few seconds. CPU inference is fine;
|
| 87 |
-
it just can't be a synchronous dependency of everything.
|
| 88 |
-
3. **One lock per model.** A shared lock turns concurrent features into "model
|
| 89 |
-
busy." A sub-agent pool — one instance and lock per job — fixed it and made the
|
| 90 |
-
fine-tune swappable for just the one agent that needed it.
|
| 91 |
-
4. **Platforms block SMTP; ship HTTPS email.** Outbound mail ports are closed on
|
| 92 |
-
Spaces, so email failed with "network unreachable." The Resend REST API over
|
| 93 |
-
443 works — and it needs a `User-Agent` header or Cloudflare 403s you.
|
| 94 |
-
5. **A custom frontend on a Gradio Space wants `gradio.Server`, not `.launch()`.**
|
| 95 |
-
Mounting a React UI and calling `app.launch()` made Gradio's SSR startup check
|
| 96 |
-
hit `gradio_api/startup-events` and 404. The fix: treat `gradio.Server` as the
|
| 97 |
-
ASGI app it is and serve it with uvicorn (Docker SDK) — custom look, still a
|
| 98 |
-
Gradio app.
|
| 99 |
-
|
| 100 |
-
## What's next
|
| 101 |
-
|
| 102 |
-
The agent traces are already public as a Hub dataset; next I want to grow the
|
| 103 |
-
fine-tune set so the Interpreter model keeps sharpening on exactly the phrasing this
|
| 104 |
-
task needs, and feed real family usage back into that loop.
|
| 105 |
-
|
| 106 |
-
*Not investment advice. Engine logic unchanged from the original; the app around
|
| 107 |
-
it — and the model that explains it — is what this hackathon built.*
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|