mirror of
https://github.com/theodore-song/polymarket-analyst.git
synced 2026-08-24 04:58:08 +00:00
Grade adaptive signals with exact market fees
This commit is contained in:
@@ -34,7 +34,7 @@ jobs:
|
||||
- run: node scripts/run-autonomous-cycle.mjs
|
||||
env:
|
||||
ARENA_URL: https://polymarket-site-eta.vercel.app
|
||||
EXPECTED_BUILD: "102"
|
||||
EXPECTED_BUILD: "103"
|
||||
RUNTIME_BRANCH: runtime-state
|
||||
RUNTIME_STATE_PATH: runtime/state.json
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -20,7 +20,7 @@ market snapshots. During an outage, cycles continue locally; cached entries are
|
||||
allowed for 90 minutes, older snapshots become mark-only, and all cached data
|
||||
expires after 24 hours.
|
||||
|
||||
Build 102 runs the headless GitHub Actions runtime every five minutes, continues
|
||||
Build 103 runs the headless GitHub Actions runtime every five minutes, continues
|
||||
from the previous agent snapshot,
|
||||
and runs the next due paper cycle even when no browser is open. It writes a
|
||||
sanitized snapshot to the `runtime-state` branch and `/api/state` uses that as a
|
||||
@@ -41,7 +41,7 @@ Polymarket event key. Related Ethereum or Bitcoin contracts cannot create
|
||||
several simultaneous copies of one move, and outcomes from the same underlying
|
||||
three-hour shock window count as one learner event.
|
||||
|
||||
Each Build 102 cycle also scans the 1,000 most-active Polymarket events for
|
||||
Each Build 103 cycle also scans the 1,000 most-active Polymarket events for
|
||||
complete negative-risk bundles and logically nested threshold or deadline
|
||||
pairs. Gamma's market-specific fee flag replaces the old blanket 0.5-cent fee
|
||||
reserve for markets declared fee-free. The closest 60 structures are then
|
||||
@@ -57,7 +57,21 @@ The Suggestions view stores scan, depth, fee, actionable, and closest
|
||||
executable-margin counts so an empty lane is evidence rather than an ambiguous
|
||||
failure.
|
||||
|
||||
Build 102 retains Build 100's retirement of the old 3-6 day resolution-window
|
||||
Build 103 also replaces the directional learner's blanket half-cent cost with
|
||||
the market's Gamma fee schedule at both the entry and future checkpoint, plus a
|
||||
separate half-cent round-trip slippage allowance. Fee-free markets pay only the
|
||||
slippage allowance; an unavailable fee schedule gets a conservative four-cent
|
||||
fee reserve and cannot look artificially profitable. Fee metadata survives
|
||||
cloud compaction and offline caching. The exact-fee replay covered the top 1,000
|
||||
active markets and 208 independent events: broad trends, reversals, and
|
||||
favorite trends all had 90% upper bounds below zero at 12 hours. A separate
|
||||
3,000-recently-closed-market study tested 297 one-decision-per-event settlement
|
||||
rules and found zero robust positive rule. Strategy 61 therefore resets online
|
||||
directional evidence under fee policy 2 and keeps those lanes observation-only
|
||||
until current independent 24-hour and 72-hour cohorts pass the existing
|
||||
promotion gate.
|
||||
|
||||
Build 103 retains Build 100's retirement of the old 3-6 day resolution-window
|
||||
capital permission. That audit clustered confidence by event but still averaged
|
||||
several correlated contracts inside each event, while production could choose
|
||||
only one. The corrected replay chooses the highest-volume eligible contract per event and
|
||||
@@ -301,8 +315,8 @@ Strategy 54 gives previously opened Politics trend positions that 72-hour observ
|
||||
ordinary signal exits. Stops, profit locks, settlement handling, and risk-budget
|
||||
reductions remain immediate.
|
||||
|
||||
Strategy 54 also subtracts a half-cent round-trip cost when grading each live
|
||||
walk-forward signal. Confidence uses the largest independent matching bucket,
|
||||
Strategy 61 applies exact Gamma entry and exit taker fees plus a half-cent
|
||||
slippage allowance when grading each live walk-forward signal. Confidence uses the largest independent matching bucket,
|
||||
not the sum of five overlapping feature buckets, and evidence from older engine
|
||||
versions is down-weighted. This prevents a handful of duplicated observations
|
||||
from authorizing larger positions or hiding a modest negative regime.
|
||||
|
||||
+52
-24
@@ -341,7 +341,7 @@ footer{margin-top:34px;padding-top:22px;border-top:1px solid var(--border);color
|
||||
<nav class="topnav">
|
||||
<div class="brand">
|
||||
<div class="logo">🏆</div>
|
||||
<div><div class="brand-name">Polymarket Arena</div><div class="brand-sub">10 agents · 5 core + 5 aggressive</div><div class="build-badge">Adaptive strategy 60 · depth-capped bundles · build 102</div></div>
|
||||
<div><div class="brand-name">Polymarket Arena</div><div class="brand-sub">10 agents · 5 core + 5 aggressive</div><div class="build-badge">Adaptive strategy 61 · exact-fee learning · build 103</div></div>
|
||||
</div>
|
||||
<div class="tabs" id="tabs">
|
||||
<button class="tab" data-tab="overview">Overview</button>
|
||||
@@ -363,7 +363,7 @@ footer{margin-top:34px;padding-top:22px;border-top:1px solid var(--border);color
|
||||
<div class="personal-banner" id="personalBanner">
|
||||
<b>Personal research mode.</b> This copy is for your own analysis, paper tracking, and manual trade research only. It does not pool money, onboard investors, custody funds, bypass eligibility rules, or place orders without your manual approval.
|
||||
</div>
|
||||
<div class="live-build-banner"><b>Build 102 active:</b> The public paper agents run autonomously every five minutes while every user device is closed. Each cycle scans the 1,000 most-active events for complete negative-risk and same-event dominance bundles, then checks the closest 60 against live CLOB depth for at least a $50 equal-unit order. A bundle can enter only when every leg has enough asks and Polymarket's fee endpoint agrees with the market-specific fee curve applied at each fill; its opened size is capped to the quantity actually depth-tested. Top-of-book gaps alone cannot trigger a trade. The corrected four-day resolution candidate remains a zero-capital learner until 40 independent settlements clear its confidence gate. Phones and computers display the same read-only autonomous state. Profits are not guaranteed.</div>
|
||||
<div class="live-build-banner"><b>Build 103 active:</b> The public paper agents run autonomously every five minutes while every user device is closed. Each cycle scans the 1,000 most-active events for complete negative-risk and same-event dominance bundles, then checks the closest 60 against live CLOB depth for at least a $50 equal-unit order. Directional research now carries Gamma fee schedules through cloud and offline caches and grades each future checkpoint with exact entry and exit taker fees plus a half-cent slippage allowance. The latest disjoint audits rejected broad directional, shock, and near-settlement rules, so they remain zero-capital until independent forward cohorts prove an edge. Phones and computers display the same read-only autonomous state. Profits are not guaranteed.</div>
|
||||
|
||||
<!-- ============ OVERVIEW ============ -->
|
||||
<section class="tabpanel" data-tab="overview">
|
||||
@@ -745,7 +745,7 @@ footer{margin-top:34px;padding-top:22px;border-top:1px solid var(--border);color
|
||||
</section>
|
||||
|
||||
<footer>
|
||||
Build 102 · Adaptive strategy 60 · Five-minute autonomous scans · Live depth, fee, and size-verified bundle audit · Corrected one-decision-per-event settlement audit · 4-day NO forward learner · Zero capital before 40 independent positive outcomes · Autonomous runtime 1 · Offline runtime 2 · Maker research 3 · Agent learning 3 · Paper trading only · Live prices from Polymarket's public Gamma and CLOB APIs · Not financial advice ·
|
||||
Build 103 · Adaptive strategy 61 · Five-minute autonomous scans · Exact-fee directional learning · Live depth, fee, and size-verified bundle audit · Corrected one-decision-per-event settlement audit · Zero capital before independent positive evidence · Autonomous runtime 1 · Offline runtime 2 · Maker research 3 · Agent learning 3 · Paper trading only · Live prices from Polymarket's public Gamma and CLOB APIs · Not financial advice ·
|
||||
<a class="market-link" href="https://github.com/theodore-song/polymarket-analyst" target="_blank" rel="noopener">Source on GitHub</a>
|
||||
</footer>
|
||||
</div>
|
||||
@@ -774,16 +774,16 @@ const POLITICS_TREND_MIN_HOLD_HOURS = 72;
|
||||
const EXIT_CONFIRM_HOURS = 6;
|
||||
const AGENTS_KEY = "pma_agents_v2";
|
||||
const SUG_KEY = "pma_suggestions_v5";
|
||||
const BUILD_VERSION = 102;
|
||||
const BUILD_VERSION = 103;
|
||||
const AGENT_LEARNING_VERSION = 3;
|
||||
const SUGGESTION_ENGINE_VERSION = 60;
|
||||
const SUGGESTION_ENGINE_VERSION = 61;
|
||||
const MAKER_STRATEGY_VERSION = 3;
|
||||
const SHOCK_FADE_STRATEGY_VERSION = 4;
|
||||
const RESOLUTION_WEEK_NO_STRATEGY_VERSION = 3;
|
||||
const PREVIOUS_STRATEGY_VERSION = 59;
|
||||
const DIRECTIONAL_SIGNAL_POLICY_VERSION = 1;
|
||||
const PREVIOUS_STRATEGY_VERSION = 60;
|
||||
const DIRECTIONAL_SIGNAL_POLICY_VERSION = 2;
|
||||
const DIRECTIONAL_SIGNAL_COMPATIBLE_STRATEGY_MIN = 51;
|
||||
const DIRECTIONAL_SIGNAL_COMPATIBLE_STRATEGY_MAX = 60;
|
||||
const DIRECTIONAL_SIGNAL_COMPATIBLE_STRATEGY_MAX = 61;
|
||||
const LEGACY_BUILD_STRATEGY_LINEAGE = Object.freeze({40:40,41:40});
|
||||
function normalizedStrategyVersion(value){
|
||||
const version=Number(value||0);
|
||||
@@ -1037,7 +1037,8 @@ function normalizeMarket(raw,{allowClosed=false}={}){
|
||||
days_to_resolution:daysUntil(raw.endDate),image:raw.image||"",condition_id:String(raw.conditionId||""),
|
||||
tick_size:toNum(raw.orderPriceMinTickSize,0.01),order_min_size:toNum(raw.orderMinSize,5),
|
||||
competitive:toNum(raw.competitive||(ev&&ev.competitive)),rewards_daily_rate:(raw.clobRewards||[]).reduce((sum,reward)=>sum+toNum(reward.rewardsDailyRate),0),
|
||||
rewards_min_size:toNum(raw.rewardsMinSize),rewards_max_spread:toNum(raw.rewardsMaxSpread),fees_enabled:Boolean(raw.feesEnabled),
|
||||
rewards_min_size:toNum(raw.rewardsMinSize),rewards_max_spread:toNum(raw.rewardsMaxSpread),
|
||||
fees_enabled:raw.feesEnabled===false?false:(raw.feesEnabled===true?true:null),fee_schedule:normalizeBundleFeeSchedule(raw),
|
||||
closed:Boolean(raw.closed),accepting_orders:raw.acceptingOrders!==false,
|
||||
url:ev.slug?`https://polymarket.com/event/${ev.slug}`:""};
|
||||
}
|
||||
@@ -1121,6 +1122,15 @@ function bundleFeeReserve(raw,price){
|
||||
if(raw&&raw.feesEnabled===false)return 0;
|
||||
const fee=bundleFeePerShare(normalizeBundleFeeSchedule(raw),price);return Number.isFinite(fee)?fee:SIGNAL_ROUND_TRIP_COST;
|
||||
}
|
||||
function signalExecutionCost(raw,entry,exit){
|
||||
const feesEnabled=raw&&raw.fees_enabled,feeSchedule=raw&&raw.fee_schedule;
|
||||
if(feesEnabled===false)return {cost:SIGNAL_ROUND_TRIP_COST,exact:true,fee:0,slippage:SIGNAL_ROUND_TRIP_COST};
|
||||
const entryFee=bundleFeePerShare(feeSchedule,entry),exitFee=bundleFeePerShare(feeSchedule,exit);
|
||||
if(Number.isFinite(entryFee)&&Number.isFinite(exitFee)){
|
||||
const fee=entryFee+exitFee;return {cost:SIGNAL_ROUND_TRIP_COST+fee,exact:true,fee,slippage:SIGNAL_ROUND_TRIP_COST};
|
||||
}
|
||||
return {cost:SIGNAL_ROUND_TRIP_COST+SIGNAL_UNKNOWN_FEE_FALLBACK,exact:false,fee:SIGNAL_UNKNOWN_FEE_FALLBACK,slippage:SIGNAL_ROUND_TRIP_COST};
|
||||
}
|
||||
function dominanceBundleSuggestions(event,{includeUnprofitable=false}={}){
|
||||
if(!event||event.closed||event.active===false)return [];
|
||||
const rawLegs=Array.isArray(event.markets)?event.markets:[],eventTags=Array.isArray(event.tags)?event.tags:[];
|
||||
@@ -1394,6 +1404,7 @@ const SUGGESTION_PER_CATEGORY=180;
|
||||
const VISIBLE_SUGGESTIONS=900;
|
||||
const REAL_WORLD_SIGNAL_LIMIT=120;
|
||||
const SIGNAL_ROUND_TRIP_COST=0.005;
|
||||
const SIGNAL_UNKNOWN_FEE_FALLBACK=0.04;
|
||||
const NEG_RISK_EVENT_SCAN_LIMIT=1000;
|
||||
const NEG_RISK_MIN_LIQUIDITY=1000;
|
||||
const NEG_RISK_MIN_NET_PROFIT=0.003;
|
||||
@@ -1700,9 +1711,9 @@ function analyzeMarket(m,realWorldSignals={}){
|
||||
let rationale;
|
||||
if(jumpRisk){rationale="Watch only: this market can jump directly to settlement before an hourly stop can protect the position, so agents will not buy it.";}
|
||||
else if(signal.type==="reversal"){rationale=`Observation only: the hourly move opposes the one-day direction, but independent audits found this reversal rule lost after costs. The signal will be graded without risking portfolio cash.`;}
|
||||
else if(signal.type==="trend"&&m.category==="Crypto"){rationale="Observation only: the event-clustered 500-market walk-forward audit found Crypto trends unreliable after costs. This signal keeps training the ledger without risking portfolio cash.";}
|
||||
else if(signal.type==="trend"&&m.category==="Sports"){rationale="Observation only: the event-clustered 500-market walk-forward audit found Sports trends lost after costs across every chronological segment. This signal keeps training the ledger without risking portfolio cash.";}
|
||||
else if(signal.type==="trend"){rationale=`Observation only: the event-clustered 500-market audit found the broad trend family lost after costs. This ${side} ${m.category} cohort must independently promote before agents can risk cash.`;}
|
||||
else if(signal.type==="trend"&&m.category==="Crypto"){rationale="Observation only: the exact-fee 1,000-market walk-forward audit found Crypto trends unreliable after costs. This signal keeps training the ledger without risking portfolio cash.";}
|
||||
else if(signal.type==="trend"&&m.category==="Sports"){rationale="Observation only: the exact-fee 1,000-market walk-forward audit found Sports trends lost after costs across every chronological segment. This signal keeps training the ledger without risking portfolio cash.";}
|
||||
else if(signal.type==="trend"){rationale=`Observation only: the exact-fee 1,000-market audit found the broad trend family lost after costs. This ${side} ${m.category} cohort must independently promote before agents can risk cash.`;}
|
||||
else if(strictTradeReady){rationale=`Confirmed setup: ${side} has ${Math.round(signal.strength*100)} signal strength across independent time windows and a ${(absNet*100).toFixed(1)}c signal margin after friction and category uncertainty.`;}
|
||||
else if(catalystTradeReady){rationale=`Catalyst-confirmed setup: ${side} price action agrees across the required windows, recent outside coverage exists, and ${(absNet*100).toFixed(1)}c of estimated movement remains after friction. News coverage confirms activity, not direction.`;}
|
||||
else if(reversalTradeReady){rationale=`Confirmed reversal: the one-day move is reversing in the hourly window with adequate liquidity and a ${(absNet*100).toFixed(1)}c post-friction signal margin.`;}
|
||||
@@ -1727,6 +1738,7 @@ function analyzeMarket(m,realWorldSignals={}){
|
||||
spread:m.spread,price_change_1h:m.price_change_1h,price_change_1d:m.price_change_1d,price_change_1w:m.price_change_1w,
|
||||
momentum_strength:+signal.strength.toFixed(2),signal_strength:+signal.strength.toFixed(2),signal_confidence:+signal.confidence.toFixed(2),signal_type:signal.type,
|
||||
trade_ready:tradeReady,entry_candidate:entryCandidate,audited_observation_only:auditedObservationOnly,jump_risk:jumpRisk,
|
||||
fees_enabled:m.fees_enabled,fee_schedule:m.fee_schedule,
|
||||
days_to_resolution:m.days_to_resolution!=null?+m.days_to_resolution.toFixed(1):null,drivers,rationale};
|
||||
}
|
||||
const pct=(x)=>Math.round(x*100)+"%";
|
||||
@@ -1874,6 +1886,7 @@ function compactSuggestionForSync(s){
|
||||
fair_value:s.fair_value,edge:s.edge,side:s.side,entry_price:s.entry_price,market_price:s.market_price,
|
||||
net_edge:s.net_edge,friction:s.friction,chase_penalty:s.chase_penalty,evidence_score:s.evidence_score,evidence_source_count:s.evidence_source_count,quality:s.quality,
|
||||
conviction:s.conviction,volume:s.volume,volume_24hr:s.volume_24hr,liquidity:s.liquidity,
|
||||
fees_enabled:s.fees_enabled,fee_schedule:s.fee_schedule,
|
||||
spread:s.spread,price_change_1h:s.price_change_1h,price_change_1d:s.price_change_1d,price_change_1w:s.price_change_1w,momentum_strength:s.momentum_strength,
|
||||
signal_strength:s.signal_strength,signal_confidence:s.signal_confidence,signal_type:s.signal_type,
|
||||
trade_ready:s.trade_ready,entry_candidate:s.entry_candidate,audited_observation_only:s.audited_observation_only,
|
||||
@@ -1924,7 +1937,8 @@ function compactCachedMarket(m){
|
||||
days_to_resolution:m.days_to_resolution,end_date:m.end_date,game_start:m.game_start,hours_to_start:m.hours_to_start,
|
||||
sports_market_type:m.sports_market_type,closed:m.closed,accepting_orders:m.accepting_orders,
|
||||
condition_id:m.condition_id,tick_size:m.tick_size,order_min_size:m.order_min_size,competitive:m.competitive,
|
||||
rewards_daily_rate:m.rewards_daily_rate,rewards_min_size:m.rewards_min_size,rewards_max_spread:m.rewards_max_spread,fees_enabled:m.fees_enabled};
|
||||
rewards_daily_rate:m.rewards_daily_rate,rewards_min_size:m.rewards_min_size,rewards_max_spread:m.rewards_max_spread,
|
||||
fees_enabled:m.fees_enabled,fee_schedule:m.fee_schedule};
|
||||
}
|
||||
function saveMarketCache(markets,suggestions,priceMap={}){
|
||||
const payload={version:BUILD_VERSION,strategy_version:SUGGESTION_ENGINE_VERSION,captured_at:nowIso(),markets:(markets||[]).slice(0,MARKET_ANALYSIS_LIMIT).map(compactCachedMarket),
|
||||
@@ -2266,11 +2280,14 @@ function updateSignalLedger(st,markets,suggestions){
|
||||
const fresh=marketMap[String(item.market_id)],future=signalPrice(fresh,item.side);
|
||||
if(dueHorizon!=null&&Number.isFinite(future)&&future>=0&&future<=1){
|
||||
const entry=Math.max(0.01,Number(item.entry_price||0.01));
|
||||
const gross=future/entry-1,estimatedCost=SIGNAL_ROUND_TRIP_COST/entry;
|
||||
const feeSource=fresh||item,executionCost=signalExecutionCost(feeSource,entry,future);
|
||||
const gross=future/entry-1,estimatedCost=executionCost.cost/entry;
|
||||
const outcome=clamp(gross-estimatedCost,-1,2);
|
||||
graded.add(dueHorizon);
|
||||
ledger.outcomes.push(Object.assign({},item,{graded_horizons:undefined,evaluated_at:nowIso(),target_horizon_hours:dueHorizon,horizon_hours:+ageHours.toFixed(1),future_price:+future.toFixed(4),
|
||||
gross_return:+gross.toFixed(4),estimated_cost_return:+estimatedCost.toFixed(4),return:+outcome.toFixed(4)}));
|
||||
gross_return:+gross.toFixed(4),estimated_cost_per_share:+executionCost.cost.toFixed(5),estimated_fee_per_share:+executionCost.fee.toFixed(5),
|
||||
estimated_slippage_per_share:+executionCost.slippage.toFixed(5),exact_fee_schedule:executionCost.exact,
|
||||
estimated_cost_return:+estimatedCost.toFixed(4),return:+outcome.toFixed(4)}));
|
||||
}
|
||||
const remaining=SIGNAL_EVAL_HORIZONS.filter(horizon=>!graded.has(horizon)&&ageHours<=horizon+SIGNAL_EVAL_TOLERANCE_HOURS);
|
||||
if(remaining.length)stillPending.push(Object.assign({},item,{graded_horizons:[...graded].sort((a,b)=>a-b)}));
|
||||
@@ -2294,6 +2311,7 @@ function updateSignalLedger(st,markets,suggestions){
|
||||
days_to_resolution:s.days_to_resolution,trade_ready_at_observation:Boolean(s.trade_ready),signal_confidence:Number(s.signal_confidence||0),
|
||||
signal_strength:Number(s.signal_strength||0),evidence_score:Number(s.evidence_score||0),evidence_source_count:Number(s.evidence_source_count||0),
|
||||
price_change_1d:Number(s.price_change_1d||0),agent_learning_version:AGENT_LEARNING_VERSION,
|
||||
fees_enabled:s.fees_enabled,fee_schedule:s.fee_schedule,
|
||||
eligible_agent_ids:AGENTS.filter(agent=>agentObservesSuggestion(agent,s)).map(agent=>agent.id),
|
||||
signal_policy_version:DIRECTIONAL_SIGNAL_POLICY_VERSION,strategy_version:SUGGESTION_ENGINE_VERSION,build_version:BUILD_VERSION});
|
||||
}
|
||||
@@ -2827,16 +2845,18 @@ function stableExploration(agentId,marketId){
|
||||
const owner=AGENTS[stableHash(`owner:${id}`)%AGENTS.length];
|
||||
return Boolean(owner&&owner.id===agentId);
|
||||
}
|
||||
const DIRECTIONAL_REPLAY_AUDIT=Object.freeze({generated_at:"2026-08-21T19:28:05Z",markets:300,events:68,cost_cents:0.5,
|
||||
favorite_trend:Object.freeze({hard_blocked:true,horizons_hours:[6,12,24,72],minimum_observations:1276,
|
||||
net_means:[-0.00724,-0.00767,-0.00714,-0.01411],upper_90:[-0.00529,-0.00646,-0.00451,-0.00888]})});
|
||||
const DIRECTIONAL_REPLAY_AUDIT=Object.freeze({generated_at:"2026-08-22T00:28:00Z",markets:1000,events:208,
|
||||
cost_model:"exact Gamma entry/exit taker fees plus 0.5c round-trip slippage",
|
||||
broad_trend:Object.freeze({events:204,event_mean:-0.03595,lower_90:-0.05143,upper_90:-0.02046}),
|
||||
reversal:Object.freeze({events:122,event_mean:-0.03022,lower_90:-0.04306,upper_90:-0.01738}),
|
||||
favorite_trend:Object.freeze({hard_blocked:true,events:173,event_mean:-0.02886,lower_90:-0.03575,upper_90:-0.02196})});
|
||||
function historicalOpportunityPrior(s){
|
||||
const rows=[],resolutionDays=Number(s.days_to_resolution),hasResolution=s.days_to_resolution!=null&&s.days_to_resolution!==""&&Number.isFinite(resolutionDays);
|
||||
if((s.signal_type||"")==="trend")rows.push({feature:"broad-trend",score:-0.0123,samples:3239,blocked:true,requiresPromotion:true,
|
||||
if((s.signal_type||"")==="trend")rows.push({feature:"broad-trend",score:DIRECTIONAL_REPLAY_AUDIT.broad_trend.event_mean,samples:7191,blocked:true,requiresPromotion:true,
|
||||
requiredPromotionFeatures:["signal:trend",`side:${s.side||"unknown"}`,`category:${s.category||"Other"}`]});
|
||||
if((s.signal_type||"")==="trend"&&["favorite","heavy-favorite"].includes(entryBand(s.entry_price)))rows.push({feature:"favorite-trend-replay",score:-0.0071,
|
||||
samples:DIRECTIONAL_REPLAY_AUDIT.favorite_trend.minimum_observations,blocked:true,hardBlocked:true});
|
||||
if((s.signal_type||"")==="reversal")rows.push({feature:"reversal",score:-0.0369,samples:207,blocked:true,requiresPromotion:true,
|
||||
samples:DIRECTIONAL_REPLAY_AUDIT.favorite_trend.events,blocked:true,hardBlocked:true});
|
||||
if((s.signal_type||"")==="reversal")rows.push({feature:"reversal",score:DIRECTIONAL_REPLAY_AUDIT.reversal.event_mean,samples:1224,blocked:true,requiresPromotion:true,
|
||||
requiredPromotionFeatures:["signal:reversal","quality:reversal"]});
|
||||
if((s.side||"")==="NO"&&hasResolution&&resolutionDays<=21)rows.push({feature:"short-no",score:-0.3733,samples:64,blocked:true,requiresPromotion:true,
|
||||
requiredPromotionFeatures:["side:NO","duration:short"]});
|
||||
@@ -6114,7 +6134,8 @@ window.PMA_ENGINE_DIAGNOSTICS=Object.freeze({
|
||||
signalEvaluationToleranceHours:SIGNAL_EVAL_TOLERANCE_HOURS,signalRetryHours:SIGNAL_LEDGER_RETRY_HOURS,
|
||||
signalPendingLimit:SIGNAL_LEDGER_PENDING_LIMIT,signalOutcomeLimit:SIGNAL_LEDGER_OUTCOME_LIMIT,
|
||||
signalDueFetchLimit:SIGNAL_LEDGER_DUE_FETCH_LIMIT,survivorshipSafeSignalGrading:true,
|
||||
signalRoundTripCostCents:SIGNAL_ROUND_TRIP_COST*100,independentConfidence:true,eventClusteredCalibration:true,
|
||||
signalRoundTripSlippageCents:SIGNAL_ROUND_TRIP_COST*100,signalUnknownFeeFallbackCents:SIGNAL_UNKNOWN_FEE_FALLBACK*100,
|
||||
exactGammaDirectionalFees:true,independentConfidence:true,eventClusteredCalibration:true,
|
||||
onePendingObservationPerMarketSide:true,oldestPendingEvidenceFirst:true,coverageAwareObservationSampling:true,uncertaintyGatedCalibration:true,
|
||||
unassignedMigrationRecordsDoNotBlockScopedReplacement:true,agentLearningVersion:AGENT_LEARNING_VERSION,
|
||||
directionalSignalsRequirePromotion:true,directionalSignalPolicyVersion:DIRECTIONAL_SIGNAL_POLICY_VERSION,
|
||||
@@ -6157,14 +6178,14 @@ window.PMA_ENGINE_DIAGNOSTICS=Object.freeze({
|
||||
adopterAgentIds:[...RESOLUTION_WEEK_NO_ADOPTER_IDS],backtestApproved:RESOLUTION_WEEK_NO_BACKTEST_APPROVED,
|
||||
shadowLimit:RESOLUTION_WEEK_NO_SHADOW_LIMIT,outcomeLimit:RESOLUTION_WEEK_NO_OUTCOME_LIMIT,capitalRequiresForwardPromotion:true,
|
||||
oneDecisionPerEventAudit:true,disjointHoldoutRequired:true,fixedDateOnly:true,sportsExcluded:true,settlementOnlyExit:true,oneOwnerPerEvent:true,sharedForwardDemotion:true},
|
||||
historicalPrior:"No broad directional rule survived the adaptive holdouts at both 24h and 72h. Contract-safe Shock Strategy 4 remains zero-capital after its untouched expansion failed. The earlier 3-6 day 50%-55% NO permission was invalidated because its audit averaged several correlated contracts per event. A corrected one-decision-per-event replay retained only the four-day discovery window, and that candidate failed the all-segment gate in a disjoint older 3,000-market holdout. It now collects zero-capital forward settlements and requires 40 independent outcomes with a positive lower confidence bound above 1% before 0.5% paper positions can begin. Favorite-priced trend remains hard-blocked. Maker capital remains disabled"}),
|
||||
historicalPrior:"An exact-fee 1,000-market directional audit found broad trend and reversal families negative across chronological segments. A 3,000-market recent settlement audit found zero robust positive rule among 297 candidates. Contract-safe shock, corrected four-day NO, sports-favorite, and maker lanes remain zero-capital until their independent forward gates pass. Complete depth-and-fee-verified bundles remain the only immediately capital-enabled entry lane."}),
|
||||
});
|
||||
function runEngineSelfTest(){
|
||||
const market=(overrides={})=>Object.assign({
|
||||
id:"self-test",question:"Will the president win the election?",event:"Election",category:"Politics",tags:["election"],
|
||||
yes_price:0.42,no_price:0.58,volume:500000,volume_24hr:60000,volume_1wk:280000,liquidity:75000,spread:0.01,
|
||||
price_change_1h:0.004,price_change_1d:0.035,price_change_1w:0.08,days_to_resolution:30,
|
||||
clob_token_ids:["yes","no"],url:"",closed:false,accepting_orders:true,
|
||||
clob_token_ids:["yes","no"],url:"",closed:false,accepting_orders:true,fees_enabled:false,fee_schedule:{rate:0,exponent:1,takerOnly:true},
|
||||
},overrides);
|
||||
const news={"self-test":{source_count:3,latest_title:"Election polling update"}};
|
||||
const trend=analyzeMarket(market(),news);
|
||||
@@ -6420,6 +6441,9 @@ function runEngineSelfTest(){
|
||||
const ledgerState={signal_ledger:{pending:[{key:"ledger-test",market_id:"ledger-test",observed_at:hoursAgo(25),side:"YES",entry_price:0.40,
|
||||
signal_type:"trend",quality:"confirmed",category:"Politics"}],outcomes:[]}};
|
||||
updateSignalLedger(ledgerState,[market({id:"ledger-test",yes_price:0.50,no_price:0.50})],[]);
|
||||
const feeFreeSignalCost=signalExecutionCost({fees_enabled:false,fee_schedule:{rate:0,exponent:1}},0.50,0.60);
|
||||
const exactFeeSignalCost=signalExecutionCost({fees_enabled:true,fee_schedule:{rate:0.07,exponent:1}},0.50,0.60);
|
||||
const unknownFeeSignalCost=signalExecutionCost({},0.50,0.60);
|
||||
const secondHorizonState={signal_ledger:{pending:[{key:"ledger-second",market_id:"ledger-second",observed_at:hoursAgo(73),graded_horizons:[24],side:"YES",entry_price:0.40,
|
||||
signal_type:"trend",quality:"confirmed",category:"Politics"}],outcomes:[]}};
|
||||
updateSignalLedger(secondHorizonState,[market({id:"ledger-second",yes_price:0.52,no_price:0.48})],[]);
|
||||
@@ -6878,6 +6902,10 @@ function runEngineSelfTest(){
|
||||
readOnlyClientsDoNotAdvance:autonomousRuntimeControlsCycle({read_only:true,source:"github-actions"},false),
|
||||
headlessRunnerCanAdvance:!autonomousRuntimeControlsCycle({read_only:true,source:"github-actions"},true)},
|
||||
trend:{ready:trend.trade_ready,quality:trend.quality,side:trend.side,margin:trend.net_edge},
|
||||
exactFeeLearning:{feeFreeUsesSlippageOnly:feeFreeSignalCost.cost===0.005,
|
||||
appliesEntryAndExitFeeCurve:Math.abs(exactFeeSignalCost.cost-0.0393)<0.0000001,
|
||||
unknownScheduleUsesConservativeFallback:unknownFeeSignalCost.cost===0.045,
|
||||
normalizedFeeScheduleSurvivesCache:compactCachedMarket(market({fees_enabled:true,fee_schedule:{rate:0.07,exponent:1}})).fee_schedule.rate===0.07},
|
||||
auditedTrendCategories:{sportsObservationOnly:!sportsTrend.trade_ready&&sportsTrend.signal_type==="trend",cryptoObservationOnly:!cryptoTrend.trade_ready&&cryptoTrend.signal_type==="trend"},
|
||||
noSignal:{ready:noSignal.trade_ready,quality:noSignal.quality,signal:noSignal.signal_type,margin:noSignal.net_edge},
|
||||
reversal:{ready:reversal.trade_ready,quality:reversal.quality,side:reversal.side,margin:reversal.net_edge},
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"generatedAt": "2026-08-22T00:55:19.692Z",
|
||||
"strategy": 61,
|
||||
"verdict": "No tested directional, shock-fade, or near-settlement rule earned capital permission. Keep those lanes observation-only and retain only live depth-and-fee-verified complete bundles as immediately capital-enabled.",
|
||||
"costModel": {
|
||||
"directional": "Gamma entry and exit taker fee schedules plus 0.5 cents of round-trip slippage",
|
||||
"settlement": "Gamma entry taker fee plus 0.5 cents of entry slippage; redemption has no modeled exit trade",
|
||||
"unknownFeeSchedule": "4 cent conservative fee fallback"
|
||||
},
|
||||
"directional": {
|
||||
"primary": {
|
||||
"activeMarkets": 1000,
|
||||
"activeMarketsSkipped": 0,
|
||||
"eventsAt12Hours": 208,
|
||||
"eventMeanAt12Hours": -0.03932,
|
||||
"lower90At12Hours": -0.05078,
|
||||
"upper90At12Hours": -0.02786,
|
||||
"robustPositiveRulesAt12Hours": 0,
|
||||
"robustNegativeRulesAt12Hours": 64
|
||||
},
|
||||
"independent": {
|
||||
"activeMarkets": 1000,
|
||||
"activeMarketsSkipped": 1000,
|
||||
"eventsAt12Hours": 254,
|
||||
"eventMeanAt12Hours": -0.05392,
|
||||
"lower90At12Hours": -0.06326,
|
||||
"upper90At12Hours": -0.04457,
|
||||
"robustPositiveRulesByHorizon": {
|
||||
"6": 0,
|
||||
"12": 0,
|
||||
"24": 0,
|
||||
"72": 0
|
||||
},
|
||||
"robustNegativeRulesByHorizon": {
|
||||
"6": 69,
|
||||
"12": 66,
|
||||
"24": 51,
|
||||
"72": 34
|
||||
}
|
||||
}
|
||||
},
|
||||
"settlement": {
|
||||
"recentResolvedMarkets": 3000,
|
||||
"marketsWithHistory": 2916,
|
||||
"testedRules": 297,
|
||||
"horizonsDays": [1, 3, 7, 14, 30],
|
||||
"robustPositiveRules": 0
|
||||
},
|
||||
"shockFade": {
|
||||
"selectedOnIndependentCohort": "fade_h12_w3_m0.08_aligned_v1_All_All",
|
||||
"freshPrimaryTrainLower90": 0.02099,
|
||||
"freshPrimaryValidationLower90": 0.02641,
|
||||
"freshPrimaryHoldoutLower90": -0.00751,
|
||||
"freshPrimaryVerdict": "rejected"
|
||||
},
|
||||
"limitations": [
|
||||
"CLOB price history is a midpoint proxy and does not reconstruct historical order-book depth.",
|
||||
"Active-market cohorts are survivorship-biased, so positive results would still require resolved-market and forward validation.",
|
||||
"The absence of a validated rule is evidence for retaining cash, not proof that no future opportunity can exist."
|
||||
]
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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/);
|
||||
|
||||
Reference in New Issue
Block a user