@coinrithm/mcp-trading 0.7.15 → 0.7.17

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 (76) hide show
  1. package/CHANGELOG.md +885 -692
  2. package/README.md +486 -400
  3. package/dist/agent/bench/bench.d.ts +59 -0
  4. package/dist/agent/bench/bench.js +319 -0
  5. package/dist/agent/bench/cassette.d.ts +97 -0
  6. package/dist/agent/bench/cassette.js +181 -0
  7. package/dist/agent/bench/costs.d.ts +28 -0
  8. package/dist/agent/bench/costs.js +60 -0
  9. package/dist/agent/bench/labelBuilder.d.ts +123 -0
  10. package/dist/agent/bench/labelBuilder.js +282 -0
  11. package/dist/agent/bench/labels.d.ts +75 -0
  12. package/dist/agent/bench/labels.js +259 -0
  13. package/dist/agent/bench/recordingClient.d.ts +51 -0
  14. package/dist/agent/bench/recordingClient.js +193 -0
  15. package/dist/agent/bench/replayClient.d.ts +41 -0
  16. package/dist/agent/bench/replayClient.js +262 -0
  17. package/dist/agent/bench/report.d.ts +115 -0
  18. package/dist/agent/bench/report.js +435 -0
  19. package/dist/agent/capitalSizing.d.ts +10 -1
  20. package/dist/agent/capitalSizing.js +28 -4
  21. package/dist/agent/cli.d.ts +25 -0
  22. package/dist/agent/cli.js +300 -2
  23. package/dist/agent/client.d.ts +14 -0
  24. package/dist/agent/client.js +23 -6
  25. package/dist/agent/configurationWarnings.js +11 -0
  26. package/dist/agent/decision.d.ts +24 -16
  27. package/dist/agent/decision.js +92 -3
  28. package/dist/agent/decisionValidator.d.ts +31 -0
  29. package/dist/agent/decisionValidator.js +148 -3
  30. package/dist/agent/engine.d.ts +5 -3
  31. package/dist/agent/engine.js +5 -3
  32. package/dist/agent/futuresCloseOutcome.d.ts +19 -0
  33. package/dist/agent/futuresCloseOutcome.js +54 -0
  34. package/dist/agent/futuresEligibility.d.ts +13 -0
  35. package/dist/agent/futuresEligibility.js +43 -5
  36. package/dist/agent/observe.d.ts +28 -2
  37. package/dist/agent/observe.js +530 -90
  38. package/dist/agent/pmPaperCandidates.d.ts +17 -0
  39. package/dist/agent/pmPaperCandidates.js +125 -0
  40. package/dist/agent/pmPaperCycle.d.ts +16 -0
  41. package/dist/agent/pmPaperCycle.js +372 -0
  42. package/dist/agent/pmPaperDecision.d.ts +187 -0
  43. package/dist/agent/pmPaperDecision.js +101 -0
  44. package/dist/agent/pmPaperPolicy.d.ts +71 -0
  45. package/dist/agent/pmPaperPolicy.js +46 -0
  46. package/dist/agent/prompt.d.ts +15 -1
  47. package/dist/agent/prompt.js +187 -9
  48. package/dist/agent/promptTables.d.ts +1 -0
  49. package/dist/agent/promptTables.js +33 -0
  50. package/dist/agent/providerCapabilities.d.ts +7 -0
  51. package/dist/agent/providerCapabilities.js +27 -1
  52. package/dist/agent/providers.d.ts +26 -7
  53. package/dist/agent/providers.js +193 -48
  54. package/dist/agent/resolve.js +3 -0
  55. package/dist/agent/runner.d.ts +17 -1
  56. package/dist/agent/runner.js +210 -38
  57. package/dist/agent/setups.d.ts +2 -1
  58. package/dist/agent/setups.js +17 -16
  59. package/dist/agent/signals.d.ts +33 -0
  60. package/dist/agent/signals.js +89 -0
  61. package/dist/agent/skill.js +46 -0
  62. package/dist/agent/skillValidator.js +56 -0
  63. package/dist/agent/strictLint.js +30 -0
  64. package/dist/agent/templates.js +30 -30
  65. package/dist/agent/types.d.ts +180 -12
  66. package/dist/agent/universe.d.ts +78 -0
  67. package/dist/agent/universe.js +215 -0
  68. package/dist/agent/version.d.ts +1 -1
  69. package/dist/agent/version.js +1 -1
  70. package/dist/client.d.ts +14 -0
  71. package/dist/client.js +31 -1
  72. package/dist/http.js +1 -1
  73. package/dist/pmPaperV2.d.ts +81 -0
  74. package/dist/pmPaperV2.js +53 -0
  75. package/dist/tools.js +84 -6
  76. package/package.json +100 -97
