hummingbot — a market-making bot that turns simulated the moment you add _paper_trade to the exchange name

Installing a 20,000-star market-making bot with zero API keys, quoting against live Binance prices, taking 22 simulated fills, and actually tripping the kill sw

Part 13. hummingbot — add _paper_trade to the exchange name and it goes simulated

Done when: you have quoted on top of live Binance prices without creating a single API key, taken simulated fills, and read the fees and the P&L as separate numbers in hbot history.

What it does A market-making and arbitrage bot: it posts a bid and an ask at the same time and lives off the difference, running strategies on top of 63 exchange connectors
Who it fits Anyone who wants to try crypto market making or cross-exchange arbitrage without putting money in first · anyone building a strategy that has to sit on two or more exchanges at once
Who it doesn't fit Anyone running directional, candle-based strategies (different grain) · anyone who needs to measure P&L over several uninterrupted days of simulation (a restart resets the paper wallet) · anyone who needs stocks
What you end up with 90 daily candles pulled with 0 keys, your own simulated orders sitting on live Binance quotes, a record of 22 fills across 11 round trips with half the loss being fees, and a stop condition that actually fired
What you need · cost Code free (Apache-2.0) · Model none (this tool does not use an LLM) · Market data free, 0 API keys · 0 exchange accounts
Time · difficulty 9.4 seconds to install, two minutes to the first quote. Difficulty medium-high — the wheel install needs two manual fixes, and pure_market_making alone has more than 40 settings
Relationship to weltiq Execution does not connect. hummingbot does the candles, the decision, the simulated fill and the record all inside itself. Section 5 is not "how to connect" but what two paper engines in the same seat measure differently
Test environment · date macOS 26.6.2 · arm64 · uv 0.10.12 · CPython 3.12.13 · PyPI hummingbot 20260920 (hbot dev-2.17.0) · 2026-09-22 01:46–02:16 KST
Disclosure weltiq pays me to write this series. Everything said about the tool comes from runs I did myself

The human sentences are the commands I actually typed, rewritten as conversation. The output is measured and the numbers are untouched. I created no exchange account and no API key — the only thing I asked the exchange for was public market data, and not one order went out to an exchange. Every fill here happened inside hummingbot's own paper_trade. None of this is a trading opinion. The raw logs are in the verification record.


1. What this is

hummingbot/hummingbot is an open-source trading bot for market making and arbitrage. It arrived in 2019, carries a pile of exchange connectors, and runs quoting strategies on top of them.

Market making is posting a buy price and a sell price at the same time. If SOL is at 117.8 you put a bid at 117.76 and an ask at 117.84. Fill both and you made 0.08. You are not calling direction; you are standing on both sides, quoting a price to whoever comes past, and taking the difference. So while LLM agents are solving "what should I buy", hummingbot is solving "at what price do I quote, and how often do I refresh".

Item Value How it was checked
URL github.com/hummingbot/hummingbot
License Apache-2.0 GitHub API
Stars ★20,124 · 4,948 forks (observed 2026-09-22) GitHub API
Last push 2026-09-20 GitHub API
Created 2019-04-02 · 166 open issues · not archived GitHub API
Version PyPI hummingbot 20260920 · hbot dev-2.17.0 hbot --version
Connectors 63 (33 spot · 30 derivative) AllConnectorSettings.get_connector_settings()

_paper_trade — the switching cost is one string

Write binance as the exchange name in the config and it is live; write binance_paper_trade and it is simulated. That is all of it, and the code really does look like that.

# hummingbot/client/settings.py:311
paper_trade_settings = ConnectorSetting(
    name=f"{e}_paper_trade",           # ← same connector, new name
    type=base_connector_settings.type,
    parent_name=base_connector_settings.name,
)
cls.all_connector_settings.update({f"{e}_paper_trade": paper_trade_settings})

# hummingbot/client/config/client_config_map.py:207
paper_trade_account_balance: Dict[str, float] = Field(
    default={"BTC": 1, "USDT": 100000, "USDC": 100000, "ETH": 20,
             "WETH": 20, "SOL": 100, "DOGE": 1000000, "HBOT": 10000000},
)

