Skip to main content

Trading Engine

Run algorithmic trading strategies as managed tasks — with a live dashboard, per-task logs, restart policies, and a market SDK that abstracts across venues.


Quickstart

1 — Install

pip install tribulnation-engine

2 — Initialize

Initialize the engine in a new directory:

engine init
# creates engine.toml, .env, and strats/ directory

Edit engine.toml and .env to configure accounts and strategies.

3 — Write a strategy

# strats/my_strat.py
from tribulnation.engine import Process, RestartPolicy

class MyStrat(Process):
  name = 'my-strat'
  restart = RestartPolicy(on='error', delay=5.0)

  async def run(self, market: str):
    mkt  = await self.sdk.market(market)
    book = await mkt.depth()
    self.log.info('mark price: %s', book.mark_price)

run() parameters become the task's argument schema — they appear as a form in the dashboard and are coerced to the declared types when a task is started.

4 — Start the engine

engine start          # foreground, logs to stdout
engine start -d       # background — writes .engine.pid and engine.log

For longer-running deployments on a systemd host, you can also install it as a user service:

engine service install --cwd . --memory 2G -v
engine service status
engine service logs -f

The generated service runs engine start, restarts automatically, and uses systemd MemoryMax when --memory is provided. It also uses a bounded stop timeout so stuck shutdowns are force-killed by systemd:

engine service install --cwd . --memory 2G --timeout-stop-sec 20

Manage it with:

engine service restart
engine service stop
engine service uninstall

5 — Run a strategy

From the dashboard at http://localhost:3121 or via the CLI:

engine task start my-strat bitcoin -a market=mexc:spot:BTCUSDT
# engine task start <process_name> <run_id> -a <arg_name>=<arg_value> ...

6 — Stop a strategy

From the dashboard or:

engine task stop my-strat bitcoin
# engine task stop <process_name> <run_id>

Strategies

The Process base

from tribulnation.engine import Process, RestartPolicy

Implement async def run(self, ...). Anything run() needs is injected via self:

Attribute What it is
self.sdk Market SDK — call await self.sdk.market('venue::symbol')
self.log Logger scoped to this task
self.run_id The run ID this task was started with
self.set_state(s) Publish a named state ('connected', 'quoting', …)
self.require(cls, id) Declare a dependency on another running task
self.ipc Call methods on Service processes

Restart policy

class Hedger(Process):
  restart = RestartPolicy(on='error', delay=5.0)
on= Behaviour
'never' Stop on exit or error
'error' Restart after an unhandled exception, with delay seconds between attempts
'always' Restart after any exit

max_attempts=N caps the number of consecutive restarts.

Dependencies

Use require() to gate a task on another reaching a named state:

class DydxMaker(Process):
  async def run(self, maker: str, hedge: str) -> None:
    async with self.require(Hedger, self.run_id, state='connected')(
      listen=maker, hedge=hedge
    ):
      # runs only while Hedger/{self.run_id} is alive and in state 'connected'
      ...

require() also auto-starts the dependency if it isn't running, and cancels the dependent if the dependency stops or errors.

Logging

Standard logging calls are automatically captured and streamed to the dashboard. No setup needed:

log = logging.getLogger(__name__)

class Hedger(Process):
  async def run(self, ...):
    log.info('listener live: %s → %s', listen, hedge)
    # or use the process logger:
    self.log.info('works the same way')

INFO and above are always captured. DEBUG records are captured only from loggers whose name starts with a prefix listed in [daemon] debug_loggers:

[daemon]
debug_loggers = ["strats"]

Config reference (engine.toml)

[engine]
processes = [
  "strats.hedger:Hedger",
  "strats.dydx_maker:DydxMaker",
]

[daemon]
host     = "localhost"
port     = 3121
db       = "engine.db"    # SQLite path; default "engine.db"
max_logs = 500            # in-memory log lines per task; default unlimited

[accounts.dydx-main]
venue    = "dydx"
mnemonic = "$DYDX_MNEMONIC"

[accounts.hyperliquid-main]
venue       = "hyperliquid"
address     = "$HYPERLIQUID_ADDRESS"
private_key = "$HYPERLIQUID_PRIVATE_KEY"

[[tasks]]
process = "hedger"
id      = "btc"
args    = { listen = "dydx:perp:BTC-USD", hedge = "hl::BTC" }

Account credentials are resolved from environment variables when the value starts with $. engine start automatically loads .env from the same directory as engine.toml.

[[tasks]] entries are started automatically when the engine starts. Each entry needs process (the registered process name), id (the run ID), and optionally args (a table of keyword arguments matching the process signature).


CLI

engine start                                          # launch gateway + manager (foreground)
engine start -d                                       # background — writes .engine.pid + engine.log
engine service install --cwd . --memory 2G -v          # install as a user systemd service
engine service logs -f                                 # follow service logs
engine stop                                           # stop a background engine
engine task start hedger btc -a listen=dydx:perp:BTC-USD -a hedge=hyperliquid::BTC
engine task stop  hedger btc
engine register strats.new_strat:NewStrat             # hot-load a process class

