@coinrithm/mcp-trading 0.7.8 → 0.7.9
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 +95 -0
- package/README.md +86 -69
- package/dist/agent/capitalSizing.js +17 -4
- package/dist/agent/client.d.ts +8 -1
- package/dist/agent/client.js +98 -39
- package/dist/agent/decisionReceipt.d.ts +4 -1
- package/dist/agent/decisionReceipt.js +27 -3
- package/dist/agent/gate.js +14 -11
- package/dist/agent/providers.d.ts +2 -1
- package/dist/agent/providers.js +109 -19
- package/dist/agent/runner.js +9 -4
- package/dist/agent/state.js +17 -2
- package/dist/agent/templates.js +4 -0
- package/dist/client.js +3 -2
- package/dist/http.d.ts +7 -1
- package/dist/http.js +36 -16
- package/dist/httpCompletion.d.ts +33 -0
- package/dist/httpCompletion.js +220 -0
- package/dist/retryAfter.d.ts +1 -0
- package/dist/retryAfter.js +16 -0
- package/dist/tools.js +19 -1
- package/package.json +4 -2
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,101 @@ ships two binaries — `coinrithm-mcp` (the MCP server) and `coinrithm-agent` (t
|
|
|
5
5
|
self-host agent runner) — versioned together. The CoinRithm **API contract** is
|
|
6
6
|
versioned separately (see `openapi.yaml` `info.version`, currently `1.7.0`).
|
|
7
7
|
|
|
8
|
+
## 0.7.9
|
|
9
|
+
|
|
10
|
+
This entry describes the package contents. Check npm for publication status;
|
|
11
|
+
a source version or hosted deployment does not confirm npm delivery.
|
|
12
|
+
|
|
13
|
+
Public market-data fidelity and runner reliability release. No MCP tool was
|
|
14
|
+
renamed or removed, and the API **contract stays 1.7.0**.
|
|
15
|
+
|
|
16
|
+
**PM evaluation budget.** Event-driven periodic prediction-market evaluations
|
|
17
|
+
now respect `maxLlmCallsPerHour` after their cooldown elapses. Budget skips make
|
|
18
|
+
no provider call and consume no call allowance. PM keeps its own cooldown;
|
|
19
|
+
open-position management and explicit always-on behavior retain their existing
|
|
20
|
+
exemptions. This runner gate is separate from hosted provider-capacity admission.
|
|
21
|
+
|
|
22
|
+
**Retry-After parsing.** Missing, blank or malformed headers no longer become
|
|
23
|
+
zero-delay retries. The runner API client uses its existing five-second fallback;
|
|
24
|
+
explicit zero, numeric seconds and HTTP dates remain supported. Model-provider
|
|
25
|
+
cooldowns share the parser and retain their existing one-hour cap.
|
|
26
|
+
|
|
27
|
+
**API request deadlines.** Each runner API operation now has a 30-second total
|
|
28
|
+
deadline covering response headers, body reads and all 429 retry waits. The
|
|
29
|
+
same client serves the hosted scheduler. Embedded callers can set a finite
|
|
30
|
+
`requestTimeoutMs` and supply an `AbortSignal`. A timeout or cancellation returns
|
|
31
|
+
an uncertain transport result without automatically replaying a trading write.
|
|
32
|
+
Timers and listeners are removed when the operation finishes.
|
|
33
|
+
|
|
34
|
+
**State persistence.** Self-host state is serialized to a private temporary file
|
|
35
|
+
and atomically renamed over the previous state. A failed serialization or rename
|
|
36
|
+
leaves the prior state intact. This is atomic replacement, not a claim of durable
|
|
37
|
+
storage across power loss.
|
|
38
|
+
|
|
39
|
+
**Agent conversion.** `coinrithm-agent eject` preserves explicit `triggerPolicy`
|
|
40
|
+
and `capitalSizing` blocks. Previously conversion could restore default hourly
|
|
41
|
+
budgets and drop equity sizing.
|
|
42
|
+
|
|
43
|
+
**Release verification.** All-source coverage gates, mandatory PostgreSQL CI,
|
|
44
|
+
dependency updates and corrected client setup docs are included. See the
|
|
45
|
+
[reliability record](https://github.com/CoinRithm/coinrithm-agent-trading/blob/main/docs/RELIABILITY.md).
|
|
46
|
+
|
|
47
|
+
**Confirmed-action journal.** Completed-action memory now requires an action
|
|
48
|
+
to be both accepted and executed. Failed writes and uncertain transport results
|
|
49
|
+
retain their attempt evidence without becoming completed moves in the next
|
|
50
|
+
decision prompt. Dry-run proposals remain unexecuted.
|
|
51
|
+
|
|
52
|
+
**Direct NVIDIA retry.** One complete HTTP 500/502/503/504 response can be
|
|
53
|
+
retried once on the identical direct NVIDIA route within the original deadline.
|
|
54
|
+
Both attempts are retained. This does not retry trading writes or change the
|
|
55
|
+
hosted shared-pool routing policy.
|
|
56
|
+
|
|
57
|
+
**Capital reconciliation.** Frozen-balance rounding residue down to -1e-8 is
|
|
58
|
+
normalized only in the sizing calculation after the independent reads agree.
|
|
59
|
+
Negative spendable cash still fails closed; wallet balances are not changed.
|
|
60
|
+
|
|
61
|
+
**Private decision input evidence.** The bounded numeric projection includes
|
|
62
|
+
nested indicator inputs and context movers, with legacy v1 records still readable.
|
|
63
|
+
It does not retain hidden reasoning or raw model output.
|
|
64
|
+
|
|
65
|
+
**Compact prediction-market evidence.** Discovery and compact event-detail
|
|
66
|
+
responses now retain the API's `source.quoteScale`, `source.methodology` and
|
|
67
|
+
`source.supportsMarketMetrics`, plus `spreadPoints`, `probabilityBook` and
|
|
68
|
+
each retained outcome's `normalizedProbability`. Venue-native bid/ask quotes
|
|
69
|
+
are never rescaled or interpreted from magnitude. Normalization remains the
|
|
70
|
+
API's calculation over the original full book, not the truncated top-five
|
|
71
|
+
outcome list. Existing payload bounds and explicit `detail: full` behavior
|
|
72
|
+
are unchanged.
|
|
73
|
+
|
|
74
|
+
**Settlement-time provenance.** Compact events retain `resolvedAtBasis` and
|
|
75
|
+
`settlementWindowClosedAt`, keeping provider expiration distinct from an
|
|
76
|
+
announced settlement time. Null and absent upstream evidence stay null and
|
|
77
|
+
absent; the MCP does not infer missing values.
|
|
78
|
+
|
|
79
|
+
**Candle semantics.** The `get_candles` description now states that these are
|
|
80
|
+
sampled composite-price bars. Each bar's `v` is a mean rolling 24-hour
|
|
81
|
+
quote-volume observation in USD, not volume traded during the candle, and
|
|
82
|
+
must not be summed across bars.
|
|
83
|
+
|
|
84
|
+
**HTTP completion diagnostics.** The hosted HTTP
|
|
85
|
+
entry now has a bounded, stderr-only completion observer with final SDK-result
|
|
86
|
+
and finish/abort accounting. Initialization, discovery, tool failures and
|
|
87
|
+
successful delivery are distinct; unknown tool names are normalized. Records
|
|
88
|
+
contain no arguments, bodies, credentials, caller/RPC IDs or caller-origin labels.
|
|
89
|
+
Credential presence is not authentication. Durations describe the HTTP request,
|
|
90
|
+
shared by batch members; server finish does not prove client receipt or use.
|
|
91
|
+
Stdio, tools, authentication and dependency versions are unchanged. See
|
|
92
|
+
`DEPLOY.md` for the measurement and retention limits. Hosted source/image
|
|
93
|
+
`18a0bb6a8a0665e91cebc10225fec6f7ebcdaaf7` passed a bounded anonymous smoke on
|
|
94
|
+
2026-09-13. Hosted verification and npm publication are separate release steps.
|
|
95
|
+
|
|
96
|
+
**Deployment boundaries.** Hosted scheduler admission reasons are private
|
|
97
|
+
scheduler telemetry, not a new SDK or MCP response field. The API's corrected
|
|
98
|
+
comparison probabilities and enriched spread names use the existing response
|
|
99
|
+
shape and reach current clients through fresh API reads. Outcome display names
|
|
100
|
+
may change; use source/event/outcome identifiers for identity, never summed
|
|
101
|
+
prices or matching labels alone. These fixes do not establish trading returns.
|
|
102
|
+
|
|
8
103
|
## 0.7.8
|
|
9
104
|
|
|
10
105
|
Runner decision-quality, evidence and paper-capital release. Additive: no MCP
|
package/README.md
CHANGED
|
@@ -4,16 +4,18 @@
|
|
|
4
4
|
(Claude, GPT, Gemini, Llama…) a 50,000 mUSD virtual account and let it trade
|
|
5
5
|
spot, futures, and prediction markets on
|
|
6
6
|
[CoinRithm](https://coinrithm.com/agentic-trading). No real money, no exchange,
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
with virtual funds and a public **Agent Arena** leaderboard using a versioned,
|
|
8
|
+
confidence-weighted realized-PnL methodology. Paper results do not establish
|
|
9
|
+
future returns or live execution performance.
|
|
10
10
|
|
|
11
11
|
**Plus a free prediction-market data surface — no key at all.** The same server
|
|
12
12
|
ships ten keyless `pm_data_*` tools serving CoinRithm's public cross-venue
|
|
13
|
-
dataset:
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
dataset: odds, cross-venue matches with a liquidity-aware reference probability,
|
|
14
|
+
a whale-trade tape, and market-wide volume statistics. Availability and freshness
|
|
15
|
+
vary by source; inspect the returned source-health and observation metadata.
|
|
16
|
+
The catalog covers 12 venues: Polymarket, Kalshi, Smarkets, Limitless, Manifold,
|
|
17
|
+
Metaculus, PredictIt, Rothera, Futuur, Myriad, ForecastEx and Gemini.
|
|
18
|
+
Point an MCP client that supports Streamable HTTP at the hosted
|
|
17
19
|
endpoint `https://mcp.coinrithm.com/mcp` and call them anonymously — the API
|
|
18
20
|
key is only needed for the trading tools.
|
|
19
21
|
|
|
@@ -23,8 +25,9 @@ Agents are **OKF bundles** — an open, model-agnostic folder of markdown + YAML
|
|
|
23
25
|
|
|
24
26
|
- **Managed — nothing to install.** Build and deploy an agent in your browser
|
|
25
27
|
with the **Agent Studio** (CoinRithm → My Agents → Studio): fork a house agent
|
|
26
|
-
or write one from scratch, and CoinRithm runs it
|
|
27
|
-
|
|
28
|
+
or write one from scratch, and CoinRithm runs it on an always-on scheduler.
|
|
29
|
+
Studio shows the configured model; shared-pool routing can use another
|
|
30
|
+
eligible model. Check each agent's configuration and run evidence.
|
|
28
31
|
- **Self-host — this package.** Bring your own model key and run the
|
|
29
32
|
`observe→decide→validate→act` loop on your machine, or wire the MCP server
|
|
30
33
|
into Claude Desktop / Cursor / Codex.
|
|
@@ -39,6 +42,17 @@ This package ships two binaries:
|
|
|
39
42
|
|
|
40
43
|
> **Paper trading only** — virtual funds (50,000 mUSD). Not financial advice.
|
|
41
44
|
|
|
45
|
+
## Version 0.7.9
|
|
46
|
+
|
|
47
|
+
This version includes the market-data fidelity and runner reliability fixes
|
|
48
|
+
listed in [CHANGELOG.md](./CHANGELOG.md). Check `npm view @coinrithm/mcp-trading
|
|
49
|
+
version` for the latest published version. Hosted deployments and npm releases
|
|
50
|
+
are separate.
|
|
51
|
+
|
|
52
|
+
Runner API operations have a 30-second total deadline, including response
|
|
53
|
+
bodies and 429 retry waits. Timeout and cancellation results remain unconfirmed;
|
|
54
|
+
the client does not automatically replay an uncertain trading write.
|
|
55
|
+
|
|
42
56
|
## Quick start
|
|
43
57
|
|
|
44
58
|
```bash
|
|
@@ -48,13 +62,15 @@ COINRITHM_API_KEY=crk_live_… npx -y @coinrithm/mcp-trading
|
|
|
48
62
|
|
|
49
63
|
Get a `crk_live_…` key from CoinRithm → Profile → API Keys. To author and run a
|
|
50
64
|
self-host agent instead, see [Agent runner](#agent-runner-coinrithm-agent).
|
|
51
|
-
Building from source? `npm
|
|
65
|
+
Building from source? Use Node 20.19+ or 22.12+, then `npm ci && npm run build`.
|
|
66
|
+
Run `npm run test:coverage` for the enforced 90% statement, branch, function,
|
|
67
|
+
and line gates. See the [coverage scope and reliability checks](https://github.com/CoinRithm/coinrithm-agent-trading/blob/main/docs/RELIABILITY.md).
|
|
52
68
|
|
|
53
69
|
## Agent runner (`coinrithm-agent`)
|
|
54
70
|
|
|
55
71
|
This package also ships a **self-host agent runner**. You write an agent as a
|
|
56
72
|
folder (strategy + hard caps in markdown/YAML); the runner compiles it and runs
|
|
57
|
-
an `observe → decide → validate → act` loop, asking
|
|
73
|
+
an `observe → decide → validate → act` loop, asking _your_ model (bring-your-own
|
|
58
74
|
key) for structured decisions and executing only the ones that pass your caps —
|
|
59
75
|
**dry-run by default**, paper-only across spot, futures, and prediction markets.
|
|
60
76
|
|
|
@@ -68,15 +84,15 @@ COINRITHM_API_KEY=crk_live_… ANTHROPIC_API_KEY=sk-ant-… \
|
|
|
68
84
|
Full guide (env vars, fail-closed guarantees, folder layout):
|
|
69
85
|
**[docs/agent-runner.md](https://github.com/CoinRithm/coinrithm-agent-trading/blob/main/docs/agent-runner.md)**.
|
|
70
86
|
The CoinRithm hosted scheduler runs this same engine for you — see the
|
|
71
|
-
[scheduler README](
|
|
87
|
+
[scheduler README](https://github.com/CoinRithm/coinrithm-agent-trading/blob/main/packages/scheduler/README.md) for the built,
|
|
72
88
|
DB-driven runtime.
|
|
73
89
|
|
|
74
90
|
## Two ways to run
|
|
75
91
|
|
|
76
|
-
| Mode
|
|
77
|
-
|
|
|
78
|
-
| **stdio** (single-user, local)
|
|
79
|
-
| **Streamable HTTP** (multi-user, hosted) | `dist/http.js`
|
|
92
|
+
| Mode | Entry | Auth | Who it's for |
|
|
93
|
+
| ---------------------------------------- | --------------- | ---------------------------------------------- | ------------------------------------------------- |
|
|
94
|
+
| **stdio** (single-user, local) | `dist/index.js` | `COINRITHM_API_KEY` env var | Claude Desktop / Cursor / Codex on your machine |
|
|
95
|
+
| **Streamable HTTP** (multi-user, hosted) | `dist/http.js` | **per-request** `Authorization: Bearer` header | The shared hosted endpoint at `mcp.coinrithm.com` |
|
|
80
96
|
|
|
81
97
|
The hosted HTTP server holds **no** key: each request brings its own
|
|
82
98
|
`crk_live_…` in the Authorization header, and the server forwards exactly that
|
|
@@ -89,15 +105,16 @@ tool requires it. See [`DEPLOY.md`](./DEPLOY.md).
|
|
|
89
105
|
The hosted Agent Studio runs your agent free on a shared pool of NVIDIA-hosted
|
|
90
106
|
models. That pool is a **fixed budget shared by every hosted agent**, so the
|
|
91
107
|
scheduler floors how often a shared agent may run, and the floor stretches as
|
|
92
|
-
more agents join. Bringing your own model key removes
|
|
93
|
-
|
|
108
|
+
more agents join. Bringing your own model key removes the shared-pool interval
|
|
109
|
+
floor. Provider quotas, execution time, trigger policies and account protections
|
|
110
|
+
still apply.
|
|
94
111
|
|
|
95
|
-
|
|
|
96
|
-
|
|
|
97
|
-
| Models
|
|
98
|
-
| Interval
|
|
99
|
-
| Rerouting | we may serve a live alternate when a model is rate-limited | never rerouted, your route is pinned
|
|
100
|
-
| Cost
|
|
112
|
+
| | Shared free pool | Your own key |
|
|
113
|
+
| --------- | ---------------------------------------------------------- | ------------------------------------------------------------ |
|
|
114
|
+
| Models | the free hosted picks | any model your provider serves |
|
|
115
|
+
| Interval | floored by fleet size | configured interval after completion, subject to other gates |
|
|
116
|
+
| Rerouting | we may serve a live alternate when a model is rate-limited | never rerouted, your route is pinned |
|
|
117
|
+
| Cost | free | you pay your provider, not CoinRithm |
|
|
101
118
|
|
|
102
119
|
Providers accepted: `nvidia`, `openai`, `groq`, `anthropic`, and any
|
|
103
120
|
`openai-compatible` endpoint (https base URL required). The key is validated by
|
|
@@ -113,11 +130,11 @@ agent file.
|
|
|
113
130
|
|
|
114
131
|
## Configure (stdio)
|
|
115
132
|
|
|
116
|
-
| Env var
|
|
117
|
-
|
|
|
118
|
-
| `COINRITHM_API_KEY` | yes (stdio only) | —
|
|
119
|
-
| `COINRITHM_API_URL` | no
|
|
120
|
-
| `PORT`
|
|
133
|
+
| Env var | Required | Default | Notes |
|
|
134
|
+
| ------------------- | ---------------- | --------------------------- | -------------------------------------------------------------------------------------- |
|
|
135
|
+
| `COINRITHM_API_KEY` | yes (stdio only) | — | A `crk_live_…` key from CoinRithm → Profile → API Keys. **Ignored by the HTTP entry.** |
|
|
136
|
+
| `COINRITHM_API_URL` | no | `https://api.coinrithm.com` | Upstream base URL (live) |
|
|
137
|
+
| `PORT` | no | `8787` | HTTP entry only |
|
|
121
138
|
|
|
122
139
|
## Run
|
|
123
140
|
|
|
@@ -136,46 +153,46 @@ agent file.
|
|
|
136
153
|
|
|
137
154
|
## Tools
|
|
138
155
|
|
|
139
|
-
| Tool
|
|
140
|
-
|
|
|
141
|
-
| `whoami`
|
|
142
|
-
| `get_portfolio`
|
|
143
|
-
| `get_wallet`
|
|
144
|
-
| `resolve_symbol`
|
|
145
|
-
| `get_equity_curve`
|
|
146
|
-
| `get_my_trades` (venue)
|
|
147
|
-
| `get_market_context` (coinId)
|
|
148
|
-
| `get_candles` (coinId, range)
|
|
149
|
-
| `discover_pm_markets`
|
|
150
|
-
| `get_performance`
|
|
151
|
-
| `get_agent_ledger`
|
|
152
|
-
| `export_agent_ledger`
|
|
153
|
-
| `export_run_evidence`
|
|
154
|
-
| `get_arena_leaderboard`
|
|
155
|
-
| `get_arena_agent` (handle)
|
|
156
|
-
| `list_open_orders`
|
|
157
|
-
| `get_positions` (venue)
|
|
158
|
-
| `spot_quote`
|
|
159
|
-
| `futures_quote`
|
|
160
|
-
| `pm_quote`
|
|
161
|
-
| `place_spot_order`
|
|
162
|
-
| `cancel_spot_order`
|
|
163
|
-
| `open_futures_position`
|
|
164
|
-
| `set_futures_sl_tp`
|
|
165
|
-
| `close_futures_position`
|
|
166
|
-
| `open_pm_position`
|
|
167
|
-
| `report_pm_opportunity`
|
|
168
|
-
| `pm_data_overview`
|
|
169
|
-
| `pm_data_sources`
|
|
170
|
-
| `pm_data_sources_health`
|
|
171
|
-
| `pm_data_events`
|
|
172
|
-
| `pm_data_event` (source, slug, detail?)
|
|
173
|
-
| `pm_data_whales` (limit, default 10)
|
|
174
|
-
| `pm_data_disagreements` (limit, sort, sourceKind, ...) | none (public) | compact `GET /api/prediction-markets/matches/public`
|
|
175
|
-
| `pm_data_calibration`
|
|
176
|
-
| `pm_data_canonical` (key?, limit, cursor)
|
|
177
|
-
| `pm_data_volume_history`
|
|
178
|
-
| `get_crypto_movers` (direction, limit)
|
|
156
|
+
| Tool | Scope | Wraps |
|
|
157
|
+
| ------------------------------------------------------ | ------------- | ------------------------------------------------------------------------------------ |
|
|
158
|
+
| `whoami` | any | `GET /api/agent/me` |
|
|
159
|
+
| `get_portfolio` | read | `GET /api/agent/portfolio` |
|
|
160
|
+
| `get_wallet` | read | `GET /api/agent/wallet` |
|
|
161
|
+
| `resolve_symbol` | read | `GET /api/agent/resolve` |
|
|
162
|
+
| `get_equity_curve` | read | `GET /api/agent/equity-curve` |
|
|
163
|
+
| `get_my_trades` (venue) | read | `GET /api/agent/trades` |
|
|
164
|
+
| `get_market_context` (coinId) | read | `GET /api/agent/market/:coinId` |
|
|
165
|
+
| `get_candles` (coinId, range) | read | `GET /api/agent/market/:coinId/candles` |
|
|
166
|
+
| `discover_pm_markets` | read | `GET /api/agent/pm/discover` |
|
|
167
|
+
| `get_performance` | read | `GET /api/agent/performance` |
|
|
168
|
+
| `get_agent_ledger` | read | `GET /api/agent/ledger` |
|
|
169
|
+
| `export_agent_ledger` | read | `GET /api/agent/ledger/export` |
|
|
170
|
+
| `export_run_evidence` | read | `GET /api/agent/ledger/export?runId=...` |
|
|
171
|
+
| `get_arena_leaderboard` | read | `GET /api/arena` |
|
|
172
|
+
| `get_arena_agent` (handle) | read | `GET /api/arena/:handle` |
|
|
173
|
+
| `list_open_orders` | read | `GET /api/agent/orders/open` |
|
|
174
|
+
| `get_positions` (venue) | read | `GET /api/agent/positions/{futures,pm}` |
|
|
175
|
+
| `spot_quote` | read | `POST /api/agent/spot/quote` |
|
|
176
|
+
| `futures_quote` | read | `POST /api/agent/futures/quote` |
|
|
177
|
+
| `pm_quote` | read | `POST /api/agent/pm/quote` |
|
|
178
|
+
| `place_spot_order` | trade:spot | `POST /api/agent/spot/order` |
|
|
179
|
+
| `cancel_spot_order` | trade:spot | `POST /api/agent/spot/order/:id/cancel` |
|
|
180
|
+
| `open_futures_position` | trade:futures | `POST /api/agent/futures/open` ¹ |
|
|
181
|
+
| `set_futures_sl_tp` | trade:futures | `POST /api/agent/futures/sl-tp` ² |
|
|
182
|
+
| `close_futures_position` | trade:futures | `POST /api/agent/futures/close` |
|
|
183
|
+
| `open_pm_position` | trade:pm | `POST /api/agent/pm/open` ¹ |
|
|
184
|
+
| `report_pm_opportunity` | read | `POST /api/agent/pm/opportunity` |
|
|
185
|
+
| `pm_data_overview` | none (public) | compact `GET /api/prediction-markets/overview` |
|
|
186
|
+
| `pm_data_sources` | none (public) | venue methodology, coverage, and comparable volume bases |
|
|
187
|
+
| `pm_data_sources_health` | none (public) | per-venue freshness, lag, and degraded reasons |
|
|
188
|
+
| `pm_data_events` | none (public) | compact `GET /api/prediction-markets/events` |
|
|
189
|
+
| `pm_data_event` (source, slug, detail?) | none (public) | bounded event evidence by default; `detail: "full"` returns the untouched API record |
|
|
190
|
+
| `pm_data_whales` (limit, default 10) | none (public) | compact `GET /api/prediction-markets/whales` |
|
|
191
|
+
| `pm_data_disagreements` (limit, sort, sourceKind, ...) | none (public) | compact `GET /api/prediction-markets/matches/public` |
|
|
192
|
+
| `pm_data_calibration` | none (public) | `GET /api/prediction-markets/calibration` |
|
|
193
|
+
| `pm_data_canonical` (key?, limit, cursor) | none (public) | `GET /api/prediction-markets/canonical` (+ `/:key` detail) |
|
|
194
|
+
| `pm_data_volume_history` | none (public) | `GET /api/prediction-markets/volume-history` |
|
|
195
|
+
| `get_crypto_movers` (direction, limit) | none (public) | `GET /api/coins/top-{gainers,losers}` |
|
|
179
196
|
|
|
180
197
|
`get_crypto_movers` is the universe scan: the biggest 24h movers across every
|
|
181
198
|
coin CoinRithm tracks, so an agent can find candidates it was never configured
|
|
@@ -8,6 +8,11 @@ export const CAPITAL_VALUATION_BASIS = "wallet_assets_spot_marked_futures_pm_at_
|
|
|
8
8
|
// This is not an exchange-fill, funding or stop-execution guarantee.
|
|
9
9
|
export const CAPITAL_FEE_BUFFER_BPS = 10;
|
|
10
10
|
const CENT_TOLERANCE = 0.011;
|
|
11
|
+
// Paper settlement writers use EPS=1e-8 on floating-point frozen balances.
|
|
12
|
+
// Thawing the final position can leave negative dust (observed: -6.82e-13).
|
|
13
|
+
// Normalize only that bounded frozen residue, never spendable cash or debt.
|
|
14
|
+
// This is much tighter than reconciliation tolerance and does not edit balances.
|
|
15
|
+
const FROZEN_RESIDUE_TOLERANCE = 1e-8;
|
|
11
16
|
const centsDown = (n) => Math.floor(n * 100) / 100;
|
|
12
17
|
const positive = (n) => asNum(n) !== undefined && n > 0;
|
|
13
18
|
const nonnegative = (n) => asNum(n) !== undefined && n >= 0;
|
|
@@ -18,7 +23,8 @@ const nonnegative = (n) => asNum(n) !== undefined && n >= 0;
|
|
|
18
23
|
* for management; only explicitly attributed current-book rows reconcile its
|
|
19
24
|
* cash and marks. Closed history never contributes unrealized. */
|
|
20
25
|
export function deriveCapitalBook(portfolio, wallet, futures, pm) {
|
|
21
|
-
const p = asObj(portfolio), w = asObj(wallet), eq = asObj(p.equity),
|
|
26
|
+
const p = asObj(portfolio), w = asObj(wallet), eq = asObj(p.equity), rawCash = asObj(w.usdt);
|
|
27
|
+
const cash = { ...rawCash };
|
|
22
28
|
const unavailable = (reason) => ({
|
|
23
29
|
status: "unavailable",
|
|
24
30
|
reason,
|
|
@@ -35,10 +41,17 @@ export function deriveCapitalBook(portfolio, wallet, futures, pm) {
|
|
|
35
41
|
return unavailable("held_spot_valuation_unproven");
|
|
36
42
|
const buckets = ["available", "frozen", "frozenPm", "frozenFutures"];
|
|
37
43
|
for (const key of buckets) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
44
|
+
const walletValue = asNum(rawCash[key]);
|
|
45
|
+
const portfolioValue = asNum(eq[key]);
|
|
46
|
+
const minimum = key === "available" ? 0 : -FROZEN_RESIDUE_TOLERANCE;
|
|
47
|
+
if (walletValue === undefined ||
|
|
48
|
+
portfolioValue === undefined ||
|
|
49
|
+
walletValue < minimum ||
|
|
50
|
+
portfolioValue < minimum ||
|
|
51
|
+
// Compare raw reads first: normalization must not hide snapshot drift.
|
|
52
|
+
Math.abs(walletValue - portfolioValue) > CENT_TOLERANCE)
|
|
41
53
|
return unavailable("cash_partitions_incomplete_or_changed");
|
|
54
|
+
cash[key] = Math.max(0, walletValue);
|
|
42
55
|
}
|
|
43
56
|
const cashTotal = buckets.reduce((sum, k) => sum + cash[k], 0);
|
|
44
57
|
if (eq.totalUsd + CENT_TOLERANCE < cashTotal)
|
package/dist/agent/client.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { AgentTrace, ApiResult } from "./types.js";
|
|
2
2
|
export declare const DEFAULT_BASE_URL = "https://api.coinrithm.com";
|
|
3
|
+
export declare const DEFAULT_REQUEST_TIMEOUT_MS = 30000;
|
|
3
4
|
export type ProvenanceReport = {
|
|
4
5
|
runtimeKind?: "hosted_scheduler" | "self_host_runner" | "byo_api" | "mcp_tool";
|
|
5
6
|
packageVersion?: string;
|
|
@@ -21,14 +22,20 @@ export interface ClientConfig {
|
|
|
21
22
|
fetchFn?: typeof fetch;
|
|
22
23
|
sleepFn?: (ms: number) => Promise<void>;
|
|
23
24
|
maxRetries?: number;
|
|
25
|
+
/** Total deadline, including response bodies and all 429 retry waits. */
|
|
26
|
+
requestTimeoutMs?: number;
|
|
27
|
+
/** Optional caller cancellation, applied to each request from this client. */
|
|
28
|
+
signal?: AbortSignal;
|
|
24
29
|
extraHeaders?: Record<string, string>;
|
|
25
30
|
}
|
|
26
31
|
export declare class CoinRithmClient {
|
|
27
32
|
private readonly apiKey;
|
|
28
33
|
private readonly baseUrl;
|
|
29
34
|
private readonly fetchFn;
|
|
30
|
-
private readonly sleepFn
|
|
35
|
+
private readonly sleepFn?;
|
|
31
36
|
private readonly maxRetries;
|
|
37
|
+
private readonly requestTimeoutMs;
|
|
38
|
+
private readonly signal?;
|
|
32
39
|
private readonly extraHeaders?;
|
|
33
40
|
rateLimitHits: number;
|
|
34
41
|
constructor(cfg: ClientConfig);
|
package/dist/agent/client.js
CHANGED
|
@@ -4,8 +4,10 @@
|
|
|
4
4
|
// from an agent file). 429 backs off on Retry-After; 401/403/409/422 are
|
|
5
5
|
// FAIL-CLOSED cycle outcomes (returned, not retried). fetch + sleep are
|
|
6
6
|
// injectable so tests run with no network and no real waits.
|
|
7
|
-
import {
|
|
7
|
+
import { setTimeout as sleep } from "node:timers/promises";
|
|
8
|
+
import { retryAfterSeconds } from "../retryAfter.js";
|
|
8
9
|
export const DEFAULT_BASE_URL = "https://api.coinrithm.com";
|
|
10
|
+
export const DEFAULT_REQUEST_TIMEOUT_MS = 30_000;
|
|
9
11
|
function traceHeaders(trace) {
|
|
10
12
|
const h = {};
|
|
11
13
|
if (!trace)
|
|
@@ -30,6 +32,8 @@ export class CoinRithmClient {
|
|
|
30
32
|
fetchFn;
|
|
31
33
|
sleepFn;
|
|
32
34
|
maxRetries;
|
|
35
|
+
requestTimeoutMs;
|
|
36
|
+
signal;
|
|
33
37
|
extraHeaders;
|
|
34
38
|
// Every 429 seen this session (read or write, retried or not) — feeds the
|
|
35
39
|
// rate-limit-pressure kill-switch, which a write-only counter would miss.
|
|
@@ -38,8 +42,15 @@ export class CoinRithmClient {
|
|
|
38
42
|
this.apiKey = cfg.apiKey;
|
|
39
43
|
this.baseUrl = (cfg.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
40
44
|
this.fetchFn = cfg.fetchFn ?? fetch;
|
|
41
|
-
this.sleepFn = cfg.sleepFn
|
|
45
|
+
this.sleepFn = cfg.sleepFn;
|
|
42
46
|
this.maxRetries = cfg.maxRetries ?? 3;
|
|
47
|
+
this.requestTimeoutMs = cfg.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
48
|
+
if (!Number.isSafeInteger(this.requestTimeoutMs) ||
|
|
49
|
+
this.requestTimeoutMs < 1 ||
|
|
50
|
+
this.requestTimeoutMs > 2_147_483_647) {
|
|
51
|
+
throw new Error("requestTimeoutMs must be an integer between 1 and 2147483647");
|
|
52
|
+
}
|
|
53
|
+
this.signal = cfg.signal;
|
|
43
54
|
this.extraHeaders = cfg.extraHeaders;
|
|
44
55
|
}
|
|
45
56
|
async request(method, path, opts = {}) {
|
|
@@ -59,53 +70,101 @@ export class CoinRithmClient {
|
|
|
59
70
|
};
|
|
60
71
|
if (opts.body !== undefined)
|
|
61
72
|
headers["Content-Type"] = "application/json";
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
73
|
+
const controller = new AbortController();
|
|
74
|
+
let timedOut = false;
|
|
75
|
+
const cancel = () => controller.abort(new Error("API request cancelled"));
|
|
76
|
+
const timer = setTimeout(() => {
|
|
77
|
+
timedOut = true;
|
|
78
|
+
controller.abort(new Error("API request deadline exceeded"));
|
|
79
|
+
}, this.requestTimeoutMs);
|
|
80
|
+
this.signal?.addEventListener("abort", cancel, { once: true });
|
|
81
|
+
if (this.signal?.aborted)
|
|
82
|
+
cancel();
|
|
83
|
+
let rejectAborted;
|
|
84
|
+
const aborted = new Promise((_, reject) => {
|
|
85
|
+
rejectAborted = () => reject(controller.signal.reason);
|
|
86
|
+
controller.signal.addEventListener("abort", rejectAborted, {
|
|
87
|
+
once: true,
|
|
88
|
+
});
|
|
89
|
+
if (controller.signal.aborted)
|
|
90
|
+
rejectAborted();
|
|
91
|
+
});
|
|
92
|
+
const perform = async () => {
|
|
93
|
+
for (let attempt = 0;; attempt++) {
|
|
94
|
+
controller.signal.throwIfAborted();
|
|
95
|
+
const res = await this.fetchFn(url.toString(), {
|
|
66
96
|
method,
|
|
67
97
|
headers,
|
|
68
98
|
body: opts.body !== undefined ? JSON.stringify(opts.body) : undefined,
|
|
99
|
+
signal: controller.signal,
|
|
69
100
|
});
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
continue;
|
|
87
|
-
}
|
|
88
|
-
const text = await res.text();
|
|
89
|
-
let data = text;
|
|
90
|
-
if (text) {
|
|
91
|
-
try {
|
|
92
|
-
data = JSON.parse(text);
|
|
101
|
+
controller.signal.throwIfAborted();
|
|
102
|
+
const retryAfter = retryAfterSeconds(res.headers.get("retry-after"));
|
|
103
|
+
if (res.status === 429)
|
|
104
|
+
this.rateLimitHits += 1;
|
|
105
|
+
if (res.status === 429 && attempt < this.maxRetries) {
|
|
106
|
+
// Release this response before waiting so retries do not retain sockets.
|
|
107
|
+
void res.body?.cancel().catch(() => { });
|
|
108
|
+
const delayMs = (retryAfter ?? 5) * 1000;
|
|
109
|
+
// Never shorten a provider's Retry-After or overflow a Node timer.
|
|
110
|
+
if (delayMs >= this.requestTimeoutMs)
|
|
111
|
+
await aborted;
|
|
112
|
+
else if (this.sleepFn)
|
|
113
|
+
await this.sleepFn(delayMs);
|
|
114
|
+
else
|
|
115
|
+
await sleep(delayMs, undefined, { signal: controller.signal });
|
|
116
|
+
continue;
|
|
93
117
|
}
|
|
94
|
-
|
|
95
|
-
|
|
118
|
+
const text = await res.text();
|
|
119
|
+
controller.signal.throwIfAborted();
|
|
120
|
+
let data = text;
|
|
121
|
+
if (text) {
|
|
122
|
+
try {
|
|
123
|
+
data = JSON.parse(text);
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
/* leave as text */
|
|
127
|
+
}
|
|
96
128
|
}
|
|
129
|
+
return {
|
|
130
|
+
ok: res.ok,
|
|
131
|
+
status: res.status,
|
|
132
|
+
data,
|
|
133
|
+
retryAfterSeconds: res.status === 429 ? retryAfter : undefined,
|
|
134
|
+
rateLimitRemaining: Number(res.headers.get("ratelimit-remaining")) || undefined,
|
|
135
|
+
ledgerEventId: res.headers.get("x-coinrithm-ledger-event-id"),
|
|
136
|
+
};
|
|
97
137
|
}
|
|
138
|
+
};
|
|
139
|
+
try {
|
|
140
|
+
// The race also bounds injected transports that do not honor AbortSignal.
|
|
141
|
+
return await Promise.race([perform(), aborted]);
|
|
142
|
+
}
|
|
143
|
+
catch (err) {
|
|
98
144
|
return {
|
|
99
|
-
ok:
|
|
100
|
-
status:
|
|
101
|
-
data
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
145
|
+
ok: false,
|
|
146
|
+
status: 0,
|
|
147
|
+
data: {
|
|
148
|
+
error: timedOut
|
|
149
|
+
? "request_timeout"
|
|
150
|
+
: controller.signal.aborted
|
|
151
|
+
? "request_aborted"
|
|
152
|
+
: "network_error",
|
|
153
|
+
message: timedOut
|
|
154
|
+
? `API request exceeded ${this.requestTimeoutMs}ms deadline`
|
|
155
|
+
: controller.signal.aborted
|
|
156
|
+
? "API request cancelled"
|
|
157
|
+
: err instanceof Error
|
|
158
|
+
? err.message
|
|
159
|
+
: String(err),
|
|
160
|
+
},
|
|
107
161
|
};
|
|
108
162
|
}
|
|
163
|
+
finally {
|
|
164
|
+
clearTimeout(timer);
|
|
165
|
+
this.signal?.removeEventListener("abort", cancel);
|
|
166
|
+
controller.signal.removeEventListener("abort", rejectAborted);
|
|
167
|
+
}
|
|
109
168
|
}
|
|
110
169
|
// ── reads ──────────────────────────────────────────────────────────────────
|
|
111
170
|
me(trace) {
|
|
@@ -3,9 +3,12 @@ import { type DailyRiskBudget } from "./prompt.js";
|
|
|
3
3
|
export declare const DECISION_INPUT_MAX_BYTES: number;
|
|
4
4
|
export type DecisionInputPhase = "before_observation" | "observed" | "decision_input";
|
|
5
5
|
type Scalar = string | number | boolean | null;
|
|
6
|
-
|
|
6
|
+
interface Row {
|
|
7
|
+
[key: string]: Scalar | string[] | Row;
|
|
8
|
+
}
|
|
7
9
|
export interface DecisionInputRecord {
|
|
8
10
|
version: "coinrithm.decision-input.v1";
|
|
11
|
+
projectionVersion?: "coinrithm.decision-input-projection.v2";
|
|
9
12
|
visibility: "private";
|
|
10
13
|
completeness: "partial";
|
|
11
14
|
phase: DecisionInputPhase;
|