alphaengine 0.2.0__tar.gz → 0.3.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 (66) hide show
  1. {alphaengine-0.2.0 → alphaengine-0.3.0}/.github/workflows/ci.yml +6 -0
  2. alphaengine-0.3.0/.gitignore +30 -0
  3. {alphaengine-0.2.0 → alphaengine-0.3.0}/PKG-INFO +271 -79
  4. alphaengine-0.3.0/README.md +457 -0
  5. {alphaengine-0.2.0 → alphaengine-0.3.0}/pyproject.toml +15 -0
  6. alphaengine-0.3.0/scripts/gen_docs.py +138 -0
  7. alphaengine-0.3.0/src/alphaengine/__main__.py +28 -0
  8. {alphaengine-0.2.0 → alphaengine-0.3.0}/src/alphaengine/_version.py +6 -1
  9. alphaengine-0.3.0/src/alphaengine/agent/__init__.py +40 -0
  10. alphaengine-0.3.0/src/alphaengine/agent/answer.py +348 -0
  11. alphaengine-0.3.0/src/alphaengine/auth.py +138 -0
  12. alphaengine-0.3.0/src/alphaengine/cli.py +3173 -0
  13. {alphaengine-0.2.0 → alphaengine-0.3.0}/src/alphaengine/client/__init__.py +4 -0
  14. alphaengine-0.3.0/src/alphaengine/client/agent.py +262 -0
  15. alphaengine-0.3.0/src/alphaengine/client/executor.py +748 -0
  16. {alphaengine-0.2.0 → alphaengine-0.3.0}/src/alphaengine/client/session.py +210 -9
  17. alphaengine-0.3.0/src/alphaengine/commands.py +390 -0
  18. {alphaengine-0.2.0 → alphaengine-0.3.0}/src/alphaengine/core/__init__.py +21 -0
  19. alphaengine-0.3.0/src/alphaengine/core/profile.py +282 -0
  20. alphaengine-0.3.0/src/alphaengine/core/screen.py +286 -0
  21. alphaengine-0.3.0/src/alphaengine/core/series_shapes.py +71 -0
  22. alphaengine-0.3.0/src/alphaengine/core/signals.py +283 -0
  23. alphaengine-0.3.0/src/alphaengine/core/stress.py +226 -0
  24. {alphaengine-0.2.0 → alphaengine-0.3.0}/src/alphaengine/core/technical.py +5 -2
  25. alphaengine-0.2.0/examples/momentum.py → alphaengine-0.3.0/src/alphaengine/demo.py +80 -10
  26. alphaengine-0.3.0/src/alphaengine/demo_book.py +31 -0
  27. alphaengine-0.3.0/src/alphaengine/demo_returns.py +18 -0
  28. alphaengine-0.3.0/src/alphaengine/demo_signal.py +44 -0
  29. alphaengine-0.3.0/src/alphaengine/demo_universe.py +18 -0
  30. alphaengine-0.3.0/src/alphaengine/loaders.py +227 -0
  31. alphaengine-0.3.0/src/alphaengine/model.py +248 -0
  32. alphaengine-0.3.0/tests/test_agent.py +136 -0
  33. alphaengine-0.3.0/tests/test_answer.py +263 -0
  34. alphaengine-0.3.0/tests/test_cli.py +1324 -0
  35. {alphaengine-0.2.0 → alphaengine-0.3.0}/tests/test_client.py +305 -0
  36. alphaengine-0.3.0/tests/test_commands.py +207 -0
  37. {alphaengine-0.2.0 → alphaengine-0.3.0}/tests/test_goldens.py +40 -0
  38. alphaengine-0.3.0/tests/test_loaders.py +231 -0
  39. alphaengine-0.3.0/tests/test_screen.py +218 -0
  40. alphaengine-0.3.0/tests/test_signals.py +278 -0
  41. alphaengine-0.2.0/.gitignore +0 -14
  42. alphaengine-0.2.0/CHANGELOG.md +0 -198
  43. alphaengine-0.2.0/README.md +0 -265
  44. alphaengine-0.2.0/src/alphaengine/agent/__init__.py +0 -16
  45. alphaengine-0.2.0/src/alphaengine/cli.py +0 -506
  46. alphaengine-0.2.0/src/alphaengine/client/executor.py +0 -329
  47. alphaengine-0.2.0/tests/test_cli.py +0 -187
  48. {alphaengine-0.2.0 → alphaengine-0.3.0}/.github/workflows/publish.yml +0 -0
  49. {alphaengine-0.2.0 → alphaengine-0.3.0}/LICENSE +0 -0
  50. {alphaengine-0.2.0 → alphaengine-0.3.0}/SECURITY.md +0 -0
  51. {alphaengine-0.2.0 → alphaengine-0.3.0}/src/alphaengine/__init__.py +0 -0
  52. {alphaengine-0.2.0 → alphaengine-0.3.0}/src/alphaengine/agent/driver.py +0 -0
  53. {alphaengine-0.2.0 → alphaengine-0.3.0}/src/alphaengine/core/backtest.py +0 -0
  54. {alphaengine-0.2.0 → alphaengine-0.3.0}/src/alphaengine/core/factors.py +0 -0
  55. {alphaengine-0.2.0 → alphaengine-0.3.0}/src/alphaengine/core/pairs.py +0 -0
  56. {alphaengine-0.2.0 → alphaengine-0.3.0}/src/alphaengine/core/performance.py +0 -0
  57. {alphaengine-0.2.0 → alphaengine-0.3.0}/src/alphaengine/core/risk.py +0 -0
  58. {alphaengine-0.2.0 → alphaengine-0.3.0}/src/alphaengine/core/validation.py +0 -0
  59. {alphaengine-0.2.0 → alphaengine-0.3.0}/src/alphaengine/py.typed +0 -0
  60. {alphaengine-0.2.0 → alphaengine-0.3.0}/src/alphaengine/study/__init__.py +0 -0
  61. {alphaengine-0.2.0 → alphaengine-0.3.0}/src/alphaengine/study/report.py +0 -0
  62. {alphaengine-0.2.0 → alphaengine-0.3.0}/src/alphaengine/study/schema.py +0 -0
  63. {alphaengine-0.2.0 → alphaengine-0.3.0}/src/alphaengine/sweep/__init__.py +0 -0
  64. {alphaengine-0.2.0 → alphaengine-0.3.0}/src/alphaengine/sweep/runner.py +0 -0
  65. {alphaengine-0.2.0 → alphaengine-0.3.0}/tests/test_smoke.py +0 -0
  66. {alphaengine-0.2.0 → alphaengine-0.3.0}/tests/test_sweep.py +0 -0
