Grade adaptive signals with exact market fees

This commit is contained in:
Theodore Song
2026-08-21 20:56:40 -04:00
parent 98744e07ba
commit 4539456ac2
10 changed files with 221 additions and 55 deletions
+21 -4
View File
@@ -6,6 +6,7 @@ const CONCURRENCY = Math.max(1, Math.min(12, Number(process.env.SETTLEMENT_CONCU
const HORIZON_DAYS = [...new Set(String(process.env.SETTLEMENT_HORIZONS || "1,3,7,14,30,90").split(",")
.map(Number).filter((value) => Number.isFinite(value) && value >= 1 && value <= 365))].sort((a, b) => a - b);
const COST_CENTS = Math.max(0, Math.min(5, Number(process.env.SETTLEMENT_COST_CENTS || 0.5)));
const EXACT_GAMMA_FEES = process.env.SETTLEMENT_EXACT_GAMMA_FEES === "1";
const FINE_GRID = String(process.env.SETTLEMENT_FINE_GRID || "false").toLowerCase() === "true";
const SELECTION_ORDER = ["volumeNum", "closedTime", "createdAt", "id"].includes(process.env.SETTLEMENT_ORDER)
? process.env.SETTLEMENT_ORDER : "volumeNum";
@@ -33,6 +34,18 @@ function categoryOf(raw) {
return "Other";
}
function feeScheduleOf(raw) {
const rate = Number(raw?.feeSchedule?.rate), exponent = Number(raw?.feeSchedule?.exponent);
return Number.isFinite(rate) && rate >= 0 && Number.isFinite(exponent) && exponent > 0
? { rate, exponent } : null;
}
function takerFeePerShare(schedule, price) {
const p = Number(price), rate = Number(schedule?.rate), exponent = Number(schedule?.exponent);
if (!(p > 0 && p < 1) || !Number.isFinite(rate) || rate < 0 || !Number.isFinite(exponent) || exponent <= 0) return null;
return rate * Math.pow(p * (1 - p), exponent);
}
function hardSettlementJumpRisk(raw) {
const text = `${raw?.question || ""} ${raw?.events?.[0]?.title || ""}`.toLowerCase();
const numericRange = /\b\d+(?:\.\d+)?\s*(?:%|percent)?\s*(?:-||—|to)\s*\d+(?:\.\d+)?\s*(?:%|percent|votes?|points?|seats?|bps|basis points?|tweets?|posts?|goals?)(?![a-z])/;
@@ -122,7 +135,8 @@ async function fetchResolvedMarkets(limit, skip = 0) {
seen.add(id); raw.push({ id, question: market.question || "", category: categoryOf(market),
eventId: String(market.events?.[0]?.id || id),
tokenId: String(tokens[0]), finalYes: outcomes[0] >= 0.99 ? 1 : 0, closedAt,
safeContract: !hardSettlementJumpRisk(market), volume: Number(market.volumeNum || market.volume || 0) });
safeContract: !hardSettlementJumpRisk(market), feeSchedule: feeScheduleOf(market),
volume: Number(market.volumeNum || market.volume || 0) });
if (raw.length >= targetCount) break;
}
if (page.length < pageSize || !payload.next_cursor || payload.next_cursor === cursor) break;
@@ -141,14 +155,16 @@ function evaluateMarket(market, points) {
const winningSide = market.finalYes ? "YES" : "NO", trend = confirmedTrendAt(points, target, point);
for (const side of ["YES", "NO"]) {
const entry = side === "YES" ? yesEntry : noEntry, final = side === winningSide ? 1 : 0;
const netReturn = final / entry - 1 - (COST_CENTS / 100) / entry;
const fee = EXACT_GAMMA_FEES ? takerFeePerShare(market.feeSchedule, entry) : null;
const entryCost = COST_CENTS / 100 + (EXACT_GAMMA_FEES && Number.isFinite(fee) ? fee : 0);
const netReturn = final / entry - 1 - entryCost / entry;
rows.push({ marketId: market.id, eventId: market.eventId, question: market.question, category: market.category,
safeContract: market.safeContract, closedAt: market.closedAt, volume: market.volume,
horizonDays, side, favorite: side === favoriteSide, winner: side === winningSide,
trend: Boolean(trend && trend.side === side), trendSide: trend?.side || null,
dayMove: trend?.dayMove || 0, weekMove: trend?.weekMove || 0,
strongTrend: Boolean(trend?.strong), moderateTrend: Boolean(trend?.moderate),
entry, band: priceBand(entry), netReturn });
entry, band: priceBand(entry), entryCost, exactFeeSchedule: EXACT_GAMMA_FEES && market.feeSchedule != null, netReturn });
}
}
return rows;
@@ -308,7 +324,8 @@ const rows = successful.flatMap((result) => result.rows);
const report = {
generatedAt: new Date().toISOString(), requestedMarkets: MARKET_LIMIT, resolvedMarkets: markets.length,
marketsWithHistory: successful.length, failures: histories.filter((result) => result?.error).length,
methodology: { horizonDays: HORIZON_DAYS, estimatedRoundTripCostCents: COST_CENTS,
methodology: { horizonDays: HORIZON_DAYS, estimatedEntrySlippageCents: COST_CENTS,
exactGammaFeeSchedules: EXACT_GAMMA_FEES, settlementRedemptionExitFeeCents: 0,
historyFidelityMinutes: 1440,
fineGrid:FINE_GRID,
testedRules:RULES.length,
+28 -6
View File
@@ -8,12 +8,15 @@ const EXTERNAL_MARKET_LIMIT = Math.max(100, Math.min(2000, Number(process.env.SH
const HISTORY_DAYS = Math.max(14, Math.min(30, Number(process.env.SHOCK_HISTORY_DAYS || 30)));
const CONCURRENCY = Math.max(1, Math.min(8, Number(process.env.SHOCK_CONCURRENCY || 6)));
const COST = Math.max(0, Math.min(0.05, Number(process.env.SHOCK_COST_CENTS || 0.5) / 100));
const EXACT_GAMMA_FEES = process.env.SHOCK_EXACT_GAMMA_FEES === "1";
const MIN_ENTRY_PRICE = Math.max(0.02, Math.min(0.40, Number(process.env.SHOCK_MIN_ENTRY_PRICE || 0.08)));
const MAX_ENTRY_PRICE = Math.max(0.60, Math.min(0.98, Number(process.env.SHOCK_MAX_ENTRY_PRICE || 0.92)));
const CACHE_FILE = String(process.env.SHOCK_CACHE_FILE || "").trim();
const OUTPUT_FILE = String(process.env.SHOCK_OUTPUT_FILE || "").trim();
const SUMMARY_ONLY = process.env.SHOCK_SUMMARY === "1";
const SUMMARY_CANDIDATES = Math.max(0, Math.min(10, Number(process.env.SHOCK_SUMMARY_CANDIDATES || 10)));
const CLOSED_ORDER = String(process.env.SHOCK_CLOSED_ORDER || "closedTime").trim() || "closedTime";
const CLOSED_ASCENDING = process.env.SHOCK_CLOSED_ASCENDING === "true";
const HOUR = 3600;
const STRATEGY4_MINIMUM_RUNWAY_HOURS = 14;
const HORIZONS = [3, 6, 12, 24];
@@ -29,6 +32,19 @@ function parseJson(value) {
try { return JSON.parse(value || "[]"); } catch { return []; }
}
function feeScheduleOf(raw) {
if (raw?.feesEnabled === false) return { rate: 0, exponent: 1, takerOnly: true };
const rate = Number(raw?.feeSchedule?.rate), exponent = Number(raw?.feeSchedule?.exponent);
return raw?.feesEnabled === true && Number.isFinite(rate) && rate >= 0 && Number.isFinite(exponent) && exponent > 0
? { rate, exponent, takerOnly: raw.feeSchedule.takerOnly !== false } : null;
}
function takerFeePerShare(schedule, price) {
const p = Number(price);
return schedule && Number.isFinite(p) && p > 0 && p < 1
? Number(schedule.rate) * Math.pow(p * (1 - p), Number(schedule.exponent || 1)) : null;
}
async function fetchJson(url, options = {}, attempts = 3) {
let lastError;
for (let attempt = 0; attempt < attempts; attempt++) {
@@ -88,7 +104,7 @@ async function fetchMarkets(limit, universe = "active") {
if (universe === "closed") {
let cursor = "";
while (markets.length < limit) {
const params = new URLSearchParams({ closed: "true", order: "volumeNum", ascending: "false", limit: "100", include_tag: "true" });
const params = new URLSearchParams({ closed: "true", order: CLOSED_ORDER, ascending: String(CLOSED_ASCENDING), limit: "100", include_tag: "true" });
if (cursor) params.set("after_cursor", cursor);
const payload = await fetchJson(`${GAMMA}/markets/keyset?${params}`), page = payload?.markets;
if (!Array.isArray(page) || !page.length) break;
@@ -100,7 +116,7 @@ async function fetchMarkets(limit, universe = "active") {
if (!id || seen.has(id) || !resolved || labels[0] !== "yes" || labels[1] !== "no" || tokens.length !== 2) continue;
seen.add(id);
const endTs = Date.parse(raw.endDate || raw.events?.[0]?.endDate || "") / 1000;
markets.push({ id, token: tokens[0], category: categoryOf(raw), question: raw.question || "",
markets.push({ id, token: tokens[0], category: categoryOf(raw), question: raw.question || "", feeSchedule: feeScheduleOf(raw),
eventTitle: raw.events?.[0]?.title || "", endTs: Number.isFinite(endTs) ? endTs : null,
eventKey: String(raw.events?.[0]?.id || raw.events?.[0]?.slug || raw.eventId || id) });
if (markets.length >= limit) break;
@@ -124,7 +140,7 @@ async function fetchMarkets(limit, universe = "active") {
if (skipped < ACTIVE_SKIP) { skipped++; continue; }
seen.add(id);
const endTs = Date.parse(raw.endDate || raw.events?.[0]?.endDate || "") / 1000;
markets.push({ id, token: tokens[0], category: categoryOf(raw), question: raw.question || "",
markets.push({ id, token: tokens[0], category: categoryOf(raw), question: raw.question || "", feeSchedule: feeScheduleOf(raw),
eventTitle: raw.events?.[0]?.title || "", endTs: Number.isFinite(endTs) ? endTs : null,
eventKey: String(raw.events?.[0]?.id || raw.events?.[0]?.slug || raw.eventId || id) });
if (markets.length >= limit) break;
@@ -226,6 +242,7 @@ function observations(market, points) {
const future = atOrAfter(points, current.t + horizon * HOUR);
if (!future || future.t - (current.t + horizon * HOUR) > 2 * HOUR) continue;
rows.push({ marketId: market.id, eventKey: market.eventKey, category: market.category, question: market.question,
feeSchedule: market.feeSchedule,
endTs: Number(market.endTs), hardSettlementJumpRisk: hardSettlementJumpRisk(market), observedAt: current.t,
evaluatedAt: future.t, horizon, price: current.p, futurePrice: future.p, ...features });
captured = true;
@@ -255,10 +272,13 @@ function simulate(row, rule) {
if (entry < MIN_ENTRY_PRICE || entry > MAX_ENTRY_PRICE) return null;
const band = priceBand(entry);
if (rule.band !== "All" && band !== rule.band) return null;
const rawNetReturn = exit / entry - 1 - COST / entry;
const entryFee = EXACT_GAMMA_FEES ? takerFeePerShare(row.feeSchedule, entry) : null;
const exitFee = EXACT_GAMMA_FEES ? takerFeePerShare(row.feeSchedule, exit) : null;
const roundTripCost = EXACT_GAMMA_FEES && Number.isFinite(entryFee) && Number.isFinite(exitFee) ? entryFee + exitFee : COST;
const rawNetReturn = (exit - entry - roundTripCost) / entry;
const netReturn = Math.max(-1, Math.min(2, rawNetReturn));
return { marketId: row.marketId, eventKey: row.eventKey, observedAt: row.observedAt, evaluatedAt: row.evaluatedAt,
category: row.category, band, side, entry, exit, netReturn };
category: row.category, band, side, entry, exit, roundTripCost, exactFeeSchedule: EXACT_GAMMA_FEES && Number.isFinite(entryFee) && Number.isFinite(exitFee), netReturn };
}
function summary(rows) {
@@ -399,10 +419,12 @@ const report = { generatedAt: new Date().toISOString(), requestedMarkets: MARKET
archivePassed: finalists.filter((candidate) => candidate.passesArchive).length,
methodology: { requestedHistoryDays: HISTORY_DAYS, medianHistoryCoverageDays: +medianCoverageDays.toFixed(2),
maximumHistoryCoverageDays: +(coverageDays.at(-1) || 0).toFixed(2), observationSpacingHours: 3, horizons: HORIZONS, windows: WINDOWS,
costCents: COST * 100, entryPriceRange: [MIN_ENTRY_PRICE, MAX_ENTRY_PRICE], returnWinsorization: [-1, 2],
costCents: COST * 100, exactGammaFeeSchedules: EXACT_GAMMA_FEES, feeFallbackCents: COST * 100,
entryPriceRange: [MIN_ENTRY_PRICE, MAX_ENTRY_PRICE], returnWinsorization: [-1, 2],
eventSplit: "25% deterministic event-disjoint holdout reserved before search",
split: "Remaining events use 60% train / 20% validation / 20% untouched chronological holdout with future-mark purge",
clusterUnit: "Polymarket event", refinementSource: "Only base rules with a positive train lower bound are refined by category and entry band",
closedArchiveOrder: `${CLOSED_ORDER} ${CLOSED_ASCENDING ? "ascending" : "descending"}`,
strategy4ContractGate: `Reject path barriers and exact numeric ranges; require ${STRATEGY4_MINIMUM_RUNWAY_HOURS} hours to market end at entry`,
limitation: "Current active-market selection is survivorship biased; any holdout winner still requires resolved-market external validation" },
partitions: Object.fromEntries(Object.entries(partitions).map(([key, value]) => [key, value.length])), exactStrategy3, exactStrategy4,
+35 -11
View File
@@ -1,11 +1,12 @@
const GAMMA = "https://gamma-api.polymarket.com";
const CLOB = "https://clob.polymarket.com";
const MARKET_LIMIT = Math.max(10, Math.min(500, Number(process.env.EVAL_MARKETS || 80)));
const MARKET_LIMIT = Math.max(10, Math.min(1000, Number(process.env.EVAL_MARKETS || 80)));
const ACTIVE_SKIP = Math.max(0, Math.min(5000, Number(process.env.EVAL_SKIP || 0)));
const CONCURRENCY = Math.max(1, Math.min(12, Number(process.env.EVAL_CONCURRENCY || 6)));
const HORIZONS = [...new Set(String(process.env.EVAL_HORIZONS || "6,12,24,72").split(",")
.map(Number).filter((value) => Number.isFinite(value) && value >= 1 && value <= 168))].sort((a, b) => a - b);
const COST_CENTS = Math.max(0, Math.min(5, Number(process.env.EVAL_COST_CENTS || 0.5)));
const EXACT_GAMMA_FEES = process.env.EVAL_EXACT_GAMMA_FEES === "1";
const HOUR = 3600;
const CATEGORY_RULES = [
@@ -26,6 +27,18 @@ function categoryOf(raw) {
return CATEGORY_RULES.find(([, keys]) => tags.some((tag) => keys.includes(tag)))?.[0] || "Other";
}
function feeScheduleOf(raw) {
const rate = Number(raw?.feeSchedule?.rate), exponent = Number(raw?.feeSchedule?.exponent);
return Number.isFinite(rate) && rate >= 0 && Number.isFinite(exponent) && exponent > 0
? { rate, exponent } : null;
}
function takerFeePerShare(schedule, price) {
const p = Number(price), rate = Number(schedule?.rate), exponent = Number(schedule?.exponent);
if (!(p > 0 && p < 1) || !Number.isFinite(rate) || rate < 0 || !Number.isFinite(exponent) || exponent <= 0) return null;
return rate * Math.pow(p * (1 - p), exponent);
}
async function fetchJson(url, attempts = 3) {
let lastError;
for (let attempt = 0; attempt < attempts; attempt++) {
@@ -116,12 +129,21 @@ function evaluateMarket(market, points) {
const exit = signal.side === "YES" ? future.p : 1 - future.p;
const fadeExit = signal.side === "YES" ? 1 - future.p : future.p;
const grossReturn = exit / entry - 1;
const netReturn = grossReturn - (COST_CENTS / 100) / entry;
const entryFee = EXACT_GAMMA_FEES ? takerFeePerShare(market.feeSchedule, entry) : null;
const exitFee = EXACT_GAMMA_FEES ? takerFeePerShare(market.feeSchedule, exit) : null;
const roundTripCost = COST_CENTS / 100
+ (EXACT_GAMMA_FEES && Number.isFinite(entryFee) && Number.isFinite(exitFee) ? entryFee + exitFee : 0);
const netReturn = grossReturn - roundTripCost / entry;
const fadeEntryFee = EXACT_GAMMA_FEES ? takerFeePerShare(market.feeSchedule, fadeEntry) : null;
const fadeExitFee = EXACT_GAMMA_FEES ? takerFeePerShare(market.feeSchedule, fadeExit) : null;
const fadeRoundTripCost = COST_CENTS / 100
+ (EXACT_GAMMA_FEES && Number.isFinite(fadeEntryFee) && Number.isFinite(fadeExitFee) ? fadeEntryFee + fadeExitFee : 0);
const fadeNetReturn = fadeEntry > 0.02 && fadeEntry < 0.98
? fadeExit / fadeEntry - 1 - (COST_CENTS / 100) / fadeEntry : null;
? fadeExit / fadeEntry - 1 - fadeRoundTripCost / fadeEntry : null;
outcomes.push({ marketId: market.id, eventKey: market.eventKey, question: market.question, category: market.category,
type: signal.type, side: signal.side, band: priceBand(entry), entry, exit, horizonHours,
grossReturn, netReturn, fadeNetReturn, hourMove: signal.hourMove, dayMove: signal.dayMove, weekMove: signal.weekMove,
grossReturn, netReturn, fadeNetReturn, roundTripCost, exactFeeSchedule: EXACT_GAMMA_FEES && market.feeSchedule != null,
hourMove: signal.hourMove, dayMove: signal.dayMove, weekMove: signal.weekMove,
observedAt: current.t, evaluatedAt: future.t });
captured = true;
}
@@ -261,11 +283,12 @@ function chronologicalEvaluation(rows) {
async function fetchActiveMarkets(limit, skip = 0) {
const markets = [], seen = new Set(), pageSize = 100;
let eligibleSkipped = 0;
for (let offset = 0; markets.length < limit && offset < (limit + skip) * 4; offset += pageSize) {
let eligibleSkipped = 0, cursor = "";
while (markets.length < limit) {
const params = new URLSearchParams({ active: "true", closed: "false", archived: "false", include_tag: "true",
limit: String(pageSize), offset: String(offset), order: "volume24hr", ascending: "false" });
const page = await fetchJson(`${GAMMA}/markets?${params}`);
limit: String(pageSize), order: "volume24hr", ascending: "false" });
if (cursor) params.set("after_cursor", cursor);
const payload = await fetchJson(`${GAMMA}/markets/keyset?${params}`), page = payload?.markets;
if (!Array.isArray(page) || !page.length) break;
for (const market of page) {
const id = String(market.id || ""), labels = parseJson(market.outcomes).map((outcome) => String(outcome).trim().toLowerCase());
@@ -274,7 +297,8 @@ async function fetchActiveMarkets(limit, skip = 0) {
seen.add(id); markets.push(market);
if (markets.length >= limit) break;
}
if (page.length < pageSize) break;
if (page.length < pageSize || !payload.next_cursor || payload.next_cursor === cursor) break;
cursor = payload.next_cursor;
}
return markets.slice(0, limit);
}
@@ -283,7 +307,7 @@ const rawMarkets = await fetchActiveMarkets(MARKET_LIMIT, ACTIVE_SKIP);
const markets = rawMarkets.map((raw) => ({ id: String(raw.id), question: raw.question || "", category: categoryOf(raw),
binaryLabels: parseJson(raw.outcomes).map((outcome) => String(outcome).trim().toLowerCase()),
eventKey: String(raw.events?.[0]?.id || raw.events?.[0]?.slug || raw.eventId || raw.id),
tokenId: String(parseJson(raw.clobTokenIds)[0] || "") })).filter((market) => market.id && market.tokenId
tokenId: String(parseJson(raw.clobTokenIds)[0] || ""), feeSchedule: feeScheduleOf(raw) })).filter((market) => market.id && market.tokenId
&& market.binaryLabels[0] === "yes" && market.binaryLabels[1] === "no");
const histories = await mapLimit(markets, CONCURRENCY, async (market) => {
const data = await fetchJson(`${CLOB}/prices-history?market=${encodeURIComponent(market.tokenId)}&interval=1m&fidelity=60`);
@@ -298,7 +322,7 @@ const primaryOutcomes = outcomes.filter((row) => row.horizonHours === primaryHor
const report = {
generatedAt: new Date().toISOString(), marketLimit: MARKET_LIMIT, activeMarketsSkipped: ACTIVE_SKIP, marketsWithHistory: successful.length,
methodology: { horizonHours: HORIZONS, primaryHorizon, observationBucketHours: 6, historyInterval: "1m", fidelityMinutes: 60,
estimatedRoundTripCostCents: COST_CENTS, clusterUnit: "event",
estimatedRoundTripSlippageCents: COST_CENTS, exactGammaFeeSchedules: EXACT_GAMMA_FEES, clusterUnit: "event",
note: "Current active-market selection and current category tags are a survivorship-biased proxy; signal inputs and future marks are time-ordered without lookahead. Confidence intervals cluster correlated markets by Polymarket event." },
overall: summarize(primaryOutcomes), byType: grouped(primaryOutcomes, "type"), byCategory: grouped(primaryOutcomes, "category"),
byBand: grouped(primaryOutcomes, "band"), bySide: grouped(primaryOutcomes, "side"),
+1 -1
View File
@@ -128,7 +128,7 @@ async function main() {
const branch = process.env.RUNTIME_BRANCH || "runtime-state";
const pathname = process.env.RUNTIME_STATE_PATH || "runtime/state.json";
const arenaUrl = (process.env.ARENA_URL || "https://polymarket-site-eta.vercel.app").replace(/\/$/, "");
const expectedBuild = Number(required("EXPECTED_BUILD", "102"));
const expectedBuild = Number(required("EXPECTED_BUILD", "103"));
await ensureRuntimeBranch(repository, branch);
const prior = await readRuntimeFile(repository, branch, pathname);
if (prior.snapshot) validateRuntimeSnapshot(prior.snapshot, 0, { allowIncomplete: true });
+2 -2
View File
@@ -9,7 +9,7 @@ const runner = fs.readFileSync(new URL("./run-autonomous-cycle.mjs", import.meta
const resolutionAudit = JSON.parse(fs.readFileSync(new URL("../research/resolution-week-no-audit.json", import.meta.url), "utf8"));
const build = Number(index.match(/const BUILD_VERSION = (\d+);/)?.[1]);
assert.equal(build, 102);
assert.equal(build, 103);
assert.deepEqual([...ALLOWED_RUNTIME_KEYS].sort(), ["pma_agents_v2", "pma_suggestions_v5"]);
assert.match(index, /function collectPublicRuntimeItems\(\)/);
assert.match(index, /const PUBLIC_RUNTIME_KEYS=Object\.freeze\(\[AGENTS_KEY,SUG_KEY\]\)/);
@@ -26,7 +26,7 @@ assert.match(api, /Buffer\.from\(file\.content/);
assert.match(api, /searchParams\.set\("runtime", `\$\{Date\.now\(\)\}/);
assert.match(workflow, /cron: "2,7,12,17,22,27,32,37,42,47,52,57 \* \* \* \*"/);
assert.match(workflow, /contents: write/);
assert.match(workflow, /EXPECTED_BUILD: "102"/);
assert.match(workflow, /EXPECTED_BUILD: "103"/);
assert.match(index, /saveSuggestions\(sugs,markets\.length,analysisMarkets\.length,bundleAudit\)/);
assert.match(index, /const NEG_RISK_EVENT_SCAN_LIMIT=1000;/);
assert.match(index, /bundleOpportunityTelemetry:true/);