alphaengine 0.6.0__tar.gz → 0.6.1__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.
- {alphaengine-0.6.0 → alphaengine-0.6.1}/PKG-INFO +1 -1
- {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/_version.py +7 -1
- {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/cli.py +158 -136
- {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/demo.py +6 -2
- {alphaengine-0.6.0 → alphaengine-0.6.1}/.github/workflows/ci.yml +0 -0
- {alphaengine-0.6.0 → alphaengine-0.6.1}/.github/workflows/publish.yml +0 -0
- {alphaengine-0.6.0 → alphaengine-0.6.1}/.gitignore +0 -0
- {alphaengine-0.6.0 → alphaengine-0.6.1}/AGENTS.md +0 -0
- {alphaengine-0.6.0 → alphaengine-0.6.1}/LICENSE +0 -0
- {alphaengine-0.6.0 → alphaengine-0.6.1}/README.md +0 -0
- {alphaengine-0.6.0 → alphaengine-0.6.1}/SECURITY.md +0 -0
- {alphaengine-0.6.0 → alphaengine-0.6.1}/pyproject.toml +0 -0
- {alphaengine-0.6.0 → alphaengine-0.6.1}/scripts/gen_docs.py +0 -0
- {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/__init__.py +0 -0
- {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/__main__.py +0 -0
- {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/agent/__init__.py +0 -0
- {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/agent/answer.py +0 -0
- {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/agent/driver.py +0 -0
- {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/auth.py +0 -0
- {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/book.py +0 -0
- {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/client/__init__.py +0 -0
- {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/client/agent.py +0 -0
- {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/client/executor.py +0 -0
- {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/client/session.py +0 -0
- {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/commands.py +0 -0
- {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/complete.py +0 -0
- {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/connectors/__init__.py +0 -0
- {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/core/__init__.py +0 -0
- {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/core/backtest.py +0 -0
- {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/core/factors.py +0 -0
- {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/core/pairs.py +0 -0
- {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/core/performance.py +0 -0
- {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/core/profile.py +0 -0
- {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/core/risk.py +0 -0
- {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/core/screen.py +0 -0
- {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/core/series_shapes.py +0 -0
- {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/core/signals.py +0 -0
- {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/core/stress.py +0 -0
- {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/core/technical.py +0 -0
- {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/core/validation.py +0 -0
- {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/core/walkforward.py +0 -0
- {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/demo_book.py +0 -0
- {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/demo_returns.py +0 -0
- {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/demo_signal.py +0 -0
- {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/demo_universe.py +0 -0
- {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/events.py +0 -0
- {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/loaders.py +0 -0
- {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/model.py +0 -0
- {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/py.typed +0 -0
- {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/repl.py +0 -0
- {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/study/__init__.py +0 -0
- {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/study/report.py +0 -0
- {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/study/schema.py +0 -0
- {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/sweep/__init__.py +0 -0
- {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/sweep/runner.py +0 -0
- {alphaengine-0.6.0 → alphaengine-0.6.1}/tests/test_agent.py +0 -0
- {alphaengine-0.6.0 → alphaengine-0.6.1}/tests/test_answer.py +0 -0
- {alphaengine-0.6.0 → alphaengine-0.6.1}/tests/test_book.py +0 -0
- {alphaengine-0.6.0 → alphaengine-0.6.1}/tests/test_cli.py +0 -0
- {alphaengine-0.6.0 → alphaengine-0.6.1}/tests/test_client.py +0 -0
- {alphaengine-0.6.0 → alphaengine-0.6.1}/tests/test_commands.py +0 -0
- {alphaengine-0.6.0 → alphaengine-0.6.1}/tests/test_events.py +0 -0
- {alphaengine-0.6.0 → alphaengine-0.6.1}/tests/test_executor_ops.py +0 -0
- {alphaengine-0.6.0 → alphaengine-0.6.1}/tests/test_goldens.py +0 -0
- {alphaengine-0.6.0 → alphaengine-0.6.1}/tests/test_loaders.py +0 -0
- {alphaengine-0.6.0 → alphaengine-0.6.1}/tests/test_model.py +0 -0
- {alphaengine-0.6.0 → alphaengine-0.6.1}/tests/test_screen.py +0 -0
- {alphaengine-0.6.0 → alphaengine-0.6.1}/tests/test_signals.py +0 -0
- {alphaengine-0.6.0 → alphaengine-0.6.1}/tests/test_smoke.py +0 -0
- {alphaengine-0.6.0 → alphaengine-0.6.1}/tests/test_sweep.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: alphaengine
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.1
|
|
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
|
|
@@ -56,4 +56,10 @@ is what a changed figure costs. The API may still move underneath it.
|
|
|
56
56
|
# A new published wheel is a new thing people `pip install`. While the leading
|
|
57
57
|
# digit is 0 the MINOR position carries that, the same way 0.5.0 carried a
|
|
58
58
|
# surface change that left every figure byte-identical.
|
|
59
|
-
|
|
59
|
+
#
|
|
60
|
+
# 0.6.1 IS THE SCREEN, NOT THE MATHS. No computed value moved. The session is a
|
|
61
|
+
# transcript now: a hue-cycling wait-star over a travelling plateau, cards for
|
|
62
|
+
# what a step found, a composer instead of a dashboard. Same rungs, same
|
|
63
|
+
# doors, same refusals. The published artefact is a new wheel because 0.6.0
|
|
64
|
+
# already left the building.
|
|
65
|
+
__version__ = "0.6.1"
|
|
@@ -190,10 +190,6 @@ def cyan(t: str) -> str:
|
|
|
190
190
|
return _c("36", t)
|
|
191
191
|
|
|
192
192
|
|
|
193
|
-
def magenta(t: str) -> str:
|
|
194
|
-
return _c("35", t)
|
|
195
|
-
|
|
196
|
-
|
|
197
193
|
def _rgb(r: int, g: int, b: int, text: str) -> str:
|
|
198
194
|
"""A single-character (or short) paint. Falls back to the 256-colour cube."""
|
|
199
195
|
if not _tty() or not text:
|
|
@@ -212,16 +208,26 @@ def _mix(start: tuple[int, int, int], end: tuple[int, int, int], t: float, ch: s
|
|
|
212
208
|
return _rgb(r, g, b, ch)
|
|
213
209
|
|
|
214
210
|
|
|
215
|
-
def
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
211
|
+
def _accent(text: str) -> str:
|
|
212
|
+
"""The one brand colour. Warm gold, the wait-star Claude Code taught people
|
|
213
|
+
to read as 'something is happening'."""
|
|
214
|
+
return _rgb(255, 176, 72, text)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
_HUES: tuple[tuple[int, int, int], ...] = (
|
|
218
|
+
(255, 176, 72),
|
|
219
|
+
(255, 120, 80),
|
|
220
|
+
(220, 110, 190),
|
|
221
|
+
(130, 150, 255),
|
|
222
|
+
(64, 200, 210),
|
|
223
|
+
(90, 220, 140),
|
|
224
|
+
(210, 220, 80),
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def _spin_mark(frame: int) -> str:
|
|
229
|
+
r, g, b = _HUES[frame % len(_HUES)]
|
|
230
|
+
return _rgb(r, g, b, STAR)
|
|
225
231
|
|
|
226
232
|
|
|
227
233
|
# ── glyphs, which are not always available ─────────────────────────────────
|
|
@@ -262,16 +268,17 @@ ON = "●" if _UNICODE_OK else "*"
|
|
|
262
268
|
OFF = "○" if _UNICODE_OK else "-"
|
|
263
269
|
#: A failed step's marker.
|
|
264
270
|
CROSS = "×" if _UNICODE_OK else "x"
|
|
265
|
-
#:
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
#:
|
|
272
|
-
|
|
273
|
-
#:
|
|
274
|
-
|
|
271
|
+
#: Nested detail, Codex-style. A step that has already run, never what comes next.
|
|
272
|
+
NEST = "└" if _UNICODE_OK else "\\"
|
|
273
|
+
#: REPL composer. Claude Code uses `>`; we keep a heavier caret so a research
|
|
274
|
+
#: session does not look like a shell waiting for bash.
|
|
275
|
+
PROMPT = "❯" if _UNICODE_OK else ">"
|
|
276
|
+
#: The wait-mark Claude Code popularised: a star that changes colour while work
|
|
277
|
+
#: is blocked. ASCII `*` where the stream cannot take it.
|
|
278
|
+
STAR = "✻" if _UNICODE_OK else "*"
|
|
279
|
+
#: Wordmark. The product is a parameter surface, not a mascot; this is just a
|
|
280
|
+
#: pin so the welcome is one glance, not a painting.
|
|
281
|
+
PIN = "α" if _stream_handles("α") else "ae"
|
|
275
282
|
|
|
276
283
|
#: The questions, as commands: what a quant TYPES maps to what the server
|
|
277
284
|
#: RUNS. One table, read by the parser, the dispatcher and the shell alike.
|
|
@@ -364,29 +371,18 @@ def _pulse(elapsed: float, *, width: int = 14) -> str:
|
|
|
364
371
|
"""A travelling heartbeat. Deliberately NOT a progress bar: nothing here
|
|
365
372
|
knows how long the step will take, and a bar that fills at a guessed rate
|
|
366
373
|
is a lie told smoothly."""
|
|
367
|
-
|
|
368
|
-
values = [
|
|
369
|
-
math.exp(-((((i / max(width - 1, 1)) - phase) * 4.8) ** 2))
|
|
370
|
-
+ 0.18 * math.exp(-((((i / max(width - 1, 1)) - ((phase + 0.55) % 1.0)) * 7.0) ** 2))
|
|
371
|
-
for i in range(width)
|
|
372
|
-
]
|
|
373
|
-
return sparkline(values, width=width)
|
|
374
|
-
|
|
374
|
+
return sparkline(_scan_wave(elapsed, width), width=width)
|
|
375
375
|
|
|
376
|
-
def _chip(label: str, *, on: bool) -> str:
|
|
377
|
-
"""A HUD cell on the status line. Reverse-video when the stream can take it."""
|
|
378
|
-
body = f" {label} "
|
|
379
|
-
if not _tty():
|
|
380
|
-
return f"[{label}]"
|
|
381
|
-
if on:
|
|
382
|
-
return _c("48;5;23;38;5;159", body)
|
|
383
|
-
return _c("48;5;236;38;5;246", body)
|
|
384
376
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
377
|
+
def _scan_wave(elapsed: float, width: int) -> list[float]:
|
|
378
|
+
"""A plateau moving across the line. The peak wraps; duration is unknown."""
|
|
379
|
+
mu = (elapsed * 0.42) % 1.0
|
|
380
|
+
denom = max(width - 1, 1)
|
|
381
|
+
return [
|
|
382
|
+
math.exp(-0.5 * ((i / denom - mu) / 0.14) ** 2)
|
|
383
|
+
+ 0.28 * math.exp(-0.5 * ((i / denom - ((mu + 0.52) % 1.0)) / 0.10) ** 2)
|
|
384
|
+
for i in range(width)
|
|
385
|
+
]
|
|
390
386
|
|
|
391
387
|
|
|
392
388
|
class Working:
|
|
@@ -442,18 +438,30 @@ class Working:
|
|
|
442
438
|
|
|
443
439
|
def _animate(self) -> None:
|
|
444
440
|
i = 0
|
|
445
|
-
#
|
|
446
|
-
#
|
|
447
|
-
#
|
|
448
|
-
#
|
|
441
|
+
# Two lines when the stream can take a ridge: a hue-cycling star (Claude
|
|
442
|
+
# Code) over a travelling plateau (the thing this tool actually judges).
|
|
443
|
+
# One line of ASCII otherwise. CSI erase is safe here because colour
|
|
444
|
+
# already committed us to ANSI the moment `_tty()` was true.
|
|
445
|
+
wide = _UNICODE_OK
|
|
449
446
|
while True:
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
447
|
+
mark = _spin_mark(i)
|
|
448
|
+
t = f"{self.elapsed:.1f}s"
|
|
449
|
+
if wide:
|
|
450
|
+
ridge = _spark_paint(_scan_wave(self.elapsed, 32), width=32)
|
|
451
|
+
block = f" {mark} {self.label}\n {ridge} {dim(t)}"
|
|
452
|
+
plain = 8 + max(len(self.plain), 32 + 2 + len(t))
|
|
453
|
+
else:
|
|
454
|
+
block = f" {_SPIN[i % len(_SPIN)]} {self.label} {t}"
|
|
455
|
+
plain = len(f" {_SPIN[0]} {self.plain} {t}")
|
|
456
|
+
if i == 0:
|
|
457
|
+
sys.stdout.write(block)
|
|
458
|
+
elif wide:
|
|
459
|
+
sys.stdout.write("\r\033[2K\033[1A\r\033[2K" + block)
|
|
460
|
+
else:
|
|
461
|
+
sys.stdout.write("\r\033[2K" + block)
|
|
454
462
|
sys.stdout.flush()
|
|
455
|
-
self._painted = max(self._painted,
|
|
456
|
-
if self._stop.wait(0.
|
|
463
|
+
self._painted = max(self._painted, plain)
|
|
464
|
+
if self._stop.wait(0.07):
|
|
457
465
|
return
|
|
458
466
|
i += 1
|
|
459
467
|
|
|
@@ -461,11 +469,10 @@ class Working:
|
|
|
461
469
|
self._stop.set()
|
|
462
470
|
if self._thread is not None:
|
|
463
471
|
self._thread.join(timeout=0.5)
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
sys.stdout.write("\r" + " " * (self._painted + 2) + "\r")
|
|
472
|
+
if _UNICODE_OK:
|
|
473
|
+
sys.stdout.write("\r\033[2K\033[1A\r\033[2K")
|
|
474
|
+
else:
|
|
475
|
+
sys.stdout.write("\r" + " " * (self._painted + 2) + "\r")
|
|
469
476
|
sys.stdout.flush()
|
|
470
477
|
|
|
471
478
|
|
|
@@ -1353,8 +1360,6 @@ def _drive(run: Any, *, quiet: bool = False) -> None:
|
|
|
1353
1360
|
"""
|
|
1354
1361
|
n = 0
|
|
1355
1362
|
failures: dict[str, int] = {}
|
|
1356
|
-
if not quiet:
|
|
1357
|
-
say(dim(f" {RAIL_V}"))
|
|
1358
1363
|
while run.status == "open" and n < 200:
|
|
1359
1364
|
if not run.permitted:
|
|
1360
1365
|
run.resume()
|
|
@@ -1370,7 +1375,7 @@ def _drive(run: Any, *, quiet: bool = False) -> None:
|
|
|
1370
1375
|
# `compute.*` executes in-process against the caller's own frames, so
|
|
1371
1376
|
# this is the one place in the product where a long wait is entirely
|
|
1372
1377
|
# local and entirely invisible.
|
|
1373
|
-
with Working(
|
|
1378
|
+
with Working(bold(op), plain=str(op)) as w:
|
|
1374
1379
|
run.step(step)
|
|
1375
1380
|
n += 1
|
|
1376
1381
|
took = dim(f"{w.elapsed:.1f}s")
|
|
@@ -1384,11 +1389,12 @@ def _drive(run: Any, *, quiet: bool = False) -> None:
|
|
|
1384
1389
|
# with no why sent people to the traceback that no longer
|
|
1385
1390
|
# prints. The message came from the executor and was written
|
|
1386
1391
|
# to be read.
|
|
1387
|
-
say(f" {red(
|
|
1392
|
+
say(f" {red(CROSS)} {bold(op)}")
|
|
1393
|
+
say(f" {NEST} {took}")
|
|
1388
1394
|
why = getattr(run, "last_error", None)
|
|
1389
1395
|
if why:
|
|
1390
1396
|
for line in str(why).splitlines():
|
|
1391
|
-
say(red(f"
|
|
1397
|
+
say(red(f" {NEST} {line}"))
|
|
1392
1398
|
if failures[key] >= 2:
|
|
1393
1399
|
run.status = "abandoned"
|
|
1394
1400
|
run.stopped = {
|
|
@@ -1403,7 +1409,8 @@ def _drive(run: Any, *, quiet: bool = False) -> None:
|
|
|
1403
1409
|
# `emit.*` seal rather than measure) and printing a blank line
|
|
1404
1410
|
# for it would read as a stall.
|
|
1405
1411
|
found = _found(seen, run)
|
|
1406
|
-
say(f" {
|
|
1412
|
+
say(f" {_accent(ON)} {found or bold(op)}")
|
|
1413
|
+
say(f" {dim(NEST)} {took}")
|
|
1407
1414
|
|
|
1408
1415
|
if run.status != "open":
|
|
1409
1416
|
return
|
|
@@ -1501,7 +1508,7 @@ def _report(run: Any) -> int:
|
|
|
1501
1508
|
"""
|
|
1502
1509
|
say("")
|
|
1503
1510
|
if run.status == "closed":
|
|
1504
|
-
say(
|
|
1511
|
+
say(f" {_accent(ON)} {green('Closed')} " + dim(str((run.artifact or {}).get("workflow", ""))))
|
|
1505
1512
|
_render_artifact(run.artifact or {})
|
|
1506
1513
|
_say_gaps(run)
|
|
1507
1514
|
_where_it_lives(run)
|
|
@@ -1509,8 +1516,8 @@ def _report(run: Any) -> int:
|
|
|
1509
1516
|
return 0
|
|
1510
1517
|
if run.status == "stopped":
|
|
1511
1518
|
stop = run.stopped or {}
|
|
1512
|
-
say(
|
|
1513
|
-
say(dim("
|
|
1519
|
+
say(f" {yellow(ON)} {yellow('Stopped')} " + str(stop.get("reason", "")))
|
|
1520
|
+
say(dim(f" {NEST} A stop is a result. The run did what it was built to do."))
|
|
1514
1521
|
# A STOP GETS ITS GAPS TOO, and needs them more than a close does: the
|
|
1515
1522
|
# run refused to answer, so what it could not cover is most of what
|
|
1516
1523
|
# there is to know about it.
|
|
@@ -1520,7 +1527,7 @@ def _report(run: Any) -> int:
|
|
|
1520
1527
|
if run.status == "abandoned":
|
|
1521
1528
|
stop = run.stopped or {}
|
|
1522
1529
|
error = stop.get("error")
|
|
1523
|
-
say(
|
|
1530
|
+
say(f" {red(CROSS)} {red('Abandoned')} {stop.get('op')} failed twice, identically.")
|
|
1524
1531
|
if error:
|
|
1525
1532
|
for line in str(error).splitlines():
|
|
1526
1533
|
say(f" {line}")
|
|
@@ -1864,11 +1871,9 @@ def _render_artifact(art: dict[str, Any]) -> None:
|
|
|
1864
1871
|
# separates from its value — "worst segment Sharpe-3.20" welded a
|
|
1865
1872
|
# 20-character label straight into a negative number.
|
|
1866
1873
|
width = max(20, max(len(label) for label, _, _ in shown))
|
|
1867
|
-
peak = max(abs(float(v)) for _, v, _ in shown) or 1.0
|
|
1868
1874
|
for label, value, unit in shown:
|
|
1869
1875
|
tail = f" {dim(unit)}" if unit else ""
|
|
1870
|
-
|
|
1871
|
-
say(f" {dim(label.ljust(width))} {bold(_num(value))}{tail} {meter}")
|
|
1876
|
+
say(f" {dim(label.ljust(width))} {bold(_num(value))}{tail}")
|
|
1872
1877
|
|
|
1873
1878
|
# SAID EVEN THOUGH THE TABLE IS RIGHT THERE. A shortlist of 20 means one
|
|
1874
1879
|
# thing when 480 names were measured and rejected, and quite another when
|
|
@@ -2319,7 +2324,7 @@ def _shape_phrase(data: Any) -> str | None:
|
|
|
2319
2324
|
|
|
2320
2325
|
|
|
2321
2326
|
def status_line(url: str, *, data: Any, project: str | None, keyed: bool) -> str:
|
|
2322
|
-
"""One
|
|
2327
|
+
"""One quiet line above the prompt: what is loaded, and what can run.
|
|
2323
2328
|
|
|
2324
2329
|
THE QUESTION THIS ANSWERS is the one asked most often inside a session and
|
|
2325
2330
|
the one the boot screen answers once and then scrolls away: is my data in?
|
|
@@ -2328,28 +2333,26 @@ def status_line(url: str, *, data: Any, project: str | None, keyed: bool) -> str
|
|
|
2328
2333
|
identical to one that did -- which is exactly how "the S&P 500 is a handful
|
|
2329
2334
|
of names" happened.
|
|
2330
2335
|
|
|
2331
|
-
Deliberately ONE LINE
|
|
2332
|
-
|
|
2333
|
-
than dim prose so the three facts (data, sign-in, model) can be scanned
|
|
2334
|
-
without parsing separators.
|
|
2336
|
+
Deliberately ONE LINE and dim. Reverse-video chips became wallpaper; this
|
|
2337
|
+
is the Codex status strip: facts, not a second UI.
|
|
2335
2338
|
"""
|
|
2336
2339
|
from .model import available_models
|
|
2337
2340
|
|
|
2338
|
-
|
|
2341
|
+
bits: list[str] = []
|
|
2339
2342
|
|
|
2340
2343
|
if data is None:
|
|
2341
|
-
|
|
2344
|
+
bits.append("no data")
|
|
2342
2345
|
else:
|
|
2343
2346
|
phrase = _shape_phrase(data)
|
|
2344
2347
|
label = project or "loaded"
|
|
2345
|
-
|
|
2348
|
+
bits.append(label + (f" {DOT} {phrase}" if phrase else ""))
|
|
2346
2349
|
|
|
2347
|
-
|
|
2350
|
+
bits.append("signed in" if keyed else "not signed in")
|
|
2348
2351
|
|
|
2349
2352
|
models = available_models()
|
|
2350
|
-
|
|
2353
|
+
bits.append(models[0][0] if models else "no model")
|
|
2351
2354
|
|
|
2352
|
-
return " " + " ".join(
|
|
2355
|
+
return " " + dim(f" {DOT} ".join(bits))
|
|
2353
2356
|
|
|
2354
2357
|
|
|
2355
2358
|
def cmd_commands(args: argparse.Namespace) -> int:
|
|
@@ -2377,7 +2380,7 @@ def cmd_commands(args: argparse.Namespace) -> int:
|
|
|
2377
2380
|
rows = in_group(group)
|
|
2378
2381
|
if not rows:
|
|
2379
2382
|
continue
|
|
2380
|
-
say(" " +
|
|
2383
|
+
say(" " + dim(title))
|
|
2381
2384
|
width = max(len(_signature(c)) for c in rows)
|
|
2382
2385
|
for c in rows:
|
|
2383
2386
|
where = "" if c.scope == "both" else dim(f" ({c.scope})")
|
|
@@ -2388,7 +2391,7 @@ def cmd_commands(args: argparse.Namespace) -> int:
|
|
|
2388
2391
|
# are the single thing people are most often hunting for when they open a
|
|
2389
2392
|
# directory at all — "how do I give it my data" is not answerable from a
|
|
2390
2393
|
# list of verbs.
|
|
2391
|
-
say(" " +
|
|
2394
|
+
say(" " + dim("Getting your data in"))
|
|
2392
2395
|
width = max(len(d) for d, _ in DATA_DOORS)
|
|
2393
2396
|
for door, what in DATA_DOORS:
|
|
2394
2397
|
say(f" {door.ljust(width)} {dim(what)}")
|
|
@@ -2509,12 +2512,11 @@ def ladder_lines(*, keyed: bool) -> list[str]:
|
|
|
2509
2512
|
),
|
|
2510
2513
|
]
|
|
2511
2514
|
|
|
2512
|
-
out = [
|
|
2513
|
-
for live, name,
|
|
2515
|
+
out: list[str] = []
|
|
2516
|
+
for live, name, _does, note in rungs:
|
|
2514
2517
|
mark = green(on) if live else dim(off)
|
|
2515
2518
|
label = bold(name.ljust(14)) if live else dim(name.ljust(14))
|
|
2516
|
-
|
|
2517
|
-
out.append(f" {mark} {label}{does_s}{note}")
|
|
2519
|
+
out.append(f" {mark} {label}{note}")
|
|
2518
2520
|
|
|
2519
2521
|
# ONE ACTIONABLE LINE, and only when something is locked. The rungs
|
|
2520
2522
|
# themselves stay in plain language — a menu that recites environment
|
|
@@ -2651,8 +2653,47 @@ def account_sleeve_lines(session: Any) -> list[str]:
|
|
|
2651
2653
|
return out
|
|
2652
2654
|
|
|
2653
2655
|
|
|
2656
|
+
def _next_moves(*, keyed: bool, data: Any, project: str | None) -> list[str]:
|
|
2657
|
+
"""The next three things to type. A boot screen that cannot name a first
|
|
2658
|
+
move is a logo."""
|
|
2659
|
+
from .model import available_models
|
|
2660
|
+
|
|
2661
|
+
models = available_models()
|
|
2662
|
+
rows: list[tuple[str, str]] = []
|
|
2663
|
+
if not keyed:
|
|
2664
|
+
rows = [
|
|
2665
|
+
("demo", "the offline half, no account"),
|
|
2666
|
+
("key quantos", "unlock workflows"),
|
|
2667
|
+
("key anthropic", "then ask in your own words"),
|
|
2668
|
+
]
|
|
2669
|
+
elif data is None:
|
|
2670
|
+
rows = [
|
|
2671
|
+
("universe <name>", "stored closes from the portal"),
|
|
2672
|
+
("--project research.momentum", "your module, your simulator"),
|
|
2673
|
+
("demo", "see a grid survive, or not"),
|
|
2674
|
+
]
|
|
2675
|
+
if not models:
|
|
2676
|
+
rows[2] = ("key anthropic", "ask in your own words")
|
|
2677
|
+
else:
|
|
2678
|
+
rows = [
|
|
2679
|
+
("screen", "what is worth a look"),
|
|
2680
|
+
("validate", "count the tries"),
|
|
2681
|
+
]
|
|
2682
|
+
if models:
|
|
2683
|
+
rows.append(('"which names are overbought?"', "the model picks the path"))
|
|
2684
|
+
else:
|
|
2685
|
+
rows.append(("key anthropic", "ask in your own words"))
|
|
2686
|
+
if project:
|
|
2687
|
+
rows[1] = ("validate", "count the tries on this project")
|
|
2688
|
+
out = ["", " " + dim("Next")]
|
|
2689
|
+
width = max(len(cmd) for cmd, _ in rows)
|
|
2690
|
+
for cmd, why in rows:
|
|
2691
|
+
out.append(f" {bold(cmd.ljust(width))} {dim(why)}")
|
|
2692
|
+
return out
|
|
2693
|
+
|
|
2694
|
+
|
|
2654
2695
|
def boot(url: str, *, project: str | None, data: Any, keyed: bool, session: Any = None) -> None:
|
|
2655
|
-
"""The opening screen.
|
|
2696
|
+
"""The opening screen. A transcript header, not a dashboard.
|
|
2656
2697
|
|
|
2657
2698
|
Suppressed entirely when stdout is not a TTY: a CI log wants a transcript.
|
|
2658
2699
|
"""
|
|
@@ -2662,57 +2703,40 @@ def boot(url: str, *, project: str | None, data: Any, keyed: bool, session: Any
|
|
|
2662
2703
|
say(f"alphaengine {__version__} {DOT} {url}")
|
|
2663
2704
|
return
|
|
2664
2705
|
|
|
2665
|
-
# THE SURFACE IS THE PRODUCT. Two ridges, same width, opposite shapes: a
|
|
2666
|
-
# plateau that survives a neighbour, and a knife that does not. Colour is
|
|
2667
|
-
# height, not decoration — the bright part is the peak.
|
|
2668
|
-
plateau = [math.exp(-0.5 * ((i / 35 - 0.5) / 0.18) ** 2) for i in range(36)]
|
|
2669
|
-
edge = [math.exp(-0.5 * ((i / 35 - 0.5) / 0.028) ** 2) for i in range(36)]
|
|
2670
2706
|
say("")
|
|
2671
|
-
say(" " +
|
|
2672
|
-
say("
|
|
2673
|
-
say(" " + dim(EDGE) + " " + dim("Find out what survives."))
|
|
2674
|
-
say(" " + _spark_paint(edge, width=36, cool=False))
|
|
2707
|
+
say(" " + _accent(PIN) + " " + bold("alphaengine") + " " + dim(__version__))
|
|
2708
|
+
say(" " + dim("Find out what survives."))
|
|
2675
2709
|
say("")
|
|
2676
2710
|
|
|
2677
2711
|
# Data is described by SHAPE, never printed. A boot screen that echoes the
|
|
2678
2712
|
# first rows of somebody's price series into their scrollback — where it
|
|
2679
2713
|
# will sit until the buffer rolls, and into any recording of the session —
|
|
2680
2714
|
# is a boundary violation for the sake of a nicer banner.
|
|
2715
|
+
bits: list[str] = []
|
|
2681
2716
|
if data is None:
|
|
2682
|
-
|
|
2717
|
+
bits.append(dim("no data"))
|
|
2683
2718
|
else:
|
|
2684
|
-
phrase = _shape_phrase(data)
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
# is exactly the confusion that costs an afternoon.
|
|
2691
|
-
from .auth import load_stored
|
|
2719
|
+
phrase = _shape_phrase(data)
|
|
2720
|
+
label = project or "loaded"
|
|
2721
|
+
bits.append(green(label + (f" {DOT} {phrase}" if phrase else "")))
|
|
2722
|
+
bits.append(green("signed in") if keyed else dim("not signed in"))
|
|
2723
|
+
if keyed:
|
|
2724
|
+
from .auth import load_stored
|
|
2692
2725
|
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
signed = green("yes") + dim(f" {DOT} from your shell")
|
|
2700
|
-
|
|
2701
|
-
host = url.replace("https://", "").replace("http://", "")
|
|
2702
|
-
rows: list[tuple[str, str]] = [
|
|
2703
|
-
("version", bold(__version__)),
|
|
2704
|
-
("host", dim(host)),
|
|
2705
|
-
("signed in", signed),
|
|
2706
|
-
("project", (bold(project) if project else dim("none"))),
|
|
2707
|
-
("data", loaded),
|
|
2708
|
-
]
|
|
2709
|
-
for line in _boxed(rows, title="session"):
|
|
2710
|
-
say(line)
|
|
2726
|
+
stored = load_stored()
|
|
2727
|
+
if "QUANTOS_API_KEY" in stored and not _shell_set("QUANTOS_API_KEY", stored):
|
|
2728
|
+
bits.append(dim("stored"))
|
|
2729
|
+
else:
|
|
2730
|
+
bits.append(dim("from your shell"))
|
|
2731
|
+
say(" " + f" {dim(DOT)} ".join(bits))
|
|
2711
2732
|
|
|
2712
2733
|
say("")
|
|
2713
2734
|
for line in ladder_lines(keyed=keyed):
|
|
2714
2735
|
say(line)
|
|
2715
2736
|
|
|
2737
|
+
for line in _next_moves(keyed=keyed, data=data, project=project):
|
|
2738
|
+
say(line)
|
|
2739
|
+
|
|
2716
2740
|
# ONLY WHEN IT CHANGES THE ANSWER: signed in, and nothing already loaded
|
|
2717
2741
|
# from disk. A run that knows its data does not need a directory of the
|
|
2718
2742
|
# account's, and the call is not worth making to print something ignorable.
|
|
@@ -2724,8 +2748,6 @@ def boot(url: str, *, project: str | None, data: Any, keyed: bool, session: Any
|
|
|
2724
2748
|
say(line)
|
|
2725
2749
|
|
|
2726
2750
|
say("")
|
|
2727
|
-
# THE ONE LINE THAT IS NOT STATUS. It is here because it is the thing most
|
|
2728
|
-
# often forgotten and the thing that makes the rest make sense.
|
|
2729
2751
|
say(" " + dim("Your data stays on this machine. Only the findings ever travel."))
|
|
2730
2752
|
say("")
|
|
2731
2753
|
|
|
@@ -2922,7 +2944,7 @@ def _ask(
|
|
|
2922
2944
|
say(dim("The server offers no workflows, so there is nothing to choose from."))
|
|
2923
2945
|
return None
|
|
2924
2946
|
|
|
2925
|
-
say(f" {
|
|
2947
|
+
say(f" {_accent(STAR)} {dim(label)}")
|
|
2926
2948
|
|
|
2927
2949
|
# Choosing the workflow is the same shape as choosing a step: an index into
|
|
2928
2950
|
# a list the server produced. Reusing `AgentDriver.pick` rather than writing
|
|
@@ -2930,7 +2952,7 @@ def _ask(
|
|
|
2930
2952
|
driver = AgentDriver(
|
|
2931
2953
|
think,
|
|
2932
2954
|
goal=request,
|
|
2933
|
-
on_thought=lambda why: say(f" {
|
|
2955
|
+
on_thought=lambda why: say(f" {_accent(STAR)} {_c('2;3', why)}"),
|
|
2934
2956
|
sink=sink,
|
|
2935
2957
|
provider=provider or None,
|
|
2936
2958
|
model=model_name or None,
|
|
@@ -2998,7 +3020,7 @@ def _ask(
|
|
|
2998
3020
|
|
|
2999
3021
|
repro = chosen.get("reproducible")
|
|
3000
3022
|
say(
|
|
3001
|
-
f" {
|
|
3023
|
+
f" {_accent(ON)} chose {bold(name)} "
|
|
3002
3024
|
+ (green("reproducible") if repro else yellow("not reproducible"))
|
|
3003
3025
|
)
|
|
3004
3026
|
|
|
@@ -3128,7 +3150,7 @@ def _answer(
|
|
|
3128
3150
|
|
|
3129
3151
|
say("")
|
|
3130
3152
|
for line in _wrap(answer.text):
|
|
3131
|
-
say(f" {
|
|
3153
|
+
say(f" {dim(NEST)} {line}")
|
|
3132
3154
|
# THE CAVEATS ARE NOT OPTIONAL AND ARE NOT STYLED AWAY. Each one is a claim
|
|
3133
3155
|
# the figures cannot support without it, and every honesty control in this
|
|
3134
3156
|
# product exists to survive exactly this last step to the screen.
|
|
@@ -3155,14 +3177,14 @@ def _wrap(text: str, width: int = 76) -> list[str]:
|
|
|
3155
3177
|
|
|
3156
3178
|
|
|
3157
3179
|
def _repl_prompt(*, keyed: bool) -> str:
|
|
3158
|
-
"""The
|
|
3180
|
+
"""The composer. Gold when the agent rung is live, otherwise quiet."""
|
|
3159
3181
|
from .model import available_models
|
|
3160
3182
|
|
|
3161
3183
|
mark = f"{PROMPT} "
|
|
3162
3184
|
if keyed and available_models():
|
|
3163
|
-
return
|
|
3185
|
+
return _accent(mark)
|
|
3164
3186
|
if keyed:
|
|
3165
|
-
return green(
|
|
3187
|
+
return green(mark)
|
|
3166
3188
|
return bold(mark)
|
|
3167
3189
|
|
|
3168
3190
|
|
|
@@ -174,8 +174,12 @@ def run() -> int:
|
|
|
174
174
|
if ui._tty():
|
|
175
175
|
sharpes = [t.sharpe for t in result.trials if t.failed is None]
|
|
176
176
|
ui.say("")
|
|
177
|
-
ui.say(
|
|
178
|
-
|
|
177
|
+
ui.say(
|
|
178
|
+
f" {ui._accent(ui.ON)} {verdict.get('verdict')} "
|
|
179
|
+
+ ui.dim(f"{result.n_trials} trials {ui.DOT} {surface.get('shape')}")
|
|
180
|
+
)
|
|
181
|
+
ui.say(" " + ui._spark_paint(sharpes or [0.0], width=32, cool=True))
|
|
182
|
+
ui.say(" " + ui.dim("trial Sharpes across the grid"))
|
|
179
183
|
return 0
|
|
180
184
|
|
|
181
185
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|