@@ -45,6 +45,12 @@ jobs:
45
45
  - run: ruff check src tests
46
46
  - run: ruff format --check src tests
47
47
  - run: mypy src
48
+ # THE FIFTH COPY GUARD. The command list lived in three hand-maintained
49
+ # places and none of them agreed: `--data` and `--universe` shipped and
50
+ # appeared in one. It is generated from `commands.py` now, and this fails
51
+ # the build when the README falls behind rather than letting a stale list
52
+ # quietly tell somebody a feature does not exist.
53
+ - run: python scripts/gen_docs.py --check
48
54
 
49
55
  # The numbers this library returns are a public contract once published: a
50
56
  # study written today has to reproduce in two years. This job exists to make
@@ -0,0 +1,30 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ .venv/
4
+ venv/
5
+ dist/
6
+ build/
7
+ *.egg-info/
8
+ .pytest_cache/
9
+ .mypy_cache/
10
+ .ruff_cache/
11
+ .coverage
12
+ htmlcov/
13
+ .DS_Store
14
+ .civenv/
15
+
16
+ # CREDENTIALS. Nothing here was ignored, so a `.env` created while working on
17
+ # this repo would have been committed to a PUBLIC repository with nothing to
18
+ # stop it. Found in a sweep 2026-08-02; no secret was ever actually committed.
19
+ .env
20
+ .env.*
21
+ *.pem
22
+ *.key
23
+ secrets.json
24
+ credentials.json
25
+
26
+ # Artifacts the package itself writes. `r.save()` drops study.json in the
27
+ # working directory by default, so running the example in a clone leaves one
28
+ # staged for the next `git add .`.
29
+ study.json
30
+ *.study.json
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: alphaengine
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: Validated research tooling for investment strategies: deflation, overfitting detection, and honest trial counts.
5
5
  Project-URL: Homepage, https://github.com/quantOSC/alphaengine
