@coinrithm/mcp-trading 0.7.16 → 0.7.18

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 (48) hide show
  1. package/CHANGELOG.md +903 -811
  2. package/README.md +488 -415
  3. package/dist/agent/capitalSizing.d.ts +10 -1
  4. package/dist/agent/capitalSizing.js +28 -4
  5. package/dist/agent/client.d.ts +13 -0
  6. package/dist/agent/client.js +17 -6
  7. package/dist/agent/decision.d.ts +21 -16
  8. package/dist/agent/decision.js +7 -0
  9. package/dist/agent/decisionValidator.d.ts +1 -0
  10. package/dist/agent/decisionValidator.js +1 -1
  11. package/dist/agent/engine.d.ts +3 -1
  12. package/dist/agent/engine.js +3 -1
  13. package/dist/agent/futuresCloseOutcome.d.ts +19 -0
  14. package/dist/agent/futuresCloseOutcome.js +54 -0
  15. package/dist/agent/futuresEligibility.d.ts +13 -0
  16. package/dist/agent/futuresEligibility.js +43 -5
  17. package/dist/agent/observe.d.ts +2 -1
  18. package/dist/agent/observe.js +1 -1
  19. package/dist/agent/pmPaperCandidates.d.ts +17 -0
  20. package/dist/agent/pmPaperCandidates.js +125 -0
  21. package/dist/agent/pmPaperCycle.d.ts +16 -0
  22. package/dist/agent/pmPaperCycle.js +372 -0
  23. package/dist/agent/pmPaperDecision.d.ts +187 -0
  24. package/dist/agent/pmPaperDecision.js +101 -0
  25. package/dist/agent/pmPaperPolicy.d.ts +71 -0
  26. package/dist/agent/pmPaperPolicy.js +46 -0
  27. package/dist/agent/prompt.d.ts +12 -1
  28. package/dist/agent/prompt.js +90 -3
  29. package/dist/agent/providerCapabilities.d.ts +1 -0
  30. package/dist/agent/providerCapabilities.js +4 -1
  31. package/dist/agent/providers.d.ts +1 -0
  32. package/dist/agent/providers.js +9 -4
  33. package/dist/agent/runner.d.ts +9 -1
  34. package/dist/agent/runner.js +152 -24
  35. package/dist/agent/skill.js +9 -0
  36. package/dist/agent/skillValidator.js +23 -0
  37. package/dist/agent/strictLint.js +1 -0
  38. package/dist/agent/templates.js +30 -30
  39. package/dist/agent/types.d.ts +31 -0
  40. package/dist/agent/version.d.ts +1 -1
  41. package/dist/agent/version.js +1 -1
  42. package/dist/client.d.ts +14 -0
  43. package/dist/client.js +31 -1
  44. package/dist/http.js +1 -1
  45. package/dist/pmPaperV2.d.ts +81 -0
  46. package/dist/pmPaperV2.js +53 -0
  47. package/dist/tools.js +106 -14
  48. package/package.json +100 -98
@@ -1,6 +1,15 @@
1
- import type { AgentSpec, CapitalBook, CapitalSizingAdjustment, Observation, ProposedAction, QuoteEvidence } from "./types.js";
1
+ import type { AgentSpec, CapitalBook, CapitalSizingPolicy, CapitalSizingAdjustment, Observation, ProposedAction, QuoteEvidence } from "./types.js";
2
2
  export declare const CAPITAL_VALUATION_BASIS = "wallet_assets_spot_marked_futures_pm_at_collateral";
3
3
  export declare const CAPITAL_FEE_BUFFER_BPS = 10;
