@hedge-layer/cli 2.1.0 → 3.0.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 +37 -38
- package/dist/index.mjs +266 -489
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1026,29 +1026,82 @@ function feedQueryParams(opts) {
|
|
|
1026
1026
|
return Object.fromEntries(entries);
|
|
1027
1027
|
}
|
|
1028
1028
|
var ENSEMBLE_SOURCES = [
|
|
1029
|
-
{ name: "liquid-
|
|
1030
|
-
{ name: "
|
|
1031
|
-
{ name: "
|
|
1032
|
-
{ name: "
|
|
1033
|
-
{ name: "
|
|
1034
|
-
{ name: "
|
|
1029
|
+
{ name: "liquid-core", params: { sortBy: "liquidity", preset: "liquidity-focused" } },
|
|
1030
|
+
{ name: "active-volume", params: { sortBy: "volume", preset: "volume-hunter" } },
|
|
1031
|
+
{ name: "movers", params: { sortBy: "movement", preset: "price-movers" } },
|
|
1032
|
+
{ name: "new-markets", params: { sortBy: "recency", preset: "new-markets" } },
|
|
1033
|
+
{ name: "uncertainty", params: { sortBy: "extremity" } },
|
|
1034
|
+
{ name: "lp-quality", params: { profile: "liquidity-provider", sortBy: "lpExpectedReturn" } }
|
|
1035
1035
|
];
|
|
1036
|
+
var EXTREME_PROBABILITY_LOW = 0.07;
|
|
1037
|
+
var EXTREME_PROBABILITY_HIGH = 0.93;
|
|
1038
|
+
var EXTREME_PROBABILITY_MAX_PENALTY = 15;
|
|
1039
|
+
var HORIZON_PEAK_DAYS = 365;
|
|
1040
|
+
var HORIZON_LONG_TERM_DECAY_PER_YEAR = 4;
|
|
1041
|
+
var HORIZON_LONG_TERM_FLOOR = 2;
|
|
1042
|
+
var ENSEMBLE_SOURCE_SCORE_PER_SOURCE = 2;
|
|
1043
|
+
var ENSEMBLE_SOURCE_SCORE_MAX = 8;
|
|
1044
|
+
var ENSEMBLE_MAX_CANDIDATES_PER_EVENT = 2;
|
|
1045
|
+
var ENSEMBLE_MAX_SINGLE_SOURCE_CANDIDATES = 5;
|
|
1036
1046
|
function num(value) {
|
|
1037
1047
|
return Number.isFinite(value) ? Number(value) : 0;
|
|
1038
1048
|
}
|
|
1049
|
+
function extremeProbabilityPenalty(candidate) {
|
|
1050
|
+
const probability = Number.isFinite(candidate.probability) ? Number(candidate.probability) : candidate.yesPrice;
|
|
1051
|
+
const boundedProbability = Math.max(0, Math.min(1, probability));
|
|
1052
|
+
if (boundedProbability < EXTREME_PROBABILITY_LOW) {
|
|
1053
|
+
return (EXTREME_PROBABILITY_LOW - boundedProbability) / EXTREME_PROBABILITY_LOW * EXTREME_PROBABILITY_MAX_PENALTY;
|
|
1054
|
+
}
|
|
1055
|
+
if (boundedProbability > EXTREME_PROBABILITY_HIGH) {
|
|
1056
|
+
return (boundedProbability - EXTREME_PROBABILITY_HIGH) / (1 - EXTREME_PROBABILITY_HIGH) * EXTREME_PROBABILITY_MAX_PENALTY;
|
|
1057
|
+
}
|
|
1058
|
+
return 0;
|
|
1059
|
+
}
|
|
1060
|
+
function horizonScore(days) {
|
|
1061
|
+
if (days === null) return 2;
|
|
1062
|
+
if (days < 3) return 0;
|
|
1063
|
+
if (days <= HORIZON_PEAK_DAYS) {
|
|
1064
|
+
return Math.log1p(days) / Math.log1p(HORIZON_PEAK_DAYS) * 10;
|
|
1065
|
+
}
|
|
1066
|
+
const yearsPastPeak = (days - HORIZON_PEAK_DAYS) / HORIZON_PEAK_DAYS;
|
|
1067
|
+
return Math.max(HORIZON_LONG_TERM_FLOOR, 10 - yearsPastPeak * HORIZON_LONG_TERM_DECAY_PER_YEAR);
|
|
1068
|
+
}
|
|
1039
1069
|
function scoreCandidate(candidate, sourceCount) {
|
|
1040
1070
|
const liquidityScore = Math.min(25, Math.log1p(Math.max(0, candidate.liquidity)) / Math.log1p(1e6) * 25);
|
|
1041
1071
|
const volumeScore = Math.min(25, Math.log1p(Math.max(0, candidate.volume24h)) / Math.log1p(1e6) * 25);
|
|
1042
1072
|
const spreadScore = Math.max(0, Math.min(15, (0.12 - Math.max(0, candidate.spread)) / 0.12 * 15));
|
|
1043
1073
|
const movementPenalty = Math.min(20, Math.abs(candidate.oneDayPriceChange) * 100);
|
|
1074
|
+
const probabilityPenalty = extremeProbabilityPenalty(candidate);
|
|
1044
1075
|
const days = candidate.daysToEnd ?? null;
|
|
1045
|
-
const
|
|
1076
|
+
const horizon = horizonScore(days);
|
|
1046
1077
|
const rewardScore = Math.max(
|
|
1047
1078
|
0,
|
|
1048
1079
|
Math.min(20, num(candidate.components?.rewardYield) * 0.1 + Math.max(0, num(candidate.lpExpectedReturnDailyPct)) * 50)
|
|
1049
1080
|
);
|
|
1050
|
-
const sourceScore = Math.min(
|
|
1051
|
-
return Math.round(
|
|
1081
|
+
const sourceScore = Math.min(ENSEMBLE_SOURCE_SCORE_MAX, sourceCount * ENSEMBLE_SOURCE_SCORE_PER_SOURCE);
|
|
1082
|
+
return Math.round(
|
|
1083
|
+
(liquidityScore + volumeScore + spreadScore + horizon + rewardScore + sourceScore - movementPenalty - probabilityPenalty) * 10
|
|
1084
|
+
) / 10;
|
|
1085
|
+
}
|
|
1086
|
+
function diversifyCandidates(candidates, limit) {
|
|
1087
|
+
const eventCounts = /* @__PURE__ */ new Map();
|
|
1088
|
+
const singleSourceCounts = /* @__PURE__ */ new Map();
|
|
1089
|
+
const diversified = [];
|
|
1090
|
+
for (const candidate of candidates) {
|
|
1091
|
+
const eventKey = candidate.eventSlug || candidate.slug;
|
|
1092
|
+
const eventCount = eventCounts.get(eventKey) ?? 0;
|
|
1093
|
+
if (eventCount >= ENSEMBLE_MAX_CANDIDATES_PER_EVENT) continue;
|
|
1094
|
+
const singleSource = candidate.sourceProfiles.length === 1 ? candidate.sourceProfiles[0] : null;
|
|
1095
|
+
if (singleSource !== null) {
|
|
1096
|
+
const sourceCount = singleSourceCounts.get(singleSource) ?? 0;
|
|
1097
|
+
if (sourceCount >= ENSEMBLE_MAX_SINGLE_SOURCE_CANDIDATES) continue;
|
|
1098
|
+
singleSourceCounts.set(singleSource, sourceCount + 1);
|
|
1099
|
+
}
|
|
1100
|
+
eventCounts.set(eventKey, eventCount + 1);
|
|
1101
|
+
diversified.push(candidate);
|
|
1102
|
+
if (diversified.length >= limit) break;
|
|
1103
|
+
}
|
|
1104
|
+
return diversified;
|
|
1052
1105
|
}
|
|
1053
1106
|
function buildFeedEnsemble(sourceResults, limit, outputPath = "candidates.json", generatedAt = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
1054
1107
|
const bySlug = /* @__PURE__ */ new Map();
|
|
@@ -1083,15 +1136,16 @@ function buildFeedEnsemble(sourceResults, limit, outputPath = "candidates.json",
|
|
|
1083
1136
|
...candidate,
|
|
1084
1137
|
sourceProfiles: [...new Set(candidate.sourceProfiles)],
|
|
1085
1138
|
ensembleScore: scoreCandidate(candidate, new Set(candidate.sourceProfiles).size)
|
|
1086
|
-
})).sort((a, b) => b.ensembleScore - a.ensembleScore || b.score - a.score)
|
|
1139
|
+
})).sort((a, b) => b.ensembleScore - a.ensembleScore || b.score - a.score);
|
|
1140
|
+
const diversifiedCandidates = diversifyCandidates(candidates, limit);
|
|
1087
1141
|
return {
|
|
1088
1142
|
generatedAt,
|
|
1089
1143
|
outputPath,
|
|
1090
1144
|
totalSources: sourceResults.length,
|
|
1091
1145
|
totalRawMarkets,
|
|
1092
1146
|
totalCandidates: bySlug.size,
|
|
1093
|
-
marketsReturned:
|
|
1094
|
-
candidates
|
|
1147
|
+
marketsReturned: diversifiedCandidates.length,
|
|
1148
|
+
candidates: diversifiedCandidates
|
|
1095
1149
|
};
|
|
1096
1150
|
}
|
|
1097
1151
|
function parseLimit(value, fallback) {
|
|
@@ -1193,7 +1247,6 @@ function registerFeedCommand(program2) {
|
|
|
1193
1247
|
|
|
1194
1248
|
// src/commands/lp.ts
|
|
1195
1249
|
import { InvalidArgumentError as InvalidArgumentError2 } from "commander";
|
|
1196
|
-
import { writeFile as writeFile2 } from "fs/promises";
|
|
1197
1250
|
|
|
1198
1251
|
// src/allocator-display.ts
|
|
1199
1252
|
import chalk5 from "chalk";
|
|
@@ -1369,118 +1422,6 @@ async function readStdin() {
|
|
|
1369
1422
|
return Buffer.concat(chunks).toString("utf8");
|
|
1370
1423
|
}
|
|
1371
1424
|
|
|
1372
|
-
// src/lp-display.ts
|
|
1373
|
-
import chalk6 from "chalk";
|
|
1374
|
-
function num3(value, fallback = 0) {
|
|
1375
|
-
const n = Number(value);
|
|
1376
|
-
return Number.isFinite(n) ? n : fallback;
|
|
1377
|
-
}
|
|
1378
|
-
function signedCurrency2(value) {
|
|
1379
|
-
const formatted = currency(Math.abs(value));
|
|
1380
|
-
if (value > 0) return chalk6.green(`+${formatted}`);
|
|
1381
|
-
if (value < 0) return chalk6.yellow(`-${formatted}`);
|
|
1382
|
-
return chalk6.dim("$0.00");
|
|
1383
|
-
}
|
|
1384
|
-
function actionSummary(actions) {
|
|
1385
|
-
if (!actions) return "none";
|
|
1386
|
-
return Object.entries(actions).filter(([, count]) => num3(count) > 0).map(([action, count]) => `${action}:${num3(count)}`).join(" ");
|
|
1387
|
-
}
|
|
1388
|
-
function displayLpScanResult(result, globalOpts) {
|
|
1389
|
-
if (globalOpts.json) {
|
|
1390
|
-
json(result);
|
|
1391
|
-
return;
|
|
1392
|
-
}
|
|
1393
|
-
heading("LP Scan");
|
|
1394
|
-
process.stdout.write(
|
|
1395
|
-
chalk6.dim(
|
|
1396
|
-
` scan ${result.scanId} \xB7 strategy ${result.strategyId} \xB7 ${result.evidenceSaved} evidence rows saved
|
|
1397
|
-
`
|
|
1398
|
-
)
|
|
1399
|
-
);
|
|
1400
|
-
process.stdout.write(
|
|
1401
|
-
chalk6.dim(
|
|
1402
|
-
` ${result.totalScanned.toLocaleString()} scanned \xB7 ${result.totalAfterFilter.toLocaleString()} after filters \xB7 profile ${result.profile}
|
|
1403
|
-
|
|
1404
|
-
`
|
|
1405
|
-
)
|
|
1406
|
-
);
|
|
1407
|
-
if (result.markets.length === 0) {
|
|
1408
|
-
warn("No markets matched the LP scan.");
|
|
1409
|
-
return;
|
|
1410
|
-
}
|
|
1411
|
-
table(
|
|
1412
|
-
result.markets.slice(0, 10).map((market) => [
|
|
1413
|
-
String(market.rank),
|
|
1414
|
-
truncate(market.question, 46),
|
|
1415
|
-
String(Math.round(num3(market.score))),
|
|
1416
|
-
compactCurrency(num3(market.liquidity)),
|
|
1417
|
-
compactCurrency(num3(market.rewardsDailyRate)) + "/day",
|
|
1418
|
-
`${num3(market.lpExpectedReturnDailyPct).toFixed(3)}%`
|
|
1419
|
-
]),
|
|
1420
|
-
["#", "Market", "Score", "Liq", "Rewards", "Exp/day"]
|
|
1421
|
-
);
|
|
1422
|
-
}
|
|
1423
|
-
function displayLpRecommendResult(result, globalOpts) {
|
|
1424
|
-
if (globalOpts.json) {
|
|
1425
|
-
json(result);
|
|
1426
|
-
return;
|
|
1427
|
-
}
|
|
1428
|
-
heading("LP Recommendations");
|
|
1429
|
-
process.stdout.write(
|
|
1430
|
-
chalk6.dim(
|
|
1431
|
-
` cycle ${result.cycleId} \xB7 strategy ${result.strategyId} \xB7 ${result.candidatesSubmitted} markets \xB7 ${result.allocationsSubmitted} current allocations
|
|
1432
|
-
`
|
|
1433
|
-
)
|
|
1434
|
-
);
|
|
1435
|
-
process.stdout.write(
|
|
1436
|
-
chalk6.dim(
|
|
1437
|
-
` PnL context ${result.pnlContextCount} rows${result.pnlSynced ? " \xB7 synced" : ""} \xB7 approvals required
|
|
1438
|
-
|
|
1439
|
-
`
|
|
1440
|
-
)
|
|
1441
|
-
);
|
|
1442
|
-
displayAllocatorCycleResult(result.result ?? { decisions: result.decisions }, globalOpts);
|
|
1443
|
-
}
|
|
1444
|
-
function displayLpEvaluateResult(result, globalOpts) {
|
|
1445
|
-
if (globalOpts.json) {
|
|
1446
|
-
json(result);
|
|
1447
|
-
return;
|
|
1448
|
-
}
|
|
1449
|
-
heading("LP Evaluation");
|
|
1450
|
-
process.stdout.write(
|
|
1451
|
-
chalk6.dim(
|
|
1452
|
-
` strategy ${result.strategyId} \xB7 ${result.summary.snapshots} snapshots \xB7 ${result.summary.markets} markets`
|
|
1453
|
-
)
|
|
1454
|
-
);
|
|
1455
|
-
if (result.pnlSynced) process.stdout.write(chalk6.dim(" \xB7 synced"));
|
|
1456
|
-
process.stdout.write("\n\n");
|
|
1457
|
-
if (result.syncError) warn(result.syncError);
|
|
1458
|
-
table(
|
|
1459
|
-
[
|
|
1460
|
-
["Realized PnL", signedCurrency2(result.summary.realizedPnl)],
|
|
1461
|
-
["Unrealized PnL", signedCurrency2(result.summary.unrealizedPnl)],
|
|
1462
|
-
["Net PnL", signedCurrency2(result.summary.netPnl)],
|
|
1463
|
-
["Capital locked", currency(result.summary.capitalLocked)],
|
|
1464
|
-
["Current value", currency(result.summary.currentValue)],
|
|
1465
|
-
["Outcomes", actionSummary(result.summary.outcomes)]
|
|
1466
|
-
]
|
|
1467
|
-
);
|
|
1468
|
-
if (result.lessons.length === 0) {
|
|
1469
|
-
warn("No PnL lessons available yet.");
|
|
1470
|
-
return;
|
|
1471
|
-
}
|
|
1472
|
-
process.stdout.write("\n");
|
|
1473
|
-
table(
|
|
1474
|
-
result.lessons.slice(0, 8).map((lesson) => [
|
|
1475
|
-
truncate(String(lesson.market_slug ?? "portfolio"), 26),
|
|
1476
|
-
String(lesson.outcome ?? "flat"),
|
|
1477
|
-
signedCurrency2(num3(lesson.net_pnl)),
|
|
1478
|
-
truncate(String(lesson.lesson ?? "no lesson"), 64)
|
|
1479
|
-
]),
|
|
1480
|
-
["Market", "Outcome", "Net", "Lesson"]
|
|
1481
|
-
);
|
|
1482
|
-
}
|
|
1483
|
-
|
|
1484
1425
|
// src/commands/lp.ts
|
|
1485
1426
|
function requireAuth5(client) {
|
|
1486
1427
|
if (!client.isAuthenticated) {
|
|
@@ -1532,59 +1473,12 @@ function validateAllocatorPercentageSizing(opts) {
|
|
|
1532
1473
|
process.exit(1);
|
|
1533
1474
|
}
|
|
1534
1475
|
}
|
|
1535
|
-
function compact(payload) {
|
|
1536
|
-
return Object.fromEntries(
|
|
1537
|
-
Object.entries(payload).filter(([, value]) => value !== void 0 && value !== "")
|
|
1538
|
-
);
|
|
1539
|
-
}
|
|
1540
|
-
async function writeArtifact(path, data, jsonMode) {
|
|
1541
|
-
if (!path) return;
|
|
1542
|
-
await writeFile2(path, JSON.stringify(data, null, 2) + "\n", "utf8");
|
|
1543
|
-
if (!jsonMode) {
|
|
1544
|
-
process.stderr.write(dim(` Saved artifact to ${path}
|
|
1545
|
-
`));
|
|
1546
|
-
}
|
|
1547
|
-
}
|
|
1548
|
-
function buildLpScanPayload(topic, opts) {
|
|
1549
|
-
return compact({
|
|
1550
|
-
topic: topic?.trim() || void 0,
|
|
1551
|
-
strategyId: opts.strategyId,
|
|
1552
|
-
profile: opts.profile,
|
|
1553
|
-
sortBy: opts.sortBy,
|
|
1554
|
-
tag: opts.tag,
|
|
1555
|
-
minVolume: opts.minVolume,
|
|
1556
|
-
minLiquidity: opts.minLiquidity,
|
|
1557
|
-
maxLiquidity: opts.maxLiquidity,
|
|
1558
|
-
minRewardsDailyRate: opts.minRewardsDailyRate,
|
|
1559
|
-
minDaysToEnd: opts.minDaysToEnd,
|
|
1560
|
-
maxDaysToEnd: opts.maxDaysToEnd,
|
|
1561
|
-
maxMarketAgeHours: opts.maxMarketAgeHours,
|
|
1562
|
-
liquidProfile: opts.liquidProfile,
|
|
1563
|
-
limit: opts.limit
|
|
1564
|
-
});
|
|
1565
|
-
}
|
|
1566
|
-
function buildLpRecommendPayload(opts) {
|
|
1567
|
-
return compact({
|
|
1568
|
-
strategyId: opts.strategyId,
|
|
1569
|
-
scanId: opts.scanId,
|
|
1570
|
-
limit: opts.limit,
|
|
1571
|
-
syncPnl: opts.syncPnl
|
|
1572
|
-
});
|
|
1573
|
-
}
|
|
1574
|
-
function buildLpEvaluatePayload(opts) {
|
|
1575
|
-
return compact({
|
|
1576
|
-
strategyId: opts.strategyId,
|
|
1577
|
-
walletAddress: opts.walletAddress,
|
|
1578
|
-
syncPnl: opts.syncPnl,
|
|
1579
|
-
limit: opts.limit
|
|
1580
|
-
});
|
|
1581
|
-
}
|
|
1582
1476
|
async function runAllocatorCycle(client, payload) {
|
|
1583
1477
|
return client.post("/api/lp/allocator", payload);
|
|
1584
1478
|
}
|
|
1585
1479
|
function registerLpCommands(program2) {
|
|
1586
|
-
const lp = program2.command("lp").description("
|
|
1587
|
-
lp.command("allocator").description("Run the allocator agent on an explicit market list").requiredOption("--markets <file>", "Candidate market JSON array or { markets }; use '-' to read stdin").option("--allocations <file>", "Existing allocations JSON array or { allocations }; use '-' to read stdin").option("--pnl <file>", "Per-market PnL context JSON array or { pnl_context } (
|
|
1480
|
+
const lp = program2.command("lp").description("Generate dry-run liquidity-provider allocation plans");
|
|
1481
|
+
lp.command("allocator").description("Run the allocator agent on an explicit market list").requiredOption("--markets <file>", "Candidate market JSON array or { markets }; use '-' to read stdin").option("--allocations <file>", "Existing allocations JSON array or { allocations }; use '-' to read stdin").option("--pnl <file>", "Per-market PnL context JSON array or { pnl_context } (external wallet/inventory export); use '-' to read stdin").option("--total-holdings <usd>", "Total holdings / portfolio value used for percentage sizing", parsePositiveNumber).option("--capital-limit-pct <pct>", "Portfolio-level allocation cap as a percent of total holdings", parsePositiveNumber).option("--per-market-limit-pct <pct>", "Per-market target cap as a percent of total holdings", parsePositiveNumber).option("--capital-limit <usd>", "Portfolio capital limit for this allocator request", parseNonNegative, 500).option("--per-market-limit <usd>", "Per-market target cap", parseNonNegative, 100).option("--min-expected-return-daily-pct <pct>", "Minimum expected daily return percent", parseNonNegative, 0.02).option("--max-inventory-imbalance <ratio>", "Maximum inventory imbalance", parseNonNegative, 0.25).option("--volatility-fill-spike-threshold <ratio>", "Fill-rate imbalance that switches quotes to defensive mode", parseNonNegative, 0.35).option("--event-no-quote-minutes-before <n>", "No-quote window before scheduled events", parseNonNegative, 60).option("--event-no-quote-minutes-after <n>", "No-quote window after scheduled events", parseNonNegative, 30).option("--allocator-min-liquidity <usd>", "Allocator safety gate: minimum market liquidity", parseNonNegative, 500).option("--max-spread <ratio>", "Allocator safety gate: maximum spread", parseNonNegative, 0.12).option("--allocator-min-days-to-end <n>", "Allocator safety gate: minimum days to resolution", parseNonNegative, 3).option("--max-markets <n>", "Maximum markets allocator may target", parsePositiveInt, 5).option("--paused", "Send strategy status paused instead of dry_run").action(async (opts) => {
|
|
1588
1482
|
const globalOpts = program2.opts();
|
|
1589
1483
|
const client = new ApiClient(globalOpts);
|
|
1590
1484
|
requireAuth5(client);
|
|
@@ -1618,310 +1512,14 @@ function registerLpCommands(program2) {
|
|
|
1618
1512
|
displayAllocatorCycleResult(response.result ?? {}, globalOpts);
|
|
1619
1513
|
}
|
|
1620
1514
|
});
|
|
1621
|
-
lp.command("scan").description("Scan LP candidates and persist them as evidence").argument("[topic]", "Human label for this scan, e.g. liquidity opportunities").option("--profile <name>", "lp-opportunity | liquidity-provider | liquid-new-or-long", "liquidity-provider").option("--sort-by <key>", "score | volume | liquidity | movement | spread | rewards | rewardYield | lpExpectedReturn | horizon").option("--tag <slug>", "Polymarket category tag, e.g. crypto, politics").option("--min-volume <usd>", "Minimum 24h volume (USD)", parseNonNegative).option("--min-liquidity <usd>", "Minimum displayed liquidity (USD)", parseNonNegative).option("--max-liquidity <usd>", "Maximum displayed liquidity (USD)", parseNonNegative).option("--min-rewards-daily-rate <usd>", "Minimum LP rewards USD/day", parseNonNegative).option("--min-days-to-end <n>", "Minimum days until resolution", parseNonNegative).option("--max-days-to-end <n>", "Maximum days until resolution", parseNonNegative).option("--max-market-age-hours <n>", "With liquid-new-or-long: max age for the new branch", parseNonNegative).option("--liquid-profile <mode>", "new-or-long").option("--limit <n>", "Max markets to scan/save (1-100, default 15)", parsePositiveInt, 15).option("--strategy-id <uuid>", "LP strategy id").option("--output <file>", "Write the evidence JSON response to a local file").action(async (topic, opts) => {
|
|
1622
|
-
const globalOpts = program2.opts();
|
|
1623
|
-
const client = new ApiClient(globalOpts);
|
|
1624
|
-
requireAuth5(client);
|
|
1625
|
-
const result = await client.post(
|
|
1626
|
-
"/api/lp/scan",
|
|
1627
|
-
buildLpScanPayload(topic, opts)
|
|
1628
|
-
);
|
|
1629
|
-
await writeArtifact(opts.output, result, Boolean(globalOpts.json));
|
|
1630
|
-
displayLpScanResult(result, globalOpts);
|
|
1631
|
-
});
|
|
1632
|
-
lp.command("recommend").description("Run allocator recommendations from saved LP evidence and current allocations").option("--strategy-id <uuid>", "LP strategy id").option("--scan-id <uuid>", "Use candidates from a specific saved scan").option("--limit <n>", "Max saved candidates to submit (1-25, default 15)", parsePositiveInt, 15).option("--sync-pnl", "Refresh wallet PnL before recommending").option("--output <file>", "Write the recommendation JSON response to a local file").action(async (opts) => {
|
|
1633
|
-
const globalOpts = program2.opts();
|
|
1634
|
-
const client = new ApiClient(globalOpts);
|
|
1635
|
-
requireAuth5(client);
|
|
1636
|
-
const result = await client.post(
|
|
1637
|
-
"/api/lp/recommend",
|
|
1638
|
-
buildLpRecommendPayload(opts)
|
|
1639
|
-
);
|
|
1640
|
-
await writeArtifact(opts.output, result, Boolean(globalOpts.json));
|
|
1641
|
-
displayLpRecommendResult(result, globalOpts);
|
|
1642
|
-
});
|
|
1643
|
-
lp.command("evaluate").description("Evaluate LP performance and return compact lessons").option("--strategy-id <uuid>", "LP strategy id").option("--wallet-address <address>", "Wallet address to sync PnL from").option("--no-sync-pnl", "Use existing PnL snapshots without refreshing").option("--limit <n>", "Max PnL snapshots to evaluate (1-100, default 50)", parsePositiveInt, 50).option("--output <file>", "Write the evaluation JSON response to a local file").action(async (opts) => {
|
|
1644
|
-
const globalOpts = program2.opts();
|
|
1645
|
-
const client = new ApiClient(globalOpts);
|
|
1646
|
-
requireAuth5(client);
|
|
1647
|
-
const result = await client.post(
|
|
1648
|
-
"/api/lp/evaluate",
|
|
1649
|
-
buildLpEvaluatePayload(opts)
|
|
1650
|
-
);
|
|
1651
|
-
await writeArtifact(opts.output, result, Boolean(globalOpts.json));
|
|
1652
|
-
displayLpEvaluateResult(result, globalOpts);
|
|
1653
|
-
});
|
|
1654
|
-
}
|
|
1655
|
-
|
|
1656
|
-
// src/commands/wallet.ts
|
|
1657
|
-
import { InvalidArgumentError as InvalidArgumentError3 } from "commander";
|
|
1658
|
-
import { spawn } from "child_process";
|
|
1659
|
-
var NETWORK_NAME = "Polygon";
|
|
1660
|
-
var POLYGON_NATIVE_USDC = "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359";
|
|
1661
|
-
var SUPPORTED_ASSETS = ["pUsd", "usdcE", "matic"];
|
|
1662
|
-
var POLYGON_NATIVE_ASSET_LABEL = "POL";
|
|
1663
|
-
var TERMINAL_WITHDRAWAL_STATUSES = /* @__PURE__ */ new Set(["succeeded", "failed", "cancelled", "expired"]);
|
|
1664
|
-
function requireAuth6(client) {
|
|
1665
|
-
if (!client.isAuthenticated) {
|
|
1666
|
-
error("Not authenticated. Run `hl auth login` first.");
|
|
1667
|
-
process.exit(1);
|
|
1668
|
-
}
|
|
1669
|
-
}
|
|
1670
|
-
function parsePositiveNumber2(value) {
|
|
1671
|
-
const n = Number(value);
|
|
1672
|
-
if (!Number.isFinite(n) || n <= 0) {
|
|
1673
|
-
throw new InvalidArgumentError3("Expected a positive number");
|
|
1674
|
-
}
|
|
1675
|
-
return n;
|
|
1676
|
-
}
|
|
1677
|
-
function parseWalletAddress(value) {
|
|
1678
|
-
const trimmed = value.trim();
|
|
1679
|
-
if (!/^0x[a-fA-F0-9]{40}$/.test(trimmed)) {
|
|
1680
|
-
throw new InvalidArgumentError3("Expected a valid EVM wallet address");
|
|
1681
|
-
}
|
|
1682
|
-
return trimmed;
|
|
1683
|
-
}
|
|
1684
|
-
function normalizeWalletAsset(value) {
|
|
1685
|
-
if (!value) return void 0;
|
|
1686
|
-
const normalized = value.trim().toLowerCase().replace(/[._\s-]/g, "");
|
|
1687
|
-
if (["pusd", "polymarketusd"].includes(normalized)) return "pUsd";
|
|
1688
|
-
if (["usdce", "usdcebridged", "bridgedusdc", "usdc"].includes(normalized)) return "usdcE";
|
|
1689
|
-
if (["matic", "pol"].includes(normalized)) return "matic";
|
|
1690
|
-
throw new InvalidArgumentError3("Unknown wallet asset. Use pUSD, USDC.e, or POL.");
|
|
1691
|
-
}
|
|
1692
|
-
function assetByKey(balances, key) {
|
|
1693
|
-
return balances.assets[key];
|
|
1694
|
-
}
|
|
1695
|
-
function tokenAddress(asset) {
|
|
1696
|
-
return asset.address ?? "native token";
|
|
1697
|
-
}
|
|
1698
|
-
function displayAmount(asset) {
|
|
1699
|
-
if (!asset.ok) {
|
|
1700
|
-
return `unavailable${asset.error ? ` (${asset.error})` : ""}`;
|
|
1701
|
-
}
|
|
1702
|
-
const numeric = Number(asset.balance);
|
|
1703
|
-
if (asset.address === null && asset.decimals === 18) {
|
|
1704
|
-
return Number.isFinite(numeric) ? numeric.toLocaleString("en-US", { maximumFractionDigits: 6 }) : asset.balance;
|
|
1705
|
-
}
|
|
1706
|
-
return Number.isFinite(numeric) ? currency(numeric) : asset.balance;
|
|
1707
|
-
}
|
|
1708
|
-
function displayAssetSymbol(asset) {
|
|
1709
|
-
return asset.address === null && asset.decimals === 18 ? POLYGON_NATIVE_ASSET_LABEL : asset.symbol;
|
|
1710
|
-
}
|
|
1711
|
-
function buildDepositInstructions(balances, assetKey) {
|
|
1712
|
-
const assets = assetKey ? [assetByKey(balances, assetKey)] : SUPPORTED_ASSETS.map((key) => assetByKey(balances, key));
|
|
1713
|
-
return {
|
|
1714
|
-
action: "deposit",
|
|
1715
|
-
publicDepositAddress: balances.walletAddress,
|
|
1716
|
-
walletAddress: balances.walletAddress,
|
|
1717
|
-
chainId: balances.chainId,
|
|
1718
|
-
network: NETWORK_NAME,
|
|
1719
|
-
asset: assetKey ? assetByKey(balances, assetKey) : null,
|
|
1720
|
-
assets,
|
|
1721
|
-
warning: "Send only Polygon assets to this address. Hedge Layer cannot reverse external transfers."
|
|
1722
|
-
};
|
|
1723
|
-
}
|
|
1724
|
-
function buildWithdrawRequestPayload(opts) {
|
|
1725
|
-
const assetKey = normalizeWalletAsset(opts.asset) ?? "pUsd";
|
|
1726
|
-
if (assetKey !== "pUsd") {
|
|
1727
|
-
throw new InvalidArgumentError3("Bridge withdrawals currently send pUSD. Use --asset pUSD.");
|
|
1728
|
-
}
|
|
1729
|
-
return {
|
|
1730
|
-
amount: opts.amount,
|
|
1731
|
-
recipientAddress: opts.to,
|
|
1732
|
-
toChainId: opts.toChainId ?? "137",
|
|
1733
|
-
toTokenAddress: opts.toTokenAddress ?? POLYGON_NATIVE_USDC
|
|
1734
|
-
};
|
|
1735
|
-
}
|
|
1736
|
-
function displayWalletStatus(status) {
|
|
1737
|
-
const ownerWallet = status.ownerWallet ?? status.wallet;
|
|
1738
|
-
const depositWallet = status.depositWallet ?? status.tradingWallet ?? null;
|
|
1739
|
-
heading("Wallet");
|
|
1740
|
-
table([
|
|
1741
|
-
["Owner linked", status.linked ? "yes" : "no"],
|
|
1742
|
-
["Provider", status.provider],
|
|
1743
|
-
["Owner wallet", ownerWallet?.wallet_address ?? "(none)"],
|
|
1744
|
-
["Owner chain", ownerWallet ? `${NETWORK_NAME} (${ownerWallet.chain_id})` : "(none)"],
|
|
1745
|
-
["Owner linked at", ownerWallet?.linked_at ? new Date(ownerWallet.linked_at).toLocaleString() : "(none)"],
|
|
1746
|
-
["Deposit wallet", depositWallet?.wallet_address ?? "(none)"],
|
|
1747
|
-
["Deposit deployed", status.depositWalletDeployed ? "yes" : "no"],
|
|
1748
|
-
["Deposit approved", status.depositWalletApproved ? "yes" : "no"],
|
|
1749
|
-
["Deposit ready", status.depositWalletReady ? "yes" : "no"],
|
|
1750
|
-
["Relayer configured", status.relayerConfigured ? "yes" : "no"]
|
|
1751
|
-
]);
|
|
1752
|
-
}
|
|
1753
|
-
function displayWalletBalances(balances) {
|
|
1754
|
-
heading("Wallet Funds");
|
|
1755
|
-
table([
|
|
1756
|
-
["Wallet", balances.walletAddress],
|
|
1757
|
-
["Chain", `${NETWORK_NAME} (${balances.chainId})`],
|
|
1758
|
-
["Available pUSD", displayAmount(balances.available)],
|
|
1759
|
-
["USDC.e", displayAmount(balances.assets.usdcE)],
|
|
1760
|
-
[displayAssetSymbol(balances.assets.matic), displayAmount(balances.assets.matic)],
|
|
1761
|
-
["Updated", new Date(balances.updatedAt).toLocaleString()]
|
|
1762
|
-
]);
|
|
1763
|
-
}
|
|
1764
|
-
function displayDepositInstructions(instructions) {
|
|
1765
|
-
heading("Deposit");
|
|
1766
|
-
table([
|
|
1767
|
-
["Public deposit address", instructions.publicDepositAddress],
|
|
1768
|
-
["Network", `${instructions.network} (${instructions.chainId})`]
|
|
1769
|
-
]);
|
|
1770
|
-
process.stdout.write("\n");
|
|
1771
|
-
table(
|
|
1772
|
-
instructions.assets.map((asset) => [
|
|
1773
|
-
displayAssetSymbol(asset),
|
|
1774
|
-
tokenAddress(asset),
|
|
1775
|
-
displayAmount(asset)
|
|
1776
|
-
]),
|
|
1777
|
-
["Asset", "Token contract", "Current balance"]
|
|
1778
|
-
);
|
|
1779
|
-
process.stdout.write("\n");
|
|
1780
|
-
warn(instructions.warning);
|
|
1781
|
-
}
|
|
1782
|
-
function displayBridgeDeposit(result) {
|
|
1783
|
-
const bridgeAddresses = result.bridge?.address ?? {};
|
|
1784
|
-
const rows = Object.entries(bridgeAddresses).filter(([, value]) => typeof value === "string" && value.length > 0).map(([network, value]) => [network.toUpperCase(), value]);
|
|
1785
|
-
if (rows.length === 0) return;
|
|
1786
|
-
process.stdout.write("\n");
|
|
1787
|
-
table(rows, ["Bridge network", "Bridge deposit address"]);
|
|
1788
|
-
if (result.bridge?.note) {
|
|
1789
|
-
process.stdout.write("\n" + dim(` ${result.bridge.note}
|
|
1790
|
-
`));
|
|
1791
|
-
}
|
|
1792
|
-
for (const warning of result.bridge?.warnings ?? []) {
|
|
1793
|
-
if (warning.message) warn(warning.message);
|
|
1794
|
-
}
|
|
1795
|
-
}
|
|
1796
|
-
function displayWithdrawIntent(intent) {
|
|
1797
|
-
heading("Withdraw");
|
|
1798
|
-
table([
|
|
1799
|
-
["Intent", intent.id],
|
|
1800
|
-
["Status", intent.status],
|
|
1801
|
-
["Wallet", intent.walletAddress],
|
|
1802
|
-
["Amount", `${intent.amount} pUSD`],
|
|
1803
|
-
["Recipient", intent.recipientAddress],
|
|
1804
|
-
["Destination", `chain ${intent.toChainId} \xB7 ${intent.toTokenAddress}`],
|
|
1805
|
-
["Bridge address", intent.bridgeAddresses.evm ?? "(none)"],
|
|
1806
|
-
["Signing URL", intent.signingUrl]
|
|
1807
|
-
]);
|
|
1808
|
-
const quote = intent.quote ?? {};
|
|
1809
|
-
if (quote.estOutputUsd !== void 0 || quote.estCheckoutTimeMs !== void 0) {
|
|
1810
|
-
process.stdout.write("\n");
|
|
1811
|
-
table([
|
|
1812
|
-
["Estimated output", quote.estOutputUsd !== void 0 ? currency(Number(quote.estOutputUsd)) : "-"],
|
|
1813
|
-
["Estimated checkout", quote.estCheckoutTimeMs !== void 0 ? `${Math.round(Number(quote.estCheckoutTimeMs) / 1e3)}s` : "-"]
|
|
1814
|
-
]);
|
|
1815
|
-
}
|
|
1816
|
-
}
|
|
1817
|
-
function openBrowser(url) {
|
|
1818
|
-
const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
|
|
1819
|
-
const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
|
|
1820
|
-
const child = spawn(command, args, { detached: true, stdio: "ignore" });
|
|
1821
|
-
child.unref();
|
|
1822
|
-
}
|
|
1823
|
-
function sleep(ms) {
|
|
1824
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1825
|
-
}
|
|
1826
|
-
async function pollWithdrawalIntent(client, id, opts) {
|
|
1827
|
-
const started = Date.now();
|
|
1828
|
-
let lastStatus = "";
|
|
1829
|
-
while (Date.now() - started <= opts.timeoutSeconds * 1e3) {
|
|
1830
|
-
const result = await client.get(
|
|
1831
|
-
`/api/wallet/withdraw/intents/${encodeURIComponent(id)}`
|
|
1832
|
-
);
|
|
1833
|
-
const intent = result.intent;
|
|
1834
|
-
if (!opts.jsonMode && intent.status !== lastStatus) {
|
|
1835
|
-
process.stderr.write(dim(` Withdrawal status: ${intent.status}
|
|
1836
|
-
`));
|
|
1837
|
-
lastStatus = intent.status;
|
|
1838
|
-
}
|
|
1839
|
-
if (TERMINAL_WITHDRAWAL_STATUSES.has(intent.status)) return intent;
|
|
1840
|
-
await sleep(opts.intervalSeconds * 1e3);
|
|
1841
|
-
}
|
|
1842
|
-
throw new Error(`Timed out waiting for withdrawal intent ${id}`);
|
|
1843
|
-
}
|
|
1844
|
-
function registerWalletCommands(program2) {
|
|
1845
|
-
const wallet = program2.command("wallet").description("Inspect linked wallet funds and funding instructions");
|
|
1846
|
-
wallet.command("status").description("Show linked owner and Polymarket deposit wallet status").action(async () => {
|
|
1847
|
-
const globalOpts = program2.opts();
|
|
1848
|
-
const client = new ApiClient(globalOpts);
|
|
1849
|
-
requireAuth6(client);
|
|
1850
|
-
const status = await client.get("/api/wallet/status");
|
|
1851
|
-
if (globalOpts.json) {
|
|
1852
|
-
json(status);
|
|
1853
|
-
return;
|
|
1854
|
-
}
|
|
1855
|
-
displayWalletStatus(status);
|
|
1856
|
-
});
|
|
1857
|
-
wallet.command("balances").alias("funds").description("Show available wallet funds").action(async () => {
|
|
1858
|
-
const globalOpts = program2.opts();
|
|
1859
|
-
const client = new ApiClient(globalOpts);
|
|
1860
|
-
requireAuth6(client);
|
|
1861
|
-
const balances = await client.get("/api/wallet/balances");
|
|
1862
|
-
if (globalOpts.json) {
|
|
1863
|
-
json(balances);
|
|
1864
|
-
return;
|
|
1865
|
-
}
|
|
1866
|
-
displayWalletBalances(balances);
|
|
1867
|
-
});
|
|
1868
|
-
wallet.command("deposit").description("Show deposit address and supported Polygon assets").option("--asset <asset>", "pUSD | USDC.e | POL").option("--bridge", "Also request Polymarket Bridge deposit addresses").action(async (opts) => {
|
|
1869
|
-
const globalOpts = program2.opts();
|
|
1870
|
-
const client = new ApiClient(globalOpts);
|
|
1871
|
-
requireAuth6(client);
|
|
1872
|
-
const asset = normalizeWalletAsset(opts.asset);
|
|
1873
|
-
const result = await client.post("/api/wallet/deposit", {
|
|
1874
|
-
bridge: Boolean(opts.bridge)
|
|
1875
|
-
});
|
|
1876
|
-
const balances = {
|
|
1877
|
-
walletAddress: result.direct.walletAddress,
|
|
1878
|
-
chainId: result.direct.chainId,
|
|
1879
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1880
|
-
available: result.direct.assets.pUsd,
|
|
1881
|
-
assets: result.direct.assets
|
|
1882
|
-
};
|
|
1883
|
-
const instructions = buildDepositInstructions(balances, asset);
|
|
1884
|
-
if (globalOpts.json) {
|
|
1885
|
-
json({ ...result, instructions });
|
|
1886
|
-
return;
|
|
1887
|
-
}
|
|
1888
|
-
displayDepositInstructions(instructions);
|
|
1889
|
-
displayBridgeDeposit(result);
|
|
1890
|
-
});
|
|
1891
|
-
wallet.command("withdraw").description("Create a browser-signed withdrawal intent and poll for completion").requiredOption("--to <address>", "Recipient EVM wallet address", parseWalletAddress).requiredOption("--amount <amount>", "Amount to withdraw", parsePositiveNumber2).option("--asset <asset>", "Source asset; currently only pUSD is supported", "pUSD").option("--to-chain-id <id>", "Destination chain id", "137").option("--to-token-address <address>", "Destination token address", parseWalletAddress, POLYGON_NATIVE_USDC).option("--no-open", "Do not open the browser signing URL").option("--no-wait", "Do not poll for completion after creating the intent").option("--poll-interval <seconds>", "Polling interval in seconds", parsePositiveNumber2, 5).option("--timeout <seconds>", "Maximum seconds to wait for completion", parsePositiveNumber2, 600).action(async (opts) => {
|
|
1892
|
-
const globalOpts = program2.opts();
|
|
1893
|
-
const client = new ApiClient(globalOpts);
|
|
1894
|
-
requireAuth6(client);
|
|
1895
|
-
const created = await client.post(
|
|
1896
|
-
"/api/wallet/withdraw/intents",
|
|
1897
|
-
buildWithdrawRequestPayload(opts)
|
|
1898
|
-
);
|
|
1899
|
-
let intent = created.intent;
|
|
1900
|
-
if (opts.open !== false) {
|
|
1901
|
-
openBrowser(intent.signingUrl);
|
|
1902
|
-
}
|
|
1903
|
-
if (opts.wait !== false) {
|
|
1904
|
-
intent = await pollWithdrawalIntent(client, intent.id, {
|
|
1905
|
-
timeoutSeconds: opts.timeout ?? 600,
|
|
1906
|
-
intervalSeconds: opts.pollInterval ?? 5,
|
|
1907
|
-
jsonMode: Boolean(globalOpts.json)
|
|
1908
|
-
});
|
|
1909
|
-
}
|
|
1910
|
-
if (globalOpts.json) {
|
|
1911
|
-
json(intent);
|
|
1912
|
-
return;
|
|
1913
|
-
} else {
|
|
1914
|
-
displayWithdrawIntent(intent);
|
|
1915
|
-
}
|
|
1916
|
-
});
|
|
1917
1515
|
}
|
|
1918
1516
|
|
|
1919
1517
|
// src/commands/signal.ts
|
|
1920
|
-
import { InvalidArgumentError as
|
|
1518
|
+
import { InvalidArgumentError as InvalidArgumentError3 } from "commander";
|
|
1921
1519
|
import { readFile as readFile2 } from "fs/promises";
|
|
1922
1520
|
|
|
1923
1521
|
// src/signal-display.ts
|
|
1924
|
-
import
|
|
1522
|
+
import chalk6 from "chalk";
|
|
1925
1523
|
function pctFromSignalValue(value) {
|
|
1926
1524
|
if (value === null || value === void 0 || Number.isNaN(value)) return null;
|
|
1927
1525
|
return Math.abs(value) <= 1 ? value * 100 : value;
|
|
@@ -1934,14 +1532,14 @@ function formatGap(value) {
|
|
|
1934
1532
|
if (value === null || value === void 0 || Number.isNaN(value)) return "n/a";
|
|
1935
1533
|
const points = Math.abs(value) <= 1 ? value * 100 : value;
|
|
1936
1534
|
const formatted = `${points >= 0 ? "+" : ""}${points.toFixed(1)}pp`;
|
|
1937
|
-
if (points > 0) return
|
|
1938
|
-
if (points < 0) return
|
|
1939
|
-
return
|
|
1535
|
+
if (points > 0) return chalk6.green(formatted);
|
|
1536
|
+
if (points < 0) return chalk6.red(formatted);
|
|
1537
|
+
return chalk6.dim(formatted);
|
|
1940
1538
|
}
|
|
1941
1539
|
function formatStrength(value) {
|
|
1942
|
-
if (value === "strong") return
|
|
1943
|
-
if (value === "weak") return
|
|
1944
|
-
return value ?
|
|
1540
|
+
if (value === "strong") return chalk6.green("strong");
|
|
1541
|
+
if (value === "weak") return chalk6.yellow("weak");
|
|
1542
|
+
return value ? chalk6.dim(value) : "n/a";
|
|
1945
1543
|
}
|
|
1946
1544
|
function analysisItems(result) {
|
|
1947
1545
|
if (!result) return [];
|
|
@@ -1988,27 +1586,27 @@ function displaySignalAnalysis(response, globalOpts) {
|
|
|
1988
1586
|
for (const item of items.slice(0, 3)) {
|
|
1989
1587
|
const analysis = item.analysis;
|
|
1990
1588
|
if (!analysis) continue;
|
|
1991
|
-
process.stdout.write("\n " +
|
|
1589
|
+
process.stdout.write("\n " + chalk6.bold(truncate(titleFor(analysis), 76)) + "\n");
|
|
1992
1590
|
if (analysis.market_link) {
|
|
1993
|
-
process.stdout.write(" " +
|
|
1591
|
+
process.stdout.write(" " + chalk6.dim("Polymarket: ") + analysis.market_link + "\n");
|
|
1994
1592
|
}
|
|
1995
1593
|
if (analysis.key_factors && analysis.key_factors.length > 0) {
|
|
1996
|
-
process.stdout.write(" " +
|
|
1594
|
+
process.stdout.write(" " + chalk6.dim("Key factors: ") + analysis.key_factors.slice(0, 4).join("; ") + "\n");
|
|
1997
1595
|
}
|
|
1998
1596
|
if (analysis.research_findings) {
|
|
1999
1597
|
process.stdout.write(
|
|
2000
|
-
" " +
|
|
1598
|
+
" " + chalk6.dim("Research: ") + truncate(analysis.research_findings, 180) + "\n"
|
|
2001
1599
|
);
|
|
2002
1600
|
}
|
|
2003
1601
|
}
|
|
2004
1602
|
if (items.length > 3) {
|
|
2005
|
-
process.stdout.write(
|
|
1603
|
+
process.stdout.write(chalk6.dim(`
|
|
2006
1604
|
... and ${items.length - 3} more
|
|
2007
1605
|
`));
|
|
2008
1606
|
}
|
|
2009
1607
|
if (result?.strong_signal_count !== void 0) {
|
|
2010
1608
|
process.stdout.write(
|
|
2011
|
-
|
|
1609
|
+
chalk6.dim(`
|
|
2012
1610
|
Strong signals: ${result.strong_signal_count}
|
|
2013
1611
|
`)
|
|
2014
1612
|
);
|
|
@@ -2016,7 +1614,7 @@ function displaySignalAnalysis(response, globalOpts) {
|
|
|
2016
1614
|
}
|
|
2017
1615
|
|
|
2018
1616
|
// src/commands/signal.ts
|
|
2019
|
-
function
|
|
1617
|
+
function requireAuth6(client) {
|
|
2020
1618
|
if (!client.isAuthenticated) {
|
|
2021
1619
|
error("Not authenticated. Run `hl auth login` first.");
|
|
2022
1620
|
process.exit(1);
|
|
@@ -2028,7 +1626,7 @@ function collect(value, previous = []) {
|
|
|
2028
1626
|
function parseProbability(value) {
|
|
2029
1627
|
const n = Number(value);
|
|
2030
1628
|
if (!Number.isFinite(n) || n < 0 || n > 100) {
|
|
2031
|
-
throw new
|
|
1629
|
+
throw new InvalidArgumentError3("Expected a probability between 0 and 100");
|
|
2032
1630
|
}
|
|
2033
1631
|
return n;
|
|
2034
1632
|
}
|
|
@@ -2100,7 +1698,7 @@ function registerSignalCommands(program2) {
|
|
|
2100
1698
|
signal.command("analyze").description("Estimate true YES probability and compare it with market pricing").argument("[url]", "Polymarket market/event URL to analyze").option("--url <url>", "Additional Polymarket URL; repeat for multiple markets", collect, []).option("--market <file>", "Inline market JSON object/array; use '-' to read stdin").option("--context <text>", "Prior search notes or analysis context for the agent").option("--question <text>", "Inline market question when not using a URL").option("--description <text>", "Inline market description or resolution criteria").option("--yes-prob <prob>", "Current YES probability or price, e.g. 0.52 or 52", parseProbability).option("--no-prob <prob>", "Current NO probability or price, e.g. 0.48 or 48", parseProbability).option("--slug <slug>", "Inline market slug").option("--link <url>", "Inline market link").action(async (url, o) => {
|
|
2101
1699
|
const globalOpts = program2.opts();
|
|
2102
1700
|
const client = new ApiClient(globalOpts);
|
|
2103
|
-
|
|
1701
|
+
requireAuth6(client);
|
|
2104
1702
|
let payload;
|
|
2105
1703
|
try {
|
|
2106
1704
|
payload = await buildSignalPayload(url, o);
|
|
@@ -2116,17 +1714,196 @@ function registerSignalCommands(program2) {
|
|
|
2116
1714
|
});
|
|
2117
1715
|
}
|
|
2118
1716
|
|
|
1717
|
+
// src/commands/quote.ts
|
|
1718
|
+
import { InvalidArgumentError as InvalidArgumentError4 } from "commander";
|
|
1719
|
+
function requireAuth7(client) {
|
|
1720
|
+
if (!client.isAuthenticated) {
|
|
1721
|
+
error("Not authenticated. Run `hl auth login` first.");
|
|
1722
|
+
process.exit(1);
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
function parsePositiveNumber2(value) {
|
|
1726
|
+
const parsed = Number(value);
|
|
1727
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
1728
|
+
throw new InvalidArgumentError4("Expected a positive number");
|
|
1729
|
+
}
|
|
1730
|
+
return parsed;
|
|
1731
|
+
}
|
|
1732
|
+
function parseQuoteAction(value) {
|
|
1733
|
+
const normalized = value.trim().toUpperCase();
|
|
1734
|
+
if (normalized !== "BUY" && normalized !== "SELL") {
|
|
1735
|
+
throw new InvalidArgumentError4("Expected buy or sell");
|
|
1736
|
+
}
|
|
1737
|
+
return normalized;
|
|
1738
|
+
}
|
|
1739
|
+
function parseQuoteOutcome(value) {
|
|
1740
|
+
const normalized = value.trim().toUpperCase();
|
|
1741
|
+
if (normalized !== "YES" && normalized !== "NO") {
|
|
1742
|
+
throw new InvalidArgumentError4("Expected yes or no");
|
|
1743
|
+
}
|
|
1744
|
+
return normalized;
|
|
1745
|
+
}
|
|
1746
|
+
function parseQuoteRoute(value) {
|
|
1747
|
+
const normalized = value.trim().toLowerCase();
|
|
1748
|
+
if (normalized !== "auto" && normalized !== "aggressive" && normalized !== "passive") {
|
|
1749
|
+
throw new InvalidArgumentError4("Expected auto, aggressive, or passive");
|
|
1750
|
+
}
|
|
1751
|
+
return normalized;
|
|
1752
|
+
}
|
|
1753
|
+
function buildQuotePayload(instrument, opts) {
|
|
1754
|
+
const normalizedInstrument = instrument.trim();
|
|
1755
|
+
if (!normalizedInstrument) {
|
|
1756
|
+
throw new Error("Provide a Polymarket slug or URL.");
|
|
1757
|
+
}
|
|
1758
|
+
const hasCash = opts.cash !== void 0;
|
|
1759
|
+
const hasShares = opts.shares !== void 0;
|
|
1760
|
+
if (hasCash === hasShares) {
|
|
1761
|
+
throw new Error("Provide exactly one of --cash or --shares.");
|
|
1762
|
+
}
|
|
1763
|
+
if (opts.action === "SELL" && hasCash) {
|
|
1764
|
+
throw new Error("SELL quotes require --shares; --cash is BUY-only.");
|
|
1765
|
+
}
|
|
1766
|
+
return {
|
|
1767
|
+
instrument: normalizedInstrument,
|
|
1768
|
+
action: opts.action,
|
|
1769
|
+
outcome: opts.outcome,
|
|
1770
|
+
size: hasCash ? { type: "cash", amount_usd: opts.cash } : { type: "shares", shares: opts.shares },
|
|
1771
|
+
...opts.signalId && { signal_forecast_id: opts.signalId },
|
|
1772
|
+
...opts.capital !== void 0 && { portfolio_capital_usd: opts.capital },
|
|
1773
|
+
route: opts.route,
|
|
1774
|
+
persist: Boolean(opts.save)
|
|
1775
|
+
};
|
|
1776
|
+
}
|
|
1777
|
+
function formatNumber(value, digits = 2) {
|
|
1778
|
+
return typeof value === "number" && Number.isFinite(value) ? value.toLocaleString("en-US", { maximumFractionDigits: digits }) : "n/a";
|
|
1779
|
+
}
|
|
1780
|
+
function formatUsd(value) {
|
|
1781
|
+
return typeof value === "number" && Number.isFinite(value) ? currency(value) : "n/a";
|
|
1782
|
+
}
|
|
1783
|
+
function formatPrice(value) {
|
|
1784
|
+
return typeof value === "number" && Number.isFinite(value) ? `$${value.toFixed(4)}` : "n/a";
|
|
1785
|
+
}
|
|
1786
|
+
function formatPercent(value) {
|
|
1787
|
+
return typeof value === "number" && Number.isFinite(value) ? percent(value) : "n/a";
|
|
1788
|
+
}
|
|
1789
|
+
function displayQuotePreview(preview, globalOpts) {
|
|
1790
|
+
if (globalOpts.json) {
|
|
1791
|
+
json(preview);
|
|
1792
|
+
return;
|
|
1793
|
+
}
|
|
1794
|
+
if (preview.error) {
|
|
1795
|
+
error(preview.error);
|
|
1796
|
+
return;
|
|
1797
|
+
}
|
|
1798
|
+
const request = preview.request;
|
|
1799
|
+
const instrument = preview.instrument;
|
|
1800
|
+
const market = preview.market ?? {};
|
|
1801
|
+
const fill = preview.fill ?? {};
|
|
1802
|
+
const economics = preview.economics ?? {};
|
|
1803
|
+
const signal = preview.signal ?? {};
|
|
1804
|
+
const sizing = preview.sizing_suggestion ?? {};
|
|
1805
|
+
heading(`Quote Preview \u2014 ${preview.status ?? "UNAVAILABLE"}`);
|
|
1806
|
+
table([
|
|
1807
|
+
["Market", instrument.question ?? instrument.slug ?? "n/a"],
|
|
1808
|
+
["Venue", preview.venue ?? "polymarket"],
|
|
1809
|
+
["Action", `${request.action ?? "n/a"} ${request.outcome ?? "n/a"}`],
|
|
1810
|
+
["Route", `${request.route_selected ?? "n/a"} (requested ${request.route_requested ?? "auto"})`],
|
|
1811
|
+
["Observed", preview.observed_at ? new Date(preview.observed_at).toLocaleString() : "n/a"],
|
|
1812
|
+
["Expires", preview.expires_at ? new Date(preview.expires_at).toLocaleString() : "n/a"]
|
|
1813
|
+
]);
|
|
1814
|
+
process.stdout.write("\n");
|
|
1815
|
+
table([
|
|
1816
|
+
["Best bid", formatPrice(market.best_bid)],
|
|
1817
|
+
["Best ask", formatPrice(market.best_ask)],
|
|
1818
|
+
["Spread", formatPrice(market.spread)],
|
|
1819
|
+
["Bid depth", `${formatNumber(market.bid_depth_shares, 4)} shares`],
|
|
1820
|
+
["Ask depth", `${formatNumber(market.ask_depth_shares, 4)} shares`],
|
|
1821
|
+
["Requested cash", formatUsd(fill.requested_cash_usd)],
|
|
1822
|
+
["Requested shares", formatNumber(fill.requested_shares, 4)],
|
|
1823
|
+
["Fillable shares", formatNumber(fill.fillable_shares, 4)],
|
|
1824
|
+
["Safety-capped shares", formatNumber(fill.safety_capped_shares, 4)],
|
|
1825
|
+
["Fill ratio", formatPercent(fill.fill_ratio)],
|
|
1826
|
+
["Average price", formatPrice(fill.average_price)],
|
|
1827
|
+
["Worst price", formatPrice(fill.worst_price)],
|
|
1828
|
+
["Passive limit", formatPrice(fill.passive_limit_price)],
|
|
1829
|
+
["Slippage", typeof fill.slippage_bps === "number" ? `${formatNumber(fill.slippage_bps)} bps` : "n/a"]
|
|
1830
|
+
], ["Quote", "Value"]);
|
|
1831
|
+
process.stdout.write("\n");
|
|
1832
|
+
table([
|
|
1833
|
+
["Gross notional", formatUsd(economics.gross_notional_usd)],
|
|
1834
|
+
["Venue fee", formatUsd(economics.venue_fee_usd)],
|
|
1835
|
+
["Fee source", economics.fee_source ?? "unavailable"],
|
|
1836
|
+
[request.action === "SELL" ? "Net proceeds" : "All-in cost", formatUsd(
|
|
1837
|
+
request.action === "SELL" ? economics.net_proceeds_usd : economics.all_in_cost_usd
|
|
1838
|
+
)],
|
|
1839
|
+
["Max loss", formatUsd(economics.max_loss_usd)],
|
|
1840
|
+
["Max payout", formatUsd(economics.max_payout_usd)],
|
|
1841
|
+
["Profit at payout", formatUsd(economics.max_profit_usd)],
|
|
1842
|
+
["Foregone payout", request.action === "SELL" ? formatUsd(economics.foregone_payout_usd) : "n/a"],
|
|
1843
|
+
["Break-even probability", formatPercent(economics.break_even_probability)]
|
|
1844
|
+
], ["Economics", "Value"]);
|
|
1845
|
+
if (preview.signal) {
|
|
1846
|
+
process.stdout.write("\n");
|
|
1847
|
+
table([
|
|
1848
|
+
["Forecast YES", formatPercent(signal.forecast_yes)],
|
|
1849
|
+
["Forecast interval", `${formatPercent(signal.lower_bound)} \u2013 ${formatPercent(signal.upper_bound)}`],
|
|
1850
|
+
["Midpoint edge", formatPercent(signal.midpoint_edge)],
|
|
1851
|
+
["Conservative edge", formatPercent(signal.conservative_edge)]
|
|
1852
|
+
], ["Signal", "Value"]);
|
|
1853
|
+
}
|
|
1854
|
+
if (preview.sizing_suggestion) {
|
|
1855
|
+
process.stdout.write("\n");
|
|
1856
|
+
table([
|
|
1857
|
+
["Suggested cash", formatUsd(sizing.suggested_max_spend_usd)],
|
|
1858
|
+
["Suggested shares", formatNumber(sizing.suggested_shares, 4)],
|
|
1859
|
+
["Capital fraction", formatPercent(sizing.allocation_fraction)]
|
|
1860
|
+
], ["Non-binding sizing", "Value"]);
|
|
1861
|
+
}
|
|
1862
|
+
if (preview.risks?.length) {
|
|
1863
|
+
process.stdout.write("\n");
|
|
1864
|
+
for (const risk of preview.risks) warn(risk);
|
|
1865
|
+
}
|
|
1866
|
+
if (preview.id) {
|
|
1867
|
+
process.stdout.write("\n" + dim(` Saved preview: ${preview.id}
|
|
1868
|
+
`));
|
|
1869
|
+
}
|
|
1870
|
+
process.stdout.write("\n");
|
|
1871
|
+
warn("Preview only \u2014 no order was signed or submitted.");
|
|
1872
|
+
const marketUrl = instrument.market_url;
|
|
1873
|
+
if (marketUrl) process.stdout.write(dim(` Market: ${marketUrl}
|
|
1874
|
+
`));
|
|
1875
|
+
}
|
|
1876
|
+
function registerQuoteCommand(program2) {
|
|
1877
|
+
program2.command("quote").description("Preview the cost, liquidity, and risk of a Polymarket trade").argument("<slug-or-url>", "Polymarket market slug or URL").requiredOption("--action <action>", "buy | sell", parseQuoteAction).requiredOption("--outcome <outcome>", "yes | no", parseQuoteOutcome).option("--cash <usd>", "Maximum cash to spend (BUY only)", parsePositiveNumber2).option("--shares <shares>", "Number of outcome shares", parsePositiveNumber2).option("--signal-id <uuid>", "Saved Signal forecast to include in edge calculations").option("--capital <usd>", "Manual portfolio capital for non-binding BUY sizing", parsePositiveNumber2).option("--route <route>", "auto | aggressive | passive", parseQuoteRoute, "auto").option("--save", "Save a freshly generated preview to quote history").action(async (instrument, opts) => {
|
|
1878
|
+
const globalOpts = program2.opts();
|
|
1879
|
+
const client = new ApiClient(globalOpts);
|
|
1880
|
+
requireAuth7(client);
|
|
1881
|
+
let payload;
|
|
1882
|
+
try {
|
|
1883
|
+
payload = buildQuotePayload(instrument, opts);
|
|
1884
|
+
} catch (error2) {
|
|
1885
|
+
error(error2 instanceof Error ? error2.message : String(error2));
|
|
1886
|
+
process.exit(1);
|
|
1887
|
+
}
|
|
1888
|
+
if (!globalOpts.json) {
|
|
1889
|
+
process.stderr.write(dim(" Refreshing public market data and order book...\n"));
|
|
1890
|
+
}
|
|
1891
|
+
const preview = await client.post("/api/quote", payload);
|
|
1892
|
+
displayQuotePreview(preview, globalOpts);
|
|
1893
|
+
});
|
|
1894
|
+
}
|
|
1895
|
+
|
|
2119
1896
|
// src/index.ts
|
|
2120
1897
|
var program = new Command4();
|
|
2121
|
-
program.name("hl").description("Hedge Layer CLI \u2014 prediction market intelligence from the terminal").version("
|
|
1898
|
+
program.name("hl").description("Hedge Layer CLI \u2014 prediction market intelligence from the terminal").version("3.0.0").option("--json", "Output as JSON (machine-readable)").option("--api-url <url>", "Override API base URL").option("--token <token>", "Override stored API token").option("--verbose", "Show HTTP request details").option("--no-color", "Disable colored output");
|
|
2122
1899
|
registerAuthCommands(program);
|
|
2123
1900
|
registerBriefCommands(program);
|
|
2124
1901
|
registerProfileCommand(program);
|
|
2125
1902
|
registerResearchCommands(program);
|
|
2126
1903
|
registerFeedCommand(program);
|
|
2127
|
-
registerWalletCommands(program);
|
|
2128
1904
|
registerLpCommands(program);
|
|
2129
1905
|
registerSignalCommands(program);
|
|
1906
|
+
registerQuoteCommand(program);
|
|
2130
1907
|
program.hook("preAction", (_thisCommand, actionCommand) => {
|
|
2131
1908
|
const opts = program.opts();
|
|
2132
1909
|
if (opts.color === false) {
|