AlpacaTradingAgent from install to a paper order — 13 agents decide and Alpaca fills

TradingAgents with an Alpaca paper account bolted on. US equities and Bitcoin go through the same pipeline, and one full cycle is followed from judgment to an a

Part 31. AlpacaTradingAgent — 13 agents decide, and Alpaca fills

Done when you have watched one cycle run from an agent team's judgment through to an order in an Alpaca paper account.

What it does 5 analysts, 3 researchers, 1 trader and 4 risk agents debate one symbol, produce BUY/HOLD/SELL with stop and target prices, and send that judgment to an Alpaca paper account as an order
Who it fits Anyone who wants judgment and execution as one body · anyone who wants US equities and Bitcoin handled by the same tool · anyone who already has an Alpaca account
Who it doesn't fit Anyone trading on seconds (one run takes 1 minute 40 seconds) · anyone without an LLM key · anyone expecting the broker to hold a stop automatically (it doesn't for crypto)
What you end up with A web dashboard showing 13 agents working in sequence, and one filled Alpaca paper order produced by their conclusion
What you need · cost Code free (Apache-2.0) · LLM key required, $0.021 per run · market data free from Alpaca · writing your own rules, as in section 5, is a paid weltiq feature (free accounts work from templates)
Time · difficulty 10 minutes to install, 2 minutes for the first run · medium (you need to be comfortable with a virtual environment and .env)
Test environment · date macOS · Python 3.12.13 · 2026-09-22 · model openai/gpt-5.6-luna via OpenRouter
Disclosure I write this series under a partnership with weltiq. Every command and number here was measured from real runs in the environment above, and all orders are simulated. The BUY/HOLD/SELL, stops and targets the tool produces are entirely that tool's output, not my opinion

Raw logs and the full record of all 6 runs: reports/a/31-alpaca-trading-agent/


1. What this is

TradingAgents, covered in Part 12, was a framework where several agents debate and issue a rating. It was a good tool, but crucially it cannot place orders. The upstream repository does not contain a single line of submit_order or TradingClient. Getting from judgment to fill means a human carries it by hand.

AlpacaTradingAgent is an extension that fills that gap with Alpaca. It attached a broker to the same analysis engine, and in doing so put US equities and Bitcoin spot onto the same pipeline.

Repository huygiatrng/AlpacaTradingAgent · ★271 · 97 forks · Apache-2.0
Last updated 2026-09-05
Upstream TauricResearch/TradingAgents (★107,991). A separate repository, not a git fork
Sibling Part 28's trading-bot-agent is the same root with Binance attached

The relationship between the three is easiest to see this way. There is one analysis engine, and the brokers attached to it differ.

Upstream has no broker and issues only a 5-step rating; this tool issues an executable action along with stop and target prices to Alpaca (equities plus crypto spot); trading-bot-agent issues a rating to Binance.

