dexterm-cli 0.1.7 → 0.2.0
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.
- package/dist/packages/agents/src/spectator.js +3 -1
- package/dist/packages/agents/src/tiers.js +9 -0
- package/dist/packages/agents/src/tools.js +10 -5
- package/dist/packages/autotrade/assets/limits.schema.json +569 -0
- package/dist/packages/autotrade/examples/executor/README.md +70 -0
- package/dist/packages/autotrade/examples/executor/jsonl-tail-executor.mjs +57 -0
- package/dist/packages/autotrade/examples/executor/kit.mjs +144 -0
- package/dist/packages/autotrade/examples/executor/signal-cmd-executor.mjs +25 -0
- package/dist/packages/autotrade/examples/executor/telegram-bot-recipe.md +47 -0
- package/dist/packages/autotrade/examples/executor/webhook-executor.mjs +47 -0
- package/dist/packages/autotrade/src/backtest.js +2 -1
- package/dist/packages/autotrade/src/chat/config.js +4 -2
- package/dist/packages/autotrade/src/chat/control.js +1 -0
- package/dist/packages/autotrade/src/chat/notify.js +16 -6
- package/dist/packages/autotrade/src/chat/secrets.js +25 -18
- package/dist/packages/autotrade/src/chat/service.js +3 -0
- package/dist/packages/autotrade/src/engine.js +16 -2
- package/dist/packages/autotrade/src/executor-health.js +217 -0
- package/dist/packages/autotrade/src/fleet.js +3 -0
- package/dist/packages/autotrade/src/forward.js +355 -0
- package/dist/packages/autotrade/src/index.js +19 -0
- package/dist/packages/autotrade/src/journey.js +477 -0
- package/dist/packages/autotrade/src/limits.js +682 -0
- package/dist/packages/autotrade/src/outbox.js +394 -0
- package/dist/packages/autotrade/src/paper-limits.js +87 -0
- package/dist/packages/autotrade/src/real-mode.js +688 -0
- package/dist/packages/autotrade/src/receipts.js +330 -0
- package/dist/packages/autotrade/src/report.js +168 -0
- package/dist/packages/autotrade/src/runner.js +62 -11
- package/dist/packages/autotrade/src/service-guard.js +125 -0
- package/dist/packages/autotrade/src/service-install.js +213 -0
- package/dist/packages/autotrade/src/service-recover.js +232 -0
- package/dist/packages/autotrade/src/service.js +289 -0
- package/dist/packages/autotrade/src/signal-channels.js +312 -0
- package/dist/packages/autotrade/src/signal.js +196 -0
- package/dist/packages/autotrade/src/strategy.js +3 -1
- package/dist/packages/autotrade/src/trade-ledger.js +2 -2
- package/dist/packages/cli/src/analyst/backend.js +3 -2
- package/dist/packages/cli/src/analyst/codex.js +3 -2
- package/dist/packages/cli/src/app-help-gen.js +14 -2
- package/dist/packages/cli/src/autotrade-cmd/audit.js +19 -2
- package/dist/packages/cli/src/autotrade-cmd/backtest.js +21 -4
- package/dist/packages/cli/src/autotrade-cmd/executor.js +91 -0
- package/dist/packages/cli/src/autotrade-cmd/fleet.js +8 -4
- package/dist/packages/cli/src/autotrade-cmd/forward.js +283 -0
- package/dist/packages/cli/src/autotrade-cmd/journey.js +208 -0
- package/dist/packages/cli/src/autotrade-cmd/limits.js +393 -0
- package/dist/packages/cli/src/autotrade-cmd/receipt.js +63 -0
- package/dist/packages/cli/src/autotrade-cmd/report.js +79 -0
- package/dist/packages/cli/src/autotrade-cmd/run.js +257 -13
- package/dist/packages/cli/src/autotrade-cmd/service.js +873 -0
- package/dist/packages/cli/src/autotrade-cmd/signal.js +824 -0
- package/dist/packages/cli/src/autotrade-cmd.js +50 -4
- package/dist/packages/cli/src/ledger-cmd.js +4 -2
- package/dist/packages/cli/src/pnl-card.js +4 -0
- package/dist/packages/cli/src/screens/autotrade-journey.js +134 -0
- package/dist/packages/cli/src/screens/autotrade-keys.js +13 -0
- package/dist/packages/cli/src/screens/autotrade-screen.js +57 -6
- package/dist/packages/cli/src/screens/autotrade-ui.js +17 -7
- package/dist/packages/cli/src/screens/autotrade-worker.js +4 -2
- package/dist/packages/cli/src/telegram-cmd.js +4 -1
- package/dist/packages/cli/src/version.js +1 -1
- package/dist/packages/data/src/trade/ledger/schema.js +1 -1
- package/dist/packages/mcp/assets/kb/app-help/autotrade-executor-kit.md +44 -0
- package/dist/packages/mcp/assets/kb/app-help/autotrade-journey.md +46 -0
- package/dist/packages/mcp/assets/kb/app-help/autotrade-limits.md +48 -0
- package/dist/packages/mcp/assets/kb/app-help/autotrade-real-signal.md +45 -0
- package/dist/packages/mcp/assets/kb/app-help/live-trading.md +1 -1
- package/dist/packages/mcp/assets/kb/app-help/screen-autotrade.md +12 -0
- package/dist/packages/mcp/assets/kb/app-help/trade-ledger.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-change-a-strategy-by-chat.md +3 -1
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-on-the-q-screen.md +1 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-running-it-all-day.md +2 -2
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-stopping-it.md +2 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-the-always-on-service.md +57 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-what-it-is-paper-self-directed.md +9 -2
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-what-it-will-not-do.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-build-a-strategy-by-chat-1-ask-for-a-strategy.md +27 -0
- package/dist/packages/mcp/assets/kb/docs/docs-build-a-strategy-by-chat-2-backtest-it-held-out-split.md +31 -0
- package/dist/packages/mcp/assets/kb/docs/docs-build-a-strategy-by-chat-3-forward-test-it-on-live-data.md +44 -0
- package/dist/packages/mcp/assets/kb/docs/docs-build-a-strategy-by-chat-4-promote-it-to-always-on-paper.md +36 -0
- package/dist/packages/mcp/assets/kb/docs/docs-build-a-strategy-by-chat-5-real-only-if-you-choose.md +36 -0
- package/dist/packages/mcp/assets/kb/docs/docs-build-a-strategy-by-chat-the-ladder.md +39 -0
- package/dist/packages/mcp/assets/kb/docs/docs-build-a-strategy-by-chat-the-report.md +31 -0
- package/dist/packages/mcp/assets/kb/docs/docs-build-a-strategy-by-chat-what-the-analyst-knows.md +30 -0
- package/dist/packages/mcp/assets/kb/docs/docs-build-a-strategy-by-chat.md +24 -0
- package/dist/packages/mcp/assets/kb/docs/docs-getting-started-install.md +3 -3
- package/dist/packages/mcp/assets/kb/docs/docs-getting-started-requirements.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-going-real-executor-health.md +38 -0
- package/dist/packages/mcp/assets/kb/docs/docs-going-real-paper-vs-real.md +33 -0
- package/dist/packages/mcp/assets/kb/docs/docs-going-real-the-digest.md +36 -0
- package/dist/packages/mcp/assets/kb/docs/docs-going-real-the-executor-kit.md +48 -0
- package/dist/packages/mcp/assets/kb/docs/docs-going-real-the-whole-path.md +37 -0
- package/dist/packages/mcp/assets/kb/docs/docs-going-real-what-dexterm-does-and-never-does.md +44 -0
- package/dist/packages/mcp/assets/kb/docs/docs-going-real-your-limits.md +77 -0
- package/dist/packages/mcp/assets/kb/docs/docs-going-real.md +24 -0
- package/dist/packages/mcp/assets/kb/docs/docs-live-trading-changed-in-0-1-7.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-live-trading-real-mode-your-strategy-s-trades-to-your.md +31 -0
- package/dist/packages/mcp/assets/kb/docs/docs-live-trading-the-short-version.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-live-trading-using-a-trading-bot-with-dexterm.md +0 -2
- package/dist/packages/mcp/assets/kb/docs/docs-live-trading-what-dexterm-does-not-do.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-real-mode-receipts-what-your-executor-did.md +42 -0
- package/dist/packages/mcp/assets/kb/docs/docs-real-mode-safety.md +34 -0
- package/dist/packages/mcp/assets/kb/docs/docs-real-mode-set-it-up.md +47 -0
- package/dist/packages/mcp/assets/kb/docs/docs-real-mode-the-channels.md +42 -0
- package/dist/packages/mcp/assets/kb/docs/docs-real-mode-the-signal.md +48 -0
- package/dist/packages/mcp/assets/kb/docs/docs-real-mode-what-it-is.md +35 -0
- package/dist/packages/mcp/assets/kb/docs/docs-real-mode-what-it-will-not-do.md +34 -0
- package/dist/packages/mcp/assets/kb/docs/docs-real-mode-where-you-see-it.md +34 -0
- package/dist/packages/mcp/assets/kb/docs/docs-real-mode.md +27 -0
- package/dist/packages/mcp/assets/kb/docs/docs-telegram-what-it-is.md +1 -1
- package/dist/packages/mcp/assets/kb/entries/autotrade.md +4 -2
- package/dist/packages/mcp/src/autotrade-tools.js +168 -5
- package/dist/packages/mcp/src/engine/restrictions.js +3 -3
- package/dist/packages/mcp/src/index.js +1 -1
- package/dist/packages/mcp/src/ledger-tools.js +155 -7
- package/dist/packages/mcp/src/server.js +21 -10
- package/dist/packages/tui/src/keymap.js +7 -0
- package/package.json +1 -1
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: docs-real-mode-the-signal
|
|
3
|
+
title: Real mode (LIVE TRADE FIRE signals) › The signal
|
|
4
|
+
kind: docs
|
|
5
|
+
page: real-mode
|
|
6
|
+
page_title: Real mode (LIVE TRADE FIRE signals)
|
|
7
|
+
heading: The signal
|
|
8
|
+
anchor: the-signal
|
|
9
|
+
url: https://dexterm.xyz/docs/real-mode#the-signal
|
|
10
|
+
tags:
|
|
11
|
+
- docs
|
|
12
|
+
- real-mode
|
|
13
|
+
- signal
|
|
14
|
+
- real
|
|
15
|
+
- mode
|
|
16
|
+
- live
|
|
17
|
+
- trade
|
|
18
|
+
- fire
|
|
19
|
+
- signals
|
|
20
|
+
confidence: high
|
|
21
|
+
owner: maintainers
|
|
22
|
+
origin: docs-generated
|
|
23
|
+
reviewed_by: generated@2026-10-16
|
|
24
|
+
generated_from: dexterm/site content/docs (task-1420)
|
|
25
|
+
sources:
|
|
26
|
+
- site/content/docs/real-mode.md
|
|
27
|
+
date: 2026-10-16
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
One line of JSON, the same bytes on every channel:
|
|
31
|
+
|
|
32
|
+
```json
|
|
33
|
+
{"schema":"dexterm-trade-fire/1","type":"trade","id":"9f2c…(32 hex)","nonce":42,"issuedAt":1792000000000,"expiresAt":1792000060000,"ttlSec":60,"runId":"…","accountId":"main","strategy":{"id":"my-strategy","version":"1.0.1","hash":"…"},"paper":false,"side":"buy","mint":"…","symbol":"PEPE","sizeSol":0.05,"sizeUsd":10,"pctOfBook":2,"sellFraction":null,"price":{"usd":0.0012,"mcap":1200000,"liqUsd":48000,"solUsd":200},"exitReason":null,"slippageBps":300,"maxImpactPct":3,"positionId":"…"}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
- `type`: `trade`, `halted` (the run ended or stopped: `outstanding` lists the nonces it had sent that had not expired, `open` the coins dexterm still counts as held for the account) or `test`.
|
|
37
|
+
- `side` `buy` or `sell`. A buy states `sizeSol` (what to spend) and `pctOfBook`. A sell states `sellFraction`, the share of the position to sell (apply it to what you really hold), and `exitReason` (the exit rule that fired: `hard-stop`, `trail`, `tp-1`, `agent-exit`, `flatten` and so on); its `sizeSol` is dexterm's modelled proceeds and can be 0 for a coin that rugged.
|
|
38
|
+
- `nonce` only ever grows, across runs and restarts. `id` is the idempotency key (a hash of the account, the run and the nonce). `paper` is always `false`.
|
|
39
|
+
- **Firewall**: the only token-made text in a signal is the symbol, reduced to `A-Z a-z 0-9 . _ $ -` (16 characters). No name, no description, no link, no agent text, no path, no key.
|
|
40
|
+
|
|
41
|
+
**What a well-behaved executor does with each signal:**
|
|
42
|
+
|
|
43
|
+
1. Check the HMAC (when you set a secret): `x-dexterm-signature: t=<unix seconds>,v1=<hex>`, where `v1` is HMAC-SHA256 of `<t>.<body>` with your secret. Refuse a timestamp more than 5 minutes off.
|
|
44
|
+
2. Ignore it after `expiresAt`. A late signal is never acted on.
|
|
45
|
+
3. Act once per `nonce` (delivery is at least once: a repeat is normal) and ignore a nonce lower than one already acted on.
|
|
46
|
+
4. Act only on the `accountId` it was set up for.
|
|
47
|
+
|
|
48
|
+
`SignalReceiver` in `@dexterm/autotrade` does these four checks, if your executor is written in Node.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: docs-real-mode-what-it-is
|
|
3
|
+
title: Real mode (LIVE TRADE FIRE signals) › What it is
|
|
4
|
+
kind: docs
|
|
5
|
+
page: real-mode
|
|
6
|
+
page_title: Real mode (LIVE TRADE FIRE signals)
|
|
7
|
+
heading: What it is
|
|
8
|
+
anchor: what-it-is
|
|
9
|
+
url: https://dexterm.xyz/docs/real-mode#what-it-is
|
|
10
|
+
tags:
|
|
11
|
+
- docs
|
|
12
|
+
- real-mode
|
|
13
|
+
- real
|
|
14
|
+
- mode
|
|
15
|
+
- live
|
|
16
|
+
- trade
|
|
17
|
+
- fire
|
|
18
|
+
- signals
|
|
19
|
+
confidence: high
|
|
20
|
+
owner: maintainers
|
|
21
|
+
origin: docs-generated
|
|
22
|
+
reviewed_by: generated@2026-10-16
|
|
23
|
+
generated_from: dexterm/site content/docs (task-1420)
|
|
24
|
+
sources:
|
|
25
|
+
- site/content/docs/real-mode.md
|
|
26
|
+
date: 2026-10-16
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
> **0.2.0, experimental.** Real mode can lead to real trades in a wallet you fund. dexterm never holds a key, never signs and never sends a transaction: your executor does, by its own rules. Not financial advice.
|
|
30
|
+
|
|
31
|
+
Every strategy runs on **paper** by default. In **real** mode the same strategy runs the same way: the same entry rules, the same clamp on every size, the same exits, the same monitor and the same run file as paper. The difference: every buy and sell it fills on its own model of the market also goes out, first, as a **LIVE TRADE FIRE** signal (`dexterm-trade-fire/1`) to the executor you chose.
|
|
32
|
+
|
|
33
|
+
- **Your executor** is a Telegram trading bot you trust, or any script of yours. It holds its own wallet and decides whether to act on a signal.
|
|
34
|
+
- **dexterm** holds no key, signs nothing and builds no transaction. It sends the signal, keeps it in an outbox until it is delivered, and records what your executor says it did.
|
|
35
|
+
- **A bot cannot read another bot's messages**, so the signal also goes over a channel your executor can read: a local webhook, a command, a stream file or MCP. The Telegram message to your own chat is the copy for you.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: docs-real-mode-what-it-will-not-do
|
|
3
|
+
title: Real mode (LIVE TRADE FIRE signals) › What it will not do
|
|
4
|
+
kind: docs
|
|
5
|
+
page: real-mode
|
|
6
|
+
page_title: Real mode (LIVE TRADE FIRE signals)
|
|
7
|
+
heading: What it will not do
|
|
8
|
+
anchor: what-it-will-not-do
|
|
9
|
+
url: https://dexterm.xyz/docs/real-mode#what-it-will-not-do
|
|
10
|
+
tags:
|
|
11
|
+
- docs
|
|
12
|
+
- real-mode
|
|
13
|
+
- will
|
|
14
|
+
- real
|
|
15
|
+
- mode
|
|
16
|
+
- live
|
|
17
|
+
- trade
|
|
18
|
+
- fire
|
|
19
|
+
- signals
|
|
20
|
+
confidence: high
|
|
21
|
+
owner: maintainers
|
|
22
|
+
origin: docs-generated
|
|
23
|
+
reviewed_by: generated@2026-10-16
|
|
24
|
+
generated_from: dexterm/site content/docs (task-1420)
|
|
25
|
+
sources:
|
|
26
|
+
- site/content/docs/real-mode.md
|
|
27
|
+
date: 2026-10-16
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
- Hold, import or ask for a key or a seed phrase, sign, or send a transaction.
|
|
31
|
+
- Trade on a replay, or let a chat tap authorise a real trade.
|
|
32
|
+
- Promise a fill. Your executor decides, at its own price, with its own fees.
|
|
33
|
+
- Run more than one real strategy per machine at a time, run one inside `run --all`, or run one under the always-on service (in 0.2.0 real runs in the foreground only).
|
|
34
|
+
- Send any signal, sells included, while no real run is running, or close a position by itself.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: docs-real-mode-where-you-see-it
|
|
3
|
+
title: Real mode (LIVE TRADE FIRE signals) › Where you see it
|
|
4
|
+
kind: docs
|
|
5
|
+
page: real-mode
|
|
6
|
+
page_title: Real mode (LIVE TRADE FIRE signals)
|
|
7
|
+
heading: Where you see it
|
|
8
|
+
anchor: where-you-see-it
|
|
9
|
+
url: https://dexterm.xyz/docs/real-mode#where-you-see-it
|
|
10
|
+
tags:
|
|
11
|
+
- docs
|
|
12
|
+
- real-mode
|
|
13
|
+
- where
|
|
14
|
+
- real
|
|
15
|
+
- mode
|
|
16
|
+
- live
|
|
17
|
+
- trade
|
|
18
|
+
- fire
|
|
19
|
+
- signals
|
|
20
|
+
confidence: high
|
|
21
|
+
owner: maintainers
|
|
22
|
+
origin: docs-generated
|
|
23
|
+
reviewed_by: generated@2026-10-16
|
|
24
|
+
generated_from: dexterm/site content/docs (task-1420)
|
|
25
|
+
sources:
|
|
26
|
+
- site/content/docs/real-mode.md
|
|
27
|
+
date: 2026-10-16
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
- **The run**: every fill and closed trade row of a real run is tagged `real-signal` and names its nonce; the start line says REAL; receipts become rows; the end has a `real_halted` row.
|
|
31
|
+
- **The `Q` screen's runner view**: the mode, the account, the kill switch, the outbox depth, each channel's health, the unconfirmed positions, the limits in force (with a BUYS PAUSED line when a limit or a silent executor stops new buys) and your executor's health.
|
|
32
|
+
- **Telegram**: every message of a real run carries the LIVE badge. A closed trade is a text line (its numbers are dexterm's model, not your executor's fills), never a PnL card.
|
|
33
|
+
- **The trade ledger**: `dexterm ledger` lists a real run's trades with the mode `real` (`--mode real`), never `paper`. Their numbers are dexterm's model of each trade it signalled; your executor's receipts are the real fills, so there is no PnL card for them.
|
|
34
|
+
- **Shared views**: the spectator stream and saved files keep signals and receipts out.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: docs-real-mode
|
|
3
|
+
title: Real mode (LIVE TRADE FIRE signals)
|
|
4
|
+
kind: docs
|
|
5
|
+
page: real-mode
|
|
6
|
+
page_title: Real mode (LIVE TRADE FIRE signals)
|
|
7
|
+
url: https://dexterm.xyz/docs/real-mode
|
|
8
|
+
tags:
|
|
9
|
+
- docs
|
|
10
|
+
- real-mode
|
|
11
|
+
- real
|
|
12
|
+
- mode
|
|
13
|
+
- live
|
|
14
|
+
- trade
|
|
15
|
+
- fire
|
|
16
|
+
- signals
|
|
17
|
+
confidence: high
|
|
18
|
+
owner: maintainers
|
|
19
|
+
origin: docs-generated
|
|
20
|
+
reviewed_by: generated@2026-10-16
|
|
21
|
+
generated_from: dexterm/site content/docs (task-1420)
|
|
22
|
+
sources:
|
|
23
|
+
- site/content/docs/real-mode.md
|
|
24
|
+
date: 2026-10-16
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
Run a strategy in real mode and every buy and sell it decides goes to your own executor (a Telegram trading bot of your choice, or any script) as a signed, numbered LIVE TRADE FIRE signal. dexterm still holds no key and signs nothing. Experimental; not financial advice.
|
|
@@ -30,4 +30,4 @@ It does three things, each one a tier you grant when you pair:
|
|
|
30
30
|
- **Control** (https://dexterm.xyz/docs/telegram#control-the-commands): `/status`, `/positions`, `/pause`, `/resume`, `/kill`, `/flatten`, `/strategies`, `/use` and `/approvals on`. Chat can always make things **safer** and never less safe.
|
|
31
31
|
- **Approve** (https://dexterm.xyz/docs/telegram#approve-one-tap-entries): every new entry waits for your tap, with [Approve] [Half size] [Skip] buttons. Only a button approves; a typed message never does.
|
|
32
32
|
|
|
33
|
-
It runs on **paper**: an approved entry fills on the paper book, with no key, wallet or signer anywhere. This is also dexterm's part in live trading (https://dexterm.xyz/docs/live-trading): dexterm holds no key, and live trades happen in a separate Telegram trading bot of your choice, which holds its own wallet. dexterm sends the alerts and run control to your chat; in this version it does not place orders in that trading bot for you. Experimental, not financial advice.
|
|
33
|
+
It runs on **paper**: an approved entry fills on the paper book, with no key, wallet or signer anywhere. This is also dexterm's part in live trading (https://dexterm.xyz/docs/live-trading): dexterm holds no key, and live trades happen in a separate Telegram trading bot of your choice, which holds its own wallet. dexterm sends the alerts and run control to your chat; in this version it does not place orders in that trading bot for you. In real mode (https://dexterm.xyz/docs/real-mode) (coming in 0.2.0) your chat also gets a LIVE TRADE FIRE copy of each signal, and the channels your executor reads are set up in the same file as this bot (`dexterm autotrade signal channels`; `dexterm telegram status` shows them). Experimental, not financial advice.
|
|
@@ -7,7 +7,7 @@ tags:
|
|
|
7
7
|
- paper
|
|
8
8
|
- backtest
|
|
9
9
|
- proposal
|
|
10
|
-
date: 2026-10-
|
|
10
|
+
date: 2026-10-24
|
|
11
11
|
confidence: high
|
|
12
12
|
owner: maintainers
|
|
13
13
|
origin: primary-doc
|
|
@@ -33,6 +33,8 @@ sources:
|
|
|
33
33
|
- "stricter coins" → add a screen clause (`top10 <= 25`) or tighten `/entry/safety/*`.
|
|
34
34
|
- "stop after a bad day" → lower `/risk/dailyLossHaltPct` or `/risk/drawdown/haltAtPct`.
|
|
35
35
|
|
|
36
|
-
**Then measure.** `strategy_backtest {proposalId}` runs the current version and the proposal on the same recorded tape (days <= 14,
|
|
36
|
+
**Then measure.** `strategy_backtest {proposalId}` runs the current version and the proposal on the same recorded tape (days <= 14, rate-limited; a proposal's backtest saves nothing). `strategy_backtest {id}` also runs the strategy on each half of the held-out split and records it as the strategy's backtest evidence. Quote the trade count and the data limits: small n means little, fills are modelled, and early-entry rugs are often unsellable. A backtest is never a prediction and never financial advice.
|
|
37
37
|
|
|
38
38
|
**Status.** `autotrade_status` shows the paper runner (heartbeat, halt, candidates, sessions and spend, positions) and the proposals waiting for the user.
|
|
39
|
+
|
|
40
|
+
**The journey (one rung at a time).** draft → backtested (`strategy_backtest {id}`: held-out split recorded) → forward-tested (`strategy_forward_start {id, hours|trades}`: paper, live data, its own bankroll; read `strategy_forward_report` when it ends; an early stop never counts) → always-on → real (`strategy_promote_propose {id, to}`). `strategy_journey {id}` shows the rungs; evidence binds to the exact rules. Never skip a rung, apply a promotion, set or suggest caps, pick a bankroll or arm anything: say "I proposed it; you apply it". Claim gains only when `heldOut.claim` / `comparison.claim` is true (>= 20 trades in both halves, net above 0); otherwise "too few trades to claim anything".
|
|
@@ -10,6 +10,19 @@
|
|
|
10
10
|
// strategy_backtest observe a strategy or a proposal on recorded history (days <= 14), read-only, rate-limited
|
|
11
11
|
// autotrade_status observe the paper runner's status file, strategies and pending proposals
|
|
12
12
|
//
|
|
13
|
+
// task-1647 (0.2.0 card C), the JOURNEY draft -> backtested -> forward-tested -> always-on -> real:
|
|
14
|
+
//
|
|
15
|
+
// strategy_journey observe the ladder for one strategy: which rungs have evidence on these rules, what is next
|
|
16
|
+
// strategy_forward_report observe a forward test next to the backtest (held-out split) of the same rules
|
|
17
|
+
// strategy_forward_start control start a forward test: live data, paper, the strategy's OWN bankroll, a window
|
|
18
|
+
// (hours <= 72 or closed trades <= 200); only after a backtest; never real
|
|
19
|
+
// strategy_forward_stop control stop one (it flattens and writes its report; an early stop never counts)
|
|
20
|
+
// strategy_promote_propose propose a PROMOTION proposal (always-on or real) with an evidence card, never applied;
|
|
21
|
+
// no rung can be skipped; always-on is the user's y, real is the user's own
|
|
22
|
+
// caps + `signal arm` with a typed phrase on their terminal
|
|
23
|
+
// strategy_backtest now also runs the strategy on each half of the held-out split and records that as the
|
|
24
|
+
// strategy's backtest evidence (the engine's numbers on the recorded tape, never model text).
|
|
25
|
+
//
|
|
13
26
|
// A proposal is a file in `$DEXTERM_HOME/strategies/.proposals/`. Nothing in this module writes a strategy:
|
|
14
27
|
// the user applies a proposal on the Q screen with a keypress (a LOOSER one needs a second keypress, the
|
|
15
28
|
// "less safe" popup), and that path re-checks everything here from scratch (`checkProposal`) instead of trusting
|
|
@@ -19,8 +32,9 @@
|
|
|
19
32
|
//
|
|
20
33
|
// @dexterm/autotrade imports @dexterm/mcp (the firewall), so the server loads the engine lazily (`loadEngine`);
|
|
21
34
|
// the CLI hands its own static import to the same functions.
|
|
35
|
+
import { spawn } from 'node:child_process';
|
|
22
36
|
import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, writeFileSync } from 'node:fs';
|
|
23
|
-
import { join } from 'node:path';
|
|
37
|
+
import { basename, join } from 'node:path';
|
|
24
38
|
import { randomBytes } from 'node:crypto';
|
|
25
39
|
import { firewallText } from '../../data/src/index.js';
|
|
26
40
|
import { firewall } from "./firewall.js";
|
|
@@ -42,6 +56,21 @@ export const AUTOTRADE_TOOLS = [...STRATEGY_READ_TOOLS, ...STRATEGY_PROPOSE_TOOL
|
|
|
42
56
|
export const PARALLEL_READ_TOOLS = ['autotrade_parallel_list', 'autotrade_parallel_compare'];
|
|
43
57
|
export const PARALLEL_CONTROL_TOOLS = ['autotrade_parallel_start', 'autotrade_parallel_stop'];
|
|
44
58
|
export const PARALLEL_TOOLS = [...PARALLEL_READ_TOOLS, ...PARALLEL_CONTROL_TOOLS];
|
|
59
|
+
/** task-1647: the journey tools. Reads; forward-test control (paper, own bankroll); promotion PROPOSALS (never applied). */
|
|
60
|
+
export const JOURNEY_READ_TOOLS = ['strategy_journey', 'strategy_forward_report'];
|
|
61
|
+
export const JOURNEY_CONTROL_TOOLS = ['strategy_forward_start', 'strategy_forward_stop'];
|
|
62
|
+
export const JOURNEY_PROPOSE_TOOLS = ['strategy_promote_propose'];
|
|
63
|
+
export const JOURNEY_TOOLS = [...JOURNEY_READ_TOOLS, ...JOURNEY_CONTROL_TOOLS, ...JOURNEY_PROPOSE_TOOLS];
|
|
64
|
+
/** Forward tests a model may start per hour (each one is a background process on the user's machine). */
|
|
65
|
+
export const FORWARD_START_RATE = { perHour: 3 };
|
|
66
|
+
export const defaultForwardSpawner = (home, t, env) => {
|
|
67
|
+
const bin = process.argv[1] ?? '';
|
|
68
|
+
if (!/^dexterm(\.m?js)?$/.test(basename(bin)))
|
|
69
|
+
return null;
|
|
70
|
+
const child = spawn(process.execPath, [bin, 'autotrade', 'forward', 'run', t.forwardId], { detached: true, stdio: 'ignore', env: { ...env, DEXTERM_HOME: home } });
|
|
71
|
+
child.unref();
|
|
72
|
+
return child.pid ?? null;
|
|
73
|
+
};
|
|
45
74
|
export const MAX_PROPOSAL_OPS = 20;
|
|
46
75
|
export const BACKTEST_MAX_DAYS = 14;
|
|
47
76
|
/** Backtests: one at a time, at least this far apart, and at most this many per hour per server process. */
|
|
@@ -56,7 +85,7 @@ const DAY_MS = 86_400_000;
|
|
|
56
85
|
* to live, change the book, raise its own daily spend or lower the floor under which a bag counts as stuck.
|
|
57
86
|
*/
|
|
58
87
|
export const STRATEGY_DENIED_FOR_MODELS = [
|
|
59
|
-
{ pointer: '/mode', why: 'only you can change the mode (a model can never switch a strategy to live)' },
|
|
88
|
+
{ pointer: '/mode', why: 'only you can change the mode (a model can never switch a strategy to real or live; real is armed on your own terminal)' },
|
|
60
89
|
{ pointer: '/bankroll', why: 'only you can change the book' },
|
|
61
90
|
{ pointer: '/agent/budgetUsdPerDay', why: 'only you can change the daily spend (a model can never raise its own budget)' },
|
|
62
91
|
{ pointer: '/execution/minExitLiquidityUsd', why: 'only you can change the stuck-bag floor' },
|
|
@@ -690,6 +719,7 @@ export class AutotradeTools {
|
|
|
690
719
|
backtestBusy = false;
|
|
691
720
|
backtestTimes = [];
|
|
692
721
|
proposalTimes = [];
|
|
722
|
+
forwardTimes = [];
|
|
693
723
|
constructor(o = {}) {
|
|
694
724
|
this.opts = o;
|
|
695
725
|
this.env = o.env ?? process.env;
|
|
@@ -710,6 +740,11 @@ export class AutotradeTools {
|
|
|
710
740
|
case 'strategy_propose_new': return await this.propose({ kind: 'new', id: String(args.id ?? ''), from: String(args.fromPreset ?? ''), name: args.name, ops: args.ops ?? [], why: args.why });
|
|
711
741
|
case 'strategy_backtest': return await this.backtest(args);
|
|
712
742
|
case 'autotrade_status': return await this.status();
|
|
743
|
+
case 'strategy_journey': return await this.journey(args);
|
|
744
|
+
case 'strategy_forward_start': return await this.forwardStart(args);
|
|
745
|
+
case 'strategy_forward_stop': return await this.forwardStop(args);
|
|
746
|
+
case 'strategy_forward_report': return await this.forwardReportTool(args);
|
|
747
|
+
case 'strategy_promote_propose': return await this.promotePropose(args);
|
|
713
748
|
case 'autotrade_parallel_list': return await this.parallelList();
|
|
714
749
|
case 'autotrade_parallel_compare': return await this.parallelCompare();
|
|
715
750
|
case 'autotrade_parallel_start': return await this.parallelControl(args, 'start');
|
|
@@ -852,11 +887,21 @@ export class AutotradeTools {
|
|
|
852
887
|
tape = at.tapeFromDoc(at.tapeFromDb(db, { since: until - days * DAY_MS, until, minPeakLiqUsd: 20_000, spanHours: 80 }));
|
|
853
888
|
}
|
|
854
889
|
const results = [];
|
|
890
|
+
let heldOut = null;
|
|
855
891
|
for (const sub of subjects) {
|
|
856
|
-
const
|
|
857
|
-
results.push({ label: sub.label, ...compactReport(report) });
|
|
892
|
+
const full = await at.backtest(sub.s, tape);
|
|
893
|
+
results.push({ label: sub.label, ...compactReport(full.report) });
|
|
894
|
+
// task-1647: a stored strategy's backtest is its journey evidence: the same rules on each half of the split.
|
|
895
|
+
if (args.id !== undefined) {
|
|
896
|
+
const { evidence } = await at.backtestEvidence(sub.s, tape, { by: this.actor, now: t, days, full });
|
|
897
|
+
at.recordBacktestEvidence(this.home(), evidence);
|
|
898
|
+
heldOut = { cutoff: evidence.heldOut.cutoff, train: evidence.heldOut.train, test: evidence.heldOut.test, verdict: evidence.verdict, claim: evidence.claim, rulesHash: evidence.rulesHash };
|
|
899
|
+
}
|
|
858
900
|
}
|
|
859
|
-
return {
|
|
901
|
+
return {
|
|
902
|
+
paper: true, readOnly: true, days, tape: { source: tape.source, mints: tape.mints.length, ticks: tape.ticks.length }, results, ...(heldOut ? { heldOut } : {}), limits: at.DATA_LIMITS,
|
|
903
|
+
note: `A backtest on recorded history with modelled fills, not a prediction. ${heldOut ? 'The held-out split (the newest 30% of coins) is recorded as this strategy\'s backtest evidence for its journey; the strategy itself was not changed. Gain wording only when heldOut.claim is true.' : 'Nothing was saved.'} Quote the trade count: small n means little.`,
|
|
904
|
+
};
|
|
860
905
|
}
|
|
861
906
|
finally {
|
|
862
907
|
this.backtestBusy = false;
|
|
@@ -873,6 +918,124 @@ export class AutotradeTools {
|
|
|
873
918
|
pendingProposals: pending.map((p) => ({ proposalId: p.proposalId, id: p.id, kind: p.kind, riskDelta: p.riskDelta, createdAt: new Date(p.createdAt).toISOString() })),
|
|
874
919
|
};
|
|
875
920
|
}
|
|
921
|
+
// ───────────────────────────── the journey (task-1647) ─────────────────────────────
|
|
922
|
+
async userStrategy(id) {
|
|
923
|
+
const { at } = await this.eng();
|
|
924
|
+
if (typeof id !== 'string' || !at.STRATEGY_ID.test(id))
|
|
925
|
+
return { error: 'id must be a strategy id from strategy_list' };
|
|
926
|
+
const store = await this.store();
|
|
927
|
+
const got = store.get(id);
|
|
928
|
+
if (!got)
|
|
929
|
+
return { error: `no strategy ${id} (strategy_list shows the ids)` };
|
|
930
|
+
if (got.source !== 'user')
|
|
931
|
+
return { error: `${id} is a shipped preset: propose a copy first (strategy_propose_new), the user applies it, then work on the copy` };
|
|
932
|
+
if (!got.ok)
|
|
933
|
+
return { error: `${id} does not validate: ${got.errors.map(at.fmtIssue).join('; ')}` };
|
|
934
|
+
return { s: got.strategy, raw: store.raw(id) };
|
|
935
|
+
}
|
|
936
|
+
async journey(args) {
|
|
937
|
+
const { at } = await this.eng();
|
|
938
|
+
const r = await this.userStrategy(args.id);
|
|
939
|
+
if ('error' in r)
|
|
940
|
+
return r;
|
|
941
|
+
const home = this.home();
|
|
942
|
+
const v = at.journeyView(home, r.s, { armedHash: at.loadArmings(home)[r.s.id]?.strategyHash ?? null });
|
|
943
|
+
const fw = at.findForwardTest(home, r.s.id, this.now());
|
|
944
|
+
const pending = new at.PromotionFiles(home).list({ status: 'pending', id: r.s.id });
|
|
945
|
+
return {
|
|
946
|
+
paper: r.s.mode !== 'real', id: v.id, version: v.version, rulesHash: v.rulesHash, mode: v.mode, stage: v.stage,
|
|
947
|
+
rungs: v.rungs.map((x) => ({ stage: x.stage, ok: x.ok, detail: cleanText(x.detail, 300) })), next: v.next, notes: v.notes.map((n) => cleanText(n, 200)),
|
|
948
|
+
forward: fw ? { forwardId: fw.forwardId, state: at.liveState(fw, this.now()), window: fw.window, progress: fw.progress, counts: fw.rulesHash === v.rulesHash && fw.state === 'done' && fw.source.startsWith('live') } : null,
|
|
949
|
+
pendingPromotions: pending.map((p) => ({ proposalId: p.proposalId, to: p.to, actor: p.actor })),
|
|
950
|
+
note: 'The ladder is draft → backtested → forward-tested → always-on → real, one rung at a time, each on evidence measured on these exact rules. Backtest with strategy_backtest, forward-test with strategy_forward_start, then propose a promotion with strategy_promote_propose; the user applies it. Never claim a result the evidence does not show.',
|
|
951
|
+
};
|
|
952
|
+
}
|
|
953
|
+
async forwardStart(args) {
|
|
954
|
+
const { at } = await this.eng();
|
|
955
|
+
const r = await this.userStrategy(args.id);
|
|
956
|
+
if ('error' in r)
|
|
957
|
+
return r;
|
|
958
|
+
const home = this.home();
|
|
959
|
+
const t = this.now();
|
|
960
|
+
while (this.forwardTimes.length && t - this.forwardTimes[0] > 3_600_000)
|
|
961
|
+
this.forwardTimes.shift();
|
|
962
|
+
if (this.forwardTimes.length >= FORWARD_START_RATE.perHour)
|
|
963
|
+
return { error: `at most ${FORWARD_START_RATE.perHour} forward tests started per hour`, reason: 'rate-limited' };
|
|
964
|
+
const v = at.journeyView(home, r.s, {});
|
|
965
|
+
if (!v.backtest)
|
|
966
|
+
return { error: `${r.s.id} has no backtest with the held-out split on its current rules: run strategy_backtest {id} first (the ladder is backtest, then forward test)`, reason: 'skip-refused' };
|
|
967
|
+
const hours = args.hours === undefined ? null : Number(args.hours);
|
|
968
|
+
const trades = args.trades === undefined ? null : Number(args.trades);
|
|
969
|
+
// No bankroll, no mode, no source: a model's forward test is always paper, live data, the strategy's own bankroll.
|
|
970
|
+
const c = at.createForwardTest(home, r.s, r.raw, { hours: hours ?? (trades === null ? 24 : null), trades, by: this.actor, now: t, source: 'live' });
|
|
971
|
+
if (!c.ok)
|
|
972
|
+
return { error: cleanText(c.why, 300) };
|
|
973
|
+
const pid = (this.opts.forwardSpawner ?? defaultForwardSpawner)(home, c.test, this.env);
|
|
974
|
+
if (!pid) {
|
|
975
|
+
at.saveForwardTest(home, { ...c.test, state: 'failed', endedAt: t, endReason: 'could not start the host from the chat' });
|
|
976
|
+
return { error: `could not start the forward test from here; the user can start it in a terminal: dexterm autotrade forward start ${r.s.id}${hours ? ` --hours ${hours}` : ''}${trades ? ` --trades ${trades}` : ''}` };
|
|
977
|
+
}
|
|
978
|
+
this.forwardTimes.push(t);
|
|
979
|
+
return {
|
|
980
|
+
paper: true, started: true, forwardId: c.test.forwardId, id: r.s.id, version: c.test.version, window: c.test.window, bankrollUsd: c.test.bankrollUsd,
|
|
981
|
+
note: `A FORWARD TEST runs in the background on LIVE data, on paper, with ${r.s.id}'s own bankroll and its own run file, until its window ends (${at.windowText(c.test.window)}). Read it with strategy_forward_report; stop it with strategy_forward_stop. Say nothing about results until it ends, and then only with the trade counts: under 20 closed trades it proves nothing.`,
|
|
982
|
+
};
|
|
983
|
+
}
|
|
984
|
+
async forwardRef(args) {
|
|
985
|
+
const { at } = await this.eng();
|
|
986
|
+
const ref = args.forwardId !== undefined ? String(args.forwardId) : args.id !== undefined ? String(args.id) : '';
|
|
987
|
+
if (!ref)
|
|
988
|
+
return { error: 'pass id (a strategy id) or forwardId' };
|
|
989
|
+
const t = at.findForwardTest(this.home(), ref, this.now());
|
|
990
|
+
return t ? { t } : { error: `no forward test for ${cleanText(ref, 40)}` };
|
|
991
|
+
}
|
|
992
|
+
async forwardStop(args) {
|
|
993
|
+
const { at } = await this.eng();
|
|
994
|
+
const f = await this.forwardRef(args);
|
|
995
|
+
if ('error' in f)
|
|
996
|
+
return f;
|
|
997
|
+
const r = at.requestForwardStop(this.home(), f.t.forwardId, this.actor, this.now());
|
|
998
|
+
if (!r.ok)
|
|
999
|
+
return { error: cleanText(r.why, 200) };
|
|
1000
|
+
return { paper: true, stopRequested: true, already: r.already, forwardId: f.t.forwardId, note: 'It flattens at its next tick, writes its report and ends. A forward test stopped early is shown in the journey but does not count as the forward-tested rung.' };
|
|
1001
|
+
}
|
|
1002
|
+
async forwardReportTool(args) {
|
|
1003
|
+
const { at } = await this.eng();
|
|
1004
|
+
const f = await this.forwardRef(args);
|
|
1005
|
+
if ('error' in f)
|
|
1006
|
+
return f;
|
|
1007
|
+
const r = at.forwardReport(this.home(), f.t, this.now());
|
|
1008
|
+
return {
|
|
1009
|
+
paper: true, readOnly: true, forwardId: r.forwardId, id: r.strategyId, version: r.version, state: r.state, source: r.source, window: r.window, elapsed: r.elapsed,
|
|
1010
|
+
endReason: r.endReason ? cleanText(r.endReason, 200) : null, progress: r.progress, metrics: r.metrics,
|
|
1011
|
+
comparison: { rows: r.comparison.rows, verdict: r.comparison.verdict, claim: r.comparison.claim, sampleOk: r.comparison.sampleOk },
|
|
1012
|
+
note: r.comparison.claim ? 'Both halves of the evidence agree; still quote the trade counts and say it is paper with modelled fills.' : 'No performance claim: report the numbers with their trade counts and the verdict lines as they are. Paper, modelled fills, not financial advice.',
|
|
1013
|
+
};
|
|
1014
|
+
}
|
|
1015
|
+
async promotePropose(args) {
|
|
1016
|
+
const { at } = await this.eng();
|
|
1017
|
+
const r = await this.userStrategy(args.id);
|
|
1018
|
+
if ('error' in r)
|
|
1019
|
+
return r;
|
|
1020
|
+
const t = this.now();
|
|
1021
|
+
while (this.proposalTimes.length && t - this.proposalTimes[0] > 600_000)
|
|
1022
|
+
this.proposalTimes.shift();
|
|
1023
|
+
if (this.proposalTimes.length >= 10)
|
|
1024
|
+
return { error: 'proposal rate limit: at most 10 proposals per 10 minutes', reason: 'rate-limited' };
|
|
1025
|
+
const home = this.home();
|
|
1026
|
+
const p = at.proposePromotion(home, r.s, String(args.to ?? ''), { actor: this.actor, why: cleanText(args.why), now: t, armedHash: at.loadArmings(home)[r.s.id]?.strategyHash ?? null });
|
|
1027
|
+
if (!p.ok)
|
|
1028
|
+
return { ok: false, applied: false, reason: p.reason, error: cleanText(p.why, 300), note: p.reason === 'skip-refused' ? 'No rung can be skipped: do the missing step first (strategy_journey says which).' : p.reason === 'evidence-floor' ? 'Nothing was proposed. Real needs the evidence floor: a COMPLETED forward test of these exact rules with the user\'s minimum of closed paper trades, and a backtest on record. The floor is the user\'s limit: you cannot propose past it, lower it, skip it or override it. Say what is missing and stop.' : 'Nothing was proposed.' };
|
|
1029
|
+
if (!p.duplicate)
|
|
1030
|
+
this.proposalTimes.push(t);
|
|
1031
|
+
const pr = p.proposal;
|
|
1032
|
+
return {
|
|
1033
|
+
ok: true, applied: false, proposalId: pr.proposalId, id: pr.id, to: pr.to, version: pr.version, card: pr.card, duplicate: p.duplicate,
|
|
1034
|
+
note: pr.to === 'real'
|
|
1035
|
+
? `This is a PROPOSAL and it is NOT applied. Real mode is the user's alone: they set their own caps (dexterm autotrade signal caps set) and arm it on their own terminal with a typed phrase (dexterm autotrade promote apply ${pr.proposalId}, which hands over to signal arm). You cannot apply it, set or suggest cap values, or arm anything.`
|
|
1036
|
+
: `This is a PROPOSAL and it is NOT applied. The user applies it with y on a real terminal (dexterm autotrade promote apply ${pr.proposalId}) or on the Q screen journey view; then the always-on service runs it on paper. You cannot apply it.`,
|
|
1037
|
+
};
|
|
1038
|
+
}
|
|
876
1039
|
// ───────────────────────────── parallel strategies (task-1529) ─────────────────────────────
|
|
877
1040
|
/** autotrade.json's strategies and, while `run --all` runs, each one's state. */
|
|
878
1041
|
async parallelList() {
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
// never raise a cap, arm a mandate or turn live on, and no tool, MCP call or chat message changes the profile.
|
|
15
15
|
import { ANALYST_ACTION_TYPES } from '../../../data/src/index.js';
|
|
16
16
|
import { PAPER_NOTE_TOOLS, PAPER_READ_TOOLS, PAPER_WRITE_TOOLS, READ_ONLY_TOOLS } from "../server.js";
|
|
17
|
-
import { PARALLEL_CONTROL_TOOLS, STRATEGY_PROPOSE_TOOLS } from "../autotrade-tools.js";
|
|
17
|
+
import { JOURNEY_CONTROL_TOOLS, JOURNEY_PROPOSE_TOOLS, PARALLEL_CONTROL_TOOLS, STRATEGY_PROPOSE_TOOLS } from "../autotrade-tools.js";
|
|
18
18
|
import { LEDGER_PROPOSE_TOOLS } from "../ledger-tools.js";
|
|
19
19
|
export const RESTRICTION_LEVELS = ['observe', 'propose', 'paper', 'full'];
|
|
20
20
|
export const RESTRICTION_RANK = { observe: 0, propose: 1, paper: 2, full: 3 };
|
|
@@ -65,7 +65,7 @@ export function engineToolAllowlist(level, actionTypes) {
|
|
|
65
65
|
if (!actionTypes || actionTypes.includes(t))
|
|
66
66
|
out.add(t);
|
|
67
67
|
if (allows(level, 'strategy-proposals') && !observeOnly)
|
|
68
|
-
for (const t of STRATEGY_PROPOSE_TOOLS)
|
|
68
|
+
for (const t of [...STRATEGY_PROPOSE_TOOLS, ...JOURNEY_PROPOSE_TOOLS])
|
|
69
69
|
out.add(t);
|
|
70
70
|
// Paper tools: the MCP server still refuses them unless the user armed a mandate and enabled agent paper trading.
|
|
71
71
|
if (allows(level, 'paper-orders') && !observeOnly)
|
|
@@ -74,7 +74,7 @@ export function engineToolAllowlist(level, actionTypes) {
|
|
|
74
74
|
// v0.1.7 integration: the other paper-side writes (start / stop / pause one listed parallel strategy, a paper
|
|
75
75
|
// journal tag, a backtest request file) join at `paper`, so `paper` and `full` offer every tool the claude path has
|
|
76
76
|
if (allows(level, 'paper-orders') && !observeOnly)
|
|
77
|
-
for (const t of [...PARALLEL_CONTROL_TOOLS, ...PAPER_NOTE_TOOLS, ...LEDGER_PROPOSE_TOOLS])
|
|
77
|
+
for (const t of [...PARALLEL_CONTROL_TOOLS, ...JOURNEY_CONTROL_TOOLS, ...PAPER_NOTE_TOOLS, ...LEDGER_PROPOSE_TOOLS])
|
|
78
78
|
out.add(t);
|
|
79
79
|
return [...out].filter((n) => !NEVER_TOOL.test(n));
|
|
80
80
|
}
|
|
@@ -7,7 +7,7 @@ export { resolvePaperAgentDesk, paperAgentDeskDb } from "./paper-agent.js";
|
|
|
7
7
|
export { OverlayStore, mergeOverlay, loadBundled, checkKb, checkSkill, moneyToolMentions, isMoneyToolName, parseFrontmatter, dextermHome, userKbDir, userSkillsDir, userDir, contentJournalPath, journalContent, writeOverlayFile, resetOverlayFile, templateFor, KB_LIMITS, SKILL_LIMITS, KB_ID, SKILL_NAME, KB_RULE, MONEY_TOOLS, } from "./user-content.js";
|
|
8
8
|
// task-1507: the AUTOTRADE chat tools (strategy reads + proposals), the model deny-list, the risk delta and the
|
|
9
9
|
// proposal files the Q screen's keypress path re-checks and applies.
|
|
10
|
-
export { AutotradeTools, ProposalFiles, checkProposal, riskDelta, diffLines, screenDelta, deniedChanges, opsPolicyErrors, opsShapeErrors, proposalCardView, proposalsDir, isProposalId, cleanText, loadEngine, STRATEGY_DENIED_FOR_MODELS, STRATEGY_READ_TOOLS, STRATEGY_PROPOSE_TOOLS, AUTOTRADE_TOOLS, PARALLEL_READ_TOOLS, PARALLEL_CONTROL_TOOLS, PARALLEL_TOOLS, MAX_PROPOSAL_OPS, BACKTEST_MAX_DAYS, BACKTEST_RATE, PROPOSAL_SCHEMA, RUNNER_STATUS_SCHEMA, } from "./autotrade-tools.js";
|
|
10
|
+
export { AutotradeTools, ProposalFiles, checkProposal, riskDelta, diffLines, screenDelta, deniedChanges, opsPolicyErrors, opsShapeErrors, proposalCardView, proposalsDir, isProposalId, cleanText, loadEngine, STRATEGY_DENIED_FOR_MODELS, STRATEGY_READ_TOOLS, STRATEGY_PROPOSE_TOOLS, AUTOTRADE_TOOLS, PARALLEL_READ_TOOLS, PARALLEL_CONTROL_TOOLS, PARALLEL_TOOLS, JOURNEY_READ_TOOLS, JOURNEY_CONTROL_TOOLS, JOURNEY_PROPOSE_TOOLS, JOURNEY_TOOLS, FORWARD_START_RATE, defaultForwardSpawner, MAX_PROPOSAL_OPS, BACKTEST_MAX_DAYS, BACKTEST_RATE, PROPOSAL_SCHEMA, RUNNER_STATUS_SCHEMA, } from "./autotrade-tools.js";
|
|
11
11
|
export { buildDossier } from "./dossier.js";
|
|
12
12
|
// task-1530: the trade-ledger tools (reads + the backtest request), and the request files the CLI runs or dismisses.
|
|
13
13
|
export { LedgerTools, LEDGER_TOOLS, LEDGER_READ_TOOLS, LEDGER_PROPOSE_TOOLS, tradeView, filterFromArgs } from "./ledger-tools.js";
|