Metadata-Version: 2.4
Name: tribulnation-engine
Version: 0.1.13
Summary: Trading engine — gateway, task manager, and process SDK
Author-email: Marcel Claramunt <marcel@tribulnation.com>
Project-URL: repo, https://github.com/tribulnation/engine.git
Requires-Python: >=3.11
Description-Content-Type: text/markdown
Requires-Dist: lazy-loader
Requires-Dist: tribulnation-sdk[dydx,hyperliquid]
Requires-Dist: typer
Requires-Dist: aiohttp
Requires-Dist: aiofiles
Requires-Dist: fastapi
Requires-Dist: aiosqlite
Requires-Dist: httpx
Requires-Dist: pydantic
Requires-Dist: python-dotenv
Requires-Dist: uvicorn
Provides-Extra: test
Requires-Dist: pytest; extra == "test"
Requires-Dist: pytest-asyncio; extra == "test"

# 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

```bash
pip install tribulnation-engine
```

### 2 — Initialize

Initialize the engine in a new directory:

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

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

### 3 — Write a strategy

```python
# 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

```bash
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:

```bash
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:

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

Manage it with:

```bash
engine service restart
engine service stop
engine service uninstall
```

### 5 — Run a strategy

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

```bash
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:

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

---

## Strategies

### The `Process` base

```python
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

```python
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:

```python
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:

```python
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`:

```toml
[daemon]
debug_loggers = ["strats"]
```

---

## Config reference (`engine.toml`)

```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

```bash
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.
