Open-source project
enarjord/passivbot avatar
enarjord/passivbot

Passivbot: perpetual futures market making with contrarian re-entry and a Rust order core

Trading bot running on Bybit, Bitget, OKX, GateIO, Binance, Kucoin, WEEX and Hyperliquid

2,118 stars678 forksPythonUnlicense

At a glance

What is it?
Passivbot is a Python and Rust trading bot that manages limit orders on perpetual futures markets across multiple exchanges without predicting price direction. It ships with a backtester and evolutionary optimizer that share the same Rust order-planning code as the live trading engine.
Who is it for?
Passivbot fits a trader who wants perpetual futures positions managed automatically with contrarian re-entry, dynamic market selection via Forager, and incremental loss management via unstucking. It does not fit spot trading, trend-following logic, or any setup where strategy behavior needs to be user-programmable beyond threshold and sizing configuration.
Can I use it commercially?
Yes. Unlicense is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 17 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 6, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A contrarian market maker that adds to losing positions, not a trend predictor

Passivbot describes itself as a contrarian market maker: it does not try to predict future price movements and does not follow trends. On each trading cycle, it places or cancels limit buy and sell orders based on configured price bands, EMA-derived thresholds, and risk limits, without any input from the user between cycles. Its own framing calls this serving the market as a price stabilizer.

What that design means in practice: the bot adds to a position as price moves against it, relying on price eventually returning toward the entry average. A trader who wants signal-based entries from technical indicators or pattern detection would use a different tool. Freqtrade, for example, treats strategy logic as user-supplied code: users define indicator signals, entry conditions, and exit rules. Passivbot offers no such interface; its contrarian re-entry and close logic is fixed, and the configurable surface covers thresholds, sizing, and trailing distances, not strategy code.

Perpetual futures involve margin requirements and carry liquidation risk, and the contrarian re-entry approach can compound an open loss before price recovers. A warning at the top of the README states that the bot is used at one's own risk.

The Rust orchestrator computes order plans for both live trading and backtesting

Order planning in Passivbot runs on a shared Rust orchestrator that handles both live trading and backtesting using the same code. Running identical logic in both contexts means the backtest result and the live run cannot diverge in the way they would if a backtester approximated what the live engine does.

An optimizer builds on that backtesting infrastructure. It iterates thousands of candidate configurations and converges on the best ones using an evolutionary algorithm. This component ships in the .[full] install profile rather than the default live profile, so a VPS dedicated to live trading does not pull in its dependencies.

Compiling the Rust component from source happens automatically during installation. The repository holds a passivbot-rust/ directory with its Cargo.toml, and rust-toolchain.toml at the root pins the Rust version. When Rust source changes between repository pulls, Passivbot attempts recompilation on the next start; manual compilation inside passivbot-rust/ uses maturin develop --release. Building requires setuptools-rust>=1.9.0, listed as a build dependency in pyproject.toml.

Python 3.13 is excluded, and a Rust toolchain is required before installation

setup.py carries this constraint: python_requires=">=3.12,<3.15,!=3.13.*". Python 3.12 and Python 3.14 are supported; Python 3.13 is excluded from the pinned dependency set. A machine with only Python 3.13 installed will fail during package installation, and the README names both supported versions explicitly.

Rust must be in place before the Python package. Instructions for Rustup are at https://www.rust-lang.org/tools/install, and the README notes that the terminal should be restarted after installation. Once Rust is available, clone the repository and move into it:

sh
git clone https://github.com/enarjord/passivbot.git
cd passivbot

On Linux or macOS, create and activate a virtual environment with a supported Python binary:

sh
PYTHON_BIN=python3.14
"$PYTHON_BIN" -m venv venv
source venv/bin/activate

Three install profiles address different needs. A VPS running live trades uses only the default:

sh
python3 -m pip install -e .

Adding .[full] installs the optimizer and backtesting dependencies; .[dev] adds lint tooling on top. All three profiles compile the Rust extension.

Three strategies: one default, one available, one deprecated since v8

Three strategy names appear in the README. The default is trailing_martingale, which opens a small position and adds to it as price moves against entry; each re-entry shifts the average entry price, and closes seek a configured profit margin or use trailing confirmation to exit. Entry sizes, distances, and trailing behavior are configurable through the bot config file, documented in docs/config.bot.md.

A second live strategy, ema_anchor, is also available. Its configuration detail lives in docs/config.bot.md rather than in the README itself. trailing_grid_v7 is a deprecated compatibility path for v7 migrations only and should not be the basis for new work. New configurations and new optimization work should start from configs/examples/default_trailing_martingale_long.json.

Grid orders and trailing orders can be combined within a single position. For a trailing entry, the bot waits for price to move past a threshold and then retraces a defined percentage before placing the order. A trailing close works the same way in the favorable direction: price must improve by a threshold and then retrace before the close order is placed. A position can split entries between grid and trailing orders simultaneously.

Forager selects markets by volume and volatility, unstucking exits losing positions incrementally

Forager handles which markets the bot is allowed to open new positions on. Of the approved markets, those with low relative volume are excluded first; what remains gets scored by three configurable weights across quote volume, EMA readiness, and 1m log-range volatility. Markets that fall below the threshold are excluded from new entries while those conditions hold, and the selection shifts as conditions change.

Unstucking addresses positions where the current price is far enough from the average entry that the normal close logic would not fire. Among multiple stuck positions, the bot picks the one with the smallest gap between entry price and current price and exits it in small increments, accepting a series of losses over time. A floor in the config prevents the bot from accepting losses that would pull the account below a configured percentage of its peak balance.