6
6
  Project-URL: Documentation, https://github.com/quantOSC/alphaengine#readme
@@ -41,49 +41,216 @@ Description-Content-Type: text/markdown
41
41
 
42
42
  # AlphaEngine
43
43
 
44
- Validated research tooling for investment strategies. Run a parameter search,
45
- get back the shape of the result and an honest read on whether it survives the
46
- number of things you tried.
44
+ **The research loop, on your machine.** Ask what is worth looking at, whether it
45
+ holds up, how much to hold, and whether anything has crossed a line. Your data
46
+ never leaves.
47
47
 
48
48
  ```bash
49
49
  pip install alphaengine
50
+ alphaengine demo # the whole offline half, no account, no data of your own
50
51
  ```
51
52
 
52
- ```python
53
- from alphaengine import sweep
53
+ ## What it answers
54
54
 
55
- r = sweep(backtest_fn, {"fast": [5, 10, 20], "slow": [50, 100, 200]}, data=prices)
55
+ Eight questions, in the order a research week actually asks them. The command
56
+ IS the question; only two of them need code of yours.
57
+
58
+ | Ask | Command | Workflow | Needs |
59
+ |---|---|---|---|
60
+ | Can I trust this data? | `diagnose` | `diagnose_data` | prices |
61
+ | What is worth a look? | `screen` | `screen_universe` | prices |
62
+ | Does this signal carry information? | `signal` | `evaluate_signal` | a signal panel + prices |
63
+ | Is this real, once you count the tries? | `validate` | `validate_study` | prices + your backtest |
64
+ | Where does it break? | `stress` | `stress_study` | a return series |
65
+ | Is it new, or my book again? | `overlap` | `check_overlap` | the candidate + the book |
66
+ | How much should I hold? | `size` | `size_position` | a return series |
67
+ | Still inside the lines? | `monitor` | `monitor_sleeve` | a return series |
68
+
69
+ ```bash
70
+ alphaengine diagnose --data prices.csv # before anything runs on it
71
+ alphaengine screen --universe sp500
72
+ alphaengine size --data returns.csv
73
+ ```
74
+
75
+ **The refusals are the point.** A screen over a universe that mostly could not
76
+ be measured is refused as a survivorship artifact rather than ranked. A size on
77
+ a record shorter than its own minimum track length is refused outright, because
78
+ a small position is still a claim and the caveat does not travel with it into a
79
+ book. A monitor with no stated tolerances reports `unchecked` — never a green
80
+ light. A stop exits 0: "this did not clear the bar" is the system working.
56
81
 
57
- r.surface() # is the result a broad plateau or a single lucky configuration?
58
- r.verdict() # deflated for the 9 trials that were actually run
59
- r.save() # study.json, on your disk
82
+ Or say it in plain English and let your own model pick:
83
+
84
+ ```bash
85
+ alphaengine
86
+ > which of my names are overbought on RSI?
60
87
  ```
61
88
 
62
- Everything above runs offline, with no account and no key.
89
+ That second path is EXPLORATORY: the model chooses a workflow and then chooses
90
+ each step from what the server permits. Two runs of the same question may
91
+ differ, and the run says so. `run <workflow>` is SCRIPTED and reproducible.
92
+ Both are legitimate; presenting one as the other is not.
93
+
94
+ ## The three rungs
95
+
96
+ Each is useful without the one above it. The boot screen shows which are lit.
63
97
 
64
- ## Try it without writing anything
98
+ | Rung | What you need | What you get |
99
+ |---|---|---|
100
+ | **The maths** | nothing | Every statistic, offline, forever. No account. |
101
+ | **Workflows** | a QuantOS `ae_live_` key | The loop end to end, with the record. |
102
+ | **Ask anything** | your OWN model key | Plain English in. Runs under your account, not ours. |
65
103
 
66
- The repo ships a runnable project module, so you can see the whole offline half
67
- work before deciding whether any of this is for you:
104
+ Nothing here stores a model key: it is read from your environment at call time
105
+ and handed to the provider's own client. There is no field to put one in.
106
+
107
+ ## Getting your data in
108
+
109
+ Three doors, and nothing is ever fetched on your behalf.
68
110
 
69
111
  ```bash
