GaussWorldTrader — a trading workstation that ships in shadow mode and places no orders

It sweeps all 11,577 US stocks in one pass and hands back entry, stop and target for every candidate — and by default it submits nothing. I carried one of those

Part 54. GaussWorldTrader — it finishes the decision, and then places no order

Done when: you have brought up the dashboard without a single key, and then pulled a candidate list carrying entry, stop and target for the whole US stock market in your own terminal.

What it does Sweeps US equities, crypto and options from Alpaca data and runs 12 strategies (one of them an LLM committee) to produce a direction plus an entry price, a stop, a target and a quantity. The resident service's default execution mode is shadow — it records the proposal and never submits the order
Who it fits People who care less about having orders placed for them than about the reasoning and the boundaries staying on the record · people who want one pass over the whole market before they pick names themselves · people who already have an Alpaca account
Who it doesn't fit Anyone following Korean equities (Alpaca only) · anyone who wants it to buy and sell on its own · anyone holding only a relay key such as OpenRouter (the wall in §2.1)
What you end up with A five-line conditional watchlist drawn from a sweep of 11,577 US stocks, and the record of one of those decisions actually filling in a weltiq paper account
What you need · cost Code free (MIT) · everything here runs with no LLM key (every decision in this article was made in fast mode, 0 LLM calls, $0) · market data from a free Alpaca key · writing the rule by hand in section 5 is a paid weltiq feature
Time · difficulty Dependency install 4.19 seconds (uv, cache already warm — a first pip install takes far longer) plus one line for TA-Lib · first decision 6.8 seconds · full-market screen 56.17 seconds · difficulty medium
Relationship to weltiq It connects. The seat this tool leaves empty is exactly weltiq's seat — the decision comes out finished and the order never goes out
Test environment · date macOS · Python 3.13.12 (also confirmed working on 3.14.7) · commit 14f357e · 2026-09-24
Disclosure I write this series under a partnership with weltiq. Every command and number here comes from runs I actually performed in the environment above, and every order is simulated. The BUY calls, stops and targets this tool produces are entirely its own output, not my opinion

Raw logs and source material: reports/a/54-gaussworldtrader/


1. What this is

Magica-Chen/GaussWorldTrader is a Python trading workstation built on top of Alpaca. Taking in market data, running strategies, backtesting and supervising sessions all live in one repository.

There is one point where it diverges from the other tools in this series. Its default is to place no order. The resident service ships with its execution mode set to shadow, and in that mode a decision is recorded only and never reaches the broker. To get an order out you have to turn on paper account selection and paper order execution separately.

Item Value How it was checked
URL github.com/Magica-Chen/GaussWorldTrader
License MIT GitHub API
Stars ★113 · 23 forks (observed 2026-09-24) GitHub API
Created 2025-08-18 GitHub API
Last push 2026-09-07 GitHub API
Commit this article ran 14f357e61ba630d334ee9e75a3ff01edaa95ed3f git log -1
Code size src/ 27,415 lines · tests/ 3,877 lines measured with wc -l
Strategies 12 (8 equity · 2 crypto · 2 options) main_cli.py list-strategies
Broker Alpaca only (alpaca-py>=0.42.0) requirements.txt

Four roles split the clock between them. In the words of the repository's own documentation (docs/FOUR_AGENT_OPERATIONS.md), PostGauss reviews the session that just closed, CloseGauss builds a conditional plan out of that settled evidence, PreGauss re-validates the plan before the open, and LiveGauss watches for triggers during the session.

What is worth noticing in this structure is not the role names but the fact that each stage leaves its output behind as an immutable record. A plan carries a valid-from time, an entry expiry and a close time (SessionPlan.valid_from / entry_expires_at / close_at), and a validation result is stamped as one of six: ELIGIBLE / DEFERRED / REJECTED / INVALIDATED / ENTRY_EXPIRED / REVIEW_REQUIRED. The decision does not drift along — it gets stamped at every stage.


2. Installing

Python 3.12 or later is enough. I ran it on 3.13.12, and the CLI came up unchanged on 3.14.7 too.