It clones the real connector and registers a twin with _paper_trade appended to the name. The real connector fetches the prices; only the fill is simulated. Four venues get a twin by default (binance, kucoin, kraken, gate_io). And the paper wallet's opening balance is simply written into the config — that line is the setup for the last experiment in section 4. The README's own "getting started" example is the paper one; the live example comes after it.

The real asset is the connector count. Counting the installed build gives 63, and in market making that is not decoration. The same coin trades a little differently on each venue, and the strategies that eat that difference (cross_exchange_market_making, amm_arb, spot_perpetual_arbitrage) only exist if you can sit on two or more exchanges at once. Connectors are the strategy's range. There are 9 V1 strategies plus 17 V2 scripts and about 20 controllers.

And instead of the old screen-filling terminal UI there is now a separate non-interactive CLI (hbot). This article used it end to end — hbot start, status, stop and history all return markdown tables, and there is --json too. That shape is exactly right for handing to an LLM.


2. Installing

This is not the official install path. hummingbot points you at Docker or a source build (Anaconda plus make install, Cython compilation), and the machine this was written on had neither. So I installed the PyPI wheel — there is a macOS arm64 / CPython 3.12 wheel. It is not a path they say they support, so it takes two manual fixes. If you can use Docker, Docker is right.

uv venv -p 3.12 ./venv-hummingbot
uv pip install --no-cache --python ./venv-hummingbot/bin/python hummingbot

With the cache cleared it was 139 packages in 9.4 seconds (a second cold run took 29.3 seconds — download-speed variance). If you remember Cython compilation taking tens of minutes, the wheel already contains the .so files, so that step is gone.

Installing from the wheel leaves two things missing — the entry point and the config folder location.

# (1) the wheel has no bin/hbot, so call the entry point directly
cat > hbot_run.py <<'EOF'
import sys
from hummingbot.cli.main import main
sys.exit(main())
EOF

# (2) it looks for conf/ logs/ data/ scripts/ controllers/ at the package install location
SP=$(python -c "import hummingbot,os;print(os.path.dirname(os.path.dirname(hummingbot.__file__)))")
for d in conf logs data certs scripts controllers; do mkdir -p ./$d; ln -sfn $PWD/$d $SP/$d; done

export HBOT_PASSWORD='anything'   # the first run creates a keystore password

Copy scripts/ and controllers/ out of the repository — those two folders only.

Verifying the install

The screen after installation. Look at the hbot doctor table at the bottom — it checks for itself whether the compiled Cython extensions import, whether the keystore opens, and how far its clock is from internet time (skew ~0.6s). All ok and you can move on. (The screenshot renders the actual terminal output verbatim.)

Putting clock skew in the self-diagnostic is a nice touch. For a bot refreshing quotes on a millisecond cadence, a drifting clock is a real incident.

Where it gets stuck

Symptom Cause Fix
No such file or directory: .../site-packages/conf/strategies/... The wheel looks for conf/ at the package install location The symlink in (2) above
hbot: command not found The wheel has no bin/hbot The hbot_run.py in (1) above
A clean hbot stop logs ModuleNotFoundError: No module named 'appnope' as an ERROR The wheel does not declare one macOS-only dependency Ignorable — the very next lines cancel the orders normally and reach Trading core shutdown complete. For a clean log, uv pip install appnope
Extra inputs are not permitted You put a key in conf_client.yml that does not exist pydantic blocks it. Check the key name
The bot dies on start (Invalid password) HBOT_PASSWORD does not match the keystore The keystore is conf/.password_verification

3. The first result

The smallest example is posting a quote. No account, no key.

What you say

Bring up SOL-USDT on Binance paper with 0.5% on each side, refreshing every 30 seconds. I have no keys.

What comes back

## deployed conf_pmm_tut13.yml
- type: v1-strategy   - pid: 76398   - status: running
- applied: ask_spread, bid_spread, exchange, market, order_amount, order_refresh_time
If you want to type it yourself
hbot create pure_market_making --with-defaults --name conf_pmm_tut13.yml
hbot deploy conf_pmm_tut13.yml \
  --set exchange=binance_paper_trade --set market=SOL-USDT \
  --set bid_spread=0.5 --set ask_spread=0.5 \
  --set order_refresh_time=30 --set order_amount=0.5
`deploy` fills the config and starts it in one go. `create` only makes the config; `start` only launches it.

The strategy config screen

