@hedge-layer/cli 1.7.0 → 2.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 +56 -5
- package/dist/index.mjs +374 -284
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -838,11 +838,12 @@ function registerResearchCommands(program2) {
|
|
|
838
838
|
});
|
|
839
839
|
table(rows, ["ID", "Status", "Brief", "Markets", "Created"]);
|
|
840
840
|
});
|
|
841
|
-
research.command("show <id>").description("Show research session details").action(async (id) => {
|
|
841
|
+
research.command("show <id>").description("Show research session details (accepts the short ID shown by `hl research list`)").action(async (id) => {
|
|
842
842
|
const globalOpts = program2.opts();
|
|
843
843
|
const client = new ApiClient(globalOpts);
|
|
844
844
|
requireAuth3(client);
|
|
845
|
-
const
|
|
845
|
+
const fullId = await resolveOrExit(client, id);
|
|
846
|
+
const assessment = await client.get(`/api/assessments/${fullId}`);
|
|
846
847
|
if (globalOpts.json) {
|
|
847
848
|
json(assessment);
|
|
848
849
|
return;
|
|
@@ -857,14 +858,53 @@ function registerResearchCommands(program2) {
|
|
|
857
858
|
displayMarketBrief(assessment.market_brief, globalOpts);
|
|
858
859
|
}
|
|
859
860
|
});
|
|
860
|
-
research.command("delete <id>").description("Delete a research session").action(async (id) => {
|
|
861
|
+
research.command("delete <id>").description("Delete a research session (accepts the short ID shown by `hl research list`)").action(async (id) => {
|
|
861
862
|
const globalOpts = program2.opts();
|
|
862
863
|
const client = new ApiClient(globalOpts);
|
|
863
864
|
requireAuth3(client);
|
|
864
|
-
await client
|
|
865
|
+
const fullId = await resolveOrExit(client, id);
|
|
866
|
+
await client.delete(`/api/assessments/${fullId}`);
|
|
865
867
|
success("Research session deleted.");
|
|
866
868
|
});
|
|
867
869
|
}
|
|
870
|
+
var FULL_UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
871
|
+
function matchAssessmentId(idOrPrefix, ids) {
|
|
872
|
+
const needle = idOrPrefix.trim().toLowerCase();
|
|
873
|
+
if (!needle) {
|
|
874
|
+
throw new Error("No research session ID provided.");
|
|
875
|
+
}
|
|
876
|
+
const exact = ids.find((id) => id.toLowerCase() === needle);
|
|
877
|
+
if (exact) return exact;
|
|
878
|
+
const matches = ids.filter((id) => id.toLowerCase().startsWith(needle));
|
|
879
|
+
if (matches.length === 1) return matches[0];
|
|
880
|
+
if (matches.length === 0) {
|
|
881
|
+
throw new Error(
|
|
882
|
+
`No research session found matching "${idOrPrefix}". Run \`hl research list\` to see available sessions.`
|
|
883
|
+
);
|
|
884
|
+
}
|
|
885
|
+
const shortIds = matches.map((id) => id.slice(0, 8)).join(", ");
|
|
886
|
+
throw new Error(
|
|
887
|
+
`"${idOrPrefix}" matches ${matches.length} research sessions (${shortIds}). Use a longer ID prefix to disambiguate.`
|
|
888
|
+
);
|
|
889
|
+
}
|
|
890
|
+
async function resolveAssessmentId(client, idOrPrefix) {
|
|
891
|
+
const trimmed = idOrPrefix.trim();
|
|
892
|
+
if (FULL_UUID.test(trimmed)) return trimmed;
|
|
893
|
+
const data = await client.get("/api/assessments", { list: "true" });
|
|
894
|
+
return matchAssessmentId(
|
|
895
|
+
trimmed,
|
|
896
|
+
data.assessments.map((a) => a.id)
|
|
897
|
+
);
|
|
898
|
+
}
|
|
899
|
+
async function resolveOrExit(client, idOrPrefix) {
|
|
900
|
+
try {
|
|
901
|
+
return await resolveAssessmentId(client, idOrPrefix);
|
|
902
|
+
} catch (e) {
|
|
903
|
+
if (e instanceof Error && e.message.includes("API error")) throw e;
|
|
904
|
+
error(e instanceof Error ? e.message : String(e));
|
|
905
|
+
process.exit(1);
|
|
906
|
+
}
|
|
907
|
+
}
|
|
868
908
|
function requireAuth3(client) {
|
|
869
909
|
if (!client.isAuthenticated) {
|
|
870
910
|
error("Not logged in. Run " + bold("hl auth login") + " first.");
|
|
@@ -1033,10 +1073,9 @@ function registerFeedCommand(program2) {
|
|
|
1033
1073
|
});
|
|
1034
1074
|
}
|
|
1035
1075
|
|
|
1036
|
-
// src/commands/
|
|
1037
|
-
import { InvalidArgumentError } from "commander";
|
|
1038
|
-
import {
|
|
1039
|
-
import chalk6 from "chalk";
|
|
1076
|
+
// src/commands/lp.ts
|
|
1077
|
+
import { InvalidArgumentError as InvalidArgumentError2 } from "commander";
|
|
1078
|
+
import { writeFile } from "fs/promises";
|
|
1040
1079
|
|
|
1041
1080
|
// src/allocator-display.ts
|
|
1042
1081
|
import chalk5 from "chalk";
|
|
@@ -1060,38 +1099,6 @@ function num(value, fallback = 0) {
|
|
|
1060
1099
|
const n = Number(value);
|
|
1061
1100
|
return Number.isFinite(n) ? n : fallback;
|
|
1062
1101
|
}
|
|
1063
|
-
function feedMarketsToAllocatorMarkets(markets) {
|
|
1064
|
-
return markets.slice(0, 25).map((m) => {
|
|
1065
|
-
const probability = num(m.probability ?? m.yesPrice, 0.5);
|
|
1066
|
-
const daysToEnd = m.daysToEnd == null || m.daysToEnd === void 0 ? void 0 : num(m.daysToEnd);
|
|
1067
|
-
return {
|
|
1068
|
-
slug: String(m.slug ?? ""),
|
|
1069
|
-
question: String(m.question ?? ""),
|
|
1070
|
-
yesTokenId: m.yesTokenId ? String(m.yesTokenId) : void 0,
|
|
1071
|
-
noTokenId: m.noTokenId ? String(m.noTokenId) : void 0,
|
|
1072
|
-
yesPrice: num(m.yesPrice, probability),
|
|
1073
|
-
noPrice: num(m.noPrice, 1 - probability),
|
|
1074
|
-
liquidity: num(m.liquidity),
|
|
1075
|
-
volume24h: num(m.volume24h),
|
|
1076
|
-
spread: num(m.spread),
|
|
1077
|
-
rewardsDailyRate: num(m.rewardsDailyRate),
|
|
1078
|
-
oneDayPriceChange: num(m.oneDayPriceChange),
|
|
1079
|
-
...daysToEnd !== void 0 && { daysToEnd },
|
|
1080
|
-
active: m.active == null ? true : Boolean(m.active)
|
|
1081
|
-
};
|
|
1082
|
-
});
|
|
1083
|
-
}
|
|
1084
|
-
function allocationsFromDecisions(decisions) {
|
|
1085
|
-
return decisions.filter((d) => String(d.market_slug ?? "")).map((d) => ({
|
|
1086
|
-
market_slug: String(d.market_slug),
|
|
1087
|
-
status: String(d.action ?? "planned").toLowerCase(),
|
|
1088
|
-
allocated_capital: num(d.target_capital),
|
|
1089
|
-
locked_capital: 0,
|
|
1090
|
-
inventory_yes: 0,
|
|
1091
|
-
inventory_no: 0,
|
|
1092
|
-
open_order_notional: Array.isArray(d.order_plan) ? d.order_plan.reduce((sum, order) => sum + num(order.notional), 0) : 0
|
|
1093
|
-
}));
|
|
1094
|
-
}
|
|
1095
1102
|
function displayAllocatorCycleResult(result, globalOpts) {
|
|
1096
1103
|
if (globalOpts.json) {
|
|
1097
1104
|
json(result);
|
|
@@ -1105,7 +1112,7 @@ function displayAllocatorCycleResult(result, globalOpts) {
|
|
|
1105
1112
|
chalk5.dim(
|
|
1106
1113
|
` ${num(result.total_markets, decisions.length)} markets \xB7 ${currency(
|
|
1107
1114
|
num(summary.target_capital)
|
|
1108
|
-
)} target capital
|
|
1115
|
+
)} target capital
|
|
1109
1116
|
|
|
1110
1117
|
`
|
|
1111
1118
|
)
|
|
@@ -1118,7 +1125,6 @@ function displayAllocatorCycleResult(result, globalOpts) {
|
|
|
1118
1125
|
const action = String(d.action ?? "UNKNOWN");
|
|
1119
1126
|
const score = num(d.score?.score);
|
|
1120
1127
|
const expected = num(d.score?.expected_return_daily_pct);
|
|
1121
|
-
const orders = Array.isArray(d.order_plan) ? d.order_plan.length : 0;
|
|
1122
1128
|
const failedChecks = Array.isArray(d.safety_checks) ? d.safety_checks.filter((check) => check.passed === false).length : 0;
|
|
1123
1129
|
return [
|
|
1124
1130
|
truncate(String(d.question ?? d.market_slug ?? "\u2014"), 42),
|
|
@@ -1126,12 +1132,21 @@ function displayAllocatorCycleResult(result, globalOpts) {
|
|
|
1126
1132
|
currency(num(d.target_capital)),
|
|
1127
1133
|
signedCurrency(num(d.capital_delta)),
|
|
1128
1134
|
`${expected.toFixed(3)}%`,
|
|
1135
|
+
regimeLabel(String(d.quote_regime ?? "\u2014")),
|
|
1129
1136
|
String(Math.round(score)),
|
|
1130
|
-
String(orders),
|
|
1131
1137
|
failedChecks === 0 ? chalk5.green("0") : chalk5.yellow(String(failedChecks))
|
|
1132
1138
|
];
|
|
1133
1139
|
});
|
|
1134
|
-
table(rows, [
|
|
1140
|
+
table(rows, [
|
|
1141
|
+
"Market",
|
|
1142
|
+
"Action",
|
|
1143
|
+
"Target",
|
|
1144
|
+
"Delta",
|
|
1145
|
+
"Exp/day",
|
|
1146
|
+
"Regime",
|
|
1147
|
+
"Score",
|
|
1148
|
+
"Fails"
|
|
1149
|
+
]);
|
|
1135
1150
|
process.stdout.write("\n");
|
|
1136
1151
|
for (const decision of decisions.slice(0, 5)) {
|
|
1137
1152
|
const action = String(decision.action ?? "UNKNOWN");
|
|
@@ -1141,17 +1156,13 @@ function displayAllocatorCycleResult(result, globalOpts) {
|
|
|
1141
1156
|
process.stdout.write(" " + chalk5.dim("Rationale: ") + truncate(decision.rationale, 120));
|
|
1142
1157
|
}
|
|
1143
1158
|
process.stdout.write("\n");
|
|
1144
|
-
const
|
|
1145
|
-
|
|
1159
|
+
const economics = decision.economics ?? {};
|
|
1160
|
+
if (economics.realized_spread_pnl !== void 0 || economics.reward_income !== void 0 || economics.net_realized_pnl !== void 0) {
|
|
1146
1161
|
process.stdout.write(
|
|
1147
|
-
" " + chalk5.dim("
|
|
1162
|
+
" " + chalk5.dim("Economics: ") + `spread ${signedCurrency(num(economics.realized_spread_pnl))}, rewards ${signedCurrency(num(economics.reward_income))}, net ${signedCurrency(num(economics.net_realized_pnl))}
|
|
1148
1163
|
`
|
|
1149
1164
|
);
|
|
1150
1165
|
}
|
|
1151
|
-
if (orders.length > 2) {
|
|
1152
|
-
process.stdout.write(chalk5.dim(` \u2026 ${orders.length - 2} more orders
|
|
1153
|
-
`));
|
|
1154
|
-
}
|
|
1155
1166
|
}
|
|
1156
1167
|
if (decisions.length > 5) {
|
|
1157
1168
|
process.stdout.write(chalk5.dim(`
|
|
@@ -1159,6 +1170,18 @@ function displayAllocatorCycleResult(result, globalOpts) {
|
|
|
1159
1170
|
`));
|
|
1160
1171
|
}
|
|
1161
1172
|
}
|
|
1173
|
+
function regimeLabel(value) {
|
|
1174
|
+
switch (value) {
|
|
1175
|
+
case "reward_optimized":
|
|
1176
|
+
return chalk5.green("reward");
|
|
1177
|
+
case "defensive":
|
|
1178
|
+
return chalk5.yellow("defense");
|
|
1179
|
+
case "no_quote":
|
|
1180
|
+
return chalk5.red("no quote");
|
|
1181
|
+
default:
|
|
1182
|
+
return chalk5.dim(value);
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1162
1185
|
function signedCurrency(value) {
|
|
1163
1186
|
const formatted = currency(Math.abs(value));
|
|
1164
1187
|
if (value > 0) return chalk5.green(`+${formatted}`);
|
|
@@ -1167,23 +1190,8 @@ function signedCurrency(value) {
|
|
|
1167
1190
|
}
|
|
1168
1191
|
|
|
1169
1192
|
// src/commands/allocator.ts
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
if (!client.isAuthenticated) {
|
|
1173
|
-
error("Not authenticated. Run `hl auth login` first.");
|
|
1174
|
-
process.exit(1);
|
|
1175
|
-
}
|
|
1176
|
-
}
|
|
1177
|
-
function isProfile2(s) {
|
|
1178
|
-
return s !== void 0 && PROFILE_CHOICES2.includes(s);
|
|
1179
|
-
}
|
|
1180
|
-
function parseNonNegative(value) {
|
|
1181
|
-
const n = Number(value);
|
|
1182
|
-
if (!Number.isFinite(n) || n < 0) {
|
|
1183
|
-
throw new InvalidArgumentError("Expected a non-negative number");
|
|
1184
|
-
}
|
|
1185
|
-
return n;
|
|
1186
|
-
}
|
|
1193
|
+
import { InvalidArgumentError } from "commander";
|
|
1194
|
+
import { readFile } from "fs/promises";
|
|
1187
1195
|
function parsePositiveNumber(value) {
|
|
1188
1196
|
const n = Number(value);
|
|
1189
1197
|
if (!Number.isFinite(n) || n <= 0) {
|
|
@@ -1191,61 +1199,6 @@ function parsePositiveNumber(value) {
|
|
|
1191
1199
|
}
|
|
1192
1200
|
return n;
|
|
1193
1201
|
}
|
|
1194
|
-
function parsePositiveInt(value) {
|
|
1195
|
-
const n = Number(value);
|
|
1196
|
-
if (!Number.isInteger(n) || n < 1) {
|
|
1197
|
-
throw new InvalidArgumentError("Expected a positive integer");
|
|
1198
|
-
}
|
|
1199
|
-
return n;
|
|
1200
|
-
}
|
|
1201
|
-
function maybeAdd(entries, key, value) {
|
|
1202
|
-
if (value !== void 0 && value !== "") entries.push([key, value]);
|
|
1203
|
-
}
|
|
1204
|
-
function feedQueryParams2(profile, opts) {
|
|
1205
|
-
const entries = [];
|
|
1206
|
-
maybeAdd(entries, "profile", profile);
|
|
1207
|
-
maybeAdd(entries, "sortBy", opts.sortBy);
|
|
1208
|
-
maybeAdd(entries, "tag", opts.tag);
|
|
1209
|
-
maybeAdd(entries, "minVolume", opts.minVolume);
|
|
1210
|
-
maybeAdd(entries, "minLiquidity", opts.minLiquidity);
|
|
1211
|
-
maybeAdd(entries, "maxLiquidity", opts.maxLiquidity);
|
|
1212
|
-
maybeAdd(entries, "minRewardsDailyRate", opts.minRewardsDailyRate);
|
|
1213
|
-
maybeAdd(entries, "minDaysToEnd", opts.minDaysToEnd);
|
|
1214
|
-
maybeAdd(entries, "maxDaysToEnd", opts.maxDaysToEnd);
|
|
1215
|
-
maybeAdd(entries, "maxMarketAgeHours", opts.maxMarketAgeHours);
|
|
1216
|
-
maybeAdd(entries, "liquidProfile", opts.liquidProfile);
|
|
1217
|
-
maybeAdd(entries, "limit", opts.limit);
|
|
1218
|
-
return Object.fromEntries(entries);
|
|
1219
|
-
}
|
|
1220
|
-
function strategyFromOptions(opts) {
|
|
1221
|
-
return {
|
|
1222
|
-
id: "cli-dry-run",
|
|
1223
|
-
name: "CLI dry-run LP strategy",
|
|
1224
|
-
status: opts.paused ? "paused" : "dry_run",
|
|
1225
|
-
...opts.totalHoldings !== void 0 && { total_holdings: opts.totalHoldings },
|
|
1226
|
-
...opts.capitalLimitPct !== void 0 && { capital_limit_pct: opts.capitalLimitPct },
|
|
1227
|
-
...opts.perMarketLimitPct !== void 0 && { per_market_limit_pct: opts.perMarketLimitPct },
|
|
1228
|
-
capital_limit: opts.capitalLimit,
|
|
1229
|
-
per_market_limit: opts.perMarketLimit,
|
|
1230
|
-
min_expected_return_daily_pct: opts.minExpectedReturnDailyPct,
|
|
1231
|
-
max_inventory_imbalance: opts.maxInventoryImbalance,
|
|
1232
|
-
max_order_notional: opts.maxOrderNotional,
|
|
1233
|
-
quote_edge_bps: opts.quoteEdgeBps,
|
|
1234
|
-
min_liquidity: opts.allocatorMinLiquidity,
|
|
1235
|
-
max_spread: opts.maxSpread,
|
|
1236
|
-
min_days_to_end: opts.allocatorMinDaysToEnd,
|
|
1237
|
-
max_markets: opts.maxMarkets
|
|
1238
|
-
};
|
|
1239
|
-
}
|
|
1240
|
-
function validatePercentageSizing(opts) {
|
|
1241
|
-
const usesPercentageSizing = opts.capitalLimitPct !== void 0 || opts.perMarketLimitPct !== void 0;
|
|
1242
|
-
if (usesPercentageSizing && opts.totalHoldings === void 0) {
|
|
1243
|
-
error(
|
|
1244
|
-
"Percentage sizing requires --total-holdings so the allocator can convert percentages into dollar caps."
|
|
1245
|
-
);
|
|
1246
|
-
process.exit(1);
|
|
1247
|
-
}
|
|
1248
|
-
}
|
|
1249
1202
|
async function readAllocations(path) {
|
|
1250
1203
|
if (!path) return [];
|
|
1251
1204
|
const raw = path === "-" ? await readStdin() : await readFile(path, "utf8");
|
|
@@ -1255,6 +1208,18 @@ async function readAllocations(path) {
|
|
|
1255
1208
|
}
|
|
1256
1209
|
return parsed;
|
|
1257
1210
|
}
|
|
1211
|
+
async function readMarketPayload(path) {
|
|
1212
|
+
if (!path) return void 0;
|
|
1213
|
+
const raw = path === "-" ? await readStdin() : await readFile(path, "utf8");
|
|
1214
|
+
const parsed = JSON.parse(raw);
|
|
1215
|
+
if (Array.isArray(parsed)) {
|
|
1216
|
+
return parsed;
|
|
1217
|
+
}
|
|
1218
|
+
if (parsed && typeof parsed === "object" && Array.isArray(parsed.markets)) {
|
|
1219
|
+
return parsed.markets;
|
|
1220
|
+
}
|
|
1221
|
+
throw new Error("Markets JSON must be an array or { markets: [...] }");
|
|
1222
|
+
}
|
|
1258
1223
|
async function readStdin() {
|
|
1259
1224
|
const chunks = [];
|
|
1260
1225
|
for await (const chunk of process.stdin) {
|
|
@@ -1262,92 +1227,18 @@ async function readStdin() {
|
|
|
1262
1227
|
}
|
|
1263
1228
|
return Buffer.concat(chunks).toString("utf8");
|
|
1264
1229
|
}
|
|
1265
|
-
async function runAllocatorCycle(client, payload) {
|
|
1266
|
-
return client.post("/api/allocator/cycle", payload);
|
|
1267
|
-
}
|
|
1268
|
-
function registerAllocatorCommands(program2) {
|
|
1269
|
-
const allocator = program2.command("allocator").description("Run dry-run liquidity allocation cycles");
|
|
1270
|
-
allocator.command("cycle").description("Find LP candidates from /api/feed and run a dry-run allocator cycle").argument(
|
|
1271
|
-
"[screening]",
|
|
1272
|
-
`Optional screening preset: ${PROFILE_CHOICES2.join(" | ")} (same as --profile)`,
|
|
1273
|
-
"lp-opportunity"
|
|
1274
|
-
).option("--profile <name>", `Screening defaults: ${PROFILE_CHOICES2.join(", ")}`).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)").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>", "Feed filter: minimum days until resolution").option("--max-days-to-end <n>", "Feed filter: maximum days until resolution").option("--max-market-age-hours <n>", "With liquid-new-or-long: max age for the new branch").option("--liquid-profile <mode>", "new-or-long (used by liquid-new-or-long screen)").option("--limit <n>", "Max feed markets to fetch (1-100, default 15)", "15").option("--total-holdings <usd>", "Total user 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 cycle", 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("--max-order-notional <usd>", "Maximum notional per planned passive order", parseNonNegative, 25).option("--quote-edge-bps <bps>", "Passive quote edge in basis points", parseNonNegative, 100).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("--allocations <file>", "Existing allocations JSON array; use '-' to read stdin").option("--repeat", "Run a second cycle using targets returned by the first cycle").option("--paused", "Send strategy status paused instead of dry_run").action(async (screening, o) => {
|
|
1275
|
-
const globalOpts = program2.opts();
|
|
1276
|
-
const client = new ApiClient(globalOpts);
|
|
1277
|
-
requireAuth5(client);
|
|
1278
|
-
validatePercentageSizing(o);
|
|
1279
|
-
let profile = o.profile ?? screening ?? "lp-opportunity";
|
|
1280
|
-
if (!isProfile2(profile)) {
|
|
1281
|
-
error(`Unknown screening "${profile}". Use: ${PROFILE_CHOICES2.join(" or ")}`);
|
|
1282
|
-
process.exit(1);
|
|
1283
|
-
}
|
|
1284
|
-
if (o.profile && screening && screening !== "lp-opportunity" && o.profile !== screening) {
|
|
1285
|
-
warn(`Both positional and --profile set; using --profile (${o.profile}).`);
|
|
1286
|
-
profile = o.profile;
|
|
1287
|
-
}
|
|
1288
|
-
const feed = await client.get("/api/feed", feedQueryParams2(profile, o));
|
|
1289
|
-
if (feed.error) {
|
|
1290
|
-
error(feed.error);
|
|
1291
|
-
process.exit(1);
|
|
1292
|
-
}
|
|
1293
|
-
if (feed.markets.length === 0) {
|
|
1294
|
-
warn("No feed markets matched the allocator criteria.");
|
|
1295
|
-
return;
|
|
1296
|
-
}
|
|
1297
|
-
const allocations = await readAllocations(o.allocations);
|
|
1298
|
-
const payload = {
|
|
1299
|
-
strategy: strategyFromOptions(o),
|
|
1300
|
-
markets: feedMarketsToAllocatorMarkets(feed.markets),
|
|
1301
|
-
allocations
|
|
1302
|
-
};
|
|
1303
|
-
if (!globalOpts.json) {
|
|
1304
|
-
process.stderr.write(
|
|
1305
|
-
chalk6.dim(
|
|
1306
|
-
` Running allocator dry-run on ${feed.markets.length} ${profile} candidates...
|
|
1307
|
-
`
|
|
1308
|
-
)
|
|
1309
|
-
);
|
|
1310
|
-
}
|
|
1311
|
-
const first = await runAllocatorCycle(client, payload);
|
|
1312
|
-
const firstResult = first.result ?? {};
|
|
1313
|
-
if (o.repeat) {
|
|
1314
|
-
const repeatPayload = {
|
|
1315
|
-
...payload,
|
|
1316
|
-
allocations: allocationsFromDecisions(firstResult.decisions ?? [])
|
|
1317
|
-
};
|
|
1318
|
-
const second = await runAllocatorCycle(client, repeatPayload);
|
|
1319
|
-
if (globalOpts.json) {
|
|
1320
|
-
json({ initial: first, repeat: second });
|
|
1321
|
-
} else {
|
|
1322
|
-
displayAllocatorCycleResult(firstResult, globalOpts);
|
|
1323
|
-
process.stdout.write("\n" + chalk6.bold("Repeated with target allocations") + "\n");
|
|
1324
|
-
displayAllocatorCycleResult(second.result ?? {}, globalOpts);
|
|
1325
|
-
}
|
|
1326
|
-
return;
|
|
1327
|
-
}
|
|
1328
|
-
if (globalOpts.json) {
|
|
1329
|
-
json(first);
|
|
1330
|
-
} else {
|
|
1331
|
-
displayAllocatorCycleResult(firstResult, globalOpts);
|
|
1332
|
-
}
|
|
1333
|
-
});
|
|
1334
|
-
}
|
|
1335
|
-
|
|
1336
|
-
// src/commands/lp.ts
|
|
1337
|
-
import { InvalidArgumentError as InvalidArgumentError2 } from "commander";
|
|
1338
|
-
import { writeFile } from "fs/promises";
|
|
1339
1230
|
|
|
1340
1231
|
// src/lp-display.ts
|
|
1341
|
-
import
|
|
1232
|
+
import chalk6 from "chalk";
|
|
1342
1233
|
function num2(value, fallback = 0) {
|
|
1343
1234
|
const n = Number(value);
|
|
1344
1235
|
return Number.isFinite(n) ? n : fallback;
|
|
1345
1236
|
}
|
|
1346
1237
|
function signedCurrency2(value) {
|
|
1347
1238
|
const formatted = currency(Math.abs(value));
|
|
1348
|
-
if (value > 0) return
|
|
1349
|
-
if (value < 0) return
|
|
1350
|
-
return
|
|
1239
|
+
if (value > 0) return chalk6.green(`+${formatted}`);
|
|
1240
|
+
if (value < 0) return chalk6.yellow(`-${formatted}`);
|
|
1241
|
+
return chalk6.dim("$0.00");
|
|
1351
1242
|
}
|
|
1352
1243
|
function actionSummary(actions) {
|
|
1353
1244
|
if (!actions) return "none";
|
|
@@ -1360,13 +1251,13 @@ function displayLpScanResult(result, globalOpts) {
|
|
|
1360
1251
|
}
|
|
1361
1252
|
heading("LP Scan");
|
|
1362
1253
|
process.stdout.write(
|
|
1363
|
-
|
|
1254
|
+
chalk6.dim(
|
|
1364
1255
|
` scan ${result.scanId} \xB7 strategy ${result.strategyId} \xB7 ${result.evidenceSaved} evidence rows saved
|
|
1365
1256
|
`
|
|
1366
1257
|
)
|
|
1367
1258
|
);
|
|
1368
1259
|
process.stdout.write(
|
|
1369
|
-
|
|
1260
|
+
chalk6.dim(
|
|
1370
1261
|
` ${result.totalScanned.toLocaleString()} scanned \xB7 ${result.totalAfterFilter.toLocaleString()} after filters \xB7 profile ${result.profile}
|
|
1371
1262
|
|
|
1372
1263
|
`
|
|
@@ -1395,13 +1286,13 @@ function displayLpRecommendResult(result, globalOpts) {
|
|
|
1395
1286
|
}
|
|
1396
1287
|
heading("LP Recommendations");
|
|
1397
1288
|
process.stdout.write(
|
|
1398
|
-
|
|
1289
|
+
chalk6.dim(
|
|
1399
1290
|
` cycle ${result.cycleId} \xB7 strategy ${result.strategyId} \xB7 ${result.candidatesSubmitted} markets \xB7 ${result.allocationsSubmitted} current allocations
|
|
1400
1291
|
`
|
|
1401
1292
|
)
|
|
1402
1293
|
);
|
|
1403
1294
|
process.stdout.write(
|
|
1404
|
-
|
|
1295
|
+
chalk6.dim(
|
|
1405
1296
|
` PnL context ${result.pnlContextCount} rows${result.pnlSynced ? " \xB7 synced" : ""} \xB7 approvals required
|
|
1406
1297
|
|
|
1407
1298
|
`
|
|
@@ -1416,11 +1307,11 @@ function displayLpEvaluateResult(result, globalOpts) {
|
|
|
1416
1307
|
}
|
|
1417
1308
|
heading("LP Evaluation");
|
|
1418
1309
|
process.stdout.write(
|
|
1419
|
-
|
|
1310
|
+
chalk6.dim(
|
|
1420
1311
|
` strategy ${result.strategyId} \xB7 ${result.summary.snapshots} snapshots \xB7 ${result.summary.markets} markets`
|
|
1421
1312
|
)
|
|
1422
1313
|
);
|
|
1423
|
-
if (result.pnlSynced) process.stdout.write(
|
|
1314
|
+
if (result.pnlSynced) process.stdout.write(chalk6.dim(" \xB7 synced"));
|
|
1424
1315
|
process.stdout.write("\n\n");
|
|
1425
1316
|
if (result.syncError) warn(result.syncError);
|
|
1426
1317
|
table(
|
|
@@ -1448,58 +1339,58 @@ function displayLpEvaluateResult(result, globalOpts) {
|
|
|
1448
1339
|
["Market", "Outcome", "Net", "Lesson"]
|
|
1449
1340
|
);
|
|
1450
1341
|
}
|
|
1451
|
-
function displayLpRunResult(result, globalOpts) {
|
|
1452
|
-
if (globalOpts.json) {
|
|
1453
|
-
json(result);
|
|
1454
|
-
return;
|
|
1455
|
-
}
|
|
1456
|
-
heading("LP Run");
|
|
1457
|
-
process.stdout.write(
|
|
1458
|
-
chalk7.dim(
|
|
1459
|
-
` cycle ${result.run.cycleId} \xB7 scan ${result.run.scanId ?? "n/a"} \xB7 strategy ${result.run.strategyId}
|
|
1460
|
-
`
|
|
1461
|
-
)
|
|
1462
|
-
);
|
|
1463
|
-
process.stdout.write(
|
|
1464
|
-
chalk7.dim(
|
|
1465
|
-
` ${result.run.opportunitiesFound} opportunities \xB7 PnL ${result.run.pnlSynced ? "synced" : "not synced"} \xB7 approvals required
|
|
1466
|
-
|
|
1467
|
-
`
|
|
1468
|
-
)
|
|
1469
|
-
);
|
|
1470
|
-
displayAllocatorCycleResult(result.run.result, globalOpts);
|
|
1471
|
-
if (result.evaluation) {
|
|
1472
|
-
process.stdout.write("\n");
|
|
1473
|
-
process.stdout.write(
|
|
1474
|
-
chalk7.dim(
|
|
1475
|
-
` Evaluation: ${signedCurrency2(result.evaluation.summary.netPnl)} net PnL across ${result.evaluation.summary.markets} markets
|
|
1476
|
-
`
|
|
1477
|
-
)
|
|
1478
|
-
);
|
|
1479
|
-
}
|
|
1480
|
-
}
|
|
1481
1342
|
|
|
1482
1343
|
// src/commands/lp.ts
|
|
1483
|
-
function
|
|
1344
|
+
function requireAuth5(client) {
|
|
1484
1345
|
if (!client.isAuthenticated) {
|
|
1485
1346
|
error("Not authenticated. Run `hl auth login` first.");
|
|
1486
1347
|
process.exit(1);
|
|
1487
1348
|
}
|
|
1488
1349
|
}
|
|
1489
|
-
function
|
|
1350
|
+
function parseNonNegative(value) {
|
|
1490
1351
|
const n = Number(value);
|
|
1491
1352
|
if (!Number.isFinite(n) || n < 0) {
|
|
1492
1353
|
throw new InvalidArgumentError2("Expected a non-negative number");
|
|
1493
1354
|
}
|
|
1494
1355
|
return n;
|
|
1495
1356
|
}
|
|
1496
|
-
function
|
|
1357
|
+
function parsePositiveInt(value) {
|
|
1497
1358
|
const n = Number(value);
|
|
1498
1359
|
if (!Number.isInteger(n) || n < 1) {
|
|
1499
1360
|
throw new InvalidArgumentError2("Expected a positive integer");
|
|
1500
1361
|
}
|
|
1501
1362
|
return n;
|
|
1502
1363
|
}
|
|
1364
|
+
function strategyFromAllocatorOptions(opts) {
|
|
1365
|
+
return {
|
|
1366
|
+
id: "cli-lp-allocator",
|
|
1367
|
+
name: "CLI LP allocator dry run",
|
|
1368
|
+
status: opts.paused ? "paused" : "dry_run",
|
|
1369
|
+
...opts.totalHoldings !== void 0 && { total_holdings: opts.totalHoldings },
|
|
1370
|
+
...opts.capitalLimitPct !== void 0 && { capital_limit_pct: opts.capitalLimitPct },
|
|
1371
|
+
...opts.perMarketLimitPct !== void 0 && { per_market_limit_pct: opts.perMarketLimitPct },
|
|
1372
|
+
capital_limit: opts.capitalLimit,
|
|
1373
|
+
per_market_limit: opts.perMarketLimit,
|
|
1374
|
+
min_expected_return_daily_pct: opts.minExpectedReturnDailyPct,
|
|
1375
|
+
max_inventory_imbalance: opts.maxInventoryImbalance,
|
|
1376
|
+
volatility_fill_spike_threshold: opts.volatilityFillSpikeThreshold,
|
|
1377
|
+
event_no_quote_minutes_before: opts.eventNoQuoteMinutesBefore,
|
|
1378
|
+
event_no_quote_minutes_after: opts.eventNoQuoteMinutesAfter,
|
|
1379
|
+
min_liquidity: opts.allocatorMinLiquidity,
|
|
1380
|
+
max_spread: opts.maxSpread,
|
|
1381
|
+
min_days_to_end: opts.allocatorMinDaysToEnd,
|
|
1382
|
+
max_markets: opts.maxMarkets
|
|
1383
|
+
};
|
|
1384
|
+
}
|
|
1385
|
+
function validateAllocatorPercentageSizing(opts) {
|
|
1386
|
+
const usesPercentageSizing = opts.capitalLimitPct !== void 0 || opts.perMarketLimitPct !== void 0;
|
|
1387
|
+
if (usesPercentageSizing && opts.totalHoldings === void 0) {
|
|
1388
|
+
error(
|
|
1389
|
+
"Percentage sizing requires --total-holdings so the allocator can convert percentages into dollar caps."
|
|
1390
|
+
);
|
|
1391
|
+
process.exit(1);
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1503
1394
|
function compact(payload) {
|
|
1504
1395
|
return Object.fromEntries(
|
|
1505
1396
|
Object.entries(payload).filter(([, value]) => value !== void 0 && value !== "")
|
|
@@ -1547,19 +1438,43 @@ function buildLpEvaluatePayload(opts) {
|
|
|
1547
1438
|
limit: opts.limit
|
|
1548
1439
|
});
|
|
1549
1440
|
}
|
|
1550
|
-
function
|
|
1551
|
-
return
|
|
1552
|
-
strategyId: opts.strategyId,
|
|
1553
|
-
limit: opts.limit,
|
|
1554
|
-
syncPnl: opts.syncPnl
|
|
1555
|
-
});
|
|
1441
|
+
async function runAllocatorCycle(client, payload) {
|
|
1442
|
+
return client.post("/api/lp/allocator", payload);
|
|
1556
1443
|
}
|
|
1557
1444
|
function registerLpCommands(program2) {
|
|
1558
1445
|
const lp = program2.command("lp").description("Run persisted liquidity-provider scan, recommendation, and evaluation workflows");
|
|
1559
|
-
lp.command("
|
|
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) => {
|
|
1560
1447
|
const globalOpts = program2.opts();
|
|
1561
1448
|
const client = new ApiClient(globalOpts);
|
|
1562
|
-
|
|
1449
|
+
requireAuth5(client);
|
|
1450
|
+
validateAllocatorPercentageSizing(opts);
|
|
1451
|
+
const markets = await readMarketPayload(opts.markets);
|
|
1452
|
+
if (!markets || markets.length === 0) {
|
|
1453
|
+
error("Markets JSON must include at least one market.");
|
|
1454
|
+
process.exit(1);
|
|
1455
|
+
}
|
|
1456
|
+
const payload = {
|
|
1457
|
+
strategy: strategyFromAllocatorOptions(opts),
|
|
1458
|
+
markets,
|
|
1459
|
+
allocations: await readAllocations(opts.allocations)
|
|
1460
|
+
};
|
|
1461
|
+
if (!globalOpts.json) {
|
|
1462
|
+
process.stderr.write(
|
|
1463
|
+
dim(` Running allocator dry-run on ${markets.length} provided candidates...
|
|
1464
|
+
`)
|
|
1465
|
+
);
|
|
1466
|
+
}
|
|
1467
|
+
const response = await runAllocatorCycle(client, payload);
|
|
1468
|
+
if (globalOpts.json) {
|
|
1469
|
+
json(response);
|
|
1470
|
+
} else {
|
|
1471
|
+
displayAllocatorCycleResult(response.result ?? {}, globalOpts);
|
|
1472
|
+
}
|
|
1473
|
+
});
|
|
1474
|
+
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) => {
|
|
1475
|
+
const globalOpts = program2.opts();
|
|
1476
|
+
const client = new ApiClient(globalOpts);
|
|
1477
|
+
requireAuth5(client);
|
|
1563
1478
|
const result = await client.post(
|
|
1564
1479
|
"/api/lp/scan",
|
|
1565
1480
|
buildLpScanPayload(topic, opts)
|
|
@@ -1567,10 +1482,10 @@ function registerLpCommands(program2) {
|
|
|
1567
1482
|
await writeArtifact(opts.output, result, Boolean(globalOpts.json));
|
|
1568
1483
|
displayLpScanResult(result, globalOpts);
|
|
1569
1484
|
});
|
|
1570
|
-
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)",
|
|
1485
|
+
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) => {
|
|
1571
1486
|
const globalOpts = program2.opts();
|
|
1572
1487
|
const client = new ApiClient(globalOpts);
|
|
1573
|
-
|
|
1488
|
+
requireAuth5(client);
|
|
1574
1489
|
const result = await client.post(
|
|
1575
1490
|
"/api/lp/recommend",
|
|
1576
1491
|
buildLpRecommendPayload(opts)
|
|
@@ -1578,10 +1493,10 @@ function registerLpCommands(program2) {
|
|
|
1578
1493
|
await writeArtifact(opts.output, result, Boolean(globalOpts.json));
|
|
1579
1494
|
displayLpRecommendResult(result, globalOpts);
|
|
1580
1495
|
});
|
|
1581
|
-
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)",
|
|
1496
|
+
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) => {
|
|
1582
1497
|
const globalOpts = program2.opts();
|
|
1583
1498
|
const client = new ApiClient(globalOpts);
|
|
1584
|
-
|
|
1499
|
+
requireAuth5(client);
|
|
1585
1500
|
const result = await client.post(
|
|
1586
1501
|
"/api/lp/evaluate",
|
|
1587
1502
|
buildLpEvaluatePayload(opts)
|
|
@@ -1589,31 +1504,6 @@ function registerLpCommands(program2) {
|
|
|
1589
1504
|
await writeArtifact(opts.output, result, Boolean(globalOpts.json));
|
|
1590
1505
|
displayLpEvaluateResult(result, globalOpts);
|
|
1591
1506
|
});
|
|
1592
|
-
lp.command("run").description("Run scan, recommendation, and evaluation as one dry-run chain").option("--strategy-id <uuid>", "LP strategy id").option("--limit <n>", "Max candidates to scan/recommend (1-25, default 15)", parsePositiveInt2, 15).option("--no-sync-pnl", "Use existing PnL snapshots without refreshing").option("--output <file>", "Write the chained run JSON response to a local file").action(async (opts) => {
|
|
1593
|
-
const globalOpts = program2.opts();
|
|
1594
|
-
const client = new ApiClient(globalOpts);
|
|
1595
|
-
requireAuth6(client);
|
|
1596
|
-
const run = await client.post(
|
|
1597
|
-
"/api/lp/run",
|
|
1598
|
-
buildLpRunPayload(opts)
|
|
1599
|
-
);
|
|
1600
|
-
let evaluation = null;
|
|
1601
|
-
try {
|
|
1602
|
-
evaluation = await client.post("/api/lp/evaluate", {
|
|
1603
|
-
strategyId: run.strategyId,
|
|
1604
|
-
syncPnl: false
|
|
1605
|
-
});
|
|
1606
|
-
} catch (error2) {
|
|
1607
|
-
if (!globalOpts.json) {
|
|
1608
|
-
warn(
|
|
1609
|
-
`Could not load evaluation summary: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
1610
|
-
);
|
|
1611
|
-
}
|
|
1612
|
-
}
|
|
1613
|
-
const combined = { run, evaluation };
|
|
1614
|
-
await writeArtifact(opts.output, combined, Boolean(globalOpts.json));
|
|
1615
|
-
displayLpRunResult(combined, globalOpts);
|
|
1616
|
-
});
|
|
1617
1507
|
}
|
|
1618
1508
|
|
|
1619
1509
|
// src/commands/wallet.ts
|
|
@@ -1624,7 +1514,7 @@ var POLYGON_NATIVE_USDC = "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359";
|
|
|
1624
1514
|
var SUPPORTED_ASSETS = ["pUsd", "usdcE", "matic"];
|
|
1625
1515
|
var POLYGON_NATIVE_ASSET_LABEL = "POL";
|
|
1626
1516
|
var TERMINAL_WITHDRAWAL_STATUSES = /* @__PURE__ */ new Set(["succeeded", "failed", "cancelled", "expired"]);
|
|
1627
|
-
function
|
|
1517
|
+
function requireAuth6(client) {
|
|
1628
1518
|
if (!client.isAuthenticated) {
|
|
1629
1519
|
error("Not authenticated. Run `hl auth login` first.");
|
|
1630
1520
|
process.exit(1);
|
|
@@ -1809,7 +1699,7 @@ function registerWalletCommands(program2) {
|
|
|
1809
1699
|
wallet.command("status").description("Show linked owner and Polymarket deposit wallet status").action(async () => {
|
|
1810
1700
|
const globalOpts = program2.opts();
|
|
1811
1701
|
const client = new ApiClient(globalOpts);
|
|
1812
|
-
|
|
1702
|
+
requireAuth6(client);
|
|
1813
1703
|
const status = await client.get("/api/wallet/status");
|
|
1814
1704
|
if (globalOpts.json) {
|
|
1815
1705
|
json(status);
|
|
@@ -1820,7 +1710,7 @@ function registerWalletCommands(program2) {
|
|
|
1820
1710
|
wallet.command("balances").alias("funds").description("Show available wallet funds").action(async () => {
|
|
1821
1711
|
const globalOpts = program2.opts();
|
|
1822
1712
|
const client = new ApiClient(globalOpts);
|
|
1823
|
-
|
|
1713
|
+
requireAuth6(client);
|
|
1824
1714
|
const balances = await client.get("/api/wallet/balances");
|
|
1825
1715
|
if (globalOpts.json) {
|
|
1826
1716
|
json(balances);
|
|
@@ -1831,7 +1721,7 @@ function registerWalletCommands(program2) {
|
|
|
1831
1721
|
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) => {
|
|
1832
1722
|
const globalOpts = program2.opts();
|
|
1833
1723
|
const client = new ApiClient(globalOpts);
|
|
1834
|
-
|
|
1724
|
+
requireAuth6(client);
|
|
1835
1725
|
const asset = normalizeWalletAsset(opts.asset);
|
|
1836
1726
|
const result = await client.post("/api/wallet/deposit", {
|
|
1837
1727
|
bridge: Boolean(opts.bridge)
|
|
@@ -1854,7 +1744,7 @@ function registerWalletCommands(program2) {
|
|
|
1854
1744
|
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) => {
|
|
1855
1745
|
const globalOpts = program2.opts();
|
|
1856
1746
|
const client = new ApiClient(globalOpts);
|
|
1857
|
-
|
|
1747
|
+
requireAuth6(client);
|
|
1858
1748
|
const created = await client.post(
|
|
1859
1749
|
"/api/wallet/withdraw/intents",
|
|
1860
1750
|
buildWithdrawRequestPayload(opts)
|
|
@@ -1879,9 +1769,209 @@ function registerWalletCommands(program2) {
|
|
|
1879
1769
|
});
|
|
1880
1770
|
}
|
|
1881
1771
|
|
|
1772
|
+
// src/commands/signal.ts
|
|
1773
|
+
import { InvalidArgumentError as InvalidArgumentError4 } from "commander";
|
|
1774
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
1775
|
+
|
|
1776
|
+
// src/signal-display.ts
|
|
1777
|
+
import chalk7 from "chalk";
|
|
1778
|
+
function pctFromSignalValue(value) {
|
|
1779
|
+
if (value === null || value === void 0 || Number.isNaN(value)) return null;
|
|
1780
|
+
return Math.abs(value) <= 1 ? value * 100 : value;
|
|
1781
|
+
}
|
|
1782
|
+
function formatProbability(value) {
|
|
1783
|
+
const pct = pctFromSignalValue(value);
|
|
1784
|
+
return pct === null ? "n/a" : `${pct.toFixed(1)}%`;
|
|
1785
|
+
}
|
|
1786
|
+
function formatGap(value) {
|
|
1787
|
+
if (value === null || value === void 0 || Number.isNaN(value)) return "n/a";
|
|
1788
|
+
const points = Math.abs(value) <= 1 ? value * 100 : value;
|
|
1789
|
+
const formatted = `${points >= 0 ? "+" : ""}${points.toFixed(1)}pp`;
|
|
1790
|
+
if (points > 0) return chalk7.green(formatted);
|
|
1791
|
+
if (points < 0) return chalk7.red(formatted);
|
|
1792
|
+
return chalk7.dim(formatted);
|
|
1793
|
+
}
|
|
1794
|
+
function formatStrength(value) {
|
|
1795
|
+
if (value === "strong") return chalk7.green("strong");
|
|
1796
|
+
if (value === "weak") return chalk7.yellow("weak");
|
|
1797
|
+
return value ? chalk7.dim(value) : "n/a";
|
|
1798
|
+
}
|
|
1799
|
+
function analysisItems(result) {
|
|
1800
|
+
if (!result) return [];
|
|
1801
|
+
if (result.analysis) return [result];
|
|
1802
|
+
return result.analyses ?? [];
|
|
1803
|
+
}
|
|
1804
|
+
function titleFor(analysis) {
|
|
1805
|
+
return analysis.market_name || analysis.market_slug || "market";
|
|
1806
|
+
}
|
|
1807
|
+
function displaySignalAnalysis(response, globalOpts) {
|
|
1808
|
+
if (globalOpts.json) {
|
|
1809
|
+
json(response);
|
|
1810
|
+
return;
|
|
1811
|
+
}
|
|
1812
|
+
if (response.error) {
|
|
1813
|
+
error(response.error);
|
|
1814
|
+
return;
|
|
1815
|
+
}
|
|
1816
|
+
const result = response.result;
|
|
1817
|
+
if (result?.error) {
|
|
1818
|
+
error(result.error);
|
|
1819
|
+
return;
|
|
1820
|
+
}
|
|
1821
|
+
const items = analysisItems(result);
|
|
1822
|
+
if (items.length === 0) {
|
|
1823
|
+
warn("No signal analysis returned.");
|
|
1824
|
+
return;
|
|
1825
|
+
}
|
|
1826
|
+
heading(
|
|
1827
|
+
items.length === 1 ? "Signal Analysis" : `Signal Analysis \u2014 ${items.length} markets`
|
|
1828
|
+
);
|
|
1829
|
+
const rows = items.map((item) => {
|
|
1830
|
+
const analysis = item.analysis ?? {};
|
|
1831
|
+
return [
|
|
1832
|
+
truncate(titleFor(analysis), 44),
|
|
1833
|
+
formatProbability(analysis.current_yes_prob),
|
|
1834
|
+
formatProbability(analysis.predicted_prob),
|
|
1835
|
+
formatGap(analysis.probability_gap),
|
|
1836
|
+
formatStrength(analysis.signal_strength),
|
|
1837
|
+
analysis.confidence ?? "n/a"
|
|
1838
|
+
];
|
|
1839
|
+
});
|
|
1840
|
+
table(rows, ["Market", "Market YES", "Agent YES", "Gap", "Signal", "Conf"]);
|
|
1841
|
+
for (const item of items.slice(0, 3)) {
|
|
1842
|
+
const analysis = item.analysis;
|
|
1843
|
+
if (!analysis) continue;
|
|
1844
|
+
process.stdout.write("\n " + chalk7.bold(truncate(titleFor(analysis), 76)) + "\n");
|
|
1845
|
+
if (analysis.market_link) {
|
|
1846
|
+
process.stdout.write(" " + chalk7.dim("Polymarket: ") + analysis.market_link + "\n");
|
|
1847
|
+
}
|
|
1848
|
+
if (analysis.key_factors && analysis.key_factors.length > 0) {
|
|
1849
|
+
process.stdout.write(" " + chalk7.dim("Key factors: ") + analysis.key_factors.slice(0, 4).join("; ") + "\n");
|
|
1850
|
+
}
|
|
1851
|
+
if (analysis.research_findings) {
|
|
1852
|
+
process.stdout.write(
|
|
1853
|
+
" " + chalk7.dim("Research: ") + truncate(analysis.research_findings, 180) + "\n"
|
|
1854
|
+
);
|
|
1855
|
+
}
|
|
1856
|
+
}
|
|
1857
|
+
if (items.length > 3) {
|
|
1858
|
+
process.stdout.write(chalk7.dim(`
|
|
1859
|
+
... and ${items.length - 3} more
|
|
1860
|
+
`));
|
|
1861
|
+
}
|
|
1862
|
+
if (result?.strong_signal_count !== void 0) {
|
|
1863
|
+
process.stdout.write(
|
|
1864
|
+
chalk7.dim(`
|
|
1865
|
+
Strong signals: ${result.strong_signal_count}
|
|
1866
|
+
`)
|
|
1867
|
+
);
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1871
|
+
// src/commands/signal.ts
|
|
1872
|
+
function requireAuth7(client) {
|
|
1873
|
+
if (!client.isAuthenticated) {
|
|
1874
|
+
error("Not authenticated. Run `hl auth login` first.");
|
|
1875
|
+
process.exit(1);
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1878
|
+
function collect(value, previous = []) {
|
|
1879
|
+
return [...previous, value];
|
|
1880
|
+
}
|
|
1881
|
+
function parseProbability(value) {
|
|
1882
|
+
const n = Number(value);
|
|
1883
|
+
if (!Number.isFinite(n) || n < 0 || n > 100) {
|
|
1884
|
+
throw new InvalidArgumentError4("Expected a probability between 0 and 100");
|
|
1885
|
+
}
|
|
1886
|
+
return n;
|
|
1887
|
+
}
|
|
1888
|
+
async function readStdin2() {
|
|
1889
|
+
const chunks = [];
|
|
1890
|
+
for await (const chunk of process.stdin) {
|
|
1891
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
1892
|
+
}
|
|
1893
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
1894
|
+
}
|
|
1895
|
+
async function readMarketPayload2(path) {
|
|
1896
|
+
if (!path) return {};
|
|
1897
|
+
const raw = path === "-" ? await readStdin2() : await readFile2(path, "utf8");
|
|
1898
|
+
const parsed = JSON.parse(raw);
|
|
1899
|
+
if (Array.isArray(parsed)) {
|
|
1900
|
+
return { markets: parsed };
|
|
1901
|
+
}
|
|
1902
|
+
if (parsed && typeof parsed === "object" && Array.isArray(parsed.markets)) {
|
|
1903
|
+
return { markets: parsed.markets };
|
|
1904
|
+
}
|
|
1905
|
+
if (parsed && typeof parsed === "object" && parsed.market) {
|
|
1906
|
+
return { market: parsed.market };
|
|
1907
|
+
}
|
|
1908
|
+
if (parsed && typeof parsed === "object") {
|
|
1909
|
+
return { market: parsed };
|
|
1910
|
+
}
|
|
1911
|
+
throw new Error("Market JSON must be an object, an array, or { market | markets }");
|
|
1912
|
+
}
|
|
1913
|
+
function inlineMarketFromOptions(opts) {
|
|
1914
|
+
const market = {};
|
|
1915
|
+
if (opts.question) market.question = opts.question;
|
|
1916
|
+
if (opts.description) market.description = opts.description;
|
|
1917
|
+
if (opts.yesProb !== void 0) market.yesPrice = opts.yesProb;
|
|
1918
|
+
if (opts.noProb !== void 0) market.noPrice = opts.noProb;
|
|
1919
|
+
if (opts.slug) market.slug = opts.slug;
|
|
1920
|
+
if (opts.link) market.link = opts.link;
|
|
1921
|
+
return Object.keys(market).length > 0 ? market : void 0;
|
|
1922
|
+
}
|
|
1923
|
+
async function buildSignalPayload(positionalUrl, opts) {
|
|
1924
|
+
const urls = [positionalUrl, ...opts.url ?? []].filter(
|
|
1925
|
+
(value) => Boolean(value)
|
|
1926
|
+
);
|
|
1927
|
+
const filePayload = await readMarketPayload2(opts.market);
|
|
1928
|
+
const inlineMarket = inlineMarketFromOptions(opts);
|
|
1929
|
+
const hasMarketInput = Boolean(filePayload.market || filePayload.markets || inlineMarket);
|
|
1930
|
+
if (urls.length > 0 && hasMarketInput) {
|
|
1931
|
+
throw new Error("Use either URL input or market JSON/options, not both.");
|
|
1932
|
+
}
|
|
1933
|
+
if (filePayload.market && inlineMarket) {
|
|
1934
|
+
throw new Error("Use either --market or inline market options, not both.");
|
|
1935
|
+
}
|
|
1936
|
+
if (filePayload.markets && inlineMarket) {
|
|
1937
|
+
throw new Error("Use either --market or inline market options, not both.");
|
|
1938
|
+
}
|
|
1939
|
+
if (urls.length === 0 && !hasMarketInput) {
|
|
1940
|
+
throw new Error("Provide a Polymarket URL or a market payload.");
|
|
1941
|
+
}
|
|
1942
|
+
const payload = urls.length === 1 ? { url: urls[0] } : urls.length > 1 ? { urls } : filePayload.market ? { market: filePayload.market } : filePayload.markets ? { markets: filePayload.markets } : { market: inlineMarket };
|
|
1943
|
+
if (opts.context) {
|
|
1944
|
+
payload.previous_analysis_context = opts.context;
|
|
1945
|
+
}
|
|
1946
|
+
return payload;
|
|
1947
|
+
}
|
|
1948
|
+
async function runSignalAnalysis(client, payload) {
|
|
1949
|
+
return client.post("/api/signal/analyze", payload);
|
|
1950
|
+
}
|
|
1951
|
+
function registerSignalCommands(program2) {
|
|
1952
|
+
const signal = program2.command("signal").description("Analyze Polymarket probability gaps with the signal agent");
|
|
1953
|
+
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) => {
|
|
1954
|
+
const globalOpts = program2.opts();
|
|
1955
|
+
const client = new ApiClient(globalOpts);
|
|
1956
|
+
requireAuth7(client);
|
|
1957
|
+
let payload;
|
|
1958
|
+
try {
|
|
1959
|
+
payload = await buildSignalPayload(url, o);
|
|
1960
|
+
} catch (e) {
|
|
1961
|
+
error(e instanceof Error ? e.message : String(e));
|
|
1962
|
+
process.exit(1);
|
|
1963
|
+
}
|
|
1964
|
+
if (!globalOpts.json) {
|
|
1965
|
+
process.stderr.write(dim(" Running signal analysis...\n"));
|
|
1966
|
+
}
|
|
1967
|
+
const result = await runSignalAnalysis(client, payload);
|
|
1968
|
+
displaySignalAnalysis(result, globalOpts);
|
|
1969
|
+
});
|
|
1970
|
+
}
|
|
1971
|
+
|
|
1882
1972
|
// src/index.ts
|
|
1883
1973
|
var program = new Command4();
|
|
1884
|
-
program.name("hl").description("Hedge Layer CLI \u2014 prediction market intelligence from the terminal").version("
|
|
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");
|
|
1885
1975
|
registerAuthCommands(program);
|
|
1886
1976
|
registerBriefCommands(program);
|
|
1887
1977
|
registerProfileCommand(program);
|
|
@@ -1889,7 +1979,7 @@ registerResearchCommands(program);
|
|
|
1889
1979
|
registerFeedCommand(program);
|
|
1890
1980
|
registerWalletCommands(program);
|
|
1891
1981
|
registerLpCommands(program);
|
|
1892
|
-
|
|
1982
|
+
registerSignalCommands(program);
|
|
1893
1983
|
program.hook("preAction", (_thisCommand, actionCommand) => {
|
|
1894
1984
|
const opts = program.opts();
|
|
1895
1985
|
if (opts.color === false) {
|