HTTP API

Method Path Description
GET /api/tasks Running tasks
GET /api/tasks/history Completed tasks (SQLite)
GET /api/task/{process}/{id} Single task
GET /api/task/{process}/{id}/logs Task logs
GET /api/task/{process}/{id}/attempts Restart attempts
POST /api/task/{process}/{id} Start a task — body: {"args": {...}}
DELETE /api/task/{process}/{id} Stop a task
DELETE /api/task/{process}/{id}?purge=true Stop and remove from history
DELETE /api/task/{process}/{id}/logs Clear logs and attempt history
GET /api/processes Registered process classes
POST /api/processes Register a process at runtime
GET /api/health Health check
GET /api/ws WebSocket event feed

Architecture

engine start
├── engine gateway    owns real venue SDK connections, serves the market RPC protocol
└── engine manager    task control plane
  ├── in-process runtime — strategies run as asyncio tasks
  ├── REST + WebSocket API (FastAPI / uvicorn)
  └── SQLite store — task history, logs, restart attempts

Strategies never hold venue connections directly. self.sdk inside a task is a ProxySDK that routes market calls to the gateway over a local Unix socket. The gateway owns the real credentials and connections; the manager owns task lifecycle and the dashboard.

SDK 2 compatibility

Engine 0.1.19 requires SDK >=2.3.0,<3, dYdX >=0.8.0 and Hyperliquid >=0.8.1. Upgrade the gateway and manager together: the RPC protocol now uses funding_rates and preserves NextFunding.interval, account Fees, candle values and ticker volumes. Spot and perpetual proxies expose the appropriate SDK product interfaces, including market/exchange collateral. SDK account IDs and qualified exchange IDs are preserved.

The gateway enters one SDK root for its lifetime. Markets on the same account share clients, caches and subscriptions. Unsubscribing streams finish cleanup before the gateway closes its venue resources.

A local testnet smoke check is available without starting the manager or its tasks:

python dev/testnet_smoke.py --env-file ../strats/engine/.env
python dev/testnet_smoke.py --env-file ../strats/engine/.env --orders

It requires DYDX_TESTNET_MNEMONIC, DYDX_TESTNET_ADDRESS, HYPERLIQUID_TESTNET_PRIVATE_KEY and HYPERLIQUID_TESTNET_ADDRESS in that file. The optional --orders check places a small post-only buy on each testnet, observes it, cancels that exact order, and verifies cancellation. Other open orders are untouched.

Releases

GitHub Actions builds and publishes releases. PRs and pushes to main type-check the package, run its Python tests, build the dashboard using npm ci, and build/check the Python wheel and source distribution. CI verifies that the wheel contains the dashboard and uploads both distributions as workflow artifacts.

  1. Create a branch named release/engine from main.
  2. Bump version in lib/pyproject.toml and open a PR to main.
  3. Merge after checks pass. The release workflow checks the version bump and rebuilds the exact merged commit, publishes its distributions to PyPI, then creates a v<version> tag and GitHub release with the distributions attached.

Ordinary PR merges do not publish. Re-running the release workflow skips files already uploaded to PyPI and preserves an existing GitHub release.

Configure a PyPI trusted publisher once for tribulnation-engine: owner tribulnation, repository engine, workflow release.yml, with the environment field left empty (matching the SDK setup). No PyPI API token or exchange credentials are required by CI.

Release files for tribulnation-engine 0.1.20

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for tribulnation-engine 0.1.20
File Size Uploaded
tribulnation_engine-0.1.20.tar.gz 117.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for tribulnation-engine 0.1.20
File Interpreter ABI Platform
tribulnation_engine-0.1.20-py3-none-any.whl Python 3 none any Details

Total release size: 248.5 kB

Release files / tribulnation_engine-0.1.20.tar.gz

Download URL tribulnation_engine-0.1.20.tar.gz
Size 117.4 kB
Tags Source
SHA-256 checksum
How to use checksums
e71ac665615785b52c487cc8a1862ddd662aefcd19562b401c9ad74fbfe67bb8
BLAKE2b-256 checksum
How to use checksums
8aca4c9563dccbee3d6df8fc521e1c888a24439098d4abc144a3696a616799d7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 22, 2026.

Transparency log

Release files / tribulnation_engine-0.1.20-py3-none-any.whl

Download URL tribulnation_engine-0.1.20-py3-none-any.whl
Size 131.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
b82cfcf422c5f0abb1da309f68e56051d597deb2963264ac29a19443cbaa51d1
BLAKE2b-256 checksum
How to use checksums
cdd009bad1aecd7e37291b92c810b68718c2bfb32936e3cbb09c1369bc050463
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 22, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.20 This release

2 release files

0.1.18

2 release files

0.1.17

2 release files

0.1.16

2 release files

0.1.15

2 release files

0.1.13

2 release files

0.1.12

2 release files

0.1.11

2 release files

0.1.10

2 release files

0.1.9

2 release files

0.1.8

2 release files

0.1.7

2 release files

0.1.6

2 release files

0.1.5

2 release files

0.1.4

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page