@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 +7 -1
- package/dist/index.mjs +234 -33
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
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
|
|
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")
|
|
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
|
|
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
|
-
` ${
|
|
1114
|
-
|
|
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 =
|
|
1127
|
-
const expected =
|
|
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(
|
|
1133
|
-
signedCurrency(
|
|
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(
|
|
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
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
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
|
|
1214
|
-
const
|
|
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
|
|
1219
|
-
return parsed
|
|
1409
|
+
if (parsed && typeof parsed === "object" && Array.isArray(parsed[key])) {
|
|
1410
|
+
return parsed[key];
|
|
1220
1411
|
}
|
|
1221
|
-
|
|
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
|
|
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]) =>
|
|
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(
|
|
1275
|
-
compactCurrency(
|
|
1276
|
-
compactCurrency(
|
|
1277
|
-
`${
|
|
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(
|
|
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
|
|
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.
|
|
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);
|