hbot config — every setting of the loaded strategy. The fifth line down, exchange | binance_paper_trade, is the only marker that this bot is simulated. Change it to binance and it is live. The four below it — bid_spread / ask_spread / order_refresh_time / order_amount — are the values actually touched in this article; everything else is a default. The live column on the right means can this be changed while the bot runs, and every entry is no — that comes back in section 4. (The screen is long, so only the first 27 lines are cropped in.)

About a minute later, hbot status.

paper_trade quoting

hbot status. Three things to look at. The Markets line, binance_PaperTrade SOL-USDT 117.8 / 117.81, is Binance's real quote at that moment. Assets showing SOL 100 / USDT 100000 is the paper wallet, exactly the config default from section 1. And the two Orders lines — sell 117.8939 and buy 117.7761 are my quotes, live right now. Fake orders sitting on top of real prices.

This is what it looks like when it works. The Age of 00:00:17 means the order was posted 17 seconds ago; it cancels and re-posts every 30. This is what market making actually is — posting, cancelling, posting again, all day. In 4 minutes 36 seconds it created 18 orders and cancelled 16. And the order record declares itself simulated: "event_source": "None_PaperTrade".

The dashboard — an empty shell without Docker

The terminal is enough, but there is a separate web dashboard. It is not in the main repository; it is hummingbot/dashboard (★367, Apache-2.0), a Streamlit app pulling 176 dependencies. The default port is 8501 (the Streamlit default) and this article moved it to --server.port 9040. Login is off by default (AUTH_SYSTEM_ENABLED=false) and no exchange key is needed — turn it on and admin / abc in credentials.yml are the defaults, so change them if you enable it.

git clone --depth 1 https://github.com/hummingbot/dashboard.git && cd dashboard
uv venv -p 3.12 ./venv-hbdash
uv pip install --python ./venv-hbdash/bin/python \
  hummingbot hummingbot-api-client nest_asyncio pydantic "streamlit>=1.36.0" watchdog \
  python-dotenv "plotly==5.24.1" pycoingecko glom defillama statsmodels pyyaml \
  "streamlit-authenticator==0.3.2" sqlalchemy
./venv-hbdash/bin/python -m streamlit run main.py \
  --server.headless true --server.port 9040 --server.address 127.0.0.1

The dashboard landing page

The first screen at http://127.0.0.1:9040. The left sidebar is the whole tool — Bot Orchestration (Instances · Deploy V2 · Credentials · Portfolio · Trading · Archived Bots), Config Generator (Grid Strike · PMM Simple · PMM Dynamic · D-Man Maker V2 · Bollinger V1 · MACD_BB V1 · SuperTrend V1 · XEMM Controller), Data (Download Candles). And read the yellow warning box — "The metrics, charts, and statistics shown below are simulated/mocked data for demonstration purposes." The Active Bots 3 · Total Portfolio $12,847 · Win Rate 74.2% right below it are all fake demo numbers, not my bot's.

Do not miss that the landing numbers are fake. It is exactly the kind of screen that gets misread when a screenshot travels on its own, so it goes here in bold. And pressing anything else in the sidebar hits the same wall.

You need the backend

Pressing Config Generator → PMM Simple. Failed to initialize API client: 404, message='Not Found', url='http://127.0.0.1:8000/docker/running'. Download Candles, Instances — the same error everywhere. Nothing but the landing page works.

The dashboard does not run on its own; it is the front end of a backend API (hummingbot/hummingbot-api, port 8000), and that backend brings bot containers up through Docker, so Docker is a prerequisite. (The error is a 404 rather than Connection refused only because another program held port 8000 on that machine. It means the same thing.) If you can use Docker, hummingbot/deploy brings the dashboard, backend and broker up together. If you cannot, use the hbot CLI — all of section 4 was done that way. Worth adding: the dashboard repository's last push was 2025-10-27, nearly a year behind the main project (2026-09-20). The right way to use hummingbot today is the hbot CLI.


4. The best way to use it — run market making for 30 minutes and see what happens

First, everything I ran, at a glance. All of it inside hummingbot, all of it with no keys.

