One tool decides, the other sets the stop — ai-hedge-fund and LangAlpha in a single order
Taking a judgment from ai-hedge-fund, which only produces an allocation, attaching the invalidation, entry and target LangAlpha produces, and putting the result
One tool decides, the other sets the stop — two tools combined into a single orderDone when you can take a judgment from a tool that only produces an allocation, attach a stop and a target from another tool, and turn the pair into one order.
| What this does | Takes a judgment from a tool that produces only a weight and a share count (ai-hedge-fund) and attaches a tool that produces an invalidation price, an entry and a target (LangAlpha), making one order out of the two |
| Who it fits | Anyone whose tool's judgment has stopped at "so where do I put the stop" |
| Who it doesn't fit | Anyone who wants to use one tool as it comes. Combining two doubles the handwork |
| What you end up with | One order with all six fields filled — symbol, direction, quantity, entry, invalidation, target — and a table saying which tool each number came from |
| What you need · cost | Code free (both open source) · model about $0.38 per cycle · market data free (free path) · a weltiq paper account |
| Time · difficulty | 40 minutes if both tools are already installed · medium (terminal and editing JSON) |
| Relationship to weltiq | It connects — the two tools' outputs are merged into one strategy document and put into resident execution |
| Test environment · date | 2026-09-22 (KST) · macOS 26.6 · US equity NVDA · model openai/gpt-6-astra |
| Disclosure | weltiq pays me to write this series. Everything below I ran myself, and the items where the combination loses are written down as they are |
1. What this is
Run thirty or so open-source tools that issue investment judgments and you start to see that judgments come in two shapes.
The level shape says "buy at $222.94, treat it as wrong below $221.56, sell at $228.50." The allocation shape says "10% of capital in this name." It does not say where to cut.
That does not make the allocation shape a bad tool. It is a portfolio manager's register, and not naming a stop is normal in that seat. The problem appears when you turn that judgment into an actual order.
Whoever takes the order asks where you will admit you were wrong. weltiq's discretionary order entrance refuses it when that field is empty, too.
POST /v1/orders/oneoff {symbol: NVDA, side: buy, qty: "1", price: "222.27", thesis: {...}}
→ 422 {"code":"invalidation_required","detail":"청산 조건(px_below/px_above)이 필요합니다"}
So what happens then. Most systems fill that field in for you. weltiq does so on the resident execution path — if the entry intent carries no invalidation, it takes the protective stop width as a band and builds one itself. The default is 2%.
So the order goes out. Except that the place where that trade admits it was wrong was not chosen by the tool — it is a system default. Later, when you look at the scorecard, the two are indistinguishable.
- "It came out because support broke" — the tool's argument was wrong
- "It came out because it fell 2%" — you simply lost
What this article does is move the authorship of that field over to the tool side. Not to raise returns, and not because you must combine tools to get an order out.
| Repository | Stars | License | Judgment shape | |
|---|---|---|---|---|
| ai-hedge-fund | virattt/ai-hedge-fund |
40k+ | MIT | Allocation (weight, share count) |
| LangAlpha | ginlix-ai/LangAlpha |
1.7k | Apache-2.0 | Levels (entry, invalidation, target) |
2. Installing
Installing the two tools was covered in Part 2 and Part 18. Here I write down only what the combination needs.
The ai-hedge-fund side — one mandate file
This tool does not so much "run a tool" as run one mandate (a fund charter). Which investor models to use, how much capital there is, and how much of it may go into one name are all in that file. In a combination, this file is the participant's name tag. Pin it before you run and do not change it during the cycle.
# ~/.hedge-fund/mandates/a2-01.yaml
name: a2-01-desk
strategies:
- name: discretionary
models: [buffett, lynch, druckenmiller] # three LLM investors only
risk:
max_position_pct: 0.10 # 10% of the book per name
max_gross_exposure: 1.0
capital: 2500
The book is set at $2,500 because the paper account's order-limit approval is $700 per order. The book and the position cap are mine to set, and the share count is computed by the tool from those two.
The LangAlpha side — one place it gets stuck
| Where it gets stuck | Symptom | Workaround |
|---|---|---|
| The agent runtime needs Docker | Throw it a turn and you get Starting workspace… followed by a single line, "An error occurred" |
With no Docker, load the 8 skill documents that hold the judgment content as a system prompt and throw it at the same model directly |
This article ran on that workaround. So the LangAlpha this article saw is not all of it — sandboxed Python execution, subagents, cumulative memory and web search are missing. With Docker you may get different values.
For market data it uses LangAlpha's backend free Yahoo path (/api/v1/market-data/*) as-is.
It comes up without Docker and needs no key.
3. First result — seeing what the allocation shape leaves blank
python drive.py ~/.hedge-fund/mandates/a2-01.yaml --tickers NVDA \
--date 2026-09-19 --model openai/gpt-6-astra --out run1.json
This is what it looks like when it works.
"target_weights": {"NVDA": 1.0},
"clamps": [{"limit": "max_position_pct", "before": 1.0, "after": 0.1}],
"final_weights": {"NVDA": 0.1},
"orders": [{"ticker": "NVDA", "side": "buy", "quantity": 1, "price": 222.27}],
Three investors each judge (their reasoning sentences come out too), the results are weighted by confidence into a target weight, the mandate's per-name cap clamps it to 0.10, and $2,500 × 0.10 ÷ $222.27 = 1 share.
There is a quantity and a price, but no stop, no target and no entry.
price is not an entry price — it is that day's mark (the close) carried straight through. That is the shape of the allocation form.
Here is the result of three runs under the same conditions.
| Run | buffett | lynch | druckenmiller | Order |
|---|---|---|---|---|
| 1 | buy 0.76 (confidence 76) | buy 0.88 (88) | buy 0.89 (89) | buy 1 share |
| 2 | buy 0.76 (76) | buy 0.86 (86) | buy 0.88 (88) | buy 1 share |
| 3 | buy 0.77 (77) | buy 0.82 (82) | buy 0.89 (89) | buy 1 share |
4. Getting the most out of it — ask the other tool for the empty fields
4.1 LangAlpha only answers if you give it a mandate document
Just throw it "trade NVDA and make me a profit" and this tool withholds everything. In Part 18 I threw it three times and got "price stop withheld" three times.
To make it produce levels you have to fill in four fields of a delegation document. This is what this article used, verbatim.
- Risk posture: the loss budget is 150 basis points of the book, read as an absolute cap
that must hold in any state. The book is USD 2,500, so the cap is USD 37.50.
- Horizon: ONE US regular session (09:30-16:00 ET) starting at the next open. ...
A multi-quarter horizon is outside the mandate and must not be substituted for it.
- The analysis this pitch rests on: none is finished. ... Deliver a screen-grade pitch
that says the underwriting is still owed rather than declining to state an expression.
- Execution: the order goes to a paper broker that requires, before it will accept
anything, a written reason, a price level below which the idea is wrong, and a quantity.
Writing "one session" is not enough. This tool's default horizon is 2 to 8 quarters, so you have to state explicitly that a multi-quarter horizon must not be substituted before it answers inside a single day. "Do not withhold; deliver it screen-grade" is also required, or it withholds again.
4.2 The sentence the combination adds — the division-of-labour clause
This is the new part of this article. Asked plainly, LangAlpha tries to decide the size as well. Which defeats the point of using the allocation shape at all. So this block goes in front of the question.
DIVISION OF LABOUR FOR THIS PITCH — read this before Step 1:
- The instrument, the direction and the quantity are ALREADY DECIDED by a separate
allocation desk whose record is quoted below. They are inputs, not questions.
Do not change them, do not re-argue them, and do not withhold the expression
because you would have sized it differently.
- What is NOT decided ... is exactly three fields: (1) the invalidation price ...
(2) the entry condition for the next open; (3) the target.
- The loss budget above constrains the DISTANCE of the stop, not the purchase outlay.
And then the question, in one line:
Another desk has already decided to buy NVDA. Give me the price level below which that idea is wrong, the entry condition for the next open, and the target. And explain why you set them where you did.
After that, the JSON the allocation tool produced is appended mechanically — the mandate name, the capital, the position cap, the target weights, the clamps, the one order line, and the three investors' reasoning sentences in full. No human summarizes them. Summarize, and at that moment the summarizer has entered the judgment.
4.3 The answer that came back
The same four numbers came out all three times.
| Field | Value | The reason the tool gave |
|---|---|---|
| Invalidation | $221.56 | The previous session's low. "losing the entire latest session's trading range defeats this particular support-and-recovery thesis. It does not prove the business is impaired." |
| Entry floor | $222.94 | The previous session's open |
| Entry ceiling | $223.87 | Computed by the tool. The point at which the reward to the target is twice the risk to the stop |
| Target | $228.50 | The previous session's high. "an observable retest objective... not a fundamental fair-value estimate." |
And all three times it left the allocation alone.
Depth: first pass. I retain the desk's direction, instrument and quantity.
4.4 One answer nobody asked for
All three times, unprompted, it separately wrote down that the loss budget I had given it was not a kind it could honor.
At the ceiling entry, the unchanged allocation's ideal stop loss consumes approximately 6.2% of the stated loss budget, before costs. But a sufficiently large discontinuous decline can exceed that budget; a stop-limit can instead fail to execute altogether.
Fill exactly at the stop and it uses 0.06 times the budget; a total loss on the stock uses 6.0 times it. In other words, an "absolute cap that holds in any state" is not guaranteed by a long position in common stock in the first place.
The allocation-shaped tool neither receives nor interprets sentences like this. Attach the level shape and it comes along. An unexpected gain from a job that started out as filling in an invalidation field.
5. Moving it into weltiq — an integration experiment
I built two strategy documents on the same symbol, the same direction and the same quantity. The only difference is where the invalidation came from.
A — allocation shape alone. No entry condition and no exit levels.
entry: (position.size = 0) → enter long L
exit: internal exit rules · protective stop 2%
That "2%" is not a value the tool produced. It is the rule block's default, and resident execution takes that width as a band and builds the invalidation price itself. That is where the allocation shape's "place where it admits it was wrong" comes from.
B — the combination. LangAlpha's four numbers went straight into the four fields.
entry: ((position.size = 0) AND (minutes after open ≤ 1)) AND ((open ≥ 222.94) AND (open ≤ 223.87))
→ enter long L · price-condition exit L (stop=221.56, limit=228.50)
exit: internal exit rules · protective stop 1.0%
"minutes after open ≤ 1" carries over the tool's "only at the official regular-session open, never chasing it later." In Part 18 I could not carry this condition over, so it ended up entering any time of day once the range was touched.
Some fields were dropped on the way. There is nowhere to put a time-based exit (flatten unconditionally at 16:00 ET), and "a 150bp absolute loss-budget cap" has no corresponding concept.
5.1 The screen where the authorship of the invalidation actually changes
I wrote this before the US open, so I did not see a fill in resident execution. Instead I ran the same engine over the previous regular session's 390 one-minute candles and looked at the orders the two documents produce.
A — allocation shape alone
INTENT OPEN buy 1 share
invalidation = {"px_below": "217.6090"}
FILL buy 1 @ 222.082205 (first minute of the regular session)
No tool said 217.6090. It is the first candle's close, $222.05, minus twice the protective stop width.
Place an order from the allocation shape alone and this is how "where you admit you were wrong" gets settled.
B — the combination (after lowering the entry floor by one cent. Why that was necessary is in section 5.2, immediately below)
INTENT OPEN buy 1 share
invalidation = {"px_below": "221.5600"}
FILL buy 1 @ 222.082205 (first minute of the regular session)
Same symbol, same direction, same quantity, same minute, same fill price. The only difference is the invalidation — $217.61 built by the system on one side, $221.56 named by the tool on the other. This one field is what this article set out to do.
5.2 Except the combination could not even get in with its original values
To run B above I had to change one number. I lowered the entry floor from $222.94 to $222.93, by one cent. Before that change, the entry never triggered once.
The cause is not the judgment but the data source. The tool read September 21's open as $222.94 from daily candles, while the first one-minute candle of the regular session, which is what the order side sees, opened at $222.93.
Line up all three levels and the same thing repeats.
| Value the tool named | What it called it | Measured on 1-minute candles | Difference |
|---|---|---|---|
| $222.94 | 9/21 open | 222.93 | −1 cent |
| $228.50 | 9/21 high | 228.49 | −1 cent |
| $221.56 | 9/21 low | 221.58 | +2 cents |
All three levels stand one tick outside the actual range. Which is why, in this replay, neither the entry, nor the target, nor the stop was ever touched.
The tool is not wrong. This was lost in transit. Daily and one-minute candles differ in how they handle the open and close and in whether pre- and post-market are included, so the endpoints do not line up. When you attach a level-shaped tool, the market data you feed it has to be the same series the order side is looking at. This article did not do that, and paid one cent for it.
(That one-cent-lower value is not a number the tool produced, so the source table below carries the original value and the change is recorded here.)
6. After using it
6.1 The source table — the key picture of this article
A table putting, next to every number in the final order, which tool and which call it came from.
| Field | Value | Source |
|---|---|---|
| Symbol | NVDA | Mission input (this tool does not pick symbols itself) |
| Direction | buy | ai-hedge-fund runs 1, 2 and 3 |
| Size | 1 share | ai-hedge-fund ($2,500 × 0.10 ÷ $222.27) |
| Entry floor | $222.94 | LangAlpha runs 1, 2 and 3 |
| Entry ceiling | $223.87 | LangAlpha runs 1, 2 and 3 |
| Invalidation | $221.56 | LangAlpha runs 1, 2 and 3 |
| Target | $228.50 | LangAlpha runs 1, 2 and 3 |
| Protective stop % | 1.0% | LangAlpha run 2, verbatim |
| Reasoning text | verbatim | Both, combined |
Numbers I produced: 0. What I did was four things, none of them numeric — pin the two mandate files in advance, write the question, assemble one tool's output into the other's prompt mechanically, and move the four numbers the tool produced into four fields of a rule document.
Two places to declare. One, for the protective stop I first computed and entered (223.87 − 221.56) / 223.87 = 1.03%, then changed it to the "−1.0%" the tool wrote in its own text — the second decimal place was a precision I had invented. Two, the one cent in section 5.2 is a number I changed, so it is not in the table above, and that run was not counted as a result for the combination either.
6.2 The three side by side
As a third branch I also produced three judgments of my own, with no tool (the control). They are all in the verification record.
| Alone | Combination | Me alone | |
|---|---|---|---|
| Six fields filled | 4/7 | 7/7 | 7/7 |
| Author of the invalidation | System default | The tool | A person |
| Consistency over 3 runs | Share count 3/3 identical | All four numbers 3/3 identical | All three invalidations differ ($224.60 / $225.50 / $226.00) |
| Cost per cycle | $0.05 | $0.38 | — |
On field coverage alone, the combination and me on my own come out the same. Where they part is in two places.
First, consistency. The combination produced the same numbers three times out of three, and I pointed at a different place all three times. All three were plausible, and I cannot give a reason for which one is right.
Second, the kind of reason. The tool attaches an observed fact — "the previous session's low" — next to the invalidation, and goes on to state what that fact would disprove ("the support-and-recovery thesis breaks; it does not mean the business is impaired"). My $224.60 is a number that is not in the data.
6.3 What the combination loses
- It doubles the handwork. One delegation document, one division-of-labour block and one assembly script on top.
- Cost goes up more than sevenfold. $0.05 → $0.38. The absolute amount is small, so it was not a problem.
- The level-shaped tool never says "buy now". All three times the concluding verb was "wait for evidence." It is a conditional hypothesis, not an instruction. Tally it as buy/wait and this tool is always waiting.
- Carry levels over as-is and one tick keeps them from triggering. Exactly as in section 5.2. The allocation shape alone has no levels, so there is nothing to misalign; the combination takes on this problem the moment it takes on levels. It is a new failure path the combination created.
- The same prompt produced different judgments on the same day. On the allocation side, the three runs in the morning had buffett neutral all three times (confidence 68), and the three in the afternoon were buy all three times (76, 76, 77). The prompt hashes were completely identical. Three runs agreeing does not mean six will.
6.4 Half the reason this combination means anything is a constraint
Stated honestly. Half the reason this combination means anything is that weltiq demands that you say where you will admit you were wrong.
Where that demand does not exist, you can send an allocation tool's output straight through as an order and be done, and nobody asks who authored the invalidation. In that case this combination may not be needed at all.
Except that where the demand does not exist, when you later look at the scorecard there is also no way to separate "it came out because the argument was wrong" from "it came out because it just fell". Which of the two is better is not something this article will conclude.
7. What to do next
This combination worked because the two tools do not produce overlapping fields. So there was nothing to choose between. When two tools put different values in the same field, you have to fix and publish the rule first — the next part attaches two tools whose directions are flatly opposed and covers that case.
weltiq setup 1. Sign up → My → Connections → issue a key 2.
claude mcp add weltiq --transport http https://weltiq.ai/mcp --header "Authorization: Bearer <key>"3. Paper only. Live orders go through a separate approval process
Tony Brown. The raw verification record, the attempts that failed and the defect list are in the verification record.