@dshtrading/connector-polymarket 0.6.3 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -3
- package/lib/clob.d.ts +73 -0
- package/lib/clob.js +122 -0
- package/lib/discovery.js +15 -16
- package/lib/errors.d.ts +11 -1
- package/lib/errors.js +14 -1
- package/lib/events.d.ts +97 -0
- package/lib/events.js +165 -0
- package/lib/gamma.d.ts +15 -2
- package/lib/gamma.js +19 -9
- package/lib/index.d.ts +7 -4
- package/lib/index.js +7 -4
- package/lib/json.js +63 -0
- package/lib/plugin.d.ts +9 -3
- package/lib/plugin.js +18 -3
- package/lib/service.d.ts +124 -6
- package/lib/service.js +200 -12
- package/lib/tools.d.ts +12 -0
- package/lib/tools.js +205 -0
- package/package.json +6 -5
package/lib/service.js
CHANGED
|
@@ -1,9 +1,30 @@
|
|
|
1
|
+
import { PolymarketError } from "./errors.js";
|
|
2
|
+
import { isCdnCached } from "./gamma.js";
|
|
3
|
+
import { assertPriceHistoryRange } from "./clob.js";
|
|
1
4
|
import { discoverFomcMeetings } from "./discovery.js";
|
|
5
|
+
import { parseEventDetail, searchEventsInPage } from "./events.js";
|
|
2
6
|
//#region src/service.ts
|
|
3
7
|
const TRADING_PREDICTION_FEED_KEY = "tradingPredictionFeed";
|
|
4
|
-
/** 单次取数的候选上限(上游 limit 上限 100,证据 Q1.4
|
|
8
|
+
/** 单次取数的候选上限(上游 limit 上限 100,证据 Q1.4;`limit=500` 与 `limit=100` 同结果)。 */
|
|
5
9
|
const PREDICTION_EVENTS_LIMIT = 100;
|
|
6
10
|
/**
|
|
11
|
+
* 会议发现结果的缓存窗口(ms)——**短 TTL,只挡同一刷新周期内的重复发现**。
|
|
12
|
+
*
|
|
13
|
+
* 历史面必须先按 `meetingId` 找到会议才能拿到各档 token;面板每换一个事件、每重绘
|
|
14
|
+
* 一次都会要历史,不缓存就是「面板每个事件打一次 gamma」(`/events` 还有
|
|
15
|
+
* `max-age=300` 的 CDN 缓存,重复打只是白等一份同一副本)。60 秒的依据:会议集合是
|
|
16
|
+
* 以天为单位变化的(新会议按周出现、结算按会议日翻转),60s 足够短到不掩盖真实变化,
|
|
17
|
+
* 又足够长到覆盖一次面板交互的全部事件请求。
|
|
18
|
+
*
|
|
19
|
+
* **失败不入缓存**:网络抖动不会被记成「没有这场会议」,下一次重试照常打上游。
|
|
20
|
+
*/
|
|
21
|
+
const MEETING_LOOKUP_TTL_MS = 6e4;
|
|
22
|
+
/** 关键词检索的返回条数默认/上限(取数页恒为 {@link PREDICTION_EVENTS_LIMIT})。 */
|
|
23
|
+
const PREDICTION_SEARCH_DEFAULT_LIMIT = 20;
|
|
24
|
+
const PREDICTION_SEARCH_MAX_LIMIT = 50;
|
|
25
|
+
/** 工具面 `source` 字段的唯一取值:数据来自哪个读取面(公开只读,无密钥)。 */
|
|
26
|
+
const POLYMARKET_SOURCE = "gamma-api.polymarket.com /events (public read-only upstream)";
|
|
27
|
+
/**
|
|
7
28
|
* 取数实现:**一次 GET 拿全量**(证据 Q2.3 的 v1 裁决理由)。
|
|
8
29
|
*
|
|
9
30
|
* `cacheBust` 的用途(证据 Q3.4):`/events` 有 `max-age=300` CDN 缓存,同一请求
|
|
@@ -12,21 +33,40 @@ const PREDICTION_EVENTS_LIMIT = 100;
|
|
|
12
33
|
*/
|
|
13
34
|
var PolymarketService = class {
|
|
14
35
|
gamma;
|
|
36
|
+
clob;
|
|
15
37
|
snapshots;
|
|
16
38
|
now;
|
|
39
|
+
meetingLookup;
|
|
17
40
|
constructor(options) {
|
|
18
41
|
this.gamma = options.gamma;
|
|
42
|
+
this.clob = options.clob;
|
|
19
43
|
this.snapshots = options.snapshots;
|
|
20
44
|
this.now = options.now ?? Date.now;
|
|
21
45
|
}
|
|
22
46
|
/**
|
|
23
|
-
*
|
|
47
|
+
* `@dshtrading/api` 形状的取数面(桥/面板消费)。只是把 {@link fomcMeetings} 的结果
|
|
48
|
+
* 投影成 `PredictionFeed`——`fetchedAt` 与工具面的 `observedAt` 是同一个取数时刻,
|
|
49
|
+
* 两个字段名各归各自的契约,不是两次取数。
|
|
50
|
+
*/
|
|
51
|
+
async listFomcMeetings(options = {}) {
|
|
52
|
+
const snapshot = await this.fomcMeetings(options);
|
|
53
|
+
return {
|
|
54
|
+
fetchedAt: snapshot.observedAt,
|
|
55
|
+
meetings: snapshot.meetings
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* FOMC 决议路径全量会议(含已结算;按 endDate 升序)——**取数逻辑的唯一实现**。
|
|
24
60
|
*
|
|
25
61
|
* **失败必须响亮**:传输失败、HTTP 403(Cloudflare 1010 的 text/plain 体)、
|
|
26
62
|
* 非 JSON 体、非数组体一律抛结构化错误——绝不返回 `{meetings: []}`。空数组
|
|
27
63
|
* 只代表「上游确实返回了空集合」这一种真实语义。
|
|
64
|
+
*
|
|
65
|
+
* `meetingId` 命中 event id 或 slug(精确匹配);给了但一场都没匹配到时抛
|
|
66
|
+
* TRADING_SYMBOL_NOT_FOUND——「上游说查不到」与「上游故障」是两件事,消费方不该把
|
|
67
|
+
* 前者当基础设施抖动去重试。
|
|
28
68
|
*/
|
|
29
|
-
async
|
|
69
|
+
async fomcMeetings(options = {}) {
|
|
30
70
|
const query = {
|
|
31
71
|
series_id: "35",
|
|
32
72
|
limit: String(100),
|
|
@@ -35,10 +75,144 @@ var PolymarketService = class {
|
|
|
35
75
|
if (options.cacheBust === true) query._cb = String(this.now());
|
|
36
76
|
const response = await this.gamma.get("/events", query);
|
|
37
77
|
const { meetings } = discoverFomcMeetings(response.body);
|
|
38
|
-
const
|
|
78
|
+
const observedAt = new Date(this.now()).toISOString();
|
|
79
|
+
const annotated = await this.annotate(meetings, observedAt);
|
|
80
|
+
const wanted = options.meetingId?.trim();
|
|
81
|
+
if (wanted === void 0 || wanted === "") return this.withFreshness(response, observedAt, { meetings: annotated });
|
|
82
|
+
const selected = annotated.filter((meeting) => meeting.id === wanted || meeting.slug === wanted);
|
|
83
|
+
if (selected.length === 0) throw new PolymarketError("TRADING_SYMBOL_NOT_FOUND", `polymarket gamma /events?series_id=35: no FOMC meeting matches ${JSON.stringify(wanted)} (accepted: event id or slug)`);
|
|
84
|
+
return this.withFreshness(response, observedAt, { meetings: selected });
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* 关键词检索(中文或英文,大小写不敏感子串,命中事件标题或 slug——**不搜档位标签**,
|
|
88
|
+
* 理由见 events.ts 的 `matchesKeyword`)。
|
|
89
|
+
*
|
|
90
|
+
* 上游 `/events` **没有关键词参数**(实测 `q=`/`title=` 与无参数逐条同结果),所以
|
|
91
|
+
* 这里是单次拉「开盘中、24h 成交额降序」的一页再本地过滤,输出带
|
|
92
|
+
* `filtering: 'local-page-filter'` 与 `scanned`,让消费方一眼看出这是「一页 ∩ 关键词」,
|
|
93
|
+
* 不是上游检索。
|
|
94
|
+
*
|
|
95
|
+
* 空关键词**先抛错、零请求**:空串会匹配一切,静默回「最热 N 条」冒充检索命中就是编造。
|
|
96
|
+
*/
|
|
97
|
+
async searchEvents(options) {
|
|
98
|
+
const keyword = options.keyword.trim();
|
|
99
|
+
if (keyword === "") throw new Error("polymarket /events: keyword must be a non-empty string");
|
|
100
|
+
const limit = options.limit ?? 20;
|
|
101
|
+
const response = await this.gamma.get("/events", {
|
|
102
|
+
closed: "false",
|
|
103
|
+
order: "volume24hr",
|
|
104
|
+
ascending: "false",
|
|
105
|
+
limit: String(100)
|
|
106
|
+
});
|
|
107
|
+
const observedAt = new Date(this.now()).toISOString();
|
|
108
|
+
const page = searchEventsInPage(response.body, keyword, limit);
|
|
109
|
+
return this.withFreshness(response, observedAt, {
|
|
110
|
+
filtering: "local-page-filter",
|
|
111
|
+
pageLimit: 100,
|
|
112
|
+
...page
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* 单事件详情(eventId 接受 event id 或 slug,精确匹配,**单次请求**)。
|
|
117
|
+
*
|
|
118
|
+
* 上游 200 空集 = 没有这个事件 → TRADING_SYMBOL_NOT_FOUND;传输/准入/契约失败照旧由
|
|
119
|
+
* 传输层结构化抛出,两者不互相冒充。
|
|
120
|
+
*/
|
|
121
|
+
async eventDetail(options) {
|
|
122
|
+
const eventId = options.eventId.trim();
|
|
123
|
+
if (eventId === "") throw new Error("polymarket /events: eventId must be a non-empty event id or slug");
|
|
124
|
+
const response = await this.gamma.get("/events", /^\d+$/.test(eventId) ? { id: eventId } : { slug: eventId });
|
|
125
|
+
const observedAt = new Date(this.now()).toISOString();
|
|
126
|
+
const event = parseEventDetail(response.body);
|
|
127
|
+
if (event === void 0) throw new PolymarketError("TRADING_SYMBOL_NOT_FOUND", `polymarket gamma /events: no event matches ${JSON.stringify(eventId)} (HTTP ${response.status} with an empty result set)`);
|
|
128
|
+
return this.withFreshness(response, observedAt, { event });
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* 单场会议在请求窗口内的逐档历史读数(面板「事件对照」的回填来源)。
|
|
132
|
+
*
|
|
133
|
+
* 三段语义(每一段都对应一条验收要求,改这里前先读它们):
|
|
134
|
+
*
|
|
135
|
+
* 1. **空 ≠ 失败**。上游 token 不存在时回 `200 {"history":[]}`(Q5.2),与本窗口确实
|
|
136
|
+
* 没有读数**无法在响应上区分**。所以空 history 只产生 `points: []`、不抛错——
|
|
137
|
+
* 但也只是「这一档在这个窗口没有可用读数」,不等于「概率为 0」「没有变动」:
|
|
138
|
+
* 面板照实留白,不许画一条 0 的线。
|
|
139
|
+
* 2. **任一档失败必须响亮抛出**(`Promise.all` 全否)。少一档就是**把失败画成没有
|
|
140
|
+
* 变动**——用户看到的是一条平的线,而真实情况是「这一档我们没取到」。宁可整块
|
|
141
|
+
* 报错让面板显示失败,也不给一份看起来完整、实则缺档的图。
|
|
142
|
+
* 3. **拿不到 token 的档位不扇出**:`clobTokenId` 缺省 = 上游没给定位键(或解析失败),
|
|
143
|
+
* 没有 token 就没有任何可请求的对象,**不猜**、也不给这一档造一条空序列;它就
|
|
144
|
+
* 不出现在 `series` 里(调用方按 bucketKey 找不到 = 这一档没有历史可对照)。
|
|
145
|
+
*
|
|
146
|
+
* 请求参数在**打 gamma 之前**校验({@link assertPriceHistoryRange}:`fromMs < toMs`、
|
|
147
|
+
* 窗口 ≤ 336h、fidelity > 0):桥侧已有同值闸门,这里是纵深防御——让非法请求
|
|
148
|
+
* 一次上游请求都不产生,而不是拿一个必然失败的窗口去打两次网络。
|
|
149
|
+
* `fidelityMinutes` 缺省 5 分钟(面板口径,依据见 clob.ts 的同名常量)。
|
|
150
|
+
*/
|
|
151
|
+
async listPriceHistory(request) {
|
|
152
|
+
const fidelityMinutes = request.fidelityMinutes ?? 5;
|
|
153
|
+
assertPriceHistoryRange(request.fromMs, request.toMs, fidelityMinutes);
|
|
154
|
+
const meetings = await this.meetingsForLookup();
|
|
155
|
+
const meeting = meetings.find((entry) => entry.id === request.meetingId);
|
|
156
|
+
if (meeting === void 0) throw new PolymarketError("TRADING_SYMBOL_NOT_FOUND", `polymarket prediction price history: meeting ${request.meetingId} is not an FOMC decision meeting (looked up ${meetings.length} open meetings)`);
|
|
157
|
+
const targets = [];
|
|
158
|
+
for (const bucket of meeting.buckets) if (bucket.clobTokenId !== void 0) targets.push({
|
|
159
|
+
key: bucket.key,
|
|
160
|
+
token: bucket.clobTokenId
|
|
161
|
+
});
|
|
162
|
+
const series = await Promise.all(targets.map(async (target) => {
|
|
163
|
+
try {
|
|
164
|
+
const history = await this.clob.priceHistory({
|
|
165
|
+
token: target.token,
|
|
166
|
+
fromMs: request.fromMs,
|
|
167
|
+
toMs: request.toMs,
|
|
168
|
+
fidelityMinutes
|
|
169
|
+
});
|
|
170
|
+
return {
|
|
171
|
+
bucketKey: target.key,
|
|
172
|
+
points: history.points
|
|
173
|
+
};
|
|
174
|
+
} catch (cause) {
|
|
175
|
+
throw bucketFailure(target.key, cause);
|
|
176
|
+
}
|
|
177
|
+
}));
|
|
39
178
|
return {
|
|
40
|
-
fetchedAt,
|
|
41
|
-
|
|
179
|
+
fetchedAt: new Date(this.now()).toISOString(),
|
|
180
|
+
meetingId: meeting.id,
|
|
181
|
+
fromMs: request.fromMs,
|
|
182
|
+
toMs: request.toMs,
|
|
183
|
+
fidelityMinutes,
|
|
184
|
+
series
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
/** 一次 gamma `/events` + 发现规则(不做 Δ 标注:历史面不需要快照)。 */
|
|
188
|
+
async fetchMeetings(cacheBust) {
|
|
189
|
+
const query = {
|
|
190
|
+
series_id: "35",
|
|
191
|
+
limit: String(100),
|
|
192
|
+
closed: "false"
|
|
193
|
+
};
|
|
194
|
+
if (cacheBust) query._cb = String(this.now());
|
|
195
|
+
const response = await this.gamma.get("/events", query);
|
|
196
|
+
return discoverFomcMeetings(response.body).meetings;
|
|
197
|
+
}
|
|
198
|
+
/** 历史面的会议定位:短 TTL 缓存(见 {@link MEETING_LOOKUP_TTL_MS};失败不缓存)。 */
|
|
199
|
+
async meetingsForLookup() {
|
|
200
|
+
const atMs = this.now();
|
|
201
|
+
const cached = this.meetingLookup;
|
|
202
|
+
if (cached !== void 0 && Math.abs(atMs - cached.atMs) < 6e4) return cached.meetings;
|
|
203
|
+
const meetings = await this.fetchMeetings(false);
|
|
204
|
+
this.meetingLookup = {
|
|
205
|
+
atMs,
|
|
206
|
+
meetings
|
|
207
|
+
};
|
|
208
|
+
return meetings;
|
|
209
|
+
}
|
|
210
|
+
withFreshness(response, observedAt, payload) {
|
|
211
|
+
return {
|
|
212
|
+
observedAt,
|
|
213
|
+
source: POLYMARKET_SOURCE,
|
|
214
|
+
stale: isCdnCached(response),
|
|
215
|
+
...payload
|
|
42
216
|
};
|
|
43
217
|
}
|
|
44
218
|
/**
|
|
@@ -61,13 +235,27 @@ var PolymarketService = class {
|
|
|
61
235
|
}
|
|
62
236
|
}
|
|
63
237
|
};
|
|
238
|
+
/**
|
|
239
|
+
* 单档失败 → 带档位键的结构化错误(保留原 code:传输失败仍是 TRADING_NETWORK,
|
|
240
|
+
* 上游拒答仍是 TRADING_UPSTREAM_ERROR,消费方的重试判断不被这层包装改变)。
|
|
241
|
+
*/
|
|
242
|
+
function bucketFailure(bucketKey, cause) {
|
|
243
|
+
const code = cause instanceof PolymarketError ? cause.code : "TRADING_UNKNOWN";
|
|
244
|
+
const reason = cause instanceof Error ? cause.message : String(cause);
|
|
245
|
+
return new PolymarketError(code, `polymarket prediction price history: bucket ${bucketKey} failed — ${reason}`, cause);
|
|
246
|
+
}
|
|
64
247
|
/** host 平面服务面:桥与工具面共用的最小接口(@dshtrading/api 的 PredictionFeedService)。 */
|
|
65
248
|
function createPolymarketPredictionFeed(service) {
|
|
66
|
-
return {
|
|
67
|
-
|
|
68
|
-
async listFomcMeetings() {
|
|
69
|
-
|
|
70
|
-
|
|
249
|
+
return {
|
|
250
|
+
/** 当前 FOMC 决议路径全部会议(含已结算)。 */
|
|
251
|
+
async listFomcMeetings() {
|
|
252
|
+
return service.listFomcMeetings();
|
|
253
|
+
},
|
|
254
|
+
/** 单场会议的逐档历史读数(回填面板打开之前的时点)。 */
|
|
255
|
+
async listPriceHistory(request) {
|
|
256
|
+
return service.listPriceHistory(request);
|
|
257
|
+
}
|
|
258
|
+
};
|
|
71
259
|
}
|
|
72
260
|
//#endregion
|
|
73
|
-
export { PREDICTION_EVENTS_LIMIT, PolymarketService, TRADING_PREDICTION_FEED_KEY, createPolymarketPredictionFeed };
|
|
261
|
+
export { MEETING_LOOKUP_TTL_MS, POLYMARKET_SOURCE, PREDICTION_EVENTS_LIMIT, PREDICTION_SEARCH_DEFAULT_LIMIT, PREDICTION_SEARCH_MAX_LIMIT, PolymarketService, TRADING_PREDICTION_FEED_KEY, createPolymarketPredictionFeed };
|
package/lib/tools.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { PolymarketService } from "./service.js";
|
|
2
|
+
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
3
|
+
//#region src/tools.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* 工具集合:service 注入即得全部工具(与桥共用同一实例,生产/测试同一路径)。
|
|
6
|
+
*
|
|
7
|
+
* 入参一律 ≤3 个(模型可预测的接口面);每次调用只发一次 gamma 请求(实测上限 100 条
|
|
8
|
+
* 一页,不翻页、不新增速率预算)。
|
|
9
|
+
*/
|
|
10
|
+
declare function createPolymarketTools(service: PolymarketService): Array<ReturnType<typeof defineTool>>;
|
|
11
|
+
//#endregion
|
|
12
|
+
export { createPolymarketTools };
|
package/lib/tools.js
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { POLYMARKET_SOURCE } from "./service.js";
|
|
2
|
+
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
3
|
+
//#region src/tools.ts
|
|
4
|
+
/**
|
|
5
|
+
* agent 工具面(市场无关命名;预测市场是跨市场信息源,归 base bundle 的 host 行)。
|
|
6
|
+
*
|
|
7
|
+
* 只读边界(硬约束):三个工具全部走 `PolymarketService` 的读取面 → gamma `GET /events`
|
|
8
|
+
* 一次请求;**没有任何下单/撤单/改参数能力**,也没有第二份取数逻辑(工具与桥
|
|
9
|
+
* `/dshtrading/api/prediction/fomc` 共用 apply() 里构造的那一个 service 实例)。
|
|
10
|
+
*
|
|
11
|
+
* 输出纪律:
|
|
12
|
+
* - 每条结果都带 `observedAt`(本进程取数时刻)、`source`(上游读取面)、`stale`
|
|
13
|
+
* (共享 CDN 是否直接命中;false 的含义是「没有缓存命中的证据」,不是「已证明实时」);
|
|
14
|
+
* - 缺值一律**省略**而不是填 0:`changeSincePrevious` 缺失 = 不知道变没变,
|
|
15
|
+
* 与「确知没变」是两件事;
|
|
16
|
+
* - 上游失败(403 Cloudflare 1010 / 非 JSON / 网络)结构化抛出,绝不回落成空数据;
|
|
17
|
+
* 空数组只代表上游确实回了空集合这一种语义。
|
|
18
|
+
*/
|
|
19
|
+
/** 官方 jsonOutput 形态:声明完整 value schema + 紧凑 JSON render(与 base / jin10 同款)。 */
|
|
20
|
+
function jsonOutput(schema) {
|
|
21
|
+
return {
|
|
22
|
+
schema,
|
|
23
|
+
render: (_args, value) => [{
|
|
24
|
+
type: "text",
|
|
25
|
+
text: JSON.stringify(value)
|
|
26
|
+
}]
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* 类型级桥:连接器返回的是 JSON 载荷,但带可选字段、没有索引签名,结构上不满足 SDK 的
|
|
31
|
+
* JsonValue。运行时校验与渲染仍走同一 schema;这里只消除类型层的结构失配,不做任何值变换。
|
|
32
|
+
*/
|
|
33
|
+
function jsonPayload(value) {
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
36
|
+
function jsonItems(values) {
|
|
37
|
+
return values;
|
|
38
|
+
}
|
|
39
|
+
const freshnessProperties = {
|
|
40
|
+
observedAt: {
|
|
41
|
+
type: "string",
|
|
42
|
+
required: true,
|
|
43
|
+
description: "Fetch instant recorded by this process (ISO 8601 UTC) — the upstream returns no response-level timestamp."
|
|
44
|
+
},
|
|
45
|
+
source: {
|
|
46
|
+
type: "string",
|
|
47
|
+
required: true,
|
|
48
|
+
description: `Upstream read surface: ${POLYMARKET_SOURCE}.`
|
|
49
|
+
},
|
|
50
|
+
stale: {
|
|
51
|
+
type: "boolean",
|
|
52
|
+
required: true,
|
|
53
|
+
description: "True when the response came straight from the shared CDN cache (cf-cache-status: HIT), i.e. a copy up to 5 minutes old. False means \"no cache hit observed\", not \"proven live\"."
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
/** 每个工具的收尾一句:三件套的语义对模型可见(模型据此判断数据有多新)。 */
|
|
57
|
+
const FRESHNESS_NOTE = " Every response carries observedAt (the fetch instant), source (the upstream read surface) and stale (true when the payload came straight from the shared 5-minute CDN cache — a copy, not necessarily live).";
|
|
58
|
+
const fomcOutput = jsonOutput({
|
|
59
|
+
type: "object",
|
|
60
|
+
additionalProperties: false,
|
|
61
|
+
properties: {
|
|
62
|
+
...freshnessProperties,
|
|
63
|
+
meetings: {
|
|
64
|
+
type: "array",
|
|
65
|
+
items: { type: "json" },
|
|
66
|
+
required: true
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
const searchOutput = jsonOutput({
|
|
71
|
+
type: "object",
|
|
72
|
+
additionalProperties: false,
|
|
73
|
+
properties: {
|
|
74
|
+
...freshnessProperties,
|
|
75
|
+
filtering: {
|
|
76
|
+
type: "string",
|
|
77
|
+
const: "local-page-filter",
|
|
78
|
+
required: true,
|
|
79
|
+
description: "This result is a LOCAL filter over one upstream page — /events has no keyword parameter."
|
|
80
|
+
},
|
|
81
|
+
pageLimit: {
|
|
82
|
+
type: "number",
|
|
83
|
+
required: true,
|
|
84
|
+
description: "Size of the single upstream page scanned (100 = upstream maximum)."
|
|
85
|
+
},
|
|
86
|
+
scanned: {
|
|
87
|
+
type: "number",
|
|
88
|
+
required: true,
|
|
89
|
+
description: "Upstream events scanned in that page."
|
|
90
|
+
},
|
|
91
|
+
matched: {
|
|
92
|
+
type: "number",
|
|
93
|
+
required: true,
|
|
94
|
+
description: "Keyword matches before truncation to the requested limit."
|
|
95
|
+
},
|
|
96
|
+
events: {
|
|
97
|
+
type: "array",
|
|
98
|
+
items: { type: "json" },
|
|
99
|
+
required: true
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
const detailOutput = jsonOutput({
|
|
104
|
+
type: "object",
|
|
105
|
+
additionalProperties: false,
|
|
106
|
+
properties: {
|
|
107
|
+
...freshnessProperties,
|
|
108
|
+
event: {
|
|
109
|
+
type: "json",
|
|
110
|
+
required: true
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
/**
|
|
115
|
+
* 工具集合:service 注入即得全部工具(与桥共用同一实例,生产/测试同一路径)。
|
|
116
|
+
*
|
|
117
|
+
* 入参一律 ≤3 个(模型可预测的接口面);每次调用只发一次 gamma 请求(实测上限 100 条
|
|
118
|
+
* 一页,不翻页、不新增速率预算)。
|
|
119
|
+
*/
|
|
120
|
+
function createPolymarketTools(service) {
|
|
121
|
+
return [
|
|
122
|
+
defineTool({
|
|
123
|
+
name: "prediction_fomc_meetings",
|
|
124
|
+
description: "Read-only FOMC decision odds from the Polymarket prediction market (public API, no key). Returns every FOMC decision event on the Fed rates path — open and already resolved — with one entry per rate-change bucket: canonical key (no_change / inc_25 / dec_50plus …), label as quoted upstream, implied probability (= upstream outcomePrices[0]) and best bid/ask. changeSincePrevious appears only when a local snapshot from an earlier fetch exists; when it is absent the difference is UNKNOWN, never zero. The probability probed here is the same value the panel bridge /dshtrading/api/prediction/fomc publishes. Pass meetingId (event id or slug) to narrow the response to one meeting. Read-only: this tool cannot place, cancel or modify anything." + FRESHNESS_NOTE,
|
|
125
|
+
parameters: { meetingId: {
|
|
126
|
+
type: "string",
|
|
127
|
+
description: "Optional FOMC meeting selector: the Polymarket event id (e.g. 606422) or its exact slug. Omit for all meetings."
|
|
128
|
+
} },
|
|
129
|
+
output: fomcOutput,
|
|
130
|
+
async execute(raw) {
|
|
131
|
+
const args = raw ?? {};
|
|
132
|
+
const meetingId = typeof args.meetingId === "string" && args.meetingId.trim() !== "" ? args.meetingId.trim() : void 0;
|
|
133
|
+
const result = await service.fomcMeetings(meetingId !== void 0 ? { meetingId } : {});
|
|
134
|
+
return {
|
|
135
|
+
observedAt: result.observedAt,
|
|
136
|
+
source: result.source,
|
|
137
|
+
stale: result.stale,
|
|
138
|
+
meetings: jsonItems(result.meetings)
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
}),
|
|
142
|
+
defineTool({
|
|
143
|
+
name: "prediction_search_events",
|
|
144
|
+
description: "Read-only keyword search over Polymarket prediction market events (public API, no key). IMPORTANT: the upstream /events endpoint has NO keyword parameter, so this is NOT an upstream search — the tool fetches ONE page of the 100 most active OPEN events (24h volume descending) and filters it locally. The response always reports filtering=\"local-page-filter\", the scanned page size and the pre-truncation match count, so you can tell \"nothing matched the keyword\" from \"the keyword exists but is not on this page\". Matching is a case-insensitive substring of the event title or slug — outcome labels are returned per hit but are not searched. Each hit carries id, title, slug, closed, endDate, volume, liquidity and its rate/outcome buckets with implied probabilities; call prediction_event_detail with an id or slug for the full quote. Use it for prediction-market exposure around macro, election, crypto or policy questions; for Fed rate odds prefer prediction_fomc_meetings (serie-complete). Read-only: this tool cannot place, cancel or modify anything." + FRESHNESS_NOTE,
|
|
145
|
+
parameters: {
|
|
146
|
+
keyword: {
|
|
147
|
+
type: "string",
|
|
148
|
+
required: true,
|
|
149
|
+
description: "Keyword, Chinese or English: case-insensitive substring of the event title or slug (outcome labels are returned per hit but are NOT searched), e.g. fed / recession / bitcoin / 降息 / 大选."
|
|
150
|
+
},
|
|
151
|
+
limit: {
|
|
152
|
+
type: "number",
|
|
153
|
+
description: `Max matches to return (integer 1..50; default 20).`
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
output: searchOutput,
|
|
157
|
+
async execute(raw) {
|
|
158
|
+
const args = raw ?? {};
|
|
159
|
+
const limit = readLimit(args.limit, 20, 50);
|
|
160
|
+
const result = await service.searchEvents({
|
|
161
|
+
keyword: String(args.keyword ?? ""),
|
|
162
|
+
limit
|
|
163
|
+
});
|
|
164
|
+
return {
|
|
165
|
+
observedAt: result.observedAt,
|
|
166
|
+
source: result.source,
|
|
167
|
+
stale: result.stale,
|
|
168
|
+
filtering: result.filtering,
|
|
169
|
+
pageLimit: result.pageLimit,
|
|
170
|
+
scanned: result.scanned,
|
|
171
|
+
matched: result.matched,
|
|
172
|
+
events: jsonItems(result.events)
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
}),
|
|
176
|
+
defineTool({
|
|
177
|
+
name: "prediction_event_detail",
|
|
178
|
+
description: "Read-only detail for ONE Polymarket prediction event (public API, no key): title, closed, endDate, event-level negRisk, and every market in it with outcomes / outcomePrices (parsed arrays, same order — outcomePrices[0] is P(outcomes[0]), normally the \"Yes\" leg), bestBid, bestAsk, spread, liquidityNum and volumeNum. Event-level volume/liquidity aggregate the whole event. Resolved markets keep closed=true; bestBid may be absent after settlement (upstream null) — absent means unknown, not zero. One request per call. An id or slug the upstream does not know fails explicitly (TRADING_SYMBOL_NOT_FOUND) instead of returning an empty market list. Read-only: this tool cannot place, cancel or modify anything." + FRESHNESS_NOTE,
|
|
179
|
+
parameters: { eventId: {
|
|
180
|
+
type: "string",
|
|
181
|
+
required: true,
|
|
182
|
+
description: "Polymarket event id (digits, e.g. 606422) or its exact slug (e.g. fed-decision-in-october-20260617190323537)."
|
|
183
|
+
} },
|
|
184
|
+
output: detailOutput,
|
|
185
|
+
async execute(raw) {
|
|
186
|
+
const args = raw ?? {};
|
|
187
|
+
const result = await service.eventDetail({ eventId: String(args.eventId ?? "") });
|
|
188
|
+
return {
|
|
189
|
+
observedAt: result.observedAt,
|
|
190
|
+
source: result.source,
|
|
191
|
+
stale: result.stale,
|
|
192
|
+
event: jsonPayload(result.event)
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
})
|
|
196
|
+
];
|
|
197
|
+
}
|
|
198
|
+
function readLimit(value, fallback, max) {
|
|
199
|
+
if (value === void 0) return fallback;
|
|
200
|
+
const parsed = Math.floor(Number(value));
|
|
201
|
+
if (!Number.isFinite(parsed) || parsed < 1) return fallback;
|
|
202
|
+
return Math.min(parsed, max);
|
|
203
|
+
}
|
|
204
|
+
//#endregion
|
|
205
|
+
export { createPolymarketTools };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dshtrading/connector-polymarket",
|
|
3
3
|
"description": "Polymarket 预测市场连接器(只读公开 API,无密钥):FOMC 决议档位与隐含概率,host 面 provide tradingPredictionFeed(跨市场信息源,归 base bundle)",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.7.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"types": "./lib/index.d.ts",
|
|
@@ -17,12 +17,13 @@
|
|
|
17
17
|
],
|
|
18
18
|
"license": "PolyForm-Noncommercial-1.0.0",
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@dshtrading/
|
|
21
|
-
"@dshtrading/
|
|
20
|
+
"@dshtrading/api": "^0.7.0",
|
|
21
|
+
"@dshtrading/dsh-home": "^0.7.0"
|
|
22
22
|
},
|
|
23
23
|
"peerDependencies": {
|
|
24
|
-
"@deepseek-ai/cordis": ">=4.
|
|
25
|
-
"@deepseek-ai/
|
|
24
|
+
"@deepseek-ai/cordis": ">=4.1.0",
|
|
25
|
+
"@deepseek-ai/dsh-tools": ">=0.2.2-rc.1",
|
|
26
|
+
"@deepseek-ai/schemastery": ">=3.19.0"
|
|
26
27
|
},
|
|
27
28
|
"devDependencies": {
|
|
28
29
|
"tsdown": "^0.22.0",
|