pandora-cli-skills 1.1.20 → 1.1.22
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/cli/lib/arbitrage_service.cjs +5 -90
- package/cli/lib/autopilot_state_store.cjs +56 -3
- package/cli/lib/mirror_sync_service.cjs +27 -14
- package/cli/lib/mirror_verify_service.cjs +1 -95
- package/cli/lib/polymarket_trade_adapter.cjs +72 -31
- package/cli/lib/similarity_service.cjs +109 -0
- package/cli/pandora.cjs +7 -0
- package/package.json +1 -1
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
const { createIndexerClient } = require('./indexer_client.cjs');
|
|
2
2
|
const { fetchPolymarketMarkets } = require('./polymarket_adapter.cjs');
|
|
3
|
+
const {
|
|
4
|
+
normalizeQuestion,
|
|
5
|
+
questionSimilarityBreakdown,
|
|
6
|
+
questionSimilarity,
|
|
7
|
+
} = require('./similarity_service.cjs');
|
|
3
8
|
|
|
4
9
|
const ARBITRAGE_SCHEMA_VERSION = '1.1.0';
|
|
5
10
|
|
|
@@ -21,96 +26,6 @@ function toUsdc(raw) {
|
|
|
21
26
|
return round(numeric / 1_000_000, 6);
|
|
22
27
|
}
|
|
23
28
|
|
|
24
|
-
function normalizeQuestion(question) {
|
|
25
|
-
return String(question || '')
|
|
26
|
-
.toLowerCase()
|
|
27
|
-
.replace(/[^a-z0-9\s]/g, ' ')
|
|
28
|
-
.replace(/\b(the|a|an|will|be|on|at|in|to|for|by|of|is|are|was|were)\b/g, ' ')
|
|
29
|
-
.replace(/\s+/g, ' ')
|
|
30
|
-
.trim();
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function tokenize(question) {
|
|
34
|
-
return new Set(normalizeQuestion(question).split(' ').filter(Boolean));
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function jaccard(a, b) {
|
|
38
|
-
if (!a.size || !b.size) return 0;
|
|
39
|
-
let intersection = 0;
|
|
40
|
-
for (const token of a) {
|
|
41
|
-
if (b.has(token)) intersection += 1;
|
|
42
|
-
}
|
|
43
|
-
const union = a.size + b.size - intersection;
|
|
44
|
-
return union ? intersection / union : 0;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function jaroDistance(s1, s2) {
|
|
48
|
-
const a = String(s1 || '');
|
|
49
|
-
const b = String(s2 || '');
|
|
50
|
-
if (a === b) return 1;
|
|
51
|
-
const maxDist = Math.floor(Math.max(a.length, b.length) / 2) - 1;
|
|
52
|
-
const aMatches = new Array(a.length).fill(false);
|
|
53
|
-
const bMatches = new Array(b.length).fill(false);
|
|
54
|
-
|
|
55
|
-
let matches = 0;
|
|
56
|
-
for (let i = 0; i < a.length; i += 1) {
|
|
57
|
-
const start = Math.max(0, i - maxDist);
|
|
58
|
-
const end = Math.min(i + maxDist + 1, b.length);
|
|
59
|
-
for (let j = start; j < end; j += 1) {
|
|
60
|
-
if (bMatches[j]) continue;
|
|
61
|
-
if (a[i] !== b[j]) continue;
|
|
62
|
-
aMatches[i] = true;
|
|
63
|
-
bMatches[j] = true;
|
|
64
|
-
matches += 1;
|
|
65
|
-
break;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
if (!matches) return 0;
|
|
70
|
-
|
|
71
|
-
let t = 0;
|
|
72
|
-
let j = 0;
|
|
73
|
-
for (let i = 0; i < a.length; i += 1) {
|
|
74
|
-
if (!aMatches[i]) continue;
|
|
75
|
-
while (!bMatches[j]) j += 1;
|
|
76
|
-
if (a[i] !== b[j]) t += 1;
|
|
77
|
-
j += 1;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const transpositions = t / 2;
|
|
81
|
-
return (matches / a.length + matches / b.length + (matches - transpositions) / matches) / 3;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function jaroWinkler(a, b) {
|
|
85
|
-
const jaro = jaroDistance(a, b);
|
|
86
|
-
let prefix = 0;
|
|
87
|
-
const s1 = String(a || '');
|
|
88
|
-
const s2 = String(b || '');
|
|
89
|
-
for (let i = 0; i < Math.min(4, s1.length, s2.length); i += 1) {
|
|
90
|
-
if (s1[i] === s2[i]) prefix += 1;
|
|
91
|
-
else break;
|
|
92
|
-
}
|
|
93
|
-
return jaro + prefix * 0.1 * (1 - jaro);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function questionSimilarityBreakdown(a, b) {
|
|
97
|
-
const normalizedLeft = normalizeQuestion(a);
|
|
98
|
-
const normalizedRight = normalizeQuestion(b);
|
|
99
|
-
const tokenScore = jaccard(tokenize(normalizedLeft), tokenize(normalizedRight));
|
|
100
|
-
const jw = jaroWinkler(normalizedLeft, normalizedRight);
|
|
101
|
-
return {
|
|
102
|
-
normalizedLeft,
|
|
103
|
-
normalizedRight,
|
|
104
|
-
tokenScore: round(tokenScore, 6),
|
|
105
|
-
jaroWinkler: round(jw, 6),
|
|
106
|
-
score: round(tokenScore * 0.55 + jw * 0.45, 6),
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
function questionSimilarity(a, b) {
|
|
111
|
-
return questionSimilarityBreakdown(a, b).score;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
29
|
function toYesProbabilityFromYesChance(rawYesChance) {
|
|
115
30
|
const raw = toNumber(rawYesChance);
|
|
116
31
|
if (raw === null) return null;
|
|
@@ -66,12 +66,65 @@ function loadState(filePath, hash) {
|
|
|
66
66
|
};
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
function sleepSync(ms) {
|
|
70
|
+
if (!Number.isFinite(ms) || ms <= 0) return;
|
|
71
|
+
const shared = new SharedArrayBuffer(4);
|
|
72
|
+
const view = new Int32Array(shared);
|
|
73
|
+
Atomics.wait(view, 0, 0, Math.max(1, Math.floor(ms)));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function acquireLock(lockPath, options = {}) {
|
|
77
|
+
const timeoutMs = Number.isFinite(Number(options.timeoutMs)) ? Number(options.timeoutMs) : 2_000;
|
|
78
|
+
const pollMs = Number.isFinite(Number(options.pollMs)) ? Number(options.pollMs) : 10;
|
|
79
|
+
const staleMs = Number.isFinite(Number(options.staleMs)) ? Number(options.staleMs) : 5 * 60 * 1000;
|
|
80
|
+
const deadline = Date.now() + Math.max(50, timeoutMs);
|
|
81
|
+
|
|
82
|
+
while (true) {
|
|
83
|
+
try {
|
|
84
|
+
return fs.openSync(lockPath, 'wx');
|
|
85
|
+
} catch (err) {
|
|
86
|
+
if (!err || err.code !== 'EEXIST') throw err;
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
const stats = fs.statSync(lockPath);
|
|
90
|
+
const ageMs = Date.now() - stats.mtimeMs;
|
|
91
|
+
if (Number.isFinite(ageMs) && ageMs > staleMs) {
|
|
92
|
+
fs.unlinkSync(lockPath);
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
} catch {
|
|
96
|
+
// best-effort stale lock cleanup
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (Date.now() >= deadline) {
|
|
100
|
+
throw new Error(`Unable to acquire state lock within ${timeoutMs}ms: ${lockPath}`);
|
|
101
|
+
}
|
|
102
|
+
sleepSync(pollMs);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
69
107
|
function saveState(filePath, state) {
|
|
70
108
|
const resolved = path.resolve(expandHome(filePath));
|
|
71
109
|
fs.mkdirSync(path.dirname(resolved), { recursive: true });
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
110
|
+
const lockPath = `${resolved}.lock`;
|
|
111
|
+
const lockFd = acquireLock(lockPath);
|
|
112
|
+
try {
|
|
113
|
+
const tmp = `${resolved}.${process.pid}.${Date.now()}.${crypto.randomBytes(4).toString('hex')}.tmp`;
|
|
114
|
+
fs.writeFileSync(tmp, JSON.stringify(state, null, 2));
|
|
115
|
+
fs.renameSync(tmp, resolved);
|
|
116
|
+
} finally {
|
|
117
|
+
try {
|
|
118
|
+
fs.closeSync(lockFd);
|
|
119
|
+
} catch {
|
|
120
|
+
// ignore lock close failures
|
|
121
|
+
}
|
|
122
|
+
try {
|
|
123
|
+
fs.unlinkSync(lockPath);
|
|
124
|
+
} catch {
|
|
125
|
+
// ignore lock cleanup failures
|
|
126
|
+
}
|
|
127
|
+
}
|
|
75
128
|
return resolved;
|
|
76
129
|
}
|
|
77
130
|
|
|
@@ -438,8 +438,6 @@ async function runMirrorSync(options, deps = {}) {
|
|
|
438
438
|
};
|
|
439
439
|
let actualRebalanceUsdc = 0;
|
|
440
440
|
let actualHedgeUsdc = 0;
|
|
441
|
-
state.idempotencyKeys.push(idempotencyKey);
|
|
442
|
-
pruneIdempotencyKeys(state);
|
|
443
441
|
state.lastExecution = {
|
|
444
442
|
mode: action.mode,
|
|
445
443
|
status: 'pending',
|
|
@@ -505,18 +503,29 @@ async function runMirrorSync(options, deps = {}) {
|
|
|
505
503
|
|
|
506
504
|
if (options.executeLive) {
|
|
507
505
|
const envCreds = readTradingCredsFromEnv();
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
506
|
+
let hedgeResult;
|
|
507
|
+
try {
|
|
508
|
+
hedgeResult = await hedgeFn({
|
|
509
|
+
host: options.polymarketHost,
|
|
510
|
+
mockUrl: options.polymarketMockUrl,
|
|
511
|
+
tokenId,
|
|
512
|
+
side: hedgeSide,
|
|
513
|
+
amountUsd: plannedHedgeUsdc,
|
|
514
|
+
privateKey: options.privateKey || envCreds.privateKey,
|
|
515
|
+
funder: options.funder || envCreds.funder,
|
|
516
|
+
apiKey: envCreds.apiKey,
|
|
517
|
+
apiSecret: envCreds.apiSecret,
|
|
518
|
+
apiPassphrase: envCreds.apiPassphrase,
|
|
519
|
+
});
|
|
520
|
+
} catch (err) {
|
|
521
|
+
hedgeResult = {
|
|
522
|
+
ok: false,
|
|
523
|
+
error: {
|
|
524
|
+
code: err && err.code ? String(err.code) : null,
|
|
525
|
+
message: err && err.message ? String(err.message) : String(err),
|
|
526
|
+
},
|
|
527
|
+
};
|
|
528
|
+
}
|
|
520
529
|
action.hedge = {
|
|
521
530
|
tokenId,
|
|
522
531
|
side: hedgeSide,
|
|
@@ -565,6 +574,10 @@ async function runMirrorSync(options, deps = {}) {
|
|
|
565
574
|
const actualSpendUsdc = round(actualRebalanceUsdc + actualHedgeUsdc, 6) || 0;
|
|
566
575
|
state.dailySpendUsdc = round((toNumber(state.dailySpendUsdc) || 0) + actualSpendUsdc, 6) || 0;
|
|
567
576
|
const executedLegCount = (actualRebalanceUsdc > 0 ? 1 : 0) + (actualHedgeUsdc > 0 ? 1 : 0);
|
|
577
|
+
if (executedLegCount > 0) {
|
|
578
|
+
state.idempotencyKeys.push(idempotencyKey);
|
|
579
|
+
pruneIdempotencyKeys(state);
|
|
580
|
+
}
|
|
568
581
|
state.tradesToday += executedLegCount;
|
|
569
582
|
state.lastExecution = action;
|
|
570
583
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const crypto = require('crypto');
|
|
2
2
|
const { createIndexerClient } = require('./indexer_client.cjs');
|
|
3
3
|
const { resolvePolymarketMarket } = require('./polymarket_trade_adapter.cjs');
|
|
4
|
+
const { questionSimilarityBreakdown } = require('./similarity_service.cjs');
|
|
4
5
|
|
|
5
6
|
const MIRROR_VERIFY_SCHEMA_VERSION = '1.0.0';
|
|
6
7
|
const USDC_DECIMALS = 6;
|
|
@@ -24,101 +25,6 @@ function normalizeUsdcRawToUsd(value) {
|
|
|
24
25
|
return round(numeric / (10 ** USDC_DECIMALS), 6);
|
|
25
26
|
}
|
|
26
27
|
|
|
27
|
-
function normalizeQuestion(question) {
|
|
28
|
-
return String(question || '')
|
|
29
|
-
.toLowerCase()
|
|
30
|
-
.replace(/[^a-z0-9\s]/g, ' ')
|
|
31
|
-
.replace(/\b(the|a|an|will|be|on|at|in|to|for|by|of|is|are|was|were)\b/g, ' ')
|
|
32
|
-
.replace(/\s+/g, ' ')
|
|
33
|
-
.trim();
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function tokenize(question) {
|
|
37
|
-
return new Set(normalizeQuestion(question).split(' ').filter(Boolean));
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function jaccard(a, b) {
|
|
41
|
-
if (!a.size || !b.size) return 0;
|
|
42
|
-
let intersection = 0;
|
|
43
|
-
for (const token of a) {
|
|
44
|
-
if (b.has(token)) intersection += 1;
|
|
45
|
-
}
|
|
46
|
-
const union = a.size + b.size - intersection;
|
|
47
|
-
return union ? intersection / union : 0;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function jaroDistance(leftInput, rightInput) {
|
|
51
|
-
const left = String(leftInput || '');
|
|
52
|
-
const right = String(rightInput || '');
|
|
53
|
-
if (left === right) return 1;
|
|
54
|
-
|
|
55
|
-
const maxDistance = Math.floor(Math.max(left.length, right.length) / 2) - 1;
|
|
56
|
-
const leftMatches = new Array(left.length).fill(false);
|
|
57
|
-
const rightMatches = new Array(right.length).fill(false);
|
|
58
|
-
|
|
59
|
-
let matches = 0;
|
|
60
|
-
for (let i = 0; i < left.length; i += 1) {
|
|
61
|
-
const start = Math.max(0, i - maxDistance);
|
|
62
|
-
const end = Math.min(i + maxDistance + 1, right.length);
|
|
63
|
-
for (let j = start; j < end; j += 1) {
|
|
64
|
-
if (rightMatches[j]) continue;
|
|
65
|
-
if (left[i] !== right[j]) continue;
|
|
66
|
-
leftMatches[i] = true;
|
|
67
|
-
rightMatches[j] = true;
|
|
68
|
-
matches += 1;
|
|
69
|
-
break;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
if (!matches) return 0;
|
|
74
|
-
|
|
75
|
-
let transpositions = 0;
|
|
76
|
-
let rightIndex = 0;
|
|
77
|
-
for (let i = 0; i < left.length; i += 1) {
|
|
78
|
-
if (!leftMatches[i]) continue;
|
|
79
|
-
while (!rightMatches[rightIndex]) {
|
|
80
|
-
rightIndex += 1;
|
|
81
|
-
}
|
|
82
|
-
if (left[i] !== right[rightIndex]) transpositions += 1;
|
|
83
|
-
rightIndex += 1;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const t = transpositions / 2;
|
|
87
|
-
return (matches / left.length + matches / right.length + (matches - t) / matches) / 3;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function jaroWinkler(left, right) {
|
|
91
|
-
const jaro = jaroDistance(left, right);
|
|
92
|
-
const a = String(left || '');
|
|
93
|
-
const b = String(right || '');
|
|
94
|
-
let prefix = 0;
|
|
95
|
-
|
|
96
|
-
for (let i = 0; i < Math.min(4, a.length, b.length); i += 1) {
|
|
97
|
-
if (a[i] === b[i]) {
|
|
98
|
-
prefix += 1;
|
|
99
|
-
} else {
|
|
100
|
-
break;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
return jaro + prefix * 0.1 * (1 - jaro);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function questionSimilarityBreakdown(leftQuestion, rightQuestion) {
|
|
108
|
-
const normalizedLeft = normalizeQuestion(leftQuestion);
|
|
109
|
-
const normalizedRight = normalizeQuestion(rightQuestion);
|
|
110
|
-
const tokenScore = jaccard(tokenize(normalizedLeft), tokenize(normalizedRight));
|
|
111
|
-
const jw = jaroWinkler(normalizedLeft, normalizedRight);
|
|
112
|
-
|
|
113
|
-
return {
|
|
114
|
-
normalizedLeft,
|
|
115
|
-
normalizedRight,
|
|
116
|
-
tokenScore: round(tokenScore, 6),
|
|
117
|
-
jaroWinkler: round(jw, 6),
|
|
118
|
-
score: round(tokenScore * 0.55 + jw * 0.45, 6),
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
|
|
122
28
|
function normalizeProbabilityFromYesChance(value) {
|
|
123
29
|
const raw = toNumber(value);
|
|
124
30
|
if (raw === null) return null;
|
|
@@ -491,15 +491,20 @@ async function fetchJson(url, timeoutMs) {
|
|
|
491
491
|
async function callWithTimeout(work, timeoutMs, label) {
|
|
492
492
|
const limitMs = Number.isInteger(timeoutMs) && timeoutMs > 0 ? timeoutMs : null;
|
|
493
493
|
if (!limitMs) {
|
|
494
|
-
return work();
|
|
494
|
+
return work(undefined);
|
|
495
495
|
}
|
|
496
496
|
|
|
497
|
+
const abortController = new AbortController();
|
|
497
498
|
let timer = null;
|
|
498
499
|
try {
|
|
499
500
|
return await Promise.race([
|
|
500
|
-
work(),
|
|
501
|
+
work(abortController.signal),
|
|
501
502
|
new Promise((_, reject) => {
|
|
502
|
-
timer = setTimeout(() =>
|
|
503
|
+
timer = setTimeout(() => {
|
|
504
|
+
// Best-effort cancellation: this only interrupts clients that support AbortSignal.
|
|
505
|
+
abortController.abort();
|
|
506
|
+
reject(new Error(`${label} timed out after ${limitMs}ms`));
|
|
507
|
+
}, limitMs);
|
|
503
508
|
}),
|
|
504
509
|
]);
|
|
505
510
|
} finally {
|
|
@@ -554,7 +559,7 @@ async function resolveByClobDirect(conditionId, hosts, options, diagnostics, tim
|
|
|
554
559
|
throw new Error('CLOB client does not expose getMarket.');
|
|
555
560
|
}
|
|
556
561
|
const market = await callWithTimeout(
|
|
557
|
-
() => client.getMarket(conditionId),
|
|
562
|
+
(_signal) => client.getMarket(conditionId),
|
|
558
563
|
timeoutMs,
|
|
559
564
|
`Polymarket getMarket(${conditionId})`,
|
|
560
565
|
);
|
|
@@ -656,7 +661,7 @@ async function resolvePolymarketMarket(options = {}) {
|
|
|
656
661
|
while (loops < maxPages) {
|
|
657
662
|
loops += 1;
|
|
658
663
|
const page = await callWithTimeout(
|
|
659
|
-
() => (cursor ? client.getMarkets(cursor) : client.getMarkets()),
|
|
664
|
+
(_signal) => (cursor ? client.getMarkets(cursor) : client.getMarkets()),
|
|
660
665
|
timeoutMs,
|
|
661
666
|
`Polymarket getMarkets(${candidateHost})`,
|
|
662
667
|
);
|
|
@@ -829,7 +834,7 @@ async function getOrderbook(clientOrOptions, tokenId, fallbackOrderbooks = null,
|
|
|
829
834
|
}
|
|
830
835
|
|
|
831
836
|
return callWithTimeout(
|
|
832
|
-
() => clientOrOptions.getOrderBook(tokenId),
|
|
837
|
+
(_signal) => clientOrOptions.getOrderBook(tokenId),
|
|
833
838
|
timeoutMs,
|
|
834
839
|
`Polymarket getOrderBook(${tokenId})`,
|
|
835
840
|
);
|
|
@@ -1322,7 +1327,7 @@ async function fetchPolymarketPositionSummary(options = {}) {
|
|
|
1322
1327
|
if (!tokenId) return;
|
|
1323
1328
|
try {
|
|
1324
1329
|
const response = await callWithTimeout(
|
|
1325
|
-
() =>
|
|
1330
|
+
(_signal) =>
|
|
1326
1331
|
client.getBalanceAllowance({
|
|
1327
1332
|
asset_type: AssetType.CONDITIONAL,
|
|
1328
1333
|
token_id: tokenId,
|
|
@@ -1355,7 +1360,7 @@ async function fetchPolymarketPositionSummary(options = {}) {
|
|
|
1355
1360
|
try {
|
|
1356
1361
|
if (baseSummary.marketId) {
|
|
1357
1362
|
openOrders = await callWithTimeout(
|
|
1358
|
-
() => client.getOpenOrders({ market: baseSummary.marketId }),
|
|
1363
|
+
(_signal) => client.getOpenOrders({ market: baseSummary.marketId }),
|
|
1359
1364
|
timeoutMs,
|
|
1360
1365
|
`Polymarket getOpenOrders(market:${baseSummary.marketId})`,
|
|
1361
1366
|
);
|
|
@@ -1364,7 +1369,7 @@ async function fetchPolymarketPositionSummary(options = {}) {
|
|
|
1364
1369
|
if (baseSummary.yesTokenId) {
|
|
1365
1370
|
grouped.push(
|
|
1366
1371
|
await callWithTimeout(
|
|
1367
|
-
() => client.getOpenOrders({ asset_id: baseSummary.yesTokenId }),
|
|
1372
|
+
(_signal) => client.getOpenOrders({ asset_id: baseSummary.yesTokenId }),
|
|
1368
1373
|
timeoutMs,
|
|
1369
1374
|
`Polymarket getOpenOrders(asset:${baseSummary.yesTokenId})`,
|
|
1370
1375
|
),
|
|
@@ -1373,7 +1378,7 @@ async function fetchPolymarketPositionSummary(options = {}) {
|
|
|
1373
1378
|
if (baseSummary.noTokenId && baseSummary.noTokenId !== baseSummary.yesTokenId) {
|
|
1374
1379
|
grouped.push(
|
|
1375
1380
|
await callWithTimeout(
|
|
1376
|
-
() => client.getOpenOrders({ asset_id: baseSummary.noTokenId }),
|
|
1381
|
+
(_signal) => client.getOpenOrders({ asset_id: baseSummary.noTokenId }),
|
|
1377
1382
|
timeoutMs,
|
|
1378
1383
|
`Polymarket getOpenOrders(asset:${baseSummary.noTokenId})`,
|
|
1379
1384
|
),
|
|
@@ -1408,6 +1413,8 @@ async function buildTradingClient(options = {}) {
|
|
|
1408
1413
|
const signatureType = resolveSignatureType(options);
|
|
1409
1414
|
const cacheKey = buildTradingCacheKey(host, chain, options);
|
|
1410
1415
|
const allowCache = options.disableCache !== true;
|
|
1416
|
+
const timeoutMs = Number.isInteger(options.timeoutMs) && options.timeoutMs > 0 ? options.timeoutMs : 12_000;
|
|
1417
|
+
const ClobCtor = options.clobClientClass || ClobClient;
|
|
1411
1418
|
|
|
1412
1419
|
if (allowCache && tradingClientCache.has(cacheKey)) {
|
|
1413
1420
|
return tradingClientCache.get(cacheKey);
|
|
@@ -1438,7 +1445,7 @@ async function buildTradingClient(options = {}) {
|
|
|
1438
1445
|
if (allowCache && derivedCredsCache.has(cacheKey)) {
|
|
1439
1446
|
creds = derivedCredsCache.get(cacheKey);
|
|
1440
1447
|
} else {
|
|
1441
|
-
const bootstrap = new
|
|
1448
|
+
const bootstrap = new ClobCtor(
|
|
1442
1449
|
host,
|
|
1443
1450
|
chain,
|
|
1444
1451
|
signer,
|
|
@@ -1454,12 +1461,27 @@ async function buildTradingClient(options = {}) {
|
|
|
1454
1461
|
if (typeof bootstrap.deriveApiKey === 'function') {
|
|
1455
1462
|
try {
|
|
1456
1463
|
// deriveApiKey expects nonce, not signature type; default to nonce 0.
|
|
1457
|
-
creds = await
|
|
1458
|
-
|
|
1459
|
-
|
|
1464
|
+
creds = await callWithTimeout(
|
|
1465
|
+
() => bootstrap.deriveApiKey(0),
|
|
1466
|
+
timeoutMs,
|
|
1467
|
+
'Polymarket deriveApiKey(0)',
|
|
1468
|
+
);
|
|
1469
|
+
} catch (err) {
|
|
1470
|
+
if (err && typeof err.message === 'string' && err.message.includes('timed out')) {
|
|
1471
|
+
throw err;
|
|
1472
|
+
}
|
|
1473
|
+
creds = await callWithTimeout(
|
|
1474
|
+
() => bootstrap.deriveApiKey(),
|
|
1475
|
+
timeoutMs,
|
|
1476
|
+
'Polymarket deriveApiKey()',
|
|
1477
|
+
);
|
|
1460
1478
|
}
|
|
1461
1479
|
} else if (typeof bootstrap.createOrDeriveApiKey === 'function') {
|
|
1462
|
-
creds = await
|
|
1480
|
+
creds = await callWithTimeout(
|
|
1481
|
+
() => bootstrap.createOrDeriveApiKey(),
|
|
1482
|
+
timeoutMs,
|
|
1483
|
+
'Polymarket createOrDeriveApiKey()',
|
|
1484
|
+
);
|
|
1463
1485
|
} else {
|
|
1464
1486
|
throw new Error('CLOB client does not support API key derivation.');
|
|
1465
1487
|
}
|
|
@@ -1469,7 +1491,7 @@ async function buildTradingClient(options = {}) {
|
|
|
1469
1491
|
}
|
|
1470
1492
|
}
|
|
1471
1493
|
|
|
1472
|
-
const client = new
|
|
1494
|
+
const client = new ClobCtor(
|
|
1473
1495
|
host,
|
|
1474
1496
|
chain,
|
|
1475
1497
|
signer,
|
|
@@ -1523,25 +1545,44 @@ async function placeHedgeOrder(options = {}) {
|
|
|
1523
1545
|
const host = options.host || DEFAULT_POLYMARKET_HOST;
|
|
1524
1546
|
const chain = options.chain || DEFAULT_POLYMARKET_CHAIN;
|
|
1525
1547
|
const cacheKey = buildTradingCacheKey(host, chain, options);
|
|
1548
|
+
const timeoutMs = Number.isInteger(options.timeoutMs) && options.timeoutMs > 0 ? options.timeoutMs : 12_000;
|
|
1526
1549
|
const client = options.client || (await buildTradingClient(options));
|
|
1527
1550
|
const side = resolveOrderSide(options.side || 'buy');
|
|
1528
1551
|
try {
|
|
1529
|
-
const tickSize =
|
|
1530
|
-
|
|
1552
|
+
const tickSize =
|
|
1553
|
+
options.tickSize ||
|
|
1554
|
+
(await callWithTimeout(
|
|
1555
|
+
() => client.getTickSize(tokenId),
|
|
1556
|
+
timeoutMs,
|
|
1557
|
+
`Polymarket getTickSize(${tokenId})`,
|
|
1558
|
+
));
|
|
1559
|
+
const negRisk =
|
|
1560
|
+
typeof options.negRisk === 'boolean'
|
|
1561
|
+
? options.negRisk
|
|
1562
|
+
: await callWithTimeout(
|
|
1563
|
+
() => client.getNegRisk(tokenId),
|
|
1564
|
+
timeoutMs,
|
|
1565
|
+
`Polymarket getNegRisk(${tokenId})`,
|
|
1566
|
+
);
|
|
1531
1567
|
|
|
1532
|
-
const response = await
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1568
|
+
const response = await callWithTimeout(
|
|
1569
|
+
() =>
|
|
1570
|
+
client.createAndPostMarketOrder(
|
|
1571
|
+
{
|
|
1572
|
+
tokenID: tokenId,
|
|
1573
|
+
amount: amountUsd,
|
|
1574
|
+
side,
|
|
1575
|
+
orderType: OrderType.FAK,
|
|
1576
|
+
},
|
|
1577
|
+
{
|
|
1578
|
+
tickSize,
|
|
1579
|
+
negRisk,
|
|
1580
|
+
},
|
|
1581
|
+
OrderType.FAK,
|
|
1582
|
+
false,
|
|
1583
|
+
),
|
|
1584
|
+
timeoutMs,
|
|
1585
|
+
`Polymarket createAndPostMarketOrder(${tokenId})`,
|
|
1545
1586
|
);
|
|
1546
1587
|
const ok = responseIndicatesSuccess(response);
|
|
1547
1588
|
if (!ok && classifyAuthFailure(response)) {
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
function toNumber(value) {
|
|
2
|
+
const numeric = Number(value);
|
|
3
|
+
if (!Number.isFinite(numeric)) return null;
|
|
4
|
+
return numeric;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function round(value, decimals = 6) {
|
|
8
|
+
const numeric = toNumber(value);
|
|
9
|
+
if (numeric === null) return null;
|
|
10
|
+
const factor = 10 ** decimals;
|
|
11
|
+
return Math.round(numeric * factor) / factor;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function normalizeQuestion(question) {
|
|
15
|
+
return String(question || '')
|
|
16
|
+
.toLowerCase()
|
|
17
|
+
.replace(/[^a-z0-9\s]/g, ' ')
|
|
18
|
+
.replace(/\b(the|a|an|will|be|on|at|in|to|for|by|of|is|are|was|were)\b/g, ' ')
|
|
19
|
+
.replace(/\s+/g, ' ')
|
|
20
|
+
.trim();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function tokenize(question) {
|
|
24
|
+
return new Set(normalizeQuestion(question).split(' ').filter(Boolean));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function jaccard(left, right) {
|
|
28
|
+
if (!left.size || !right.size) return 0;
|
|
29
|
+
let intersection = 0;
|
|
30
|
+
for (const token of left) {
|
|
31
|
+
if (right.has(token)) intersection += 1;
|
|
32
|
+
}
|
|
33
|
+
const union = left.size + right.size - intersection;
|
|
34
|
+
return union ? intersection / union : 0;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function jaroDistance(leftInput, rightInput) {
|
|
38
|
+
const left = String(leftInput || '');
|
|
39
|
+
const right = String(rightInput || '');
|
|
40
|
+
if (left === right) return 1;
|
|
41
|
+
|
|
42
|
+
const maxDistance = Math.floor(Math.max(left.length, right.length) / 2) - 1;
|
|
43
|
+
const leftMatches = new Array(left.length).fill(false);
|
|
44
|
+
const rightMatches = new Array(right.length).fill(false);
|
|
45
|
+
|
|
46
|
+
let matches = 0;
|
|
47
|
+
for (let i = 0; i < left.length; i += 1) {
|
|
48
|
+
const start = Math.max(0, i - maxDistance);
|
|
49
|
+
const end = Math.min(i + maxDistance + 1, right.length);
|
|
50
|
+
for (let j = start; j < end; j += 1) {
|
|
51
|
+
if (rightMatches[j]) continue;
|
|
52
|
+
if (left[i] !== right[j]) continue;
|
|
53
|
+
leftMatches[i] = true;
|
|
54
|
+
rightMatches[j] = true;
|
|
55
|
+
matches += 1;
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (!matches) return 0;
|
|
61
|
+
|
|
62
|
+
let transpositions = 0;
|
|
63
|
+
let rightIndex = 0;
|
|
64
|
+
for (let i = 0; i < left.length; i += 1) {
|
|
65
|
+
if (!leftMatches[i]) continue;
|
|
66
|
+
while (!rightMatches[rightIndex]) rightIndex += 1;
|
|
67
|
+
if (left[i] !== right[rightIndex]) transpositions += 1;
|
|
68
|
+
rightIndex += 1;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const t = transpositions / 2;
|
|
72
|
+
return (matches / left.length + matches / right.length + (matches - t) / matches) / 3;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function jaroWinkler(left, right) {
|
|
76
|
+
const jaro = jaroDistance(left, right);
|
|
77
|
+
const a = String(left || '');
|
|
78
|
+
const b = String(right || '');
|
|
79
|
+
let prefix = 0;
|
|
80
|
+
for (let i = 0; i < Math.min(4, a.length, b.length); i += 1) {
|
|
81
|
+
if (a[i] === b[i]) prefix += 1;
|
|
82
|
+
else break;
|
|
83
|
+
}
|
|
84
|
+
return jaro + prefix * 0.1 * (1 - jaro);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function questionSimilarityBreakdown(leftQuestion, rightQuestion) {
|
|
88
|
+
const normalizedLeft = normalizeQuestion(leftQuestion);
|
|
89
|
+
const normalizedRight = normalizeQuestion(rightQuestion);
|
|
90
|
+
const tokenScore = jaccard(tokenize(normalizedLeft), tokenize(normalizedRight));
|
|
91
|
+
const jw = jaroWinkler(normalizedLeft, normalizedRight);
|
|
92
|
+
return {
|
|
93
|
+
normalizedLeft,
|
|
94
|
+
normalizedRight,
|
|
95
|
+
tokenScore: round(tokenScore, 6),
|
|
96
|
+
jaroWinkler: round(jw, 6),
|
|
97
|
+
score: round(tokenScore * 0.55 + jw * 0.45, 6),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function questionSimilarity(leftQuestion, rightQuestion) {
|
|
102
|
+
return questionSimilarityBreakdown(leftQuestion, rightQuestion).score;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
module.exports = {
|
|
106
|
+
normalizeQuestion,
|
|
107
|
+
questionSimilarityBreakdown,
|
|
108
|
+
questionSimilarity,
|
|
109
|
+
};
|
package/cli/pandora.cjs
CHANGED
|
@@ -610,7 +610,14 @@ function formatErrorValue(value) {
|
|
|
610
610
|
}
|
|
611
611
|
}
|
|
612
612
|
|
|
613
|
+
let failureAlreadyEmitted = false;
|
|
614
|
+
|
|
613
615
|
function emitFailure(outputMode, error) {
|
|
616
|
+
if (failureAlreadyEmitted) {
|
|
617
|
+
process.exit(error instanceof CliError ? error.exitCode : 1);
|
|
618
|
+
}
|
|
619
|
+
failureAlreadyEmitted = true;
|
|
620
|
+
|
|
614
621
|
const envelope = toErrorEnvelope(error);
|
|
615
622
|
|
|
616
623
|
if (outputMode === 'json') {
|