@coinrithm/mcp-trading 0.7.3 → 0.7.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +197 -158
- package/README.md +279 -240
- package/dist/agent/cli.js +9 -5
- package/dist/agent/client.js +4 -0
- package/dist/agent/indicators.d.ts +1 -0
- package/dist/agent/indicators.js +3 -0
- package/dist/agent/manifest.d.ts +1 -0
- package/dist/agent/manifest.js +3 -0
- package/dist/agent/observationReceipt.d.ts +6 -0
- package/dist/agent/observationReceipt.js +10 -0
- package/dist/agent/observe.d.ts +4 -0
- package/dist/agent/observe.js +23 -4
- package/dist/agent/runner.js +28 -8
- package/dist/agent/types.d.ts +4 -0
- package/dist/client.d.ts +22 -0
- package/dist/client.js +24 -0
- package/dist/http.js +3 -1
- package/dist/tools.d.ts +22 -0
- package/dist/tools.js +617 -16
- package/package.json +1 -1
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// A compact, privacy-safe receipt for the exact structured observation used by
|
|
2
|
+
// one decision. We persist the digest, never the full prompt or model output.
|
|
3
|
+
import { INDICATOR_VERSION } from "./indicators.js";
|
|
4
|
+
import { sha256, stableStringify } from "./util.js";
|
|
5
|
+
export function buildObservationReceipt(observation) {
|
|
6
|
+
return {
|
|
7
|
+
observationHash: sha256(stableStringify(observation)),
|
|
8
|
+
indicatorVersion: INDICATOR_VERSION,
|
|
9
|
+
};
|
|
10
|
+
}
|
package/dist/agent/observe.d.ts
CHANGED
|
@@ -4,4 +4,8 @@ export interface ObserveOutput {
|
|
|
4
4
|
observation: Observation;
|
|
5
5
|
skip?: string;
|
|
6
6
|
}
|
|
7
|
+
export declare function isCalibrationChurnMarket(market: {
|
|
8
|
+
slug?: string;
|
|
9
|
+
title?: string;
|
|
10
|
+
}): boolean;
|
|
7
11
|
export declare function observe(client: CoinRithmClient, spec: AgentSpec, state: RunState, trace?: AgentTrace): Promise<ObserveOutput>;
|
package/dist/agent/observe.js
CHANGED
|
@@ -28,6 +28,16 @@ const PM_COIN_NAMES = {
|
|
|
28
28
|
UNI: "Uniswap",
|
|
29
29
|
SUI: "Sui",
|
|
30
30
|
};
|
|
31
|
+
// Repeated micro-contracts are useful for execution smoke tests but are a poor
|
|
32
|
+
// calibration universe: outcomes overlap heavily, resolve too quickly to admit
|
|
33
|
+
// meaningful independent research, and drown the public scorecard in Bitcoin
|
|
34
|
+
// coin flips. Non-mechanical calibration agents receive a deeper discovery
|
|
35
|
+
// page with these rows removed. Mechanical baselines intentionally keep the
|
|
36
|
+
// unmodified universe so their reference contract remains reproducible.
|
|
37
|
+
const PM_CALIBRATION_CHURN_RE = /(updown|up-or-down|-5-?min|-5m-|-15m|15m(?:-|$)|(?:5|15)\s+min(?:ute)?s?|-1h-|hourly|-daily-|\bdaily\b|what-price-will[^\n]*(?:today|tomorrow)|-above-on-|-price-on-|this[ -]week|of[ -]the[ -]week|-weekly-)/i;
|
|
38
|
+
export function isCalibrationChurnMarket(market) {
|
|
39
|
+
return PM_CALIBRATION_CHURN_RE.test(`${market.slug ?? ""} ${market.title ?? ""}`);
|
|
40
|
+
}
|
|
31
41
|
// Fetch candles for one coin and reduce them to a compact indicator bundle.
|
|
32
42
|
// Tolerant by design: any failure (HTTP error, malformed/sparse candles) returns
|
|
33
43
|
// null so the cycle proceeds with price-only context rather than skipping.
|
|
@@ -289,6 +299,9 @@ export async function observe(client, spec, state, trace) {
|
|
|
289
299
|
let pmResolutions = [];
|
|
290
300
|
let pmMarkets = [];
|
|
291
301
|
if (wantPm) {
|
|
302
|
+
const curatedCalibrationBoard = spec.objective?.primary === "calibration" &&
|
|
303
|
+
spec.model?.provider !== "mechanical";
|
|
304
|
+
const primaryDiscoveryLimit = curatedCalibrationBoard ? 30 : 12;
|
|
292
305
|
// Bias PM discovery toward CRYPTO markets the agent has a price view on — the
|
|
293
306
|
// only PM edge a price agent reliably has (probed 2026-06-24: the default board
|
|
294
307
|
// is World Cup / elections / F1, which an agent has no edge on). The discover
|
|
@@ -299,7 +312,7 @@ export async function observe(client, spec, state, trace) {
|
|
|
299
312
|
const pmQuery = PM_COIN_NAMES[topCoin] ?? spec.risk.watchlist[0] ?? "Bitcoin";
|
|
300
313
|
const [pmPosR, pmDiscFirst] = await Promise.all([
|
|
301
314
|
client.pmPositions(undefined, trace),
|
|
302
|
-
client.discoverPmMarkets({ q: pmQuery, limit:
|
|
315
|
+
client.discoverPmMarkets({ q: pmQuery, limit: primaryDiscoveryLimit }, trace),
|
|
303
316
|
]);
|
|
304
317
|
let pmDiscR = pmDiscFirst;
|
|
305
318
|
const firstCount = pmDiscR.ok
|
|
@@ -378,6 +391,9 @@ export async function observe(client, spec, state, trace) {
|
|
|
378
391
|
// quoteable id NESTED at outcomes[].externalMarketId — expandPmMarkets turns
|
|
379
392
|
// that into one row per quoteable outcome (eligible + not-held filtered).
|
|
380
393
|
let mergedRows = expandPmMarkets(pmDiscR.data, heldPmKeys);
|
|
394
|
+
if (curatedCalibrationBoard) {
|
|
395
|
+
mergedRows = mergedRows.filter((market) => !isCalibrationChurnMarket(market));
|
|
396
|
+
}
|
|
381
397
|
// ── Crypto-targeted secondary discover (pm_ref hallucination fix) ────────
|
|
382
398
|
// The prompt tells the model its SHARPEST PM edge is the crypto price view it
|
|
383
399
|
// JUST formed — but that is only actionable if the board actually LISTS a
|
|
@@ -409,10 +425,13 @@ export async function observe(client, spec, state, trace) {
|
|
|
409
425
|
// rows that actually reference the targeted coin (a fuzzy backend match
|
|
410
426
|
// can't dilute the board with off-topic events).
|
|
411
427
|
const primaryEventKeys = new Set(mergedRows.map((m) => `${m.source}|${m.slug}`));
|
|
412
|
-
|
|
428
|
+
let secRows = expandPmMarkets(secR.data, heldPmKeys)
|
|
413
429
|
.filter((m) => titleMentionsCoin(m.title, topAnalyzed))
|
|
414
|
-
.filter((m) => !primaryEventKeys.has(`${m.source}|${m.slug}`))
|
|
415
|
-
|
|
430
|
+
.filter((m) => !primaryEventKeys.has(`${m.source}|${m.slug}`));
|
|
431
|
+
if (curatedCalibrationBoard) {
|
|
432
|
+
secRows = secRows.filter((market) => !isCalibrationChurnMarket(market));
|
|
433
|
+
}
|
|
434
|
+
secRows = secRows.slice(0, 4);
|
|
416
435
|
// Reserve slots for the targeted rows so the 12-cap can't slice off the
|
|
417
436
|
// very markets the secondary fetch exists to surface. Primary rows keep
|
|
418
437
|
// priority; the targeted rows are appended.
|
package/dist/agent/runner.js
CHANGED
|
@@ -17,6 +17,7 @@ import { makeDecisionId, makeTrace, exportRunEvidence } from "./runEvidence.js";
|
|
|
17
17
|
import { rollDay, checkKillSwitch, accrueRealized, saveState, } from "./state.js";
|
|
18
18
|
import { asObj, asNum, asStr } from "./extract.js";
|
|
19
19
|
import { parseCadenceMs, sleep } from "./util.js";
|
|
20
|
+
import { buildObservationReceipt } from "./observationReceipt.js";
|
|
20
21
|
// Independent-forecast kill-switch. Default ON: the fleet elicits + submits its
|
|
21
22
|
// OWN forecastProbability on PM opens. Set HOUSE_AGENT_FORECAST_ENABLED to
|
|
22
23
|
// "false"/"0"/"no"/"off" to ship pm/open requests WITHOUT the field, byte-identical
|
|
@@ -371,6 +372,10 @@ export async function runCycle(deps) {
|
|
|
371
372
|
// OBSERVE
|
|
372
373
|
const obs = await observe(client, spec, state, baseTrace);
|
|
373
374
|
const observation = obs.observation;
|
|
375
|
+
const observationReceipt = buildObservationReceipt(observation);
|
|
376
|
+
// Reads build the observation, so its hash cannot exist before they finish.
|
|
377
|
+
// From this point every durable write carries the exact decision-input receipt.
|
|
378
|
+
Object.assign(baseTrace, observationReceipt);
|
|
374
379
|
accrueRealized(state, observation.newClosedTrades);
|
|
375
380
|
state.cursor = observation.syncCursor;
|
|
376
381
|
for (const t of observation.newClosedTrades) {
|
|
@@ -419,13 +424,20 @@ export async function runCycle(deps) {
|
|
|
419
424
|
disabled: true,
|
|
420
425
|
disabledReason: state.disabledReason,
|
|
421
426
|
live,
|
|
427
|
+
...observationReceipt,
|
|
422
428
|
};
|
|
423
429
|
}
|
|
424
430
|
if (obs.skip) {
|
|
425
431
|
state.consecutiveRejectCycles += 1;
|
|
426
432
|
saveState(stateFile, state);
|
|
427
433
|
log(`skip: ${obs.skip}`);
|
|
428
|
-
return {
|
|
434
|
+
return {
|
|
435
|
+
decision: "skip",
|
|
436
|
+
skipReason: obs.skip,
|
|
437
|
+
planned: [],
|
|
438
|
+
live,
|
|
439
|
+
...observationReceipt,
|
|
440
|
+
};
|
|
429
441
|
}
|
|
430
442
|
// GATE (slice 2): only SPEND an LLM call when a deterministic trigger fires — a
|
|
431
443
|
// flagged entry setup or an open position to manage. No trigger => a cheap
|
|
@@ -451,6 +463,7 @@ export async function runCycle(deps) {
|
|
|
451
463
|
estimatedCostUsd: 0,
|
|
452
464
|
writeAttempted: 0,
|
|
453
465
|
writeAccepted: 0,
|
|
466
|
+
...observationReceipt,
|
|
454
467
|
};
|
|
455
468
|
}
|
|
456
469
|
// DECIDE. Two paths share the same downstream validate+act loop:
|
|
@@ -519,6 +532,7 @@ export async function runCycle(deps) {
|
|
|
519
532
|
decisionType: "model_error",
|
|
520
533
|
writeAttempted: 0,
|
|
521
534
|
writeAccepted: 0,
|
|
535
|
+
...observationReceipt,
|
|
522
536
|
};
|
|
523
537
|
}
|
|
524
538
|
const parsed = parseDecision(res.text);
|
|
@@ -539,6 +553,7 @@ export async function runCycle(deps) {
|
|
|
539
553
|
decisionType: "model_error",
|
|
540
554
|
writeAttempted: 0,
|
|
541
555
|
writeAccepted: 0,
|
|
556
|
+
...observationReceipt,
|
|
542
557
|
};
|
|
543
558
|
}
|
|
544
559
|
state.consecutiveModelFailures = 0;
|
|
@@ -578,6 +593,7 @@ export async function runCycle(deps) {
|
|
|
578
593
|
writeAttempted: decision.actions.length,
|
|
579
594
|
writeAccepted: 0,
|
|
580
595
|
...(postedOpportunity ? { opportunity: postedOpportunity } : {}),
|
|
596
|
+
...observationReceipt,
|
|
581
597
|
};
|
|
582
598
|
}
|
|
583
599
|
// VALIDATE (+ ACT when live). Quote evidence is fetched by the runner.
|
|
@@ -778,13 +794,16 @@ export async function runCycle(deps) {
|
|
|
778
794
|
const seq = state.intentSeq[intentKey] ?? 0;
|
|
779
795
|
const idem = `${runId}:${intentKey}:${seq}`;
|
|
780
796
|
const meta = action;
|
|
781
|
-
const trace =
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
797
|
+
const trace = {
|
|
798
|
+
...makeTrace(runId, decisionId, spec, meta.confidence ?? decision.confidence,
|
|
799
|
+
// The trade's "why" on the Arena live floor. Prefer the model's per-action
|
|
800
|
+
// summary; else the decision rationale, but kept HONEST about this action's
|
|
801
|
+
// market (a multi-action decision's rationale can be about a DIFFERENT market
|
|
802
|
+
// than a secondary trade — see rationaleForAction). Sanitized short reasoning
|
|
803
|
+
// only — never raw chain-of-thought.
|
|
804
|
+
rationaleForAction(action, decision.rationale, meta.rationaleSummary, decision.actions.length)),
|
|
805
|
+
...observationReceipt,
|
|
806
|
+
};
|
|
788
807
|
const r = await executeAction(client, action, observation, trace, idem, provenance);
|
|
789
808
|
planned.push({
|
|
790
809
|
action,
|
|
@@ -868,6 +887,7 @@ export async function runCycle(deps) {
|
|
|
868
887
|
writeAttempted: decision.actions.length,
|
|
869
888
|
writeAccepted: planned.filter((p) => p.accepted).length,
|
|
870
889
|
...(postedOpportunity ? { opportunity: postedOpportunity } : {}),
|
|
890
|
+
...observationReceipt,
|
|
871
891
|
};
|
|
872
892
|
}
|
|
873
893
|
export async function runLoop(deps, opts = {}) {
|
package/dist/agent/types.d.ts
CHANGED
|
@@ -301,6 +301,8 @@ export interface AgentTrace {
|
|
|
301
301
|
strategyLabel?: string;
|
|
302
302
|
confidence?: number;
|
|
303
303
|
rationaleSummary?: string;
|
|
304
|
+
observationHash?: string;
|
|
305
|
+
indicatorVersion?: string;
|
|
304
306
|
}
|
|
305
307
|
export interface ApiResult {
|
|
306
308
|
ok: boolean;
|
|
@@ -330,6 +332,8 @@ export interface CycleResult {
|
|
|
330
332
|
disabled?: boolean;
|
|
331
333
|
disabledReason?: string;
|
|
332
334
|
live: boolean;
|
|
335
|
+
observationHash?: string;
|
|
336
|
+
indicatorVersion?: string;
|
|
333
337
|
triggerCodes?: string[];
|
|
334
338
|
llmCallMade?: boolean;
|
|
335
339
|
tokensIn?: number;
|
package/dist/client.d.ts
CHANGED
|
@@ -48,6 +48,10 @@ export declare class CoinRithmClient {
|
|
|
48
48
|
getPublicPmOverview(query?: {
|
|
49
49
|
fiat?: string;
|
|
50
50
|
}): Promise<ApiResult>;
|
|
51
|
+
getPublicPmSources(query?: {
|
|
52
|
+
fiat?: string;
|
|
53
|
+
}): Promise<ApiResult>;
|
|
54
|
+
getPublicPmSourcesHealth(): Promise<ApiResult>;
|
|
51
55
|
listPublicPmEvents(query?: {
|
|
52
56
|
q?: string;
|
|
53
57
|
source?: string;
|
|
@@ -61,6 +65,24 @@ export declare class CoinRithmClient {
|
|
|
61
65
|
fiat?: string;
|
|
62
66
|
}): Promise<ApiResult>;
|
|
63
67
|
getPublicPmWhales(): Promise<ApiResult>;
|
|
68
|
+
getPublicPmMatches(query?: {
|
|
69
|
+
limit?: number;
|
|
70
|
+
offset?: number;
|
|
71
|
+
sort?: string;
|
|
72
|
+
minDivergence?: number;
|
|
73
|
+
sourceKind?: string;
|
|
74
|
+
status?: string;
|
|
75
|
+
maxSnapshotAgeMinutes?: number;
|
|
76
|
+
requirePriced?: boolean;
|
|
77
|
+
fiat?: string;
|
|
78
|
+
}): Promise<ApiResult>;
|
|
79
|
+
getPublicPmCalibration(): Promise<ApiResult>;
|
|
80
|
+
getPublicPmCanonicalList(query?: {
|
|
81
|
+
limit?: number;
|
|
82
|
+
cursor?: number;
|
|
83
|
+
}): Promise<ApiResult>;
|
|
84
|
+
getPublicPmCanonicalDetail(key: string): Promise<ApiResult>;
|
|
85
|
+
getPublicPmVolumeHistory(): Promise<ApiResult>;
|
|
64
86
|
whoami(apiKey?: string, agentTrace?: AgentTrace): Promise<ApiResult>;
|
|
65
87
|
getPortfolio(query?: {
|
|
66
88
|
fiat?: string;
|
package/dist/client.js
CHANGED
|
@@ -203,6 +203,12 @@ export class CoinRithmClient {
|
|
|
203
203
|
getPublicPmOverview(query) {
|
|
204
204
|
return this.publicRequest("/api/prediction-markets/overview", query);
|
|
205
205
|
}
|
|
206
|
+
getPublicPmSources(query) {
|
|
207
|
+
return this.publicRequest("/api/prediction-markets/sources", query);
|
|
208
|
+
}
|
|
209
|
+
getPublicPmSourcesHealth() {
|
|
210
|
+
return this.publicRequest("/api/prediction-markets/sources/health");
|
|
211
|
+
}
|
|
206
212
|
listPublicPmEvents(query) {
|
|
207
213
|
return this.publicRequest("/api/prediction-markets/events", query);
|
|
208
214
|
}
|
|
@@ -212,6 +218,24 @@ export class CoinRithmClient {
|
|
|
212
218
|
getPublicPmWhales() {
|
|
213
219
|
return this.publicRequest("/api/prediction-markets/whales");
|
|
214
220
|
}
|
|
221
|
+
// Cross-venue disagreement clusters (approved event matches, graph-clustered).
|
|
222
|
+
getPublicPmMatches(query) {
|
|
223
|
+
return this.publicRequest("/api/prediction-markets/matches/public", query);
|
|
224
|
+
}
|
|
225
|
+
// Per-venue forecast-accuracy calibration (ECE + reliability curve). No query params.
|
|
226
|
+
getPublicPmCalibration() {
|
|
227
|
+
return this.publicRequest("/api/prediction-markets/calibration");
|
|
228
|
+
}
|
|
229
|
+
getPublicPmCanonicalList(query) {
|
|
230
|
+
return this.publicRequest("/api/prediction-markets/canonical", query);
|
|
231
|
+
}
|
|
232
|
+
getPublicPmCanonicalDetail(key) {
|
|
233
|
+
return this.publicRequest(`/api/prediction-markets/canonical/${encodeURIComponent(key)}`);
|
|
234
|
+
}
|
|
235
|
+
// Global daily volume trend (real-money venues only). No query params.
|
|
236
|
+
getPublicPmVolumeHistory() {
|
|
237
|
+
return this.publicRequest("/api/prediction-markets/volume-history");
|
|
238
|
+
}
|
|
215
239
|
// Every method takes an optional trailing `apiKey` (the per-request key for
|
|
216
240
|
// the multi-user HTTP path). When omitted, the constructor key (stdio) is used.
|
|
217
241
|
// ---- reads (scope: read) ----
|
package/dist/http.js
CHANGED
|
@@ -46,7 +46,9 @@ async function main() {
|
|
|
46
46
|
const app = express();
|
|
47
47
|
app.use(express.json());
|
|
48
48
|
// Lightweight, unauthenticated liveness probe (handy for Coolify/uptime checks).
|
|
49
|
-
|
|
49
|
+
// Keep `/healthz` as the deployment contract and expose `/health` as a
|
|
50
|
+
// compatibility alias for agents and generic uptime monitors.
|
|
51
|
+
app.get(["/health", "/healthz"], (_req, res) => {
|
|
50
52
|
res.json({
|
|
51
53
|
ok: true,
|
|
52
54
|
service: "coinrithm-mcp",
|
package/dist/tools.d.ts
CHANGED
|
@@ -1,4 +1,26 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import { CoinRithmClient } from "./client.js";
|
|
3
3
|
export declare const PAPER_NOTE: string;
|
|
4
|
+
/**
|
|
5
|
+
* Keep keyless discovery calls small enough for an agent context window.
|
|
6
|
+
* Full event evidence remains available from pm_data_event.
|
|
7
|
+
*/
|
|
8
|
+
export declare function compactPublicPmOverview(data: unknown): unknown;
|
|
9
|
+
export declare function compactPublicPmEvents(data: unknown): unknown;
|
|
10
|
+
/**
|
|
11
|
+
* Default event detail for agents: enough provenance and comparison evidence
|
|
12
|
+
* to reason safely without recursively spending an entire context window.
|
|
13
|
+
* Callers can explicitly request detail=full for the untouched API record.
|
|
14
|
+
*/
|
|
15
|
+
export declare function compactPublicPmEvent(data: unknown): unknown;
|
|
16
|
+
export declare function compactPublicPmWhales(data: unknown, limit: number): unknown;
|
|
17
|
+
/**
|
|
18
|
+
* Keep cross-venue disagreement clusters small enough for an agent context
|
|
19
|
+
* window: each event is reduced to eventSummary (drops descriptions, images,
|
|
20
|
+
* sparklines) and each pairwise comparison keeps only its top-5 highest-delta
|
|
21
|
+
* shared outcomes (compactComparison) — the same bounding pm_data_event
|
|
22
|
+
* applies to crossSourceMatches. Verified live: a 5-cluster page drops from
|
|
23
|
+
* ~466KB to ~48KB.
|
|
24
|
+
*/
|
|
25
|
+
export declare function compactPublicPmDisagreements(data: unknown): unknown;
|
|
4
26
|
export declare function registerTools(server: McpServer, client: CoinRithmClient): void;
|