@@ -0,0 +1,89 @@
1
+ // What counts as a signal, per agent (owner 2026-10-07, Telegram 56624: show
2
+ // that personality changes trade handling; audit: setup thresholds and the
3
+ // indicator window were hard-coded for every agent).
4
+ //
5
+ // signals: # optional; absent = the platform defaults below
6
+ // rsiOversold: 30 # RSI at or under this flags an oversold fade
7
+ // rsiOverbought: 72 # RSI at or over this flags an overbought fade
8
+ // strongMovePct: 3 # |24h %| that counts as a strong push
9
+ // leanMovePct: 1 # smaller move that still confirms a trend stack
10
+ // minStrength: 0.6 # flags under this are not raised at all
11
+ // data:
12
+ // indicatorRange: 1W # 1D = 5m bars, 1W = 15m, 1M = 1h, 3M = 4h
13
+ //
14
+ // The flags feed both the prompt's setup shortlist AND the deterministic gate
15
+ // that decides whether a cycle calls the model at all, so these numbers change
16
+ // what the agent notices and when it thinks, not just what it is told.
17
+ export const DEFAULT_SIGNAL_THRESHOLDS = {
18
+ rsiOversold: 35,
19
+ rsiOverbought: 68,
20
+ strongMovePct: 2.0,
21
+ leanMovePct: 0.8,
22
+ minStrength: 0.5,
23
+ };
24
+ /** Candle range -> bar interval, as GET /api/agent/market/:id/candles serves. */
25
+ export const INDICATOR_RANGES = {
26
+ "1D": 300,
27
+ "1W": 900,
28
+ "1M": 3_600,
29
+ "3M": 14_400,
30
+ };
31
+ export const DEFAULT_INDICATOR_RANGE = "1D";
32
+ /** Effective thresholds: declared values over the defaults. */
33
+ export function signalThresholdsOf(spec) {
34
+ return { ...DEFAULT_SIGNAL_THRESHOLDS, ...(spec.signals ?? {}) };
35
+ }
36
+ export function indicatorRangeOf(spec) {
37
+ return spec.data?.indicatorRange ?? DEFAULT_INDICATOR_RANGE;
38
+ }
39
+ /** "5-minute", "15-minute", "1-hour", "4-hour". */
40
+ export function barLabel(range) {
41
+ const s = INDICATOR_RANGES[range];
42
+ return s < 3_600 ? `${s / 60}-minute` : `${s / 3_600}-hour`;
43
+ }
44
+ const BOUNDS = {
45
+ rsiOversold: [5, 50],
46
+ rsiOverbought: [50, 95],
47
+ strongMovePct: [0.1, 50],
48
+ leanMovePct: [0.05, 20],
49
+ minStrength: [0, 1],
50
+ };
51
+ /** Validation issues for raw `signals` / `data` blocks ([] when valid). */
52
+ export function signalIssues(rawSignals, rawData) {
53
+ const issues = [];
54
+ const add = (code, reason) => issues.push([code, reason]);
55
+ const isMap = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
56
+ if (rawSignals !== undefined) {
57
+ if (!isMap(rawSignals)) {
58
+ add("skill_signals", "signals must be a mapping");
59
+ }
60
+ else {
61
+ for (const [key, [lo, hi]] of Object.entries(BOUNDS)) {
62
+ const v = rawSignals[key];
63
+ if (v === undefined)
64
+ continue;
65
+ if (typeof v !== "number" || !Number.isFinite(v) || v < lo || v > hi)
66
+ add("skill_signals_range", `signals.${key} must be a number between ${lo} and ${hi}`);
67
+ }
68
+ const t = signalThresholdsOf({
69
+ signals: rawSignals,
70
+ });
71
+ if (t.rsiOversold >= t.rsiOverbought)
72
+ add("skill_signals_rsi", "signals.rsiOversold must be below signals.rsiOverbought");
73
+ if (t.leanMovePct > t.strongMovePct)
74
+ add("skill_signals_moves", "signals.leanMovePct must not exceed signals.strongMovePct");
75
+ }
76
+ }
77
+ if (rawData !== undefined) {
78
+ if (!isMap(rawData)) {
79
+ add("skill_data", "data must be a mapping");
80
+ }
81
+ else if (rawData.indicatorRange !== undefined &&
82
+ // A real string only: String(['1W']) is "1W" and must not pass.
83
+ (typeof rawData.indicatorRange !== "string" ||
84
+ !Object.hasOwn(INDICATOR_RANGES, rawData.indicatorRange))) {
85
+ add("skill_data_range", `data.indicatorRange must be one of ${Object.keys(INDICATOR_RANGES).join(", ")}`);
86
+ }
87
+ }
88
+ return issues;
89
+ }
@@ -75,6 +75,15 @@ function buildCapitalSizing(raw) {
75
75
  totalCapitalPct: num(policy.totalCapitalPct, Number.NaN),
76
76
  cashReservePct: num(policy.cashReservePct, Number.NaN),
77
77
  minRewardRisk: num(policy.minRewardRisk, Number.NaN),
78
+ ...(policy.highConviction !== undefined
79
+ ? {
80
+ highConviction: {
81
+ minConfidence: num(obj(policy.highConviction).minConfidence, Number.NaN),
82
+ perTicketCapitalPct: num(obj(policy.highConviction).perTicketCapitalPct, Number.NaN),
83
+ futuresRiskPct: num(obj(policy.highConviction).futuresRiskPct, Number.NaN),
84
+ },
85
+ }
86
+ : {}),
78
87
  };
