Local-first financial intelligence agent. A multi-agent pipeline that pulls live Hyperliquid market data, reasons over it with a fully on-device LLM via
@qvac/sdk, and grounds its output with local RAG — zero cloud inference.
Built for the QVAC Hackathon I — Unleash Edge AI (General Purpose track). MIT licensed. Everything runs on the host machine.
▶ Demo video (2:31): https://youtu.be/7NrfnMkW6PM
- No cloud, no API keys for inference. Every token is generated locally through
@qvac/sdk(llama.cpp under the hood). The only network calls are to Hyperliquid's free, public, no-auth market data endpoint. - Runs on a constrained laptop. Developed and benchmarked on a Surface Pro 7 (Intel i7-1065G7, 16 GB RAM, integrated Iris graphics, Windows 11) — well inside the ≤32 GB General Purpose track.
- Evidence-first. Every inference call and data fetch is logged with timestamp,
model name, token count, and tokens/sec to
logs/run-<date>.jsonlfor the hackathon evidence bundle.
Hyperliquid public API ──► tools/hyperliquid.js ──► tools/market.js (tool calling)
│
data/ (knowledge base) ──► agents/rag.js ──────────────┤
▼
agents/analyst.js ──► agents/signal.js
(factual summary) (structured signal)
└──── all inference via lib/qvac.js → @qvac/sdk ───┘
│
logs/run-*.jsonl (evidence)
(Above: the default pipeline mode. The orchestrator, scanner, and psy
agents run as their own modes — see the table and CLI below.)
| Agent | Role |
|---|---|
| analyst | Fetches live data for a coin, produces a factual market summary. Open-interest/price deltas come from a persisted prior snapshot (lib/state.js) — never invented. |
| rag | On-device vector retrieval (EmbeddingGemma-300M → ragIngest/ragSearch) over the local data/ knowledge base, with a keyword fallback. |
| signal | Emits a structured, hedged signal as GBNF-constrained JSON (responseFormat: json_schema) so the object is always valid. |
| orchestrator | Tool-calling agent: the LLM picks Hyperliquid tools to answer free-form questions (see Tool calling below). |
| scanner | Sweeps the whole perp universe, flags anomalies (extreme funding, big 24h moves, OI surges) with a deterministic score, and has the LLM summarize the watchlist. |
| psy | Behavioral-risk / discipline reviewer running on MedGemma-4B (QVAC's medical model) — critiques a signal for FOMO, over-leverage, chasing. Demonstrates "right model for the job" + the Psy-model bonus. |
| journal | On-device vector RAG over your private trade journal (journal/, gitignored) — feeds your own notes/rules into the signal & psy agents. Never leaves the device. |
| trader | Turns a signal into a risk-sized order ticket and (opt-in) signs + places it on Hyperliquid testnet locally. Keys stay on-device; only the signed order is transmitted. See TRADING.md. |
- Node.js ≥ 22.17 (QVAC requires ≥18; quickstart targets ≥22.17).
npm ≥ 10.9. - ~5 GB free disk for model artifacts; 16 GB RAM is comfortable for ≤8B Q4 models.
- Supported hosts:
win32-x64,darwin-arm64/x64,linux-arm64/x64.
Windows note: use a real Node 22 (e.g. via
nvm-windows:nvm install 22.17.0 && nvm use 22.17.0).nvm useneeds an elevated shell to switch the global symlink; alternatively call the versioned binary directly. Native QVAC binaries ship prebuilt forwin32-x64, so no C++ toolchain is needed.
git clone <this-repo> && cd qvac-finance-agent
npm install # pulls @qvac/sdk + native runtimes
# 1. Prove on-device inference works (downloads a ~0.8 GB model on first run):
npm run quickstart
# 2. Run the vertical slice — live BTC data -> local LLM commentary:
npm run slice # or: node src/index.js --slice ETH
# 3. Run the full multi-agent pipeline (analyst -> vector-RAG -> signal):
npm start # or: node src/index.js SOL
# 4. Ask the tool-calling agent a free-form question:
node src/index.js --agent "What is the price and funding of ETH?"
# 5. Scan the whole market for anomalies (extreme funding / moves / OI surges):
node src/index.js --scan 6
# 6. Behavioral-risk review of a signal using MedGemma-4B (downloads ~2.6 GB once):
node src/index.js --psy ETH
# 7. Plan a risk-sized order from the signal (DRY-RUN — sends nothing):
node src/index.js --trade BTC
# ...and place it on Hyperliquid TESTNET (opt-in, needs both flags + setup):
node src/index.js --trade BTC --execute --confirm # see TRADING.mdFirst run downloads the model from the QVAC distributed registry; subsequent runs are offline-capable.
The package exposes a qvac-finance-agent binary (alias qvac-fin), so you can run
it as a command from any directory instead of node src/index.js:
npm install -g . # from the repo (or: npm link for development)
qvac-finance-agent --help
qvac-finance-agent ETH # full pipeline
qvac-finance-agent --slice SOL # vertical slice
qvac-finance-agent --scan 8 # market anomaly scan
qvac-finance-agent --agent "funding on BTC?" # tool-calling agent
qvac-finance-agent --psy ETH # MedGemma behavioral review
qvac-finance-agent --trade BTC # plan a risk-sized order (dry-run)
qvac-finance-agent --versionData and logs resolve relative to the install location, so the command works from any
working directory. The CLI runs on Node ≥ 22.17 (it uses whichever node is on your
PATH); inference still runs fully on-device via @qvac/sdk, with models cached in
~/.qvac after the first download.
The orchestrator agent lets the local LLM choose which Hyperliquid tool to
call. Native tool-call token emission was tested across LLAMA_TOOL_CALLING_1B,
QWEN3_4B, and every toolDialect — sub-8B models on this SDK build / hardware
did not reliably emit parseable tool-call frames. So the agent uses
GBNF-constrained tool selection instead: the model outputs its decision as
JSON constrained by a responseFormat: json_schema, which is deterministic even
on a 1B model. Each step is {action:"call_tool",tool,arguments} or
{action:"final",answer}; we execute the chosen tool against the live API, feed
the result back, and loop. Tool specs live in src/tools/market.js.
Verified on-device (1B): --agent "price and funding of ETH" →
get_coin_snapshot({coin:"ETH"}) → "ETH ~$1573.9, funding ~7.95% annualized."
Configure via the QVAC_MODEL env var. Accepts a FEATURED alias (src/lib/qvac.js)
or any raw @qvac/sdk constant. Default: LLAMA_3_2_1B_INST_Q4_0.
| Alias | SDK constant | Params | Notes |
|---|---|---|---|
LLAMA_1B |
LLAMA_3_2_1B_INST_Q4_0 |
1B | Default; fastest, used for the slice. |
LLAMA_TOOL_1B |
LLAMA_TOOL_CALLING_1B_INST_Q4_K |
1B | Tuned for tool calling (multi-agent). |
QWEN3_4B |
QWEN3_4B_INST_Q4_K_M |
4B | Strong small reasoner (optional upgrade via QVAC_MODEL). |
QWEN3_8B |
QWEN3_8B_INST_Q4_K_M |
8B | ~5 GB — practical ceiling on 16 GB RAM. |
MEDGEMMA_4B |
MEDGEMMA_4B_IT_Q4_1 |
4B | On-device medical reasoner (closest available to QVAC "Psy"). |
EMBED |
EMBEDDINGGEMMA_300M_Q4_0 |
0.3B | Embeddings for on-device RAG. |
Note on the "Psy" bonus:
@qvac/sdkv0.12.2 does not export aMEDPSYconstant — the available medical model is MedGemma-4B. If QVAC MedPsy is published to the distributed registry it can be loaded by name via the registry API; this is tracked as an open item.
On 16 GB RAM with integrated Iris graphics (no discrete GPU), ≤8B Q4 is the ceiling and inference is CPU-bound and slow — see Performance below.
Surface Pro 7 · i7-1065G7 (4c/8t) · 16 GB · Intel Iris Plus (integrated) · Win 11 · Node 22.17.
| Stage | Model | Result |
|---|---|---|
| First load (cold, incl. ~0.8 GB download) | LLAMA_1B | ~113 s |
| Warm load (cached) | LLAMA_1B | ~5 s |
| Generation (warm) | LLAMA_1B | ~4.1 tok/s (116 tok / 28 s) |
| Generation (cold) | LLAMA_1B | ~1.2 tok/s |
| Generation | MEDGEMMA_4B | ~1.7 tok/s (340 tok / 203 s) |
| Embedding query | EMBEDDINGGEMMA_300M | ~58 ms |
Inference runs on CPU (no GPU offload observed), so throughput is low. Practical
implications: keep prompts/outputs short, prefer the 1B for interactive modes
and the 4B for the psy review, and treat 8B as "works but slow." All timings are
logged to logs/run-*.jsonl.
Context window: the Llama-3.2-1B build here exposes a 1024-token window, so the agents cap what they feed the model — RAG context is truncated (
MAX_RAG_CHARSinsignal.js) and tool results are capped in the orchestrator. This is a deliberate edge-AI constraint, not a bug.
POST https://api.hyperliquid.xyz/info with a JSON body. We use metaAndAssetCtxs
(mid/mark/oracle price, funding, open interest, 24h volume), l2Book, and
candleSnapshot. No API key, no rate-limit tier required.
src/
index.js entry/dispatch: --slice | --agent | --scan | --psy | --trade | default pipeline
agents/
analyst.js factual market summary (honest cross-run deltas)
signal.js schema-constrained JSON trade signal
rag.js on-device vector retrieval (+ keyword fallback)
orchestrator.js tool-calling agent (GBNF tool selection)
scanner.js market-wide anomaly watchlist
psy.js MedGemma-4B behavioral-risk review
journal.js on-device RAG over the private trade journal
trader.js risk sizing, journal-rule enforcement, execution guards
tools/{market,hyperliquid}.js tool specs + Hyperliquid client (retry)
tools/exchange.js local order signing + Hyperliquid testnet placement
lib/{qvac,logger,state}.js SDK wrapper + evidence log + snapshot store
scripts/
quickstart.js QVAC inference smoke test
data/ local RAG knowledge base (4 docs)
journal/ private trade journal (gitignored; README + EXAMPLE committed)
logs/ JSONL evidence logs (hardware + inference timings)
.env.example runtime env vars (model + testnet trading key)
TRADING.md trading agent: safety model + testnet setup
Educational demonstration of on-device AI. Not financial advice. Analysis modes
read public market data only. The optional trading agent is testnet-only by
default, dry-run unless you pass both --execute and --confirm, and gated by
the safety model in TRADING.md. Use at your own risk.
MIT — see LICENSE.