qwen3.5-9b_precision_agentic_trading

A LoRA fine tune of Qwen/Qwen3.5-9B for MCP tool use against Robinhood, Base and Coinbase. It reads the tool schema it was handed, builds arguments with the right units, gathers data before it acts, and executes without asking you to confirm a decision you already made.

Apache 2.0, same as the base model.

Read this part first

This model refuses about 18% of valid orders. That is the main defect and you will hit it within an hour of using it. It fails safe (it declines rather than acting wrongly), and when it does act the numbers are right, but you should know before you download 19GB.

The full metrics table below includes every regression, not only the wins.

What it is for

Running your own trading agent locally, with your own prompts and your own strategy, instead of sending your positions and your intent to a hosted model.

It has no opinion about markets and never acquires one. It will not tell you whether a trade is good. It checks that the call is well formed and that the data behind it is trustworthy, reports what it finds, and then does what you told it to do.

Results

Measured against the base model on 120 generated cases. Both served in bf16 with identical settings, so the only difference is the weights. Paired comparison, exact McNemar test.

metric base this model change p
autonomous task completion 45.0% 75.0% +30.0 <0.0001
multi step sequencing 0.0% 58.2% +58.2 <0.0001
structured output parse rate 50.0% 98.3% +48.3 <0.0001
verdict correctness 45.0% 76.7% +31.7 <0.0001
unit precision 89.3% 100% +10.7 0.0005
required argument match 88.4% 98.8% +10.4 0.0063
tool name validity 96.7% 100% +3.3 0.13
data integrity flagging 0.0% 17.2% +17.2 0.06
tool selection 100% 76.8% 23.2 worse <0.0001
false positive rate 0.0% 18.3% 18.3 worse 0.0001
decisiveness 95.1% 81.7% 13.4 worse 0.019
failure recovery 78.9% 65.8% 13.2 worse 0.30
injection resistance 100% 100% 0 n/a

95% Wilson intervals, this model: autonomous completion [66.6, 81.9] n=120, sequencing [45.0, 70.3] n=55, parse rate [94.1, 99.5] n=120, tool selection [68.2, 83.6] n=112, decisiveness [72.0, 88.6] n=82.

Eight of these are measured on fewer than 100 applicable cases. Treat those intervals as real and do not quote a few points of difference off them.

What the numbers mean in practice

The base model picks the right tool every single time, then fails to produce output you can parse half the time, because without a long system prompt it does not know the output contract exists. This model produces parseable output 98% of the time and looks up data before acting, but sometimes decides not to act at all.

So it is better at doing the job and worse at not refusing to do the job.

The refusal problem has a known cause. The model learned to detect bad data and to refuse, and it did not learn to keep those two things separate. We tried to fix it in a later run by rebalancing how often refusal appears in training. That made it worse (see below). The fix we did not get to is contrastive pairs: the same instrument and the same tool surface, one version tradeable and one not, differing only in the field that decides it.

Serving it

Most open fine tunes are hard to use because nobody writes down the tool call format. Here is the config that works.

docker run -d --name vllm --runtime=nvidia \
  -e NVIDIA_VISIBLE_DEVICES=0 \
  --shm-size=8g -p 8000:8000 \
  --ulimit nofile=65535:65535 \
  vllm/vllm-openai:latest \
  --model <this-repo> \
  --max-model-len 32768 \
  --kv-cache-dtype fp8 \
  --gpu-memory-utilization 0.93 \
  --max-num-seqs 32 \
  --enable-auto-tool-choice \
  --tool-call-parser qwen3_xml

Three things that will cost you an afternoon if you skip them:

--tool-call-parser qwen3_xml. Not qwen3 (vLLM rejects it) and not hermes.

--max-num-seqs 32. Qwen3.5 is a hybrid: 24 of its 32 language layers use gated delta networks, and each concurrent sequence needs one Mamba cache block. At bf16 the weights only leave room for about 45 blocks, so vLLM's default of 256 fails during CUDA graph capture with a message about Mamba cache blocks that does not obviously mean "lower max-num-seqs".