Neither Forager nor unstucking generates a notification during operation; both run without manual intervention. This matches what the README identifies as the design goal: a bot that requires minimal user involvement once configured.

v8 breaks every v7 config, and the migration command gates output on ambiguous fields

A blockquote at the top of the README warns that v8 introduces breaking changes to both config structure and strategy fields, and that a v7 config should not be run live without review. Use the dedicated migration command:

sh
passivbot tool migrate-config-v7 \
  path/to/config_v7.json \
  path/to/config_v8_trailing_grid_v7.json \
  --report path/to/v7_migration_report.json

This command writes the output in the deprecated trailing_grid_v7 format, not in the new trailing_martingale shape. If the input contains unsupported or ambiguous fields, the command returns nonzero and does not write the output file. Writing the output in that case requires --allow-manual-review-output. Once produced, the README instructs users to review the report and backtest the result before any live run.

v8 does not silently reinterpret v7 strategy fields. The migration command is a compatibility bridge: it preserves v7 trailing-grid behavior in a wrapper rather than translating parameters into equivalent v8 ones. Anyone expecting a converted v8 config from this tool will find a working v7-compatible config in v8's config format instead.

Docker Compose defines separate images for research and live trading

Two Dockerfiles sit in the repository: Dockerfile for general use and Dockerfile_live for live trading. docker-compose.yml defines two matching services.

Activated with the default profile, the passivbot service mounts the entire project directory into /app/ with read-write access, which suits research and backtesting. Activated with the live profile, passivbot-live uses a tighter layout: configs are mounted read-only from ./configs/, api-keys.json is mounted read-only, and logs and monitor data each get their own writable volumes.

Five environment variables configure the live service: PB_USER, PB_CONFIG_PATH, PB_API_KEYS_PATH, PB_LOG_DIR, and PB_MONITOR_ROOT. Two have defaults in the compose file: PB_CONFIG_PATH resolves to /data/configs/live.json and PB_API_KEYS_PATH to /data/api-keys.json. PB_USER has no default, so starting a container without setting it passes an empty string.

Commented example commands in docker-compose.yml show how to invoke the main script, the backtester, and the optimizer: python src/main.py configs/examples/forager_long_only.json, python src/backtest.py configs/examples/top20mcap.json, and python src/optimize.py configs/examples/forager_long_only.json. Those lines are commented out and are not the active default commands.

The README title and the repository description list different exchanges

One detail in the project's own documentation is internally inconsistent: the exchange count. Eight exchanges appear in the repository description: Bybit, Bitget, OKX, GateIO, Binance, Kucoin, WEEX, and Hyperliquid. Ten appear in the README title, which adds Bitunix and Lighter. Neither Bitunix nor Lighter appears in the repository topic tags, which include bybit, bitget, binance, gateio, hyperliquid, and okx.

For Lighter specifically, a note in the README directs users to docs/exchanges/lighter.md for signer setup and exchange-specific behavior. No equivalent note appears for any of the other nine exchanges named in the README, making Lighter the only one called out as requiring additional steps beyond the standard API key configuration.

Before choosing an exchange, verify it appears in the README title rather than relying on the shorter repository description, and check whether a dedicated page exists under docs/exchanges/. Passivbot is distributed under the Unlicense, placing all code in the public domain, with the last repository push on 2026-10-06 and v8.1.0 as the most recent tagged version from 2026-08-10.

Editorial conclusion

Passivbot fits a trader who wants perpetual futures positions managed automatically with contrarian re-entry, dynamic market selection via Forager, and incremental loss management via unstucking. It does not fit spot trading, trend-following logic, or any setup where strategy behavior needs to be user-programmable beyond threshold and sizing configuration. Before going live, confirm that your exchange is listed in the README title rather than only the repository description, check docs/exchanges/ for exchange-specific requirements, and backtest any configuration before running live.

Frequently asked questions

What is Passivbot?

Passivbot is a Python and Rust trading bot for perpetual futures markets that acts as a contrarian market maker. On each cycle, it places and cancels limit orders automatically across multiple exchanges without predicting price direction, and it includes a backtester and an evolutionary optimizer that share its Rust order-planning code.

Does Passivbot support Python 3.13?

Python 3.13 is not supported. setup.py carries the constraint python_requires=">=3.12,<3.15,!=3.13.*", excluding 3.13 explicitly from the pinned dependency set. Only Python 3.12 and Python 3.14 work.

How do I migrate a Passivbot v7 config to v8?

A dedicated command handles the conversion: passivbot tool migrate-config-v7. It writes the output in the deprecated trailing_grid_v7 format; if the input contains ambiguous fields, it returns nonzero and requires --allow-manual-review-output to produce the file. Backtest the result before any live run.

Which exchanges does Passivbot support?

Ten exchanges appear in the README title: Bybit, OKX, Bitget, Bitunix, GateIO, Binance, Kucoin, Hyperliquid, WEEX, and Lighter. Eight appear in the repository description, which omits Bitunix and Lighter. Lighter requires additional signer setup documented in docs/exchanges/lighter.md.

Can Passivbot be run with Docker?

Yes. A docker-compose.yml in the repository defines two services: a default service for general use and passivbot-live for live trading. Five environment variables configure the live service, including PB_USER, PB_CONFIG_PATH (defaulting to /data/configs/live.json), and PB_API_KEYS_PATH (defaulting to /data/api-keys.json).

Official sources

  1. enarjord/passivbot on GitHub
  2. License: Unlicense
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/enarjord-passivbot.svg)](https://hysenlabs.com/projects/enarjord-passivbot)

Was this useful?