4
+ /** The ticket and stop-risk limits for one action. Owner 2026-10-08: a
5
+ * risk-taker may size a futures entry it rates at or above
6
+ * highConviction.minConfidence far above its base ticket. Only futures_open
7
+ * qualifies; PM and spot sizing never read confidence. */
8
+ export declare function capitalLimitsFor(policy: CapitalSizingPolicy, action: ProposedAction): {
9
+ perTicketCapitalPct: number;
10
+ futuresRiskPct: number;
11
+ highConviction: boolean;
12
+ };
4
13
  /** Reconcile the bounded open-position reads against the wallet's frozen
5
14
  * buckets. A full page is not assumed complete: the collateral checksums must
6
15
  * agree. No held mark, unknown status, missing bucket, or mismatched book can
@@ -16,6 +16,28 @@ const FROZEN_RESIDUE_TOLERANCE = 1e-8;
16
16
  const centsDown = (n) => Math.floor(n * 100) / 100;
17
17
  const positive = (n) => asNum(n) !== undefined && n > 0;
18
18
  const nonnegative = (n) => asNum(n) !== undefined && n >= 0;
19
+ /** The ticket and stop-risk limits for one action. Owner 2026-10-08: a
20
+ * risk-taker may size a futures entry it rates at or above
21
+ * highConviction.minConfidence far above its base ticket. Only futures_open
22
+ * qualifies; PM and spot sizing never read confidence. */
23
+ export function capitalLimitsFor(policy, action) {
24
+ const hc = policy.highConviction;
25
+ if (hc &&
26
+ action.type === "futures_open" &&
27
+ typeof action.confidence === "number" &&
28
+ Number.isFinite(action.confidence) &&
29
+ action.confidence >= hc.minConfidence)
30
+ return {
31
+ perTicketCapitalPct: hc.perTicketCapitalPct,
32
+ futuresRiskPct: hc.futuresRiskPct,
33
+ highConviction: true,
34
+ };
35
+ return {
36
+ perTicketCapitalPct: policy.perTicketCapitalPct,
37
+ futuresRiskPct: policy.futuresRiskPct,
38
+ highConviction: false,
39
+ };
40
+ }
19
41
  /** Reconcile the bounded open-position reads against the wallet's frozen
20
42
  * buckets. A full page is not assumed complete: the collateral checksums must
21
43
  * agree. No held mark, unknown status, missing bucket, or mismatched book can
@@ -119,6 +141,7 @@ export function prepareCapitalAction(action, spec, observation, budget) {
119
141
  if (validateCapitalSizingPolicy(spec.capitalSizing).length > 0)
120
142
  return { action, rejection: "capital_policy_invalid" };
121
143
  const policy = spec.capitalSizing;
144
+ const limits = capitalLimitsFor(policy, action);
122
145
  const adjustment = {
123
146
  version: policy.version,
124
147
  basis: "owned_collateral_spot_marked_negative_position_marks_only",
@@ -134,7 +157,7 @@ export function prepareCapitalAction(action, spec, observation, budget) {
134
157
  !nonnegative(budget.openMarginMusd))
135
158
  return reject("capital_budget_unavailable");
136
159
  adjustment.conservativeEquityMusd = equity;
137
- const ticket = Math.min(spec.risk.perTradeMarginMusd, (equity * policy.perTicketCapitalPct) / 100);
160
+ const ticket = Math.min(spec.risk.perTradeMarginMusd, (equity * limits.perTicketCapitalPct) / 100);
138
161
  const room = Math.min(ticket, (equity * policy.totalCapitalPct) / 100 - budget.committedCapitalMusd, budget.cashAvailableMusd - (equity * policy.cashReservePct) / 100);
139
162
  if (!positive(room))
140
163
  return reject("capital_allocation_or_reserve_exhausted");
@@ -161,7 +184,7 @@ export function prepareCapitalAction(action, spec, observation, budget) {
161
184
  const distance = Math.abs(mark - stop) / mark;
162
185
  const fee = CAPITAL_FEE_BUFFER_BPS / 10_000;
163
186
  const riskPerMargin = action.leverage * (distance + fee * (1 + stop / mark));
164
- const riskBudget = (equity * policy.futuresRiskPct) / 100;
187
+ const riskBudget = (equity * limits.futuresRiskPct) / 100;
165
188
  const margin = centsDown(Math.min(ticket, riskBudget / riskPerMargin, spec.limits.maxOpenMarginMusd - budget.openMarginMusd, room / (1 + action.leverage * fee)));
166
189
  Object.assign(adjustment, {
167
190
  proposedAmountMusd: action.marginMusd,
@@ -188,6 +211,7 @@ export function validateCapitalAction(action, spec, observation, budget, quote)
188
211
  !nonnegative(budget.committedCapitalMusd) ||
189
212
  !nonnegative(budget.openMarginMusd))
190
213
  return "capital_budget_unavailable";
214
+ const limits = capitalLimitsFor(p, action);
191
215
  let cost, allocated;
192
216
  if (action.type === "pm_open") {
193
217
  cost = allocated = action.stakeMusd;
@@ -227,7 +251,7 @@ export function validateCapitalAction(action, spec, observation, budget, quote)
227
251
  const reward = (notional * favorable) / entry -
228
252
  entryFee -
229
253
  ((notional * target) / entry) * feeRate;
230
- if (risk > (equity * p.futuresRiskPct) / 100 + 1e-8)
254
+ if (risk > (equity * limits.futuresRiskPct) / 100 + 1e-8)
231
255
  return "capital_quote_stop_risk_exceeded";
232
256
  if (reward / risk + 1e-8 < p.minRewardRisk)
233
257
  return "capital_quote_reward_risk_too_low";
@@ -235,7 +259,7 @@ export function validateCapitalAction(action, spec, observation, budget, quote)
235
259
  if (!positive(cost) || !positive(allocated))
236
260
  return "capital_quote_cost_evidence_missing";
237
261
  if (cost >
238
- Math.min(spec.risk.perTradeMarginMusd, (equity * p.perTicketCapitalPct) / 100) +
262
+ Math.min(spec.risk.perTradeMarginMusd, (equity * limits.perTicketCapitalPct) / 100) +
239
263
  1e-8)
240
264
  return "capital_ticket_cap_exceeded";
241
265
  if (budget.committedCapitalMusd + cost >
@@ -1,3 +1,4 @@
1
+ import { type PmPaperOpen, type PmPaperClose } from "../pmPaperV2.js";
1
2
  import { AgentTrace, ApiResult } from "./types.js";
2
3
  export declare const DEFAULT_BASE_URL = "https://api.coinrithm.com";
3
4
  export declare const DEFAULT_REQUEST_TIMEOUT_MS = 30000;
@@ -86,6 +87,10 @@ export declare class CoinRithmClient {
86
87
  side: string;
87
88
  quantity: number;
88
89
  }, trace?: AgentTrace): Promise<ApiResult>;
90
+ discoverPmPaperV2(query?: {
91
+ q?: string;
92
+ limit?: number;
93
+ }, trace?: AgentTrace): Promise<ApiResult>;
89
94
  discoverPmMarkets(query?: {
90
95
  q?: string;
91
96
  source?: string;
@@ -143,6 +148,14 @@ export declare class CoinRithmClient {
143
148
  agentTrace?: AgentTrace;
144
149
  }): Promise<ApiResult>;
145
150
  cancelSpotOrder(orderId: number, idempotencyKey?: string, trace?: AgentTrace): Promise<ApiResult>;
151
+ openPmPaperV2(body: PmPaperOpen): Promise<ApiResult>;
152
+ replayPmPaperV2Open(body: PmPaperOpen): Promise<ApiResult>;
153
+ closePmPaperV2(body: PmPaperClose): Promise<ApiResult>;
154
+ pmPaperV2Positions(query?: {
155
+ status?: "open";
156
+ beforeId?: number;
157
+ limit?: number;
158
+ }): Promise<ApiResult>;
146
159
  openPmPosition(body: {
147
160
  source: string;
148
161
  slug: string;
@@ -1,9 +1,4 @@
1
- // Thin CoinRithm agent-API client for the runner (futures-focused v1).
2
- //
3
- // Auth: the user's own crk_live_ key from COINRITHM_API_KEY (env only — never
4
- // from an agent file). 429 backs off on Retry-After; 401/403/409/422 are
5
- // FAIL-CLOSED cycle outcomes (returned, not retried). fetch + sleep are
6
- // injectable so tests run with no network and no real waits.
1
+ import { PM_PAPER_V2_BASE, } from "../pmPaperV2.js";
7
2
  import { createHash } from "node:crypto";
8
3
  import { setTimeout as sleep } from "node:timers/promises";
9
4
  import { retryAfterSeconds } from "../retryAfter.js";
@@ -328,6 +323,9 @@ export class CoinRithmClient {
328
323
  });
329
324
  }
330
325
  // ── prediction markets ───────────────────────────────────────────────────
326
+ discoverPmPaperV2(query, trace) {
327
+ return this.request("GET", "/api/agent/pm/v2/discover", { query, trace });
328
+ }
331
329
  discoverPmMarkets(query, trace) {
332
330
  return this.request("GET", "/api/agent/pm/discover", { query, trace });
333
331
  }
@@ -367,6 +365,19 @@ export class CoinRithmClient {
367
365
  trace,
368
366
  });
369
367
  }
368
+ // Explicit opt-in contract; API derives and authorizes the configured house tuple.
369
+ openPmPaperV2(body) {
370
+ return this.request("POST", `${PM_PAPER_V2_BASE}/open`, { body });
371
+ }
372
+ replayPmPaperV2Open(body) {
373
+ return this.request("POST", `${PM_PAPER_V2_BASE}/open/replay`, { body });
374
+ }
375
+ closePmPaperV2(body) {
376
+ return this.request("POST", `${PM_PAPER_V2_BASE}/close`, { body });
377
+ }
378
+ pmPaperV2Positions(query) {
379
+ return this.request("GET", `${PM_PAPER_V2_BASE}/positions`, { query });
380
+ }
370
381
  openPmPosition(body) {
371
382
  return this.request("POST", "/api/agent/pm/open", { body });
372
383
  }
@@ -13,24 +13,24 @@ export declare const actionSchema: z.ZodDiscriminatedUnion<"type", [z.ZodObject<
13
13
  thesis: z.ZodOptional<z.ZodEffects<z.ZodAny, import("./types.js").Thesis | undefined, any>>;
14
14
  }, "strict", z.ZodTypeAny, {
15
15
  symbol: string;
16
- side: "long" | "short";
17
16
  type: "futures_open";
17
+ side: "long" | "short";
18
+ thesis?: import("./types.js").Thesis | undefined;
18
19
  leverage?: any;
19
20
  marginMusd?: any;
20
21
  stopLossPrice?: any;
21
22
  takeProfitPrice?: any;
22
- thesis?: import("./types.js").Thesis | undefined;
23
23
  confidence?: any;
24
24
  rationaleSummary?: string | undefined;
25
25
  }, {
26
26
  symbol: string;
27
- side: "long" | "short";
28
27
  type: "futures_open";
28
+ side: "long" | "short";
29
+ thesis?: any;
29
30
  leverage?: unknown;
30
31
  marginMusd?: unknown;
31
32
  stopLossPrice?: unknown;
32
33
  takeProfitPrice?: unknown;
33
- thesis?: any;
34
34
  confidence?: unknown;
35
35
  rationaleSummary?: string | undefined;
36
36
  }>, z.ZodObject<{
@@ -42,16 +42,16 @@ export declare const actionSchema: z.ZodDiscriminatedUnion<"type", [z.ZodObject<
42
42
  thesis: z.ZodOptional<z.ZodEffects<z.ZodAny, import("./types.js").Thesis | undefined, any>>;
43
43
  }, "strict", z.ZodTypeAny, {
44
44
  type: "futures_close";
45
+ thesis?: import("./types.js").Thesis | undefined;
45
46
  positionId?: any;
46
47
  fraction?: any;
47
- thesis?: import("./types.js").Thesis | undefined;
48
48
  confidence?: any;
49
49
  rationaleSummary?: string | undefined;
50
50
  }, {
51
51
  type: "futures_close";
52
+ thesis?: any;
52
53
  positionId?: unknown;
53
54
  fraction?: unknown;
54
- thesis?: any;
55
55
  confidence?: unknown;
56
56
  rationaleSummary?: string | undefined;
57
57
  }>, z.ZodObject<{
@@ -64,18 +64,18 @@ export declare const actionSchema: z.ZodDiscriminatedUnion<"type", [z.ZodObject<
64
64
  thesis: z.ZodOptional<z.ZodEffects<z.ZodAny, import("./types.js").Thesis | undefined, any>>;
65
65
  }, "strict", z.ZodTypeAny, {
66
66
  type: "futures_set_sltp";
67
+ thesis?: import("./types.js").Thesis | undefined;
68
+ positionId?: any;
67
69
  stopLossPrice?: any;
68
70
  takeProfitPrice?: any;
69
- positionId?: any;
70
- thesis?: import("./types.js").Thesis | undefined;
71
71
  confidence?: any;
72
72
  rationaleSummary?: string | undefined;
73
73
  }, {
74
74
  type: "futures_set_sltp";
75
+ thesis?: any;
76
+ positionId?: unknown;
75
77
  stopLossPrice?: unknown;
76
78
  takeProfitPrice?: unknown;
77
- positionId?: unknown;
78
- thesis?: any;
79
79
  confidence?: unknown;
80
80
  rationaleSummary?: string | undefined;
81
81
  }>, z.ZodObject<{
@@ -91,24 +91,24 @@ export declare const actionSchema: z.ZodDiscriminatedUnion<"type", [z.ZodObject<
91
91
  thesis: z.ZodOptional<z.ZodEffects<z.ZodAny, import("./types.js").Thesis | undefined, any>>;
92
92
  }, "strict", z.ZodTypeAny, {
93
93
  symbol: string;
94
+ type: "spot_order";
94
95
  side: "buy" | "sell";
95
96
  orderType: "limit" | "market" | "stop";
96
- type: "spot_order";
97
+ thesis?: import("./types.js").Thesis | undefined;
97
98
  quantity?: any;
98
99
  limitPrice?: any;
99
100
  stopPrice?: any;
100
- thesis?: import("./types.js").Thesis | undefined;
101
101
  confidence?: any;
102
102
  rationaleSummary?: string | undefined;
103
103
  }, {
104
104
  symbol: string;
105
+ type: "spot_order";
105
106
  side: "buy" | "sell";
106
107
  orderType: "limit" | "market" | "stop";
107
- type: "spot_order";
108
+ thesis?: any;
108
109
  quantity?: unknown;
109
110
  limitPrice?: unknown;
110
111
  stopPrice?: unknown;
111
- thesis?: any;
112
112
  confidence?: unknown;
113
113
  rationaleSummary?: string | undefined;
114
114
  }>, z.ZodObject<{
@@ -145,9 +145,9 @@ export declare const actionSchema: z.ZodDiscriminatedUnion<"type", [z.ZodObject<
145
145
  source?: string | undefined;
146
146
  slug?: string | undefined;
147
147
  outcomeExternalMarketId?: string | undefined;
148
- stakeMusd?: any;
149
148
  forecastProbability?: number | undefined;
150
149
  thesis?: import("./types.js").Thesis | undefined;
150
+ stakeMusd?: any;
151
151
  confidence?: any;
152
152
  rationaleSummary?: string | undefined;
153
153
  ref?: string | undefined;
@@ -156,9 +156,9 @@ export declare const actionSchema: z.ZodDiscriminatedUnion<"type", [z.ZodObject<
156
156
  source?: string | undefined;
157
157
  slug?: string | undefined;
158
158
  outcomeExternalMarketId?: string | undefined;
159
- stakeMusd?: unknown;
160
159
  forecastProbability?: any;
161
160
  thesis?: any;
161
+ stakeMusd?: unknown;
162
162
  confidence?: unknown;
163
163
  rationaleSummary?: string | undefined;
164
164
  ref?: string | undefined;
@@ -548,3 +548,8 @@ export type ParseDecisionResult = {
548
548
  actionsStringDiagnostic?: ActionsStringDiagnostic;
549
549
  };
550
550
  export declare function parseDecision(text: string): ParseDecisionResult;
551
+ /** Only the trusted host selects a versioned contract; default stays legacy. */
552
+ export declare function parseDecisionForContract(text: string, contract?: "pm_paper_v2"): ParseDecisionResult | {
553
+ ok: true;
554
+ decision: z.infer<typeof import("./pmPaperDecision.js").pmPaperDecisionSchema>;
555
+ };
@@ -1,3 +1,4 @@
1
+ import { parsePmPaperDecision } from "./pmPaperDecision.js";
1
2
  // Parse the model's single text response into a strict, structured Decision.