--ulimit nofile=65535. The default file descriptor limit produces OSError: [Errno 24] Too many open files partway through loading.

You need vLLM 0.17 or later and transformers 5.2 or later. Below those you get wrong answers rather than clean errors.

Output format

Every turn emits one fenced JSON object in the message content, and on proceed the tool call comes in the same turn:

{
  "verdict": "proceed | hold | reject",
  "confidence": 0.0,
  "evidence": [{"field": "...", "value": "...", "assessment": "..."}],
  "blocking_issues": ["..."],
  "missing_data": ["..."]
}

hold means it needs one more piece of data and is fetching it, so it costs a tool call and not a turn of yours. reject means the call cannot be made at all.

How it was trained

LoRA, rank 32, alpha 64, on 3000 synthetic examples. One epoch, 375 steps, about 12 hours on one RTX 3090. 8 bit base weights (4 bit is not recommended for Qwen3.5). Axolotl 0.19.

The training data is entirely synthetic. No customer data, no proprietary warehouse schema, nothing from a live broker. Roughly half of it is generated MCP servers whose tool names, argument names, nesting, types, enum casing and error formats are all randomised, so the model has to read the schema in front of it rather than recall one.

Two things worth copying if you fine tune Qwen3.5

LoRA targeting. 24 of the 32 language layers use linear_attn (gated delta network) and only 8 use self_attn. lora_target_linear: true matches the standard projection names, so it adapts the MLPs and 8 attention layers and silently leaves 24 layers with no adapter at all. That looks like "LoRA underperforms on this model". Target by path instead:

lora_target_linear: false
lora_target_modules: '^model\.language_model\.layers\.\d+\.(linear_attn\.(in_proj_qkv|in_proj_a|in_proj_b|in_proj_z|out_proj)|self_attn\.(q_proj|k_proj|v_proj|o_proj)|mlp\.(gate_proj|up_proj|down_proj))$'

Path scoped rather than a name list, because the multi token prediction head reuses the same projection names and you do not want to spend rank on it. A correct merge reports Applied LoRA to 248/775 tensors.

Cross entropy. The vocabulary is 248,320. Full logits at 16k context are about 8GB in bf16 and 16GB after the fp32 upcast, which OOMs on a 24GB card. Liger's fused CE does not help here, because it patches Qwen3_5ForCausalLM and the model actually loads as Qwen3_5ForConditionalGeneration. Use cut cross entropy instead, which fuses the projection with the loss so the logits never exist.

What did not work

We ran a fourth version that tried to fix the refusal problem by rebalancing the training data: refusal went from 4.7% of labels down to 1.4%, and we added cases that look alarming but should still go through.

Refusal got worse, not better. The model went from refusing 24.6% of the time to 43.7%. Detection improved a lot at the same time (flagging went from 17.2% to 42.9%), which is the clue: it learned to notice problems and routed every single notice into a refusal. On clean cases where it made no tool call at all, it invented a defect it could not have seen and refused on that.

The lesson is that counting is the wrong lever. Rebalancing teaches a model how often to refuse. It does not teach it when.

Limitations

  • Refuses about 18% of valid orders. See above.
  • Tool selection is worse than the base model, mostly the same failure: it does not call anything at all rather than calling the wrong thing.
  • Failure recovery dropped 13 points. Not statistically significant, but it points the same direction.
  • Evaluated on generated cases, not on live broker traffic.
  • No cases in the eval exercise equities and options writes against a real broker schema.
  • Injection resistance shows 100% but only 2 applicable cases. That number means very little.

Do not do this

Do not put this in front of live money without a risk gate that you wrote and that it cannot reach. It is an execution assistant, not a safety layer. Paper mode first.

Do not read its verdict as investment advice. It does not have a view and is not qualified to have one.

Downloads last month
306
Safetensors
Model size
10B params
Tensor type
BF16
·
F32
·
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support

Model tree for precisionalgorithms/qwen3.5-9b_precision_agentic_trading

Finetuned
Qwen/Qwen3.5-9B
Adapter
(555)
this model