79
88
  }
80
89
  // Coerce raw frontmatter into a best-effort AgentSpec. This NEVER throws on bad
@@ -118,6 +127,32 @@ export function buildSpec(raw) {
118
127
  pmMinEntryProbabilityPct: risk.pmMinEntryProbabilityPct,
119
128
  }
120
129
  : {}),
130
+ // The three PM policy dials follow the same as-written rule.
131
+ ...(risk.pmMinEdgeGapPct !== undefined
132
+ ? {
133
+ pmMinEdgeGapPct: risk.pmMinEdgeGapPct,
134
+ }
135
+ : {}),
136
+ ...(risk.pmMaxEdgePoints !== undefined
137
+ ? {
138
+ pmMaxEdgePoints: risk.pmMaxEdgePoints,
139
+ }
140
+ : {}),
141
+ ...(risk.pmMaxOpenPerEvent !== undefined
142
+ ? {
143
+ pmMaxOpenPerEvent: risk.pmMaxOpenPerEvent,
144
+ }
145
+ : {}),
146
+ ...(risk.pmMaxOpenPerCoinHorizon !== undefined
147
+ ? {
148
+ pmMaxOpenPerCoinHorizon: risk.pmMaxOpenPerCoinHorizon,
149
+ }
150
+ : {}),
151
+ ...(risk.pmMinMinutesToClose !== undefined
152
+ ? {
153
+ pmMinMinutesToClose: risk.pmMinMinutesToClose,
154
+ }
155
+ : {}),
121
156
  maxLeverage: num(risk.maxLeverage, 1),
122
157
  perTradeMarginMusd: num(risk.perTradeMarginMusd, 0),
123
158
  maxConcurrentPositions: num(risk.maxConcurrentPositions, 0),
@@ -155,6 +190,17 @@ export function buildSpec(raw) {
155
190
  },
156
191
  objective: buildObjective(raw.objective),
157
192
  capabilities: strArr(raw.capabilities).filter((c) => ALLOWED_CAPABILITIES.includes(c)),
193
+ // Signal thresholds and data diet: carried AS WRITTEN so skillValidator
194
+ // fails closed on a malformed block instead of a typo meaning "defaults".
195
+ ...(raw.signals !== undefined
196
+ ? { signals: raw.signals }
197
+ : {}),
198
+ ...(raw.data !== undefined ? { data: raw.data } : {}),
199
+ // Market boundaries: carried AS WRITTEN (an object) so skillValidator can
200
+ // fail closed on a malformed block instead of a typo meaning "no limits".
201
+ ...(raw.universe !== undefined
202
+ ? { universe: raw.universe }
203
+ : {}),
158
204
  // OKF v2 (load-bearing): the gate reads this; omitted -> DEFAULT_TRIGGER_POLICY.
159
205
  // This is the agent's INTENT — the platform deployment overlay may tighten it
160
206
  // server-side, and it can never widen a hard cap (caps live in the runner).
@@ -1,5 +1,7 @@
1
1
  import { fail, VENUES, PROVIDERS, SPEC_VERSION, OBJECTIVE_PRIMARIES, ALLOWED_CAPABILITIES, } from "./types.js";
2
+ import { universeIssues } from "./universe.js";
2
3
  import { parseCadenceMs, scanForSecrets } from "./util.js";
4
+ import { signalIssues } from "./signals.js";
3
5
  import { entryPredicateIssues } from "./entryPredicates.js";
4
6
  const isObj = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
5
7
  const isPosNum = (v) => typeof v === "number" && Number.isFinite(v) && v > 0;
@@ -26,6 +28,7 @@ export function validateCapitalSizingPolicy(value) {
26
28
  ...percentKeys,
27
29
  "cashReservePct",
28
30
  "minRewardRisk",
31
+ "highConviction",
29
32
  ]);