What I did Result Time
90 daily BTC-USDT candles through the candles feed (no key) 90 rows, but the unfinished candle comes along with them 3.406 s
Quoting at a 0.5% spread 18 created, 16 cancelled, 0 fills 4 min 36 s
Narrowed to 0.01% and restarted first fill in 7 seconds, fee percent 0.001 7 fills in 4 min 39 s
Tripping the stop condition (kill switch) -0.05 no · -0.001 no · -0.0001 fired in 14 seconds three tries
kill -9 with a position open, then restart paper wallet reset to its initial values, fill records preserved 10 s
Seven runs summed 22 fills, 11 round trips · base_vol 0 · fees 1.29339 · total_pnl −2.56183 about 30 min

The fill record declares itself simulated (event_source: None_PaperTrade), exchange_order_id is an empty string, and the fill price is exactly the limit price posted. The raw logs for each row are in the verification record.

Why the stop condition did not fire twice

hummingbot's stop condition is one line, kill_switch_rate in conf_client.yml. At -0.05 it stayed quiet through 3 minutes 33 seconds and four fills at a loss; -0.001 did nothing either; -0.0001 fired in 14 seconds.

The kill switch firing

Above is the firing log, below is hbot status after it fired. Look at Current profitability is -0.000001088… in the log — it just barely crossed the -0.000001 threshold. Then look at hbot status below. state: running, and yet the Markets, Assets and Orders tables have vanished entirely. That is the point of this section.

The code explains it. kill_switch_rate is divided by 100 (kill_switch_rate / Decimal(100)), and the thing it is compared against, _profitability, is return against the whole portfolio's valuation. This paper wallet held SOL 100 plus USDT 100,000, about 111,700 USDT, so a loss of 1–2 USDT is only around −0.00001. That is a definition problem rather than a bug, and a trap for the user — write -5 and it means 5% of everything you hold, not 5% on this trade. For market making, which puts 0.1% of capital to work, it is effectively a threshold that never triggers.

What happens after it fires is more interesting. The log stopped at 02:05:22 and the strategy went down with Strategy stopped successfully, but hbot status still said state: running three minutes later, because the process was alive. Anyone watching only hbot status would not have known it had stopped.

Kill it and bring it back and the wallet resets

I killed the process with kill -9 while a position was open, then restarted it.

just before the kill  01:57:42   SOL  98.499     USDT 100176.2895
after the restart     01:58:12   SOL 100.4995    USDT  99941.1684

check: 100 + 0.4995            = 100.4995    ← restarted from SOL 100 and bought 0.5 (0.1% fee)
       100000 − 0.5 × 117.6632 =  99941.1684 ← restarted from USDT 100000

The paper wallet started over from the config defaults seen in section 1 (SOL 100 / USDT 100000). The accumulated P&L and the inventory held both became things that never happened. And yet the trade record survives — the trade count in hbot history continued 7 → 8, because it lives in sqlite.

On kill and restart
Fill records (TradeFill) survive — sqlite
Open-order tracking state attempts restoration (restore_market_states)
Paper wallet balance reset to the configured initial values
Cumulative P&L broken, because the wallet resets

In live trading this is not a problem — the truth about your balance is at the exchange and reconnecting reads it again. A simulation has no exchange. The only place left to ask is the config file, and all that holds is the opening balance.

So, the 30-minute conclusion

The accumulated fills

Above is the hbot history aggregate, below the last 8 raw fills (the TradeFill table). In the table look first at trades 22 · buys 11 · sells 11 · base_vol 0 — exactly 11 round trips, with inventory back where it started. Then look at fees 1.29339 and total_pnl -2.56183 side by side. Half the loss is fees. In the raw rows below you can see a fill every one or two minutes, with the price bouncing between 117.4 and 117.7.

One caution. That screenshot was taken after the bot was shut down, which is why return% reads -201.967. As hbot itself notes underneath, it means "the bot is stopped so the current balance cannot be read — realized P&L is accurate, current price and unrealized values are approximate." With the bot running, the same command gave -0.0023. The realized figures (trade_pnl, fees, total_pnl) are identical either way.

The most important line in that table is not total_pnl but fees. 11 buys, 11 sells, exactly matched, with base_vol 0 so the inventory came back to where it started — market making worked as intended. And the result is −2.56 USDT, of which 1.29 is fees. The remaining −1.27 means "I bought higher than I sold": I kept buying on the way down and kept selling on the way up.

