mirror of
https://github.com/theodore-song/polymarket-analyst.git
synced 2026-08-19 18:48:10 +00:00
Grade signals beyond the active scan
This commit is contained in:
@@ -15,7 +15,7 @@ https://polymarket-site-eta.vercel.app/personal.html
|
||||
|
||||
The site fetches live Polymarket markets, generates agent suggestions, lets you
|
||||
run frequent paper cycles, and syncs the shared arena state through Neon or
|
||||
Vercel Blob. Build 42 also installs an offline app shell and caches timestamped
|
||||
Vercel Blob. Build 43 also installs an offline app shell and caches timestamped
|
||||
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.
|
||||
@@ -82,7 +82,7 @@ feature views before repeatable positive evidence can increase size or repeatabl
|
||||
negative evidence can block a new entry. Mixed evidence stays close to neutral
|
||||
instead of being mistaken for an edge.
|
||||
|
||||
Build 42 enforces the documented offline boundary end to end. Cached snapshots
|
||||
Build 43 enforces the documented offline boundary end to end. Cached snapshots
|
||||
under 90 minutes old may continue paper execution. Older snapshots remain usable
|
||||
for valuation and chart snapshots for up to 24 hours, but cannot trigger entries,
|
||||
stop-losses, gain-stops, risk rebalances, settlements, or policy exits. Network
|
||||
@@ -95,6 +95,11 @@ adaptive baselines, pending signal grades, and trade evidence remain in strategy
|
||||
40 until the actual entry, sizing, or exit logic changes. Legacy build 40 and 41
|
||||
records are migrated into the same strategy lineage without losing evidence.
|
||||
|
||||
Build 43 independently refreshes markets for matured pending signals that have
|
||||
left the current top-500 activity scan. Unavailable markets remain queued for a
|
||||
bounded retry window. This prevents activity-rank survivorship from deciding
|
||||
which wins and losses reach the adaptive calibration ledger.
|
||||
|
||||
Run `npm run evaluate:settlements` to evaluate fixed decisions made 1, 3, 7,
|
||||
14, 30, and 90 days before known binary settlements. The audit uses one
|
||||
observation per resolved market and horizon, includes losing contracts at zero,
|
||||
|
||||
+54
-11
@@ -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 40 · build 42</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 40 · build 43</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 42 active:</b> code releases and strategy lineage are now separate. Operational fixes no longer reset adaptive returns or down-weight same-strategy evidence. Strategy 40 keeps its uncertainty gates and offline freshness boundary. This remains paper trading; profits are not guaranteed.</div>
|
||||
<div class="live-build-banner"><b>Build 43 active:</b> matured signals are repriced even after their markets leave the top-500 activity scan, removing a survivorship bias from adaptive learning. Strategy lineage 40 and its return baseline continue unchanged. This remains paper trading; 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 42 · Adaptive strategy 40 · Paper trading only · Live prices from Polymarket's public Gamma API · Not financial advice ·
|
||||
Build 43 · Adaptive strategy 40 · Paper trading only · Live prices from Polymarket's public Gamma API · 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,7 +774,7 @@ 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 = 42;
|
||||
const BUILD_VERSION = 43;
|
||||
const SUGGESTION_ENGINE_VERSION = 40;
|
||||
const LEGACY_BUILD_STRATEGY_LINEAGE = Object.freeze({40:40,41:40});
|
||||
function normalizedStrategyVersion(value){
|
||||
@@ -923,6 +923,8 @@ const PRICE_REQUEST_TIMEOUT_MS = 4000;
|
||||
const SIGNAL_EVAL_HOURS = 12;
|
||||
const SIGNAL_LEDGER_PENDING_LIMIT = 300;
|
||||
const SIGNAL_LEDGER_OUTCOME_LIMIT = 500;
|
||||
const SIGNAL_LEDGER_RETRY_HOURS = 168;
|
||||
const SIGNAL_LEDGER_DUE_FETCH_LIMIT = 80;
|
||||
const currentCycleHour = () => {const p=partsInLocalTime();return `${p.year}-${p.month}-${p.day}T${p.hour}:${p.minute}|s${SUGGESTION_ENGINE_VERSION}`;};
|
||||
function cycleHourFromIso(iso){
|
||||
const d=new Date(iso);
|
||||
@@ -1369,6 +1371,7 @@ function compactAgentStateForSync(st,limits=SYNC_LIMITS){
|
||||
if(st.signal_ledger&&typeof st.signal_ledger==="object")out.signal_ledger={
|
||||
pending:(st.signal_ledger.pending||[]).slice(-SIGNAL_LEDGER_PENDING_LIMIT),
|
||||
outcomes:(st.signal_ledger.outcomes||[]).slice(-SIGNAL_LEDGER_OUTCOME_LIMIT),
|
||||
expired_ungraded:Number(st.signal_ledger.expired_ungraded||0),
|
||||
};
|
||||
delete out.whales;
|
||||
delete out.copycatLeader;
|
||||
@@ -1624,6 +1627,20 @@ function summarizeLearningBucket(bucket,shrinkage){
|
||||
}
|
||||
function defaultSignalLedger(){return {pending:[],outcomes:[]};}
|
||||
function signalPrice(m,side){return side==="YES"?Number(m&&m.yes_price):Number(m&&m.no_price);}
|
||||
function pendingSignalMarketIds(ledger,knownIds=new Set(),now=Date.now()){
|
||||
const rows=((ledger&&ledger.pending)||[]).map(item=>({item,started:new Date(item.observed_at||0).getTime()}))
|
||||
.filter(row=>Number.isFinite(row.started))
|
||||
.filter(row=>{const age=(now-row.started)/3600000;return age>=SIGNAL_EVAL_HOURS&&age<=SIGNAL_LEDGER_RETRY_HOURS;})
|
||||
.sort((a,b)=>a.started-b.started);
|
||||
const ids=[],seen=new Set();
|
||||
for(const {item} of rows){
|
||||
const id=String(item.market_id||"");
|
||||
if(!id||knownIds.has(id)||seen.has(id))continue;
|
||||
seen.add(id);ids.push(id);
|
||||
if(ids.length>=SIGNAL_LEDGER_DUE_FETCH_LIMIT)break;
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
function updateSignalLedger(st,markets,suggestions){
|
||||
const ledger=st.signal_ledger&&typeof st.signal_ledger==="object"?st.signal_ledger:defaultSignalLedger();
|
||||
ledger.pending=Array.isArray(ledger.pending)?ledger.pending:[];ledger.outcomes=Array.isArray(ledger.outcomes)?ledger.outcomes:[];
|
||||
@@ -1637,7 +1654,8 @@ function updateSignalLedger(st,markets,suggestions){
|
||||
const outcome=clamp(gross-estimatedCost,-1,2);
|
||||
ledger.outcomes.push(Object.assign({},item,{evaluated_at:nowIso(),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)}));
|
||||
}else if(ageHours<=72)stillPending.push(item);
|
||||
}else if(ageHours<=SIGNAL_LEDGER_RETRY_HOURS)stillPending.push(item);
|
||||
else ledger.expired_ungraded=Number(ledger.expired_ungraded||0)+1;
|
||||
}
|
||||
const existing=new Set(stillPending.map(x=>x.key)),bucket=Math.floor(now/(6*3600000));
|
||||
for(const s of (suggestions||[]).filter(x=>x.trade_ready)){
|
||||
@@ -1666,6 +1684,7 @@ function buildSignalCalibration(ledger){
|
||||
current_samples:((ledger&&ledger.outcomes)||[]).filter(x=>normalizedStrategyVersion(x.strategy_version)===SUGGESTION_ENGINE_VERSION).length,buckets:learned,
|
||||
promoted_buckets:learnedRows.filter(r=>r.weight>=8&&r.lower_bound>0.003).length,
|
||||
demoted_buckets:learnedRows.filter(r=>r.weight>=8&&r.upper_bound<-0.003).length,
|
||||
expired_ungraded:Number(ledger&&ledger.expired_ungraded||0),
|
||||
pending:((ledger&&ledger.pending)||[]).length};
|
||||
}
|
||||
function calibratedOpportunity(s,calibration){
|
||||
@@ -2413,21 +2432,30 @@ async function runDailyCycle(){
|
||||
const emailOffsets=agentHistoryOffsets(st);
|
||||
const focus=getFocus();
|
||||
// Price map for all open strategy positions.
|
||||
const ids=new Set();
|
||||
const positionIds=new Set();
|
||||
AGENTS.forEach(a=>
|
||||
(st.agents[a.id].positions||[]).forEach(pos=>{if(pos.market_id)ids.add(pos.market_id);}));
|
||||
(st.agents[a.id].positions||[]).forEach(pos=>{if(pos.market_id)positionIds.add(String(pos.market_id));}));
|
||||
setStatus("marking positions…",true);
|
||||
const priceMap={};
|
||||
const cachedById={};
|
||||
(analysisMarkets||[]).forEach(m=>{cachedById[String(m.id)]=m;});
|
||||
Object.entries((cache&&cache.price_map)||{}).forEach(([id,m])=>{cachedById[String(id)]=m;});
|
||||
const analysisIds=new Set((analysisMarkets||[]).map(m=>String(m.id)));
|
||||
const dueSignalIds=runMode==="live"?pendingSignalMarketIds(st.signal_ledger,analysisIds):[];
|
||||
const ids=new Set([...positionIds,...dueSignalIds]);
|
||||
if(runMode==="live"&&dueSignalIds.length)setStatus(`refreshing positions and ${dueSignalIds.length} matured signal market${dueSignalIds.length===1?"":"s"}…`,true);
|
||||
const freshPrices=runMode==="live"
|
||||
?await mapWithConcurrency([...ids],12,async id=>[String(id),await fetchMarketPrice(id)])
|
||||
:[];
|
||||
const freshById=Object.fromEntries(freshPrices);
|
||||
for(const id of ids)priceMap[id]=freshById[String(id)]||cachedById[String(id)]||null;
|
||||
for(const id of ids)priceMap[id]=runMode==="live"
|
||||
?(freshById[String(id)]||((analysisIds.has(String(id)))?cachedById[String(id)]:null))
|
||||
:(cachedById[String(id)]||null);
|
||||
if(runMode==="live")saveMarketCache(analysisMarkets,sugs,priceMap);
|
||||
if(runMode==="live")updateSignalLedger(st,analysisMarkets,sugs);
|
||||
if(runMode==="live"){
|
||||
const supplemental=dueSignalIds.map(id=>freshById[String(id)]).filter(Boolean);
|
||||
updateSignalLedger(st,[...analysisMarkets,...supplemental],sugs);
|
||||
}
|
||||
const marketLearning=buildSignalCalibration(st.signal_ledger);
|
||||
setStatus("ten strategy agents trading…",true);
|
||||
const preBoard=AGENTS.map(a=>({id:a.id,eq:equity(st.agents[a.id])})).sort((x,y)=>y.eq-x.eq);
|
||||
@@ -2633,7 +2661,7 @@ function decisionSummary(p){
|
||||
const blockerRows=Object.entries(d.rejectionCounts||{}).filter(([,count])=>count>0).sort((a,b)=>b[1]-a[1]);
|
||||
const blockers=blockerRows.length?` Blocks: ${blockerRows.slice(0,4).map(([key,count])=>`${blockerLabels[key]||key} ${count}`).join(", ")}.`:"";
|
||||
const learning=d.learning?` Learning: ${d.learning.samples} completed trades retained with older strategies down-weighted, ${(d.learning.global_score*100).toFixed(2)}% shrunk expectancy; ${d.learning.current_samples||0} completed under adaptive strategy ${SUGGESTION_ENGINE_VERSION}${d.learning.best?`; strongest ${d.learning.best.feature.replace(":"," ")}`:""}${d.learning.worst?`; weakest ${d.learning.worst.feature.replace(":"," ")}`:""}.`:"";
|
||||
const calibration=d.marketLearning?` Walk-forward calibration: ${d.marketLearning.samples||0} net-of-cost graded signals (${d.marketLearning.current_samples||0} under adaptive strategy ${SUGGESTION_ENGINE_VERSION}), ${d.marketLearning.pending||0} awaiting a future price; ${d.marketLearning.promoted_buckets||0} feature cohorts promoted and ${d.marketLearning.demoted_buckets||0} demoted. Confidence counts independent outcomes once and uncertainty gates sizing. Historical prior: reversal and sports-trend entries require the fixed 15% exploration lane; crypto, longshots, and YES entries are sized down, not forbidden. Politics trends receive 72 hours before ordinary signal exits.`:"";
|
||||
const calibration=d.marketLearning?` Walk-forward calibration: ${d.marketLearning.samples||0} net-of-cost graded signals (${d.marketLearning.current_samples||0} under adaptive strategy ${SUGGESTION_ENGINE_VERSION}), ${d.marketLearning.pending||0} awaiting a future price${d.marketLearning.expired_ungraded?`, ${d.marketLearning.expired_ungraded} expired ungraded`:""}; ${d.marketLearning.promoted_buckets||0} feature cohorts promoted and ${d.marketLearning.demoted_buckets||0} demoted. Matured markets are repriced even after leaving the active scan. Confidence counts independent outcomes once and uncertainty gates sizing. Historical prior: reversal and sports-trend entries require the fixed 15% exploration lane; crypto, longshots, and YES entries are sized down, not forbidden. Politics trends receive 72 hours before ordinary signal exits.`:"";
|
||||
return `${d.mode} mode: ${d.reason}${emotion} Limits now: ${d.maxNew} new trade${d.maxNew===1?"":"s"}, max ${(d.maxFrac*100).toFixed(1)}% per position${d.minConv?`, conviction ${d.minConv}+`:""}.${learning}${calibration}${exposure}${allocation}${candidates}${blockers}`;
|
||||
}
|
||||
function renderAgentBrief(cfg,p,st){
|
||||
@@ -4206,7 +4234,9 @@ window.PMA_ENGINE_DIAGNOSTICS=Object.freeze({
|
||||
coreGapTradeLossPct:MAX_CORE_GAP_TRADE_LOSS_PCT*100,aggressiveGapTradeLossPct:MAX_AGGRESSIVE_GAP_TRADE_LOSS_PCT*100,
|
||||
offlineEntryMaxAgeMinutes:OFFLINE_ENTRY_MAX_AGE_MS/60000,offlineCacheMaxAgeHours:OFFLINE_CACHE_MAX_AGE_MS/3600000,explorationPct:15,
|
||||
networkTimeoutSeconds:NETWORK_REQUEST_TIMEOUT_MS/1000,priceTimeoutSeconds:PRICE_REQUEST_TIMEOUT_MS/1000,
|
||||
staleCacheIsMarkOnly:true,strategyEvidenceSurvivesBuilds:true,signalRoundTripCostCents:SIGNAL_ROUND_TRIP_COST*100,independentConfidence:true,uncertaintyGatedCalibration:true,
|
||||
staleCacheIsMarkOnly:true,strategyEvidenceSurvivesBuilds:true,signalRetryHours:SIGNAL_LEDGER_RETRY_HOURS,
|
||||
signalDueFetchLimit:SIGNAL_LEDGER_DUE_FETCH_LIMIT,survivorshipSafeSignalGrading:true,
|
||||
signalRoundTripCostCents:SIGNAL_ROUND_TRIP_COST*100,independentConfidence:true,uncertaintyGatedCalibration:true,
|
||||
historicalPrior:"reversal and sports trends blocked outside exploration; crypto, longshots, and YES sized down"}),
|
||||
});
|
||||
function runEngineSelfTest(){
|
||||
@@ -4252,6 +4282,16 @@ function runEngineSelfTest(){
|
||||
const ledgerState={signal_ledger:{pending:[{key:"ledger-test",market_id:"ledger-test",observed_at:hoursAgo(13),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 dueFetchLedger={pending:[
|
||||
{key:"known",market_id:"known-active",observed_at:hoursAgo(13)},
|
||||
{key:"outside-a",market_id:"outside-active-scan",observed_at:hoursAgo(14)},
|
||||
{key:"outside-b",market_id:"outside-active-scan",observed_at:hoursAgo(13)},
|
||||
{key:"young",market_id:"too-young",observed_at:hoursAgo(2)}],outcomes:[]};
|
||||
const dueFetchIds=pendingSignalMarketIds(dueFetchLedger,new Set(["known-active"]));
|
||||
const retryLedgerState={signal_ledger:{pending:[{key:"retry",market_id:"temporarily-unavailable",observed_at:hoursAgo(100),side:"YES",entry_price:0.4}],outcomes:[]}};
|
||||
updateSignalLedger(retryLedgerState,[],[]);
|
||||
const expiredLedgerState={signal_ledger:{pending:[{key:"expired",market_id:"never-returned",observed_at:hoursAgo(169),side:"YES",entry_price:0.4}],outcomes:[]}};
|
||||
updateSignalLedger(expiredLedgerState,[],[]);
|
||||
const calibrationCandidate={signal_type:"trend",quality:"confirmed",category:"Politics",side:"YES",entry_price:0.42};
|
||||
const calibrationFromReturns=returns=>buildSignalCalibration({pending:[],outcomes:returns.map(ret=>Object.assign({},calibrationCandidate,
|
||||
{strategy_version:SUGGESTION_ENGINE_VERSION,return:ret,evaluated_at:closedAt}))});
|
||||
@@ -4319,6 +4359,9 @@ function runEngineSelfTest(){
|
||||
historicalPriorBlocksSportsTrend:!priorSportsTrend.allowed&&priorSportsTrend.blocked_by==="historical",
|
||||
historicalPriorAllowsPoliticsTrend:!priorPoliticsTrend.blocked,
|
||||
ledgerMaturesWithoutLookahead:ledgerState.signal_ledger.pending.length===0&&ledgerState.signal_ledger.outcomes.length===1&&ledgerState.signal_ledger.outcomes[0].return===0.2375,
|
||||
fetchesMaturedMarketsOutsideActiveScan:dueFetchIds.length===1&&dueFetchIds[0]==="outside-active-scan",
|
||||
keepsUnavailableGradesQueued:retryLedgerState.signal_ledger.pending.length===1&&retryLedgerState.signal_ledger.outcomes.length===0,
|
||||
expiresOnlyAfterRetryWindow:expiredLedgerState.signal_ledger.pending.length===0&&expiredLedgerState.signal_ledger.expired_ungraded===1,
|
||||
ledgerIsNetOfCosts:ledgerState.signal_ledger.outcomes[0].gross_return===0.25&&ledgerState.signal_ledger.outcomes[0].estimated_cost_return===0.0125,
|
||||
independentCalibrationConfidence:singleCalibration.confidence<0.06,
|
||||
legacyBuildSharesStrategyLineage:normalizedStrategyVersion(41)===SUGGESTION_ENGINE_VERSION,
|
||||
|
||||
Reference in New Issue
Block a user