30
33
  for (const key of Object.keys(p)) {
31
34
  if (!allowedKeys.has(key))
@@ -55,6 +58,28 @@ export function validateCapitalSizingPolicy(value) {
55
58
  p.totalCapitalPct + p.cashReservePct > 100) {
56
59
  add("skill_capital_sizing_total_reserve", "capitalSizing.totalCapitalPct + cashReservePct cannot exceed 100");
57
60
  }
61
+ if (p.highConviction !== undefined) {
62
+ const hc = p.highConviction;
63
+ if (!isObj(hc)) {
64
+ add("skill_capital_sizing_high_conviction", "capitalSizing.highConviction must be an object");
65
+ }
66
+ else {
67
+ for (const key of Object.keys(hc)) {
68
+ if (!["minConfidence", "perTicketCapitalPct", "futuresRiskPct"].includes(key))
69
+ add("skill_capital_sizing_unknown_key", `unknown capitalSizing.highConviction key "${key}"`);
70
+ }
71
+ if (!isPosNum(hc.minConfidence) || hc.minConfidence > 1)
72
+ add("skill_capital_sizing_high_conviction", "capitalSizing.highConviction.minConfidence must be a finite number in (0, 1]");
73
+ for (const key of ["perTicketCapitalPct", "futuresRiskPct"]) {
74
+ if (!isPosNum(hc[key]) || hc[key] > 100)
75
+ add("skill_capital_sizing_percent", `capitalSizing.highConviction.${key} must be a finite number in (0, 100]`);
76
+ }
77
+ if (isPosNum(hc.perTicketCapitalPct) &&
78
+ isPosNum(p.totalCapitalPct) &&
79
+ hc.perTicketCapitalPct > p.totalCapitalPct)
80
+ add("skill_capital_sizing_ticket_cap", "capitalSizing.highConviction.perTicketCapitalPct cannot exceed totalCapitalPct");
81
+ }
82
+ }
58
83
  return issues;
59
84
  }
60
85
  export function validateSkill(parsed, mode = "self-host") {
@@ -88,6 +113,12 @@ export function validateSkill(parsed, mode = "self-host") {
88
113
  add("skill_venue_unknown", `unknown venue "${String(v)}" (allowed: ${VENUES.join(", ")})`);
89
114
  }
90
115
  }
116
+ // Signal thresholds and data diet (optional).
117
+ for (const [code, reason] of signalIssues(raw.signals, raw.data))
118
+ add(code, reason);
119
+ // Market boundaries (optional).
120
+ for (const [code, reason] of universeIssues(raw.universe))
121
+ add(code, reason);
91
122
  // Risk — always required