Everything hard about market making is in that one line. Eleven perfect round trips and it still lost, because there is no way to beat a 0.2% round-trip fee with a one-way spread of 0.01–0.05%. Real market makers get maker fees rebated, or get paid separately for supplying liquidity, or set a much wider spread and give up on fill frequency. I did none of the three, so I lost.

And that is what this exercise is worth. Without spending a cent, I learned in 30 minutes that my configuration does not work. Finding it out on a live account would have cost more than $1.29 in fees, and worse, I would probably have missed the structure — "half of it is fees" — and put it down to a bad market. The order I would suggest: (a) run _paper_trade for a few days, (b) read fees and trade_pnl in hbot history separately, (c) check whether anything is left after the fees, and (d) only then add keys. Note though that this P&L is only trustworthy over an uninterrupted stretch (see the wallet reset above), and that the real market maker's income sources — fee rebates, liquidity rewards — are simply not in this simulation.


5. Moving it to weltiq — the seam is shallow

Let me be honest first: the seam between this tool and weltiq is shallow. hummingbot does the candles, the decision, the simulated fill and the record all inside itself, in its own sqlite. Running all of section 4 inside hummingbot is the proof. If you do crypto market making, there is no reason to bolt a paper account onto it.

What there is reason to do is put the two side by side, because both are paper and they measure differently. A comparison needs the same ruler, so I matched the candles first.

30 overlapping daily candles, all four values (OHLC) match on 30 · differ on 0
  2026-09-20T09:00+09:00  hb(2026-09-19) o=80883.86 c=81249.99 | wq o=80883.86 c=81249.99 → match
  2026-09-21T09:00+09:00  hb(2026-09-20) o=81249.99 c=81178.00 | wq o=81249.99 c=81178.00 → match

hb last candle start (UTC): 2026-09-21   close 85987.32
wq last candle label:       2026-09-21T09:00:00+09:00   close 81178.00

All 30 matched to the decimal. Two things differ, though: hummingbot labels a candle with its start time in UTC and includes the unfinished candle, while weltiq labels it with its closing time in the requested timezone and stores only closed candles. The label difference is a convention problem you fix by shifting one candle (that is how the comparison above got to 30/30). The unfinished candle is a character problem — the last two lines above show it: hummingbot has today's candle at close 85987.32 and weltiq does not. Telling a bot that refreshes quotes every 15 seconds to "wait for the candle to close" is a useless rule; to a rule that decides on one candle, an unfinished candle is the reason the same input gives a different answer. They measure in different units.

Two paper engines in the same seat — what differs

I brought up a weltiq strategy container and photographed both at the same moment.

hummingbot at 02:15:07

hummingbot at 02:15:07. Look at the two Orders lines — sell 117.6438 and buy 117.5262, Age 00:00:01. Posted one second ago. This bot never stops quoting.**

weltiq at 02:15:09

The weltiq container at the same moment (02:15:09). Three things. Engine: attached — the engine is on it. Entry conditions not met — waiting (normal) — it states that not placing an order is the normal state. Last bar 9/22, 02:15 AM is the time of the last closed candle. And on the right, Auto-stop −200 · 9/22, 04:14 AM — the stop condition that had to be supplied when the container was created.

hummingbot paper_trade weltiq paper
What it measures Quotes. Simulates fills against the live order book Closed candles. A signal only runs once the candle ends
Fill price Exactly the limit price you posted Recorded by the fill rule, written to the journal
Stop condition Optional. Leave it out and it runs without one A required gate. Without one the container does not come up
On reconnect Paper balance resets to initial values Carries the position and P&L over, and records what was carried
Is the state visible from outside hbot status. Still running after the kill switch fires A status light per symbol, with a reason
Record Its own sqlite (TradeFill) Journal, ledger, GET /export
Best at Market making and arbitrage Candle-based signals

The last row matters most. Market making is not what weltiq does. weltiq's blocks read candles and emit "enter now / exit now"; there is no field for quoting both sides and managing inventory. Section 4's 30-minute experiment cannot be done on weltiq. That is not a shortcoming but a statement that they are different objects, and it is a reason to use hummingbot. Conversely three weltiq columns do not exist in hummingbot — the enforced stop condition, carrying state across a reconnect, and state that is readable from outside.

This comparison turned up a blank on weltiq's side

