tribulnation-engine 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. tribulnation_engine-0.1.0/PKG-INFO +255 -0
  2. tribulnation_engine-0.1.0/README.md +237 -0
  3. tribulnation_engine-0.1.0/pyproject.toml +39 -0
  4. tribulnation_engine-0.1.0/setup.cfg +4 -0
  5. tribulnation_engine-0.1.0/src/tribulnation/engine/__init__.py +3 -0
  6. tribulnation_engine-0.1.0/src/tribulnation/engine/__init__.pyi +51 -0
  7. tribulnation_engine-0.1.0/src/tribulnation/engine/cli/__init__.py +18 -0
  8. tribulnation_engine-0.1.0/src/tribulnation/engine/cli/gateway.py +61 -0
  9. tribulnation_engine-0.1.0/src/tribulnation/engine/cli/init.py +116 -0
  10. tribulnation_engine-0.1.0/src/tribulnation/engine/cli/manager.py +124 -0
  11. tribulnation_engine-0.1.0/src/tribulnation/engine/cli/register.py +29 -0
  12. tribulnation_engine-0.1.0/src/tribulnation/engine/cli/service.py +232 -0
  13. tribulnation_engine-0.1.0/src/tribulnation/engine/cli/start.py +165 -0
  14. tribulnation_engine-0.1.0/src/tribulnation/engine/cli/stop.py +25 -0
  15. tribulnation_engine-0.1.0/src/tribulnation/engine/cli/task.py +72 -0
  16. tribulnation_engine-0.1.0/src/tribulnation/engine/client/__init__.py +9 -0
  17. tribulnation_engine-0.1.0/src/tribulnation/engine/client/__init__.pyi +7 -0
  18. tribulnation_engine-0.1.0/src/tribulnation/engine/client/engine.py +46 -0
  19. tribulnation_engine-0.1.0/src/tribulnation/engine/client/tasks.py +46 -0
  20. tribulnation_engine-0.1.0/src/tribulnation/engine/config.py +91 -0
  21. tribulnation_engine-0.1.0/src/tribulnation/engine/diagnostics.py +95 -0
  22. tribulnation_engine-0.1.0/src/tribulnation/engine/gateway/__init__.py +3 -0
  23. tribulnation_engine-0.1.0/src/tribulnation/engine/gateway/__init__.pyi +8 -0
  24. tribulnation_engine-0.1.0/src/tribulnation/engine/gateway/codec.py +504 -0
  25. tribulnation_engine-0.1.0/src/tribulnation/engine/gateway/proxy.py +435 -0
  26. tribulnation_engine-0.1.0/src/tribulnation/engine/gateway/server.py +309 -0
  27. tribulnation_engine-0.1.0/src/tribulnation/engine/manager/__init__.py +3 -0
  28. tribulnation_engine-0.1.0/src/tribulnation/engine/manager/__init__.pyi +12 -0
  29. tribulnation_engine-0.1.0/src/tribulnation/engine/manager/api.py +366 -0
  30. tribulnation_engine-0.1.0/src/tribulnation/engine/manager/loader.py +65 -0
  31. tribulnation_engine-0.1.0/src/tribulnation/engine/manager/manager.py +139 -0
  32. tribulnation_engine-0.1.0/src/tribulnation/engine/manager/store.py +305 -0
  33. tribulnation_engine-0.1.0/src/tribulnation/engine/py.typed +0 -0
  34. tribulnation_engine-0.1.0/src/tribulnation/engine/runtime/__init__.py +3 -0
  35. tribulnation_engine-0.1.0/src/tribulnation/engine/runtime/__init__.pyi +14 -0
  36. tribulnation_engine-0.1.0/src/tribulnation/engine/runtime/context.py +77 -0
  37. tribulnation_engine-0.1.0/src/tribulnation/engine/runtime/local.py +366 -0
  38. tribulnation_engine-0.1.0/src/tribulnation/engine/runtime/logging.py +36 -0
  39. tribulnation_engine-0.1.0/src/tribulnation/engine/runtime/process.py +203 -0
  40. tribulnation_engine-0.1.0/src/tribulnation/engine/ui/_app/immutable/assets/0.BS0LOXZX.css +1 -0
  41. tribulnation_engine-0.1.0/src/tribulnation/engine/ui/_app/immutable/assets/2.Cg5XSmJo.css +1 -0
  42. tribulnation_engine-0.1.0/src/tribulnation/engine/ui/_app/immutable/chunks/1VhAfQzX.js +1 -0
  43. tribulnation_engine-0.1.0/src/tribulnation/engine/ui/_app/immutable/chunks/9qXYRaA8.js +1 -0
  44. tribulnation_engine-0.1.0/src/tribulnation/engine/ui/_app/immutable/chunks/Bswyvc5d.js +1 -0
  45. tribulnation_engine-0.1.0/src/tribulnation/engine/ui/_app/immutable/chunks/GNg5PO02.js +1 -0
  46. tribulnation_engine-0.1.0/src/tribulnation/engine/ui/_app/immutable/chunks/wbPk3Yxo.js +81 -0
  47. tribulnation_engine-0.1.0/src/tribulnation/engine/ui/_app/immutable/entry/app.D8Kbc3Vj.js +2 -0
  48. tribulnation_engine-0.1.0/src/tribulnation/engine/ui/_app/immutable/entry/start.BdONDuMw.js +1 -0
  49. tribulnation_engine-0.1.0/src/tribulnation/engine/ui/_app/immutable/nodes/0.D5ZShN99.js +1 -0
  50. tribulnation_engine-0.1.0/src/tribulnation/engine/ui/_app/immutable/nodes/1.DnfkyBS5.js +1 -0
  51. tribulnation_engine-0.1.0/src/tribulnation/engine/ui/_app/immutable/nodes/2.BpaXpkZF.js +90 -0
  52. tribulnation_engine-0.1.0/src/tribulnation/engine/ui/_app/version.json +1 -0
  53. tribulnation_engine-0.1.0/src/tribulnation/engine/ui/favicon.svg +6 -0
  54. tribulnation_engine-0.1.0/src/tribulnation/engine/ui/index.html +41 -0
  55. tribulnation_engine-0.1.0/src/tribulnation_engine.egg-info/PKG-INFO +255 -0
  56. tribulnation_engine-0.1.0/src/tribulnation_engine.egg-info/SOURCES.txt +58 -0
  57. tribulnation_engine-0.1.0/src/tribulnation_engine.egg-info/dependency_links.txt +1 -0
  58. tribulnation_engine-0.1.0/src/tribulnation_engine.egg-info/entry_points.txt +2 -0
  59. tribulnation_engine-0.1.0/src/tribulnation_engine.egg-info/requires.txt +9 -0
  60. tribulnation_engine-0.1.0/src/tribulnation_engine.egg-info/top_level.txt +1 -0