92
123
  if (!isObj(raw.risk)) {
93
124
  add("skill_risk", "risk block is required (the caps the agent runs under)");
@@ -115,6 +146,31 @@ export function validateSkill(parsed, mode = "self-host") {
115
146
  if (typeof f !== "number" || !Number.isFinite(f) || f < 0 || f > 100)
116
147
  add("skill_risk_pm_entry_floor", "risk.pmMinEntryProbabilityPct must be a number between 0 and 100 (points; omit for no floor)");
117
148
  }
149
+ if (r.pmMinEdgeGapPct !== undefined) {
150
+ const g = r.pmMinEdgeGapPct;
151
+ if (typeof g !== "number" || !Number.isFinite(g) || g < 0 || g > 100)
152
+ add("skill_risk_pm_edge_gap", "risk.pmMinEdgeGapPct must be a number between 0 and 100 (% of the gap to 100; omit for the global minimum edge only)");
153
+ }
154
+ if (r.pmMaxEdgePoints !== undefined) {
155
+ const m = r.pmMaxEdgePoints;
156
+ if (typeof m !== "number" || !Number.isFinite(m) || m < 0 || m > 100)
157
+ add("skill_risk_pm_max_edge", "risk.pmMaxEdgePoints must be a number between 0 and 100 (points over cost; omit for no cap)");
158
+ }
159
+ if (r.pmMaxOpenPerEvent !== undefined) {
160
+ const n = r.pmMaxOpenPerEvent;
161
+ if (typeof n !== "number" || !Number.isInteger(n) || n < 1 || n > 50)
162
+ add("skill_risk_pm_per_event", "risk.pmMaxOpenPerEvent must be a whole number between 1 and 50 (omit for no per-event cap)");
163
+ }
164
+ if (r.pmMaxOpenPerCoinHorizon !== undefined) {
165
+ const n = r.pmMaxOpenPerCoinHorizon;
166
+ if (typeof n !== "number" || !Number.isInteger(n) || n < 1 || n > 50)
167
+ add("skill_risk_pm_coin_horizon", "risk.pmMaxOpenPerCoinHorizon must be a whole number between 1 and 50 (omit for no per-coin-horizon cap)");
168
+ }
169
+ if (r.pmMinMinutesToClose !== undefined) {
170
+ const m = r.pmMinMinutesToClose;
171
+ if (typeof m !== "number" || !Number.isFinite(m) || m < 0 || m > 10_080)
172
+ add("skill_risk_pm_close_cutoff", "risk.pmMinMinutesToClose must be a number of minutes between 0 and 10080 (omit for no cutoff)");
173
+ }
118
174
  // Fail-closed on the side restriction: a typo ("shorts_only") must never
119
175
  // silently mean "unrestricted" — that is exactly how a prose-only
120
176
  // constraint failed live on 2026-08-24.
@@ -30,8 +30,29 @@ const ALLOWED_KEYS = {
30
30
  // it) but was missing here, so any bundle actually SETTING it got an
31
31
  // unknown_key lint — the knob existed and was unreachable (audit rank 10).
32
32
  "triggerPolicy",
33
+ "signals",
34
+ "data",
35
+ "universe",
33
36
  ],
34
37
  trigger: ["cadence", "timezone", "events"],
38
+ signals: [
39
+ "rsiOversold",
40
+ "rsiOverbought",
41
+ "strongMovePct",
42
+ "leanMovePct",
43
+ "minStrength",
44
+ ],
45
+ data: ["indicatorRange"],
46
+ universe: [
47
+ "rank",
48
+ "minVolume24hUsd",
49
+ "excludeStablecoins",
50
+ "includeSectors",
51
+ "excludeSectors",
52
+ "sort",
53
+ "resolveTop",
54
+ "scanLimit",
55
+ ],
35
56
  triggerPolicy: [
36
57
  "mode",
37
58
  "skipLlmWhenNoTrigger",
@@ -51,6 +72,11 @@ const ALLOWED_KEYS = {
51
72
  "direction",
52
73
  "entryPredicates",
53
74
  "pmMinEntryProbabilityPct",
75
+ "pmMinEdgeGapPct",
76
+ "pmMaxEdgePoints",
77
+ "pmMaxOpenPerEvent",
78
+ "pmMaxOpenPerCoinHorizon",
79
+ "pmMinMinutesToClose",
54
80
  ],
55
81
  sizing: null,
56
82
  capitalSizing: [
@@ -61,6 +87,7 @@ const ALLOWED_KEYS = {
61
87
  "totalCapitalPct",
62
88
  "cashReservePct",
63
89
  "minRewardRisk",
90
+ "highConviction",
64
91
  ],
65
92
  limits: [
66
93
  "maxTradesPerDay",
@@ -141,6 +168,9 @@ export function strictLint(raw) {
141
168
  "sync",
142
169
  "killSwitch",
143
170
  "objective",
171
+ "signals",
172
+ "data",
173
+ "universe",
144
174
  ]) {
145
175
  if (isObj(raw[block]))
146
176
  lintKeys(block, raw[block], issues);
@@ -50,37 +50,37 @@ const PRESETS = {
50
50
  modelFail: 5,
51
51
  },
52
52
  };
53
- const THESIS_BODY = `# Momentum Futures — strategy
54
-
55
- You operate a CoinRithm **paper-trading** futures account (50,000 virtual mUSD).
56
- Everything here is simulated; it is not financial advice and never touches real
57
- money. Edit this prose freely (any language) — it is your agent's borders.
58
-
59
- ## Each cycle
60
-
61
- 1. Ground yourself: read your portfolio and open positions first. Never assume
62
- balances or what is already open.
63
- 2. Scan the watchlist. A candidate is a coin whose short and medium momentum
64
- agree (both up, or both down) and is not already an open position.
65
- 3. Pick the strongest candidate and commit when the read is clear — even a
66
- moderate-confidence one — sized small with a stop. Skip only when the signals
67
- contradict or the data is stale; a quiet tape where your edge is still real
68
- is an act, not a skip.
69
- 4. Quote before you open. Read the liquidation price and confirm it is sane. If
70
- the quote is not eligible, relay the reason and stop.
71
- 5. Open small and protected: enter in the trend direction and set a stop-loss at
72
- open. Place the take-profit a touch wider than the stop.
73
- 6. Stay in sync: poll your trades for any stop / take-profit / liquidation that
74
- fired while you were not looking, and react to what actually happened.
75
-
76
- The hard caps (leverage, margin, watchlist) live in the config blocks above and
53
+ const THESIS_BODY = `# Momentum Futures — strategy
54
+
55
+ You operate a CoinRithm **paper-trading** futures account (50,000 virtual mUSD).
56
+ Everything here is simulated; it is not financial advice and never touches real
57
+ money. Edit this prose freely (any language) — it is your agent's borders.
58
+
59
+ ## Each cycle
60
+
61
+ 1. Ground yourself: read your portfolio and open positions first. Never assume
62
+ balances or what is already open.
63
+ 2. Scan the watchlist. A candidate is a coin whose short and medium momentum
64
+ agree (both up, or both down) and is not already an open position.
65
+ 3. Pick the strongest candidate and commit when the read is clear — even a
66
+ moderate-confidence one — sized small with a stop. Skip only when the signals
67
+ contradict or the data is stale; a quiet tape where your edge is still real
68
+ is an act, not a skip.
69
+ 4. Quote before you open. Read the liquidation price and confirm it is sane. If
70
+ the quote is not eligible, relay the reason and stop.
71
+ 5. Open small and protected: enter in the trend direction and set a stop-loss at
72
+ open. Place the take-profit a touch wider than the stop.
73
+ 6. Stay in sync: poll your trades for any stop / take-profit / liquidation that
74
+ fired while you were not looking, and react to what actually happened.
75
+
76
+ The hard caps (leverage, margin, watchlist) live in the config blocks above and
77
77
  are enforced by the runner — change them there, not in this prose.`;
78
- const PERSONA_STUB = `# Persona
79
-
80
- Decisive and in character. Acts on a clear read — even a moderate-confidence one
81
- — sized small and protected with a stop, and skips only when the read is
82
- contradictory or the data is stale. States its reasoning plainly in its own
83
- voice, and never frames paper results as real-money advice.
78
+ const PERSONA_STUB = `# Persona
79
+
80
+ Decisive and in character. Acts on a clear read — even a moderate-confidence one
81
+ — sized small and protected with a stop, and skips only when the read is
82
+ contradictory or the data is stale. States its reasoning plainly in its own
83
+ voice, and never frames paper results as real-money advice.
84
84
  `;
85
85
  export function buildAgentObject(name, preset) {
86
86
  const p = PRESETS[preset];
@@ -1,6 +1,7 @@
1
1
  import type { FuturesEntryEligibility } from "./futuresEligibility.js";
2
2
  import { IndicatorSet } from "./indicators.js";
3
3
  import type { DecisionInputRecord } from "./decisionReceipt.js";
4
+ import type { DecideRouteAttempt } from "./providers.js";
4
5
  export declare const SPEC_VERSION = "coinrithm.agent.v1";
5
6
  export type Venue = "spot" | "futures" | "pm";
6
7
  export declare const VENUES: readonly Venue[];
@@ -36,6 +37,11 @@ export interface RiskConfig {
36
37
  blocklist?: string[];
37
38
  direction?: "long_only" | "short_only";
38
39
  pmMinEntryProbabilityPct?: number;
40
+ pmMinEdgeGapPct?: number;
41
+ pmMaxEdgePoints?: number;
42
+ pmMaxOpenPerEvent?: number;
43
+ pmMaxOpenPerCoinHorizon?: number;
44
+ pmMinMinutesToClose?: number;
39
45
  }
40
46
  export interface LimitsConfig {
41
47
  maxTradesPerDay: number;
@@ -52,6 +58,15 @@ export interface CapitalSizingPolicy {
52
58
  totalCapitalPct: number;
53
59
  cashReservePct: number;
54
60
  minRewardRisk: number;
61
+ /** Opt-in (owner 2026-10-08): a futures_open the model rates at or above
62
+ * minConfidence uses these larger ticket and stop-risk limits instead of
63
+ * the base ones. Every other cap (total, reserve, open margin, R:R) holds. */
64
+ highConviction?: CapitalHighConviction;
65
+ }
66
+ export interface CapitalHighConviction {
67
+ minConfidence: number;
68
+ perTicketCapitalPct: number;
69
+ futuresRiskPct: number;
55
70
  }
56
71
  export type CapitalBook = {
57
72
  status: "unavailable";
@@ -116,6 +131,25 @@ export interface AgentSpec {
116
131
  objective?: ObjectiveConfig;
117
132
  capabilities: Capability[];
118
133
  triggerPolicy?: TriggerPolicy;
134
+ signals?: Partial<import("./signals.js").SignalThresholds>;
135
+ data?: {
136
+ indicatorRange?: import("./signals.js").IndicatorRange;
137
+ };
138
+ universe?: UniverseConfig;
139
+ }
140
+ /** Declared market boundaries; see universe.ts for semantics and defaults. */
141
+ export interface UniverseConfig {
142
+ rank?: {
143
+ min?: number;
144
+ max?: number;
145
+ };
146
+ minVolume24hUsd?: number;
147
+ excludeStablecoins?: boolean;
148
+ includeSectors?: string[];
149
+ excludeSectors?: string[];
150
+ sort?: "gainers_24h" | "losers_24h" | "abs_change_24h" | "abs_change_1h" | "volume_24h" | "rank";
151
+ resolveTop?: number;
152
+ scanLimit?: number;
119
153
  }
120
154
  export interface ParsedSkill {
121
155
  spec: AgentSpec;
@@ -136,8 +170,8 @@ export interface Freshness {
136
170
  basis?: string;
137
171
  }
138
172
  export interface IndicatorContext {
139
- range: "1D";
140
- nominalIntervalSeconds: 300;
173
+ range: import("./signals.js").IndicatorRange;
174
+ nominalIntervalSeconds: number;
141
175
  barCount: number;
142
176
  timestampedBarCount: number;
143
177
  checkedIntervalCount: number;
@@ -150,6 +184,105 @@ export interface IndicatorContext {
150
184
  intervalStatus: "regular" | "irregular" | "unknown";
151
185
  };
152
186
  }
187
+ export interface OpenInterestContext {
188
+ totalUsd: number;
189
+ venues: string[];
190
+ change1hPct: number | null;
191
+ change1hVenues: string[];
192
+ change24hPct: number | null;
193
+ change24hVenues: string[];
194
+ asOf: string;
195
+ stale: boolean;
196
+ }
197
+ export interface DatedValue {
198
+ value: number;
199
+ asOf: string;
200
+ stale: boolean;
201
+ }
202
+ export interface PositioningContext {
203
+ venue: string;
204
+ symbol: string;
205
+ longShortAccountRatio?: DatedValue;
206
+ longAccountPct?: DatedValue;
207
+ topTraderPositionRatio?: DatedValue;
208
+ takerBuySellRatio?: DatedValue;
209
+ }
210
+ export interface LiquidationWindow {
211
+ longLiquidatedUsdt: number;
212
+ shortLiquidatedUsdt: number;
213
+ events: number;
214
+ capturedPct: number;
215
+ }
216
+ export interface LiquidationContext {
217
+ venue: string;
218
+ instId: string;
219
+ last1h: LiquidationWindow;
220
+ last24h: LiquidationWindow;
221
+ lastEventAt: string | null;
222
+ }
223
+ export interface MacroQuote {
224
+ symbol: string;
225
+ label: string;
226
+ kind: string;
227
+ price: number;
228
+ change24hPct: number | null;
229
+ asOf: string;
230
+ stale: boolean;
231
+ }
232
+ export interface MacroContext {
233
+ note: string;
234
+ quotes: MacroQuote[];
235
+ }
236
+ export interface ChainTvlContext {
237
+ chain: string;
238
+ tvlUsd: number;
239
+ publishedAt: string | null;
240
+ fetchedAt: string | null;
241
+ sourceObservedAt: null;
242
+ stale: boolean;
243
+ dayAt: string | null;
244
+ change1dPct: number | null;
245
+ change7dPct: number | null;
246
+ }
247
+ export interface StablecoinSupplyContext {
248
+ totalUsd: number;
249
+ dayAt: string;
250
+ change1dPct: number | null;
251
+ change7dPct: number | null;
252
+ stale: boolean;
253
+ }
254
+ export interface DepthSideContext {
255
+ usd: number;
256
+ reachPct: number;
257
+ levels: number;
258
+ complete: boolean;
259
+ }
260
+ export interface DepthContext {
261
+ venue: string;
262
+ bid: DepthSideContext | null;
263
+ ask: DepthSideContext | null;
264
+ asOf: string;
265
+ stale: boolean;
266
+ }
267
+ export interface FundingVenueRate {
268
+ role: "settlement_reference" | "context_only";
269
+ venue: string;
270
+ symbol: string;
271
+ source: "paper_futures_reference" | "hyperliquid_predicted_fundings";
272
+ rateFraction: number;
273
+ intervalHours: number | null;
274
+ hourlyEquivalentFraction: number | null;
275
+ nextFundingTime: string | null;
276
+ fetchedAt: string;
277
+ sourceAt: null;
278
+ ageSeconds: number;
279
+ stale: boolean;
280
+ freshnessBasis: "collection_time";
281
+ }
282
+ export interface FundingByVenueContext {
283
+ rates: FundingVenueRate[];
284
+ sameTime: false;
285
+ }
153
286
  export interface WatchEntry {
154
287
  symbol: string;
155
288
  coinId: string | null;
@@ -166,8 +299,15 @@ export interface WatchEntry {
166
299
  indicators?: IndicatorSet;
167
300
  indicatorContext?: IndicatorContext;
168
301
  discovered?: boolean;
302
+ withinBoundaries?: boolean;
169
303
  slug?: string;
170
304
  fundamentals?: CoinFundamentals;
305
+ openInterest?: OpenInterestContext;
306
+ positioning?: PositioningContext;
307
+ liquidations?: LiquidationContext;
308
+ chainTvl?: ChainTvlContext;
309
+ depth?: DepthContext;
310
+ fundingByVenue?: FundingByVenueContext;
171
311
  futuresEntryEligibility?: FuturesEntryEligibility;
172
312
  }
173
313
  export interface CoinFundamentals {
@@ -227,6 +367,8 @@ export interface PmPosition {
227
367
  entryProbability?: number;
228
368
  currentProbability?: number;
229
369
  openedAt?: string;
370
+ coins?: string[];
371
+ endDate?: string;
230
372
  thesis?: ThesisView;
231
373
  }
232
374
  export interface PmResolution {
@@ -250,6 +392,7 @@ export interface PmMarket {
250
392
  volumeUsd?: number;
251
393
  endDate?: string;
252
394
  liquidityUsd?: number;
395
+ coins?: string[];
253
396
  quality?: PmQuality;
254
397
  decisionSupport?: PmDecisionSupport;
255
398
  consensus?: PmConsensus | null;
@@ -322,6 +465,8 @@ export interface NewsItem {
322
465
  ageHours?: number;
323
466
  publishedAt?: string;
324
467
  coins?: string[];
468
+ url?: string;
469
+ category?: string;
325
470
  }
326
471
  export type WhaleContextStatus = "available" | "partial" | "unavailable";
327
472
  export type WhaleContextFailure = "timeout" | "network_error" | "http_error" | "invalid_payload";
@@ -414,6 +559,8 @@ export interface Observation {
414
559
  label: string;
415
560
  fetchedAt?: string;
416
561
  };
562
+ macro?: MacroContext;
563
+ stablecoinSupply?: StablecoinSupplyContext;
417
564
  syncCursor: string | null;
418
565
  newClosedTrades: Array<Record<string, unknown>>;
419
566
  polledBeforeWrite: boolean;
@@ -549,6 +696,8 @@ export interface FuturesFundingQuoteEvidence {
549
696
  annualizedRate?: number;
550
697
  }
551
698
  export interface RunState {
699
+ /** Exact checkpointed v2 request; validated before replay, never model-controlled. */
700
+ pmPaperV2Pending?: unknown;
552
701
  runId: string;
553
702
  cyclesRun: number;
554
703
  writesToday: number;
@@ -580,6 +729,13 @@ export interface RunState {
580
729
  did: string;
581
730
  }>;
582
731
  theses?: Record<string, PositionThesis>;
732
+ lastRejections?: LastRejection[];
733
+ }
734
+ export interface LastRejection {
735
+ at: string;
736
+ action: string;
737
+ code: string;
738
+ reason?: string;
583
739
  }
584
740
  export interface AgentTrace {
585
741
  runId?: string;
@@ -605,8 +761,21 @@ export interface PlannedAction {
605
761
  reason?: string;
606
762
  quote?: QuoteEvidence;
607
763
  executed?: boolean;
764
+ /** V2 close accepted but exposure remains open; accounting is separate. */
765
+ executionPending?: boolean;
766
+ executionReplayed?: boolean;
767
+ /** A newly accepted v2 intent/fill consumes write budget; replay does not. */
768
+ writeRecorded?: boolean;
608
769
  result?: unknown;
609
770
  capitalSizing?: CapitalSizingAdjustment;
771
+ /** Set when the runner replaced the model's futures take-profit; persisted
772
+ * with the cycle so the requested target is never lost. */
773
+ takeProfitRepair?: TakeProfitRepair;
774
+ }
775
+ export interface TakeProfitRepair {
776
+ requestedTakeProfitPrice: number | null;
777
+ appliedTakeProfitPrice: number;
778
+ reason: "missing" | "wrong_side" | "below_reward_risk_floor";
610
779
  }
611
780
  export interface CycleResult {
612
781
  /** Private non-enumerable evidence. Never forward through AgentTrace/public APIs. */
@@ -638,19 +807,18 @@ export interface CycleResult {
638
807
  tokensIn?: number;
639
808
  tokensOut?: number;
640
809
  estimatedCostUsd?: number;
810
+ providerUsage?: {
811
+ promptTokens: number;
812
+ completionTokens: number;
813
+ cacheReadTokens?: number;
814
+ cacheWriteTokens?: number;
815
+ cacheWrite5mTokens?: number;
816
+ cacheWrite1hTokens?: number;
817
+ };
641
818
  effectiveProvider?: string;
642
819
  effectiveModel?: string;
643
820
  routeReason?: string;
644
- routeAttempts?: Array<{
645
- provider: string;
646
- model: string;
647
- outcome: "success" | "failed" | "deferred";
648
- failureClass?: "capacity" | "permanent" | "transient" | "malformed";
649
- status?: number;
650
- retryAfterMs?: number;
651
- latencyMs: number;
652
- error?: string;
653
- }>;
821
+ routeAttempts?: DecideRouteAttempt[];
654
822
  decisionType?: "act" | "skip" | "gate_skip" | "model_error";
655
823
  writeAttempted?: number;
656
824
  writeAccepted?: number;