To line them up I first created a SOLUSDT one-minute container, and this happened.

A container with no engine attached

The SOLUSDT one-minute container. It says running, but next to it in red is Engine not attached — state unknown and the Engine field reads not attached. Five minutes later it was unchanged. ORDERS and FILLS below are empty, naturally.

The cause was candles — that day the last hour held BTCUSDT 60, SOLUSDT 0, ETHUSDT 0 one-minute bars. That day, binance-spot one-minute collection was running for BTCUSDT only, so the SOL one-minute container had no candles to attach to and the engine never picked it up (daily candles for SOLUSDT were fine). So the side-by-side in this article switched to BTCUSDT, and that one attached within 40 seconds.

Since then (2026-09-22): fixed. Crypto now uses the same subscription path as equities (market.symbol_subscriptions) — attaching a container subscribes to that symbol's feed and backfills three days of one-minute candles. It is not a pre-declared collection list; attaching is subscribing. The number of symbols currently receiving binance-spot 1m candles is 89. The Engine not attached — state unknown screen above had the same cause, and creating a SOL one-minute container the same way today attaches normally.

And this connects back to the market-making story. If hummingbot's strength is 63 connectors, weltiq's counterpart is candle coverage. All the connectors in the world do not help if the prices do not arrive. That blank was on the one-minute side when this was written, and it was fixed as noted.


6. After using it

What was good

  1. _paper_trade really is one string. No keys to delete, no different command to learn. When the switching cost is low, people actually use it. The README's opening example is the paper one too
  2. hbot is good to drive from a script. Markdown tables, --json, reliable exit codes — this whole article was done without opening the TUI once. And hbot doctor checks the extensions, the keystore, clock skew and disk in one pass
  3. 63 connectors. This is the body of the tool. The strategies that require sitting on two or more exchanges at once come from here
  4. It charges the fee properly. It is a simulation and it still takes 0.1%. That is how you get "eleven perfect round trips and it still lost" — not being let off is the right behaviour. The dashboard labelling its own landing numbers as fake has the same character

What fell short

  1. Kill it and bring it back and the paper balance resets. The biggest drawback. You cannot measure several days of simulated operation with this tool alone
  2. The kill switch threshold is not what intuition says (it is against total holdings), and the state after it fires is not visible from outside — the strategy was down and hbot status still said running three minutes later. On top of that you cannot change a running bot's spread without stopping it, and stopping it triggers the wallet reset above
  3. The fills are too kind. Exactly at your limit price, no order-book depth consumed, no effect from your own order joining the book. The thing that matters most in market making — who is in front of me — is absent from this simulation
  4. The wheel install is not the official path (the three lines in section 2), and the dashboard is an empty shell without Docker. It is also crypto only, and the barrier is not low — pure_market_making alone has more than 40 settings

Cost

Item Value
Code · market data Free (Apache-2.0 · 0 API keys · 0 exchange accounts)
Model None — this tool does not use an LLM
Time Install 9.4s · 90 candles 3.4s · first fill in 7s

Who it fits — anyone who wants to try crypto market making or cross-exchange arbitrage without putting money in first. Section 4 alone earns its keep. Who it does not fit — anyone running directional candle-based strategies, anyone who needs several uninterrupted days of simulated P&L, and anyone who needs stocks.


7. Coming next

Next is QuantDinger (★11.9k), pointing the opposite way. hummingbot's asset was its reach across exchanges — 63 connectors, quoting. QuantDinger's body is the strategy runtime: the adapter extension points are open and an Agent Gateway and MCP are attached, so where the fill happens is designed to be swappable.

So the next question inverts too. This time it was "it has its own paper engine, so there is no seat for us"; next time it is "plug weltiq into that adapter seat and does the whole strategy runtime run on top of those fills."

Getting ready for weltiq — 3 lines 1. Sign up: {{CTA_URL}} 2. My page → Connections, issue an API key 3. claude mcp add weltiq --header "Authorization: Bearer <key>" https://weltiq.ai/mcp

That is where to look when you want the same rules applied to the stock and overseas markets hummingbot cannot reach.


Every command, output and number in this article came from actual runs on 2026-09-22. No real money moved; everything is simulated. None of it is a trading opinion. The trial and error, raw logs and the items not run are in the verification record.