70
- git clone https://github.com/quantOSC/alphaengine && cd alphaengine
71
- pip install -e .
72
- python -m examples.momentum
112
+ alphaengine run screen_universe --data prices.csv # a local CSV
113
+ alphaengine run screen_universe --universe sp500 # registered in the portal
114
+ alphaengine run validate_study --project research.momentum # a module of yours
73
115
  ```
74
116
 
117
+ `--data` reads three shapes, decided by the header and nothing else:
118
+
75
119
  ```
76
- trials 9 (derived_from_grid)
77
- verdict marginal
78
- surface ridge
79
- dsr 0.6352
120
+ wide date,AAPL,MSFT,NVDA one column per name
121
+ long date,symbol,close one row per name per day
122
+ series date,close a single series, or one bare column of numbers
80
123
  ```
81
124
 
82
- That is a moving-average crossover on **synthetic prices from a fixed seed**
83
- no download, no data licence, same numbers on every machine. It is a
84
- demonstration of the wiring, not a strategy. A crossover on a random walk has no
85
- edge, and the verdict says so rather than flattering it. **That is the example
86
- working, not failing.**
125
+ A file it cannot place raises and names all three rather than guessing. A loader
126
+ that guesses eventually guesses wrong on somebody's book, and the failure is not
127
+ an error: it is a screen that ranked returns as prices and produced a shortlist
128
+ nobody can tell is wrong.
129
+
130
+ `--project` is the only door that can carry a **simulator**, because a simulator
131
+ is code. That is why `validate_study` needs it — and `evaluate_signal` needs it
132
+ for the scores it measures — while the rest run on prices alone.
133
+
134
+ `--universe` brings the closes you stored with it in the portal. That is your own
135
+ upload decrypted back to your own account, not us fetching market data, and the
136
+ distinction is the whole of the data boundary below.
137
+
138
+ ## Command reference
139
+
140
+ `alphaengine commands` prints this directory in the terminal, and
141
+ `alphaengine commands run` expands any one of them.
142
+
143
+ <!-- COMMANDS:START -->
144
+ <!-- Generated by scripts/gen_docs.py from src/alphaengine/commands.py.
145
+ Do not edit by hand; run `python scripts/gen_docs.py --write`. -->
146
+
147
+ ### Get started
148
+
149
+ | Command | Does | Where |
150
+ |---|---|---|
151
+ | `demo` | run the built-in example offline, with no account and no data | shell + session |
152
+ | `workflows` | what the server offers, what each needs, and which reproduce | shell + session |
153
+ | `key [quantos \| anthropic \| openai]` | enter a credential now, or see which rungs are unlocked | session |
154
+ | `commands [verb]` | this directory, or one command in full | shell + session |
155
+
156
+ ### Do the work
157
+
158
+ | Command | Does | Where |
159
+ |---|---|---|
160
+ | `diagnose` | can I trust this data? | shell + session |
161
+ | `screen` | what is worth a look? | shell + session |
162
+ | `signal` | does this signal carry information? | shell + session |
163
+ | `validate` | is this real? | shell + session |
164
+ | `stress` | where does it break? | shell + session |
165
+ | `overlap` | is it new, or my book again? | shell + session |
166
+ | `size` | how much? | shell + session |
167
+ | `monitor` | still inside the lines? | shell + session |
168
+ | `run <workflow>` | run one workflow exactly as written | shell + session |
169
+ | `<anything else>` | ask in plain English; your model picks a workflow and drives it | session |
170
+ | `status` | the current run, and what is loaded | session |
171
+
172
+ ### Bring your data
173
+
174
+ | Command | Does | Where |
175
+ |---|---|---|
176
+ | `universe <name>` | load a universe you registered in the portal, with its closes | session |
177
+ | `data <file.csv>` | load a local CSV without leaving the session | session |
178
+ | `project <module>` | load `data` and `backtest_fn` from a module of yours | session |
179
+
180
+ ### Session
181
+
182
+ | Command | Does | Where |
183
+ |---|---|---|
184
+ | `logout` | remove stored credentials from this machine | shell + session |
185
+ | `version` | print the version | shell |
186
+ | `help` | the short list | session |
187
+ | `quit` | leave the session | session |
188
+
189
+ ### Data flags
190
+
191
+ | Flag | What it takes |
192
+ |---|---|
193
+ | `--data FILE` | a local CSV. Wide (date,AAPL,MSFT), long (date,symbol,close), or one series. |
194
+ | `--universe NAME` | a universe you registered in the portal, with the closes you stored. |
195
+ | `--project MODULE` | a Python module. The only door that can carry a simulator. |
196
+
197
+ ### Flags
198
+
199
+ | Flag | Does |
200
+ |---|---|
201
+ | `--url URL` | workflow server (default $QUANTOS_API_URL or the public API) |
202
+ | `--key KEY` | portal-issued ae_live_ key (default $QUANTOS_API_KEY) |
203
+ | `--project MODULE` | a module exposing `data` and `backtest_fn` |
204
+ | `--data FILE` | a local CSV: wide, long, or a single series |
205
+ | `--universe NAME` | a universe registered in the portal, with its stored closes |
206
+ | `--symbol TICKER` | one name out of a loaded universe; its closes become the return series |
207
+ | `--sleeve NAME` | the sleeve this run belongs to, and the budget it is bound by |
208
+ | `--thesis ID` | a draft thesis to propose this run's shortlist as a sleeve for |
209
+ | `--label TEXT` | what to call the artifact this run produces |
210
+ | `--input K=V` | a workflow input; repeatable |
211
+ | `--quiet` | only the result, no step narration |
212
+
213
+ ### Examples
214
+
215
+ ```bash
216
+ alphaengine diagnose --universe <name>
217
+ alphaengine screen --universe <name>
218
+ alphaengine signal
219
+ alphaengine validate
220
+ alphaengine stress
221
+ alphaengine overlap
222
+ alphaengine size
223
+ alphaengine monitor
224
+ alphaengine demo
225
+ alphaengine workflows
226
+ alphaengine commands
227
+ alphaengine run screen_universe --universe sp500
228
+ alphaengine run size_position --data returns.csv
229
+ alphaengine run validate_study --project research.momentum
230
+ alphaengine logout
231
+ alphaengine version
232
+ ```
233
+
234
+ <!-- COMMANDS:END -->
235
+
236
+ ## The library, without the CLI
237
+
238
+ The commands above are a wrapper. Everything they compute is importable, runs
239
+ offline, and needs no account:
240
+
241
+ ```python
242
+ from alphaengine import sweep
243
+
244
+ r = sweep(backtest_fn, {"fast": [5, 10, 20], "slow": [50, 100, 200]}, data=prices)
245
+
246
+ r.surface() # is the result a broad plateau or a single lucky configuration?
247
+ r.verdict() # deflated for the 9 trials that were actually run
248
+ r.save() # study.json, on your disk
249
+ ```
250
+
251
+ The trial count is **derived from the grid that ran**, never asserted. Omit it
252
+ and it is recorded as `not_recorded` rather than defaulting to a flattering 1,
253
+ and an unrecorded denominator cannot reach an `edge` verdict at all.
87
254
 
88
255
  ## Writing your own `backtest_fn`
89
256
 
@@ -102,7 +269,8 @@ correspond. In practice: pick a warm-up long enough for the slowest window in
102
269
  your grid and start every configuration there.
103
270
 
104
271
  ```python