2
3
  // Accepts futures + spot + prediction-market actions. Anything else — invalid
3
4
  // JSON, an unknown action type, a free-form endpoint/tool name, extra unknown
@@ -479,3 +480,9 @@ export function parseDecision(text) {
479
480
  },
480
481
  };
481
482
  }
483
+ /** Only the trusted host selects a versioned contract; default stays legacy. */
484
+ export function parseDecisionForContract(text, contract) {
485
+ return contract === "pm_paper_v2"
486
+ ? parsePmPaperDecision(text)
487
+ : parseDecision(text);
488
+ }
@@ -51,6 +51,7 @@ export declare function pmCoinHorizonOf(item: {
51
51
  }, refMs: number): PmCoinHorizon | null;
52
52
  /** Event key shared by pm_open actions, held positions and markets. */
53
53
  export declare function pmEventKey(source: string, slug: string): string;
54
+ export declare const TRIGGER_PRICE_EPS = 1e-8;
54
55
  /**
55
56
  * True when a thesis says, in so many words, that it is betting AGAINST the
56
57
  * outcome the action is buying. Deliberately narrow: it fires only when the
@@ -60,7 +60,7 @@ const PM_MIN_STAKE_MUSD = 10; // server minimum prediction-market stake
60
60
  // Match backend-v2 validateSlTpPrices, including its boundary tolerance. This
61
61
  // preflight uses observed evidence only; the API still checks a fresh mark and
62
62
  // locked position at execution. Older observations can omit these prices.
63
- const TRIGGER_PRICE_EPS = 1e-8;
63
+ export const TRIGGER_PRICE_EPS = 1e-8;
64
64
  function validateTriggerUpdate(action, pos) {
65
65
  // executeAction omits null as well as undefined; neither clears a trigger in
66
66
  // this runner. Check the resulting state, including retained protection.
@@ -2,7 +2,7 @@ export { runCycle, type RunnerDeps } from "./runner.js";
2
2
  export { buildAgentDefinitionSnapshot, type AgentDefinitionSnapshot, } from "./definitionSnapshot.js";
3
3
  export { DECISION_INPUT_MAX_BYTES, sanitizeDecisionInputRecord, type DecisionInputRecord, } from "./decisionReceipt.js";
4
4
  export { selectProvider, providerForRoute, classifyProviderFailure, anthropicUsage, type ProviderUsage, type ProviderEnv, type ProviderRouteOptions, type Provider, type DecideInput, type DecideResult, type DecideRouteAttempt, type DecideRouteMeta, } from "./providers.js";
5
- export { parseDecision, ACTIONS_STRING_DIAGNOSTICS, type ActionsStringDiagnostic, } from "./decision.js";
5
+ export { parseDecision, parseDecisionForContract, ACTIONS_STRING_DIAGNOSTICS, type ActionsStringDiagnostic, } from "./decision.js";
6
6
  export { chatShapeFor, buildChatBody, canRetrySuperToolOutput, type ChatShape, } from "./providerCapabilities.js";
7
7
  export { probeDecisionContract, type ProbeRoute, type ProbeDecisionResult, } from "./decisionProbe.js";
8
8
  export { CoinRithmClient } from "./client.js";
@@ -14,3 +14,5 @@ export { makeRunId } from "./runEvidence.js";
14
14
  export { parseCadenceMs } from "./util.js";
15
15
  export { BENCHMARK_AGENTS, BENCHMARK_STRATEGIES, decideMechanical, isBenchmarkStrategy, type BenchmarkStrategy, type BenchmarkAgentDefinition, } from "./mechanical.js";
16
16
  export type { AgentSpec, RunState, CycleResult, OpportunityReport, PostedOpportunity, PlannedAction, Venue, ProviderName, ModelConfig, } from "./types.js";
17
+ export { readPmHousePolicies, selectPmHousePolicy, type PmHousePolicy, } from "./pmPaperPolicy.js";
18
+ export { runPmPaperCycle, type PmPaperCycleResult } from "./pmPaperCycle.js";
@@ -9,7 +9,7 @@ export { runCycle } from "./runner.js";
9
9
  export { buildAgentDefinitionSnapshot, } from "./definitionSnapshot.js";
10
10
  export { DECISION_INPUT_MAX_BYTES, sanitizeDecisionInputRecord, } from "./decisionReceipt.js";
11
11
  export { selectProvider, providerForRoute, classifyProviderFailure, anthropicUsage, } from "./providers.js";
12
- export { parseDecision, ACTIONS_STRING_DIAGNOSTICS, } from "./decision.js";
12
+ export { parseDecision, parseDecisionForContract, ACTIONS_STRING_DIAGNOSTICS, } from "./decision.js";
13
13
  // Reliability slice A: the declarative request-capability table and the
14
14
  // representative decision probe (route eligibility = a REAL parsed decision,
15
15
  // never a bare HTTP 200 — the 62f3a12 lesson).
@@ -25,3 +25,5 @@ export { parseCadenceMs } from "./util.js";
25
25
  // Mechanical BENCHMARK baseline agents (sol #7): the deterministic, non-LLM
26
26
  // reference line the scheduler seeds into agent_runtime and the runner executes.
27
27
  export { BENCHMARK_AGENTS, BENCHMARK_STRATEGIES, decideMechanical, isBenchmarkStrategy, } from "./mechanical.js";
28
+ export { readPmHousePolicies, selectPmHousePolicy, } from "./pmPaperPolicy.js";
29
+ export { runPmPaperCycle } from "./pmPaperCycle.js";
@@ -0,0 +1,19 @@
1
+ import type { ApiResult, ProposedAction } from "./types.js";
2
+ /** Future v2 producer contract; legacy responses retain their existing res.ok
3
+ * meaning. A filled exit closes exposure, even while accounting remains pending. */
4
+ export declare function executionOutcome(action: ProposedAction, result: ApiResult): {
5
+ executed: boolean;
6
+ pending: boolean;
7
+ newWrite: boolean;
8
+ v2: boolean;
9
+ replayed: boolean;
10
+ };
11
+ export declare function exitOutcomeMetadata(outcome: ReturnType<typeof executionOutcome>): {
12
+ executionPending: boolean;
13
+ executionReplayed: boolean;
14
+ writeRecorded: boolean;
15
+ } | {
16
+ executionPending?: undefined;
17
+ executionReplayed?: undefined;
18
+ writeRecorded?: undefined;
19
+ };
@@ -0,0 +1,54 @@
1
+ import { asObj } from "./extract.js";
2
+ /** Future v2 producer contract; legacy responses retain their existing res.ok
3
+ * meaning. A filled exit closes exposure, even while accounting remains pending. */
4
+ export function executionOutcome(action, result) {
5
+ const body = asObj(result.data);
6
+ if (action.type !== "futures_close" ||
7
+ body.executionModel !== "hl_paper_v2") {
8
+ return {
9
+ executed: result.ok,
10
+ pending: false,
11
+ newWrite: result.ok,
12
+ v2: false,
13
+ replayed: false,
14
+ };
15
+ }
16
+ const intent = asObj(body.intent);
17
+ const common = result.ok &&
18
+ Number.isSafeInteger(body.positionId) &&
19
+ body.positionId === action.positionId &&
20
+ (action.fraction === undefined || action.fraction === 1) &&
21
+ body.accepted === true &&
22
+ typeof body.replayed === "boolean" &&
23
+ typeof intent.id === "string" &&
24
+ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(intent.id);
25
+ const pending = !!common &&
26
+ result.status === 202 &&
27
+ body.executed === false &&
28
+ typeof intent.status === "string" &&
29
+ typeof body.accountingStatus === "string" &&
30
+ ["pending", "leased", "retry", "manual_attention"].includes(intent.status) &&
31
+ ["open", "pending", "manual_attention"].includes(body.accountingStatus);
32
+ const executed = !!common &&
33
+ result.status === 200 &&
34
+ body.executed === true &&
35
+ intent.status === "filled" &&
36
+ typeof body.accountingStatus === "string" &&
37
+ ["pending", "settled", "manual_attention"].includes(body.accountingStatus);
38
+ return {
39
+ executed,
40
+ pending,
41
+ newWrite: (executed || pending) && body.replayed === false,
42
+ v2: true,
43
+ replayed: body.replayed === true,
44
+ };
45
+ }
46
+ export function exitOutcomeMetadata(outcome) {
47
+ return outcome.v2
48
+ ? {
49
+ executionPending: outcome.pending,
50
+ executionReplayed: outcome.replayed,
51
+ writeRecorded: outcome.newWrite,
52
+ }
53
+ : {};
54
+ }
@@ -34,3 +34,16 @@ export interface FuturesEntryPreflightRejection {
34
34
  * - referenceRequired false.
35
35
  */
36
36
  export declare function futuresEntryPreflight(action: ProposedAction, observation: Observation): FuturesEntryPreflightRejection | null;
37
+ /** Prompt-only choices from the SAME reference preflight, not trade approval.
38
+ * Keep unknown evidence and held-position exceptions distinct from eligibility.
39
+ * Watch rows, setup receipts and all execution/risk checks remain unchanged. */
40
+ export declare function futuresEntryChoices(observation: Observation): {
41
+ candidates: Array<{
42
+ symbol: string;
43
+ reference: "eligible" | "unknown" | "not_required" | "held_position";
44
+ }>;
45
+ blocked: Array<{
46
+ symbol: string;
47
+ code: FuturesEntryPreflightRejection["code"];
48
+ }>;
49
+ };
@@ -66,11 +66,17 @@ export function futuresEntryEligibilityOf(marketContext) {
66
66
  export function futuresEntryPreflight(action, observation) {
67
67
  if (action.type !== "futures_open")
68
68
  return null;
69
- const base = baseSymbol(action.symbol);
70
- const holdsCoin = (observation.openPositions ?? []).some((p) => p.venue === "futures" &&
69
+ return futuresReferenceBlock(action.symbol, observation);
70
+ }
71
+ function holdsFutures(symbol, observation) {
72
+ const base = baseSymbol(symbol);
73
+ return (observation.openPositions ?? []).some((p) => p.venue === "futures" &&
71
74
  (p.status ?? "open") === "open" &&
72
75
  baseSymbol(p.symbol) === base);
73
- if (holdsCoin)
76
+ }
77
+ function futuresReferenceBlock(symbol, observation) {
78
+ const base = baseSymbol(symbol);
79
+ if (holdsFutures(symbol, observation))
74
80
  return null;
75
81
  const entry = observation.watch.find((w) => baseSymbol(w.symbol) === base);
76
82
  const e = entry?.futuresEntryEligibility;
@@ -80,11 +86,43 @@ export function futuresEntryPreflight(action, observation) {
80
86
  if (e.status === "reference_stale") {
81
87
  return {
82
88
  code: "futures_reference_stale",
83
- reason: `${action.symbol}: the perpetual reference${e.venue ? ` (${e.venue})` : ""} is stale or unusable for a NEW futures open${when} (too old, or its refresh time is missing, invalid or in the future); the server would refuse it (perpetual_reference_stale). Trade another coin or wait for a fresh reference.`,
89
+ reason: `${symbol}: the perpetual reference${e.venue ? ` (${e.venue})` : ""} is stale or unusable for a NEW futures open${when} (too old, or its refresh time is missing, invalid or in the future); the server would refuse it (perpetual_reference_stale). Trade another coin or wait for a fresh reference.`,
84
90
  };
85
91
  }
86
92
  return {
87
93
  code: "futures_reference_unavailable",
88
- reason: `${action.symbol}: CoinRithm has no supported perpetual reference for this coin${when}, so the server refuses a NEW futures open (perpetual_reference_unavailable). Use spot for it, or another coin for futures.`,
94
+ reason: `${symbol}: CoinRithm has no supported perpetual reference for this coin${when}, so the server refuses a NEW futures open (perpetual_reference_unavailable). Use spot for it, or another coin for futures.`,
89
95
  };
90
96
  }
97
+ /** Prompt-only choices from the SAME reference preflight, not trade approval.
98
+ * Keep unknown evidence and held-position exceptions distinct from eligibility.
99
+ * Watch rows, setup receipts and all execution/risk checks remain unchanged. */
100
+ export function futuresEntryChoices(observation) {
101
+ const candidates = [];
102
+ const blocked = [];
103
+ const seen = new Set();
104
+ for (const entry of observation.watch) {
105
+ const base = baseSymbol(entry.symbol);
106
+ if (seen.has(base))
107
+ continue;
108
+ seen.add(base);
109
+ const rejection = futuresReferenceBlock(entry.symbol, observation);
110
+ if (rejection) {
111
+ blocked.push({ symbol: entry.symbol, code: rejection.code });
112
+ }
113
+ else {
114
+ const e = entry.futuresEntryEligibility;
115
+ candidates.push({
116
+ symbol: entry.symbol,
117
+ reference: holdsFutures(entry.symbol, observation)
118
+ ? "held_position"
119
+ : !e
120
+ ? "unknown"
121
+ : !e.referenceRequired
122
+ ? "not_required"
123
+ : "eligible",
124
+ });
125
+ }
126
+ }
127
+ return { candidates, blocked };
128
+ }
@@ -1,5 +1,5 @@
1
1
  import { CoinRithmClient } from "./client.js";
2
- import { AgentSpec, RunState, Observation, OpenInterestContext, PositioningContext, LiquidationContext, MacroContext, ChainTvlContext, StablecoinSupplyContext, DepthContext, FundingByVenueContext, AgentTrace, PmCalibration, WhaleContext } from "./types.js";
2
+ import { AgentSpec, RunState, Observation, PmMarket, OpenInterestContext, PositioningContext, LiquidationContext, MacroContext, ChainTvlContext, StablecoinSupplyContext, DepthContext, FundingByVenueContext, AgentTrace, PmCalibration, WhaleContext } from "./types.js";
3
3
  export interface ObserveOutput {
4
4
  observation: Observation;
5
5
  skip?: string;
@@ -34,6 +34,7 @@ export declare function depthOf(m: Record<string, unknown>, nowMs?: number): Dep
34
34
  export declare function chainTvlOf(m: Record<string, unknown>, nowMs?: number): ChainTvlContext | undefined;
35
35
  export declare function stablecoinSupplyOf(m: Record<string, unknown>, nowMs?: number): StablecoinSupplyContext | undefined;
36
36
  export declare function macroOf(m: Record<string, unknown>, nowMs?: number): MacroContext | undefined;
37
+ export declare function expandPmMarkets(discData: unknown, heldPmKeys: Set<string>): Omit<PmMarket, "ref">[];
37
38
  /** Bench-only observation overrides; live callers pass none. */
38
39
  export interface ObserveOptions {
39
40
  /** Read the curated PM board (depth 30, churn removed, calibration read)
@@ -967,7 +967,7 @@ function titleMentionsCoin(title, symbol) {
967
967
  // backend flagged not-openable (eligible === false) and markets the agent already
968
968
  // holds (heldPmKeys). Shared by the primary board fetch and the crypto-targeted
969
969
  // secondary fetch so both go through the exact same filters.
970
- function expandPmMarkets(discData, heldPmKeys) {
970
+ export function expandPmMarkets(discData, heldPmKeys) {
971
971
  const dd = asObj(discData);
972
972
  return (asArr(dd.data ?? dd.markets ?? dd.results)
973
973
  .map(asObj)
@@ -0,0 +1,17 @@
1
+ /** Immutable entry identity excludes aliases and the opposite order side.
2
+ * Unknown identities hold new entries, but do not block closing/replaying. */
3
+ export declare function pmHeldNativeMarkets(positions: readonly unknown[]): {
4
+ keys: Set<string>;
5
+ complete: boolean;
6
+ };
7
+ export declare function pmCandidateWithinHorizon(candidate: {
8
+ nativeEndAt: string | null;
9
+ }, maxEndDays: number | undefined, nowMs: number): boolean;
10
+ /** Dedicated v2 discovery only. Filter and deduplicate before the model cap;
11
+ * the legacy expansion and legacy agent discovery remain unchanged. */
12
+ export declare function pmPaperCandidates(payload: unknown, held: ReadonlySet<string>, maxEndDays: number | undefined, nowMs: number): (Omit<import("./types.js").PmMarket, "ref"> & {
13
+ nativeMarketKey: string;
14
+ nativeEndAt: string | null;
15
+ nativeEndBasis: "gamma_market_endDate" | "kalshi_market_close_time";
16
+ settlementTimeKnown: false;
17
+ })[];