git clone https://github.com/Magica-Chen/GaussWorldTrader.git
cd GaussWorldTrader
python -m venv .venv && source .venv/bin/activate
python -m pip install -r requirements.txt

One thing has to be installed up front — TA-Lib. It is not a Python package but a wrapper around a C library, and without that C library underneath the install dies halfway through.

brew install ta-lib        # macOS. 0.6.4 on my machine
# On Linux, build from source. On Windows I'd suggest WSL (the repository uses POSIX file locks)

I measured dependency installation at 4.19 seconds. That was with uv, and with the cache already warm. A first pip install takes far longer — there is a lot to fetch (vectorbt, streamlit, scikit-learn and plotly all come along).

This is the line that tells you the install worked.

python main_cli.py list-strategies

If 12 strategies come back as a table, the install is done. They split into 8 equity · 2 crypto · 2 options, and multi_agent is the strategy that uses the LLM committee

I ran the tests as well. 235 of 237 passed, 1 failed, 1 skipped — 5.95 seconds. The failure is the one that spawns the research worker as a subprocess, test_t23_subprocess_worker_has_no_broker_environment_and_completes, where the worker dies with a RuntimeError. The thing that test set out to prove — that broker keys do not leak into the subprocess — passes; what dies is the step after it, the check that the worker actually completes.

2.1 Where it is blocked — you cannot swap the LLM provider

This is the one wall in this article I could not get through. Stating it up front.

The repository's .env.example lists OPENAI_BASE_URL as a configuration item (line 17). But nothing in the code reads that variable. Search the whole repository and the only hit is that one line in .env.example; the actual provider classes hard-code the address.

# src/llm/providers.py:394
self.base_url = "https://api.openai.com/v1"

DeepSeek, Anthropic and Moonshot are hard-coded the same way. So a relay service key such as OpenRouter cannot run the LLM mode at all. That is the kind of key I have, so every decision in this article was produced in fast mode (arithmetic only, no LLM).

export MULTI_AGENT_MODE=fast   # 0 LLM calls

It isn't all bad news. The very existence of fast mode tells you something about the design — the committee structure stays intact and an arithmetic rule is dropped into each analyst's seat. So even with no key, the shape of the decision that comes out is identical. Sections 3 and 4 are the proof.

On money. Running this tool cost nothing. 0 LLM calls (the tool prints usage: {'calls': 0, ..., 'estimated_cost_usd': 0.0} itself), market data from a free Alpaca key. Turn LLM mode on and the per-session budget sits in the gauss.research section — the default is session_llm_absolute_cap_usd = "5.00", split 10:60:20:10 across the four roles.


3. It's installed, so let's just run it once

3.1 The dashboard first, with no keys

One nice thing about this tool. A real screen comes up with no keys at all.

python -m streamlit run examples/dashboard_preview.py

The offline preview, up with no keys. The blue banner reading «SHADOW MODE — decisions are observations; broker submission is disabled» is this tool in one line. The four cards above it show execution mode Shadow · environment Paper · plans recorded 1 · entries Paused

Every record on that screen is synthetic. The screen says so three times itself — OFFLINE PREVIEW · SYNTHETIC RECORDS · NO BROKER CONNECTED. The data-quality field reads Synthetic demonstration as well. For refusing to dress invented numbers up as real ones, this preview is well built.

The screen you get with keys in and a real account attached is a different command.

python dashboard.py     # http://localhost:3721

That one grows to eight sections: market data, analysis, backtesting, watchlist, account, orders and news.

3.2 Keys in, first decision

Put the Alpaca keys in .env. Use the paper address.

cp .env.example .env
# ALPACA_API_KEY / ALPACA_SECRET_KEY / ALPACA_BASE_URL=https://paper-api.alpaca.markets

You ask it like this

Look at SPY, QQQ, Apple, Microsoft, Nvidia and Tesla over 400 days and show me what the committee strategy says.

