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 orderDone 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 thegauss.researchsection — the default issession_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
- Address:
http://localhost:8501(the Streamlit default. To change it,--server.port 8511) - Login: none. It asks for no account and no key
- On first open: a session screen filled with synthetic records, readable straight away with nothing to click
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
- 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.
- 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. - 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
- S1, looking at one global symbol: Alpaca can serve
BTC/USD, but this tool's value isn't in looking at a single name, so D stands in for it - S2, a Korean symbol: not possible with this tool. It is Alpaca-only, so there is no route to
fetch
005930 - S3, reviewing my own execution history: this tool reads only its own session records. There is no entrance for a weltiq export
- S5, placing one order: done in section 5 (
= S5). Not an order on this tool's side — an order on weltiq's side - S7, tripping a stop condition: attempted, and it never triggered. I deliberately set a tight limit (max loss 12 USD) on run 2 in section 5, but that run never entered at all, so there was no chance to trip it. Details in 5-3
- S8, disconnecting and reconnecting: not run. This tool's session service has
reconcileandrestorecommands, but this article's decision path (run-strategy) doesn't go through the resident service, so there is nothing to disconnect - S9, re-measuring with different fees · S10, head-to-head: not run. Those belong to the B-track parts
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.
- 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 - 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 - Create the slot together with stop conditions. Max loss 400 USD · max run time 2 hours. Leave them out and it is rejected
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
- The default is the safe side. The resident service ships as
shadow, and paper account selection and paper order execution are each a separate switch. Going live requires a distinct command,arm-live, that explicitly binds account, policy and deployment version. Tools in this series that only go out once switched on aren't rare, but this is the only one that has to be switched on three times - The decision comes out finished. Plenty of tools stop at a direction; this one fills in entry price, stop, target and quantity. And on the session path it adds a valid-from time and an expiry on top
- It writes down what it doesn't know. When the event calendar fails to attach,
UNAVAILABLE (ValueError)stays in the body of the report, and it warns in sentences about the limits of FRED data too — "A 'FOMC Press Release' entry is not proof of a policy meeting that day." - The keyless preview is a real screen. It isn't a separately built demo page but the same renderer with synthetic records plugged in, and the screen declares three times that it is synthetic
What fell short
- You cannot swap the LLM provider.
.env.exampleadvertisesOPENAI_BASE_URLand the code never reads that variable (§2.1). Anyone holding only a relay key cannot use LLM mode shadowprotects a narrower range than you'd think. That default belongs to the resident service (gauss_bot.py), and the CLI'srun-strategy --executedoes not look at that setting. Whatsrc/trade/engine/execution.py:205looks at is a single CLI flag. Turn the flag on and the order goes out to Alpaca regardless ofshadow- It produces fractional shares. Alpaca accepts them, but they get shaved on the way across
(§5-2). There is also no field to carry over the position cap the tool produced (
max_position_pct) - One test is broken. 1 out of 237. The subprocess research worker dies with a
RuntimeError— which looks like the first thing anyone trying to use LLM mode will run into - It is Alpaca-only. There is no route to Korean equities
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.