105
- WARMUP = 200 # covers the slowest `slow` in the grid
272
+ WARMUP = 200 # covers the slowest `slow` in the grid
273
+
106
274
 
107
275
  def backtest_fn(*, data, fast, slow):
108
276
  close = data["close"]
@@ -141,53 +309,6 @@ spike surrounded by failures is a result fitted to its own parameters.
141
309
  back, and a content hash of the data it ran on. Readable in a text editor,
142
310
  diffable, and versioned so it still parses in two years.
143
311
 
144
- ## Running a workflow from the terminal
145
-
146
- The offline half above is complete on its own. A *workflow* adds a sequence —
147
- what to run, in what order, and what stops the run — and that sequence lives on
148
- a QuantOS workflow server, so this part needs an account.
149
-
150
- ```bash
151
- alphaengine version # no account needed
152
- alphaengine workflows # what your workspace offers
153
- alphaengine run validate_study --project examples.momentum
154
- ```
155
-
156
- The run narrates itself, because a loop you cannot watch is a loop you cannot
157
- trust. The server names an op, this machine executes it and hands back figures:
158
-
159
- ```
160
- validate_study · <your workflow server>
161
- server → compute.sweep
162
- local · done
163
- server → compute.deflated_sharpe
164
- local · done
165
- server → Stop: the surface is a ridge, not a plateau
166
- ```
167
-
168
- `--project` names an ordinary module of yours exposing `data` and, if the
169
- workflow sweeps, `backtest_fn`. Authenticate with a key from the portal:
170
-
171
- ```bash
172
- export QUANTOS_API_KEY=ae_live_... # QUANTOS_API_URL for self-hosted or VPC
173
- ```
174
-
175
- **A stop exits 0.** "This did not clear the bar" is the system working, not a
176
- broken build — a non-zero exit there would make every CI pipeline treat an
177
- honest refusal as a failure, which is exactly the pressure that gets honesty
178
- controls switched off. Only a run that could not execute a step exits non-zero.
179
-
180
- > **Install this into the venv you do research in — not with `pipx` or
181
- > `uv tool install`.** The compute steps execute in-process against your own
182
- > DataFrames, so tool isolation, which is normally the right way to install a
183
- > CLI, is the one thing that cannot work here. You cannot have isolation and
184
- > in-process data access, and your data not moving is the point.
185
-
186
- **No LLM dependency and no key field.** `AgentDriver` takes a callable, so you
187
- bring your own model by passing a function. There is nowhere in this tool to put
188
- a model key, ours or yours, and that is how "runs under your own account" is
189
- satisfied structurally rather than promised.
190
-
191
312
  ## What is in it