And this comes back (what follows is the tool's own output, with metadata cut because it runs long)

Decisions:
ExecutionDecision(symbol='SPY', side='buy', quantity=6.441442638324476,
  order_type='limit', limit_price=767.76, action='BUY',
  reason='Fast mode weighted vote: BUY=0.55, SELL=0.00, HOLD=0.21, risk=medium',
  stop_loss=756.2436, take_profit=790.7928, metadata={…})
If you want to type it yourself
export MULTI_AGENT_MODE=fast
python main_cli.py run-strategy --strategy multi_agent --days 400 \
    SPY QQQ AAPL MSFT NVDA TSLA

But the metadata I cut is where this tool's real value sits. Unfolded, it looks like this.

From the top: the decision in one line · the risk assessment (position cap 3.25%, ATR volatility 0.91%, and the risk_flags: ['agent_disagreement'] the tool attached to itself) · the three committee members' individual opinions (only technical says BUY 0.55, the other two HOLD) · call usage (calls 0, cost $0.0). **It keeps not just the decision but who disagreed and why**

Five places beginners get stuck.

Symptom Cause Fix
pip install dies on ta-lib the C library is missing brew install ta-lib first
No data for AAPL the Alpaca keys weren't read check where .env is — it has to be the repository root
No actionable decisions. This is normal. HOLD across the board looks like this add more symbols, or raise --days
the report says warmup incomplete the default --days 60 can't fill SMA50 give it room, e.g. --days 400
FRED API key not provided only the macro_factor strategy uses it the other strategies run fine

4. Getting serious — real scenarios

4.0 First, what shape does this tool's decision come out in

Before the scenarios, one thing worth setting down. The biggest dividing line between the tools in this series is how far they fill the decision in before handing it over.

Layer What comes out Where
① direction only BUY / SELL / HOLD + indicators + signal strength SignalSnapshot
② direction + levels ① + target entry price · stop · target (no quantity) ActionPlan
③ direction + levels + sizing ② + quantity · order type StrategySignal → ExecutionDecision

It produces all three. And the fact that the layers are separated matters — the quantity attaches last, and everything before it is a decision that has nothing to do with an account. That is why in the full-market screening report (scenario D in §4) the sizing comes out as a formula rather than a number. The account isn't attached yet.

Shares = floor(min(0.0025 × equity / (entry − stop), 0.10 × equity / entry, available cash / entry))

Go down the resident-session path and entry rules, invalidation rules and validity times get added on top. The plan carries valid_from, entry_expires_at and close_at, and the validation result is stamped as one of ELIGIBLE / DEFERRED / REJECTED / INVALIDATED / ENTRY_EXPIRED / REVIEW_REQUIRED. Tools whose decisions come with an expiry date are rare in this series.

Scenario D (= S4). Sweeping every US stock at once

This is what this tool does best. It isn't picking names for you — it is the pass that filters the whole market before you pick.

python gauss_bot.py --config examples/gauss.free-delayed.example.toml --once

It took 56.17 seconds. Here is what it did in that time.

The filtering leaves numbers behind — 11,577 listed → 11,555 with a recent bar → 2,802 through the liquidity screen → 199 actually analysed with 60 or more bars. Look at the columns in the table. Trigger · Max entry · Stop · Target are all filled in for every symbol

Three-line summary

  1. What came out — a conditional watchlist of 5 symbols. Each one carries four numbers: trigger, max entry, stop and target. And the report writes out in sentences how those numbers were computed: the trigger is the previous high + 0.05 ATR, the stop distance is 1.5 ATR, and the target is twice that.
  2. How many steps — one command line. One run also leaves the same content on disk as a file at results/gauss/research/<timestamp>/report.md.
  3. What doesn't work — the event calendar is half dead. Straight from the measured log: Calendar coverage: earnings: UNAVAILABLE (ValueError); macro: UNAVAILABLE (ValueError). That's because I didn't supply Finnhub and FRED keys — but the tool doesn't quietly pass over the fact, it writes "UNAVAILABLE" into the body of the report. Tools in this series that do that are not common.

I'll carry the report's closing section over as it stands, too — it shows what this tool calls its own results.

These are unvalidated research scenarios, not execution approvals or backtested expectancy estimates. No orders, strategy approvals or changes to the running session database are made.

Scenario S6. One turn of the loop — from analysis to a fill, and on to fixing the rule

Section 5 covers this in one go. Because this tool places no order, the place where the loop closes is exactly weltiq's seat, and splitting them into separate sections would mean writing the same story twice.

Scenarios I did not run


5. Connecting it to weltiq

5-1. Why this is weltiq's seat

This tool takes the decision all the way. It doesn't stop at a direction — it produces an entry price, a stop, a target and a quantity. And yet the resident service's default mode is shadow, so that finished decision never goes out as an order.

What this tool leaves behind: "buy 6.44 shares of SPY at 767.76, stop at 756.2436, target 790.7928"

What this tool does not do: place that order

The blank is precisely there. So the procedure has three steps.

  1. Move the tool's numbers into a rule document. Entry condition close ≤ 767.76, stop 756.2436, target 790.7928, quantity 6.441442638324476 — all of it exactly as the tool output it
  2. Write the reasoning. weltiq will not accept a rule with no reasoning. For this run I carried over what the tool itself said — that of the three committee members only technical said BUY (0.55) while the other two said HOLD, right down to the tool attaching risk_flags: ['agent_disagreement'] on its own
  3. Create the slot together with stop conditions. Max loss 400 USD · max run time 2 hours. Leave them out and it is rejected
If you want to type it yourself
POST /v1/strategy-rules   → doc_hash ce7bb23e…
POST /v1/ai/thesis        → {doc_hash, text}
POST /v1/containers       → {source:"ai", doc_hash, venue:"us-equity",
                             symbols:["SPY"], tf:"1m", balance:"100000",
                             stop_max_loss:"400", stop_after_hours:2}
The full script is at [`reports/a/54-gaussworldtrader/to_weltiq.py`](/news-assets/evidence/a/54-gaussworldtrader/to_weltiq.py).

5-2. It actually filled (= S5)

The fill came 48 seconds after the slot was created.

The slot detail. At the top, the tool's numbers have become a single rule line, unchanged. Look at the ORDERS panel — a buy of 6 shares is filled, and above it a protective stop_market for 6 shares sits alongside it as acked. In the FILLS panel, the fill price is 767.846773 and the fee is 0

Here is the one fill line left in the ledger.

Fill time 2026-09-24T03:40:12.805719+09:00
Quantity 6 (the tool's value was 6.441442638324476)
Fill price 767.846773
Expected price (expected_px) 767.75
Limit price from the tool 767.76
Fee 0 USD
Fill character taker · order_type: market · quote_source: alpaca-iex

Two things surface here.

First, the fractional share gets cut. The tool produces 6.441442638324476 shares, and 6 shares landed in the ledger. 0.4414 shares, about $339 worth, disappeared. The tool reads the Alpaca account's fractional_trading setting and uses fractional shares as they are, while weltiq's us-equity venue works in whole shares. This isn't a question of which side is right — it is that you need to know this field gets shaved on the way across.

Second, the "limit" does not carry across as a limit. The tool produced order_type='limit', limit_price=767.76, and on weltiq's side that number becomes a closed-bar entry condition (close ≤ 767.76) while the actual order goes out at market. So the fill landed 0.086773 above the value the tool wrote. Because weltiq's ledger records the expectation of 767.75 and the fill of 767.846773 side by side, the slip is left behind as a number — 0.096773, or 1.26bp.

5-3. I closed the loop (= S6)

That one line handed me something to fix. So I ran a second attempt.

Run 1 Run 2
Entry condition close ≤ 767.76 (the tool's value as is) close ≤ 767.66 (lowered by the measured slip of 0.0968)
Stop · target · quantity the tool's values unchanged — I didn't touch them
Stop conditions max loss 400 · 2 hours max loss 12 · 1 hour (deliberately tight)
doc_hash ce7bb23e… e8aa3458…
Result filled in 48 seconds 0 fills

Run 2 not buying is the result of this section. Over 23 minutes, across 99 measurements at one-minute intervals, SPY stayed between 767.69 and 768.29. Run 2's threshold was 767.66 — it never touched it, by 3 cents.

Same tool, same decision, and the only thing the human changed was a 10-cent entry threshold. That alone made one run buy and the other not. I lowered the threshold to make up for the slip, and this time the entry itself disappeared. This is the thing you only see by turning the loop a full revolution — with run 1 alone I'd have stopped at "so drop it 10 cents".

Run 1 I stopped by hand after observing it, and it liquidated. Here is that revolution's accounting.

Holding time 17 minutes 49 seconds (03:40:12 → 03:58:01)
Entry 6 shares @ 767.846773 · fee 0
Exit 6 shares @ 768.1081765 · fee 0.09493817
Slip on the exit side expected 768.21 → actual 768.1081765 = −0.1018
Realised P&L +1.5684210 (before fees)
Equity net of fees 100,001.47348283 — +0.0015%

The fee is 0 on the buy and only attaches on the sell. That is how US equity fees are structured. And the slip went against me on both sides — 0.0968 more expensive going in, 0.1018 cheaper going out. Together 0.1986, which is 12.7% of this run's P&L of 1.5684. The gap between the target (790.79) and the stop (756.24) this tool produced is 34 dollars, and the round-trip slip alone is 20 cents.

And the tight stop condition never triggered. I had set max loss at 12 USD, but run 2 never entered, so there was no way to lose anything. weltiq's max loss watches cumulative realised P&L, so with no position there is nothing to trip no matter how tight you set it. S7 went unobserved this time.

What carried over and what didn't

What the tool produced The weltiq rule
action BUY · side buy entry long — unchanged
limit_price 767.76 close ≤ 767.76 — its character changes. It stops being a limit price and becomes an entry condition
stop_loss 756.2436 levels.stop — unchanged. The engine actually placed it as a protective order (stop_market, 6 shares, acked)
take_profit 790.7928 levels.limit — unchanged
quantity 6.441442638324476 6 — the fraction is cut
confidence 0.7254871504666395 There is no field for it. Only as a sentence in the reasoning document
risk_assessment.max_position_pct 0.0325 There is no field for it. Nowhere to hold a position cap, so it survives only as a quantity
reports — each committee member's opinion and their individual indicators There is no field for it. Only as a summary in the reasoning document

5-4. So where does this lead

Once a human rewrites the tool's decision as a rule, that one rule passes through backtest and paper in the same body. weltiq's mode branching lives only at the adapter boundaries (clock, market data, broker), and the strategy document's doc_hash is the thread that stitches the stages together. The ce7bb23e… and e8aa3458… above are that thread, and the point where run 1 and run 2 diverged is recorded precisely between those two hashes.

The live stage is not open yet. "The same document goes through unchanged" is the part that is true; what comes after it is planned. The switch itself amounts to swapping a connector, but the promotion ladder is the gate — meaning the difference is not capability but a gate.

(Both slots in this article were stopped after I checked them. Run 2 was deleted as well, and run 1 could not be deleted — the ledger shows the liquidation complete, but the live snapshot on the slot screen froze one second before the liquidation and still reads "holding 6 shares of SPY", and the deletion check looks at that snapshot. This is a defect on weltiq's side, and I'm writing it down here. Reviving it and liquidating again would clear it, but an order to sell 6 shares that don't exist could go out, so I didn't. The slot is stopped and detached, so it does nothing.)


6. After using it

What was good

What fell short

Who it fits, who it doesn't

Fits — people who want one pass over the whole market before picking names, and want that filtering left behind as numbers. 11,577 → 2,802 → 199 → 5 gets written into the report every time.

Doesn't fit — people who want it to buy and sell on its own. This tool deliberately doesn't. And anyone whose only LLM key is a relay key.


7. Coming next

Next up is rmbell09-lang/tradesight. Where this part's tool made "placing no order" its default, the next one is a self-hosted implementation of broker reconciliation, evidence gates and a promotion ladder. What do you say when the seat you've repeatedly called "this is our seat" throughout the series turns out to have already been built by someone else — I think that's what that piece will be.