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.
Files changed (119) hide show
  1. package/dist/packages/agents/src/spectator.js +3 -1
  2. package/dist/packages/agents/src/tiers.js +9 -0
  3. package/dist/packages/agents/src/tools.js +10 -5
  4. package/dist/packages/autotrade/assets/limits.schema.json +569 -0
  5. package/dist/packages/autotrade/examples/executor/README.md +70 -0
  6. package/dist/packages/autotrade/examples/executor/jsonl-tail-executor.mjs +57 -0
  7. package/dist/packages/autotrade/examples/executor/kit.mjs +144 -0
  8. package/dist/packages/autotrade/examples/executor/signal-cmd-executor.mjs +25 -0
  9. package/dist/packages/autotrade/examples/executor/telegram-bot-recipe.md +47 -0
  10. package/dist/packages/autotrade/examples/executor/webhook-executor.mjs +47 -0
  11. package/dist/packages/autotrade/src/backtest.js +2 -1
  12. package/dist/packages/autotrade/src/chat/config.js +4 -2
  13. package/dist/packages/autotrade/src/chat/control.js +1 -0
  14. package/dist/packages/autotrade/src/chat/notify.js +16 -6
  15. package/dist/packages/autotrade/src/chat/secrets.js +25 -18
  16. package/dist/packages/autotrade/src/chat/service.js +3 -0
  17. package/dist/packages/autotrade/src/engine.js +16 -2
  18. package/dist/packages/autotrade/src/executor-health.js +217 -0
  19. package/dist/packages/autotrade/src/fleet.js +3 -0
  20. package/dist/packages/autotrade/src/forward.js +355 -0
  21. package/dist/packages/autotrade/src/index.js +19 -0
  22. package/dist/packages/autotrade/src/journey.js +477 -0
  23. package/dist/packages/autotrade/src/limits.js +682 -0
  24. package/dist/packages/autotrade/src/outbox.js +394 -0
  25. package/dist/packages/autotrade/src/paper-limits.js +87 -0
  26. package/dist/packages/autotrade/src/real-mode.js +688 -0
  27. package/dist/packages/autotrade/src/receipts.js +330 -0
  28. package/dist/packages/autotrade/src/report.js +168 -0
  29. package/dist/packages/autotrade/src/runner.js +62 -11
  30. package/dist/packages/autotrade/src/service-guard.js +125 -0
  31. package/dist/packages/autotrade/src/service-install.js +213 -0
  32. package/dist/packages/autotrade/src/service-recover.js +232 -0
  33. package/dist/packages/autotrade/src/service.js +289 -0
  34. package/dist/packages/autotrade/src/signal-channels.js +312 -0
  35. package/dist/packages/autotrade/src/signal.js +196 -0
  36. package/dist/packages/autotrade/src/strategy.js +3 -1
  37. package/dist/packages/autotrade/src/trade-ledger.js +2 -2
  38. package/dist/packages/cli/src/analyst/backend.js +3 -2
  39. package/dist/packages/cli/src/analyst/codex.js +3 -2
  40. package/dist/packages/cli/src/app-help-gen.js +14 -2
  41. package/dist/packages/cli/src/autotrade-cmd/audit.js +19 -2
  42. package/dist/packages/cli/src/autotrade-cmd/backtest.js +21 -4
  43. package/dist/packages/cli/src/autotrade-cmd/executor.js +91 -0
  44. package/dist/packages/cli/src/autotrade-cmd/fleet.js +8 -4
  45. package/dist/packages/cli/src/autotrade-cmd/forward.js +283 -0
  46. package/dist/packages/cli/src/autotrade-cmd/journey.js +208 -0
  47. package/dist/packages/cli/src/autotrade-cmd/limits.js +393 -0
  48. package/dist/packages/cli/src/autotrade-cmd/receipt.js +63 -0
  49. package/dist/packages/cli/src/autotrade-cmd/report.js +79 -0
  50. package/dist/packages/cli/src/autotrade-cmd/run.js +257 -13
  51. package/dist/packages/cli/src/autotrade-cmd/service.js +873 -0
  52. package/dist/packages/cli/src/autotrade-cmd/signal.js +824 -0
  53. package/dist/packages/cli/src/autotrade-cmd.js +50 -4
  54. package/dist/packages/cli/src/ledger-cmd.js +4 -2
  55. package/dist/packages/cli/src/pnl-card.js +4 -0
  56. package/dist/packages/cli/src/screens/autotrade-journey.js +134 -0
  57. package/dist/packages/cli/src/screens/autotrade-keys.js +13 -0
  58. package/dist/packages/cli/src/screens/autotrade-screen.js +57 -6
  59. package/dist/packages/cli/src/screens/autotrade-ui.js +17 -7
  60. package/dist/packages/cli/src/screens/autotrade-worker.js +4 -2
  61. package/dist/packages/cli/src/telegram-cmd.js +4 -1
  62. package/dist/packages/cli/src/version.js +1 -1
  63. package/dist/packages/data/src/trade/ledger/schema.js +1 -1
  64. package/dist/packages/mcp/assets/kb/app-help/autotrade-executor-kit.md +44 -0
  65. package/dist/packages/mcp/assets/kb/app-help/autotrade-journey.md +46 -0
  66. package/dist/packages/mcp/assets/kb/app-help/autotrade-limits.md +48 -0
  67. package/dist/packages/mcp/assets/kb/app-help/autotrade-real-signal.md +45 -0
  68. package/dist/packages/mcp/assets/kb/app-help/live-trading.md +1 -1
  69. package/dist/packages/mcp/assets/kb/app-help/screen-autotrade.md +12 -0
  70. package/dist/packages/mcp/assets/kb/app-help/trade-ledger.md +1 -1
  71. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-change-a-strategy-by-chat.md +3 -1
  72. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-on-the-q-screen.md +1 -0
  73. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-running-it-all-day.md +2 -2
  74. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-stopping-it.md +2 -0
  75. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-the-always-on-service.md +57 -0
  76. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-what-it-is-paper-self-directed.md +9 -2
  77. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-what-it-will-not-do.md +1 -1
  78. package/dist/packages/mcp/assets/kb/docs/docs-build-a-strategy-by-chat-1-ask-for-a-strategy.md +27 -0
  79. package/dist/packages/mcp/assets/kb/docs/docs-build-a-strategy-by-chat-2-backtest-it-held-out-split.md +31 -0
  80. package/dist/packages/mcp/assets/kb/docs/docs-build-a-strategy-by-chat-3-forward-test-it-on-live-data.md +44 -0
  81. package/dist/packages/mcp/assets/kb/docs/docs-build-a-strategy-by-chat-4-promote-it-to-always-on-paper.md +36 -0
  82. package/dist/packages/mcp/assets/kb/docs/docs-build-a-strategy-by-chat-5-real-only-if-you-choose.md +36 -0
  83. package/dist/packages/mcp/assets/kb/docs/docs-build-a-strategy-by-chat-the-ladder.md +39 -0
  84. package/dist/packages/mcp/assets/kb/docs/docs-build-a-strategy-by-chat-the-report.md +31 -0
  85. package/dist/packages/mcp/assets/kb/docs/docs-build-a-strategy-by-chat-what-the-analyst-knows.md +30 -0
  86. package/dist/packages/mcp/assets/kb/docs/docs-build-a-strategy-by-chat.md +24 -0
  87. package/dist/packages/mcp/assets/kb/docs/docs-getting-started-install.md +3 -3
  88. package/dist/packages/mcp/assets/kb/docs/docs-getting-started-requirements.md +1 -1
  89. package/dist/packages/mcp/assets/kb/docs/docs-going-real-executor-health.md +38 -0
  90. package/dist/packages/mcp/assets/kb/docs/docs-going-real-paper-vs-real.md +33 -0
  91. package/dist/packages/mcp/assets/kb/docs/docs-going-real-the-digest.md +36 -0
  92. package/dist/packages/mcp/assets/kb/docs/docs-going-real-the-executor-kit.md +48 -0
  93. package/dist/packages/mcp/assets/kb/docs/docs-going-real-the-whole-path.md +37 -0
  94. package/dist/packages/mcp/assets/kb/docs/docs-going-real-what-dexterm-does-and-never-does.md +44 -0
  95. package/dist/packages/mcp/assets/kb/docs/docs-going-real-your-limits.md +77 -0
  96. package/dist/packages/mcp/assets/kb/docs/docs-going-real.md +24 -0
  97. package/dist/packages/mcp/assets/kb/docs/docs-live-trading-changed-in-0-1-7.md +1 -1
  98. package/dist/packages/mcp/assets/kb/docs/docs-live-trading-real-mode-your-strategy-s-trades-to-your.md +31 -0
  99. package/dist/packages/mcp/assets/kb/docs/docs-live-trading-the-short-version.md +1 -1
  100. package/dist/packages/mcp/assets/kb/docs/docs-live-trading-using-a-trading-bot-with-dexterm.md +0 -2
  101. package/dist/packages/mcp/assets/kb/docs/docs-live-trading-what-dexterm-does-not-do.md +1 -1
  102. package/dist/packages/mcp/assets/kb/docs/docs-real-mode-receipts-what-your-executor-did.md +42 -0
  103. package/dist/packages/mcp/assets/kb/docs/docs-real-mode-safety.md +34 -0
  104. package/dist/packages/mcp/assets/kb/docs/docs-real-mode-set-it-up.md +47 -0
  105. package/dist/packages/mcp/assets/kb/docs/docs-real-mode-the-channels.md +42 -0
  106. package/dist/packages/mcp/assets/kb/docs/docs-real-mode-the-signal.md +48 -0
  107. package/dist/packages/mcp/assets/kb/docs/docs-real-mode-what-it-is.md +35 -0
  108. package/dist/packages/mcp/assets/kb/docs/docs-real-mode-what-it-will-not-do.md +34 -0
  109. package/dist/packages/mcp/assets/kb/docs/docs-real-mode-where-you-see-it.md +34 -0
  110. package/dist/packages/mcp/assets/kb/docs/docs-real-mode.md +27 -0
  111. package/dist/packages/mcp/assets/kb/docs/docs-telegram-what-it-is.md +1 -1
  112. package/dist/packages/mcp/assets/kb/entries/autotrade.md +4 -2
  113. package/dist/packages/mcp/src/autotrade-tools.js +168 -5
  114. package/dist/packages/mcp/src/engine/restrictions.js +3 -3
  115. package/dist/packages/mcp/src/index.js +1 -1
  116. package/dist/packages/mcp/src/ledger-tools.js +155 -7
  117. package/dist/packages/mcp/src/server.js +21 -10
  118. package/dist/packages/tui/src/keymap.js +7 -0
  119. package/package.json +1 -1
