@hedge-layer/cli 3.0.1 → 4.0.1
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 +6 -29
- package/dist/index.mjs +80 -332
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -63,12 +63,7 @@ hl quote "example-market" --action buy --outcome yes --cash 25
|
|
|
63
63
|
hl quote "example-market" --action buy --outcome yes --cash 25 \
|
|
64
64
|
--signal-id <forecast-id> --capital 1000 --save
|
|
65
65
|
|
|
66
|
-
# 7.
|
|
67
|
-
hl --json feed liquidity-provider --limit 15 | jq '{ markets: .markets }' > markets.json
|
|
68
|
-
hl lp allocator --markets markets.json
|
|
69
|
-
your execution workflow
|
|
70
|
-
|
|
71
|
-
# 8. Analyze a market probability edge
|
|
66
|
+
# 7. Analyze a market probability edge
|
|
72
67
|
hl signal analyze "https://polymarket.com/event/example-market"
|
|
73
68
|
```
|
|
74
69
|
|
|
@@ -91,35 +86,17 @@ estimated fill, slippage, fees, cost or proceeds, payout risk, and optional
|
|
|
91
86
|
Signal edge. It never signs or submits an order. `--cash` is BUY-only; SELL
|
|
92
87
|
quotes require `--shares`.
|
|
93
88
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
```bash
|
|
98
|
-
hl lp allocator --markets markets.json
|
|
99
|
-
```
|
|
89
|
+
Liquidity allocation, wallet management, and trade execution are outside the
|
|
90
|
+
official `hl` CLI.
|
|
100
91
|
|
|
101
|
-
|
|
92
|
+
For a broader discovery screen, `hl feed ensemble` runs several feed lenses
|
|
93
|
+
(liquid core, active volume, movers, new markets, uncertainty, and LP quality),
|
|
94
|
+
de-duplicates by slug, and writes a ranked candidate file:
|
|
102
95
|
|
|
103
96
|
```bash
|
|
104
97
|
hl feed ensemble --limit 25 --output candidates.json
|
|
105
|
-
# or: hl --json feed lp-opportunity --limit 15 > markets.json
|
|
106
|
-
external-pnl-export --json > pnl.json # optional: wallet PnL + live inventory
|
|
107
|
-
hl lp allocator --markets candidates.json --pnl pnl.json --allocations pnl.json
|
|
108
|
-
your execution workflow ...
|
|
109
98
|
```
|
|
110
99
|
|
|
111
|
-
`hl feed ensemble` runs several feed lenses (liquid core, active volume, movers, new markets, uncertainty, and LP quality), de-duplicates by slug, and writes a daily candidate file.
|
|
112
|
-
|
|
113
|
-
`hl lp allocator` submits the candidate market list through the web API to the
|
|
114
|
-
allocator agent. Allocator output shows target capital, quote regime, failed
|
|
115
|
-
safety checks, and split spread/reward economics. Trade execution stays outside
|
|
116
|
-
the `hl` CLI.
|
|
117
|
-
|
|
118
|
-
`--allocations` tells the allocator what you already hold (enabling HOLD,
|
|
119
|
-
REDUCE, and EXIT decisions), and `--pnl` feeds per-market PnL into its caution
|
|
120
|
-
overlay so borderline allocations on losing markets are downgraded to WATCH or
|
|
121
|
-
HOLD. Both flags accept the same external wallet/inventory export shape.
|
|
122
|
-
|
|
123
100
|
Signal-agent analysis is available under `hl signal`:
|
|
124
101
|
|
|
125
102
|
```bash
|
package/dist/index.mjs
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { Command as
|
|
4
|
+
import { Command as Command3 } from "commander";
|
|
5
5
|
|
|
6
6
|
// src/commands/auth.ts
|
|
7
7
|
import readline from "readline/promises";
|
|
8
|
+
import { Writable } from "stream";
|
|
8
9
|
|
|
9
10
|
// src/config.ts
|
|
10
11
|
import { readFileSync, writeFileSync, mkdirSync, existsSync, unlinkSync } from "fs";
|
|
@@ -292,42 +293,59 @@ function relativeTime(date) {
|
|
|
292
293
|
}
|
|
293
294
|
|
|
294
295
|
// src/commands/auth.ts
|
|
296
|
+
async function promptHidden(prompt, options = {}) {
|
|
297
|
+
const input = options.input ?? process.stdin;
|
|
298
|
+
const output = options.output ?? process.stderr;
|
|
299
|
+
const createInterface = options.createInterface ?? readline.createInterface;
|
|
300
|
+
const mutedOutput = new Writable({
|
|
301
|
+
write(_chunk, _encoding, callback) {
|
|
302
|
+
callback();
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
const rl = createInterface({
|
|
306
|
+
input,
|
|
307
|
+
output: mutedOutput,
|
|
308
|
+
terminal: Boolean(input.isTTY)
|
|
309
|
+
});
|
|
310
|
+
output.write(prompt);
|
|
311
|
+
try {
|
|
312
|
+
return (await rl.question("")).trim();
|
|
313
|
+
} finally {
|
|
314
|
+
output.write("\n");
|
|
315
|
+
rl.close();
|
|
316
|
+
}
|
|
317
|
+
}
|
|
295
318
|
function registerAuthCommands(program2) {
|
|
296
319
|
const auth = program2.command("auth").description("Manage API authentication");
|
|
297
320
|
auth.command("login").description("Authenticate with a Hedge Layer API token").option("--api-url <url>", "API base URL", DEFAULT_API_URL).action(async (cmdOpts) => {
|
|
298
321
|
const globalOpts = program2.opts();
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
process.stderr.write(
|
|
303
|
-
`Create an API token at ${bold("https://hedgelayer.ai/account/settings")} \u2192 API Tokens
|
|
322
|
+
heading("Hedge Layer CLI \u2014 Login");
|
|
323
|
+
process.stderr.write(
|
|
324
|
+
`Create an API token at ${bold("https://hedgelayer.ai/account/settings")} \u2192 API Tokens
|
|
304
325
|
|
|
305
326
|
`
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
}
|
|
312
|
-
const apiUrl = globalOpts.apiUrl ?? cmdOpts.apiUrl ?? DEFAULT_API_URL;
|
|
313
|
-
const client = new ApiClient({ token, apiUrl });
|
|
314
|
-
process.stderr.write("\nValidating token...");
|
|
315
|
-
let profile;
|
|
316
|
-
try {
|
|
317
|
-
profile = await client.get("/api/profile");
|
|
318
|
-
} catch {
|
|
319
|
-
process.stderr.write("\n");
|
|
320
|
-
error("Token validation failed. Check your token and try again.");
|
|
321
|
-
process.exit(1);
|
|
322
|
-
}
|
|
323
|
-
process.stderr.write(" done\n\n");
|
|
324
|
-
saveConfig({ api_url: apiUrl, token });
|
|
325
|
-
success(`Logged in as ${bold(profile.handle || profile.user_id)}`);
|
|
326
|
-
process.stderr.write(` Config saved to ${dim(configPath())}
|
|
327
|
-
`);
|
|
328
|
-
} finally {
|
|
329
|
-
rl.close();
|
|
327
|
+
);
|
|
328
|
+
const token = await promptHidden("Paste your API token: ");
|
|
329
|
+
if (!token.startsWith("hl_") || token.length !== 43) {
|
|
330
|
+
error('Invalid token format. Tokens start with "hl_" and are 43 characters.');
|
|
331
|
+
process.exit(1);
|
|
330
332
|
}
|
|
333
|
+
const apiUrl = globalOpts.apiUrl ?? cmdOpts.apiUrl ?? DEFAULT_API_URL;
|
|
334
|
+
const client = new ApiClient({ token, apiUrl });
|
|
335
|
+
process.stderr.write("\nValidating token...");
|
|
336
|
+
let profile;
|
|
337
|
+
try {
|
|
338
|
+
profile = await client.get("/api/profile");
|
|
339
|
+
} catch {
|
|
340
|
+
process.stderr.write("\n");
|
|
341
|
+
error("Token validation failed. Check your token and try again.");
|
|
342
|
+
process.exit(1);
|
|
343
|
+
}
|
|
344
|
+
process.stderr.write(" done\n\n");
|
|
345
|
+
saveConfig({ api_url: apiUrl, token });
|
|
346
|
+
success(`Logged in as ${bold(profile.handle || profile.user_id)}`);
|
|
347
|
+
process.stderr.write(` Config saved to ${dim(configPath())}
|
|
348
|
+
`);
|
|
331
349
|
});
|
|
332
350
|
auth.command("status").description("Show current authentication status").action(async () => {
|
|
333
351
|
const globalOpts = program2.opts();
|
|
@@ -1245,281 +1263,12 @@ function registerFeedCommand(program2) {
|
|
|
1245
1263
|
});
|
|
1246
1264
|
}
|
|
1247
1265
|
|
|
1248
|
-
// src/commands/
|
|
1249
|
-
import { InvalidArgumentError as InvalidArgumentError2 } from "commander";
|
|
1250
|
-
|
|
1251
|
-
// src/allocator-display.ts
|
|
1252
|
-
import chalk5 from "chalk";
|
|
1253
|
-
function actionColor(action) {
|
|
1254
|
-
switch (action) {
|
|
1255
|
-
case "ALLOCATE":
|
|
1256
|
-
case "INCREASE":
|
|
1257
|
-
return chalk5.green(action);
|
|
1258
|
-
case "REDUCE":
|
|
1259
|
-
case "EXIT":
|
|
1260
|
-
return chalk5.yellow(action);
|
|
1261
|
-
case "SKIP":
|
|
1262
|
-
return chalk5.dim(action);
|
|
1263
|
-
case "WATCH":
|
|
1264
|
-
return chalk5.cyan(action);
|
|
1265
|
-
default:
|
|
1266
|
-
return action;
|
|
1267
|
-
}
|
|
1268
|
-
}
|
|
1269
|
-
function num2(value, fallback = 0) {
|
|
1270
|
-
const n = Number(value);
|
|
1271
|
-
return Number.isFinite(n) ? n : fallback;
|
|
1272
|
-
}
|
|
1273
|
-
function displayAllocatorCycleResult(result, globalOpts) {
|
|
1274
|
-
if (globalOpts.json) {
|
|
1275
|
-
json(result);
|
|
1276
|
-
return;
|
|
1277
|
-
}
|
|
1278
|
-
const decisions = Array.isArray(result.decisions) ? result.decisions : [];
|
|
1279
|
-
const summary = result.summary ?? {};
|
|
1280
|
-
const dryRun = result.dry_run === false ? "live" : "dry run";
|
|
1281
|
-
heading(`Allocator Cycle \u2014 ${dryRun}`);
|
|
1282
|
-
process.stdout.write(
|
|
1283
|
-
chalk5.dim(
|
|
1284
|
-
` ${num2(result.total_markets, decisions.length)} markets \xB7 ${currency(
|
|
1285
|
-
num2(summary.target_capital)
|
|
1286
|
-
)} target capital
|
|
1287
|
-
|
|
1288
|
-
`
|
|
1289
|
-
)
|
|
1290
|
-
);
|
|
1291
|
-
if (decisions.length === 0) {
|
|
1292
|
-
warn("No allocator decisions returned.");
|
|
1293
|
-
return;
|
|
1294
|
-
}
|
|
1295
|
-
const rows = decisions.map((d) => {
|
|
1296
|
-
const action = String(d.action ?? "UNKNOWN");
|
|
1297
|
-
const score = num2(d.score?.score);
|
|
1298
|
-
const expected = num2(d.score?.expected_return_daily_pct);
|
|
1299
|
-
const failedChecks = Array.isArray(d.safety_checks) ? d.safety_checks.filter((check) => check.passed === false).length : 0;
|
|
1300
|
-
return [
|
|
1301
|
-
truncate(String(d.question ?? d.market_slug ?? "\u2014"), 42),
|
|
1302
|
-
actionColor(action),
|
|
1303
|
-
currency(num2(d.target_capital)),
|
|
1304
|
-
signedCurrency(num2(d.capital_delta)),
|
|
1305
|
-
`${expected.toFixed(3)}%`,
|
|
1306
|
-
regimeLabel(String(d.quote_regime ?? "\u2014")),
|
|
1307
|
-
String(Math.round(score)),
|
|
1308
|
-
failedChecks === 0 ? chalk5.green("0") : chalk5.yellow(String(failedChecks))
|
|
1309
|
-
];
|
|
1310
|
-
});
|
|
1311
|
-
table(rows, [
|
|
1312
|
-
"Market",
|
|
1313
|
-
"Action",
|
|
1314
|
-
"Target",
|
|
1315
|
-
"Delta",
|
|
1316
|
-
"Exp/day",
|
|
1317
|
-
"Regime",
|
|
1318
|
-
"Score",
|
|
1319
|
-
"Fails"
|
|
1320
|
-
]);
|
|
1321
|
-
process.stdout.write("\n");
|
|
1322
|
-
for (const decision of decisions.slice(0, 5)) {
|
|
1323
|
-
const action = String(decision.action ?? "UNKNOWN");
|
|
1324
|
-
process.stdout.write(" " + chalk5.dim("\u25B8 ") + truncate(String(decision.question ?? decision.market_slug ?? "\u2014"), 65) + "\n");
|
|
1325
|
-
process.stdout.write(" " + chalk5.dim("Action: ") + actionColor(action));
|
|
1326
|
-
if (decision.rationale) {
|
|
1327
|
-
process.stdout.write(" " + chalk5.dim("Rationale: ") + truncate(decision.rationale, 120));
|
|
1328
|
-
}
|
|
1329
|
-
process.stdout.write("\n");
|
|
1330
|
-
const economics = decision.economics ?? {};
|
|
1331
|
-
if (economics.realized_spread_pnl !== void 0 || economics.reward_income !== void 0 || economics.net_realized_pnl !== void 0) {
|
|
1332
|
-
process.stdout.write(
|
|
1333
|
-
" " + chalk5.dim("Economics: ") + `spread ${signedCurrency(num2(economics.realized_spread_pnl))}, rewards ${signedCurrency(num2(economics.reward_income))}, net ${signedCurrency(num2(economics.net_realized_pnl))}
|
|
1334
|
-
`
|
|
1335
|
-
);
|
|
1336
|
-
}
|
|
1337
|
-
}
|
|
1338
|
-
if (decisions.length > 5) {
|
|
1339
|
-
process.stdout.write(chalk5.dim(`
|
|
1340
|
-
\u2026 and ${decisions.length - 5} more decisions
|
|
1341
|
-
`));
|
|
1342
|
-
}
|
|
1343
|
-
}
|
|
1344
|
-
function regimeLabel(value) {
|
|
1345
|
-
switch (value) {
|
|
1346
|
-
case "reward_optimized":
|
|
1347
|
-
return chalk5.green("reward");
|
|
1348
|
-
case "defensive":
|
|
1349
|
-
return chalk5.yellow("defense");
|
|
1350
|
-
case "no_quote":
|
|
1351
|
-
return chalk5.red("no quote");
|
|
1352
|
-
default:
|
|
1353
|
-
return chalk5.dim(value);
|
|
1354
|
-
}
|
|
1355
|
-
}
|
|
1356
|
-
function signedCurrency(value) {
|
|
1357
|
-
const formatted = currency(Math.abs(value));
|
|
1358
|
-
if (value > 0) return chalk5.green(`+${formatted}`);
|
|
1359
|
-
if (value < 0) return chalk5.yellow(`-${formatted}`);
|
|
1360
|
-
return chalk5.dim("$0.00");
|
|
1361
|
-
}
|
|
1362
|
-
|
|
1363
|
-
// src/commands/allocator.ts
|
|
1266
|
+
// src/commands/signal.ts
|
|
1364
1267
|
import { InvalidArgumentError } from "commander";
|
|
1365
1268
|
import { readFile } from "fs/promises";
|
|
1366
|
-
function parsePositiveNumber(value) {
|
|
1367
|
-
const n = Number(value);
|
|
1368
|
-
if (!Number.isFinite(n) || n <= 0) {
|
|
1369
|
-
throw new InvalidArgumentError("Expected a positive number");
|
|
1370
|
-
}
|
|
1371
|
-
return n;
|
|
1372
|
-
}
|
|
1373
|
-
function parseAllocationsInput(parsed) {
|
|
1374
|
-
const rows = extractArray(parsed, "allocations");
|
|
1375
|
-
if (!rows) {
|
|
1376
|
-
throw new Error("Allocations JSON must be an array or { allocations: [...] }");
|
|
1377
|
-
}
|
|
1378
|
-
return rows;
|
|
1379
|
-
}
|
|
1380
|
-
function parsePnlContextInput(parsed) {
|
|
1381
|
-
const rows = extractArray(parsed, "pnl_context");
|
|
1382
|
-
if (!rows) {
|
|
1383
|
-
throw new Error("PnL JSON must be an array or { pnl_context: [...] }");
|
|
1384
|
-
}
|
|
1385
|
-
return rows;
|
|
1386
|
-
}
|
|
1387
|
-
async function readAllocations(path) {
|
|
1388
|
-
if (!path) return [];
|
|
1389
|
-
return parseAllocationsInput(await readJsonInput(path));
|
|
1390
|
-
}
|
|
1391
|
-
async function readPnlContext(path) {
|
|
1392
|
-
if (!path) return [];
|
|
1393
|
-
return parsePnlContextInput(await readJsonInput(path));
|
|
1394
|
-
}
|
|
1395
|
-
async function readMarketPayload(path) {
|
|
1396
|
-
if (!path) return void 0;
|
|
1397
|
-
const parsed = await readJsonInput(path);
|
|
1398
|
-
const rows = extractArray(parsed, "markets");
|
|
1399
|
-
if (!rows) {
|
|
1400
|
-
throw new Error("Markets JSON must be an array or { markets: [...] }");
|
|
1401
|
-
}
|
|
1402
|
-
return rows;
|
|
1403
|
-
}
|
|
1404
|
-
function extractArray(parsed, key) {
|
|
1405
|
-
if (Array.isArray(parsed)) {
|
|
1406
|
-
return parsed;
|
|
1407
|
-
}
|
|
1408
|
-
if (parsed && typeof parsed === "object" && Array.isArray(parsed[key])) {
|
|
1409
|
-
return parsed[key];
|
|
1410
|
-
}
|
|
1411
|
-
return void 0;
|
|
1412
|
-
}
|
|
1413
|
-
async function readJsonInput(path) {
|
|
1414
|
-
const raw = path === "-" ? await readStdin() : await readFile(path, "utf8");
|
|
1415
|
-
return JSON.parse(raw);
|
|
1416
|
-
}
|
|
1417
|
-
async function readStdin() {
|
|
1418
|
-
const chunks = [];
|
|
1419
|
-
for await (const chunk of process.stdin) {
|
|
1420
|
-
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
1421
|
-
}
|
|
1422
|
-
return Buffer.concat(chunks).toString("utf8");
|
|
1423
|
-
}
|
|
1424
|
-
|
|
1425
|
-
// src/commands/lp.ts
|
|
1426
|
-
function requireAuth5(client) {
|
|
1427
|
-
if (!client.isAuthenticated) {
|
|
1428
|
-
error("Not authenticated. Run `hl auth login` first.");
|
|
1429
|
-
process.exit(1);
|
|
1430
|
-
}
|
|
1431
|
-
}
|
|
1432
|
-
function parseNonNegative(value) {
|
|
1433
|
-
const n = Number(value);
|
|
1434
|
-
if (!Number.isFinite(n) || n < 0) {
|
|
1435
|
-
throw new InvalidArgumentError2("Expected a non-negative number");
|
|
1436
|
-
}
|
|
1437
|
-
return n;
|
|
1438
|
-
}
|
|
1439
|
-
function parsePositiveInt(value) {
|
|
1440
|
-
const n = Number(value);
|
|
1441
|
-
if (!Number.isInteger(n) || n < 1) {
|
|
1442
|
-
throw new InvalidArgumentError2("Expected a positive integer");
|
|
1443
|
-
}
|
|
1444
|
-
return n;
|
|
1445
|
-
}
|
|
1446
|
-
function strategyFromAllocatorOptions(opts) {
|
|
1447
|
-
return {
|
|
1448
|
-
id: "cli-lp-allocator",
|
|
1449
|
-
name: "CLI LP allocator dry run",
|
|
1450
|
-
status: opts.paused ? "paused" : "dry_run",
|
|
1451
|
-
...opts.totalHoldings !== void 0 && { total_holdings: opts.totalHoldings },
|
|
1452
|
-
...opts.capitalLimitPct !== void 0 && { capital_limit_pct: opts.capitalLimitPct },
|
|
1453
|
-
...opts.perMarketLimitPct !== void 0 && { per_market_limit_pct: opts.perMarketLimitPct },
|
|
1454
|
-
capital_limit: opts.capitalLimit,
|
|
1455
|
-
per_market_limit: opts.perMarketLimit,
|
|
1456
|
-
min_expected_return_daily_pct: opts.minExpectedReturnDailyPct,
|
|
1457
|
-
max_inventory_imbalance: opts.maxInventoryImbalance,
|
|
1458
|
-
volatility_fill_spike_threshold: opts.volatilityFillSpikeThreshold,
|
|
1459
|
-
event_no_quote_minutes_before: opts.eventNoQuoteMinutesBefore,
|
|
1460
|
-
event_no_quote_minutes_after: opts.eventNoQuoteMinutesAfter,
|
|
1461
|
-
min_liquidity: opts.allocatorMinLiquidity,
|
|
1462
|
-
max_spread: opts.maxSpread,
|
|
1463
|
-
min_days_to_end: opts.allocatorMinDaysToEnd,
|
|
1464
|
-
max_markets: opts.maxMarkets
|
|
1465
|
-
};
|
|
1466
|
-
}
|
|
1467
|
-
function validateAllocatorPercentageSizing(opts) {
|
|
1468
|
-
const usesPercentageSizing = opts.capitalLimitPct !== void 0 || opts.perMarketLimitPct !== void 0;
|
|
1469
|
-
if (usesPercentageSizing && opts.totalHoldings === void 0) {
|
|
1470
|
-
error(
|
|
1471
|
-
"Percentage sizing requires --total-holdings so the allocator can convert percentages into dollar caps."
|
|
1472
|
-
);
|
|
1473
|
-
process.exit(1);
|
|
1474
|
-
}
|
|
1475
|
-
}
|
|
1476
|
-
async function runAllocatorCycle(client, payload) {
|
|
1477
|
-
return client.post("/api/lp/allocator", payload);
|
|
1478
|
-
}
|
|
1479
|
-
function registerLpCommands(program2) {
|
|
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) => {
|
|
1482
|
-
const globalOpts = program2.opts();
|
|
1483
|
-
const client = new ApiClient(globalOpts);
|
|
1484
|
-
requireAuth5(client);
|
|
1485
|
-
validateAllocatorPercentageSizing(opts);
|
|
1486
|
-
const markets = await readMarketPayload(opts.markets);
|
|
1487
|
-
if (!markets || markets.length === 0) {
|
|
1488
|
-
error("Markets JSON must include at least one market.");
|
|
1489
|
-
process.exit(1);
|
|
1490
|
-
}
|
|
1491
|
-
if (opts.allocations === "-" && opts.pnl === "-") {
|
|
1492
|
-
error("Only one of --allocations and --pnl can read from stdin.");
|
|
1493
|
-
process.exit(1);
|
|
1494
|
-
}
|
|
1495
|
-
const pnlContext = await readPnlContext(opts.pnl);
|
|
1496
|
-
const payload = {
|
|
1497
|
-
strategy: strategyFromAllocatorOptions(opts),
|
|
1498
|
-
markets,
|
|
1499
|
-
allocations: await readAllocations(opts.allocations),
|
|
1500
|
-
...pnlContext.length > 0 && { pnl_context: pnlContext }
|
|
1501
|
-
};
|
|
1502
|
-
if (!globalOpts.json) {
|
|
1503
|
-
process.stderr.write(
|
|
1504
|
-
dim(` Running allocator dry-run on ${markets.length} provided candidates...
|
|
1505
|
-
`)
|
|
1506
|
-
);
|
|
1507
|
-
}
|
|
1508
|
-
const response = await runAllocatorCycle(client, payload);
|
|
1509
|
-
if (globalOpts.json) {
|
|
1510
|
-
json(response);
|
|
1511
|
-
} else {
|
|
1512
|
-
displayAllocatorCycleResult(response.result ?? {}, globalOpts);
|
|
1513
|
-
}
|
|
1514
|
-
});
|
|
1515
|
-
}
|
|
1516
|
-
|
|
1517
|
-
// src/commands/signal.ts
|
|
1518
|
-
import { InvalidArgumentError as InvalidArgumentError3 } from "commander";
|
|
1519
|
-
import { readFile as readFile2 } from "fs/promises";
|
|
1520
1269
|
|
|
1521
1270
|
// src/signal-display.ts
|
|
1522
|
-
import
|
|
1271
|
+
import chalk5 from "chalk";
|
|
1523
1272
|
function pctFromSignalValue(value) {
|
|
1524
1273
|
if (value === null || value === void 0 || Number.isNaN(value)) return null;
|
|
1525
1274
|
return Math.abs(value) <= 1 ? value * 100 : value;
|
|
@@ -1532,14 +1281,14 @@ function formatGap(value) {
|
|
|
1532
1281
|
if (value === null || value === void 0 || Number.isNaN(value)) return "n/a";
|
|
1533
1282
|
const points = Math.abs(value) <= 1 ? value * 100 : value;
|
|
1534
1283
|
const formatted = `${points >= 0 ? "+" : ""}${points.toFixed(1)}pp`;
|
|
1535
|
-
if (points > 0) return
|
|
1536
|
-
if (points < 0) return
|
|
1537
|
-
return
|
|
1284
|
+
if (points > 0) return chalk5.green(formatted);
|
|
1285
|
+
if (points < 0) return chalk5.red(formatted);
|
|
1286
|
+
return chalk5.dim(formatted);
|
|
1538
1287
|
}
|
|
1539
1288
|
function formatStrength(value) {
|
|
1540
|
-
if (value === "strong") return
|
|
1541
|
-
if (value === "weak") return
|
|
1542
|
-
return value ?
|
|
1289
|
+
if (value === "strong") return chalk5.green("strong");
|
|
1290
|
+
if (value === "weak") return chalk5.yellow("weak");
|
|
1291
|
+
return value ? chalk5.dim(value) : "n/a";
|
|
1543
1292
|
}
|
|
1544
1293
|
function analysisItems(result) {
|
|
1545
1294
|
if (!result) return [];
|
|
@@ -1586,27 +1335,27 @@ function displaySignalAnalysis(response, globalOpts) {
|
|
|
1586
1335
|
for (const item of items.slice(0, 3)) {
|
|
1587
1336
|
const analysis = item.analysis;
|
|
1588
1337
|
if (!analysis) continue;
|
|
1589
|
-
process.stdout.write("\n " +
|
|
1338
|
+
process.stdout.write("\n " + chalk5.bold(truncate(titleFor(analysis), 76)) + "\n");
|
|
1590
1339
|
if (analysis.market_link) {
|
|
1591
|
-
process.stdout.write(" " +
|
|
1340
|
+
process.stdout.write(" " + chalk5.dim("Polymarket: ") + analysis.market_link + "\n");
|
|
1592
1341
|
}
|
|
1593
1342
|
if (analysis.key_factors && analysis.key_factors.length > 0) {
|
|
1594
|
-
process.stdout.write(" " +
|
|
1343
|
+
process.stdout.write(" " + chalk5.dim("Key factors: ") + analysis.key_factors.slice(0, 4).join("; ") + "\n");
|
|
1595
1344
|
}
|
|
1596
1345
|
if (analysis.research_findings) {
|
|
1597
1346
|
process.stdout.write(
|
|
1598
|
-
" " +
|
|
1347
|
+
" " + chalk5.dim("Research: ") + truncate(analysis.research_findings, 180) + "\n"
|
|
1599
1348
|
);
|
|
1600
1349
|
}
|
|
1601
1350
|
}
|
|
1602
1351
|
if (items.length > 3) {
|
|
1603
|
-
process.stdout.write(
|
|
1352
|
+
process.stdout.write(chalk5.dim(`
|
|
1604
1353
|
... and ${items.length - 3} more
|
|
1605
1354
|
`));
|
|
1606
1355
|
}
|
|
1607
1356
|
if (result?.strong_signal_count !== void 0) {
|
|
1608
1357
|
process.stdout.write(
|
|
1609
|
-
|
|
1358
|
+
chalk5.dim(`
|
|
1610
1359
|
Strong signals: ${result.strong_signal_count}
|
|
1611
1360
|
`)
|
|
1612
1361
|
);
|
|
@@ -1614,7 +1363,7 @@ function displaySignalAnalysis(response, globalOpts) {
|
|
|
1614
1363
|
}
|
|
1615
1364
|
|
|
1616
1365
|
// src/commands/signal.ts
|
|
1617
|
-
function
|
|
1366
|
+
function requireAuth5(client) {
|
|
1618
1367
|
if (!client.isAuthenticated) {
|
|
1619
1368
|
error("Not authenticated. Run `hl auth login` first.");
|
|
1620
1369
|
process.exit(1);
|
|
@@ -1626,20 +1375,20 @@ function collect(value, previous = []) {
|
|
|
1626
1375
|
function parseProbability(value) {
|
|
1627
1376
|
const n = Number(value);
|
|
1628
1377
|
if (!Number.isFinite(n) || n < 0 || n > 100) {
|
|
1629
|
-
throw new
|
|
1378
|
+
throw new InvalidArgumentError("Expected a probability between 0 and 100");
|
|
1630
1379
|
}
|
|
1631
1380
|
return n;
|
|
1632
1381
|
}
|
|
1633
|
-
async function
|
|
1382
|
+
async function readStdin() {
|
|
1634
1383
|
const chunks = [];
|
|
1635
1384
|
for await (const chunk of process.stdin) {
|
|
1636
1385
|
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
1637
1386
|
}
|
|
1638
1387
|
return Buffer.concat(chunks).toString("utf8");
|
|
1639
1388
|
}
|
|
1640
|
-
async function
|
|
1389
|
+
async function readMarketPayload(path) {
|
|
1641
1390
|
if (!path) return {};
|
|
1642
|
-
const raw = path === "-" ? await
|
|
1391
|
+
const raw = path === "-" ? await readStdin() : await readFile(path, "utf8");
|
|
1643
1392
|
const parsed = JSON.parse(raw);
|
|
1644
1393
|
if (Array.isArray(parsed)) {
|
|
1645
1394
|
return { markets: parsed };
|
|
@@ -1669,7 +1418,7 @@ async function buildSignalPayload(positionalUrl, opts) {
|
|
|
1669
1418
|
const urls = [positionalUrl, ...opts.url ?? []].filter(
|
|
1670
1419
|
(value) => Boolean(value)
|
|
1671
1420
|
);
|
|
1672
|
-
const filePayload = await
|
|
1421
|
+
const filePayload = await readMarketPayload(opts.market);
|
|
1673
1422
|
const inlineMarket = inlineMarketFromOptions(opts);
|
|
1674
1423
|
const hasMarketInput = Boolean(filePayload.market || filePayload.markets || inlineMarket);
|
|
1675
1424
|
if (urls.length > 0 && hasMarketInput) {
|
|
@@ -1698,7 +1447,7 @@ function registerSignalCommands(program2) {
|
|
|
1698
1447
|
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) => {
|
|
1699
1448
|
const globalOpts = program2.opts();
|
|
1700
1449
|
const client = new ApiClient(globalOpts);
|
|
1701
|
-
|
|
1450
|
+
requireAuth5(client);
|
|
1702
1451
|
let payload;
|
|
1703
1452
|
try {
|
|
1704
1453
|
payload = await buildSignalPayload(url, o);
|
|
@@ -1715,38 +1464,38 @@ function registerSignalCommands(program2) {
|
|
|
1715
1464
|
}
|
|
1716
1465
|
|
|
1717
1466
|
// src/commands/quote.ts
|
|
1718
|
-
import { InvalidArgumentError as
|
|
1719
|
-
function
|
|
1467
|
+
import { InvalidArgumentError as InvalidArgumentError2 } from "commander";
|
|
1468
|
+
function requireAuth6(client) {
|
|
1720
1469
|
if (!client.isAuthenticated) {
|
|
1721
1470
|
error("Not authenticated. Run `hl auth login` first.");
|
|
1722
1471
|
process.exit(1);
|
|
1723
1472
|
}
|
|
1724
1473
|
}
|
|
1725
|
-
function
|
|
1474
|
+
function parsePositiveNumber(value) {
|
|
1726
1475
|
const parsed = Number(value);
|
|
1727
1476
|
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
1728
|
-
throw new
|
|
1477
|
+
throw new InvalidArgumentError2("Expected a positive number");
|
|
1729
1478
|
}
|
|
1730
1479
|
return parsed;
|
|
1731
1480
|
}
|
|
1732
1481
|
function parseQuoteAction(value) {
|
|
1733
1482
|
const normalized = value.trim().toUpperCase();
|
|
1734
1483
|
if (normalized !== "BUY" && normalized !== "SELL") {
|
|
1735
|
-
throw new
|
|
1484
|
+
throw new InvalidArgumentError2("Expected buy or sell");
|
|
1736
1485
|
}
|
|
1737
1486
|
return normalized;
|
|
1738
1487
|
}
|
|
1739
1488
|
function parseQuoteOutcome(value) {
|
|
1740
1489
|
const normalized = value.trim().toUpperCase();
|
|
1741
1490
|
if (normalized !== "YES" && normalized !== "NO") {
|
|
1742
|
-
throw new
|
|
1491
|
+
throw new InvalidArgumentError2("Expected yes or no");
|
|
1743
1492
|
}
|
|
1744
1493
|
return normalized;
|
|
1745
1494
|
}
|
|
1746
1495
|
function parseQuoteRoute(value) {
|
|
1747
1496
|
const normalized = value.trim().toLowerCase();
|
|
1748
1497
|
if (normalized !== "auto" && normalized !== "aggressive" && normalized !== "passive") {
|
|
1749
|
-
throw new
|
|
1498
|
+
throw new InvalidArgumentError2("Expected auto, aggressive, or passive");
|
|
1750
1499
|
}
|
|
1751
1500
|
return normalized;
|
|
1752
1501
|
}
|
|
@@ -1874,10 +1623,10 @@ function displayQuotePreview(preview, globalOpts) {
|
|
|
1874
1623
|
`));
|
|
1875
1624
|
}
|
|
1876
1625
|
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)",
|
|
1626
|
+
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)", parsePositiveNumber).option("--shares <shares>", "Number of outcome shares", parsePositiveNumber).option("--signal-id <uuid>", "Saved Signal forecast to include in edge calculations").option("--capital <usd>", "Manual portfolio capital for non-binding BUY sizing", parsePositiveNumber).option("--route <route>", "auto | aggressive | passive", parseQuoteRoute, "auto").option("--save", "Save a freshly generated preview to quote history").action(async (instrument, opts) => {
|
|
1878
1627
|
const globalOpts = program2.opts();
|
|
1879
1628
|
const client = new ApiClient(globalOpts);
|
|
1880
|
-
|
|
1629
|
+
requireAuth6(client);
|
|
1881
1630
|
let payload;
|
|
1882
1631
|
try {
|
|
1883
1632
|
payload = buildQuotePayload(instrument, opts);
|
|
@@ -1894,14 +1643,13 @@ function registerQuoteCommand(program2) {
|
|
|
1894
1643
|
}
|
|
1895
1644
|
|
|
1896
1645
|
// src/index.ts
|
|
1897
|
-
var program = new
|
|
1898
|
-
program.name("hl").description("Hedge Layer CLI \u2014 prediction market intelligence from the terminal").version("
|
|
1646
|
+
var program = new Command3();
|
|
1647
|
+
program.name("hl").description("Hedge Layer CLI \u2014 prediction market intelligence from the terminal").version("4.0.1").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");
|
|
1899
1648
|
registerAuthCommands(program);
|
|
1900
1649
|
registerBriefCommands(program);
|
|
1901
1650
|
registerProfileCommand(program);
|
|
1902
1651
|
registerResearchCommands(program);
|
|
1903
1652
|
registerFeedCommand(program);
|
|
1904
|
-
registerLpCommands(program);
|
|
1905
1653
|
registerSignalCommands(program);
|
|
1906
1654
|
registerQuoteCommand(program);
|
|
1907
1655
|
program.hook("preAction", (_thisCommand, actionCommand) => {
|