@@ -0,0 +1,255 @@
1
+ Metadata-Version: 2.4
2
+ Name: tribulnation-engine
3
+ Version: 0.1.0
4
+ Summary: Trading engine — gateway, task manager, and process SDK
5
+ Author-email: Marcel Claramunt <marcel@tribulnation.com>
6
+ Project-URL: repo, https://github.com/tribulnation/engine.git
7
+ Requires-Python: >=3.11
8
+ Description-Content-Type: text/markdown
9
+ Requires-Dist: typer
10
+ Requires-Dist: aiohttp
11
+ Requires-Dist: aiofiles
12
+ Requires-Dist: fastapi
13
+ Requires-Dist: aiosqlite
14
+ Requires-Dist: httpx
15
+ Requires-Dist: pydantic
16
+ Requires-Dist: python-dotenv
17
+ Requires-Dist: uvicorn
18
+
19
+ # Trading Engine
20
+
21
+ Run algorithmic trading strategies as managed tasks — with a live dashboard, per-task logs, restart policies, and a market SDK that abstracts across venues.
22
+
23
+ ---
24
+
25
+ ## Quickstart
26
+
27
+ ### 1 — Install
28
+
29
+ ```bash
30
+ pip install tribulnation-engine
31
+ ```
32
+
33
+ ### 2 — Initialize
34
+
35
+ Initialize the engine in a new directory:
36
+
37
+ ```bash
38
+ engine init
39
+ # creates engine.toml, .env, and strats/ directory
40
+ ```
41
+
42
+ Edit `engine.toml` and `.env` to configure accounts and strategies.
43
+
44
+ ### 3 — Write a strategy
45
+
46
+ ```python
47
+ # strats/my_strat.py
48
+ from tribulnation.engine import Process, RestartPolicy
49
+
50
+ class MyStrat(Process):
51
+ name = 'my-strat'
52
+ restart = RestartPolicy(on='error', delay=5.0)
53
+
54
+ async def run(self, market: str):
55
+ mkt = await self.sdk.market(market)
56
+ book = await mkt.depth()
57
+ self.log.info('mark price: %s', book.mark_price)
58
+ ```
59
+
60
+ `run()` parameters become the task's argument schema — they appear as a form in
61
+ the dashboard and are coerced to the declared types when a task is started.
62
+
63
+ ### 4 — Start the engine
64
+
65
+ ```bash
66
+ engine start # foreground, logs to stdout
67
+ engine start -d # background — writes .engine.pid and engine.log
68
+ ```
69
+
70
+ ### 5 — Run a strategy
71
+
72
+ From the dashboard at `http://localhost:3121` or via the CLI:
73
+
74
+ ```bash
75
+ engine task start my-strat bitcoin -a market=mexc:spot:BTCUSDT
76
+ # engine task start <process_name> <run_id> -a <arg_name>=<arg_value> ...
77
+ ```
78
+
79
+ ### 6 — Stop a strategy
80
+
81
+ From the dashboard or:
82
+
83
+ ```bash
84
+ engine task stop my-strat bitcoin
85
+ # engine task stop <process_name> <run_id>
86
+ ```
87
+
88
+ ---
89
+
90
+ ## Strategies
91
+
92
+ ### The `Process` base
93
+
94
+ ```python
95
+ from tribulnation.engine import Process, RestartPolicy
96
+ ```
97
+
98
+ Implement `async def run(self, ...)`. Anything `run()` needs is injected via
99
+ `self`:
100
+
101
+ | Attribute | What it is |
102
+ |-----------|------------|
103
+ | `self.sdk` | Market SDK — call `await self.sdk.market('venue::symbol')` |
104
+ | `self.log` | Logger scoped to this task |
105
+ | `self.run_id` | The run ID this task was started with |
106
+ | `self.set_state(s)` | Publish a named state (`'connected'`, `'quoting'`, …) |
107
+ | `self.require(cls, id)` | Declare a dependency on another running task |
108
+ | `self.ipc` | Call methods on `Service` processes |
109
+
110
+ ### Restart policy
111
+
112
+ ```python
113
+ class Hedger(Process):
114
+ restart = RestartPolicy(on='error', delay=5.0)
115
+ ```
116
+
117
+ | `on=` | Behaviour |
118
+ |-------|-----------|
119
+ | `'never'` | Stop on exit or error |
120
+ | `'error'` | Restart after an unhandled exception, with `delay` seconds between attempts |
121
+ | `'always'` | Restart after any exit |
122
+
123
+ `max_attempts=N` caps the number of consecutive restarts.
124
+
125
+ ### Dependencies
126
+
127
+ Use `require()` to gate a task on another reaching a named state:
128
+
129
+ ```python
130
+ class DydxMaker(Process):
131
+ async def run(self, maker: str, hedge: str) -> None:
132
+ async with self.require(Hedger, self.run_id, state='connected')(
133
+ listen=maker, hedge=hedge
134
+ ):
135
+ # runs only while Hedger/{self.run_id} is alive and in state 'connected'
136
+ ...
137
+ ```
138
+
139
+ `require()` also auto-starts the dependency if it isn't running, and cancels
140
+ the dependent if the dependency stops or errors.
141
+
142
+ ### Logging
143
+
144
+ Standard `logging` calls are automatically captured and streamed to the
145
+ dashboard. No setup needed:
146
+
147
+ ```python
148
+ log = logging.getLogger(__name__)
149
+
150
+ class Hedger(Process):
151
+ async def run(self, ...):
152
+ log.info('listener live: %s → %s', listen, hedge)
153
+ # or use the process logger:
154
+ self.log.info('works the same way')
155
+ ```
156
+
157
+ `INFO` and above are always captured. `DEBUG` records are captured only from
158
+ loggers whose name starts with a prefix listed in `[daemon] debug_loggers`:
159
+
160
+ ```toml
161
+ [daemon]
162
+ debug_loggers = ["strats"]
163
+ ```
164
+
165
+ ---
166
+
167
+ ## Config reference (`engine.toml`)
168
+
169
+ ```toml
170
+ [engine]
171
+ processes = [
172
+ "strats.hedger:Hedger",
173
+ "strats.dydx_maker:DydxMaker",
174
+ ]
175
+
176
+ [daemon]
177
+ host = "localhost"
178
+ port = 3121
179
+ db = "engine.db" # SQLite path; default "engine.db"
180
+ max_logs = 500 # in-memory log lines per task; default unlimited
181
+
182
+ [accounts.dydx-main]
183
+ venue = "dydx"
184
+ mnemonic = "$DYDX_MNEMONIC"
185
+
186
+ [accounts.hyperliquid-main]
187
+ venue = "hyperliquid"
188
+ address = "$HYPERLIQUID_ADDRESS"
189
+ private_key = "$HYPERLIQUID_PRIVATE_KEY"
190
+
191
+ [[tasks]]
192
+ process = "hedger"
193
+ id = "btc"
194
+ args = { listen = "dydx:perp:BTC-USD", hedge = "hl::BTC" }
195
+ ```
196
+
197
+ Account credentials are resolved from environment variables when the value
198
+ starts with `$`. `engine start` automatically loads `.env` from the same
199
+ directory as `engine.toml`.
200
+
201
+ `[[tasks]]` entries are started automatically when the engine starts. Each
202
+ entry needs `process` (the registered process name), `id` (the run ID), and
203
+ optionally `args` (a table of keyword arguments matching the process
204
+ signature).
205
+
206
+ ---
207
+
208
+ ## CLI
209
+
210
+ ```bash
211
+ engine start # launch gateway + manager (foreground)
212
+ engine start -d # background — writes .engine.pid + engine.log
213
+ engine stop # stop a background engine
214
+ engine task start hedger btc -a listen=dydx:perp:BTC-USD -a hedge=hyperliquid::BTC
215
+ engine task stop hedger btc
216
+ engine register strats.new_strat:NewStrat # hot-load a process class
217
+ ```
218
+
219
+ ---
220
+
221
+ ## HTTP API
222
+
223
+ | Method | Path | Description |
224
+ |--------|------|-------------|
225
+ | `GET` | `/api/tasks` | Running tasks |
226
+ | `GET` | `/api/tasks/history` | Completed tasks (SQLite) |
227
+ | `GET` | `/api/task/{process}/{id}` | Single task |
228
+ | `GET` | `/api/task/{process}/{id}/logs` | Task logs |
229
+ | `GET` | `/api/task/{process}/{id}/attempts` | Restart attempts |
230
+ | `POST` | `/api/task/{process}/{id}` | Start a task — body: `{"args": {...}}` |
231
+ | `DELETE` | `/api/task/{process}/{id}` | Stop a task |
232
+ | `DELETE` | `/api/task/{process}/{id}?purge=true` | Stop and remove from history |
233
+ | `DELETE` | `/api/task/{process}/{id}/logs` | Clear logs and attempt history |
234
+ | `GET` | `/api/processes` | Registered process classes |
235
+ | `POST` | `/api/processes` | Register a process at runtime |
236
+ | `GET` | `/api/health` | Health check |
237
+ | `GET` | `/api/ws` | WebSocket event feed |
238
+
239
+ ---
240
+
241
+ ## Architecture
242
+
243
+ ```
244
+ engine start
245
+ ├── engine gateway owns real venue SDK connections, serves the market RPC protocol
246
+ └── engine manager task control plane
247
+ ├── in-process runtime — strategies run as asyncio tasks
248
+ ├── REST + WebSocket API (FastAPI / uvicorn)
249
+ └── SQLite store — task history, logs, restart attempts
250
+ ```
251
+
252
+ Strategies never hold venue connections directly. `self.sdk` inside a task is a
253
+ `ProxySDK` that routes market calls to the gateway over a local Unix socket. The
254
+ gateway owns the real credentials and connections; the manager owns task
255
+ lifecycle and the dashboard.
@@ -0,0 +1,237 @@
1
+ # Trading Engine
2
+
3
+ Run algorithmic trading strategies as managed tasks — with a live dashboard, per-task logs, restart policies, and a market SDK that abstracts across venues.
4
+
5
+ ---
6
+
7
+ ## Quickstart
8
+
9
+ ### 1 — Install
10
+
11
+ ```bash
12
+ pip install tribulnation-engine
13
+ ```
14
+
15
+ ### 2 — Initialize
16
+
17
+ Initialize the engine in a new directory:
18
+
19
+ ```bash
20
+ engine init
21
+ # creates engine.toml, .env, and strats/ directory
22
+ ```
23
+
24
+ Edit `engine.toml` and `.env` to configure accounts and strategies.
25
+
26
+ ### 3 — Write a strategy
27
+
28
+ ```python
29
+ # strats/my_strat.py
30
+ from tribulnation.engine import Process, RestartPolicy
31
+
32
+ class MyStrat(Process):
33
+ name = 'my-strat'
34
+ restart = RestartPolicy(on='error', delay=5.0)
35
+
36
+ async def run(self, market: str):
37
+ mkt = await self.sdk.market(market)
38
+ book = await mkt.depth()
39
+ self.log.info('mark price: %s', book.mark_price)
40
+ ```
41
+
42
+ `run()` parameters become the task's argument schema — they appear as a form in
43
+ the dashboard and are coerced to the declared types when a task is started.
44
+
45
+ ### 4 — Start the engine
46
+
47
+ ```bash
48
+ engine start # foreground, logs to stdout
49
+ engine start -d # background — writes .engine.pid and engine.log
50
+ ```
51
+
52
+ ### 5 — Run a strategy
53
+
54
+ From the dashboard at `http://localhost:3121` or via the CLI:
55
+
56
+ ```bash
57
+ engine task start my-strat bitcoin -a market=mexc:spot:BTCUSDT
58
+ # engine task start <process_name> <run_id> -a <arg_name>=<arg_value> ...
59
+ ```
60
+
61
+ ### 6 — Stop a strategy
62
+
63
+ From the dashboard or:
64
+
65
+ ```bash
66
+ engine task stop my-strat bitcoin
67
+ # engine task stop <process_name> <run_id>
68
+ ```
69
+
70
+ ---
71
+
72
+ ## Strategies
73
+
74
+ ### The `Process` base
75
+
76
+ ```python
77
+ from tribulnation.engine import Process, RestartPolicy
78
+ ```
79
+
80
+ Implement `async def run(self, ...)`. Anything `run()` needs is injected via
81
+ `self`:
82
+
83
+ | Attribute | What it is |
84
+ |-----------|------------|
85
+ | `self.sdk` | Market SDK — call `await self.sdk.market('venue::symbol')` |
86
+ | `self.log` | Logger scoped to this task |
87
+ | `self.run_id` | The run ID this task was started with |
88
+ | `self.set_state(s)` | Publish a named state (`'connected'`, `'quoting'`, …) |
89
+ | `self.require(cls, id)` | Declare a dependency on another running task |
90
+ | `self.ipc` | Call methods on `Service` processes |
91
+
92
+ ### Restart policy
93
+
94
+ ```python
95
+ class Hedger(Process):
96
+ restart = RestartPolicy(on='error', delay=5.0)
97
+ ```
98
+
99
+ | `on=` | Behaviour |
100
+ |-------|-----------|
101
+ | `'never'` | Stop on exit or error |
102
+ | `'error'` | Restart after an unhandled exception, with `delay` seconds between attempts |
103
+ | `'always'` | Restart after any exit |
104
+
105
+ `max_attempts=N` caps the number of consecutive restarts.
106
+
107
+ ### Dependencies
108
+
109
+ Use `require()` to gate a task on another reaching a named state:
110
+
111
+ ```python
112
+ class DydxMaker(Process):
113
+ async def run(self, maker: str, hedge: str) -> None:
114
+ async with self.require(Hedger, self.run_id, state='connected')(
115
+ listen=maker, hedge=hedge
116
+ ):
117
+ # runs only while Hedger/{self.run_id} is alive and in state 'connected'
118
+ ...
119
+ ```
120
+
121
+ `require()` also auto-starts the dependency if it isn't running, and cancels
122
+ the dependent if the dependency stops or errors.
123
+
124
+ ### Logging
125
+
126
+ Standard `logging` calls are automatically captured and streamed to the
127
+ dashboard. No setup needed:
128
+
129
+ ```python
130
+ log = logging.getLogger(__name__)
131
+
132
+ class Hedger(Process):
133
+ async def run(self, ...):
134
+ log.info('listener live: %s → %s', listen, hedge)
135
+ # or use the process logger:
136
+ self.log.info('works the same way')
137
+ ```
138
+
139
+ `INFO` and above are always captured. `DEBUG` records are captured only from
140
+ loggers whose name starts with a prefix listed in `[daemon] debug_loggers`:
141
+
142
+ ```toml
143
+ [daemon]
144
+ debug_loggers = ["strats"]
145
+ ```
146
+
147
+ ---
148
+
149
+ ## Config reference (`engine.toml`)
150
+
151
+ ```toml
152
+ [engine]
153
+ processes = [
154
+ "strats.hedger:Hedger",
155
+ "strats.dydx_maker:DydxMaker",
156
+ ]
157
+
158
+ [daemon]
159
+ host = "localhost"
160
+ port = 3121
161
+ db = "engine.db" # SQLite path; default "engine.db"
162
+ max_logs = 500 # in-memory log lines per task; default unlimited
163
+
164
+ [accounts.dydx-main]
165
+ venue = "dydx"
166
+ mnemonic = "$DYDX_MNEMONIC"
167
+
168
+ [accounts.hyperliquid-main]
169
+ venue = "hyperliquid"
170
+ address = "$HYPERLIQUID_ADDRESS"
171
+ private_key = "$HYPERLIQUID_PRIVATE_KEY"
172
+
173
+ [[tasks]]
174
+ process = "hedger"
175
+ id = "btc"
176
+ args = { listen = "dydx:perp:BTC-USD", hedge = "hl::BTC" }
177
+ ```
178
+
179
+ Account credentials are resolved from environment variables when the value
180
+ starts with `$`. `engine start` automatically loads `.env` from the same
181
+ directory as `engine.toml`.
182
+
183
+ `[[tasks]]` entries are started automatically when the engine starts. Each
184
+ entry needs `process` (the registered process name), `id` (the run ID), and
185
+ optionally `args` (a table of keyword arguments matching the process
186
+ signature).
187
+
188
+ ---
189
+
190
+ ## CLI
191
+
192
+ ```bash
193
+ engine start # launch gateway + manager (foreground)
194
+ engine start -d # background — writes .engine.pid + engine.log
195
+ engine stop # stop a background engine
196
+ engine task start hedger btc -a listen=dydx:perp:BTC-USD -a hedge=hyperliquid::BTC
197
+ engine task stop hedger btc
198
+ engine register strats.new_strat:NewStrat # hot-load a process class
199
+ ```
200
+
201
+ ---
202
+
203
+ ## HTTP API
204
+
205
+ | Method | Path | Description |
206
+ |--------|------|-------------|
207
+ | `GET` | `/api/tasks` | Running tasks |
208
+ | `GET` | `/api/tasks/history` | Completed tasks (SQLite) |
209
+ | `GET` | `/api/task/{process}/{id}` | Single task |
210
+ | `GET` | `/api/task/{process}/{id}/logs` | Task logs |
211
+ | `GET` | `/api/task/{process}/{id}/attempts` | Restart attempts |
212
+ | `POST` | `/api/task/{process}/{id}` | Start a task — body: `{"args": {...}}` |
213
+ | `DELETE` | `/api/task/{process}/{id}` | Stop a task |
214
+ | `DELETE` | `/api/task/{process}/{id}?purge=true` | Stop and remove from history |
215
+ | `DELETE` | `/api/task/{process}/{id}/logs` | Clear logs and attempt history |
216
+ | `GET` | `/api/processes` | Registered process classes |
217
+ | `POST` | `/api/processes` | Register a process at runtime |
218
+ | `GET` | `/api/health` | Health check |
219
+ | `GET` | `/api/ws` | WebSocket event feed |
220
+
221
+ ---
222
+
223
+ ## Architecture
224
+
225
+ ```
226
+ engine start
227
+ ├── engine gateway owns real venue SDK connections, serves the market RPC protocol
228
+ └── engine manager task control plane
229
+ ├── in-process runtime — strategies run as asyncio tasks
230
+ ├── REST + WebSocket API (FastAPI / uvicorn)
231
+ └── SQLite store — task history, logs, restart attempts
232
+ ```
233
+
234
+ Strategies never hold venue connections directly. `self.sdk` inside a task is a
235
+ `ProxySDK` that routes market calls to the gateway over a local Unix socket. The
236
+ gateway owns the real credentials and connections; the manager owns task
237
+ lifecycle and the dashboard.
@@ -0,0 +1,39 @@
1
+ [build-system]
2
+ requires = ["setuptools", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "tribulnation-engine"
7
+ version = "0.1.0"
8
+ authors = [
9
+ {name="Marcel Claramunt", email="marcel@tribulnation.com"}
10
+ ]
11
+ description = "Trading engine — gateway, task manager, and process SDK"
12
+ dependencies = [
13
+ "typer",
14
+ "aiohttp",
15
+ "aiofiles",
16
+ "fastapi",
17
+ "aiosqlite",
18
+ "httpx",
19
+ "pydantic",
20
+ "python-dotenv",
21
+ "uvicorn",
22
+ ]
23
+ requires-python = ">=3.11"
24
+ readme = {file="README.md", content-type="text/markdown"}
25
+
26
+ [tool.setuptools.package-data]
27
+ "tribulnation.engine" = ["ui/**/*"]
28
+
29
+ [project.urls]
30
+ repo = "https://github.com/tribulnation/engine.git"
31
+
32
+ [project.scripts]
33
+ engine = "tribulnation.engine.cli:app"
34
+
35
+ # [project.optional-dependencies]
36
+ # test = [
37
+ # "pytest < 5.0.0",
38
+ # "pytest-cov[all]"
39
+ # ]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,3 @@
1
+ import lazy_loader as lazy
2
+
3
+ __getattr__, __dir__, __all__ = lazy.attach_stub(__name__, __file__)
@@ -0,0 +1,51 @@
1
+ """Tribulnation Engine — gateway, task manager, and strategy SDK.
2
+
3
+ Quick-start::
4
+
5
+ # 1. Define a worker:
6
+ from tribulnation.engine import Process
7
+
8
+ class Hedge(Process):
9
+ async def run(self, listen: str, market: str) -> None:
10
+ mkt = await self.sdk.perp_market(listen)
11
+ self.set_state('connected')
12
+ async for trade in await mkt.trades_stream():
13
+ ...
14
+
15
+ # 2. Connect a client:
16
+ from tribulnation.engine import TradingEngine
17
+
18
+ async with TradingEngine.at('unix:///tmp/engine-gateway.sock', api_url='http://localhost:3121') as e:
19
+ book = await (await e.sdk.perp_market('dydx:perp:BTC-USD')).depth()
20
+ await e.task.start('hedge', 'btc', market='dydx:perp:BTC-USD', listen='hyper:perp:BTC')
21
+ """
22
+
23
+ from .client import TaskClient, TradingEngine
24
+ from .config import DaemonConfig, EngineConfig
25
+ from .gateway import EngineError, EngineGateway, ProxySDK
26
+ from .runtime import (
27
+ Process,
28
+ RPC,
29
+ Require,
30
+ RestartPolicy,
31
+ Runtime,
32
+ )
33
+ from .manager import Manager, Store, load_process
34
+
35
+ __all__ = [
36
+ 'Process',
37
+ 'RPC',
38
+ 'Require',
39
+ 'RestartPolicy',
40
+ 'Runtime',
41
+ 'EngineGateway',
42
+ 'ProxySDK',
43
+ 'EngineError',
44
+ 'TradingEngine',
45
+ 'TaskClient',
46
+ 'EngineConfig',
47
+ 'DaemonConfig',
48
+ 'Manager',
49
+ 'Store',
50
+ 'load_process',
51
+ ]
@@ -0,0 +1,18 @@
1
+ import typer
2
+
3
+ from .init import init
4
+ from .start import start
5
+ from .stop import stop
6
+ from .gateway import gateway
7
+ from .manager import manager
8
+ from .register import register
9
+ from .task import task_app
10
+
11
+ app = typer.Typer()
12
+ app.command()(init)
13
+ app.command()(start)
14
+ app.command()(stop)
15
+ app.command()(gateway)
16
+ app.command()(manager)
17
+ app.command()(register)
18
+ app.add_typer(task_app, name='task')
@@ -0,0 +1,61 @@
1
+ """CLI command: ``engine gateway`` — start the standalone gateway process."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing_extensions import Annotated
6
+ from pathlib import Path
7
+ import asyncio
8
+ import logging
9
+
10
+ import typer
11
+ from dotenv import load_dotenv
12
+
13
+ _GATEWAY_LOGGERS = ('tribulnation.engine.gateway.server',)
14
+
15
+ _FMT = '%(asctime)s %(name)-40s %(message)s'
16
+ _DATEFMT = '%H:%M:%S'
17
+
18
+
19
+ def gateway(
20
+ socket: Annotated[
21
+ str | None,
22
+ typer.Option('--socket', '-s', help='Unix socket path (overrides engine.toml).'),
23
+ ] = None,
24
+ config: Annotated[
25
+ Path, typer.Option('--config', '-c', help='Path to TOML config file.')
26
+ ] = Path('engine.toml'),
27
+ verbose: Annotated[
28
+ int,
29
+ typer.Option(
30
+ '--verbose',
31
+ '-v',
32
+ count=True,
33
+ help='Increase log verbosity. Repeat for all debug output.',
34
+ ),
35
+ ] = 0,
36
+ ):
37
+ """Start the WebSocket gateway on a Unix socket."""
38
+ _configure_logging(verbose)
39
+ load_dotenv(config.parent / '.env')
40
+
41
+ from tribulnation.engine.config import EngineConfig
42
+ from tribulnation.engine.gateway.server import run_gateway
43
+
44
+ cfg = EngineConfig.load(config)
45
+ sock = socket or cfg.daemon.socket
46
+ asyncio.run(run_gateway(sock, cfg))
47
+
48
+
49
+ def _configure_logging(verbose: int) -> None:
50
+ if verbose >= 2:
51
+ logging.basicConfig(level=logging.DEBUG, format=_FMT, datefmt=_DATEFMT)
52
+ return
53
+ if verbose:
54
+ handler = logging.StreamHandler()
55
+ handler.setLevel(logging.DEBUG)
56
+ handler.setFormatter(logging.Formatter(_FMT, datefmt=_DATEFMT))
57
+ root = logging.getLogger()
58
+ root.setLevel(logging.WARNING)
59
+ root.addHandler(handler)
60
+ for name in _GATEWAY_LOGGERS:
61
+ logging.getLogger(name).setLevel(logging.DEBUG)