@@ -20,7 +20,9 @@ const ADDR_RE = new RegExp(`${BASE58}{32,44}`, 'g');
20
20
  const LONG_B58_RE = new RegExp(`${BASE58}{45,}`, 'g');
21
21
  /** A hex run of ≥64 chars is a 32-byte key / signature. */
22
22
  const HEX_KEY_RE = /\b[0-9a-fA-F]{64,}\b/g;
23
- export const SENSITIVE_KEY_RE = /(secret|private|privkey|mnemonic|seed|passphrase|password|signature|sig|apikey|api[_-]?key|keypair|token)/i;
23
+ // task-1645: real mode's trade-fire signals and receipts stay out of shared views (`sig` already covers signal,
24
+ // signalId and txSig; receipt, nonce and trade_fire are added).
25
+ export const SENSITIVE_KEY_RE = /(secret|private|privkey|mnemonic|seed|passphrase|password|signature|sig|apikey|api[_-]?key|keypair|token|receipt|nonce|trade[_-]?fire)/i;
24
26
  /** Mask one address the way the product's shortAddr does (head4…tail4). */
25
27
  export function maskAddress(s) {
26
28
  return s.length <= 10 ? s : `${s.slice(0, 4)}…${s.slice(-4)}`;
@@ -76,6 +76,9 @@ export const TOOL_TIERS = Object.freeze({
76
76
  telegram_status: 'observe',
77
77
  trading_status: 'observe',
78
78
  backtest_requests: 'observe',
79
+ // THE JOURNEY (task-1647): a strategy's ladder and a forward test's report vs its backtest
80
+ strategy_journey: 'observe',
81
+ strategy_forward_report: 'observe',
79
82
  subscribe: 'observe',
80
83
  unsubscribe: 'observe',
81
84
  // ---- annotate: non-financial navigation + agent-owned annotations
@@ -93,6 +96,12 @@ export const TOOL_TIERS = Object.freeze({
93
96
  strategy_propose_new: 'propose',
94
97
  // a backtest REQUEST (task-1530): a file the user runs or dismisses with `dexterm autotrade requests`
95
98
  backtest_request: 'propose',
99
+ // THE JOURNEY (task-1647): a paper forward test (the strategy's own bankroll, a bounded window, never real) and a
100
+ // promotion PROPOSAL the user applies (always-on: a key; real: their own caps + a typed phrase on their terminal).
101
+ // Vetted by hand: "promote" is on the never-list for agent tiers; this one only writes a proposal file.
102
+ strategy_forward_start: 'propose',
103
+ strategy_forward_stop: 'propose',
104
+ strategy_promote_propose: 'propose',
96
105
  });
97
106
  /** Tools that are proposals (write-shaped): they need an idempotency key and count against the proposal quota. */
98
107
  export const PROPOSAL_TOOLS = new Set(['propose_alert', 'propose_order_plan', 'add_to_watchlist']);
@@ -50,16 +50,18 @@ const TOOLS_DEF = [
50
50
  { name: 'strategy_validate', tier: 'observe', description: 'AUTOTRADE (paper): validate a whole dexterm-strategy/1 document ({ok, errors, warnings}). Saves nothing.', inputSchema: { type: 'object', properties: { doc: { free: true } }, required: ['doc'], additionalProperties: false } },
51
51
  { name: 'strategy_backtest', tier: 'observe', description: 'AUTOTRADE (paper): backtest a strategy or a pending proposal on recorded history (days <= 14). Read-only, one at a time, rate-limited.', inputSchema: { type: 'object', properties: { id: STRATEGY, proposalId: { type: 'string', maxLength: 40 }, tape: { type: 'string', enum: ['recorded'] }, days: { type: 'integer', minimum: 1, maximum: 14 } }, additionalProperties: false } },
52
52
  { name: 'autotrade_status', tier: 'observe', description: 'AUTOTRADE (paper): the paper runner\'s status, the strategy count and the proposals waiting for the user.', inputSchema: { type: 'object', properties: {}, additionalProperties: false } },
53
- { name: 'ledger_trades', tier: 'observe', description: 'TRADE LEDGER: trades from the unified ledger (paper, the agent paper desk, autotrade runs, the live signer), newest first: strategy id+version+hash, profile + entry reason and where each came from, exit reason, size and % of book, entry/exit price and market cap, fees, impact, hold, MFE/MAE, rug flag, wallet mode, tx signatures, model session ids and cost. PAPER rows are simulated.', inputSchema: { type: 'object', properties: { source: { type: 'string', enum: ['paper', 'desk', 'autotrade', 'live'] }, mode: { type: 'string', enum: ['paper', 'devnet', 'live'] }, strategy: { type: 'string', maxLength: 50, pattern: '^[a-z][a-z0-9-]{0,31}(@[0-9a-z.]{1,16})?$' }, profile: PROFILE, exit_reason: PROFILE, mint: MINT, run_id: RUN_ID, days: { type: 'number', minimum: 0, maximum: 3650 }, status: { type: 'string', enum: ['open', 'closed'] }, limit: { type: 'integer', minimum: 1, maximum: 200 }, fills: { type: 'boolean' } }, additionalProperties: false } },
54
- { name: 'ledger_positions', tier: 'observe', description: 'TRADE LEDGER: the open trades (cost, % of book, held time, profile). No price fetch, no balance.', inputSchema: { type: 'object', properties: { source: { type: 'string', enum: ['paper', 'desk', 'autotrade', 'live'] }, mode: { type: 'string', enum: ['paper', 'devnet', 'live'] }, strategy: { type: 'string', maxLength: 50, pattern: '^[a-z][a-z0-9-]{0,31}(@[0-9a-z.]{1,16})?$' }, profile: PROFILE, exit_reason: PROFILE, mint: MINT, run_id: RUN_ID, days: { type: 'number', minimum: 0, maximum: 3650 }, limit: { type: 'integer', minimum: 1, maximum: 200 } }, additionalProperties: false } },
55
- { name: 'ledger_equity', tier: 'observe', description: 'TRADE LEDGER: the realized equity curve and its max drawdown (at most 200 points).', inputSchema: { type: 'object', properties: { source: { type: 'string', enum: ['paper', 'desk', 'autotrade', 'live'] }, mode: { type: 'string', enum: ['paper', 'devnet', 'live'] }, strategy: { type: 'string', maxLength: 50, pattern: '^[a-z][a-z0-9-]{0,31}(@[0-9a-z.]{1,16})?$' }, profile: PROFILE, exit_reason: PROFILE, mint: MINT, run_id: RUN_ID, days: { type: 'number', minimum: 0, maximum: 3650 } }, additionalProperties: false } },
56
- { name: 'ledger_summary', tier: 'observe', description: 'TRADE LEDGER: win rate with its 95% interval, P/L, drawdown, rug rate, hold, MFE/MAE, declared-tag coverage, and breakdowns by profile, exit reason, strategy, source and mode.', inputSchema: { type: 'object', properties: { source: { type: 'string', enum: ['paper', 'desk', 'autotrade', 'live'] }, mode: { type: 'string', enum: ['paper', 'devnet', 'live'] }, strategy: { type: 'string', maxLength: 50, pattern: '^[a-z][a-z0-9-]{0,31}(@[0-9a-z.]{1,16})?$' }, profile: PROFILE, exit_reason: PROFILE, mint: MINT, run_id: RUN_ID, days: { type: 'number', minimum: 0, maximum: 3650 } }, additionalProperties: false } },
57
- { name: 'ledger_compare', tier: 'observe', description: 'TRADE LEDGER: one row per strategy version side by side (the forward-test table).', inputSchema: { type: 'object', properties: { source: { type: 'string', enum: ['paper', 'desk', 'autotrade', 'live'] }, mode: { type: 'string', enum: ['paper', 'devnet', 'live'] }, strategy: { type: 'string', maxLength: 50, pattern: '^[a-z][a-z0-9-]{0,31}(@[0-9a-z.]{1,16})?$' }, profile: PROFILE, exit_reason: PROFILE, mint: MINT, run_id: RUN_ID, days: { type: 'number', minimum: 0, maximum: 3650 } }, additionalProperties: false } },
53
+ { name: 'ledger_trades', tier: 'observe', description: 'TRADE LEDGER: trades from the unified ledger (paper, the agent paper desk, autotrade runs, the live signer), newest first: strategy id+version+hash, profile + entry reason and where each came from, exit reason, size and % of book, entry/exit price and market cap, fees, impact, hold, MFE/MAE, rug flag, wallet mode, tx signatures, model session ids and cost. PAPER rows are simulated.', inputSchema: { type: 'object', properties: { source: { type: 'string', enum: ['paper', 'desk', 'autotrade', 'live'] }, mode: { type: 'string', enum: ['paper', 'devnet', 'live', 'real'] }, strategy: { type: 'string', maxLength: 50, pattern: '^[a-z][a-z0-9-]{0,31}(@[0-9a-z.]{1,16})?$' }, profile: PROFILE, exit_reason: PROFILE, mint: MINT, run_id: RUN_ID, days: { type: 'number', minimum: 0, maximum: 3650 }, status: { type: 'string', enum: ['open', 'closed'] }, limit: { type: 'integer', minimum: 1, maximum: 200 }, fills: { type: 'boolean' } }, additionalProperties: false } },
54
+ { name: 'ledger_positions', tier: 'observe', description: 'TRADE LEDGER: the open trades (cost, % of book, held time, profile). No price fetch, no balance.', inputSchema: { type: 'object', properties: { source: { type: 'string', enum: ['paper', 'desk', 'autotrade', 'live'] }, mode: { type: 'string', enum: ['paper', 'devnet', 'live', 'real'] }, strategy: { type: 'string', maxLength: 50, pattern: '^[a-z][a-z0-9-]{0,31}(@[0-9a-z.]{1,16})?$' }, profile: PROFILE, exit_reason: PROFILE, mint: MINT, run_id: RUN_ID, days: { type: 'number', minimum: 0, maximum: 3650 }, limit: { type: 'integer', minimum: 1, maximum: 200 } }, additionalProperties: false } },
55
+ { name: 'ledger_equity', tier: 'observe', description: 'TRADE LEDGER: the realized equity curve and its max drawdown (at most 200 points).', inputSchema: { type: 'object', properties: { source: { type: 'string', enum: ['paper', 'desk', 'autotrade', 'live'] }, mode: { type: 'string', enum: ['paper', 'devnet', 'live', 'real'] }, strategy: { type: 'string', maxLength: 50, pattern: '^[a-z][a-z0-9-]{0,31}(@[0-9a-z.]{1,16})?$' }, profile: PROFILE, exit_reason: PROFILE, mint: MINT, run_id: RUN_ID, days: { type: 'number', minimum: 0, maximum: 3650 } }, additionalProperties: false } },
56
+ { name: 'ledger_summary', tier: 'observe', description: 'TRADE LEDGER: win rate with its 95% interval, P/L, drawdown, rug rate, hold, MFE/MAE, declared-tag coverage, and breakdowns by profile, exit reason, strategy, source and mode.', inputSchema: { type: 'object', properties: { source: { type: 'string', enum: ['paper', 'desk', 'autotrade', 'live'] }, mode: { type: 'string', enum: ['paper', 'devnet', 'live', 'real'] }, strategy: { type: 'string', maxLength: 50, pattern: '^[a-z][a-z0-9-]{0,31}(@[0-9a-z.]{1,16})?$' }, profile: PROFILE, exit_reason: PROFILE, mint: MINT, run_id: RUN_ID, days: { type: 'number', minimum: 0, maximum: 3650 } }, additionalProperties: false } },
57
+ { name: 'ledger_compare', tier: 'observe', description: 'TRADE LEDGER: one row per strategy version side by side (the forward-test table).', inputSchema: { type: 'object', properties: { source: { type: 'string', enum: ['paper', 'desk', 'autotrade', 'live'] }, mode: { type: 'string', enum: ['paper', 'devnet', 'live', 'real'] }, strategy: { type: 'string', maxLength: 50, pattern: '^[a-z][a-z0-9-]{0,31}(@[0-9a-z.]{1,16})?$' }, profile: PROFILE, exit_reason: PROFILE, mint: MINT, run_id: RUN_ID, days: { type: 'number', minimum: 0, maximum: 3650 } }, additionalProperties: false } },
58
58
  { name: 'candidate_dossier', tier: 'observe', description: 'AUTOTRADE: what a run recorded about a candidate coin (rules matched, tick, the session\'s decisions, clamp, fills, close). Text fields are untrusted data.', inputSchema: { type: 'object', properties: { mint: MINT, run_id: RUN_ID }, required: ['mint'], additionalProperties: false } },
59
59
  { name: 'outcomes_lookup', tier: 'observe', description: 'The recorder\'s code-only outcome labels for a coin, or for every candidate of a run (what the skips turned into). Not predictions.', inputSchema: { type: 'object', properties: { mint: MINT, run_id: RUN_ID, limit: { type: 'integer', minimum: 1, maximum: 100 } }, additionalProperties: false } },
60
60
  { name: 'telegram_status', tier: 'observe', description: 'The Telegram connector\'s settings (enabled, paired chat count, tiers, approvals, notify). Never a token or an id.', inputSchema: { type: 'object', properties: {}, additionalProperties: false } },
61
61
  { name: 'trading_status', tier: 'observe', description: 'The live trading signer\'s STATE: mode, kill switch, caps it enforces, a balance band. Never a key, an address or an exact balance; it cannot change anything.', inputSchema: { type: 'object', properties: {}, additionalProperties: false } },
62
62
  { name: 'backtest_requests', tier: 'observe', description: 'Your backtest requests and their results once the user ran them.', inputSchema: { type: 'object', properties: {}, additionalProperties: false } },
63
+ { name: 'strategy_journey', tier: 'observe', description: 'AUTOTRADE: one of the user\'s strategies on the ladder draft → backtested → forward-tested → always-on → real: the evidence per rung on these exact rules and what the next rung needs. Read-only.', inputSchema: { type: 'object', properties: { id: STRATEGY }, required: ['id'], additionalProperties: false } },
64
+ { name: 'strategy_forward_report', tier: 'observe', description: 'AUTOTRADE (paper): a forward test next to the backtest of the same rules, with plain verdict lines (it says when a sample is too small). Read-only.', inputSchema: { type: 'object', properties: { id: STRATEGY, forwardId: { type: 'string', maxLength: 40 } }, additionalProperties: false } },
63
65
  // ---------------------------------------------------------------- annotate: non-financial navigation + agent-owned notes
64
66
  { name: 'open_token', tier: 'annotate', description: "Open a token's detail screen so the user can see it. `mint` must be one of the mints currently on screen (see screen_state). Non-financial navigation; it never trades.", inputSchema: { type: 'object', properties: { mint: MINT }, required: ['mint'], additionalProperties: false } },
65
67
  { name: 'show_in_screener', tier: 'annotate', description: "Show the screener with an optional filter / sort / highlighted mint. `filter` is dexterm's grammar; `highlight` must be a mint on screen.", inputSchema: { type: 'object', properties: { filter: { type: 'string', maxLength: 200 }, sort: { type: 'object', properties: { key: { type: 'string', maxLength: 24 }, dir: { type: 'string', enum: ['asc', 'desc'] } }, required: ['key'], additionalProperties: false }, highlight: MINT }, additionalProperties: false } },
@@ -71,6 +73,9 @@ const TOOLS_DEF = [
71
73
  { name: 'strategy_propose_edit', tier: 'propose', description: 'AUTOTRADE (paper): PROPOSE JSON-patch ops (<= 20) on one of the user\'s strategies, with a why. Returns the diff, validation and riskDelta. Never applied by you: the user applies it with a key on the Q screen (a looser edit needs a second key). /mode, /bankroll, /agent/budgetUsdPerDay and /execution/minExitLiquidityUsd are user-only.', inputSchema: { type: 'object', properties: { id: STRATEGY, ops: PATCH_OPS, why: { type: 'string', maxLength: 200 } }, required: ['id', 'ops', 'why'], additionalProperties: false } },
72
74
  { name: 'strategy_propose_new', tier: 'propose', description: 'AUTOTRADE (paper): PROPOSE a new paper strategy copied from a preset under a new id and name, with optional ops (<= 20). Never applied by you; the user confirms it with a key.', inputSchema: { type: 'object', properties: { fromPreset: STRATEGY, id: STRATEGY, name: { type: 'string', maxLength: 80 }, ops: PATCH_OPS, why: { type: 'string', maxLength: 200 } }, required: ['fromPreset', 'id', 'name'], additionalProperties: false } },
73
75
  { name: 'backtest_request', tier: 'propose', description: 'PROPOSE a longer backtest (a stored strategy id, 1 to 60 days, optionally every candidate) with a why. The user runs or dismisses it with `dexterm autotrade requests`; nothing runs here.', inputSchema: { type: 'object', properties: { id: STRATEGY, days: { type: 'integer', minimum: 1, maximum: 60 }, every_candidate: { type: 'boolean' }, why: { type: 'string', maxLength: 200 } }, required: ['id', 'why'], additionalProperties: false } },
76
+ { name: 'strategy_forward_start', tier: 'propose', description: 'AUTOTRADE (paper): start a forward test of one of the user\'s strategies on live data, on paper, with its own bankroll, for hours (0.25-72) and/or closed trades (1-200). Needs a backtest first. Never real; cannot set a bankroll or a cap.', inputSchema: { type: 'object', properties: { id: STRATEGY, hours: { type: 'number', minimum: 0.25, maximum: 72 }, trades: { type: 'integer', minimum: 1, maximum: 200 } }, required: ['id'], additionalProperties: false } },
77
+ { name: 'strategy_forward_stop', tier: 'propose', description: 'AUTOTRADE (paper): stop a running forward test (it flattens and writes its report; an early stop does not count).', inputSchema: { type: 'object', properties: { id: STRATEGY, forwardId: { type: 'string', maxLength: 40 } }, additionalProperties: false } },
78
+ { name: 'strategy_promote_propose', tier: 'propose', description: 'AUTOTRADE: PROPOSE promoting a strategy one rung (always-on or real) with an evidence card. Never applied by you and no rung can be skipped: the user applies always-on with a key, real only above their evidence floor (a completed forward test with their minimum of closed paper trades + a backtest), with their own caps and a typed phrase on their terminal.', inputSchema: { type: 'object', properties: { id: STRATEGY, to: { type: 'string', enum: ['always-on', 'real'] }, why: { type: 'string', maxLength: 200 } }, required: ['id', 'to', 'why'], additionalProperties: false } },
74
79
  { name: 'add_to_watchlist', tier: 'propose', description: "Propose adding a token to the user's watchlist. Appears as a PROPOSAL labelled with this agent. Needs idempotency_key.", inputSchema: { type: 'object', properties: { mint: MINT, note: { type: 'string', maxLength: 200 }, idempotency_key: IDEMPOTENCY }, required: ['mint', 'idempotency_key'], additionalProperties: false } },
75
80
  ];
76
81
  /** task-1537: the read tools whose results carry prices also carry market-cap twins; their descriptions say so + the basis. */
@@ -0,0 +1,569 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://dexterm.xyz/schemas/limits.schema.json",
4
+ "title": "dexterm-limits/1",
5
+ "description": "dexterm real-mode limits: $DEXTERM_HOME/autotrade/real/limits.json. A number in global replaces the placeholder default (null turns it off); every other scope can only tighten (the stricter number wins). Only `dexterm autotrade limits` writes it; raising needs a typed confirmation on a real terminal.",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": [
9
+ "schema"
10
+ ],
11
+ "properties": {
12
+ "schema": {
13
+ "const": "dexterm-limits/1"
14
+ },
15
+ "global": {
16
+ "$ref": "#/$defs/globalSet"
17
+ },
18
+ "modes": {
19
+ "type": "object",
20
+ "additionalProperties": false,
21
+ "properties": {
22
+ "real": {
23
+ "$ref": "#/$defs/set"
24
+ },
25
+ "paper": {
26
+ "$ref": "#/$defs/set"
27
+ }
28
+ }
29
+ },
30
+ "strategies": {
31
+ "type": "object",
32
+ "propertyNames": {
33
+ "pattern": "^[a-z0-9][a-z0-9._-]{0,63}$"
34
+ },
35
+ "additionalProperties": {
36
+ "$ref": "#/$defs/set"
37
+ }
38
+ },
39
+ "accounts": {
40
+ "type": "object",
41
+ "propertyNames": {
42
+ "pattern": "^[a-z0-9][a-z0-9._-]{0,63}$"
43
+ },
44
+ "additionalProperties": {
45
+ "$ref": "#/$defs/set"
46
+ }
47
+ },
48
+ "updatedAt": {
49
+ "type": "number"
50
+ },
51
+ "migratedFrom": {
52
+ "type": "string"
53
+ }
54
+ },
55
+ "$defs": {
56
+ "set": {
57
+ "type": "object",
58
+ "additionalProperties": false,
59
+ "properties": {
60
+ "perTradeSol": {
61
+ "type": "number",
62
+ "minimum": 1e-06,
63
+ "maximum": 1000000000000,
64
+ "description": "the most SOL one buy signal may spend (a bigger buy is clamped down to it) (SOL; a cap: smaller is stricter)"
65
+ },
66
+ "perTradePct": {
67
+ "type": "number",
68
+ "minimum": 0.01,
69
+ "maximum": 100,
70
+ "description": "the most one buy may spend as a percent of the book (bookSol, else the strategy book); with perTradeSol set too, the smaller wins (% of book; a cap: smaller is stricter)"
71
+ },
72
+ "dailySol": {
73
+ "type": "number",
74
+ "minimum": 1e-06,
75
+ "maximum": 1000000000000,
76
+ "description": "SOL all buy signals may spend per local day (a buy over it is refused) (SOL/day; a cap: smaller is stricter)"
77
+ },
78
+ "buysPerDay": {
79
+ "type": "integer",
80
+ "minimum": 0,
81
+ "maximum": 1000000,
82
+ "description": "buy signals per local day (0 = no buys) (buys/day; a cap: smaller is stricter)"
83
+ },
84
+ "maxOpenPositions": {
85
+ "type": "integer",
86
+ "minimum": 0,
87
+ "maximum": 100000,
88
+ "description": "real positions open at once, across every strategy and account on this home (open; a cap: smaller is stricter)"
89
+ },
90
+ "maxExposurePct": {
91
+ "type": "number",
92
+ "minimum": 0.01,
93
+ "maximum": 100,
94
+ "description": "the cost of every open real position together, as a percent of the book (a buy is clamped to what is left) (% of book; a cap: smaller is stricter)"
95
+ },
96
+ "minPositionSol": {
97
+ "type": "number",
98
+ "minimum": 0,
99
+ "maximum": 1000000000000,
100
+ "description": "a buy smaller than this after every clamp is refused (no dust orders) (SOL; a floor: larger is stricter)"
101
+ },
102
+ "maxPositionSol": {
103
+ "type": "number",
104
+ "minimum": 1e-06,
105
+ "maximum": 1000000000000,
106
+ "description": "the most SOL one position may cost, adds included (a buy is clamped to what is left) (SOL; a cap: smaller is stricter)"
107
+ },
108
+ "perCoinSolPerDay": {
109
+ "type": "number",
110
+ "minimum": 1e-06,
111
+ "maximum": 1000000000000,
112
+ "description": "SOL spent on one coin per local day, re-entries included (a buy over it is refused) (SOL/day; a cap: smaller is stricter)"
113
+ },
114
+ "perCoinBuysPerDay": {
115
+ "type": "integer",
116
+ "minimum": 0,
117
+ "maximum": 100000,
118
+ "description": "buy signals on one coin per local day (buys/day; a cap: smaller is stricter)"
119
+ },
120
+ "bookSol": {
121
+ "type": "number",
122
+ "minimum": 1e-06,
123
+ "maximum": 1000000000000,
124
+ "description": "what the percent limits are a percent of: the size of YOUR account (unset = the strategy's own book) (SOL; a cap: smaller is stricter)"
125
+ },
126
+ "dailyLossSol": {
127
+ "type": "number",
128
+ "minimum": 1e-06,
129
+ "maximum": 1000000000000,
130
+ "description": "when the day's realized loss reaches this, no new buy goes out until the next local day (exits still do) (SOL/day; a cap: smaller is stricter)"
131
+ },
132
+ "maxDrawdownSol": {
133
+ "type": "number",
134
+ "minimum": 1e-06,
135
+ "maximum": 1000000000000,
136
+ "description": "the drawdown breaker: realized PnL this far below its peak trips it; no new buy until you reset it (typed) (SOL; a cap: smaller is stricter)"
137
+ },
138
+ "lossStreak": {
139
+ "type": "integer",
140
+ "minimum": 1,
141
+ "maximum": 10000,
142
+ "description": "this many losing closes in a row pause new buys for lossStreakPauseMin (losses; a cap: smaller is stricter)"
143
+ },
144
+ "lossStreakPauseMin": {
145
+ "type": "number",
146
+ "minimum": 1,
147
+ "maximum": 525600,
148
+ "description": "how long the loss-streak pause lasts (min; a floor: larger is stricter)"
149
+ },
150
+ "maxHoldMinutes": {
151
+ "type": "number",
152
+ "minimum": 1,
153
+ "maximum": 525600,
154
+ "description": "a real position held this long gets its exit signal (the stricter of this and the exit profile's own cap) (min; a cap: smaller is stricter)"
155
+ },
156
+ "minLiquidityUsd": {
157
+ "type": "number",
158
+ "minimum": 0,
159
+ "maximum": 1000000000000,
160
+ "description": "no buy on a coin with less pool liquidity (unknown liquidity is refused while this is set) (USD; a floor: larger is stricter)"
161
+ },
162
+ "minMcapUsd": {
163
+ "type": "number",
164
+ "minimum": 0,
165
+ "maximum": 1000000000000,
166
+ "description": "no buy on a coin below this market cap (unknown is refused while this is set) (USD; a floor: larger is stricter)"
167
+ },
168
+ "maxSlippageBps": {
169
+ "type": "integer",
170
+ "minimum": 1,
171
+ "maximum": 10000,
172
+ "description": "the slippage tolerance a signal states (the stricter of this and the strategy's) (bps; a cap: smaller is stricter)"
173
+ },
174
+ "maxPriceImpactPct": {
175
+ "type": "number",
176
+ "minimum": 0.01,
177
+ "maximum": 100,
178
+ "description": "the price impact a buy may have on the pool (constant-product estimate): a buy is clamped to it; the signal states it (%; a cap: smaller is stricter)"
179
+ },
180
+ "signalTtlSec": {
181
+ "type": "integer",
182
+ "minimum": 10,
183
+ "maximum": 600,
184
+ "description": "how long an executor may act on a signal (the stricter of this and the channel setting) (s; a cap: smaller is stricter)"
185
+ },
186
+ "maxBuysPerMinute": {
187
+ "type": "integer",
188
+ "minimum": 0,
189
+ "maximum": 10000,
190
+ "description": "the signal rate limit: buy signals in any 60 s (buys/min; a cap: smaller is stricter)"
191
+ },
192
+ "maxSellsPerMinute": {
193
+ "type": "integer",
194
+ "minimum": 1,
195
+ "maximum": 10000,
196
+ "description": "OPTIONAL: sells are never capped unless you set this; a sell over it waits for the next tick (sells/min; a cap: smaller is stricter)"
197
+ },
198
+ "minForwardTrades": {
199
+ "type": "integer",
200
+ "minimum": 0,
201
+ "maximum": 100000,
202
+ "description": "the evidence floor for real: arming needs a COMPLETED forward test of these exact rules with at least this many closed paper trades, and a backtest on record (below it only a typed override on your own terminal arms, journaled) (trades; a floor: larger is stricter)"
203
+ },
204
+ "executorSilenceMin": {
205
+ "type": "number",
206
+ "minimum": 1,
207
+ "maximum": 525600,
208
+ "description": "no heartbeat, receipt or ack from your executor for this long pauses new buy signals (exits still go out) (min; a cap: smaller is stricter)"
209
+ }
210
+ }
211
+ },
212
+ "globalSet": {
213
+ "type": "object",
214
+ "additionalProperties": false,
215
+ "properties": {
216
+ "perTradeSol": {
217
+ "oneOf": [
218
+ {
219
+ "type": "null",
220
+ "description": "off: this limit does not apply"
221
+ },
222
+ {
223
+ "type": "number",
224
+ "minimum": 1e-06,
225
+ "maximum": 1000000000000,
226
+ "description": "the most SOL one buy signal may spend (a bigger buy is clamped down to it) (SOL; a cap: smaller is stricter)"
227
+ }
228
+ ]
229
+ },
230
+ "perTradePct": {
231
+ "oneOf": [
232
+ {
233
+ "type": "null",
234
+ "description": "off: this limit does not apply"
235
+ },
236
+ {
237
+ "type": "number",
238
+ "minimum": 0.01,
239
+ "maximum": 100,
240
+ "description": "the most one buy may spend as a percent of the book (bookSol, else the strategy book); with perTradeSol set too, the smaller wins (% of book; a cap: smaller is stricter)"
241
+ }
242
+ ]
243
+ },
244
+ "dailySol": {
245
+ "oneOf": [
246
+ {
247
+ "type": "null",
248
+ "description": "off: this limit does not apply"
249
+ },
250
+ {
251
+ "type": "number",
252
+ "minimum": 1e-06,
253
+ "maximum": 1000000000000,
254
+ "description": "SOL all buy signals may spend per local day (a buy over it is refused) (SOL/day; a cap: smaller is stricter)"
255
+ }
256
+ ]
257
+ },
258
+ "buysPerDay": {
259
+ "oneOf": [
260
+ {
261
+ "type": "null",
262
+ "description": "off: this limit does not apply"
263
+ },
264
+ {
265
+ "type": "integer",
266
+ "minimum": 0,
267
+ "maximum": 1000000,
268
+ "description": "buy signals per local day (0 = no buys) (buys/day; a cap: smaller is stricter)"
269
+ }
270
+ ]
271
+ },
272
+ "maxOpenPositions": {
273
+ "oneOf": [
274
+ {
275
+ "type": "null",
276
+ "description": "off: this limit does not apply"
277
+ },
278
+ {
279
+ "type": "integer",
280
+ "minimum": 0,
281
+ "maximum": 100000,
282
+ "description": "real positions open at once, across every strategy and account on this home (open; a cap: smaller is stricter)"
283
+ }
284
+ ]
285
+ },
286
+ "maxExposurePct": {
287
+ "oneOf": [
288
+ {
289
+ "type": "null",
290
+ "description": "off: this limit does not apply"
291
+ },
292
+ {
293
+ "type": "number",
294
+ "minimum": 0.01,
295
+ "maximum": 100,
296
+ "description": "the cost of every open real position together, as a percent of the book (a buy is clamped to what is left) (% of book; a cap: smaller is stricter)"
297
+ }
298
+ ]
299
+ },
300
+ "minPositionSol": {
301
+ "oneOf": [
302
+ {
303
+ "type": "null",
304
+ "description": "off: this limit does not apply"
305
+ },
306
+ {
307
+ "type": "number",
308
+ "minimum": 0,
309
+ "maximum": 1000000000000,
310
+ "description": "a buy smaller than this after every clamp is refused (no dust orders) (SOL; a floor: larger is stricter)"
311
+ }
312
+ ]
313
+ },
314
+ "maxPositionSol": {
315
+ "oneOf": [
316
+ {
317
+ "type": "null",
318
+ "description": "off: this limit does not apply"
319
+ },
320
+ {
321
+ "type": "number",
322
+ "minimum": 1e-06,
323
+ "maximum": 1000000000000,
324
+ "description": "the most SOL one position may cost, adds included (a buy is clamped to what is left) (SOL; a cap: smaller is stricter)"
325
+ }
326
+ ]
327
+ },
328
+ "perCoinSolPerDay": {
329
+ "oneOf": [
330
+ {
331
+ "type": "null",
332
+ "description": "off: this limit does not apply"
333
+ },
334
+ {
335
+ "type": "number",
336
+ "minimum": 1e-06,
337
+ "maximum": 1000000000000,
338
+ "description": "SOL spent on one coin per local day, re-entries included (a buy over it is refused) (SOL/day; a cap: smaller is stricter)"
339
+ }
340
+ ]
341
+ },
342
+ "perCoinBuysPerDay": {
343
+ "oneOf": [
344
+ {
345
+ "type": "null",
346
+ "description": "off: this limit does not apply"
347
+ },
348
+ {
349
+ "type": "integer",
350
+ "minimum": 0,
351
+ "maximum": 100000,
352
+ "description": "buy signals on one coin per local day (buys/day; a cap: smaller is stricter)"
353
+ }
354
+ ]
355
+ },
356
+ "bookSol": {
357
+ "oneOf": [
358
+ {
359
+ "type": "null",
360
+ "description": "off: this limit does not apply"
361
+ },
362
+ {
363
+ "type": "number",
364
+ "minimum": 1e-06,
365
+ "maximum": 1000000000000,
366
+ "description": "what the percent limits are a percent of: the size of YOUR account (unset = the strategy's own book) (SOL; a cap: smaller is stricter)"
367
+ }
368
+ ]
369
+ },
370
+ "dailyLossSol": {
371
+ "oneOf": [
372
+ {
373
+ "type": "null",
374
+ "description": "off: this limit does not apply"
375
+ },
376
+ {
377
+ "type": "number",
378
+ "minimum": 1e-06,
379
+ "maximum": 1000000000000,
380
+ "description": "when the day's realized loss reaches this, no new buy goes out until the next local day (exits still do) (SOL/day; a cap: smaller is stricter)"
381
+ }
382
+ ]
383
+ },
384
+ "maxDrawdownSol": {
385
+ "oneOf": [
386
+ {
387
+ "type": "null",
388
+ "description": "off: this limit does not apply"
389
+ },
390
+ {
391
+ "type": "number",
392
+ "minimum": 1e-06,
393
+ "maximum": 1000000000000,
394
+ "description": "the drawdown breaker: realized PnL this far below its peak trips it; no new buy until you reset it (typed) (SOL; a cap: smaller is stricter)"
395
+ }
396
+ ]
397
+ },
398
+ "lossStreak": {
399
+ "oneOf": [
400
+ {
401
+ "type": "null",
402
+ "description": "off: this limit does not apply"
403
+ },
404
+ {
405
+ "type": "integer",
406
+ "minimum": 1,
407
+ "maximum": 10000,
408
+ "description": "this many losing closes in a row pause new buys for lossStreakPauseMin (losses; a cap: smaller is stricter)"
409
+ }
410
+ ]
411
+ },
412
+ "lossStreakPauseMin": {
413
+ "oneOf": [
414
+ {
415
+ "type": "null",
416
+ "description": "off: this limit does not apply"
417
+ },
418
+ {
419
+ "type": "number",
420
+ "minimum": 1,
421
+ "maximum": 525600,
422
+ "description": "how long the loss-streak pause lasts (min; a floor: larger is stricter)"
423
+ }
424
+ ]
425
+ },
426
+ "maxHoldMinutes": {
427
+ "oneOf": [
428
+ {
429
+ "type": "null",
430
+ "description": "off: this limit does not apply"
431
+ },
432
+ {
433
+ "type": "number",
434
+ "minimum": 1,
435
+ "maximum": 525600,
436
+ "description": "a real position held this long gets its exit signal (the stricter of this and the exit profile's own cap) (min; a cap: smaller is stricter)"
437
+ }
438
+ ]
439
+ },
440
+ "minLiquidityUsd": {
441
+ "oneOf": [
442
+ {
443
+ "type": "null",
444
+ "description": "off: this limit does not apply"
445
+ },
446
+ {
447
+ "type": "number",
448
+ "minimum": 0,
449
+ "maximum": 1000000000000,
450
+ "description": "no buy on a coin with less pool liquidity (unknown liquidity is refused while this is set) (USD; a floor: larger is stricter)"
451
+ }
452
+ ]
453
+ },
454
+ "minMcapUsd": {
455
+ "oneOf": [
456
+ {
457
+ "type": "null",
458
+ "description": "off: this limit does not apply"
459
+ },
460
+ {
461
+ "type": "number",
462
+ "minimum": 0,
463
+ "maximum": 1000000000000,
464
+ "description": "no buy on a coin below this market cap (unknown is refused while this is set) (USD; a floor: larger is stricter)"
465
+ }
466
+ ]
467
+ },
468
+ "maxSlippageBps": {
469
+ "oneOf": [
470
+ {
471
+ "type": "null",
472
+ "description": "off: this limit does not apply"
473
+ },
474
+ {
475
+ "type": "integer",
476
+ "minimum": 1,
477
+ "maximum": 10000,
478
+ "description": "the slippage tolerance a signal states (the stricter of this and the strategy's) (bps; a cap: smaller is stricter)"
479
+ }
480
+ ]
481
+ },
482
+ "maxPriceImpactPct": {
483
+ "oneOf": [
484
+ {
485
+ "type": "null",
486
+ "description": "off: this limit does not apply"
487
+ },
488
+ {
489
+ "type": "number",
490
+ "minimum": 0.01,
491
+ "maximum": 100,
492
+ "description": "the price impact a buy may have on the pool (constant-product estimate): a buy is clamped to it; the signal states it (%; a cap: smaller is stricter)"
493
+ }
494
+ ]
495
+ },
496
+ "signalTtlSec": {
497
+ "oneOf": [
498
+ {
499
+ "type": "null",
500
+ "description": "off: this limit does not apply"
501
+ },
502
+ {
503
+ "type": "integer",
504
+ "minimum": 10,
505
+ "maximum": 600,
506
+ "description": "how long an executor may act on a signal (the stricter of this and the channel setting) (s; a cap: smaller is stricter)"
507
+ }
508
+ ]
509
+ },
510
+ "maxBuysPerMinute": {
511
+ "oneOf": [
512
+ {
513
+ "type": "null",
514
+ "description": "off: this limit does not apply"
515
+ },
516
+ {
517
+ "type": "integer",
518
+ "minimum": 0,
519
+ "maximum": 10000,
520
+ "description": "the signal rate limit: buy signals in any 60 s (buys/min; a cap: smaller is stricter)"
521
+ }
522
+ ]
523
+ },
524
+ "maxSellsPerMinute": {
525
+ "oneOf": [
526
+ {
527
+ "type": "null",
528
+ "description": "off: this limit does not apply"
529
+ },
530
+ {
531
+ "type": "integer",
532
+ "minimum": 1,
533
+ "maximum": 10000,
534
+ "description": "OPTIONAL: sells are never capped unless you set this; a sell over it waits for the next tick (sells/min; a cap: smaller is stricter)"
535
+ }
536
+ ]
537
+ },
538
+ "minForwardTrades": {
539
+ "oneOf": [
540
+ {
541
+ "type": "null",
542
+ "description": "off: this limit does not apply"
543
+ },
544
+ {
545
+ "type": "integer",
546
+ "minimum": 0,
547
+ "maximum": 100000,
548
+ "description": "the evidence floor for real: arming needs a COMPLETED forward test of these exact rules with at least this many closed paper trades, and a backtest on record (below it only a typed override on your own terminal arms, journaled) (trades; a floor: larger is stricter)"
549
+ }
550
+ ]
551
+ },
552
+ "executorSilenceMin": {
553
+ "oneOf": [
554
+ {
555
+ "type": "null",
556
+ "description": "off: this limit does not apply"
557
+ },
558
+ {
559
+ "type": "number",
560
+ "minimum": 1,
561
+ "maximum": 525600,
562
+ "description": "no heartbeat, receipt or ack from your executor for this long pauses new buy signals (exits still go out) (min; a cap: smaller is stricter)"
563
+ }
564
+ ]
565
+ }
566
+ }
567
+ }
568
+ }
569
+ }