@hedge-layer/cli 2.0.0 → 2.2.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 CHANGED
@@ -86,7 +86,8 @@ The lightweight manual loop is:
86
86
 
87
87
  ```bash
88
88
  hl --json feed lp-opportunity --limit 15 > markets.json
89
- hl lp allocator --markets markets.json
89
+ hl-trader pnl --json > pnl.json # optional: wallet PnL + live inventory
90
+ hl lp allocator --markets markets.json --pnl pnl.json --allocations pnl.json
90
91
  hl-trader buy ...
91
92
  ```
92
93
 
@@ -95,6 +96,11 @@ allocator agent. Allocator output shows target capital, quote regime, failed
95
96
  safety checks, and split spread/reward economics. Trade execution stays outside
96
97
  the `hl` allocator command.
97
98
 
99
+ `--allocations` tells the allocator what you already hold (enabling HOLD,
100
+ REDUCE, and EXIT decisions), and `--pnl` feeds per-market PnL into its caution
101
+ overlay so borderline allocations on losing markets are downgraded to WATCH or
102
+ HOLD. Both flags accept the `hl-trader pnl --json` output file directly.
103
+
98
104
  Wallet commands are available under `hl wallet`:
99
105
 
100
106
  ```bash
package/dist/index.mjs CHANGED
@@ -980,6 +980,7 @@ function displayMarketBrief(brief, globalOpts) {
980
980
  }
981
981
 
982
982
  // src/commands/feed.ts
983
+ import { writeFile } from "fs/promises";
983
984
  function requireAuth4(client) {
984
985
  if (!client.isAuthenticated) {
985
986
  error("Not authenticated. Run `hl auth login` first.");
@@ -1024,8 +1025,136 @@ function feedQueryParams(opts) {
1024
1025
  add("limit", opts.limit);
1025
1026
  return Object.fromEntries(entries);
1026
1027
  }
1028
+ var ENSEMBLE_SOURCES = [
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
+ ];
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;
1046
+ function num(value) {
1047
+ return Number.isFinite(value) ? Number(value) : 0;
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
+ }
1069
+ function scoreCandidate(candidate, sourceCount) {
1070
+ const liquidityScore = Math.min(25, Math.log1p(Math.max(0, candidate.liquidity)) / Math.log1p(1e6) * 25);
1071
+ const volumeScore = Math.min(25, Math.log1p(Math.max(0, candidate.volume24h)) / Math.log1p(1e6) * 25);
1072
+ const spreadScore = Math.max(0, Math.min(15, (0.12 - Math.max(0, candidate.spread)) / 0.12 * 15));
1073
+ const movementPenalty = Math.min(20, Math.abs(candidate.oneDayPriceChange) * 100);
1074
+ const probabilityPenalty = extremeProbabilityPenalty(candidate);
1075
+ const days = candidate.daysToEnd ?? null;
1076
+ const horizon = horizonScore(days);
1077
+ const rewardScore = Math.max(
1078
+ 0,
1079
+ Math.min(20, num(candidate.components?.rewardYield) * 0.1 + Math.max(0, num(candidate.lpExpectedReturnDailyPct)) * 50)
1080
+ );
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;
1105
+ }
1106
+ function buildFeedEnsemble(sourceResults, limit, outputPath = "candidates.json", generatedAt = (/* @__PURE__ */ new Date()).toISOString()) {
1107
+ const bySlug = /* @__PURE__ */ new Map();
1108
+ let totalRawMarkets = 0;
1109
+ for (const { source, result } of sourceResults) {
1110
+ for (const market of result.markets ?? []) {
1111
+ totalRawMarkets++;
1112
+ const existing = bySlug.get(market.slug);
1113
+ if (!existing) {
1114
+ bySlug.set(market.slug, {
1115
+ ...market,
1116
+ ensembleScore: 0,
1117
+ sourceProfiles: [source],
1118
+ sourceRanks: { [source]: market.rank }
1119
+ });
1120
+ continue;
1121
+ }
1122
+ existing.sourceProfiles.push(source);
1123
+ existing.sourceRanks[source] = market.rank;
1124
+ if (market.score > existing.score) existing.score = market.score;
1125
+ existing.volume24h = Math.max(existing.volume24h, market.volume24h);
1126
+ existing.liquidity = Math.max(existing.liquidity, market.liquidity);
1127
+ existing.rewardsDailyRate = Math.max(existing.rewardsDailyRate, market.rewardsDailyRate);
1128
+ existing.lpExpectedReturnDailyPct = Math.max(
1129
+ num(existing.lpExpectedReturnDailyPct),
1130
+ num(market.lpExpectedReturnDailyPct)
1131
+ );
1132
+ existing.lpRiskFlags = [.../* @__PURE__ */ new Set([...existing.lpRiskFlags ?? [], ...market.lpRiskFlags ?? []])];
1133
+ }
1134
+ }
1135
+ const candidates = [...bySlug.values()].map((candidate) => ({
1136
+ ...candidate,
1137
+ sourceProfiles: [...new Set(candidate.sourceProfiles)],
1138
+ ensembleScore: scoreCandidate(candidate, new Set(candidate.sourceProfiles).size)
1139
+ })).sort((a, b) => b.ensembleScore - a.ensembleScore || b.score - a.score);
1140
+ const diversifiedCandidates = diversifyCandidates(candidates, limit);
1141
+ return {
1142
+ generatedAt,
1143
+ outputPath,
1144
+ totalSources: sourceResults.length,
1145
+ totalRawMarkets,
1146
+ totalCandidates: bySlug.size,
1147
+ marketsReturned: diversifiedCandidates.length,
1148
+ candidates: diversifiedCandidates
1149
+ };
1150
+ }
1151
+ function parseLimit(value, fallback) {
1152
+ const parsed = Number.parseInt(value ?? "", 10);
1153
+ if (!Number.isFinite(parsed)) return fallback;
1154
+ return Math.max(1, Math.min(100, parsed));
1155
+ }
1027
1156
  function registerFeedCommand(program2) {
1028
- program2.command("feed").description(
1157
+ const feed = program2.command("feed").description(
1029
1158
  "Rank active Polymarket markets (same engine as chat getFeed / GET /api/feed). Use --profile for curated screens."
1030
1159
  ).argument(
1031
1160
  "[screening]",
@@ -1033,7 +1162,50 @@ function registerFeedCommand(program2) {
1033
1162
  ).option(
1034
1163
  "--profile <name>",
1035
1164
  `Screening defaults: ${PROFILE_CHOICES.join(", ")} \u2014 explicit flags override`
1036
- ).option("--sort-by <key>", "score | volume | liquidity | movement | spread | recency | extremity | rewards | rewardYield | lpExpectedReturn | horizon").option("--preset <name>", "Attention weight preset (default, volume-hunter, lp-opportunity, \u2026)").option("--tag <slug>", "Polymarket category tag, e.g. crypto, politics").option("--min-volume <usd>", "Minimum 24h volume (USD)").option("--min-liquidity <usd>", "Minimum displayed liquidity (USD)").option("--max-liquidity <usd>", "Maximum displayed liquidity (USD)").option("--min-rewards-daily-rate <usd>", "Minimum LP rewards USD/day").option("--min-days-to-end <n>", "Min calendar days until resolution").option("--max-days-to-end <n>", "Max calendar days until resolution").option("--max-market-age-hours <n>", 'With liquid-new-or-long: max age (hours) for the "new" branch').option("--liquid-profile <mode>", "new-or-long (used by liquid-new-or-long screen)").option("--limit <n>", "Max markets to return (1\u2013100, default 15)", "15").action(async (screening, o) => {
1165
+ ).option("--sort-by <key>", "score | volume | liquidity | movement | spread | recency | extremity | rewards | rewardYield | lpExpectedReturn | horizon").option("--preset <name>", "Attention weight preset (default, volume-hunter, lp-opportunity, \u2026)").option("--tag <slug>", "Polymarket category tag, e.g. crypto, politics").option("--min-volume <usd>", "Minimum 24h volume (USD)").option("--min-liquidity <usd>", "Minimum displayed liquidity (USD)").option("--max-liquidity <usd>", "Maximum displayed liquidity (USD)").option("--min-rewards-daily-rate <usd>", "Minimum LP rewards USD/day").option("--min-days-to-end <n>", "Min calendar days until resolution").option("--max-days-to-end <n>", "Max calendar days until resolution").option("--max-market-age-hours <n>", 'With liquid-new-or-long: max age (hours) for the "new" branch').option("--liquid-profile <mode>", "new-or-long (used by liquid-new-or-long screen)").option("--limit <n>", "Max markets to return (1\u2013100, default 15)", "15");
1166
+ feed.command("ensemble").description("Run multiple feed screens, merge by slug, and write ranked candidates JSON").option("--limit <n>", "Max candidates to return/write (1-100, default 25)", "25").option("--output <file>", "Output JSON path", "candidates.json").action(async (o) => {
1167
+ const globalOpts = program2.opts();
1168
+ const client = new ApiClient(globalOpts);
1169
+ requireAuth4(client);
1170
+ const perSourceLimit = "100";
1171
+ const sourceResults = [];
1172
+ try {
1173
+ for (const source of ENSEMBLE_SOURCES) {
1174
+ const result = await client.get("/api/feed", {
1175
+ ...source.params,
1176
+ limit: perSourceLimit
1177
+ });
1178
+ if (result.error) {
1179
+ throw new Error(result.error);
1180
+ }
1181
+ sourceResults.push({ source: source.name, result });
1182
+ }
1183
+ const outputPath = o.output ?? "candidates.json";
1184
+ const ensemble = buildFeedEnsemble(sourceResults, parseLimit(o.limit, 25), outputPath);
1185
+ await writeFile(outputPath, JSON.stringify(ensemble, null, 2) + "\n", "utf8");
1186
+ if (globalOpts.json) {
1187
+ json(ensemble);
1188
+ return;
1189
+ }
1190
+ heading(`Feed Ensemble \u2014 ${ensemble.marketsReturned} candidates`);
1191
+ table(
1192
+ ensemble.candidates.slice(0, 15).map((m) => [
1193
+ String(Math.round(m.ensembleScore)),
1194
+ truncate(m.question, 48),
1195
+ `${Math.round(m.yesPrice * 100)}%`,
1196
+ compactCurrency(m.volume24h),
1197
+ compactCurrency(m.liquidity),
1198
+ m.sourceProfiles.join(",")
1199
+ ]),
1200
+ ["Score", "Market", "YES", "24h Vol", "Liq", "Sources"]
1201
+ );
1202
+ success(`Wrote ${outputPath}`);
1203
+ } catch (e) {
1204
+ error(e instanceof Error ? e.message : String(e));
1205
+ process.exit(1);
1206
+ }
1207
+ });
1208
+ feed.action(async (screening, o) => {
1037
1209
  const globalOpts = program2.opts();
1038
1210
  let profile;
1039
1211
  try {
@@ -1075,7 +1247,7 @@ function registerFeedCommand(program2) {
1075
1247
 
1076
1248
  // src/commands/lp.ts
1077
1249
  import { InvalidArgumentError as InvalidArgumentError2 } from "commander";
1078
- import { writeFile } from "fs/promises";
1250
+ import { writeFile as writeFile2 } from "fs/promises";
1079
1251
 
1080
1252
  // src/allocator-display.ts
1081
1253
  import chalk5 from "chalk";
@@ -1095,7 +1267,7 @@ function actionColor(action) {
1095
1267
  return action;
1096
1268
  }
1097
1269
  }
1098
- function num(value, fallback = 0) {
1270
+ function num2(value, fallback = 0) {
1099
1271
  const n = Number(value);
1100
1272
  return Number.isFinite(n) ? n : fallback;
1101
1273
  }
@@ -1110,8 +1282,8 @@ function displayAllocatorCycleResult(result, globalOpts) {
1110
1282
  heading(`Allocator Cycle \u2014 ${dryRun}`);
1111
1283
  process.stdout.write(
1112
1284
  chalk5.dim(
1113
- ` ${num(result.total_markets, decisions.length)} markets \xB7 ${currency(
1114
- num(summary.target_capital)
1285
+ ` ${num2(result.total_markets, decisions.length)} markets \xB7 ${currency(
1286
+ num2(summary.target_capital)
1115
1287
  )} target capital
1116
1288
 
1117
1289
  `
@@ -1123,14 +1295,14 @@ function displayAllocatorCycleResult(result, globalOpts) {
1123
1295
  }
1124
1296
  const rows = decisions.map((d) => {
1125
1297
  const action = String(d.action ?? "UNKNOWN");
1126
- const score = num(d.score?.score);
1127
- const expected = num(d.score?.expected_return_daily_pct);
1298
+ const score = num2(d.score?.score);
1299
+ const expected = num2(d.score?.expected_return_daily_pct);
1128
1300
  const failedChecks = Array.isArray(d.safety_checks) ? d.safety_checks.filter((check) => check.passed === false).length : 0;
1129
1301
  return [
1130
1302
  truncate(String(d.question ?? d.market_slug ?? "\u2014"), 42),
1131
1303
  actionColor(action),
1132
- currency(num(d.target_capital)),
1133
- signedCurrency(num(d.capital_delta)),
1304
+ currency(num2(d.target_capital)),
1305
+ signedCurrency(num2(d.capital_delta)),
1134
1306
  `${expected.toFixed(3)}%`,
1135
1307
  regimeLabel(String(d.quote_regime ?? "\u2014")),
1136
1308
  String(Math.round(score)),
@@ -1159,7 +1331,7 @@ function displayAllocatorCycleResult(result, globalOpts) {
1159
1331
  const economics = decision.economics ?? {};
1160
1332
  if (economics.realized_spread_pnl !== void 0 || economics.reward_income !== void 0 || economics.net_realized_pnl !== void 0) {
1161
1333
  process.stdout.write(
1162
- " " + chalk5.dim("Economics: ") + `spread ${signedCurrency(num(economics.realized_spread_pnl))}, rewards ${signedCurrency(num(economics.reward_income))}, net ${signedCurrency(num(economics.net_realized_pnl))}
1334
+ " " + chalk5.dim("Economics: ") + `spread ${signedCurrency(num2(economics.realized_spread_pnl))}, rewards ${signedCurrency(num2(economics.reward_income))}, net ${signedCurrency(num2(economics.net_realized_pnl))}
1163
1335
  `
1164
1336
  );
1165
1337
  }
@@ -1199,26 +1371,49 @@ function parsePositiveNumber(value) {
1199
1371
  }
1200
1372
  return n;
1201
1373
  }
1374
+ function parseAllocationsInput(parsed) {
1375
+ const rows = extractArray(parsed, "allocations");
1376
+ if (!rows) {
1377
+ throw new Error("Allocations JSON must be an array or { allocations: [...] }");
1378
+ }
1379
+ return rows;
1380
+ }
1381
+ function parsePnlContextInput(parsed) {
1382
+ const rows = extractArray(parsed, "pnl_context");
1383
+ if (!rows) {
1384
+ throw new Error("PnL JSON must be an array or { pnl_context: [...] }");
1385
+ }
1386
+ return rows;
1387
+ }
1202
1388
  async function readAllocations(path) {
1203
1389
  if (!path) return [];
1204
- const raw = path === "-" ? await readStdin() : await readFile(path, "utf8");
1205
- const parsed = JSON.parse(raw);
1206
- if (!Array.isArray(parsed)) {
1207
- throw new Error("Allocations JSON must be an array");
1208
- }
1209
- return parsed;
1390
+ return parseAllocationsInput(await readJsonInput(path));
1391
+ }
1392
+ async function readPnlContext(path) {
1393
+ if (!path) return [];
1394
+ return parsePnlContextInput(await readJsonInput(path));
1210
1395
  }
1211
1396
  async function readMarketPayload(path) {
1212
1397
  if (!path) return void 0;
1213
- const raw = path === "-" ? await readStdin() : await readFile(path, "utf8");
1214
- const parsed = JSON.parse(raw);
1398
+ const parsed = await readJsonInput(path);
1399
+ const rows = extractArray(parsed, "markets");
1400
+ if (!rows) {
1401
+ throw new Error("Markets JSON must be an array or { markets: [...] }");
1402
+ }
1403
+ return rows;
1404
+ }
1405
+ function extractArray(parsed, key) {
1215
1406
  if (Array.isArray(parsed)) {
1216
1407
  return parsed;
1217
1408
  }
1218
- if (parsed && typeof parsed === "object" && Array.isArray(parsed.markets)) {
1219
- return parsed.markets;
1409
+ if (parsed && typeof parsed === "object" && Array.isArray(parsed[key])) {
1410
+ return parsed[key];
1220
1411
  }
1221
- throw new Error("Markets JSON must be an array or { markets: [...] }");
1412
+ return void 0;
1413
+ }
1414
+ async function readJsonInput(path) {
1415
+ const raw = path === "-" ? await readStdin() : await readFile(path, "utf8");
1416
+ return JSON.parse(raw);
1222
1417
  }
1223
1418
  async function readStdin() {
1224
1419
  const chunks = [];
@@ -1230,7 +1425,7 @@ async function readStdin() {
1230
1425
 
1231
1426
  // src/lp-display.ts
1232
1427
  import chalk6 from "chalk";
1233
- function num2(value, fallback = 0) {
1428
+ function num3(value, fallback = 0) {
1234
1429
  const n = Number(value);
1235
1430
  return Number.isFinite(n) ? n : fallback;
1236
1431
  }
@@ -1242,7 +1437,7 @@ function signedCurrency2(value) {
1242
1437
  }
1243
1438
  function actionSummary(actions) {
1244
1439
  if (!actions) return "none";
1245
- return Object.entries(actions).filter(([, count]) => num2(count) > 0).map(([action, count]) => `${action}:${num2(count)}`).join(" ");
1440
+ return Object.entries(actions).filter(([, count]) => num3(count) > 0).map(([action, count]) => `${action}:${num3(count)}`).join(" ");
1246
1441
  }
1247
1442
  function displayLpScanResult(result, globalOpts) {
1248
1443
  if (globalOpts.json) {
@@ -1271,10 +1466,10 @@ function displayLpScanResult(result, globalOpts) {
1271
1466
  result.markets.slice(0, 10).map((market) => [
1272
1467
  String(market.rank),
1273
1468
  truncate(market.question, 46),
1274
- String(Math.round(num2(market.score))),
1275
- compactCurrency(num2(market.liquidity)),
1276
- compactCurrency(num2(market.rewardsDailyRate)) + "/day",
1277
- `${num2(market.lpExpectedReturnDailyPct).toFixed(3)}%`
1469
+ String(Math.round(num3(market.score))),
1470
+ compactCurrency(num3(market.liquidity)),
1471
+ compactCurrency(num3(market.rewardsDailyRate)) + "/day",
1472
+ `${num3(market.lpExpectedReturnDailyPct).toFixed(3)}%`
1278
1473
  ]),
1279
1474
  ["#", "Market", "Score", "Liq", "Rewards", "Exp/day"]
1280
1475
  );
@@ -1333,7 +1528,7 @@ function displayLpEvaluateResult(result, globalOpts) {
1333
1528
  result.lessons.slice(0, 8).map((lesson) => [
1334
1529
  truncate(String(lesson.market_slug ?? "portfolio"), 26),
1335
1530
  String(lesson.outcome ?? "flat"),
1336
- signedCurrency2(num2(lesson.net_pnl)),
1531
+ signedCurrency2(num3(lesson.net_pnl)),
1337
1532
  truncate(String(lesson.lesson ?? "no lesson"), 64)
1338
1533
  ]),
1339
1534
  ["Market", "Outcome", "Net", "Lesson"]
@@ -1398,7 +1593,7 @@ function compact(payload) {
1398
1593
  }
1399
1594
  async function writeArtifact(path, data, jsonMode) {
1400
1595
  if (!path) return;
1401
- await writeFile(path, JSON.stringify(data, null, 2) + "\n", "utf8");
1596
+ await writeFile2(path, JSON.stringify(data, null, 2) + "\n", "utf8");
1402
1597
  if (!jsonMode) {
1403
1598
  process.stderr.write(dim(` Saved artifact to ${path}
1404
1599
  `));
@@ -1443,7 +1638,7 @@ async function runAllocatorCycle(client, payload) {
1443
1638
  }
1444
1639
  function registerLpCommands(program2) {
1445
1640
  const lp = program2.command("lp").description("Run persisted liquidity-provider scan, recommendation, and evaluation workflows");
1446
- 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; 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) => {
1641
+ 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 } (hl-trader pnl --json output); 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) => {
1447
1642
  const globalOpts = program2.opts();
1448
1643
  const client = new ApiClient(globalOpts);
1449
1644
  requireAuth5(client);
@@ -1453,10 +1648,16 @@ function registerLpCommands(program2) {
1453
1648
  error("Markets JSON must include at least one market.");
1454
1649
  process.exit(1);
1455
1650
  }
1651
+ if (opts.allocations === "-" && opts.pnl === "-") {
1652
+ error("Only one of --allocations and --pnl can read from stdin.");
1653
+ process.exit(1);
1654
+ }
1655
+ const pnlContext = await readPnlContext(opts.pnl);
1456
1656
  const payload = {
1457
1657
  strategy: strategyFromAllocatorOptions(opts),
1458
1658
  markets,
1459
- allocations: await readAllocations(opts.allocations)
1659
+ allocations: await readAllocations(opts.allocations),
1660
+ ...pnlContext.length > 0 && { pnl_context: pnlContext }
1460
1661
  };
1461
1662
  if (!globalOpts.json) {
1462
1663
  process.stderr.write(
@@ -1971,7 +2172,7 @@ function registerSignalCommands(program2) {
1971
2172
 
1972
2173
  // src/index.ts
1973
2174
  var program = new Command4();
1974
- program.name("hl").description("Hedge Layer CLI \u2014 prediction market intelligence from the terminal").version("2.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");
2175
+ program.name("hl").description("Hedge Layer CLI \u2014 prediction market intelligence from the terminal").version("2.2.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");
1975
2176
  registerAuthCommands(program);
1976
2177
  registerBriefCommands(program);
1977
2178
  registerProfileCommand(program);