192
313
 
193
314
  | Module | Contents |
@@ -212,10 +333,10 @@ the PM who will act on it, `report()` sends the study — and only the study.
212
333
  import os
213
334
  from alphaengine import Study, sweep
214
335
 
215
- os.environ["QUANTOS_API_KEY"] = "ae_live_..." # created in the portal
336
+ os.environ["QUANTOS_API_KEY"] = "ae_live_..." # created in the portal
216
337
 
217
338
  r = sweep(backtest_fn, grid, data=prices)
218
- r.save() # yours, on your disk, always
339
+ r.save() # yours, on your disk, always
219
340
 
220
341
  Study.from_sweep(r, label="momentum, 9 configs").report()
221
342
  ```
@@ -293,12 +414,83 @@ While the leading digit is 0 the minor position carries that rule — 0.1 → 0.
293
414
  what a changed figure costs — so every 0.2.x release produces identical numbers.
294
415
  CI fails if a pinned value moves.
295
416
 
417
+ ## Installing: "command not found"
418
+
419
+ The console script lives in your venv's `Scripts/` (Windows) or `bin/` (POSIX),
420
+ which joins your PATH only while that venv is **activated**. A fresh install
421
+ followed by `alphaengine` therefore says *command not found*, which reads as a
422
+ broken install and is not one.
423
+
424
+ ```powershell
425
+ .\.venv\Scripts\Activate.ps1 # Windows PowerShell
426
+ source .venv/bin/activate # macOS / Linux
427
+ alphaengine
428
+ ```
429
+
430
+ Or skip activation entirely — **`python -m alphaengine` always works**, because
431
+ the interpreter that can import the package can always run it:
432
+
433
+ ```bash
434
+ .venv/Scripts/python.exe -m alphaengine # Windows, unactivated
435
+ .venv/bin/python -m alphaengine # POSIX, unactivated
436
+ ```
437
+
438
+ This is the price of in-process data access, and it is the one cost we will not
439
+ engineer around: a launcher that worked from anywhere would have to run in its
440
+ own environment, which is exactly the isolation that makes `compute.*` unable to
441
+ see your DataFrames.
442
+
443
+ The run narrates itself, because a loop you cannot watch is a loop you cannot
444
+ trust. The server names an op, this machine executes it and hands back figures:
445
+
446
+ ```
447
+ validate_study · <your workflow server>
448
+ server → compute.sweep
449
+ local · done
450
+ server → compute.deflated_sharpe
451
+ local · done
452
+ server → Stop: the surface is a ridge, not a plateau
453
+ ```
454
+
455
+ `--project` names an ordinary module of yours exposing `data` and, if the
456
+ workflow sweeps, `backtest_fn`. Authenticate with a key from the portal:
457
+
458
+ ```bash
459
+ export QUANTOS_API_KEY=ae_live_... # QUANTOS_API_URL for self-hosted or VPC
460
+ ```
461
+
462
+ **A stop exits 0.** "This did not clear the bar" is the system working, not a
463
+ broken build — a non-zero exit there would make every CI pipeline treat an
464
+ honest refusal as a failure, which is exactly the pressure that gets honesty
465
+ controls switched off. Only a run that could not execute a step exits non-zero.
466
+
467
+ > **Install this into the venv you do research in — not with `pipx` or
468
+ > `uv tool install`.** The compute steps execute in-process against your own
469
+ > DataFrames, so tool isolation, which is normally the right way to install a
470
+ > CLI, is the one thing that cannot work here. You cannot have isolation and
471
+ > in-process data access, and your data not moving is the point.
472
+
473
+ **No LLM dependency and no key field.** `AgentDriver` takes a callable, so you
474
+ bring your own model by passing a function. There is nowhere in this tool to put
475
+ a model key, ours or yours, and that is how "runs under your own account" is
476
+ satisfied structurally rather than promised.
477
+
296
478
  ## Development
297
479
 
298
480
  ```bash
299
- python -m venv .venv && .venv/bin/pip install -e '.[factors]' pytest ruff
300
- .venv/bin/python -m pytest -q
301
- .venv/bin/python -m ruff check .
481
+ python -m venv .venv && .venv/bin/pip install -e '.[dev,factors]'
482
+ ```
483
+
484
+ CI runs five gates. Run all of them before pushing — `ruff check` and
485
+ `ruff format` are different tools with different opinions, and passing one
486
+ says nothing about the other:
487
+
488
+ ```bash
489
+ python -m ruff check src tests
490
+ python -m ruff format --check src tests
491
+ python -m mypy src
492
+ python -m pytest
493
+ python -m pytest tests -m golden -q # the frozen figures
302
494
  ```
303
495
 
304
496
  ## Licence