There are thirteen agents. Five analysts (market, social, news, fundamental and macro — macro added to upstream's four), three researchers (bull, bear, research manager), one trader, and four on risk (aggressive, conservative and neutral argue, and a risk manager makes the final call).

The biggest difference from upstream is not the team roster but the fact that there is a non-LLM layer between judgment and order. An order amount cap, a per-symbol concentration cap, daily-loss and drawdown circuit breakers, consecutive-rejection detection, and a kill switch that stops every order the moment one file exists — all of it runs on arithmetic alone. No matter what the model says, this layer is not persuadable.


2. Installing

git clone https://github.com/huygiatrng/AlpacaTradingAgent.git
cd AlpacaTradingAgent
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt          # 204 packages, about 770MB
cp env.sample .env

Five lines in .env are the minimum to make it run.

ALPACA_API_KEY=...            # Alpaca paper key
ALPACA_SECRET_KEY=...
ALPACA_USE_PAPER=True         # do not touch this line
LLM_PROVIDER=openrouter       # or openai / anthropic / google ...
OPENROUTER_API_KEY=...

Finnhub, FRED, CoinDesk and Reddit keys are optional. Without them the corresponding analyst simply runs with no source and no error is raised — which becomes a problem later, and it comes up again immediately below.

Where it gets stuck

Symptom Why What to do
The report opens with "The analyst did not return a complete technical narrative" The place where an analyst calls its tools reads an old LangChain format. Only the direct OpenAI path has a special case, so on other providers the tool calls vanish entirely and no error is raised In NormalizedChatOpenAI.invoke in llm_clients/openai_client.py, copy the response's .tool_calls back into additional_kwargs["tool_calls"] (twelve lines). Not applicable if you use an OpenAI key
The social and fundamental reports end with "no verified signals" The main source for both is an OpenAI-only web search tool. It does not attach on other providers No workaround. Market (Alpaca) and news (Google News) are fine, so a judgment still comes out
It cannot find a crypto symbol Notation Write BTC/USD, not BTCUSD

The first one is the nastiest. Because a conclusion comes out even with no data, it looks normal unless you read the body of the report. Measured on the same symbol and the same date: before the fix, 0 tool calls and a 734-character market report ("no substantive market data was provided"); after the fix, 15 calls and 2,493 characters ("recent close $227.30 … consider buying near $222.50"). The direction happened to be HOLD both times, but the first one is not a judgment.


3. First result

python run_webui_dash.py --port 9420

Open http://127.0.0.1:9420 in a browser. There is no login. On first open the safety-guardrail status sits at the top, and below it the positions and recent orders of your Alpaca paper account are already showing.

On the left, under Analysis Configuration, enter a symbol (NVDA for stocks, BTC/USD for crypto), pick the analysts, set the research depth, choose an LLM provider and model, and press Start Analysis. Choose OpenRouter and the model field becomes free text, so write the full ID, like openai/gpt-5.6-luna.

In the Analysis Status table on the right, the thirteen agents appear one line at a time and complete in order.

The thirteen agents finishing in sequence

The Analysis Status table is this tool's progress screen. COMPLETED fills in across five analysts → three researchers → the trader → four risk agents. When all thirteen lines are green, one run is finished. Below the table, Tool Calls: 26 | LLM Calls: 13 | Generated Reports: 13 is the basis for what this run cost — if tool calls are 0, that is the section 2 problem, and you can see it immediately.

One NVDA run took about 1 minute 40 seconds and cost $0.021. When it ends, the final verdict appears below in Decision Summary, and all thirteen reports attach as tabs under Agent Reports & Audit Trail. Those reports are this tool's real output.

The market analyst report — the part where entry, stop and target are laid out as a table

The Summary Table at the end of the report is the most important picture in this article. Entry $224–$226 pullback, or confirmation of a break above $228.87, target $234.75, stop $218.00, reward/risk about 1.3:1, current action wait for a pullback or breakout confirmation. When these fields are filled with numbers, it worked — this is where this tool parts ways with tools that hand you a single word like "buy" or "wait". Above it, Invalidation even states the condition for being wrong: a close below $220.92 weakens the continuation case.

If you prefer a terminal, python -m cli.main runs the same pipeline. It is interactive and takes no arguments, though, so if you want to throw the same question at it repeatedly and automatically, call it from Python.

g = TradingAgentsGraph(config=cfg, selected_analysts=["market","news"])
state, decision = g.propagate("BTC/USD", "2026-09-22")   # decision = "BUY"|"HOLD"|"SELL"
intent = state["final_trade_intent"]                     # stop, target and sizing rationale live here

4. Getting the most out of it — the same question six times

You must not look at one run and write "a cautious tool". I ran 3 on Bitcoin and 3 on NVDA under the same conditions, with fresh agent memory each run so they could not see each other.

Run Symbol Final Confidence Stop First target Order
1 BTC/USD BUY medium 82,400 87,385 market buy
2 BTC/USD HOLD medium 84,800 88,500 none
3 BTC/USD HOLD medium 84,750 88,500 none
4 NVDA HOLD medium 220.92 234.00 none
5 NVDA HOLD medium 220.92 234.75 none
6 NVDA HOLD medium 220.92 234.75 none

The direction wobbles and the levels do not. The stop across the three NVDA runs was 220.92, exactly the same, and 228.87 appeared in the entry-condition sentence in all three. On Bitcoin, the 87,385 target was the same in two runs. What the judgment split on was "is this worth entering now", not "where is support".

The fact that the refusals are numeric is worth noticing too. One of the HOLD runs out of the six wrote this:

"the proposed pullback setup offers only about 1.7:1 reward-to-risk, below the required minimum." (that tool's output)

The entry guidance from the same run read like this:

"Conditional only: $84,900-$85,300 after VWAP/pivot reclaim and stabilization, or high-volume close above $87,385 with successful retest. Do not enter between triggers." (that tool's output)

Side by side with Part 12's original

Part 12 ran upstream TradingAgents 13 times on the same model and the same settings and came out like this. Here are this tool's 6 runs, measured the same way, alongside it.

Final verdict Upstream (13 runs) This tool (6 runs)
Sell 13 / 13 0 / 6
Wait 0 / 13 5 / 6
Buy 0 / 13 1 / 6

(The upstream 13/13 is measured on the trader's final proposal. The 5-step ratings from those same runs were also entirely on the sell side, Sell 7 and Underweight 6, and wait never appeared once.)

With samples of 13 against 6 and different symbols and dates, no causal claim can be made. But the fact that the prompts carry directly opposite instructions is confirmed in the source. Upstream's decision-maker is instructed: "The risk debate always contains conflicting stances; deciding which is stronger is the job, so conflict alone is not a reason to Hold. Commit to the stronger case…" This tool's risk manager is instructed: "Treat high contradiction or low freshness scores as reasons to reduce size, wait for confirmation, or choose HOLD/NEUTRAL."

Upstream punishes waiting; this tool authorizes it. The judgment here turns straight into an order, so there was presumably a reason to change it.

From judgment to a simulated order

I actually sent run 1, the BUY. The judgment object was passed straight into the very function the web UI's execute button calls.

Order $100 market buy, BTC/USD
Fill 0.001146041 BTC @ $85,559.67 — 1.5 milliseconds after submission
Left in the position 0.001143519 BTC
Difference 0.000002522 BTC = 0.2201% of the filled quantity. Alpaca took its fee in the coin itself
Stop 82,400 Never reached the broker

The dashboard right after the fill — guardrails and the Alpaca paper account

The six Safety Guardrails panels at the top are the non-LLM layer — kill switch, order cap $25,000, concentration limit, daily loss -1.09% today, drawdown, consecutive rejections 0 in a row. Bottom left, Open Positions is the position that just appeared (0.001143519 BTC, entry $85,559.67), and the top row of Recent Orders on the right is the order placed for this article. This is the spot where a judgment became an order. The second row, NVDA · canceled, shows what happens to an equity order placed while the US market is closed.

That last row matters. The tool returned two lines of warning.

"Protective bracket/OTO orders are not supported for crypto assets; controls remain advisory." "Broker stop-loss/take-profit orders were not submitted; controls remain advisory." (that tool's output)

No matter how clearly an agent writes "stop 82,400", on crypto that stop does not exist at the broker. On a US equity it goes out as a real protective order alongside the entry. That the tool warns you about the difference deserves credit.

One more thing: automatic ordering ships turned off. Analysis-only is the default, and execution has to be enabled separately.


5. Moving it into weltiq — an integration experiment

I put the same judgment into a weltiq paper account as well. The purpose is single: how do the same numbers, out of the same sentence, move differently in two bodies.

weltiq setup — sign up ({{CTA_URL}}) → My → issue a key under Connections → then the three steps below. (Writing a tool's output directly into a rule like this is a paid feature. Free accounts pick from templates.)

  1. Move it into a rule document. The entry range, stop and target the tool produced go in as three numbers, unchanged. (close ≥ 84,900) and (close ≤ 85,300) enters long, stop 82,400, target 87,385.
  2. Write the reasoning. weltiq does not accept a rule with no reasoning. To pass, you have to write in plain language why this rule expects to make money and what would mean the premise is wrong.
  3. Create the slot with stop conditions. Without a maximum loss and a maximum run time it is rejected. For the quantity I applied the tool's own "initial stop puts 0.5% of capital at risk" and computed 0.0185 BTC — that one line is my arithmetic, not that tool's output.

I watched the slot for 47 minutes. The result is zero fills. The entry range that tool specified was 84,900–85,300, and over those 47 minutes the actual price stayed between 85,427 and 85,705 — the low was 127 above the top of the range.

That is this section's result. The same judgment filled instantly at $85,559.67 on Alpaca, and on weltiq it never bought once in 47 minutes. The range that tool wrote in its own words — "do not enter between triggers" — was honored on one side and not on the other. (The slot was stopped and deleted after checking.)

What carried over and what did not

What the tool produced The weltiq rule
Direction · entry range · stop · first target Go in as-is
"after VWAP/pivot reclaim and stabilization" · "then trail the exit" · second target 90,000 Cannot move. Either they are not conditions expressible as a rule, or there is only one field
Invalidation condition · holding period · confidence No field to hold them; they survive only as prose in the reasoning document

And now the main point of this part.

Alpaca side (the tool itself) weltiq side
Entry Market, immediately. BUY means it buys now Waits for the condition. It has to confirm the tool's range on a closed candle before buying
Fees 0.2201% taken out of the coin (verified directly) Recorded separately as fee on the fill (confirmed from documentation — no fill this time, so not measured)
Stop 82,400 Never reached the broker → advisory (verified directly) Enforced by the engine as the rule's stop (confirmed from documentation — no entry, so no chance to trigger)
Stop conditions Account-level circuit breakers Per-slot maximum loss and maximum run time. Without them the document itself is rejected

The same numbers become an instant fill on one side and a waiting condition on the other. What made the difference is not the judgment but the execution model. This tool's execution contract has exactly one order type, market, and the entry condition lives only in a free-text field, so the executor never reads the entry condition.


6. After using it

What was good

  1. Judgment through to fill is one body. The stretch where Part 12's upstream required a human to carry things by hand is gone.
  2. It produces levels. Stops and targets come out as absolute prices, and qualitative phrases like "below support" or relative values like "8% from entry" are deliberately discarded.
  3. There is a non-LLM layer between judgment and order. Order caps, concentration, circuit breakers and the kill switch are all arithmetic.
  4. Two markets, one pipeline. Equities and Bitcoin spot use the same prompts and the same schema.

What fell short

  1. On any provider other than OpenAI, the tool calls vanish entirely (section 2). With no error. If it gets fixed I will correct this paragraph.
  2. The entry condition is not in the execution contract. A judgment that says "enter only conditionally" becomes an immediate market buy.
  3. Crypto gets no broker stop (an Alpaca constraint). There is a warning, but you have to read it.
  4. Confidence has only three values, high/medium/low, and all six of these runs were medium.

Cost (measured, 2026-09-22)

Code · market data Free (Apache-2.0) · Alpaca free. Finnhub, FRED, CoinDesk and Reddit keys are optional
Per run $0.021 · 80–113 seconds (average of 6 runs, openai/gpt-5.6-luna, 4 analysts, 1 debate round)

7. What to do next

To read this tool properly it helps to look at its two siblings. The upstream with no broker is Part 12, TradingAgents, and the one with Binance attached to the same engine is Part 28, trading-bot-agent. The six runs in this part say that it is not only the broker that was swapped — the judgment side changed too.

One thing to try: change only LLM_PROVIDER, run the same symbol twice, and see whether the stop level holds.

weltiq setup 1. Sign up: {{CTA_URL}} 2. My → Connections → issue a key 3. claude mcp add weltiq --transport http https://weltiq.ai/mcp --header "Authorization: Bearer <key>" Moving a tool's judgment into a rule and running it on paper leaves a record behind.