Verify accounting and rank current strategy

This commit is contained in:
Theodore Song
2026-08-18 13:05:10 -04:00
parent 97186482df
commit c7abdf8d4d
3 changed files with 57 additions and 20 deletions
+7 -3
View File
@@ -15,11 +15,15 @@ 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 53 also installs an offline app shell and caches timestamped
Vercel Blob. Build 55 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.
Build 55 ranks the competition by each agent's return since Strategy 49 began.
Historical replay equity remains visible for context, but it no longer makes an
agent look like the current leader when the live adaptive strategy is losing.
Each agent learns bounded weights from its own v34+ trade outcomes across signal
type, setup quality, category, side, entry-price band, and time to resolution.
The learner shrinks small samples toward neutral, caps sizing changes to
@@ -99,7 +103,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 53 enforces the documented offline boundary end to end. Cached snapshots
Build 55 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
@@ -112,7 +116,7 @@ adaptive baselines, pending signal grades, and trade evidence remain in one stra
lineage 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 53 independently refreshes markets for matured pending signals that have
Build 55 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.
+49 -16
View File
@@ -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 49 · build 53</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 49 · build 55</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 53 active:</b> adaptive evidence is clustered by Polymarket event, and the pending ledger preserves the oldest signals until grading. Correlated outcome markets cannot promote a strategy as independent bets. Crypto and Sports trends remain observation-only; settlement-jump barriers remain excluded. This remains paper trading; profits are not guaranteed.</div>
<div class="live-build-banner"><b>Build 55 active:</b> the competition is ranked by current adaptive-strategy return rather than legacy replay equity. Event-clustered evidence, protected signal grading, and transaction conservation tests remain active. This remains paper trading; profits are not guaranteed.</div>
<!-- ============ OVERVIEW ============ -->
<section class="tabpanel" data-tab="overview">
@@ -425,7 +425,7 @@ footer{margin-top:34px;padding-top:22px;border-top:1px solid var(--border);color
<div class="card-h"><h3>Returns — all agents</h3><div><span class="small muted">$10,000 start each</span><div class="rangebar" data-chart-ranges></div></div></div>
<div class="chart-wrap"><svg id="chart2" viewBox="0 0 960 320" preserveAspectRatio="xMidYMid meet"></svg></div>
<div class="legend" id="comboLegend2"></div>
<div class="small muted" style="margin-top:10px">The initial week is an approximate historical replay using prices available on each day and current liquidity as a proxy. Adaptive return continues across builds until the trading strategy itself changes.</div>
<div class="small muted" style="margin-top:10px">Leaderboard rank uses current adaptive-strategy return. The chart and legacy/replay figure preserve older history for context; adaptive return continues across builds until the trading strategy itself changes.</div>
</div>
</section>
@@ -745,7 +745,7 @@ footer{margin-top:34px;padding-top:22px;border-top:1px solid var(--border);color
</section>
<footer>
Build 53 · Adaptive strategy 49 · Paper trading only · Live prices from Polymarket's public Gamma API · Not financial advice ·
Build 55 · Adaptive strategy 49 · 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 = 53;
const BUILD_VERSION = 55;
const SUGGESTION_ENGINE_VERSION = 49;
const PREVIOUS_STRATEGY_VERSION = 48;
const LEGACY_BUILD_STRATEGY_LINEAGE = Object.freeze({40:40,41:40});
@@ -2606,12 +2606,15 @@ function format24h(change){
const value=fmtPct(change.pct);
return change.hours>=20?value:`${value} (${Math.max(1,Math.round(change.hours))}h)`;
}
function rankBoardRows(rows){
return [...rows].sort((a,b)=>(b.engineRet-a.engineRet)||(b.enginePnl-a.enginePnl)||(b.eq-a.eq));
}
function board(){
const st=loadState();
return AGENTS.map(c=>{const p=st.agents[c.id]||defaultPortfolio();const eq=equity(p);
return rankBoardRows(AGENTS.map(c=>{const p=st.agents[c.id]||defaultPortfolio();const eq=equity(p);
const baseline=Number(p.engine_baseline&&normalizedStrategyVersion(p.engine_baseline.version)===SUGGESTION_ENGINE_VERSION?p.engine_baseline.equity:eq);
return {c,p,eq,pnl:eq-STARTING_BALANCE,ret:(eq/STARTING_BALANCE-1)*100,enginePnl:eq-baseline,engineRet:baseline>0?(eq/baseline-1)*100:0,change24h:portfolioChange24h(p,eq)};})
.sort((a,b)=>b.eq-a.eq);
);
}
const MEDALS=["🥇","🥈","🥉"];
@@ -2622,16 +2625,16 @@ function renderOverview(){
const b=board(); const lead=b[0];
const avgRet=b.reduce((s,r)=>s+r.ret,0)/b.length;
const engineAvg=b.reduce((s,r)=>s+r.engineRet,0)/b.length;
const core=b.filter(r=>!r.c.aggressive),coreAvg=core.reduce((s,r)=>s+r.ret,0)/Math.max(1,core.length);
const aggressive=b.filter(r=>r.c.aggressive),aggressiveAvg=aggressive.reduce((s,r)=>s+r.ret,0)/Math.max(1,aggressive.length);
const core=b.filter(r=>!r.c.aggressive),coreAvg=core.reduce((s,r)=>s+r.engineRet,0)/Math.max(1,core.length);
const aggressive=b.filter(r=>r.c.aggressive),aggressiveAvg=aggressive.reduce((s,r)=>s+r.engineRet,0)/Math.max(1,aggressive.length);
const sugCount=(loadSuggestions().suggestions||[]).length;
const stats=[
{ic:lead.c.emoji,label:"Leader",value:lead.c.name.split(" ")[0]},
{ic:"📈",label:"Leader return",value:fmtPct(lead.ret),cls:signClass(lead.pnl)},
{ic:"📈",label:"Leader adaptive",value:fmtPct(lead.engineRet),cls:signClass(lead.enginePnl)},
{ic:"⚖️",label:"Legacy / replay avg",value:fmtPct(avgRet),cls:signClass(avgRet)},
{ic:"🧪",label:"Adaptive avg",value:fmtPct(engineAvg),cls:signClass(engineAvg)},
{ic:"🧠",label:"Core strategy avg",value:fmtPct(coreAvg),cls:signClass(coreAvg)},
{ic:"⚡",label:"Aggressive avg",value:fmtPct(aggressiveAvg),cls:signClass(aggressiveAvg)},
{ic:"🧠",label:"Core adaptive avg",value:fmtPct(coreAvg),cls:signClass(coreAvg)},
{ic:"⚡",label:"Aggressive adaptive",value:fmtPct(aggressiveAvg),cls:signClass(aggressiveAvg)},
{ic:"💡",label:"Scored ideas",value:sugCount},
{ic:"🏆",label:"Agents",value:AGENTS.length},
];
@@ -2774,13 +2777,13 @@ function renderAgentBrief(cfg,p,st){
}
function renderLeaderboard(){
const b=board(); const st=loadState();
$("lbSub").textContent="· "+b[0].c.name+" leads";
$("lbSub").textContent=`· ${b[0].c.name} leads Strategy ${SUGGESTION_ENGINE_VERSION}`;
$("leaderboard").innerHTML=b.map((r,i)=>`
<div class="lb-card" data-agent="${r.c.id}" style="border-top:3px solid ${r.c.color}">
<span class="rankpill">#${i+1}</span>
<div class="lb-top"><span class="medal">${MEDALS[i]||""}</span><span class="lb-emoji">${r.c.emoji}</span>
<div><div class="lb-name">${r.c.name}</div><div class="lb-blurb">${agentBlurb(r.c,st)}</div></div></div>
<div class="lb-mid"><div class="lb-eq">${fmtUSD(r.eq)}</div><div><div class="lb-ret ${signClass(r.pnl)}">${fmtPct(r.ret)}</div><div class="small ${signClass(r.enginePnl)}">adaptive ${fmtPct(r.engineRet)}</div><div class="small ${signClass(r.change24h.pct)}">24h ${format24h(r.change24h)}</div></div></div>
<div class="lb-mid"><div class="lb-eq">${fmtUSD(r.eq)}</div><div><div class="lb-ret ${signClass(r.enginePnl)}">${fmtPct(r.engineRet)}</div><div class="small ${signClass(r.pnl)}">legacy/replay ${fmtPct(r.ret)}</div><div class="small ${signClass(r.change24h.pct)}">24h ${format24h(r.change24h)}</div></div></div>
<div class="lb-foot"><span class="muted small">${r.p.positions.length} open · ${r.p.closed.length} closed</span>${miniSpark(r.p.snapshots,r.c.color)}</div>
</div>`).join("");
document.querySelectorAll("#leaderboard .lb-card").forEach(el=>{
@@ -4472,6 +4475,27 @@ function runEngineSelfTest(){
original_shares:2000,original_cost:1000,unrealized_pnl:0,opened_at:hoursAgo(48),strategy_version:35,quality:"confirmed",signal_type:"trend",price_change_1d:0.06};
riskBook.positions=[riskPos];
normalizeLegacyPositionRisk(riskBook,riskPos,riskCfg,market({id:"risk-test",yes_price:0.5,no_price:0.5,days_to_resolution:5,price_change_1d:0.06}));
const accountingBook=(price=0.5)=>{
const book=defaultPortfolio();book.cash=9000;
const pos={market_id:`accounting-${price}`,question:"Accounting fixture",side:"YES",shares:2000,entry_price:0.5,current_price:price,cost:1000,value:2000*price,
original_shares:2000,original_cost:1000,unrealized_pnl:2000*price-1000,opened_at:hoursAgo(48),strategy_version:SUGGESTION_ENGINE_VERSION,
quality:"confirmed",signal_type:"trend",gain_stops:{},stop_losses:{}};
book.positions=[pos];return {book,pos};
};
const fullCloseFixture=accountingBook(0.6),fullCloseEquity=equity(fullCloseFixture.book);
closePosition(fullCloseFixture.book,fullCloseFixture.pos,"Accounting full close");fullCloseFixture.book.positions=[];
const partialFixture=accountingBook(0.6),partialEquityBefore=equity(partialFixture.book);
takeProfitPosition(partialFixture.book,partialFixture.pos,GAIN_STOP_TIERS[0]);
const partialEquityAfter=equity(partialFixture.book),partialRealizedAfterFirst=partialFixture.pos.partial_realized_pnl;
closePosition(partialFixture.book,partialFixture.pos,"Accounting final close");partialFixture.book.positions=[];
const stopFixture=accountingBook(0.4);
markToMarket(stopFixture.book,{[stopFixture.pos.market_id]:market({id:stopFixture.pos.market_id,yes_price:0.4,no_price:0.6})},AGENTS[0],{policyExits:true,executeTrades:true});
const settlementFixture=accountingBook(1);
markToMarket(settlementFixture.book,{[settlementFixture.pos.market_id]:market({id:settlementFixture.pos.market_id,yes_price:1,no_price:0,closed:true,accepting_orders:false})},AGENTS[0],{policyExits:true,executeTrades:true});
const adaptiveRankFixture=rankBoardRows([
{c:{id:"legacy-leader"},eq:20000,enginePnl:-200,engineRet:-1},
{c:{id:"adaptive-leader"},eq:9000,enginePnl:200,engineRet:2},
]);
const mock={agents:{}};AGENTS.forEach(a=>mock.agents[a.id]=defaultPortfolio());
["value","momentum","favorite"].forEach((id,i)=>mock.agents[id].positions.push({market_id:"overlap-test",question:"Overlap test",side:"YES",shares:1000,current_price:0.5,entry_price:0.5,cost:500,value:500,unrealized_pnl:0,conviction:70-i,opened_at:hoursAgo(24)}));
reduceStrategyOverlap(mock);
@@ -4510,11 +4534,11 @@ function runEngineSelfTest(){
minConv:0,maxNew:1,maxFrac:0.04,reserve:0.10,targetExposure:0.60,learning:buildAdaptiveProfile(staleEntryBook),marketLearning:{samples:0,pending:0,buckets:{}},
},new Set(),{});
const buildMigrationState=defaultState();
buildMigrationState.engine_version=52;buildMigrationState.strategy_version=SUGGESTION_ENGINE_VERSION;
buildMigrationState.engine_version=54;buildMigrationState.strategy_version=SUGGESTION_ENGINE_VERSION;
buildMigrationState.agents.value.engine_baseline={version:SUGGESTION_ENGINE_VERSION,started_at:hoursAgo(2),equity:9876.54};
reconcileStateVersions(buildMigrationState);
const strategyMigrationState=defaultState();
strategyMigrationState.engine_version=52;strategyMigrationState.strategy_version=PREVIOUS_STRATEGY_VERSION;
strategyMigrationState.engine_version=54;strategyMigrationState.strategy_version=PREVIOUS_STRATEGY_VERSION;
strategyMigrationState.agents.value.cash=9876.54;
strategyMigrationState.agents.value.engine_baseline={version:PREVIOUS_STRATEGY_VERSION,started_at:hoursAgo(2),equity:10000};
reconcileStateVersions(strategyMigrationState);
@@ -4578,6 +4602,15 @@ function runEngineSelfTest(){
explainsPathDependentBarrier:pathBarrierContract.rationale.includes("jump directly to settlement"),preservesFixedDateLevel:!fixedDateLevelContract.jump_risk,
preservesOrdinaryDatedContract:!ordinaryDatedContract.jump_risk,boundedStake:boundedStakeForRisk(10000,1000,riskCfg,{entry_price:0.82,days_to_resolution:5,price_change_1d:0.06}),
legacyNormalizedValue:riskPos.value,equityPreserved:+equity(riskBook).toFixed(2),capsBinaryGap:aggressiveGapBudget===0.03&&riskPos.value<=300.01},
accounting:{
fullCloseConservesEquity:Math.abs(fullCloseFixture.book.cash-fullCloseEquity)<=0.02&&fullCloseFixture.pos.realized_pnl===200,
partialSaleConservesEquity:Math.abs(partialEquityAfter-partialEquityBefore)<=0.02&&partialRealizedAfterFirst===50,
partialThenCloseReconciles:partialFixture.book.cash===10200&&partialFixture.pos.realized_pnl===200,
stopOutReconciles:stopFixture.book.positions.length===0&&stopFixture.book.cash===9800&&stopFixture.book.closed[0].realized_pnl===-200,
settlementReconciles:settlementFixture.book.positions.length===0&&settlementFixture.book.cash===11000&&settlementFixture.book.closed[0].realized_pnl===1000,
riskTrimConservesEquity:+equity(riskBook).toFixed(2)===10000,
adaptiveReturnSetsLeader:adaptiveRankFixture[0].c.id==="adaptive-leader",
},
offline:{fresh:offlineCachePolicy(30*60000),staleEntry:offlineCachePolicy(3*3600000),expired:offlineCachePolicy(25*3600000),
staleMarkUpdatesValue:markOnlyBook.positions.length===1&&markOnlyBook.positions[0].value===800,
staleMarkPreservesTrades:markOnlyBook.positions.length===1&&markOnlyBook.positions[0].shares===1000&&markOnlyBook.cash===9500&&markOnlyBook.positions[0].price_status==="cached-mark-only",
+1 -1
View File
@@ -1,4 +1,4 @@
const CACHE_NAME = "polymarket-arena-build-53";
const CACHE_NAME = "polymarket-arena-build-55";
const APP_SHELL = ["/", "/index.html", "/personal.html", "/cycle-worker.js"];
self.addEventListener("install", event => {